Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions src/scenarios/server/tasks/capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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' } },
{
Expand All @@ -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>'}`
);
}
}
Expand Down
25 changes: 11 additions & 14 deletions src/scenarios/server/tasks/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}!"
Expand All @@ -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';
Expand All @@ -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):**
Expand Down Expand Up @@ -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',
Expand All @@ -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) {
Expand All @@ -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]
});
}
Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -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]
});
}
Expand Down
43 changes: 22 additions & 21 deletions src/scenarios/server/tasks/required-task-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@
* 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.
*
* 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.
*
Expand All @@ -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:**
Expand All @@ -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.

Expand All @@ -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\`.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(() => {});
Expand Down Expand Up @@ -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({
Expand All @@ -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 (
Expand Down
14 changes: 7 additions & 7 deletions src/seps/sep-2663.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 ─────────────────────────────────────────────────
Expand Down
Loading