Skip to content

Commit b0d8345

Browse files
wanlin31copybara-github
authored andcommitted
feat: enable server side MCP and disable all other AFC when server side MCP is configured.
PiperOrigin-RevId: 807887769
1 parent 2665746 commit b0d8345

11 files changed

Lines changed: 343 additions & 11 deletions

google/genai/_extra_utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,12 @@ def find_afc_incompatible_tool_indexes(
141141
return incompatible_tools_indexes
142142

143143
for index, tool in enumerate(config_model.tools):
144-
if isinstance(tool, types.Tool) and tool.function_declarations:
144+
if not isinstance(tool, types.Tool):
145+
continue
146+
if tool.function_declarations:
147+
incompatible_tools_indexes.append(index)
148+
if tool.mcp_servers:
145149
incompatible_tools_indexes.append(index)
146-
147150
return incompatible_tools_indexes
148151

149152

google/genai/_live_converters.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,6 +1397,13 @@ def _Tool_to_mldev(
13971397
if getv(from_object, ['url_context']) is not None:
13981398
setv(to_object, ['urlContext'], getv(from_object, ['url_context']))
13991399

1400+
if getv(from_object, ['mcp_servers']) is not None:
1401+
setv(
1402+
to_object,
1403+
['mcpServers'],
1404+
[item for item in getv(from_object, ['mcp_servers'])],
1405+
)
1406+
14001407
return to_object
14011408

14021409

@@ -1450,6 +1457,9 @@ def _Tool_to_vertex(
14501457
if getv(from_object, ['url_context']) is not None:
14511458
setv(to_object, ['urlContext'], getv(from_object, ['url_context']))
14521459

1460+
if getv(from_object, ['mcp_servers']) is not None:
1461+
raise ValueError('mcp_servers parameter is not supported in Vertex AI.')
1462+
14531463
return to_object
14541464

14551465

google/genai/_tokens_converters.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,4 +522,11 @@ def _Tool_to_mldev(
522522
if getv(from_object, ['url_context']) is not None:
523523
setv(to_object, ['urlContext'], getv(from_object, ['url_context']))
524524

525+
if getv(from_object, ['mcp_servers']) is not None:
526+
setv(
527+
to_object,
528+
['mcpServers'],
529+
[item for item in getv(from_object, ['mcp_servers'])],
530+
)
531+
525532
return to_object

google/genai/batches.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,13 @@ def _Tool_to_mldev(
14981498
if getv(from_object, ['url_context']) is not None:
14991499
setv(to_object, ['urlContext'], getv(from_object, ['url_context']))
15001500

1501+
if getv(from_object, ['mcp_servers']) is not None:
1502+
setv(
1503+
to_object,
1504+
['mcpServers'],
1505+
[item for item in getv(from_object, ['mcp_servers'])],
1506+
)
1507+
15011508
return to_object
15021509

15031510

google/genai/caches.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,13 @@ def _Tool_to_mldev(
704704
if getv(from_object, ['url_context']) is not None:
705705
setv(to_object, ['urlContext'], getv(from_object, ['url_context']))
706706

707+
if getv(from_object, ['mcp_servers']) is not None:
708+
setv(
709+
to_object,
710+
['mcpServers'],
711+
[item for item in getv(from_object, ['mcp_servers'])],
712+
)
713+
707714
return to_object
708715

709716

@@ -757,6 +764,9 @@ def _Tool_to_vertex(
757764
if getv(from_object, ['url_context']) is not None:
758765
setv(to_object, ['urlContext'], getv(from_object, ['url_context']))
759766

767+
if getv(from_object, ['mcp_servers']) is not None:
768+
raise ValueError('mcp_servers parameter is not supported in Vertex AI.')
769+
760770
return to_object
761771

762772

google/genai/models.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3770,6 +3770,13 @@ def _Tool_to_mldev(
37703770
if getv(from_object, ['url_context']) is not None:
37713771
setv(to_object, ['urlContext'], getv(from_object, ['url_context']))
37723772

3773+
if getv(from_object, ['mcp_servers']) is not None:
3774+
setv(
3775+
to_object,
3776+
['mcpServers'],
3777+
[item for item in getv(from_object, ['mcp_servers'])],
3778+
)
3779+
37733780
return to_object
37743781

37753782

@@ -3824,6 +3831,9 @@ def _Tool_to_vertex(
38243831
if getv(from_object, ['url_context']) is not None:
38253832
setv(to_object, ['urlContext'], getv(from_object, ['url_context']))
38263833

3834+
if getv(from_object, ['mcp_servers']) is not None:
3835+
raise ValueError('mcp_servers parameter is not supported in Vertex AI.')
3836+
38273837
return to_object
38283838

38293839

@@ -5600,7 +5610,7 @@ def generate_content(
56005610
'Tools at indices [%s] are not compatible with automatic function '
56015611
'calling (AFC). AFC is disabled. If AFC is intended, please '
56025612
'include python callables in the tool list, and do not include '
5603-
'function declaration in the tool list.',
5613+
'function declaration and MCP server in the tool list.',
56045614
indices_str,
56055615
)
56065616
return self._generate_content(
@@ -7469,7 +7479,7 @@ async def generate_content(
74697479
'Tools at indices [%s] are not compatible with automatic function '
74707480
'calling (AFC). AFC is disabled. If AFC is intended, please '
74717481
'include python callables in the tool list, and do not include '
7472-
'function declaration in the tool list.',
7482+
'function declaration and MCP server in the tool list.',
74737483
indices_str,
74747484
)
74757485
return await self._generate_content(
@@ -7633,7 +7643,7 @@ async def base_async_generator(model, contents, config): # type: ignore[no-unty
76337643
'Tools at indices [%s] are not compatible with automatic function '
76347644
'calling (AFC). AFC is disabled. If AFC is intended, please '
76357645
'include python callables in the tool list, and do not include '
7636-
'function declaration in the tool list.',
7646+
'function declaration and MCP server in the tool list.',
76377647
indices_str,
76387648
)
76397649
response = await self._generate_content_stream(

google/genai/tests/afc/test_find_afc_incompatible_tool_indexes.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def test_empty_tools_list_returns_empty_list():
8383

8484
def test_all_compatible_tools_returns_empty_list_with_empty_fd():
8585
"""Verifies that an empty list is returned when all tools are compatible.
86+
8687
A tool is compatible if it's not a `types.Tool` or if its
8788
`function_declarations` attribute is empty or None from config.
8889
"""
@@ -118,6 +119,7 @@ def test_all_compatible_tools_returns_empty_list_with_empty_fd():
118119

119120
def test_all_compatible_tools_returns_empty_list_with_none_fd():
120121
"""Verifies that an empty list is returned when all tools are compatible.
122+
121123
A tool is compatible if it's not a `types.Tool` or if its
122124
`function_declarations` attribute is empty or None from config.
123125
"""
@@ -153,6 +155,7 @@ def test_all_compatible_tools_returns_empty_list_with_none_fd():
153155

154156
def test_all_compatible_tools_returns_empty_list():
155157
"""Verifies that an empty list is returned when all tools are compatible.
158+
156159
A tool is compatible if it's not a `types.Tool` or if its
157160
`function_declarations` attribute is empty or None from config.
158161
"""
@@ -211,8 +214,7 @@ def test_single_incompatible_tool():
211214

212215

213216
def test_multiple_incompatible_tools():
214-
"""Verifies that all correct indexes are returned for multiple incompatible
215-
tools. """
217+
"""Verifies correct indexes are returned for multiple incompatible tools."""
216218
result = find_afc_incompatible_tool_indexes(
217219
config=types.GenerateContentConfig(
218220
tools=[
@@ -238,3 +240,29 @@ def test_multiple_incompatible_tools():
238240
)
239241
)
240242
assert result == [2, 5]
243+
244+
def test_mcp_tool_incompatible():
245+
"""Verifies correct indexes are returned for multiple incompatible tools."""
246+
result = find_afc_incompatible_tool_indexes(
247+
config=types.GenerateContentConfig(
248+
tools=[
249+
types.Tool(
250+
google_search_retrieval=types.GoogleSearchRetrieval()
251+
),
252+
types.Tool(retrieval=types.Retrieval()),
253+
types.Tool(
254+
function_declarations=[
255+
types.FunctionDeclaration(name='test_function')
256+
]
257+
),
258+
types.Tool(code_execution=types.ToolCodeExecution()),
259+
260+
get_weather_tool,
261+
mcp_to_genai_tool_adapter,
262+
types.Tool(
263+
mcp_servers=[types.McpServer(name='test_mcp_server')]
264+
),
265+
]
266+
)
267+
)
268+
assert result == [2, 6]

google/genai/tests/chats/test_send_message.py

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -768,9 +768,8 @@ def test_mcp_tools(client):
768768
)
769769
],},
770770
)
771-
response = chat.send_message('What is the weather in Boston?');
772-
response = chat.send_message('What is the weather in San Francisco?');
773-
771+
response = chat.send_message('What is the weather in Boston?')
772+
response = chat.send_message('What is the weather in San Francisco?')
774773

775774

776775
def test_mcp_tools_stream(client):
@@ -842,3 +841,101 @@ async def test_async_mcp_tools_stream(client):
842841
'What is the weather in San Francisco?'
843842
):
844843
pass
844+
845+
846+
def test_server_side_mcp_tools(client):
847+
with pytest_helper.exception_if_vertex(client, ValueError):
848+
chat = client.chats.create(
849+
model='gemini-2.5-flash',
850+
config={
851+
'tools': [
852+
{
853+
'mcp_servers': [
854+
{
855+
'name': 'weather_server',
856+
'streamable_http_transport': {
857+
'url': (
858+
'https://gemini-api-demos.uc.r.appspot.com/mcp'
859+
),
860+
'headers': {
861+
'AUTHORIZATION': 'Bearer github_pat_XXXX',
862+
},
863+
'timeout': '10s',
864+
},
865+
},
866+
],
867+
},
868+
],
869+
},
870+
)
871+
response = chat.send_message('What is the weather in Boston on 02/02/2026?')
872+
response = chat.send_message(
873+
'What is the weather in San Francisco on 02/02/2026?'
874+
)
875+
876+
877+
def test_server_side_mcp_tools_stream(client):
878+
with pytest_helper.exception_if_vertex(client, ValueError):
879+
chat = client.chats.create(
880+
model='gemini-2.5-flash',
881+
config={
882+
'tools': [
883+
{
884+
'mcp_servers': [
885+
{
886+
'name': 'weather_server',
887+
'streamable_http_transport': {
888+
'url': (
889+
'https://gemini-api-demos.uc.r.appspot.com/mcp'
890+
),
891+
'headers': {
892+
'AUTHORIZATION': 'Bearer github_pat_XXXX',
893+
},
894+
'timeout': '10s',
895+
},
896+
},
897+
],
898+
},
899+
],
900+
},
901+
)
902+
for chunk in chat.send_message_stream(
903+
'What is the weather in Boston on 02/02/2026?'
904+
):
905+
pass
906+
for chunk in chat.send_message_stream(
907+
'What is the weather in San Francisco on 02/02/2026?'
908+
):
909+
pass
910+
911+
912+
@pytest.mark.asyncio
913+
async def test_async_server_side_mcp_tools(client):
914+
with pytest_helper.exception_if_vertex(client, ValueError):
915+
chat = client.aio.chats.create(
916+
model='gemini-2.5-flash',
917+
config={
918+
'tools': [
919+
{
920+
'mcp_servers': [
921+
{
922+
'name': 'weather_server',
923+
'streamable_http_transport': {
924+
'url': (
925+
'https://gemini-api-demos.uc.r.appspot.com/mcp'
926+
),
927+
'headers': {
928+
'AUTHORIZATION': 'Bearer github_pat_XXXX',
929+
},
930+
'timeout': '10s',
931+
},
932+
},
933+
],
934+
},
935+
],
936+
},
937+
)
938+
await chat.send_message('What is the weather in Boston on 02/02/2026?')
939+
await chat.send_message(
940+
'What is the weather in San Francisco on 02/02/2026?'
941+
)

google/genai/tests/models/test_generate_content_tools.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,3 +1759,68 @@ async def test_function_declaration_with_callable_async_stream(client):
17591759
},
17601760
):
17611761
pass
1762+
1763+
def test_server_side_mcp_only(client):
1764+
"""Test server side mcp, happy path."""
1765+
with pytest_helper.exception_if_vertex(client, ValueError):
1766+
response = client.models.generate_content(
1767+
model='gemini-2.5-pro',
1768+
contents=('What is the weather like in New York (NY) on 02/02/2026?'),
1769+
config=types.GenerateContentConfig(
1770+
tools=[types.Tool(
1771+
mcp_servers=[types.McpServer(
1772+
name='get_weather',
1773+
streamable_http_transport=types.StreamableHttpTransport(
1774+
url='https://gemini-api-demos.uc.r.appspot.com/mcp',
1775+
headers={'AUTHORIZATION': 'Bearer github_pat_XXXX'},
1776+
),
1777+
)]
1778+
)]
1779+
)
1780+
)
1781+
assert response.text
1782+
1783+
@pytest.mark.asyncio
1784+
async def test_server_side_mcp_only_async(client):
1785+
"""Test server side mcp, happy path."""
1786+
with pytest_helper.exception_if_vertex(client, ValueError):
1787+
response = await client.aio.models.generate_content(
1788+
model='gemini-2.5-flash',
1789+
contents=(
1790+
'What is the weather like in New York on 02/02/2026?'
1791+
),
1792+
config=types.GenerateContentConfig(
1793+
tools=[types.Tool(
1794+
mcp_servers=[types.McpServer(
1795+
name='get_weather',
1796+
streamable_http_transport=types.StreamableHttpTransport(
1797+
url='https://gemini-api-demos.uc.r.appspot.com/mcp',
1798+
headers={'AUTHORIZATION': 'Bearer github_pat_XXXX'},
1799+
),
1800+
)]
1801+
1802+
)]
1803+
)
1804+
)
1805+
assert response.text
1806+
1807+
def test_server_side_mcp_only_stream(client):
1808+
"""Test server side mcp, happy path."""
1809+
with pytest_helper.exception_if_vertex(client, ValueError):
1810+
response = client.models.generate_content_stream(
1811+
model='gemini-2.5-pro',
1812+
contents=('What is the weather like in New York (NY) on 02/02/2026?'),
1813+
config=types.GenerateContentConfig(
1814+
tools=[types.Tool(
1815+
mcp_servers=[types.McpServer(
1816+
name='get_weather',
1817+
streamable_http_transport=types.StreamableHttpTransport(
1818+
url='https://gemini-api-demos.uc.r.appspot.com/mcp',
1819+
headers={'AUTHORIZATION': 'Bearer github_pat_XXXX'},
1820+
),
1821+
)]
1822+
)]
1823+
)
1824+
)
1825+
for chunk in response:
1826+
pass

0 commit comments

Comments
 (0)