Python: feat(core): migrate MCP integration to mcp 2.0.0 - #7468
Python: feat(core): migrate MCP integration to mcp 2.0.0#7468PratikWayase wants to merge 1 commit into
Conversation
all breaking API changes while preserving existing behavior.
There was a problem hiding this comment.
Pull request overview
Migrates the Python agent-framework-core MCP integration to the MCP Python SDK v2.0.0, updating internal protocol handling and tests to match MCP’s breaking API/model changes while enabling the workspace to resolve MCP 2.x.
Changes:
- Updated core MCP client integration (
_mcp.py) for MCP 2.0.0 API/model changes (snake_case fields,MCPError, request model usage, task payload parsing via Pydantic extras). - Updated MCP-related unit tests to construct MCP 2.0-compatible models and assertions, including conditional skipping of the WebSocket transport test when not available.
- Updated workspace/package dependency constraints to allow installing MCP 2.0.0.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| python/packages/core/agent_framework/_mcp.py | Adapts MCP integration code to SDK 2.0.0 request/response models, error types, and renamed fields. |
| python/packages/core/tests/core/test_mcp.py | Updates tests for MCP 2.0.0 model shapes/fields and skips WebSocket test when transport module is unavailable. |
| python/packages/core/pyproject.toml | Updates the all optional dependency group to allow MCP 2.0.0. |
| python/packages/foundry_hosting/pyproject.toml | Updates package dependency to allow MCP 2.0.0. |
| python/packages/hosting-mcp/pyproject.toml | Updates package dependency to allow MCP 2.0.0. |
Suppressed comments (1)
python/packages/core/agent_framework/_mcp.py:2474
- The comment explaining the MCP 2.0
GetTaskPayloadResultextras is mis-indented, making it look like it’s part of theexceptblock even though the extraction logic is outside the handler. Aligning the comment with the surrounding code will avoid confusion.
# In MCP 2.0, GetTaskPayloadResult only declares `meta`; the actual
# CallToolResult fields are carried as Pydantic extra fields.
| "agent-framework-core>=1.13.0,<2", | ||
| "agent-framework-hosting==1.0.0a260730", | ||
| "mcp>=1.11.0,<2", | ||
| "mcp>=2.0.0", | ||
| "pydantic>=2,<3", |
| "azure-ai-agentserver-invocations>=1.0.0b6,<2", | ||
| "httpx>=0.28,<1", | ||
| "mcp>=1.24.0,<2", | ||
| "mcp>=2.0.0", |
| [project.optional-dependencies] | ||
| all = [ | ||
| "mcp>=1.24.0,<2", | ||
| "mcp>=2.0.0", |
| # Inspect the raw payload: a CreateTaskResult carries `task.task_id`; | ||
| # a legacy CallToolResult carries `content` and/or `isError`. |
| raw: dict[str, Any] = lenient.model_dump(by_alias=True, exclude_none=True) | ||
| raw.pop("_meta", None) | ||
| # In MCP 2.0, Result may carry CallToolResult fields as Pydantic extras | ||
| if hasattr(lenient, "__pydantic_extra__") and lenient.__pydantic_extra__: |
There was a problem hiding this comment.
What happens when this receives a real MCP 2.0 task response? types.Result and GetTaskPayloadResult use Pydantic's default extra="ignore", so the SDK discards the task and payload fields before these checks and __pydantic_extra__ is always None. This makes task creation, polling, and result retrieval fail for valid server responses; could we preserve the raw response or deserialize into the concrete task result types before those fields are lost?
| "azure-ai-agentserver-invocations>=1.0.0b6,<2", | ||
| "httpx>=0.28,<1", | ||
| "mcp>=1.24.0,<2", | ||
| "mcp>=2.0.0", |
There was a problem hiding this comment.
Should this package be migrated before raising its MCP floor? _responses.py:120 still imports McpError, which MCP 2.0 removed in favor of MCPError, so installing the dependency declared here makes agent_framework_foundry_hosting fail at import time. Could we update that exception handling in this PR or retain the <2 constraint until the package migration lands?
| content=tool_use_contents, | ||
| model=response.model or "unknown", | ||
| stopReason="toolUse", | ||
| stop_reason="tool_use", |
There was a problem hiding this comment.
Could this retain the protocol value toolUse? MCP 2.0 renamed the Python field to stop_reason, but its canonical wire value remains toolUse; because the type also accepts arbitrary strings, tool_use silently serializes to {"stopReason": "tool_use"}. A conforming server may then fail to recognize that sampling stopped for tool execution and break the tool-use continuation flow.
Summary
Migrate
agent-framework-corefrommcp>=1.xtomcp>=2.0.0, adapting to all breaking API changes in the MCP Python SDK while preserving existing framework behavior. This includes updating production code, test suites, workspace dependency pins, and the lockfile.Motivation & Context
The MCP Python SDK 2.0.0 introduced significant breaking changes including snake_case attribute renames, Union type aliases replacing wrapper classes, exception restructuring, and payload serialization changes. Without this migration,
agent-framework-corecannot use MCP 2.0 servers or benefit from upstream improvements. This change unblocks adoption of MCP 2.0 across the agent framework ecosystem.Fixes #7446
Description & Review Guide
What are the major changes?
Production code (
_mcp.py):McpError→MCPErrorand removed.errorwrapper (.code,.messageaccessed directly)mime_type,is_error,structured_content,input_schema,next_cursor,protocol_version,stop_reason,tool_use_id,poll_interval,status_message,task_support)types.ClientRequest()wrappers in 4 locations (now a Union type alias in MCP 2.0)message.root.method→message.methodforServerNotificationdispatchread_timeout_secondsfromtimedeltatofloat__pydantic_extra__extraction in_fetch_task_resultand_call_tool_as_task_create(MCP 2.0GetTaskPayloadResultandResultonly declaremeta; tool result fields are carried as extras)system_prompt,tool_choice,stop_sequences,max_tokens)_send_with_one_reconnectrequest param type fromtypes.ClientRequesttoAnymime_type=keyword arg in content preparation constructorsTest code (
test_mcp.py):Mock(spec=ServerNotification)with real notification instances (ToolListChangedNotification,PromptListChangedNotification) since Union types don't support isinstance checks against MocksAnyUrlobjects to plain strings forurifieldsMCPErrorconstructor calls to usecode=andmessage=directly_make_create_task_result/_make_task_snapshottimestamps fromdatetimeto ISO 8601 strings_make_payloadto construct viaCallToolResultround-trip for extras compatibilitymcp.client.websocketremoved in base MCP 2.0.0).root.method/.root.paramsto direct attribute accessWorkspace dependencies:
foundry_hostingandhosting-mcppyproject.tomlto allowmcp>=2.0.0(previously pinned<2, causing downgrade onuv sync)uv.lockwithmcp==2.0.0resolved across the entire workspaceWhat is the impact of these changes?
foundry_hostingandhosting-mcpare required to preventuv sync/poe syntaxfrom downgrading MCP back to 1.x. These packages may need their own MCP 2.0 migration in follow-up PRs.MCPWebsocketToolnow requiresmcp[ws]extra; the basemcppackage no longer bundles WebSocket support.What do you want reviewers to focus on?
__pydantic_extra__extraction logic in_fetch_task_resultand_call_tool_as_task_create— this is the most novel pattern, needed because MCP 2.0'sGetTaskPayloadResultandResultmodels only declaremetaand carry tool result fields as Pydantic extras.message.methodvs oldmessage.root.method) — confirm theisinstance(message, types.ServerNotification)check works correctly with real notification instances in tests.foundry_hostingandhosting-mcptomcp>=2.0.0is acceptable for this PR scope, or whether those packages should remain pinned and be migrated separately.Related Issue
Fixes #7446
Contribution Checklist