Python: correct MCP tool argument filtering documentation - #7801
Conversation
The documentation for MCPTool's outbound argument filtering did not match its behavior. The comment on _prepare_call_kwargs stated that framework runtime kwargs are "stripped so it is never forwarded to the MCP server", and packages/core/AGENTS.md repeated the same claim. In practice, runtime kwargs (FunctionInvocationContext.kwargs, seeded from function_invocation_kwargs) are merged with the model-supplied arguments in _call_tool_with_runtime_kwargs before the filter runs, so provenance is no longer distinguishable at that point. The allowlist is built from the tool's declared inputSchema.properties as advertised by the server, plus names opted in through additional_tool_argument_names. A runtime kwarg is therefore forwarded whenever the server declares a property of the same name, without the model supplying it. Update the comments, docstrings and docs to describe the actual rule, and point each transport at its appropriate channel for values that should not become tool arguments (env for stdio, header_provider for streamable HTTP). Also narrow the docstring of test_call_tool_forwards_only_declared_arguments, which claimed more than it asserts (it covers undeclared names only), and add a companion test pinning the declared-name behavior so the documented rule stays verifiable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Corrects Python MCP argument-filtering documentation to reflect server-declared schema behavior.
Changes:
- Clarifies MCP argument allowlisting and credential guidance.
- Adds coverage for declared runtime arguments.
- Updates the API-key authentication sample.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
python/samples/02-agents/mcp/mcp_api_key_auth.py |
Expands credential-handling guidance. |
python/packages/core/tests/core/test_mcp.py |
Adds runtime-argument forwarding coverage. |
python/packages/core/AGENTS.md |
Updates MCP architecture guidance. |
python/packages/core/agent_framework/_mcp.py |
Revises MCP filtering docstrings and comments. |
Suppressed comments (3)
python/packages/core/agent_framework/_mcp.py:2861
- This “whenever” statement has exceptions: declared framework-denylisted names are removed unless explicitly opted in, and
_metais never a tool argument. Without that qualification, this constructor docstring still overstates what the server schema can select.
Note: this setting widens the allowlist, it does not bound it. The per-tool
allowlist is built from the server's own advertised ``inputSchema.properties``,
so a runtime keyword argument passed through ``function_invocation_kwargs`` is
forwarded to the server whenever the server declares a property of the same
name - whether or not you listed it here, and without the model having to
python/packages/core/agent_framework/_mcp.py:3081
- This repeats the unconditional forwarding claim, but
_prepare_call_kwargsexcludes framework-denylisted names unless they are explicit extras and always reserves_meta. Document those exceptions so this transport’s public contract matches the filter.
Note: this setting widens the allowlist, it does not bound it. The per-tool
allowlist is built from the server's own advertised ``inputSchema.properties``,
so a runtime keyword argument passed through ``function_invocation_kwargs`` is
forwarded to the server as an ordinary tool argument whenever the server
declares a property of the same name - whether or not you listed it here, and
python/packages/core/agent_framework/_mcp.py:3368
- This unconditional statement also overlooks the framework denylist and reserved
_metabehavior. A matching schema property is insufficient for those names unless a denylisted name is explicitly opted in;_metacan never become an argument.
Note: this setting widens the allowlist, it does not bound it. The per-tool
allowlist is built from the server's own advertised ``inputSchema.properties``,
so a runtime keyword argument passed through ``function_invocation_kwargs`` is
forwarded to the server as an ordinary tool argument whenever the server
declares a property of the same name - whether or not you listed it here, and
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: No findings
Scope: full PR (1 commit(s)): 7c41864c5395
Model: gpt-5.6-sol
Overview
The Python changes accurately expose the existing allowlist behavior and add a regression test for forwarding server-declared names, while the implementation retains denylist, metadata, schema-validation, and origin-scoping guards. The review confirmed concerns in the new header_provider guidance and runtime-path test, but each concern already has an unresolved inline thread with the same trigger and root cause. No additional publishable Critical, High, or Medium issue was established across the full authoritative range.
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
No publishable findings remained after source verification for this scope.
Corrects and tightens the documentation added in the previous commit. - header_provider does not withhold values from the outbound argument filter; it reads the runtime kwargs without consuming them. The earlier wording recommended it as a way to keep a value out of tool arguments, which is wrong. Replaced in four places with the pattern that does work: source the credential outside function_invocation_kwargs, for example by reading a ContextVar inside the provider, which still allows a different value per request. - Note the _meta key and the framework denylist as exceptions wherever the docs say server-declared names are forwarded. - Rework test_call_tool_forwards_runtime_kwargs_the_server_declares to invoke the generated FunctionTool with a FunctionInvocationContext, so it exercises the real runtime-kwargs path instead of calling call_tool directly. Verified by mutation: removing the merge in _call_tool_with_runtime_kwargs now fails the test. - Add a test covering the recommended ContextVar pattern. - Condense the transport docstring notes, which had grown into three near-duplicate blocks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Looks like we should still address Copilot's feedback, Giles Odigwe (@giles17). Aside from that I don't have anything else on my end. |
|
Thanks Evan Mattson (@moonbox3) — all five of Copilot's comments are addressed in c18ab1a and the threads are resolved, with a reply on each explaining what changed. Two of them caught real errors, so a summary of the substantive changes:
I also refreshed the PR description, since two bullets described the pre-correction position, and took a pass to condense the transport docstrings, which had grown into three near-duplicate blocks. One open item: |
Motivation & Context
The documentation describing how
MCPToolfilters arguments before an outboundtools/calldoes not match what the code does, in a way that could lead callersto make incorrect assumptions about where values they pass at runtime end up.
The comment on
_prepare_call_kwargsstated that framework runtime kwargs are"stripped so it is never forwarded to the MCP server", and
packages/core/AGENTS.mdrepeated the same claim.The actual behavior is narrower.
_call_tool_with_runtime_kwargsmerges theruntime kwargs (
FunctionInvocationContext.kwargs, seeded fromfunction_invocation_kwargs) with the model-supplied arguments into a singleflat dict before the filter runs, so by the time
_prepare_call_kwargsseesthem the two are no longer distinguishable. The allowlist it filters against is
the tool's declared
inputSchema.properties— as advertised by the server —plus any names opted in through
additional_tool_argument_names. A runtimekwarg is therefore forwarded whenever the server declares a property of the same
name, without the model supplying it and without the caller listing it. The
_metakey and the framework denylist names are the exceptions, dropped evenwhen declared.
That distinction matters because
function_invocation_kwargsis documented asthe channel for values that stay out of the model's context, and is scoped to
the run rather than to a single server, so the same dict is shared with every
MCPToolattached to the agent. Callers choosing what to put in it should beable to rely on the documentation to know what reaches a given server.
Description & Review Guide
What are the major changes?
_prepare_call_kwargscomment to describe the actual rule:the merge happens upstream, provenance is not available at the filter, and
the declared half of the allowlist comes from the server's advertised
schema. Also clarified that the existing "no per-call override" property
constrains what a model can widen, which is a separate question from what
the schema declares.
additional_tool_argument_namesdocstrings on all threetransports to describe the setting as widening the allowlist rather than
bounding it, and to name the
_meta/denylist exceptions.call_tooldocstring, whichpreviously said only "Remaining arguments to pass to the tool".
packages/core/AGENTS.md, andnarrowed the description of
_MCP_FRAMEWORK_DENYLISTso it is not read as ageneral-purpose guard — it covers a fixed set of non-serializable framework
objects and does not generalize to arbitrary caller-chosen names.
header_providerreads the runtime kwargs withoutconsuming them, so reading a value there does not withhold it from the
outbound argument filter. The guidance instead points at the axis that
actually decides the outcome: where the value is sourced from. Sourcing it
outside
function_invocation_kwargs— for example by reading aContextVarinside the provider — keeps it out of tool arguments while still allowing a
different value per request.
mcp_api_key_auth.pysample accordingly, since it is thepattern most likely to be copied.
test_call_tool_forwards_only_declared_arguments,which claimed framework runtime kwargs are stripped while only asserting
that undeclared ones are, and added
test_call_tool_forwards_runtime_kwargs_the_server_declarespinning thedeclared-name behavior through the real runtime-kwargs path. A second test
covers the
ContextVarpattern the guidance recommends, so the advice isheld by a test rather than by prose alone.
What is the impact of these changes?
No behavior change. Everything here is comments, docstrings,
AGENTS.md, asample note, and tests. The new tests document existing behavior rather than
changing it, so that the guidance stays verifiable and any future change to
this path surfaces as a test failure instead of silent documentation drift.
What do you want reviewers to focus on?
Whether the guidance lands in the places a caller would actually look before
deciding what to pass through
function_invocation_kwargs, and whether theper-transport advice is right in each case, given that only
MCPStreamableHTTPToolhas aheader_providerhook,MCPStdioToolhasenv,and
MCPWebsocketToolhas neither.Related Issue
Fixes #
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.