fix(mcp): prevent tool-call arguments from overriding MCP routing - #589
Open
sainikhiljuluri wants to merge 1 commit into
Open
fix(mcp): prevent tool-call arguments from overriding MCP routing#589sainikhiljuluri wants to merge 1 commit into
sainikhiljuluri wants to merge 1 commit into
Conversation
The MCP tool wrapper bound its remote routing target via a default argument (`_invoke(_remote=remote, **kwargs)`). That defeats closure late-binding, but it also makes `_remote` a caller-overridable keyword. Because the registry forwards the model's raw tool-call arguments to the wrapper unchanged, a tool call whose arguments include a `_remote` key re-routes to a different tool on the same server than the one the approval gate and the include/exclude filter vetted (both key off the visible tool name). Bind `remote` in a dedicated closure instead, so it is no longer a bindable parameter; a stray `_remote` argument is now forwarded to the remote tool as an ordinary argument and cannot change the routing target. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Summary
The MCP tool wrapper bound its remote routing target through a default argument, which made that target overridable by the model-supplied tool-call arguments. A tool call whose arguments contain a
_remotekey would run a different tool on the same server than the one the approval gate and the include/exclude filter actually vetted. This hardens the wrapper so the routing target is fixed at wrap time and cannot be redirected by call arguments.Details
build_callableswraps each MCP tool as:The default-argument form is used to defeat closure late-binding, but it also exposes
_remoteas a normal, caller-overridable keyword. The registry forwards the model's raw tool-call arguments to the wrapper unchanged (spec.func(**(arguments or {}))), and_remoteis not part of the model-facing schema — so if the arguments dict happens to carry a_remotekey, Python binds it andcall_asyncis invoked with a caller-chosen tool name.That matters because the approval/reviewer decision (
PermissionEngine.evaluate), the per-tool read/write classification for connector-backed servers, and theinclude_tools/exclude_toolsfilter all key off the visible tool name (mcp__<server>__<tool>). The gate vets one tool; a_remoteoverride runs another on the same server — including a tool the include/exclude filter was meant to hide. This is reachable without a compromised machine: a prompt-injection payload in content the agent is processing, or a malicious/compromised MCP server steering the model, is enough to get a_remotekey into the arguments.Fix
Bind
remotein a dedicated closure (_make_invoke) instead of a default argument. This still defeats late-binding, but_remoteis no longer a bindable parameter, so a stray_remoteargument is simply forwarded to the remote tool as an ordinary argument and cannot change the routing target. One function, no signature or schema change for callers.Test plan
Added
tests/test_mcp.py::test_remote_tool_routing_is_not_overridable_by_arguments: it wraps a singleread_filetool and invokes it with_remote="delete_file"in the arguments, asserting the bridge still routes toread_file. Verified it fails onmain(routes todelete_file) and passes with this change.Risk / backward compatibility
_remotekey in call arguments no longer redirects routing.