Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions core/src/main/java/com/google/adk/tools/mcp/McpToolset.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ public class McpToolset implements BaseToolset {
private static final long RETRY_DELAY_MILLIS = 100;
protected static final Class<? extends McpToolsetConfig> CONFIG_TYPE = McpToolsetConfig.class;

/**
* System property that lets an operator forbid agent configs from declaring local (stdio) MCP
* servers without modifying the embedding application. Setting it to {@code false} turns the
* rejection on; any other value leaves the historical behaviour in place.
*/
private static final String ALLOW_CONFIG_STDIO_PROPERTY = "adk.mcp.allowConfigStdioServers";

/**
* Whether an agent config may declare a local (stdio) MCP server. Defaults to {@code true}, the
* behaviour restored in #1360, so existing configs keep working unchanged. It can be turned off
* at launch with {@code -Dadk.mcp.allowConfigStdioServers=false}, or at runtime with {@link
* #setAllowConfigStdioServers}.
*/
private static volatile boolean allowConfigStdioServers =
!"false".equalsIgnoreCase(System.getProperty(ALLOW_CONFIG_STDIO_PROPERTY));

/**
* Initializes the McpToolset with SSE server parameters.
*
Expand Down Expand Up @@ -416,6 +432,19 @@ public static McpToolset fromConfig(BaseTool.ToolConfig config, String configAbs
+ " for McpToolset");
}

if ((mcpToolsetConfig.stdioServerParams() != null
|| mcpToolsetConfig.stdioConnectionParams() != null)
&& !allowConfigStdioServers) {
throw new ConfigurationException(
"Refusing to start a local MCP server declared in an agent config."
+ " stdioServerParams and stdioConnectionParams launch the config-supplied command"
+ " as a local process while tools are resolved, before the model is contacted."
+ " This rejection is disabled by default; it is active because"
+ " McpToolset.setAllowConfigStdioServers(false) was called or -D"
+ ALLOW_CONFIG_STDIO_PROPERTY
+ "=false was set. Remote transports (sseServerParams) are unaffected.");
}

List<String> toolNames = mcpToolsetConfig.toolFilter();
Object connectionParameters = resolveConnectionParameters(mcpToolsetConfig);

Expand All @@ -430,6 +459,16 @@ public static McpToolset fromConfig(BaseTool.ToolConfig config, String configAbs
}
}

/**
* Allows or forbids agent configs to declare local (stdio) MCP servers. The default is {@code
* true}, matching the behaviour of previous releases. Applications that load agent configs they
* did not author call this with {@code false} at startup, so a config cannot launch a local
* process during tool resolution.
*/
public static void setAllowConfigStdioServers(boolean allow) {
allowConfigStdioServers = allow;
}

/**
* Resolves the single connection-parameters object from an already-validated config. {@code
* stdioServerParams} is converted to the MCP SDK {@link ServerParameters}, the type {@link
Expand Down
69 changes: 69 additions & 0 deletions core/src/test/java/com/google/adk/tools/mcp/McpToolsetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.spec.McpSchema;
import java.util.List;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -52,6 +53,11 @@ public class McpToolsetTest {
@Mock private McpSyncClient mockMcpSyncClient;
@Mock private ReadonlyContext mockReadonlyContext;

@After
public void restoreConfigStdioDefault() {
McpToolset.setAllowConfigStdioServers(true);
}

private static final McpJsonMapper jsonMapper = McpJsonDefaults.getMapper();

private static final ImmutableMap<String, Object> STDIO_SERVER_PARAMS =
Expand Down Expand Up @@ -211,6 +217,69 @@ public void testFromConfig_validStdioConnectionParams_createsToolset()
assertThat(toolset).isNotNull();
}

@Test
public void testFromConfig_stdioServerParams_rejectedWhenDisallowed() {
McpToolset.setAllowConfigStdioServers(false);
BaseTool.ToolArgsConfig args = new BaseTool.ToolArgsConfig();
args.put("stdioServerParams", STDIO_SERVER_PARAMS);

BaseTool.ToolConfig config = new BaseTool.ToolConfig("mcp_toolset", args);
String configPath = "/path/to/config.yaml";

ConfigurationException exception =
assertThrows(ConfigurationException.class, () -> McpToolset.fromConfig(config, configPath));

assertThat(exception)
.hasMessageThat()
.contains("Refusing to start a local MCP server declared in an agent config");
}

@Test
public void testFromConfig_stdioConnectionParams_rejectedWhenDisallowed() {
McpToolset.setAllowConfigStdioServers(false);
BaseTool.ToolArgsConfig args = new BaseTool.ToolArgsConfig();
args.put(
"stdioConnectionParams",
ImmutableMap.of("timeout", 10f, "serverParams", STDIO_SERVER_PARAMS));

BaseTool.ToolConfig config = new BaseTool.ToolConfig("mcp_toolset", args);
String configPath = "/path/to/config.yaml";

ConfigurationException exception =
assertThrows(ConfigurationException.class, () -> McpToolset.fromConfig(config, configPath));

assertThat(exception)
.hasMessageThat()
.contains("Refusing to start a local MCP server declared in an agent config");
}

@Test
public void testFromConfig_stdioServerParams_allowedByDefault() throws ConfigurationException {
BaseTool.ToolArgsConfig args = new BaseTool.ToolArgsConfig();
args.put("stdioServerParams", STDIO_SERVER_PARAMS);

BaseTool.ToolConfig config = new BaseTool.ToolConfig("mcp_toolset", args);
String configPath = "/path/to/config.yaml";

McpToolset toolset = McpToolset.fromConfig(config, configPath);

assertThat(toolset).isNotNull();
}

@Test
public void testFromConfig_sseParams_unaffectedByStdioRejection() throws ConfigurationException {
McpToolset.setAllowConfigStdioServers(false);
BaseTool.ToolArgsConfig args = new BaseTool.ToolArgsConfig();
args.put("sseServerParams", ImmutableMap.of("url", "http://localhost:8080"));

BaseTool.ToolConfig config = new BaseTool.ToolConfig("mcp_toolset", args);
String configPath = "/path/to/config.yaml";

McpToolset toolset = McpToolset.fromConfig(config, configPath);

assertThat(toolset).isNotNull();
}

@Test
public void testFromConfig_onlySseParams_doesNotUseStdioBranch() throws ConfigurationException {
// This test ensures that when only SSE params are provided, the SSE branch is taken
Expand Down
Loading