Skip to content

v2: extension methods shadowed by legacy spec-method registry — custom tasks/get & tasks/cancel handlers unreachable (-32601 before handler lookup) #2598

Description

@czh2774

Summary

On SDK v2 (@modelcontextprotocol/server@2.0.0 + @modelcontextprotocol/node@2.0.0), a server implementing the Tasks extension (io.modelcontextprotocol/tasks, SEP-2663) cannot serve tasks/get or tasks/cancel: requests to these methods are rejected with -32601 before custom request handlers are consulted, even when a handler is registered and the client declares the tasks extension capability.

Root cause

packages/core-internal/src/shared/protocol.ts gates incoming requests with:

if (isSpecRequestMethod(request.method) && !codec.hasRequestMethod(request.method)) {
    // → -32601 before custom handler lookup
}

isSpecRequestMethod() (packages/core-internal/src/wire/codec.ts) answers against the union of the 2025 and 2026 method registries. tasks/get and tasks/cancel are in that union — they existed in the 2025-11-25 revision as experimental core methods — but the 2026-07-28 codec does not contain them, because tasks moved out of core into the extension. Result:

  • tasks/get / tasks/cancel → "spec method" (per the legacy registry) + "not in modern codec" → rejected with -32601 before the handler table is checked.
  • tasks/update (a name new in the redesigned extension, absent from the legacy registry) → passes the gate, and a custom handler registered via server.server.setRequestHandler(...) works fine.

So any extension method that happens to collide with a historical core method name is unservable in a modern-era session, while brand-new names work. The asymmetry between tasks/update (works) and tasks/get/tasks/cancel (unreachable) makes this easy to verify.

Repro sketch

import { McpServer } from '@modelcontextprotocol/server';
// server advertises { extensions: { 'io.modelcontextprotocol/tasks': {} } } in server/discover

server.server.setRequestHandler('tasks/update', updateSchema, handler); // ✅ reachable
server.server.setRequestHandler('tasks/get', getSchema, handler);       // registered, but…

// client (2026-07-28 _meta envelope, tasks extension declared in clientCapabilities):
// → tasks/update: handler runs
// → tasks/get:    -32601 (handler never consulted)

Impact

The officially specified Tasks extension polling flow (tasks/get) and cooperative cancellation (tasks/cancel) cannot be implemented on SDK v2 without bypassing the SDK. We currently work around it by intercepting these two methods at the HTTP layer (routing on the Mcp-Method request header before the request reaches the SDK), which we would very much like to delete.

Possible directions

  1. Scope the gate to the negotiated era's registry only — don't reject a method merely because an older revision once defined it (the modern codec's ignorance of a legacy name shouldn't outrank an explicitly registered handler).
  2. Or: let explicitly registered custom handlers take precedence over the spec-method gate.
  3. Or: when a server advertises an extension, admit that extension's method names into hasRequestMethod for the session.

Option 1 or 2 also future-proofs other extensions whose method names were ever part of a historical core revision.

Related: #2189 (Tasks extension tracking), #2188 (extensions framework), #2528 / #2569 (extension notifications in subscriptions/listen — the notification-side sibling of this request-side gap).

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