modelcontextprotocol / modelcontextprotocol/typescript-sdk
subscriptions/listen cannot carry extension notifications (blocks notifications/tasks)
Nobody has claimed this yet.
- 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/listenmechanism.
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:
- Make
SubscriptionFilterSchemaalooseObject, and widenServerEventwith an
extension-carrying variant, e.g.{ kind: 'extension'; method: string; params: unknown }, with a
matchingServerNotifier.extension(method, params). - Add a registration hook so an extension can contribute a filter key plus an event kind.
- Document that extension notifications must be served ahead of
createMcpHandler, and export
enough of the listen-stream machinery (ack framing,SUBSCRIPTION_ID_META_KEYstamping,
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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