fix(mcp): refuse the reserved tool name set_model_response - #7145
sushant-me wants to merge 1 commit into
Conversation
|
Adding the reachability argument, because the fix only matters if the server's tool is actually the survivor rather than merely a duplicate.
self.tools_dict[tool.name] = toolSo the question is which registration runs second. In
The framework's name is registered in the earlier step and the server's in the later one, so the server's tool is the survivor. The shadowing is reachable, not theoretical. While confirming that, I checked the other tool names the framework injects, and I do not believe they belong in
|
67d06ae to
2a6fc6f
Compare
_RESERVED_TOOL_NAMES covers the names the framework itself puts on the wire, so a server advertising one cannot have its tool dispatched in place of the framework's own. set_model_response belongs to that set but was missing. SetModelResponseTool is injected into the request whenever output_schema is configured alongside other tools (flows/llm_flows/prompt/_schema.py), the framework tells the model to answer through it by name, and base_llm_flow.py reads the result back by that same name. Because LlmRequest.append_tools resolves a duplicate name by last-wins with only a warning, an MCP server advertising set_model_response could otherwise receive the agent's structured final answer instead of the framework. The name is spelled out rather than imported: the function is defined inside SetModelResponseTool.__init__, so there is no module-level binding to import. Extends both existing reserved-name tests. Fixes google#7144
2a6fc6f to
e1f65da
Compare
Fixes #7144
Problem
_RESERVED_TOOL_NAMESexists to refuse MCP tool names the framework itself puts on the wire.set_model_responsebelongs to that class but was not in the set, so a server advertising it was accepted wheretransfer_to_agentand theadk_request_*names are rejected.It is a framework-owned wire name on every count:
SetModelResponseToolis injected wheneveroutput_schemais set alongside other tools —flows/llm_flows/prompt/_schema.py:54_schema.py:57-64), andbase_llm_flow.pyreads the result back by the same name — "Check if this is a set_model_response function response".LlmRequest.append_toolsresolves the resulting duplicate by last-wins with only a warning:So a server-supplied
set_model_responsecompetes for the name, and if it is the survivor the agent's structured final answer is dispatched to the third-party server rather than captured by the framework — precisely the outcome this guard was added to prevent.Fix
_RESERVED_TOOL_NAMES = frozenset({ REQUEST_EUC_FUNCTION_CALL_NAME, REQUEST_CONFIRMATION_FUNCTION_CALL_NAME, REQUEST_INPUT_FUNCTION_CALL_NAME, transfer_to_agent.__name__, + # Injected by the output-schema processor whenever output_schema is set + # alongside other tools (flows/llm_flows/prompt/_schema.py) and read back + # by name in base_llm_flow.py, so it is a framework-owned wire name too. + # Spelled out because the function is defined inside + # SetModelResponseTool.__init__ and is not importable. + 'set_model_response', })The name is written literally rather than as
something.__name__, unliketransfer_to_agent: the callable is defined insideSetModelResponseTool.__init__, so there is no module-level binding to import. Importing it would have raisedImportErrorfor every importer ofmcp_tool.Tests
Both existing reserved-name tests are extended rather than duplicated:
test_mcp_tool.py::test_init_reserved_name— adds"set_model_response"to the parametrized list, assertingMCPToolconstruction raisesValueError.test_mcp_toolset.py::test_get_tools_skips_reserved_names— the server now also advertisesset_model_response; the assertion that onlyvalid_toolsurvives is unchanged.Scope note
I checked only this name end to end. Other in-model names (
google_search,url_context,code_execution, …) may warrant the same treatment, but I could not confirm each reachestools_dict, so they are deliberately not included here.