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:
parent
d70d7ad475
commit
bb11cff348
4 changed files with 51 additions and 52 deletions
|
|
@ -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>'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 ─────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Reference in a new issue