Python: Reject MCP servers passed as tools to Claude and Copilot agents - #7835
Conversation
There was a problem hiding this comment.
Pull request overview
Adds shared MCP tool expansion so Claude and GitHub Copilot agents can expose MCP server functions.
Changes:
- Adds
_expand_mcp_tools()with connection, validation, and deduplication. - Integrates expansion and lifecycle management into both provider agents.
- Adds MCP expansion and concurrency tests.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
python/packages/core/agent_framework/_mcp.py |
Adds shared MCP expansion helper. |
python/packages/core/tests/core/test_mcp.py |
Tests expansion and validation. |
python/packages/claude/agent_framework_claude/_agent.py |
Integrates MCP tools into Claude SDK options. |
python/packages/claude/tests/test_claude_agent.py |
Tests Claude MCP integration. |
python/packages/github_copilot/agent_framework_github_copilot/_agent.py |
Integrates MCP tools into Copilot sessions. |
python/packages/github_copilot/tests/test_github_copilot_agent.py |
Tests Copilot MCP integration. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
|
/review |
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (2 commit(s)): cabe20e88030, 68d2e04cce20
Model: gpt-5.6-sol
Overview
The PR centralizes MCP expansion and gives both provider agents explicit connection ownership, duplicate-name validation, progressive-disclosure rejection, approval integration, and concurrency guards. The new behavior is well covered across agent-, default-, and run-level tools, but the provider dependency bounds do not yet guarantee the new core helper exists. Copilot also retains dynamically supplied per-run MCP connections for the lifetime of the agent, allowing resources to accumulate across completed runs.
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
3 verified findings remained after source verification (2 high, 1 medium) across 2 files. Details are attached to the affected lines below.
Affected areas: python/packages/claude/agent_framework_claude/_agent.py, python/packages/github_copilot/agent_framework_github_copilot/_agent.py
b54b8a4 to
e4e6670
Compare
e4e6670 to
7d5f38d
Compare
7d5f38d to
30c956e
Compare
|
NekoPunch (@orangeCatDeveloper) Giles Odigwe (@giles17) I think we are doing the wrong thing here, both Claude and GithubCopilotAgents have specific MCP server config inputs, so AFAIC we should not allow |
30c956e to
f516877
Compare
f516877 to
cb59f18
Compare
cb59f18 to
5e9acb3
Compare
5e9acb3 to
4ec0b43
Compare
4ec0b43 to
58d337b
Compare
58d337b to
36077f6
Compare
36077f6 to
860db85
Compare
Eduard van Valkenburg (@eavanvalkenburg) Agreed—done. Both agents now reject MCPTool, and the error carries the |
| self._custom_tools.extend(normalize_tools(non_builtin_tools)) | ||
| # Check after normalizing: it flattens tool-collection wrappers, which can hide an MCPTool. | ||
| normalized = normalize_tools(non_builtin_tools) | ||
| for tool in normalized: |
There was a problem hiding this comment.
do we have to loop again here, can't we check for MCPTool in the loop on line 424?
There was a problem hiding this comment.
The second loop checks the normalized tools because normalize_tools() expands tool-collection wrappers that may contain an MCPTool. Checking only in the first loop would miss those wrapped tools.
860db85 to
a6ab298
Compare
Eduard van Valkenburg (eavanvalkenburg)
left a comment
There was a problem hiding this comment.
minor comment left, should be good otherwise
An MCPTool handed to ClaudeAgent or GitHubCopilotAgent was silently dropped: no error, no warning, and none of its tools reached the model. Both SDKs connect to MCP servers themselves and take their own mcp_servers config, so the framework cannot honor a framework-managed MCPTool there. It is now refused with the native configuration to write instead.
a6ab298 to
0495338
Compare
Motivation & Context
An MCP server handed to
ClaudeAgentorGitHubCopilotAgentis silently dropped: no error, no warning, and none of its tools reach the model.The same code works on
ChatAgent, so the failure looks like a model problem rather than a wiring one.Description & Review Guide
Both provider agents convert tools themselves and only recognise
FunctionTool; anything else is skipped, and anMCPTool-- a server connection rather than a callable tool -- fell into that branch.Neither agent should be connecting to MCP servers in the first place: both SDKs run their own tool loop and take MCP servers as configuration (
ClaudeAgentOptions.mcp_servers,create_session(mcp_servers=...)), which is what the existing samples use. What a framework-managedMCPToolpromises on top of that -- middleware, tracing, approval routing, result parsing -- cannot survive a connection the framework does not own, so the agents now refuse it and quote the native configuration to write instead, filled in with the server's own name.Both checks run on the normalized tool list, since
normalize_tools()flattens tool-collection wrappers that would otherwise hide anMCPTool, and Copilot checks the option-supplied tools as well as the constructor's. Claude's tool classification was only treatinglistas a collection, so a tuple bypassed it -- it now follows the same rule asnormalize_tools(), which also fixes built-in tool names passed as a tuple being mistaken for custom tools.A configuration that silently did nothing now fails, at construction time where the tools are passed there. Nobody's working code changes behaviour: an
MCPToolnever contributed anything to these agents.I first implemented this as a translation into each SDK's native
mcp_serversconfig, so thatChatAgentcode would port over unchanged, and dropped it. Rejection can only name the options anMCPToolcarries explicitly; it cannot cover what the framework supplies ambiently -- middleware, tracing, approval routing -- because those are not options anyone passed. Ported code would have run and quietly behaved differently, which is a worse failure than the one this PR started from. Happy to revisit if the project does wanttools=to acceptMCPTooleverywhere.Before / after, the server from the snippet above:
Related Issue
Fixes #3651
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.