[SEP-2663] refactor!: remove 2025-11 experimental tasks (#2128)

This commit is contained in:
Felix Weinberger 2026-06-02 13:04:18 +01:00 • committed by GitHub
commit c8d7401b46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 423 additions and 18280 deletions

View file

@ -1118,13 +1118,6 @@ export const REQUIREMENTS: Record<string, Requirement> = {
'The client treats cursors as opaque tokens — it does not parse, modify, or persist them — and does not assume a fixed page size.'
},
// Tasks
'protocol:meta:related-task': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#related-task-metadata',
behavior: 'Messages may carry related-task _meta associating them with a task.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'protocol:meta:request-to-handler': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic#_meta',
behavior: "_meta sent in a request's params by the client is delivered intact to the server-side request handler."
@ -1177,265 +1170,6 @@ export const REQUIREMENTS: Record<string, Requirement> = {
note: 'Stateless hosting creates a fresh server per request and has no standalone GET stream, so there is no server→client channel to deliver/observe these.'
},
'tasks:auth:context-isolation': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-isolation-and-access-control',
behavior:
'When an authorization context is available, task operations are scoped to the context that created the task: other contexts cannot get it, retrieve its result, cancel it, or see it in tasks/list.',
transports: ['streamableHttp'],
note: 'This exercises the HTTP hosting/auth layer; the matrix transport arg is ignored, so it runs as a single streamableHttp-labelled cell to avoid duplicate runs.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:bidirectional': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#definitions',
behavior: 'Task APIs are bidirectional: the server may create, get, list, and cancel tasks on the client.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:cancel:no-handler-abort': {
source: 'sdk',
behavior:
'tasks/cancel marks the task cancelled without aborting the originating request handler (the spec says receivers SHOULD attempt to stop execution).',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:cancel:remains-cancelled': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-cancellation',
behavior: 'After tasks/cancel, the task remains cancelled even if the underlying handler subsequently completes or fails.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:cancel:terminal-rejected': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-cancellation',
behavior: 'tasks/cancel on a task already in a terminal state returns Invalid params (-32602).',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:cancel:working': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-cancellation',
behavior: 'tasks/cancel on a working task transitions it to cancelled and returns the updated task.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:create:ttl-honored': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#ttl-and-resource-management',
behavior:
'tasks/get responses include the actual ttl applied by the receiver (or null for unlimited); the create-task result carries the same value.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:create:via-tool-call': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#creating-tasks',
behavior: 'A task-augmented tools/call returns a create-task result instead of the tool result.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:get': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#getting-tasks',
behavior: "tasks/get returns the task's current status, ttl, timestamps, and status message.",
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:lifecycle:initial-working': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-status-lifecycle',
behavior: "A newly created task has status 'working'.",
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:lifecycle:input-required': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#input-required-status',
behavior:
'While a task awaits a side-channel client response its status is input_required; once the response arrives the task leaves input_required (typically returning to working).',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:list:invalid-cursor': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#protocol-errors',
behavior: 'tasks/list with an invalid cursor returns Invalid params (-32602).',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:list:pagination': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#listing-tasks',
behavior: 'tasks/list returns created tasks and supports cursor pagination.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:no-capability:ignore-task-param': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-support-and-handling',
behavior:
'A receiver that did not declare task capability for a request type processes the request normally and returns the ordinary result, ignoring the task augmentation.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:progress:after-create': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-progress-notifications',
behavior:
'After the create-task result, progress notifications keyed to the original progress token continue to reach the caller until the task is terminal.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:request-cancel:no-task-cancel': {
source: 'sdk',
behavior: 'A cancellation notification for the originating request does not auto-cancel the created task.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:result:failed': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-execution-errors',
behavior: 'tasks/result for a failed task returns the failure result (isError true).',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:result:related-task-meta': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#related-task-metadata',
behavior: 'The tasks/result response carries related-task _meta naming the requested task.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:result:terminal': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#result-retrieval',
behavior: 'tasks/result for a completed task returns the stored result of the original request type.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:side-channel:drain-fifo': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#input-required-status',
behavior: 'tasks/result drains queued related-task messages in FIFO order before returning the final result.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:side-channel:drop-on-cancel': {
source: 'sdk',
behavior: 'When a task is cancelled before tasks/result, queued related-task messages are dropped.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:side-channel:elicitation': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#input-required-status',
behavior:
"An elicitation issued mid-task is delivered through the tasks/result side-channel, and the client's response routes back to the handler.",
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:side-channel:queue': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#input-required-status',
behavior: 'Server-to-client requests with related-task metadata sent while no tasks/result is open are queued.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:side-channel:sampling': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#input-required-status',
behavior:
"A sampling request issued mid-task is delivered through the tasks/result side-channel, and the client's response routes back to the task.",
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:side-channel:stream': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#result-retrieval',
behavior:
'Calling tasks/result while the task is working streams related-task messages as they are produced, then returns the result.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:status-notification': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-status-notification',
behavior: 'Task status notifications deliver status updates carrying the full task fields.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:tool-level:forbidden-with-task-32601': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#tool-level-negotiation',
behavior: 'A task-augmented tools/call on a tool that does not support tasks returns Method not found (-32601).',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:tool-level:required-no-task-32601': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#tool-level-negotiation',
behavior: 'A plain tools/call on a tool that requires task augmentation returns Method not found (-32601).',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'tasks:unknown-id': {
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#protocol-errors',
behavior: 'tasks/get, tasks/result, and tasks/cancel for an unknown task id return Invalid params (-32602).',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
// Tasks: registerToolTask (SDK)
'mcpserver:tooltask:advertise': {
source: 'sdk',
behavior: 'registerToolTask tools advertise their execution.taskSupport in tools/list.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'mcpserver:tooltask:autopoll-cancelled': {
source: 'sdk',
behavior: 'Auto-polling surfaces a cancelled task as an error result.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'mcpserver:tooltask:autopoll-failed': {
source: 'sdk',
behavior: 'Auto-polling surfaces a task that ends failed as the failed CallToolResult.',
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'mcpserver:tooltask:forbidden-throws': {
source: 'sdk',
behavior: "registerToolTask throws at registration if taskSupport:'forbidden'.",
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'mcpserver:tooltask:optional-autopoll': {
source: 'sdk',
behavior:
"A taskSupport:'optional' tool called without task augmentation transparently creates and polls the task, returning the final CallToolResult.",
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'mcpserver:tooltask:required-with-task': {
source: 'sdk',
behavior: "A taskSupport:'required' tool called with task augmentation returns CreateTaskResult.",
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
'mcpserver:tooltask:required-without-task': {
source: 'sdk',
behavior: "A registerToolTask tool with taskSupport:'required' returns isError when called without task augmentation.",
deferred:
'Tasks are experimental and the spec is being substantially revised. Scenarios deferred until the next spec revision settles.'
},
// Client streaming API (SDK)
'client:stream:non-task-single': {
source: 'sdk',
behavior: 'requestStream() on a non-task request yields exactly the final result.',
deferred: 'client.stream() is a thin wrapper over tasks; deferred with tasks.'
},
'client:stream:task-elicitation': {
source: 'sdk',
behavior:
'callToolStream() over a task-augmented tool with mid-task elicitation delivers it to the client handler and yields the final result.',
deferred: 'client.stream() is a thin wrapper over tasks; deferred with tasks.'
},
'client:stream:terminal-error': {
source: 'sdk',
behavior:
'requestStream() yields a terminal error message and nothing further on server error, timeout, abort, network error, or task failure.',
deferred: 'client.stream() is a thin wrapper over tasks; deferred with tasks.'
},
'client:stream:tool-validation': {
source: 'sdk',
behavior:
'callToolStream() applies the same outputSchema validation as callTool(); mismatch yields an error, isError skips validation.',
deferred:
'client.stream() is a thin wrapper over tasks; deferred with tasks, part of the task-streaming work and not transport-specific.'
},
// McpServer reach-through (SDK)
'mcpserver:reach-through:set-request-handler': {
@ -2601,13 +2335,6 @@ export const REQUIREMENTS: Record<string, Requirement> = {
behavior:
'With enforceStrictCapabilities: true, calling a list method for a capability the server did not advertise rejects with a capability error instead of resolving empty.'
},
'tasks:result:failed-task-stored-result': {
source: 'sdk',
behavior:
'When a task-augmented tool call fails, the failure is stored with status failed and a subsequent tasks/result request for that task returns the stored error result instead of losing it.',
transports: STATEFUL_TRANSPORTS,
note: 'Task polling and result retrieval need the same server instance across requests, which stateless hosting does not provide.'
},
// Consumer-contract additions (sourced from real SDK dependents)
'client-transport:http:error-status-code': {
source: 'sdk',

View file

@ -1,109 +0,0 @@
/**
* Self-contained test bodies for experimental task-augmented tool calls
* (`tasks:` requirements): a tool whose asynchronous work fails after the
* server has already returned a CreateTaskResult must store that failure in
* the task store with status `failed`, and a later `tasks/result` request for
* the task must return the stored error result.
*/
import { Client } from '@modelcontextprotocol/client';
import { InMemoryTaskStore, isSpecType, McpServer, RELATED_TASK_META_KEY, specTypeSchemas } from '@modelcontextprotocol/server';
import { expect, vi } from 'vitest';
import { z } from 'zod/v4';
import { tapWire, wire } from '../helpers/index.js';
import { verifies } from '../helpers/verifies.js';
import type { TestArgs } from '../types.js';
verifies('tasks:result:failed-task-stored-result', async ({ transport }: TestArgs) => {
const taskStore = new InMemoryTaskStore();
try {
// The deployment work this tool kicks off always fails, so the handler's failure path runs.
const pushReleaseImage = async (version: string): Promise<void> => {
throw new Error(`Deploy of ${version} failed: image registry unreachable`);
};
const makeServer = () => {
const s = new McpServer(
{ name: 's', version: '0' },
{ capabilities: { tasks: { requests: { tools: { call: {} } }, taskStore } } }
);
s.experimental.tasks.registerToolTask(
'deploy-release',
{
description: 'Deploys a release image to the production fleet',
inputSchema: z.object({ version: z.string() })
},
{
async createTask({ version }, ctx) {
const task = await ctx.task.store.createTask({ ttl: 60_000, pollInterval: 50 });
const work = (async () => {
// Let the CreateTaskResult response go out before the work fails.
await new Promise(resolve => setTimeout(resolve, 10));
try {
await pushReleaseImage(version);
await ctx.task.store.storeTaskResult(task.taskId, 'completed', {
content: [{ type: 'text', text: `Deployed ${version}` }]
});
} catch (error) {
const text = error instanceof Error ? error.message : String(error);
await ctx.task.store.storeTaskResult(task.taskId, 'failed', {
content: [{ type: 'text', text }],
isError: true
});
}
})();
// A status notification emitted after teardown must not surface as an unhandled rejection.
work.catch(() => {});
return { task };
},
async getTask(_args, ctx) {
return await ctx.task.store.getTask(ctx.task.id);
},
async getTaskResult(_args, ctx) {
const stored = await ctx.task.store.getTaskResult(ctx.task.id);
const validated = specTypeSchemas.CallToolResult['~standard'].validate(stored);
if (validated.issues) {
throw new Error('stored task result is not a valid CallToolResult');
}
return validated.value;
}
}
);
return s;
};
const client = new Client({ name: 'c', version: '0' }, { capabilities: { tasks: {} } });
await using _ = await wire(transport, makeServer, client);
const tap = tapWire(client);
const createResult = await client.request({
method: 'tools/call',
params: { name: 'deploy-release', arguments: { version: '2.4.1' }, task: { ttl: 60_000 } }
});
if (!isSpecType.CreateTaskResult(createResult)) {
throw new Error('expected the task-augmented tools/call to return a CreateTaskResult');
}
const taskId = createResult.task.taskId;
// The failure must be recorded in the task store with terminal status 'failed', observable via tasks/get.
await vi.waitFor(
async () => {
const polled = await client.experimental.tasks.getTask(taskId);
expect(polled.status).toBe('failed');
},
{ timeout: 5000 }
);
// A subsequent tasks/result request returns the stored error result rather than losing it.
const stored = await client.experimental.tasks.getTaskResult(taskId);
expect(stored.isError).toBe(true);
expect(stored.content).toEqual([{ type: 'text', text: 'Deploy of 2.4.1 failed: image registry unreachable' }]);
expect(stored._meta?.[RELATED_TASK_META_KEY]).toEqual({ taskId });
const tasksResultRequests = tap.sent.filter(m => 'method' in m && m.method === 'tasks/result');
expect(tasksResultRequests).toHaveLength(1);
} finally {
taskStore.cleanup();
}
});