Skip to content

v2.3.0

Latest

Choose a tag to compare

@maxisbey maxisbey released this 02 Oct 22:02
2118f14

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 new max_sse_event_size option needs it.
  • Nothing to do unless you pin httpx2 below 2.10.

A tool with an invalid x-mcp-header annotation fails at registration (#3620)

  • @mcp.tool(), add_tool and Tool.from_function raise InvalidSignature, 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, int or bool parameter (so also str | 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.
  • ping and list requests without a cursor go out with no params member.
  • On the receiving side ctx.meta is None rather than {}, and middleware sees ctx.params as None for 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/discover already left it out.
  • Client code reading capabilities.experimental on a legacy connection should handle None.

Mcp-Param-* validation looks the tool up by name (#3630)

  • MCPServer no longer runs tools/list for every tools/call, so middleware no longer sees that extra request.
  • The registered schema is what gets checked. Middleware that filters or rewrites tools/list no longer affects it.

An interactive OAuth login no longer counts against request timeouts (#3635)

  • The timeout pauses while OAuthClientProvider waits on redirect_handler and callback_handler.
  • This fixes Client(mode="auto") settling on 2025-11-25 when the login took longer than 10 seconds.
  • A request timeout no longer ends a login nobody finishes. Put a limit inside callback_handler if you need one.

New

  • max_sse_event_size= on streamable_http_client and StreamableHttpParameters. The default stays 1 MiB per SSE event; raise it, or pass None, for larger tool results (#3600).
  • MCPServer(subscriptions=False) stops serving subscriptions/listen and advertises listChanged and subscribe as false (#3626).
  • Server(get_tool_input_schema=...) lets a low-level server supply a tool's schema for header validation without running its tools/list handler (#3630).
  • Client.call_tool re-lists the tools and retries once after a HeaderMismatch (-32020) rejection (#3627).

Fixes

  • ctx: Context[AppState] works on prompts and resource templates, not only on tools (#3624).
  • An explicit "structuredContent": null is checked against the output schema instead of being treated as missing (#3621).
  • A progress_callback that raises no longer fails the call on an in-process Client(server) (#3623).
  • Client OpenTelemetry spans record JSON-RPC error responses. With mode="auto", connecting to a server without server/discover now shows one ERROR span for the probe (#3629).
  • stdio_client resolves the executable off the event loop on Windows (#3510).

Docs

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