Skip to content

ServerCapabilities drops unknown keys, so a server can't advertise events for ChatGPT's MCP Events #3640

Description

@s1980amber-commits

Initial Checks

Release line

2.x (current stable)

Description

What happens. ServerCapabilities is documented as "Not a closed set", but unknown keys are silently dropped (see the example below). Spec results are also sieved against the schema in ServerRunner._serialize, so even a custom server/discover handler that returns a dict with capabilities.events loses it before it reaches the wire.

What I expected. Extra capability keys to be kept, as the docstring suggests.

Why it matters. ChatGPT's MCP Events (launched 29 September 2026, docs at https://developers.openai.com/plugins/build/mcp-events) only subscribes to servers whose server/discover result contains top-level capabilities.events. With the SDK as it is, a Python server can't advertise it without working around the SDK.

Workaround. The events/* methods themselves register fine with add_request_handler. The capability can be added by a ServerMiddleware that edits the serialised result after call_next. That works, and I've tested it end to end with ChatGPT, but it relies on internals.

Possible fixes, in rough order of effort:

  1. Let ServerCapabilities keep extra keys (extra="allow"), so the "not a closed set" docstring holds and the sieve passes them through.
  2. A supported way to add capability keys, like extensions already has.
  3. First class Events support, following the draft in modelcontextprotocol/experimental-ext-triggers-events.

For anyone who needs it before then, I've published the workaround plus the full Events implementation (verification, signing, retries, persistence) as mcp-webhook-events on PyPI, MIT licensed: https://github.com/s1980amber-commits/mcp-webhook-events

Example Code

import mcp_types as t

caps = t.ServerCapabilities(tools={}, events={})
print(caps.model_dump(by_alias=True, exclude_none=True))
# prints {'tools': {}}  (the 'events' key is dropped)

Python & MCP Python SDK

Python 3.12.3 (Linux) and 3.14.4 (Windows)
mcp 2.2.0, mcp-types 2.2.0
Protocol 2026-07-28, Streamable HTTP, stateless

Activity

  1. added
    v2Affects the v2 line (2.x on main)
    spec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revision
    on Oct 3, 2026
  2. epistemedeus commented on Oct 4, 2026

    @epistemedeus

    Reproduced on main 2118f14f with mcp/mcp-types 2.3.0. The public capability models discard unknown keys, and the generated wire model discards them again through ServerRunner._serialize → serialize_server_result.

    I prepared a fork candidate that preserves unknown keys only on ClientCapabilities and ServerCapabilities, including their generated schema-era models. Known fields still validate; nested known capability objects stay closed. The wire sieve still removes cross-era tasks on 2026-07-28 and extensions on older versions. This is not a hardcoded events field.

    The regression checks actual Streamable HTTP discover bytes, modern and legacy SDK consumers, another unknown capability key, malformed known fields and an ordinary tools call. Independent remote replay passes the full pytest suite and the strict 100% coverage gate, plus the edited-test Ruff/Pyright checks.

    This is a fork patch, not an upstream release or complete Events implementation. It does not register events/* methods, verify callbacks or deliver subscriptions; your middleware remains the workaround for installed releases without this fix. Prepared with AI assistance and independently reviewed/tested. No new PR is being opened or the automatically closed draft reopened.

  3. s1980amber-commits commented on Oct 4, 2026

    @s1980amber-commits
    Author

    Thanks for this. I tested it against mcp-webhook-events, which currently relies on the middleware workaround.

    Setup: mcp and mcp-types 2.3.0 from PyPI, with the mcp-types part of your change applied (it applies cleanly to the 2.3.0 release). A real MCPServer with the library installed, our middleware removed, and events added through the low-level server's get_capabilities, the same way as your test. Checked with an actual server/discover call over Streamable HTTP (2026-07-28).

    • Stock 2.3.0: capabilities come back as prompts, resources, tools. No events.
    • 2.3.0 with your fix: events, prompts, resources, tools.
    • With your fix and our middleware still in place: no clash, a single events key, and all 33 of our tests pass, including the HTTP ones.

    So once something like this lands, servers can advertise events without any middleware, and I'll switch the library over to the get_capabilities route. It would be good to see it upstream.

  4. epistemedeus commented on Oct 4, 2026

    @epistemedeus

    Thanks, I independently replayed the published 2.3.0 wheels with the four-file types patch and your library at 2a5a005. All 33 library tests pass with both stock and patched packages. The stock pass uses your existing middleware; it does not mean the unpatched get_capabilities path works.

    The patched no-middleware path advertises events: {"listChanged": false} over Streamable HTTP. With both enabled, your middleware's capabilities["events"] = {} wins after serialization, without a duplicate key. A second custom capability, including a nested null, survives too. Malformed known fields still fail validation and ordinary tools calls work.

    This second replay used Python 3.14.8/Pydantic 2.13.5; the earlier replay used Python 3.12.3. The installed runner is unchanged. The types-only fork change remains a release-specific compatibility patch, not an upstream release or an Events method implementation. Prepared with AI assistance and independently replayed.

  5. s1980amber-commits commented on Oct 4, 2026

    @s1980amber-commits
    Author

    Thanks for taking the time to replay all that, it's really appreciated. Good to know the patched route works without the middleware and that the two don't clash. If the change lands upstream I'll switch the library over to get_capabilities and keep the middleware for older SDK versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingspec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revisionv2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions