Repository navigation
Conversation
An openapi block can fix an argument with params; an mcp block could only pick tools with allow. A tool may take an argument that bounds what a call reaches, and the only way to hold the agent to it was the instruction, which nothing enforces when the model leaves it out. params on an mcp block names tools out of allow and fixes argument values, mirroring openapi's value. The fixed arguments are taken out of the schema the model is shown, and each call carries the declared values over anything the model wrote. validate cannot see a server's schemas, so the tool names are held against allow, which is also why params needs allow: with every tool offered, a misspelt name would fix nothing and nobody would know. Each fixed argument is checked against the listed schema at run time instead; a tool whose schema no longer declares one is not offered and the log says why, since the value would otherwise ride under a name the server ignores. The tool ADK listed is narrowed on a copy, because the listing may be cached and built into tools again on the next turn. 🤖 Generated with Claude Code
The tools are listed for every model request, and a server whose schema no longer declares a fixed argument stays that way until someone changes the declaration or the server. Each listing logged the same warning again, so one drift filled the log a line per turn and buried everything else the agent wrote there. The toolset now remembers what it has warned about and says each thing once. The tool is still kept away on every listing. 🤖 Generated with Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
An
mcp:block can choose which tools to offer withallow, but not constrain their arguments. A tool may take an argument that bounds what a call reaches. The only way to hold an agent to such a bound was to ask in the instruction, and nothing stops a call in which the model leaves it out. Anopenapi:block already hasparamsfor this.What changes
paramson anmcp:block, keyed by tool name and then argument name, with the samevalueform asopenapi::Only
valueis offered;prefix/suffixare not, since nothing has needed them for MCP tools yet.nullis refused, as foropenapi:: a server reads it as no value, so nothing would be fixed.What the model sees. A fixed argument is removed from the tool's
inputSchema(propertiesandrequired) before ADK builds the function declaration from it.What is sent. Every call carries the declared values over whatever the model wrote under the same names, so a smuggled value is overwritten, never sent.
validate refuses a
paramstool name that is not inallow, and refusesparamson a block withoutallow: with every tool offered, a misspelt name would fix nothing and the tool it meant would be offered as the server lists it, with nobody told. The same check runs again when the runtime builds the toolset, since the archive that reached it is whatever was packed.Checked where the tools are listed. validate cannot see a server's schemas; they are listed while the agent runs. So each fixed argument is checked against the listed
inputSchemathere. A tool whose schema does not declare one (renamed on the server, say) is not offered, and a warning names the tool and the argument, never the value. The tools are listed for every model request, so each warning is logged once per toolset rather than on every turn. Offering it would send the value under a name the server ignores, and the call would run unbounded. Raising instead would take every other tool of the agent down with it on every turn.Implementation. The listed
McpToolis replaced by a subclass built from its state rather than throughMcpTool.__init__, whose parameters change between ADK releases; that keeps the session, headers and confirmation setting it was built with, andisinstance(tool, McpTool)still holds. The narrowed schema goes on a copy of the MCP tool model, because ADK may cache a listing and build tools from it again on the next turn. ADK 2.6.0 and 2.14.0 both build the declaration from_mcp_tool's input schema and passrun_async'sargstocall_tool. The schema field is read asinputSchemaorinput_schema, since ADK 2.14 allows MCP SDK 2.x, which renames it.README documents
paramsformcp:blocks next toallow.Compatibility
Additive. Blocks without
paramsbuild the same toolset. An agent that uses it has to be archived with a gete that includes this change.Testing
McpToolover MCP SDKToolmodels, no server): the fixed argument is gone from the declaration's properties and required list; a tool whose schema lacks it is not offered, the log names the tool and argument but not the value, and only once however often the tools are listed; the listed MCP tool is left as the server described it, and a second listing from the same objects still fixes the argument; a toolparamsdoes not name is offered as listed;does_notand per-name confirmation still reach a fixed tool; the build refuses aparamsname outsideallow.lookuptool now takes an optionalscopeand echoes its arguments): the declaration the model gets from the real listing has noscope, and a call whose arguments sayscope: everywherereaches the server with the declared value.paramsname outsideallowandparamswithoutalloware refused; the first also throughgete validate, which prefixes the tool's position.valueform passes; an empty mapping, a bare value,{},null,prefixandvaluewithsuffixare refused.pytest,ruff check,ruff format --checkandmypypass locally.