Python: Fix MCP tools duplicated on second turn when runtime tools are present#4432
Python: Fix MCP tools duplicated on second turn when runtime tools are present#4432giles17 merged 4 commits intomicrosoft:mainfrom
Conversation
When AG-UI's collect_server_tools pre-expands MCP functions on turn 2 (after the MCP server is connected), _prepare_run_context unconditionally appends them again from self.mcp_tools, duplicating every MCP tool. Skip MCP functions whose names already exist in the final tool list, following the same name-based dedup pattern used in _merge_options. Fixes microsoft#4381 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug (issue #4381) where MCPStdioTool functions are duplicated on the second and subsequent turns when AgentFrameworkAgent (AG-UI) is used with an Agent that also receives client-side tools at runtime. On turn 2, AG-UI's collect_server_tools pre-expands MCP functions (since the server is now connected) and passes them as runtime tools=, but _prepare_run_context unconditionally extended final_tools from self.mcp_tools again — doubling every MCP function.
Changes:
- Added name-based deduplication in
_prepare_run_contextbefore extendingfinal_toolswithmcp_server.functions, skipping any function already present by name. - Added a regression test
test_mcp_tools_not_duplicated_when_passed_as_runtime_toolsthat simulates the turn-2 scenario with a connected mock MCP tool.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
python/packages/core/agent_framework/_agents.py |
Adds existing_names set before extending from self.mcp_tools, skipping functions already present in final_tools |
python/packages/core/tests/core/test_agents.py |
New regression test verifying no duplication when MCP functions are passed as runtime tools |
There is one bug in the fix itself: the existing_names set comprehension at line 1054 uses t.name directly on all items in final_tools, but final_tools can contain Mapping/dict-based tool definitions (which pass through normalize_tools and the else branch at line 1052) that do not have a .name attribute. This would cause an AttributeError at runtime when any runtime tool is a dict-style tool definition.
The file already defines _get_tool_name(t) (lines 84–91) that safely handles both dict-based tools and attribute-based tools, and is used for exactly this purpose in _merge_options (line 110). The fix should use _get_tool_name instead of direct .name access.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Fixes #4381
When
AgentFrameworkAgent(fromagent-framework-ag-ui) is used with anAgentthat has anMCPStdioTool, and the AG-UI request includes client-side tools, the MCP tool functions are expanded twice on the second and subsequent turns - producing a tool list with every MCP tool name duplicated.Root Cause
In
_prepare_run_context(_agents.py),self.mcp_toolsfunctions were unconditionally appended tofinal_toolseven when they were already present from runtimetools(pre-expanded by AG-UI'scollect_server_toolson turn 2 when the MCP server is connected).Fix
Added name-based deduplication before extending
final_toolswith MCP server functions - collecting existing tool names first and skipping any MCP functions already present. This follows the same dedup pattern used in_merge_options.Changes
packages/core/agent_framework/_agents.py- 2-line fix in_prepare_run_contextpackages/core/tests/core/test_agents.py- addedtest_mcp_tools_not_duplicated_when_passed_as_runtime_toolsVerification
Reproduced the issue using the repro script from the issue, confirmed turn 2 no longer duplicates tools: