fix(sep-2663): use renumbered draft error-code constants in tasks scenarios (#356)

The tasks extension scenarios hardcoded -32003 (Missing Required Client
Capability) and -32001 (Header Mismatch). The draft spec types renumbered
these to -32021 and -32020 per modelcontextprotocol#2907, so a server
built against current draft.ts now returns the new codes and these checks
would produce false failures.

Import MISSING_REQUIRED_CLIENT_CAPABILITY and HEADER_MISMATCH from
spec-types/draft instead of hardcoding, matching the existing
http-standard-headers scenario. The checks now assert whatever the schema
currently defines, so a future renumber propagates automatically.

Also drop the now-stale code number from the affected check-ID slugs
(for example sep-2663-server-returns-32003-when-required becomes
sep-2663-server-returns-missing-capability-when-required) and update the
sep-2663.yaml requirement text to -32021, keeping it consistent with the
already-updated sep-2575 and sep-2243 manifests.

Scenarios are still in the pending list so default CI is unaffected. The
traceability.json manifest already predates the upstream renumber and is
refreshed separately from a full suite run.
This commit is contained in:
Sri Panyam 2026-06-22 03:55:23 -07:00 • committed by GitHub
commit bb11cff348
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 51 additions and 52 deletions

View file

@ -12,6 +12,7 @@
import { ClientScenario, ConformanceCheck } from '../../../types';
import type { Connection, RunContext } from '../../../connection';
import { MISSING_REQUIRED_CLIENT_CAPABILITY } from '../../../spec-types/draft';
import { SEP_2575_REF, SEP_2663_REF } from './mrtr-helpers';
import { errMsg, failureCheck } from './mrtr-helpers';
import { TASKS_EXTENSION_ID } from './helpers';
@ -117,16 +118,16 @@ export class TasksCapabilityNegotiationScenario implements ClientScenario {
});
}
// Check 2: tasks/* methods rejected with -32003 (Missing Required
// Client Capability) when the client did not negotiate the tasks
// extension. Follows the SEP-2575 §"Missing Required Capabilities"
// pattern — same code path as the required-task-error scenario and
// (when implemented) subscriptions/listen for tasks.
// Check 2: tasks/* methods rejected with the
// MissingRequiredClientCapability error when the client did not
// negotiate the tasks extension. Follows the SEP-2575 §"Missing
// Required Capabilities" pattern — same code path as the
// required-task-error scenario and (when implemented)
// subscriptions/listen for tasks.
{
const id = 'sep-2663-tasks-methods-non-declaring-32003';
const id = 'sep-2663-tasks-methods-non-declaring';
const name = 'TasksMethodsGatedWithoutExtension';
const description =
'tasks/get, tasks/update, tasks/cancel return -32003 when the client did not negotiate the tasks extension (SEP-2575 Missing Required Capabilities)';
const description = `tasks/get, tasks/update, tasks/cancel return ${MISSING_REQUIRED_CLIENT_CAPABILITY} when the client did not negotiate the tasks extension (SEP-2575 Missing Required Capabilities)`;
const cases: Array<{ method: string; params: any }> = [
{ method: 'tasks/get', params: { taskId: 'gate-test' } },
{
@ -141,9 +142,9 @@ export class TasksCapabilityNegotiationScenario implements ClientScenario {
await withoutExt.request(tc.method, tc.params);
errs.push(`${tc.method} MUST reject (it returned a result)`);
} catch (e: any) {
if (e.code !== -32003) {
if (e.code !== MISSING_REQUIRED_CLIENT_CAPABILITY) {
errs.push(
`${tc.method} MUST return -32003; got ${e.code ?? '<missing>'}`
`${tc.method} MUST return ${MISSING_REQUIRED_CLIENT_CAPABILITY}; got ${e.code ?? '<missing>'}`
);
}
}

View file

@ -7,13 +7,13 @@
* to route or shape JSON-RPC traffic without parsing the body. The
* server MUST reject requests where the routing headers disagree with
* (or are missing for a name-carrying body) the JSON-RPC envelope, with
* HTTP 400 + JSON-RPC `-32001 HeaderMismatch`.
* HTTP 400 + JSON-RPC `-32020 HeaderMismatch`.
*
* This scenario exercises the validation on the tasks surface
* specifically — the upstream `http-header-validation` scenario covers
* the general case; here we verify mcpkit's tasks/* methods route
* through the same validator (matched headers → success; mismatched
* header → -32001).
* header → -32020).
*
* Required server fixtures:
* - greet — sync-only, returns "Hello, {name}!"
@ -24,11 +24,12 @@ import { McpError } from '@modelcontextprotocol/sdk/types.js';
import { ClientScenario, ConformanceCheck } from '../../../types';
import type { Connection, RunContext } from '../../../connection';
import { HEADER_MISMATCH } from '../../../spec-types/draft';
import { SEP_2243_REF, SEP_2663_REF } from './mrtr-helpers';
import { errMsg, failureCheck } from './mrtr-helpers';
import { TASKS_EXTENSION_ID } from './helpers';
const HEADER_MISMATCH_ERROR_CODE = -32001;
const HEADER_MISMATCH_ERROR_CODE = HEADER_MISMATCH;
export class TasksRequestHeadersScenario implements ClientScenario {
name = 'tasks-request-headers';
@ -53,7 +54,7 @@ into the HTTP layer for routing intermediaries:
Per SEP-2243 §"Server Behavior", servers that process the request body
MUST validate that header values match the body. Per its "Validation
Failure Conditions", both missing required headers and mismatched
values trigger rejection with JSON-RPC error code \`-32001\`
values trigger rejection with JSON-RPC error code \`-32020\`
(HeaderMismatch) and HTTP 400.
**Required server fixtures (\`tools/list\` MUST include all):**
@ -190,8 +191,7 @@ values trigger rejection with JSON-RPC error code \`-32001\`
{
const id = 'tasks-headers-reject-mismatched-method';
const name = 'TasksHeadersRejectMismatchedMethod';
const description =
'When Mcp-Method header disagrees with body on a tools/call, server MUST reject with -32001 HeaderMismatch (SEP-2243 §Server Validation)';
const description = `When Mcp-Method header disagrees with body on a tools/call, server MUST reject with ${HEADER_MISMATCH_ERROR_CODE} HeaderMismatch (SEP-2243 §Server Validation)`;
try {
await conn.request(
'tools/call',
@ -204,8 +204,7 @@ values trigger rejection with JSON-RPC error code \`-32001\`
description,
status: 'FAILURE',
timestamp: new Date().toISOString(),
errorMessage:
'tools/call with Mcp-Method: tasks/get returned a successful response; SEP-2243 requires -32001 rejection',
errorMessage: `tools/call with Mcp-Method: tasks/get returned a successful response; SEP-2243 requires ${HEADER_MISMATCH_ERROR_CODE} rejection`,
specReferences: [SEP_2243_REF]
});
} catch (error) {
@ -227,7 +226,7 @@ values trigger rejection with JSON-RPC error code \`-32001\`
description,
status: 'FAILURE',
timestamp: new Date().toISOString(),
errorMessage: `expected -32001 HeaderMismatch; got ${code ?? errMsg(error)}`,
errorMessage: `expected ${HEADER_MISMATCH_ERROR_CODE} HeaderMismatch; got ${code ?? errMsg(error)}`,
specReferences: [SEP_2243_REF]
});
}
@ -244,8 +243,7 @@ values trigger rejection with JSON-RPC error code \`-32001\`
{
const id = 'sep-2663-server-rejects-mismatched-mcp-name-on-tasks-get';
const name = 'Sep2663ServerRejectsMismatchedMcpNameOnTasksGet';
const description =
'When Mcp-Name header disagrees with params.taskId on tasks/get, server MUST reject with -32001 HeaderMismatch';
const description = `When Mcp-Name header disagrees with params.taskId on tasks/get, server MUST reject with ${HEADER_MISMATCH_ERROR_CODE} HeaderMismatch`;
if (!routingTaskId) {
checks.push({
id,
@ -270,8 +268,7 @@ values trigger rejection with JSON-RPC error code \`-32001\`
description,
status: 'FAILURE',
timestamp: new Date().toISOString(),
errorMessage:
'tasks/get with mismatched Mcp-Name returned a result; SEP-2243 requires -32001 rejection',
errorMessage: `tasks/get with mismatched Mcp-Name returned a result; SEP-2243 requires ${HEADER_MISMATCH_ERROR_CODE} rejection`,
specReferences: [SEP_2243_REF, SEP_2663_REF]
});
} catch (error) {
@ -293,7 +290,7 @@ values trigger rejection with JSON-RPC error code \`-32001\`
description,
status: 'FAILURE',
timestamp: new Date().toISOString(),
errorMessage: `expected -32001 HeaderMismatch; got ${code ?? errMsg(error)}`,
errorMessage: `expected ${HEADER_MISMATCH_ERROR_CODE} HeaderMismatch; got ${code ?? errMsg(error)}`,
specReferences: [SEP_2243_REF, SEP_2663_REF]
});
}

View file

@ -2,12 +2,13 @@
* SEP-2663 Tasks Extension — required-task error conformance.
*
* SEP-2575 (Stateless MCP) §"Missing Required Capabilities" defines
* JSON-RPC error code `-32003` for the case where a server cannot
* service a request without a client capability the client did not
* declare. SEP-2663 §"Required Capabilities" applies that rule to the
* tasks extension: if a tool's declared task support is "required" and
* the client did not declare `io.modelcontextprotocol/tasks` during
* `initialize`, the server MUST reject with `-32003`. The error data
* the `MissingRequiredClientCapability` JSON-RPC error code (-32021) for
* the case where a server cannot service a request without a client
* capability the client did not declare. SEP-2663 §"Required
* Capabilities" applies that rule to the tasks extension: if a tool's
* declared task support is "required" and the client did not declare
* `io.modelcontextprotocol/tasks` during `initialize`, the server MUST
* reject with that error. The error data
* SHOULD carry a `requiredCapabilities` object whose shape mirrors the
* `InitializeRequest` capabilities, so the client can self-describe
* what to add without needing out-of-band documentation.
@ -15,7 +16,7 @@
* This scenario verifies the failure path:
* 1. Initialize a session WITHOUT declaring the tasks extension.
* 2. Call a tool whose task support is `required`.
* 3. Expect a JSON-RPC error with `code: -32003` and
* 3. Expect a JSON-RPC error with `code: -32021` and
* `data.requiredCapabilities.extensions["io.modelcontextprotocol/tasks"]`
* present.
*
@ -31,17 +32,17 @@ import { McpError } from '@modelcontextprotocol/sdk/types.js';
import { ClientScenario, ConformanceCheck } from '../../../types';
import type { Connection, RunContext } from '../../../connection';
import { MISSING_REQUIRED_CLIENT_CAPABILITY } from '../../../spec-types/draft';
import { SEP_2575_REF, SEP_2663_REF } from './mrtr-helpers';
import { errMsg } from './mrtr-helpers';
import { TASKS_EXTENSION_ID } from './helpers';
const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003;
const REQUIRED_TASK_TOOL = 'failing_job';
export class TasksRequiredTaskErrorScenario implements ClientScenario {
name = 'tasks-required-task-error';
readonly source = { extensionId: 'io.modelcontextprotocol/tasks' } as const;
description = `Verify the -32003 error path for required-task tools when the
description = `Verify the -32021 error path for required-task tools when the
client has not negotiated the io.modelcontextprotocol/tasks extension.
**Server Implementation Requirements:**
@ -51,7 +52,7 @@ Capabilities":
> If a server is unable to service a request to a client that does not
> declare this extension capability without returning \`CreateTaskResult\`,
> the server MUST return an error with the code \`-32003\` (Missing
> the server MUST return an error with the code \`-32021\` (Missing
> Required Client Capability), indicating the required extension in the
> error response.
@ -70,7 +71,7 @@ shape mirrors \`InitializeRequest.capabilities\`, e.g.
The scenario calls \`tools/call\` for a tool registered with task support
\`required\` from a client that did NOT declare the extension. A
conformant server MUST reject with \`-32003\`.
conformant server MUST reject with \`-32021\`.
**Required server fixtures (\`tools/list\` MUST include all):**
- \`failing_job\` — registered with task support declared as \`required\`.
@ -102,10 +103,11 @@ conformant server MUST reject with \`-32003\`.
return checks;
}
// Check 1: tools/call for a required-task tool returns -32003.
const id = 'sep-2663-server-returns-32003-when-required';
const name = 'Sep2663ServerReturns32003WhenRequired';
const description = `tools/call for a TaskSupport=required tool MUST reject with code -32003 (Missing Required Client Capability) when the client did not declare ${TASKS_EXTENSION_ID}`;
// Check 1: tools/call for a required-task tool returns the
// MissingRequiredClientCapability error.
const id = 'sep-2663-server-returns-missing-capability-when-required';
const name = 'Sep2663ServerReturnsMissingCapabilityWhenRequired';
const description = `tools/call for a TaskSupport=required tool MUST reject with code ${MISSING_REQUIRED_CLIENT_CAPABILITY} (Missing Required Client Capability) when the client did not declare ${TASKS_EXTENSION_ID}`;
let observed: { code?: number; data?: unknown } = {};
let errored = false;
@ -134,7 +136,7 @@ conformant server MUST reject with \`-32003\`.
description,
status: 'FAILURE',
timestamp: new Date().toISOString(),
errorMessage: `tools/call for ${REQUIRED_TASK_TOOL} returned a successful response from a client that did not declare ${TASKS_EXTENSION_ID}; spec requires -32003 rejection in this case.`,
errorMessage: `tools/call for ${REQUIRED_TASK_TOOL} returned a successful response from a client that did not declare ${TASKS_EXTENSION_ID}; spec requires ${MISSING_REQUIRED_CLIENT_CAPABILITY} rejection in this case.`,
specReferences: [SEP_2575_REF, SEP_2663_REF]
});
await conn.close().catch(() => {});
@ -170,9 +172,9 @@ conformant server MUST reject with \`-32003\`.
// canonical error example in the spec shows this shape; flag a
// FAILURE only when the field shape is broken, not when it's absent.
{
const id2 = 'sep-2663-server-returns-32003-data-shape';
const name2 = 'Sep2663ServerReturns32003DataShape';
const description2 = `Error data for -32003 SHOULD carry data.requiredCapabilities.extensions["${TASKS_EXTENSION_ID}"]`;
const id2 = 'sep-2663-server-returns-missing-capability-data-shape';
const name2 = 'Sep2663ServerReturnsMissingCapabilityDataShape';
const description2 = `Error data for ${MISSING_REQUIRED_CLIENT_CAPABILITY} SHOULD carry data.requiredCapabilities.extensions["${TASKS_EXTENSION_ID}"]`;
const data = observed.data as any;
if (data === undefined || data === null) {
checks.push({
@ -181,8 +183,7 @@ conformant server MUST reject with \`-32003\`.
description: description2,
status: 'INFO',
timestamp: new Date().toISOString(),
errorMessage:
'Error returned -32003 but carried no `data` field; the spec example shows requiredCapabilities, but data is SHOULD.',
errorMessage: `Error returned ${MISSING_REQUIRED_CLIENT_CAPABILITY} but carried no \`data\` field; the spec example shows requiredCapabilities, but data is SHOULD.`,
specReferences: [SEP_2575_REF, SEP_2663_REF]
});
} else if (

View file

@ -11,17 +11,17 @@ requirements:
- check: sep-2663-client-rejects-task-result-on-unsupported
text: 'A client that receives CreateTaskResult in response to an unsupported request type MUST interpret this as an invalid response to the request.'
# The spec sentence "MUST return an error with the code -32003 ...,
# The spec sentence "MUST return an error with the code -32021 ...,
# indicating the required extension in the error response" couples two
# observable requirements: the JSON-RPC error code, and the data-shape
# that names the missing extension. Split into two YAML entries so each
# is verifiable independently — Check 1 fires on code mismatch, Check 2
# fires on payload shape.
- check: sep-2663-server-returns-32003-when-required
text: 'If a server is unable to service a request to a client that does not declare this extension capability without returning CreateTaskResult, the server MUST return an error with the code -32003 (Missing Required Client Capability).'
- check: sep-2663-server-returns-missing-capability-when-required
text: 'If a server is unable to service a request to a client that does not declare this extension capability without returning CreateTaskResult, the server MUST return an error with the code -32021 (Missing Required Client Capability).'
- check: sep-2663-server-returns-32003-data-shape
text: 'The -32003 error response MUST indicate the required extension in the error response payload (the spec example shows a `data.requiredCapabilities.extensions["io.modelcontextprotocol/tasks"]` shape).'
- check: sep-2663-server-returns-missing-capability-data-shape
text: 'The -32021 error response MUST indicate the required extension in the error response payload (the spec example shows a `data.requiredCapabilities.extensions["io.modelcontextprotocol/tasks"]` shape).'
# ── Polymorphic Results ───────────────────────────────────────────────────
- check: sep-2663-result-type-task-on-create
@ -93,7 +93,7 @@ requirements:
text: 'The resultType field MUST be set to "complete" on CancelTaskResult as it is the standard result shape for the tasks/cancel request.'
# ── Task Status Notifications ─────────────────────────────────────────────
- check: sep-2663-subscribe-32003-non-declaring
- check: sep-2663-subscribe-missing-capability-non-declaring
text: 'If a client requests task status notifications but does not declare the io.modelcontextprotocol/tasks extension capability, the server MUST return a JSON-RPC error specifying the missing capabilities:'
- check: sep-2663-no-progress-or-message-on-task-stream
@ -124,7 +124,7 @@ requirements:
- check: sep-2663-tasks-update-cancel-invalid-task-id-32602
text: 'Servers SHOULD return this error for tasks/update and tasks/cancel.'
- check: sep-2663-tasks-methods-non-declaring-32003
- check: sep-2663-tasks-methods-non-declaring
text: 'Servers MUST return this error for non-declaring clients issuing tasks/get, tasks/update, and tasks/cancel requests.'
# ── Task Execution Errors ─────────────────────────────────────────────────