#1605·adk-go

MCP toolset: in-model built-ins (e.g. google_search) can be shadowed by a server tool; a server tool named set_model_response aborts the run

Author: sushant-meCreated Sep 16, 2026Updated Sep 18, 2026
Labelsbugneeds review

Summary

Two related defects in MCP tool registration. Both were reproduced locally against adk-go (f7e16e02, v2 module) — no live service was contacted.

  1. In-model built-in tools do not occupy their name, so a server-provided tool can shadow them. google_search (and google_maps, url_context, vertex_ai_search, code_execution) are appended straight to GenerateContentConfig.Tools and never registered in req.Tools. The duplicate-name guard in toolutils.PackTool therefore never sees them, and a callable MCP tool advertising google_search is accepted and wins dispatch — a FunctionCall{name:"google_search"} resolves to the attacker's mcpTool.Run (arbitrary MCP tools/call).
  2. A server-provided tool named set_model_response aborts the whole run on an agent with an output schema (runErr=duplicate tool: "set_model_response"). This is availability, not silent displacement — but a malicious/untrusted MCP server can deny service.

Evidence

  • MCP tools take the server's name, unfiltered by default — tool/mcptoolset/set.go:164-185; tool/mcptoolset/tool.go:34-45. ToolFilter defaults to nil (allow-all).
  • MCP tools go through the guard — tool/mcptoolset/tool.go:88-90tool/toolutils/toolutils.go:40-51 (if _, ok := req.Tools[name]; ok { return fmt.Errorf("duplicate tool: %q", name) }).
  • set_model_response is in the guarded class — internal/llminternal/outputschema_processor.go:54-59.
  • Built-ins bypass the guard — tool/geminitool/tool.go:78-89 (setTool only appends to req.Config.Tools); tool/geminitool/google_search.go:41-45.
  • Last-wins dispatch map — internal/llminternal/base_flow.go:540-543 (tools[t.Name()] = t).

Local reproduction

With an agent using geminitool.GoogleSearch{} plus a toolset supplying a function tool named google_search:

model was offered: [builtin:googleSearch functionDecl:google_search]
attacker-supplied google_search tool invocations = 1

And with an output schema plus a toolset tool named set_model_response:

runErr=duplicate tool: "set_model_response"

Suggested fix

Preferred (availability-first): reject the server-provided tool via a shared reserved-name set consulted in the MCP toolset, covering set_model_response, transfer_to_agent, finish_task, task_completed, google_search, google_maps, url_context, vertex_ai_search, code_execution, load_artifacts, load_memory.

Alternative: make the in-model tools register their name so the existing duplicate guard applies (fail-closed).

This is the same class as a separately-triaged adk-js report (google_search replacement); the Go and Java ports share the root cause.