pip install -U mcp. Docs: https://py.sdk.modelcontextprotocol.io/
Mostly fixes, plus three new options. A few things behave differently, so skim these first:
Behaviour changes
httpx2>=2.10.0 is now required (#3600)
- It was
>=2.5.0. The newmax_sse_event_sizeoption needs it. - Nothing to do unless you pin
httpx2below 2.10.
A tool with an invalid x-mcp-header annotation fails at registration (#3620)
@mcp.tool(),add_toolandTool.from_functionraiseInvalidSignature, naming the tool and the problem.- Until now the server started, and 2026-07-28 clients silently dropped the tool from their listing.
- Refused: anything other than a plain
str,intorboolparameter (so alsostr | None,float, lists and enums), a header name that isn't a valid token, and two names that differ only by case. - For an optional header parameter, give the schema directly:
Annotated[str | None, WithJsonSchema({"type": "string", "x-mcp-header": "Region"})] = None.
Empty _meta and params are no longer sent (#3628)
- On 2025-11-25 and earlier connections, 2.x sent
"_meta": {}on every request. Some servers reject that. It is now left out, as in v1. pingand list requests without a cursor go out with noparamsmember.- On the receiving side
ctx.metaisNonerather than{}, and middleware seesctx.paramsasNonefor a request without params. - 2026-07-28 connections are unchanged.
initialize leaves out experimental when none is configured (#3614)
- It used to send
"experimental": {}.server/discoveralready left it out. - Client code reading
capabilities.experimentalon a legacy connection should handleNone.
Mcp-Param-* validation looks the tool up by name (#3630)
MCPServerno longer runstools/listfor everytools/call, so middleware no longer sees that extra request.- The registered schema is what gets checked. Middleware that filters or rewrites
tools/listno longer affects it.
An interactive OAuth login no longer counts against request timeouts (#3635)
- The timeout pauses while
OAuthClientProviderwaits onredirect_handlerandcallback_handler. - This fixes
Client(mode="auto")settling on2025-11-25when the login took longer than 10 seconds. - A request timeout no longer ends a login nobody finishes. Put a limit inside
callback_handlerif you need one.
New
max_sse_event_size=onstreamable_http_clientandStreamableHttpParameters. The default stays 1 MiB per SSE event; raise it, or passNone, for larger tool results (#3600).MCPServer(subscriptions=False)stops servingsubscriptions/listenand advertiseslistChangedandsubscribeas false (#3626).Server(get_tool_input_schema=...)lets a low-level server supply a tool's schema for header validation without running itstools/listhandler (#3630).Client.call_toolre-lists the tools and retries once after aHeaderMismatch(-32020) rejection (#3627).
Fixes
ctx: Context[AppState]works on prompts and resource templates, not only on tools (#3624).- An explicit
"structuredContent": nullis checked against the output schema instead of being treated as missing (#3621). - A
progress_callbackthat raises no longer fails the call on an in-processClient(server)(#3623). - Client OpenTelemetry spans record JSON-RPC error responses. With
mode="auto", connecting to a server withoutserver/discovernow shows oneERRORspan for the probe (#3629). stdio_clientresolves the executable off the event loop on Windows (#3510).
Docs
- New pages: Cancellation and Header parameters, plus a middleware tutorial.
- All twelve translations are refreshed (#3636).
What's Changed
- Detect event-loop blocking in tests by @Kludex in #3510
- Bump httpcore2 from 2.5.0 to 2.10.0 by @dependabot[bot] in #3482
- Ask cubic for a review when the intake gate lets a PR back in by @maxisbey in #3489
- docs: remove v2 notice from top of README by @claude[bot] in #3604
- Bump anyio from 4.10.0 to 4.14.2 by @dependabot[bot] in #3547
- Make the deeply nested body test independent of stack size by @maxisbey in #3610
- Fall back to an NTFS junction when the symlink escape test can't create a symlink by @maxisbey in #3609
- Group Dependabot security updates for uv dependencies by @Kludex in #3611
- Expose an SSE event size limit in Streamable HTTP clients by @Kludex in #3600
- Bump the github-actions group with 5 updates by @dependabot[bot] in #3612
- docs: add a middleware tutorial that refuses tool calls by @maxisbey in #3613
- Omit an unset experimental capability from the initialize result by @maxisbey in #3614
- Document when a caller-supplied request id can be minted again by @maxisbey in #3619
- Document the timeout a hand-built httpx2 client needs by @maxisbey in #3618
- Reject a tool with an invalid x-mcp-header annotation at registration by @maxisbey in #3620
- Validate an explicit null structuredContent against the output schema by @maxisbey in #3621
- Contain a raising progress callback on the in-process dispatcher by @maxisbey in #3623
- Keep the live Context when a prompt or template annotates Context[T] by @maxisbey in #3624
- Describe the ID-JAG confidential-client rule as SDK policy, not a SEP-990 requirement by @maxisbey in #3622
- Let MCPServer opt out of serving subscriptions/listen by @maxisbey in #3626
- Document how a handler deals with cancellation by @maxisbey in #3625
- Omit an empty _meta and empty params from outbound requests by @maxisbey in #3628
- Record JSON-RPC error responses on the client OpenTelemetry span by @maxisbey in #3629
- Retry a tool call once after a HeaderMismatch rejection by @maxisbey in #3627
- Look the tool schema up by name for Mcp-Param-* validation instead of running tools/list by @maxisbey in #3630
- Bump urllib3 from 2.7.0 to 2.8.0 by @dependabot[bot] in #3607
- Let a newer Deploy Docs run cancel the one in progress by @maxisbey in #3633
- Link What's new to the Header parameters page by @maxisbey in #3632
- Keep inline-snapshot disabled when pytest runs in a terminal by @maxisbey in #3634
- Stop counting an interactive OAuth login against request timeouts by @maxisbey in #3635
- docs: refresh translations for recent English changes by @maxisbey in #3636
Full Changelog: v2.2.0...v2.3.0