Skip to content

[v2] Allow MCPServer to disable subscriptions/listen #3357

Description

@owlofjaylan

Initial Checks

Release line

2.x (current stable)

Description

What happened?

MCPServer always installs a subscriptions/listen handler:

on_subscriptions_listen=ListenHandler(self._subscriptions)

The constructor exposes subscriptions: SubscriptionBus | None = None, but None creates an InMemorySubscriptionBus; it does not disable subscriptions.

This causes server/discover to advertise:

{
  "tools": {"listChanged": true},
  "prompts": {"listChanged": true},
  "resources": {"listChanged": true, "subscribe": true}
}

Our MCP server has a static catalog and never publishes change notifications. It runs as a stateless Streamable HTTP server on AWS Lambda.

Claude Code 2.1.235 automatically opened four subscriptions/listen streams during discovery. Each request followed the long-lived SSE path and held a Lambda invocation until timeout.

We currently reject these requests in ASGI middleware. This prevents the timeout, but the server still advertises subscription support, so clients continue attempting the requests.

The low-level Server supports disabling this correctly through:

Server(..., on_subscriptions_listen=None)

The high-level MCPServer has no equivalent public option. Removing the handler through _lowlevel_server._request_handlers works, but relies on private internals.

What did you expect?

MCPServer should provide a supported opt-out, for example:

MCPServer(..., subscriptions=False)

or:

MCPServer(..., enable_subscriptions=False)

When disabled:

  • subscriptions/listen should not be registered.
  • server/discover should advertise the relevant change and subscription capabilities as false.
  • An unsolicited subscriptions/listen call should return Method not found.
  • Existing behavior should remain the default for compatibility.
    This matches SEP-2575’s stateless-first, “pay as you go” design. Long-lived streams should only be enabled when the server uses them.

Area

Server

References

AI assistance disclosure: I used an AI coding assistant to inspect the SDK source and help draft this report. I reviewed and verified the contents.

Example Code

from mcp.server import MCPServer

server = MCPServer("static-server")

capabilities = server._lowlevel_server.get_capabilities(
    protocol_version="2026-07-28"
)

print(capabilities.model_dump(by_alias=True, exclude_none=True))

Python & MCP Python SDK

Python 3.11
mcp 2.0.0
AWS Lambda
Streamable HTTP
json_response=True
stateless_http=True

Activity

  1. mikerva commented on Aug 21, 2026

    @mikerva

    I had this exact issue yesterday as well. Bump! ^^

  2. thijn-minerva commented on Aug 21, 2026

    @thijn-minerva

    Wow! Coincidentally I have the exact same issue! Would really love this resolved, cause leads to some confusing situations

  3. 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 Aug 21, 2026
  4. owlofjaylan commented on Aug 24, 2026

    @owlofjaylan
    Author

    Might actually be better off mirroring the Go SDK here and passing ServerCapabilities as a parameter instead of enable_subscriptions=False. Server doesn't currently take capabilities as param, but this should be an easy fix.

  5. tomoki-takahashi-oisix commented on Sep 14, 2026

    @tomoki-takahashi-oisix

    Independent reproduction of the same problem, same stack (stateless Streamable HTTP on AWS Lambda, clients = Claude Code 2.1.2xx and a hosted connector). Details, a Lambda-free reproduction script and production numbers are in #3493 — filed before I found this issue, so treat #3357 as the primary and #3493 as supporting data:

    • 19 of 19 subscriptions/listen POSTs in a 30-minute sample never completed (ack event, then the stream stays open); every other method completed in milliseconds.
    • ~1,300–1,800 invocations/day ran to the 900 s Lambda timeout because of this — 99% of the function's billed GB-seconds — and clients re-sent listen immediately after each timeout, so the streams were permanently occupied.
    • server/discover advertised listChanged: true / subscribe: true for a server that never publishes anything.

    Our workaround (works on 2.1.1 and 2.2.0, but relies on private attributes): mcp._lowlevel_server._request_handlers.pop("subscriptions/listen"). With the handler gone, get_capabilities() flips to listChanged: false / subscribe: false and a listen POST gets the standard 404 + -32601; the TypeScript client treats that as a soft error and the connection keeps working.

    +1 to a public knob. Either an explicit flag or a ServerCapabilities parameter (as suggested above) would do, as long as get_capabilities() follows it.

  6. 1320800521 commented on Sep 20, 2026

    @1320800521

    Independent verification from XBSTACK on 2026-09-20 confirms the same behavior on both mcp==2.1.1 and mcp==2.2.0 with a minimal local Streamable HTTP harness (no Lambda required).

    Observed:

    • server/discover advertises listChanged/subscribe=true by default.
    • subscriptions/listen stays open beyond the test window when no disconnect is propagated.
    • Removing the private subscriptions/listen handler flips the advertised subscription capability off and the same listen call returns -32601 Method not found immediately.
    • The containment works, but it depends on _lowlevel_server._request_handlers, so it is not a supported long-term solution.

    Repro, logs, and version matrix:
    https://github.com/xbstack/mcp-subscriptions-listen-serverless-repro

    Related deployment notes:
    https://www.xbstack.com/ai/mcp-streamable-http-deployment/?utm_source=github&utm_medium=referral&utm_campaign=mcp_subscriptions_listen_serverless&utm_content=issue_reply&ref=github

    This supports the request for a public capability/opt-out surface rather than relying on middleware rejection or private handler mutation.

  7. mikerva commented on Oct 1, 2026

    @mikerva

    I had this exact issue yesterday as well. Bump! ^^

  8. maxisbey commented on Oct 2, 2026

    @maxisbey
    Contributor

    MCPServer(..., subscriptions=False) landed in #3626. Thanks for the clear report, and to everyone who added numbers and repros.

    It leaves subscriptions/listen unregistered, so server/discover reports the listChanged and subscribe flags as false and a stray listen call gets Method not found. The default is unchanged. Turning it off has an example.

    Once it's released, you can drop the _lowlevel_server._request_handlers workaround.

    The ServerCapabilities-style parameter suggested above isn't part of this. It fits better with the capabilities work in #2896.

    If it doesn't cover your setup, feel free to open a new issue.

    AI Disclaimer

  9. 1320800521 commented on Oct 8, 2026

    @1320800521

    XBSTACK follow-up to our September mcp==2.1.1 / 2.2.0 reproductions: the official Python SDK v2.3.0 release notes now explicitly include MCPServer(subscriptions=False) from #3626. For a static/serverless MCP server, this is the supported opt-out to test instead of deleting _lowlevel_server._request_handlers privately. After upgrading, please verify server/discover advertises the expected false capabilities, an unsolicited subscriptions/listen is rejected, and your real client and adapter behave correctly. Note the v2.3.0 httpx2>=2.10.0 requirement too.

    Our existing measurements cover 2.1.1 / 2.2.0 only; we have not yet rerun a production Lambda integration on 2.3.0. We've updated the bilingual deployment troubleshooting guide with that boundary: https://www.xbstack.com/en/ai/mcp-streamable-http-deployment/?utm_source=github&utm_medium=referral&utm_campaign=mcp_subscriptions_optout_v230&utm_content=issue_followup

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

    spec-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