Skip to content

Proposal: experimental Events extension module (client and server) #2945

Open

Description

@panyam

I'd like to add support for the Events extension (design sketch) under experimental/, next to tasks. I'm opening this before writing code because the extension isn't an accepted SEP yet, and I'd rather hear now whether that rules it out.

Why now

The conformance repo is adding an Events suite. Server scenarios are in modelcontextprotocol/conformance#521, and client scenarios are tracked in modelcontextprotocol/conformance#540. That repo runs its suites against this SDK and builds its everything-client on it. Without events support here, the client scenarios have to ship a hand-written JSON-RPC client as their passing example, which is how the MRTR scenario works today. An SDK module would replace that, and other SDKs would have a reference to compare against.

What it would cover

The extension has three delivery modes, and the module would cover all three on both sides.

On the client side, that means events/list; a poll loop that drains on hasMore and applies a 1000 ms floor; an events/stream call that skips the default request timeout and reconnects with its cursor when the stream goes quiet; webhook events/subscribe with a refresh loop scheduled from refreshBefore; cursor handling for truncated: true and cursor: null; and secret generation from a CSPRNG.

On the server side, it means the capability declaration, events/list, events/poll, events/stream with heartbeats, and webhook subscribe/unsubscribe with signed delivery. The five conformance server scenarios would grade this half.

Existing implementation

mcpkit (Go) has a server and a client for this extension, and its server was the main target while the modelcontextprotocol/conformance#521 suite was being built. Where the document is silent, the TypeScript module would follow mcpkit's behaviour, and the conformance suite would hold both to the same rows.

Questions/Considerations

  1. Picking experimental/ as the location for this as the extension is not yet accepted. Any thoughts?

Activity

  1. Ben-Home commented on Oct 3, 2026

    @Ben-Home

    Opening this before writing code is the right call, and I would not let the not-yet-accepted-SEP status rule out an experimental/ module. There is precedent in the repo for exactly this: tasks shipped under experimental/ while its SEP was still in flight. The cost of an experimental module is that it can change or be removed, which is a documented property of that namespace, not a promise of permanence. Given the conformance suite is already building server scenarios against this and the alternative is a hand-written JSON-RPC client, the module pays for itself as a reference implementation even if the extension later changes shape.

    On the location question specifically: the deciding factor should be whether the modules under experimental/ have their own entry points and no effect on the default server surface. If adding experimental/events only ships when a caller opts in (capability declared by the caller, routes registered explicitly), then being in experimental/ rather than top-level costs nothing and signals the right thing. If it would touch shared transport or capability negotiation code that non-users also exercise, that is the case for waiting on the SEP, not the extension status by itself.

    Two things I would want pinned down before the code lands, since they are the parts that are hard to change once other SDKs copy the shape:

    1. The reconnect and cursor semantics on events/stream. Skipping the default request timeout and reconnecting with a cursor is correct for a long-lived stream, but the behaviour on a stream that drops without advancing the cursor needs to be explicit, otherwise a silent reconnect loop looks identical to an idle stream. Same for truncated: true: if the cursor does not advance past the truncated window, the client can spin.

    2. The 1000 ms floor on the poll loop. Worth documenting why it is a floor and not a debounce, because a client that drains on hasMore and re-polls immediately will otherwise hammer the server, and downstream implementers will copy whichever behaviour they read first.

    Following mcpkit where the document is silent is a reasonable default, and holding both to the same conformance rows is the right way to keep the reference and the Go implementation honest. This mirrors what we ran into building a single MCP server across many business APIs: the transport-level semantics (reconnect, cursor advance, error-vs-empty) are what other implementations copy, so they are worth getting explicit in the module even when the design doc leaves them open.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions