Skip to content

Commit d504023

Browse files
authored
Fix mcp tool cloning for handoff pattern (#1883)
1 parent 9e1b3c9 commit d504023

File tree

2 files changed

+42
-1
lines changed

2 files changed

+42
-1
lines changed

python/packages/core/agent_framework/_workflows/_handoff.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ def _clone_chat_agent(agent: ChatAgent) -> ChatAgent:
8080
options = agent.chat_options
8181
middleware = list(agent.middleware or [])
8282

83+
# Reconstruct the original tools list by combining regular tools with MCP tools.
84+
# ChatAgent.__init__ separates MCP tools into _local_mcp_tools during initialization,
85+
# so we need to recombine them here to pass the complete tools list to the constructor.
86+
# This makes sure MCP tools are preserved when cloning agents for handoff workflows.
87+
all_tools = list(options.tools) if options.tools else []
88+
if agent._local_mcp_tools:
89+
all_tools.extend(agent._local_mcp_tools)
90+
8391
return ChatAgent(
8492
chat_client=agent.chat_client,
8593
instructions=options.instructions,
@@ -101,7 +109,7 @@ def _clone_chat_agent(agent: ChatAgent) -> ChatAgent:
101109
store=options.store,
102110
temperature=options.temperature,
103111
tool_choice=options.tool_choice, # type: ignore[arg-type]
104-
tools=list(options.tools) if options.tools else None,
112+
tools=all_tools if all_tools else None,
105113
top_p=options.top_p,
106114
user=options.user,
107115
additional_chat_options=dict(options.additional_properties),

python/packages/core/tests/workflow/test_handoff.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
from collections.abc import AsyncIterable, AsyncIterator
44
from dataclasses import dataclass
55
from typing import Any, cast
6+
from unittest.mock import MagicMock
67

78
import pytest
89

910
from agent_framework import (
1011
AgentRunResponse,
1112
AgentRunResponseUpdate,
1213
BaseAgent,
14+
ChatAgent,
1315
ChatMessage,
1416
FunctionCallContent,
1517
HandoffBuilder,
@@ -20,6 +22,8 @@
2022
WorkflowEvent,
2123
WorkflowOutputEvent,
2224
)
25+
from agent_framework._mcp import MCPTool
26+
from agent_framework._workflows._handoff import _clone_chat_agent
2327

2428

2529
@dataclass
@@ -368,3 +372,32 @@ async def async_termination(conv: list[ChatMessage]) -> bool:
368372
user_messages = [msg for msg in final_conv_list if msg.role == Role.USER]
369373
assert len(user_messages) == 2
370374
assert termination_call_count > 0
375+
376+
377+
async def test_clone_chat_agent_preserves_mcp_tools() -> None:
378+
"""Test that _clone_chat_agent preserves MCP tools when cloning an agent."""
379+
mock_chat_client = MagicMock()
380+
381+
mock_mcp_tool = MagicMock(spec=MCPTool)
382+
mock_mcp_tool.name = "test_mcp_tool"
383+
384+
def sample_function() -> str:
385+
return "test"
386+
387+
original_agent = ChatAgent(
388+
chat_client=mock_chat_client,
389+
name="TestAgent",
390+
instructions="Test instructions",
391+
tools=[mock_mcp_tool, sample_function],
392+
)
393+
394+
assert hasattr(original_agent, "_local_mcp_tools")
395+
assert len(original_agent._local_mcp_tools) == 1
396+
assert original_agent._local_mcp_tools[0] == mock_mcp_tool
397+
398+
cloned_agent = _clone_chat_agent(original_agent)
399+
400+
assert hasattr(cloned_agent, "_local_mcp_tools")
401+
assert len(cloned_agent._local_mcp_tools) == 1
402+
assert cloned_agent._local_mcp_tools[0] == mock_mcp_tool
403+
assert len(cloned_agent.chat_options.tools) == 1

0 commit comments

Comments
 (0)