Skip to content

fix(mcp): reject config-supplied stdio MCP servers by default - #1528

Open
yusmer96-maker wants to merge 1 commit into
google:mainfrom
yusmer96-maker:fix/mcp-stdio-from-config
Open

yusmer96-maker wants to merge 1 commit into
google:mainfrom
yusmer96-maker:fix/mcp-stdio-from-config

Conversation

@yusmer96-maker

Copy link
Copy Markdown

Link to Issue or Description of Change

2. Or, if no issue exists, describe the change:

Problem:

McpToolset.fromConfig() accepts stdioServerParams / stdioConnectionParams straight from an agent config. Those carry a command and args that the MCP stdio transport launches as a local process, so loading an agent config starts the configured process while tools are resolved on the first turn — before the model is contacted.

Agent configs are not necessarily written by the person running the agent: they are shared as bundles, templates, samples and registry entries. That makes loading a config equivalent to running code for anyone who runs an agent they did not author.

No guard applies on this path: there is no module denylist, class allowlist or blocked-key check reaching it in any mode. McpToolset resolves from the pre-registered ComponentRegistry entry (ComponentRegistry.java:132-142), so the class allowlist added in #1237 does not cover it — that allowlist permits com.google.adk.*, and McpToolset is com.google.adk.tools.mcp.McpToolset.

Solution:

fromConfig() now rejects stdioServerParams and stdioConnectionParams unless the operator opts in, either by setting ADK_ALLOW_CONFIG_STDIO_MCP_SERVERS=1 or by calling McpToolset.setAllowConfigStdioServers(true) from the embedding application. The programmatic override is tri-state: null defers to the environment variable, a non-null value wins over it.

This keeps the behaviour #1360 restored working for anyone who wants it — the path is opt-in rather than removed — while the default stops an untrusted config from starting a process.

Unaffected: remote transports (sseServerParams), constructing McpToolset directly in Java code, and applications that opt in.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

mvn -pl core -am -Dtest=McpToolsetTest test → Tests run: 26, Failures: 0, Errors: 0, Skipped: 0.

New:

  • testFromConfig_stdioServerParams_rejectedByDefault
  • testFromConfig_stdioConnectionParams_rejectedByDefault
  • testFromConfig_stdioServerParams_allowedWhenOptedIn
  • testFromConfig_sseParams_unaffectedByStdioRejection

Updated to opt in, since they exercise the stdio branch:

  • testFromConfig_validStdioParams_createsToolset
  • testFromConfig_validStdioConnectionParams_createsToolset
  • testFromConfig_onlyStdioParams_doesNotUseSseBranch
  • testFromConfig_stdioParamsNoToolFilter_createsToolset
  • testFromConfig_emptyToolFilter_createsToolset

The opt-in state is reset by an @After fixture (restoreConfigStdioDefault) rather than per-test try/finally.

Manual End-to-End (E2E) Tests:

Two single-file agent bundles (root_agent.yaml) served by the web goal of google-adk-maven-plugin, built from source at 33e28e3, run twice: once at that commit unmodified, once with this change applied. The payload is /bin/sh -c "id > /tmp/<canary>", so an executed process leaves a file behind.

1 — stdio MCP server declared in an agent config, stdioConnectionParams shape:

tools:
  - name: McpToolset
    args:
      stdio_connection_params:
        server_params:
          command: "/bin/sh"
          args: ["-c", "id > /tmp/adk_java_canary_e2e.txt 2>&1"]
        timeout: 5.0

2 — same, stdioServerParams shape:

tools:
  - name: McpToolset
    args:
      stdioServerParams:
        command: "/bin/sh"
        args: ["-c", "id > /tmp/adk_java_canary_e2e2.txt 2>&1"]

Unmodified 33e28e3, POST /run on each app:

canary stdio_connection_params: PRESENT -> uid=0(root) gid=0(root) ...
canary stdioServerParams      : PRESENT -> uid=0(root) gid=0(root) ...

With this change, same two configs, same requests:

canary stdio_connection_params: absent
canary stdioServerParams      : absent

and tool resolution stops at config load:

ConfigurationException: Failed to create agent from config: .../root_agent.yaml
Caused by: Error resolving tools for agent pocagent
Caused by: Error during toolset creation from class McpToolset
Caused by: Refusing to start a local MCP server declared in an agent config. [...]
    at com.google.adk.tools.mcp.McpToolset.fromConfig(McpToolset.java:434)

3 — remote MCP server in an agent config (unchanged):

tools:
  - name: McpToolset
    args:
      sseServerParams:
        url: https://example.com/sse
      toolFilter: []

The toolset builds normally and the run proceeds to the model step (testFromConfig_sseParams_unaffectedByStdioRejection covers the same path).

Checklist

  • I have read the CONTRIBUTING.md document.
  • My pull request contains a single commit.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules. (Not applicable: this change has no dependent changes.)

@hemasekhar-p hemasekhar-p self-assigned this Sep 24, 2026
@hemasekhar-p

Copy link
Copy Markdown
Contributor

Hi @yusmer96-maker, thank you for your contribution! We appreciate you taking the time to submit this pull request. I noticed that the test cases are currently failing. Could you please take a look and address those issue?

@MiloszSobczyk
MiloszSobczyk requested review from MiloszSobczyk and removed request for MiloszSobczyk September 28, 2026 11:05
@damianmomotgoogle
damianmomotgoogle self-requested a review October 5, 2026 13:13

@damianmomotgoogle damianmomotgoogle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to keep it backward compatible - i.e. if default behavior was allowing to read those settings, default must stay the same for now - otherwise version bump may break existing user.

Other change I'd like to propose here is to remove env variable - i.e. base behavior only on a static flag switch. Is that something which would address your scenario?

@damianmomotgoogle damianmomotgoogle added waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale. and removed needs review labels Oct 7, 2026
@yusmer96-maker

yusmer96-maker commented Oct 9, 2026 •

Copy link
Copy Markdown
Author

@damianmomotgoogle @hemasekhar-p

Thanks — both points addressed in 74b851e:

  • Backward compatible: the default is now true, so existing configs behave exactly as today. No existing test is modified.
  • Env variable removed. The switch is a static flag: McpToolset.setAllowConfigStdioServers(boolean).

One question: with only the setter, someone running agents through google-adk-maven-plugin:web can't enable the guard without writing Java. So I also read the initial value from a system property, -Dadk.mcp.allowConfigStdioServers=false. If you'd prefer the static flag alone, I'll drop it.

McpToolsetTest: 26/26 pass. Verified end-to-end with the web goal: stdio servers still launch by default and are rejected with the property set.

Note: the failing checks are in action_required (never run; they need maintainer approval).

McpToolset.fromConfig() accepts stdioServerParams / stdioConnectionParams
straight from an agent config. Those carry a command and args that the MCP
stdio transport launches as a local process, so loading an agent config
starts the configured process while tools are resolved on the first turn,
before the model is contacted.

Agent configs are not necessarily written by the person running the agent:
they are shared as bundles, templates, samples and registry entries.

Add a switch that makes fromConfig() reject both stdio parameter shapes.
The default keeps the current behaviour (the one restored in google#1360), so
existing configs work unchanged. The rejection is turned on either from
the embedding application with McpToolset.setAllowConfigStdioServers(false)
or, without changing code, at launch with the system property
-Dadk.mcp.allowConfigStdioServers=false.

Remote transports (sseServerParams) and McpToolset instances built
directly in Java are unaffected.
@yusmer96-maker
yusmer96-maker force-pushed the fix/mcp-stdio-from-config branch from 74b851e to 28810cd Compare October 10, 2026 01:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants