Skip to content

Python: correct MCP tool argument filtering documentation - #7801

Merged
Giles Odigwe (giles17) merged 2 commits into
microsoft:mainfrom
giles17:mcp-kwargs-docs
Aug 21, 2026
Merged

Python: correct MCP tool argument filtering documentation#7801
Giles Odigwe (giles17) merged 2 commits into
microsoft:mainfrom
giles17:mcp-kwargs-docs

Conversation

@giles17

@giles17 Giles Odigwe (giles17) commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Motivation & Context

The documentation describing how MCPTool filters arguments before an outbound
tools/call does not match what the code does, in a way that could lead callers
to make incorrect assumptions about where values they pass at runtime end up.

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.

The actual behavior is narrower. _call_tool_with_runtime_kwargs merges the
runtime kwargs (FunctionInvocationContext.kwargs, seeded from
function_invocation_kwargs) with the model-supplied arguments into a single
flat dict before the filter runs, so by the time _prepare_call_kwargs sees
them 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 runtime
kwarg 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
_meta key and the framework denylist names are the exceptions, dropped even
when declared.

That distinction matters because function_invocation_kwargs is documented as
the 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
MCPTool attached to the agent. Callers choosing what to put in it should be
able to rely on the documentation to know what reaches a given server.

Description & Review Guide

  • What are the major changes?

    • Rewrote the _prepare_call_kwargs comment 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.
    • Updated the additional_tool_argument_names docstrings on all three
      transports to describe the setting as widening the allowlist rather than
      bounding it, and to name the _meta/denylist exceptions.
    • Documented the allowlist on the base call_tool docstring, which
      previously said only "Remaining arguments to pass to the tool".
    • Corrected the corresponding bullets in packages/core/AGENTS.md, and
      narrowed the description of _MCP_FRAMEWORK_DENYLIST so it is not read as a
      general-purpose guard — it covers a fixed set of non-serializable framework
      objects and does not generalize to arbitrary caller-chosen names.
    • Documented that header_provider reads the runtime kwargs without
      consuming 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 a ContextVar
      inside the provider — keeps it out of tool arguments while still allowing a
      different value per request.
    • Updated the mcp_api_key_auth.py sample accordingly, since it is the
      pattern most likely to be copied.
    • Narrowed the docstring of 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_declares pinning the
      declared-name behavior through the real runtime-kwargs path. A second test
      covers the ContextVar pattern the guidance recommends, so the advice is
      held 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, a
    sample 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 the
    per-transport advice is right in each case, given that only
    MCPStreamableHTTPTool has a header_provider hook, MCPStdioTool has env,
    and MCPWebsocketTool has neither.

Related Issue

Fixes #

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

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>
Copilot AI balanced review requested due to automatic review settings August 20, 2026 19:01
@agent-framework-automation agent-framework-automation Bot added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Aug 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 _meta is 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_kwargs excludes 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 _meta behavior. A matching schema property is insufficient for those names unless a denylisted name is explicitly opted in; _meta can 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.

Comment thread python/packages/core/agent_framework/_mcp.py Outdated
Comment thread python/packages/core/agent_framework/_mcp.py Outdated
Comment thread python/samples/02-agents/mcp/mcp_api_key_auth.py Outdated
Comment thread python/packages/core/AGENTS.md Outdated
Comment thread python/packages/core/tests/core/test_mcp.py Outdated
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   _mcp.py139211591%254, 260, 369, 388, 609, 688–689, 803, 828, 879, 994, 997, 1007, 1011, 1058–1059, 1064, 1071–1072, 1079, 1084–1085, 1092–1093, 1097, 1102–1103, 1112, 1119–1120, 1138, 1151, 1175–1176, 1195–1198, 1200–1201, 1205, 1231, 1265–1267, 1269, 1323–1325, 1384–1385, 1668, 1709–1710, 1723, 1726, 1735–1736, 1741–1742, 1748, 1802–1803, 1823–1824, 1833–1834, 1839–1840, 1846, 1939, 1942, 1969, 1992–1996, 2019–2021, 2026, 2030–2031, 2138, 2145, 2147, 2223, 2238–2239, 2246–2247, 2252–2253, 2258, 2262, 2277, 2339, 2522, 2524, 2546, 2548–2551, 2564–2565, 2609, 2671, 3133–3134, 3179, 3410–3411, 3429
TOTAL47238436390% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
9613 36 💤 0 ❌ 0 🔥 2m 30s ⏱️

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@moonbox3

Copy link
Copy Markdown
Contributor

Looks like we should still address Copilot's feedback, Giles Odigwe (@giles17). Aside from that I don't have anything else on my end.

@giles17

Copy link
Copy Markdown
Contributor Author

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:

  • header_provider does not withhold anything. It reads the runtime kwargs without consuming them, so the same values continue to the outbound argument filter. My original wording recommended it as a way to keep a credential out of tool arguments, which was wrong. The guidance now points at the axis that actually decides the outcome — where the value is sourced from. Reading a ContextVar inside the provider keeps it out of the runtime kwargs entirely while still allowing a different value per request, and that pattern now has a test rather than being asserted only in prose.
  • The new test was not pinning what it claimed. It called call_tool directly, which never reached _call_tool_with_runtime_kwargs. It now invokes the generated FunctionTool with a FunctionInvocationContext; verified by mutation (removing the merge fails the reworked test, but not the original).
  • The _meta/denylist exceptions are now noted everywhere the docs state the forwarding rule.

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: Fixes # is still blank as there is no tracking issue. Happy to open one if you would like it linked.

@giles17
Giles Odigwe (giles17) added this pull request to the merge queue Aug 21, 2026
Merged via the queue into microsoft:main with commit c6a0e90 Aug 21, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants