modelcontextprotocol / modelcontextprotocol/typescript-sdk

subscriptions/listen cannot carry extension notifications (blocks notifications/tasks)

Open
#2,569 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement needs design spec-2026-07-28 v2
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

subscriptions/listen cannot carry extension notifications (blocks notifications/tasks)

Package: @modelcontextprotocol/server@2.0.0
Spec: 2026-07-28 · Tasks extension (io.modelcontextprotocol/tasks, SEP-2663)

Summary

The Tasks extension specifies that clients opt into task status notifications through
subscriptions/listen:

Servers can push status updates via notifications/tasks. Clients opt into these through the
subscriptions/listen mechanism.

The SDK's subscriptions/listen implementation cannot express this, in both directions.

Detail

1. The filter is a strict, closed object. SubscriptionFilterSchema admits exactly four keys:

const SubscriptionFilterSchema = z.object({
  toolsListChanged: z.boolean().optional(),
  promptsListChanged: z.boolean().optional(),
  resourcesListChanged: z.boolean().optional(),
  resourceSubscriptions: z.array(z.string()).optional(),
});

It is z.object, not z.looseObject, so an extension-namespaced key such as
"io.modelcontextprotocol/tasks": true does not survive parsing. A client has no way to opt in.

2. ServerEvent is a closed union over the same four kinds.

type ServerEvent =
  | { kind: 'tools_list_changed' }
  | { kind: 'prompts_list_changed' }
  | { kind: 'resources_list_changed' }
  | { kind: 'resource_updated'; uri: string };

ServerEventBus.publish accepts only these, and ServerNotifier exposes only the matching four
methods. A server has no way to publish a task event onto an open stream.

3. The only task notification in the method registry is the 2025 name.
notificationMethodKeys carries notifications/tasks/status (2025-11-25 vocabulary, already marked
@deprecated elsewhere in the SDK), not the extension's notifications/tasks.

Net effect: subscriptions/listen is usable for the four core change types and closed to every
extension, including the one official extension that depends on it.

Reproduction

Send a subscriptions/listen whose filter contains an extension key:

{"jsonrpc":"2.0","id":1,"method":"subscriptions/listen",
 "params":{"notifications":{"io.modelcontextprotocol/tasks":true}}}

The stream opens, returns HTTP 200 and a well-formed acknowledgement carrying
"notifications": {} — an empty filter. No error is raised on either side; the client waits
indefinitely for events it believes it subscribed to.

(Worth noting independently: the same silent-empty-filter behaviour occurs for core keys when the
server has not declared the corresponding listChanged capability. An ack echoing an empty filter
after a non-empty request is difficult to distinguish from a working subscription. A warning, or
echoing which requested keys were dropped, would make that failure visible.)

Suggested direction

Any one of:

  1. Make SubscriptionFilterSchema a looseObject, and widen ServerEvent with an
    extension-carrying variant, e.g. { kind: 'extension'; method: string; params: unknown }, with a
    matching ServerNotifier.extension(method, params).
  2. Add a registration hook so an extension can contribute a filter key plus an event kind.
  3. Document that extension notifications must be served ahead of createMcpHandler, and export
    enough of the listen-stream machinery (ack framing, SUBSCRIPTION_ID_META_KEY stamping,
    graceful close) that a third-party implementation stays wire-compatible.

Workaround in use

We serve the extension's stream ourselves, in front of createMcpHandler, keyed on the extension
identifier so it cannot collide with a future core key; core subscriptions/listen requests are
passed through untouched. This is the same seam we already use for tasks/get / tasks/cancel /
tasks/update, which the handler likewise rejects as unknown methods.

Happy to open a PR for option 1 if that direction is welcome.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading SubscriptionFilterSchema, ServerEvent, ServerEventBus.publish, ServerNotifier, and notificationMethodKeys, then run the subscriptions/listen reproduction from the issue. Determine which supported direction is chosen for extension filters and events, and verify that task subscriptions are preserved, published, acknowledged, and closed with wire-compatible behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend-api-design
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.