[SEP-2663] refactor!: remove 2025-11 experimental tasks (#2128)
This commit is contained in:
parent
600ba75f34
commit
c8d7401b46
2 changed files with 423 additions and 18280 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
Loading…
Reference in a new issue