Python: migrate mcp connector to mcp 2.x - #14251
Conversation
Bump the mcp dependency to >=2.0.0,<3.0 and update the connector for the 2.0 breaking changes: - Server: the lowlevel @server.list_tools()/call_tool()/get_prompt()/ set_logging_level() decorators were removed. Handlers are now passed to the Server(...) constructor as on_list_tools/on_call_tool/on_list_prompts/ on_get_prompt/on_set_logging_level, take a (ServerRequestContext, params) pair, and return result models (ListToolsResult/CallToolResult/...). The per-request session for sending log messages is taken from the handler context instead of the removed server.request_context. - Client: streamablehttp_client -> streamable_http_client (2-tuple); its headers/timeout/sse_read_timeout kwargs were removed, so the streamable http plugin builds an httpx2 client via create_mcp_http_client instead. ClientSession.read_timeout_seconds now takes float seconds. - camelCase model fields are snake_case: input_schema, mime_type, tool_use_id, model_preferences, max_tokens, system_prompt. - RequestContext -> ClientRequestContext for the sampling callback; message_handler now receives ServerNotification|Exception (no RequestResponder), and list-changed notifications are matched via .method directly. - McpError -> MCPError (raised with code/message). - The websocket transport was removed in mcp 2.0; MCPWebsocketPlugin now raises a clear configuration error at connect time instead of failing at import. - Tests and the test MCP server asset updated; uv.lock regenerated. Tests: 23 passed (unit), 1 passed (integration, real stdio server). ruff and mypy clean.
|
@ProgrammerPlus1998 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
1 similar comment
|
@ProgrammerPlus1998 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
There was a problem hiding this comment.
🟡 Not ready to approve
There are correctness and reliability issues in the updated connector (notably streamable HTTP timeout semantics and server logging context handling under concurrency) that should be addressed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR migrates the Python MCP connector integration in Semantic Kernel to the MCP Python SDK 2.x API, unblocking installation alongside mcp>=2.0.0 and updating server/client behaviors and tests to match the breaking changes.
Changes:
- Update
python/semantic_kernel/connectors/mcp.pyfor MCP 2.x (server handler registration viaServer(...), client transport/notification changes, snake_case model fields, httpx2-based streamable HTTP). - Bump dependency pins to
mcp>=2.0.0,<3.0and regenerateuv.lock. - Update MCP test assets and unit tests to match new SDK shapes and removal of websocket transport.
File summaries
| File | Description |
|---|---|
| python/semantic_kernel/connectors/mcp.py | Migrates MCP server/client connector implementation to the MCP 2.x API and transport expectations. |
| python/pyproject.toml | Updates MCP dependency pins to require SDK 2.x. |
| python/tests/unit/connectors/mcp/test_mcp.py | Adapts unit tests/mocks to MCP 2.x request/response shapes and transport changes. |
| python/tests/assets/test_plugins/TestMCPPlugin/mcp_server.py | Updates test MCP server asset to the new server class. |
| python/uv.lock | Regenerates lockfile reflecting MCP 2.x transitive dependency changes (e.g., httpx2). |
Review details
Suppressed comments (1)
python/semantic_kernel/connectors/mcp.py:930
- This docstring says extra kwargs are passed to the websocket client constructor, but
get_mcp_client()unconditionally raises because websocket transport was removed in mcp 2.x, so those kwargs are never used. Updating the docstring avoids misleading callers.
Any extra arguments passed to the constructor will be passed to the
websocket client constructor.
- Files reviewed: 4/5 changed files
- Comments generated: 2
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| # In mcp 2.x there is no server-level request_context; handlers receive a per-request | ||
| # ServerRequestContext carrying the connection-scoped session. We track the most recent | ||
| # one so helper functions (e.g. _log) can reach the session to emit log messages. | ||
| current_ctx: dict[str, ServerRequestContext | None] = {"ctx": None} |
| timeout = httpx2.Timeout( | ||
| self.timeout if self.timeout else 30.0, | ||
| read=self.sse_read_timeout if self.sse_read_timeout else 300.0, | ||
| ) |
Motivation and Context
The
mcpdependency is pinned tomcp>=1.26.0,<2.0, which blocks the MCP Python SDK 2.x line. 2.0 introduced several breaking changes that the connector relies on, so the integration and its tests need to be updated before the pin can be loosened.Fixes #14246
This takes the "upgrade to 2.x" approach (require the 2.x SDK) rather than trying to support both 1.x and 2.x at once.
Description
Updates the MCP connector (
python/semantic_kernel/connectors/mcp.py) to the 2.0 API and bumps the pin tomcp>=2.0.0,<3.0(both the main dependency and themcpextra).uv.lockregenerated.Server side (
create_mcp_server_from_kernel/create_mcp_server_from_functions):@server.list_tools()/call_tool()/get_prompt()/list_prompts()/set_logging_level()decorators were removed. Handlers are now passed to theServer(...)constructor (on_list_tools,on_call_tool,on_list_prompts,on_get_prompt,on_set_logging_level), receive a(ServerRequestContext, params)pair, and return result models (ListToolsResult,CallToolResult,GetPromptResult,EmptyResult).ServerRequestContext(the removedserver.request_contextno longer exists).Client side (
MCP*Plugin):streamablehttp_client→streamable_http_client; it now yields a 2-tuple and no longer acceptsheaders/timeout/sse_read_timeout, soMCPStreamableHttpPluginbuilds anhttpx2client viacreate_mcp_http_clientwhen those are set.ClientSession(read_timeout_seconds=...)now takes float seconds instead of atimedelta.mcp.shared.context.RequestContext→mcp.client.session.ClientRequestContext.message_handlernow receivesServerNotification | Exception(no moreRequestResponder); list-changed notifications are matched via.methoddirectly.session.initialize_resultinstead of the removed_request_id.McpError→MCPError(raised withcode=/message=).input_schema,mime_type,tool_use_id,model_preferences,max_tokens,system_prompt.Removed in 2.0:
mcp.client.websocket) no longer exists, soMCPWebsocketPlugin.get_mcp_client()now raises a clearKernelPluginInvalidConfigurationErrorat connect time instead of failing at import. (Happy to drop the class entirely if preferred.)Tests / assets:
tests/assets/test_plugins/TestMCPPlugin/mcp_server.py:FastMCP→MCPServer.tests/unit/connectors/mcp/test_mcp.py: updated fixtures/mocks for the 2.0 shapes (input_schema,streamable_http_client2-tuple,initialize_result), rewrote the server-handler assertions to the newget_request_handler(method)API, and replaced the websocket test with one asserting the removal error.Contribution Checklist
ruff check+ruff formatclean,mypyclean on the connector)mcp2.0.0