Skip to content

v2: modern-era Client drops inbound extension-method requests registered via setRequestHandler #2478

Description

@khandrew1

Summary

On a connection that negotiated a modern (2026-07-28) protocol version, Client drops every inbound JSON-RPC request — including consumer-owned extension methods (e.g. ui/*) that the client has explicitly registered a handler for via the documented 3-arg setRequestHandler(method, { params, result? }, handler) form. The handler silently never fires, no error response is written, and the sender hangs until its request timeout. The only signal is a local onerror on the receiving side.

This makes the documented custom-methods path (#1974, docs/advanced/custom-methods.md) unusable for any extension whose protocol includes peer→client requests, unless the consumer overrides the protected _shouldDropInbound hook — i.e. relies on non-public API.

Where this bites: MCP Apps (ext-apps)

I'm working on migrating modelcontextprotocol/ext-apps to v2 (modelcontextprotocol/ext-apps#702). Following the guidance from #1974 ("build custom behavior on Client or Server instead of subclassing Protocol") and the role assignment from earlier composition port (ext-apps#612), the iframe view composes a Client on the postMessage wire and the host composes a Server. The apps protocol legitimately sends host→view (i.e. server→client) requests such as ui/resource-teardown, which the view registers with the 3-arg setRequestHandler. On a modern-era connection these are dropped before handler lookup.

Code

The drop landed as part of the 2026-07-28 implementation (#2286):

  • packages/client/src/client/client.tsClient._shouldDropInbound returns 'drop' for any JSONRPCRequest once isModernProtocolVersion(this._negotiatedProtocolVersion) holds.
  • packages/core-internal/src/shared/protocol.ts_onrequest consults the hook before handler lookup, so registered custom handlers are unreachable.

The rationale in the code comments is clear and sound for spec methods: the 2026-07-28 era has no server→client request channel (SEP-2322 moved sampling/elicitation/roots into in-band input_required results), and on stdio a client must never write JSON-RPC responses.

The inconsistency

Elsewhere the SDK deliberately treats non-spec methods as era-blind: the era gates in protocol.ts only apply to spec methods, and wire/codec.ts describes anything outside the spec universe as "a consumer-owned extension method (era-blind, schema-explicit)". The inbound drop is the one place where extension traffic is subjected to a spec-era direction rule. Meanwhile setRequestHandler("ui/foo", …) on a Client succeeds without warning, producing a handler that can never run — arguably a footgun independent of the extension use case.

Possible approaches

  1. Consult registered handlers before dropping: in Client._shouldDropInbound (or in _onrequest), dispatch inbound requests whose method has an explicitly registered non-spec handler; keep the drop for spec methods and unhandled traffic. This preserves the direction discipline the drop was added for while making the documented custom-methods API self-consistent.
  2. Opt-in surface on ClientOptions: e.g. an inboundRequestMethods: string[] | (method: string) => boolean allowlist, defaulting to the current drop-everything behavior.
  3. Bless the override point: document _shouldDropInbound as a supported protected extension seam for protocol dialects.
  4. Spec-level clarification: state whether extensions (SEP-2133 / capabilities.extensions) may define peer→client requests on the modern era at all — if the answer is no, ext-apps needs a different shape (notifications + view-initiated requests only), and that's worth knowing before the port.

Option 1 seems most aligned with how the rest of v2 treats extension methods, and I'm happy to put up a PR for whichever direction maintainers prefer.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions