From a35284e5de4a4b005f15a3dff1079a5dc24a7700 Mon Sep 17 00:00:00 2001 From: "HenD.YA" Date: Sat, 1 Aug 2026 01:40:57 -0500 Subject: [PATCH 1/2] fix(mcp): reject config-supplied stdio MCP servers by default McpToolset.from_config() passed stdio_server_params.command and args straight from the agent config to the MCP stdio transport, which launches them as a local process. Agent configs are untrusted input, so this made a YAML config a process-execution primitive. Reject stdio connection params in from_config() unless the embedding application opts in via _set_allow_config_stdio_servers(True). Remote transports (SSE, streamable HTTP) are unaffected. --- src/google/adk/tools/mcp_tool/mcp_toolset.py | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/google/adk/tools/mcp_tool/mcp_toolset.py b/src/google/adk/tools/mcp_tool/mcp_toolset.py index e8531fcaa6..cf730463a4 100644 --- a/src/google/adk/tools/mcp_tool/mcp_toolset.py +++ b/src/google/adk/tools/mcp_tool/mcp_toolset.py @@ -61,6 +61,22 @@ logger = logging.getLogger("google_adk." + __name__) +# Whether an agent config may declare a stdio MCP server. Stdio connection +# params carry a `command` and `args` that are launched as a local process, and +# agent configs are untrusted input (CVE-2026-4810), so they are rejected in +# `from_config()` unless the embedding application explicitly opts in. +_ALLOW_CONFIG_STDIO_SERVERS = False + + +def _set_allow_config_stdio_servers(value: bool) -> None: + """Sets whether `McpToolset.from_config()` may build stdio MCP servers. + + Args: + value: True to allow agent configs to launch local MCP servers. + """ + global _ALLOW_CONFIG_STDIO_SERVERS + _ALLOW_CONFIG_STDIO_SERVERS = value + T = TypeVar("T") @@ -515,6 +531,17 @@ def from_config( """Creates an McpToolset from a configuration object.""" mcp_toolset_config = McpToolsetConfig.model_validate(config.model_dump()) + if ( + mcp_toolset_config.stdio_server_params + or mcp_toolset_config.stdio_connection_params + ) and not _ALLOW_CONFIG_STDIO_SERVERS: + raise ValueError( + "Stdio MCP servers are not allowed in agent configs: they launch a" + " local process from a config-supplied 'command'. Build the" + " McpToolset in code, or call _set_allow_config_stdio_servers(True)" + " if this application only loads trusted configs." + ) + if mcp_toolset_config.stdio_server_params: connection_params = mcp_toolset_config.stdio_server_params elif mcp_toolset_config.stdio_connection_params: From 12f75d254aa1fd9e8f30df2b6e953a99321ea190 Mon Sep 17 00:00:00 2001 From: "HenD.YA" Date: Sat, 1 Aug 2026 02:13:39 -0500 Subject: [PATCH 2/2] test(mcp): cover config-supplied stdio MCP server rejection Add tests for from_config() rejecting stdio_server_params and stdio_connection_params by default, allowing them under the _set_allow_config_stdio_servers(True) opt-in, and leaving remote transports (SSE) unaffected. Update the existing credential_key test to opt in for its stdio config. --- .../tools/mcp_tool/test_mcp_toolset.py | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/unittests/tools/mcp_tool/test_mcp_toolset.py b/tests/unittests/tools/mcp_tool/test_mcp_toolset.py index ceff08918a..74a9393a2f 100644 --- a/tests/unittests/tools/mcp_tool/test_mcp_toolset.py +++ b/tests/unittests/tools/mcp_tool/test_mcp_toolset.py @@ -41,6 +41,7 @@ from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams from google.adk.tools.mcp_tool.mcp_tool import MCPTool +from google.adk.tools.mcp_tool.mcp_toolset import _set_allow_config_stdio_servers from google.adk.tools.mcp_tool.mcp_toolset import McpToolset from google.adk.tools.tool_configs import ToolArgsConfig from mcp import StdioServerParameters @@ -254,11 +255,51 @@ def test_from_config_with_credential_key(self): auth_scheme=auth_scheme, credential_key="my_custom_key", ) - toolset = McpToolset.from_config(config, "") + _set_allow_config_stdio_servers(True) + try: + toolset = McpToolset.from_config(config, "") + finally: + _set_allow_config_stdio_servers(False) assert isinstance(toolset._auth_scheme, OAuth2) assert toolset._auth_config.credential_key == "my_custom_key" + def test_from_config_rejects_stdio_server_params(self): + """Config-supplied stdio servers are rejected by default.""" + config = ToolArgsConfig(stdio_server_params=self.mock_stdio_params) + with pytest.raises(ValueError, match="not allowed in agent configs"): + McpToolset.from_config(config, "") + + def test_from_config_rejects_stdio_connection_params(self): + """The stdio_connection_params spelling is rejected the same way.""" + config = ToolArgsConfig( + stdio_connection_params=StdioConnectionParams( + server_params=self.mock_stdio_params + ) + ) + with pytest.raises(ValueError, match="not allowed in agent configs"): + McpToolset.from_config(config, "") + + def test_from_config_allows_stdio_when_opted_in(self): + """The opt-in restores the previous behavior.""" + config = ToolArgsConfig(stdio_server_params=self.mock_stdio_params) + _set_allow_config_stdio_servers(True) + try: + toolset = McpToolset.from_config(config, "") + finally: + _set_allow_config_stdio_servers(False) + assert isinstance(toolset, McpToolset) + + def test_from_config_allows_remote_connection_params(self): + """Remote MCP servers are unaffected: no local process is launched.""" + config = ToolArgsConfig( + sse_connection_params=SseConnectionParams( + url="https://example.com/sse" + ) + ) + toolset = McpToolset.from_config(config, "") + assert isinstance(toolset, McpToolset) + def test_init_missing_connection_params(self): """Test initialization with missing connection params raises error.""" with pytest.raises(ValueError, match="Missing connection params"):