diff --git a/examples/clients/typescript/everything-client.ts b/examples/clients/typescript/everything-client.ts index ef272e4a..2c9e8425 100644 --- a/examples/clients/typescript/everything-client.ts +++ b/examples/clients/typescript/everything-client.ts @@ -265,12 +265,8 @@ async function runRequestMetadataClient(serverUrl: string): Promise { const clone = response.clone(); try { const errorResult = await clone.json(); - // -32004 is UnsupportedProtocolVersionError in the draft schema; - // -32001 is tolerated for servers that predate the dedicated code. - if ( - errorResult.error?.code === -32004 || - errorResult.error?.code === -32001 - ) { + // UnsupportedProtocolVersionError is -32022 in the draft schema. + if (errorResult.error?.code === -32022) { logger.debug( 'Received UnsupportedProtocolVersionError, starting negotiation...' ); diff --git a/examples/servers/typescript/everything-server.ts b/examples/servers/typescript/everything-server.ts index f8a02720..52b4b6a0 100644 --- a/examples/servers/typescript/everything-server.ts +++ b/examples/servers/typescript/everything-server.ts @@ -1260,7 +1260,7 @@ app.post('/mcp', async (req, res) => { return res.status(400).json({ jsonrpc: '2.0', id, - error: { code: -32001, message: 'Missing MCP-Protocol-Version header' } + error: { code: -32020, message: 'Missing MCP-Protocol-Version header' } }); } @@ -1283,25 +1283,25 @@ app.post('/mcp', async (req, res) => { }); } - // Header Mismatch Verification (-32001, HTTP 400) + // Header Mismatch Verification (-32020, HTTP 400) if (reqVersion !== metaVersion) { return res.status(400).json({ jsonrpc: '2.0', id, error: { - code: -32001, + code: -32020, message: 'Mismatched MCP-Protocol-Version header' } }); } - // Protocol Version Negotiation Matrix (-32004, HTTP 400) + // Protocol Version Negotiation Matrix (-32022, HTTP 400) if (metaVersion !== '2026-07-28') { return res.status(400).json({ jsonrpc: '2.0', id, error: { - code: -32004, + code: -32022, message: 'UnsupportedProtocolVersionError', data: { supported: ['2026-07-28'], @@ -1655,13 +1655,13 @@ app.post('/mcp', async (req, res) => { if (name === 'test_missing_capability') { const clientCaps = meta['io.modelcontextprotocol/clientCapabilities']; - // Missing Required Client Capability Check (-32003, HTTP 400) + // Missing Required Client Capability Check (-32021, HTTP 400) if (!clientCaps?.sampling) { return res.status(400).json({ jsonrpc: '2.0', id, error: { - code: -32003, + code: -32021, message: 'MissingRequiredClientCapabilityError', data: { requiredCapabilities: ['sampling'] } } diff --git a/src/mock-server/mock-server.test.ts b/src/mock-server/mock-server.test.ts index 8af684ca..2d26cf54 100644 --- a/src/mock-server/mock-server.test.ts +++ b/src/mock-server/mock-server.test.ts @@ -100,7 +100,7 @@ describe('validateStatelessRequest', () => { }); }); - it('rejects versions outside the supported list with -32004 and echoes it', () => { + it('rejects versions outside the supported list with -32022 and echoes it', () => { const v = validateStatelessRequest( { headers: { 'mcp-protocol-version': '2099-01-01' }, @@ -124,7 +124,7 @@ describe('validateStatelessRequest', () => { status: 400, body: { error: { - code: -32004, + code: -32022, data: { supported: [DRAFT_PROTOCOL_VERSION], requested: '2099-01-01' } } } @@ -216,7 +216,7 @@ describe('createServerStateless', () => { params: { _meta: meta } }); expect(status).toBe(400); - expect(body.error.code).toBe(-32001); + expect(body.error.code).toBe(-32020); } finally { await srv.close(); } @@ -258,7 +258,7 @@ describe('createServerStateless', () => { } }); - it('accepts the version it was created for and rejects others with -32004', async () => { + it('accepts the version it was created for and rejects others with -32022', async () => { const srv = await createServerStateless( { 'tools/list': () => ({ tools: [] }) }, DRAFT_PROTOCOL_VERSION @@ -292,7 +292,7 @@ describe('createServerStateless', () => { { 'mcp-protocol-version': '2099-01-01' } ); expect(rejected.status).toBe(400); - expect(rejected.body.error.code).toBe(-32004); + expect(rejected.body.error.code).toBe(-32022); expect(rejected.body.error.data.supported).toEqual([ DRAFT_PROTOCOL_VERSION ]); diff --git a/src/mock-server/stateless.ts b/src/mock-server/stateless.ts index 904d0e03..2a69f987 100644 --- a/src/mock-server/stateless.ts +++ b/src/mock-server/stateless.ts @@ -84,7 +84,7 @@ export type StatelessValidation = * `auth/helpers/createServer.ts`) uses the same validation as this module. * * `supportedVersions` is the list of wire protocolVersion strings this - * endpoint accepts; anything else is rejected with -32004 carrying + * endpoint accepts; anything else is rejected with -32022 carrying * `{ supported, requested }` in the error data, and the list is echoed in * the `server/discover` result. */ @@ -108,7 +108,7 @@ export function validateStatelessRequest( const headerVersion = req.headers['mcp-protocol-version']; if (!headerVersion) { - return reject(400, -32001, 'Missing MCP-Protocol-Version header'); + return reject(400, -32020, 'Missing MCP-Protocol-Version header'); } const missing = META_KEYS.filter((k) => meta?.[k] === undefined); if (missing.length > 0) { @@ -121,7 +121,7 @@ export function validateStatelessRequest( if (meta?.[META_KEYS[0]] !== headerVersion) { return reject( 400, - -32001, + -32020, 'MCP-Protocol-Version header does not match _meta.protocolVersion' ); } @@ -136,7 +136,7 @@ export function validateStatelessRequest( jsonrpc: '2.0', id, error: { - code: -32004, + code: -32022, message: 'Unsupported protocol version', data: { supported: supportedVersions, diff --git a/src/scenarios/client/draft-result-fields.test.ts b/src/scenarios/client/draft-result-fields.test.ts index 54f6f9be..1fdc6d44 100644 --- a/src/scenarios/client/draft-result-fields.test.ts +++ b/src/scenarios/client/draft-result-fields.test.ts @@ -238,7 +238,7 @@ describe('request-metadata mock results (2026-07-28)', () => { ); const headers = { 'MCP-Protocol-Version': DRAFT_PROTOCOL_VERSION }; try { - // The first request is always answered with the simulated -32004 + // The first request is always answered with the simulated -32022 // rejection (retry probe); results are served from the second on. const first = await post( serverUrl, diff --git a/src/scenarios/client/request-metadata.test.ts b/src/scenarios/client/request-metadata.test.ts index e2046fed..567d1caf 100644 --- a/src/scenarios/client/request-metadata.test.ts +++ b/src/scenarios/client/request-metadata.test.ts @@ -105,7 +105,7 @@ async function incompatibleVersionClient(serverUrl: string) { if (response.status === 400) { const body = await response.json(); - if (body.error?.code === -32004) { + if (body.error?.code === -32022) { return body; // Abort cleanly } } diff --git a/src/scenarios/client/request-metadata.ts b/src/scenarios/client/request-metadata.ts index 4d66f2cb..61dc5e11 100644 --- a/src/scenarios/client/request-metadata.ts +++ b/src/scenarios/client/request-metadata.ts @@ -282,7 +282,7 @@ export class RequestMetadataScenario implements Scenario { id: request.id ?? null, error: { // UnsupportedProtocolVersionError per the draft schema. - code: -32004, + code: -32022, message: 'Unsupported protocol version', data: { supported: [DRAFT_PROTOCOL_VERSION], diff --git a/src/scenarios/index.ts b/src/scenarios/index.ts index b7230ab8..c9857ec2 100644 --- a/src/scenarios/index.ts +++ b/src/scenarios/index.ts @@ -120,7 +120,7 @@ const pendingClientScenariosList: ClientScenario[] = [ // HTTP Standardization (SEP-2243) // Pending until the everything-server fully implements SEP-2243 - // header validation (case-insensitive names, whitespace trimming, -32001 error code) + // header validation (case-insensitive names, whitespace trimming, -32020 error code) new HttpHeaderValidationScenario(), new HttpCustomHeaderServerValidationScenario() ]; diff --git a/src/scenarios/server/http-standard-headers.ts b/src/scenarios/server/http-standard-headers.ts index 94ef9e82..59bd30ca 100644 --- a/src/scenarios/server/http-standard-headers.ts +++ b/src/scenarios/server/http-standard-headers.ts @@ -8,7 +8,7 @@ * - Reject case variations of header values (case-sensitive) * - Handle whitespace trimming per HTTP spec * - Validate Base64-encoded custom header values - * - Return 400 Bad Request with error code -32001 (HeaderMismatch) + * - Return 400 Bad Request with error code -32020 (HeaderMismatch) * * This is a ClientScenario (connects to a server under test and validates * its behavior). @@ -66,8 +66,8 @@ const REJECT_STATUS_CHECK_ID = 'sep-2243-server-reject-invalid-headers'; const REJECT_ERROR_CODE_CHECK_ID = 'sep-2243-server-reject-error-code'; // CUSTOM-header (Mcp-Param-*) rejections map to the param-validation -// requirements instead. The -32001 error-code half of every custom-header -// rejection is the "reject with 400 + JSON-RPC error code -32001 if any +// requirements instead. The -32020 error-code half of every custom-header +// rejection is the "reject with 400 + JSON-RPC error code -32020 if any // validation fails" requirement. const PARAM_REJECT_ERROR_CODE_CHECK_ID = 'sep-2243-server-reject-param-mismatch'; @@ -148,8 +148,8 @@ async function sendRawRequest( /** * Builds two checks for a rejection case: one for the HTTP 400 status, one for - * the -32001 JSON-RPC error code. Per SEP-2243 §Server Validation, 400 is MUST - * but -32001 is SHOULD for *standard* headers (and MUST for *custom* headers, + * the -32020 JSON-RPC error code. Per SEP-2243 §Server Validation, 400 is MUST + * but -32020 is SHOULD for *standard* headers (and MUST for *custom* headers, * §Server Behavior for Custom Headers) — so a server returning 400 with a * different error code is compliant for standard headers and must not FAIL. * @@ -196,7 +196,7 @@ function createRejectionChecks( { id: errorCodeId, name: `${name}ErrorCode`, - description: `${description} — uses JSON-RPC error code -32001 (HeaderMismatch)`, + description: `${description} — uses JSON-RPC error code -32020 (HeaderMismatch)`, status: codeOk ? 'SUCCESS' : opts.errorCodeSeverity, timestamp: ts, errorMessage: codeOk @@ -265,7 +265,7 @@ export class HttpHeaderValidationScenario implements ClientScenario { - Server MUST reject case-mismatched header values (method values are case-sensitive) - Server MUST accept extra whitespace around header values (per HTTP spec) - Server MUST return HTTP 400 Bad Request for validation failures -- Server MUST return JSON-RPC error with code -32001 (HeaderMismatch)`; +- Server MUST return JSON-RPC error with code -32020 (HeaderMismatch)`; async run(ctx: RunContext): Promise { const { serverUrl } = ctx; @@ -507,7 +507,7 @@ export class HttpHeaderValidationScenario implements ClientScenario { ...extraHeaders }); if (expectation === 'reject') { - // Standard-header rejection: 400 is MUST, -32001 is SHOULD. All + // Standard-header rejection: 400 is MUST, -32020 is SHOULD. All // standard-header rejection cases collapse onto the coarse requirement // ids; checkId/checkName still distinguish the case via name/details. checks.push( @@ -926,10 +926,10 @@ export class HttpCustomHeaderServerValidationScenario implements ClientScenario ) ); } else { - // Custom-header rejection: both 400 and -32001 are MUST per + // Custom-header rejection: both 400 and -32020 are MUST per // §Server Behavior for Custom Headers. The status half maps to the // param-validation requirement this case exercises (checkId); the - // error-code half is the "400 + -32001 if any validation fails" + // error-code half is the "400 + -32020 if any validation fails" // requirement. checks.push( ...createRejectionChecks( @@ -992,7 +992,7 @@ export class HttpCustomHeaderServerValidationScenario implements ClientScenario } ); - // Custom-header rejection: both 400 and -32001 are MUST. A header + // Custom-header rejection: both 400 and -32020 are MUST. A header // omitted while the body carries a value is a header/body mismatch, so // the status half maps to the validate-param-match requirement. checks.push( diff --git a/src/scenarios/server/stateless.test.ts b/src/scenarios/server/stateless.test.ts index a2fb5f32..f8b93bfa 100644 --- a/src/scenarios/server/stateless.test.ts +++ b/src/scenarios/server/stateless.test.ts @@ -180,7 +180,7 @@ describe('Stateless Server Scenario Negative Tests', () => { jsonrpc: '2.0', id: reqBody.id, error: { - code: -32004, + code: -32022, message: 'Unsupported protocol version', // Spec Violation: data.requested is a required member data: { supported: ['2026-07-28'] } diff --git a/src/scenarios/server/stateless.ts b/src/scenarios/server/stateless.ts index a995b83b..ae157b1a 100644 --- a/src/scenarios/server/stateless.ts +++ b/src/scenarios/server/stateless.ts @@ -40,9 +40,9 @@ export class ServerStatelessScenario implements ClientScenario { - Dynamically checks prompt capability declaration constraints, validates that active RPC handlers match advertised discovery capacities. 3. **Version Negotiation & Headers (3 Checks)** - Mismatched or unknown protocol versions must return an \`UnsupportedProtocolVersionError\` (HTTP status code \`400 Bad Request\`) carrying precise version tracking arrays. - - Absent or altered protocol version header metadata must trigger a \`-32001 Header Mismatch\` error with an HTTP 400 boundary state. + - Absent or altered protocol version header metadata must trigger a \`-32020 Header Mismatch\` error with an HTTP 400 boundary state. 4. **Client Capability Constraints (2 Checks)** - - Accessing platform capabilities without explicit declaration drops requests with a \`-32003 MissingRequiredClientCapabilityError\` containing needed capabilities, returning an HTTP status code \`400 Bad Request\`. + - Accessing platform capabilities without explicit declaration drops requests with a \`-32021 MissingRequiredClientCapabilityError\` containing needed capabilities, returning an HTTP status code \`400 Bad Request\`. 5. **Methods & Routing Mechanics (5 Checks)** - Removed legacy endpoints (\`initialize\`, \`ping\`, \`logging/setLevel\`, etc.) or generic unknown methods must cleanly yield an HTTP status code \`404 Not Found\` alongside a JSON-RPC \`-32601 Method not found\` payload. All error returns must preserve original request ID mappings. - Validates response streams contain only \`IncompleteResult\` chunks and never independent top-level JSON-RPC requests, while enforcing that no log messages are emitted when \`_meta.../logLevel\` is omitted. @@ -608,9 +608,9 @@ export class ServerStatelessScenario implements ClientScenario { () => { if (!resAbsent) return { error: 'Header verification endpoint network hit failed' }; - if (resAbsent.status !== 400 || dataAbsent?.error?.code !== -32001) { + if (resAbsent.status !== 400 || dataAbsent?.error?.code !== -32020) { return { - error: `Expected HTTP 400 and JSON-RPC error -32001, got status ${resAbsent.status} with code ${dataAbsent?.error?.code}` + error: `Expected HTTP 400 and JSON-RPC error -32020, got status ${resAbsent.status} with code ${dataAbsent?.error?.code}` }; } return { details: { response: dataAbsent } }; @@ -631,12 +631,12 @@ export class ServerStatelessScenario implements ClientScenario { if (data401) checkErrorId(data401, 401); // Determine if this server actively enforces client capabilities - const serverRequiresCapability = data401?.error?.code === -32003; + const serverRequiresCapability = data401?.error?.code === -32021; await runCheck( 'sep-2575-server-rejects-undeclared-capability', 'ServerRejectsUndeclaredCapability', - 'A server MUST NOT rely on capabilities the client has not declared. If processing a request requires a capability the client did not include in io.modelcontextprotocol/clientCapabilities, the server MUST return a MissingRequiredClientCapabilityError (-32003).', + 'A server MUST NOT rely on capabilities the client has not declared. If processing a request requires a capability the client did not include in io.modelcontextprotocol/clientCapabilities, the server MUST return a MissingRequiredClientCapabilityError (-32021).', () => { if (!res401) return { @@ -645,22 +645,22 @@ export class ServerStatelessScenario implements ClientScenario { }; if (!serverRequiresCapability) { - // The server didn't return -32003, so this requirement isn't + // The server didn't return -32021, so this requirement isn't // exercised for this method. Report SKIPPED rather than a green PASS. return { skipped: true, details: { - note: 'Skipped requirement tracking: Server returned a non-32003 response, indicating it does not require explicit client capability authorization constraints for this method.', + note: 'Skipped requirement tracking: Server returned a non-32021 response, indicating it does not require explicit client capability authorization constraints for this method.', response: data401 } }; } - // If it DOES return -32003, strictly validate the requirement payload structure + // If it DOES return -32021, strictly validate the requirement payload structure const reqCaps = data401?.error?.data?.requiredCapabilities; if (!Array.isArray(reqCaps) || !reqCaps.includes('sampling')) { return { - error: `Server responded with error code -32003 but failed to provide an array containing the expected 'sampling' capability in error.data.requiredCapabilities`, + error: `Server responded with error code -32021 but failed to provide an array containing the expected 'sampling' capability in error.data.requiredCapabilities`, details: { response: data401 } }; } @@ -680,7 +680,7 @@ export class ServerStatelessScenario implements ClientScenario { }; if (!serverRequiresCapability) { - // No -32003 means the HTTP-400 requirement doesn't apply here. + // No -32021 means the HTTP-400 requirement doesn't apply here. // Report SKIPPED rather than a green PASS. return { skipped: true, diff --git a/src/seps/sep-2243.yaml b/src/seps/sep-2243.yaml index bb7803f1..a39b6d2c 100644 --- a/src/seps/sep-2243.yaml +++ b/src/seps/sep-2243.yaml @@ -8,7 +8,7 @@ requirements: - check: sep-2243-server-reject-invalid-headers text: 'Servers that process the request body MUST reject requests with mismatched or missing standard-header values, returning HTTP 400 Bad Request.' - check: sep-2243-server-reject-error-code - text: 'When rejecting a request due to header validation failure, servers SHOULD include a JSON-RPC error response using error code -32001.' + text: 'When rejecting a request due to header validation failure, servers SHOULD include a JSON-RPC error response using error code -32020.' - check: sep-2243-client-supports-custom-headers text: 'MCP clients MUST support this feature [custom headers via x-mcp-header].' - check: sep-2243-client-mirrors-designated-params @@ -45,7 +45,7 @@ requirements: - check: sep-2243-server-validate-param-match text: 'Any server that processes the message body MUST validate that encoded header values, after decoding if Base64-encoded, match the corresponding parameter values in the body.' - check: sep-2243-server-reject-param-mismatch - text: 'Servers MUST reject requests with a 400 Bad Request HTTP status and JSON-RPC error code -32001 if any validation fails.' + text: 'Servers MUST reject requests with a 400 Bad Request HTTP status and JSON-RPC error code -32020 if any validation fails.' - text: 'Clients SHOULD log a warning when rejecting a tool definition due to invalid x-mcp-header, including the tool name and the reason.' excluded: 'Log output is not wire-observable.' diff --git a/src/seps/sep-2575.yaml b/src/seps/sep-2575.yaml index 798cae4d..057355d8 100644 --- a/src/seps/sep-2575.yaml +++ b/src/seps/sep-2575.yaml @@ -5,7 +5,7 @@ requirements: text: 'Every client request MUST include the following io.modelcontextprotocol/* fields in _meta: protocolVersion, clientInfo, clientCapabilities.' url: https://modelcontextprotocol.io/specification/draft/basic/index#meta - check: sep-2575-server-rejects-undeclared-capability - text: 'A server MUST NOT rely on capabilities the client has not declared. If processing a request requires a capability the client did not include in io.modelcontextprotocol/clientCapabilities, the server MUST return a MissingRequiredClientCapabilityError (-32003).' + text: 'A server MUST NOT rely on capabilities the client has not declared. If processing a request requires a capability the client did not include in io.modelcontextprotocol/clientCapabilities, the server MUST return a MissingRequiredClientCapabilityError (-32021).' url: https://modelcontextprotocol.io/specification/draft/basic/index#meta - check: sep-2575-missing-capability-http-400 text: 'On HTTP, the response status MUST be 400 Bad Request [for MissingRequiredClientCapabilityError].' diff --git a/src/spec-types/SOURCE b/src/spec-types/SOURCE index 5719bd9e..33d35e99 100644 --- a/src/spec-types/SOURCE +++ b/src/spec-types/SOURCE @@ -1 +1 @@ -modelcontextprotocol@2fb207da428f43a4981513ec2aef218b7533c5b3 +modelcontextprotocol@f817239f4d6b1efff2c4dfc2f7af85c985d73076 diff --git a/src/spec-types/draft.ts b/src/spec-types/draft.ts index dfa64e7d..c48f9d71 100644 --- a/src/spec-types/draft.ts +++ b/src/spec-types/draft.ts @@ -321,7 +321,7 @@ export interface InvalidRequestError extends Error { * * In MCP, a server returns this error when a client invokes a method the server does not implement — either a genuinely unknown method, or one gated behind a server capability the server did not advertise (e.g., calling `prompts/list` when the `prompts` capability was not advertised). * - * A request that requires a client capability the client did not declare is signalled instead by {@link MissingRequiredClientCapabilityError} (`-32003`). + * A request that requires a client capability the client did not declare is signalled instead by {@link MissingRequiredClientCapabilityError} (`-32021`). * * @see {@link https://www.jsonrpc.org/specification#error_object | JSON-RPC 2.0 Error Object} * @@ -380,6 +380,26 @@ export interface InternalError extends Error { code: typeof INTERNAL_ERROR; } +/* + * MCP error codes. + * + * JSON-RPC 2.0 reserves `-32000` to `-32099` for implementation-defined + * server errors. MCP partitions that range: + * + * - `-32000` to `-32019`: implementation-defined. Existing SDKs and + * implementations use codes here for their own purposes; the specification + * will never define codes in this sub-range, and receivers must not assign + * cross-implementation semantics to them. + * - `-32020` to `-32099`: reserved for error codes defined by the MCP + * specification. Every code allocated here is recorded in this file. + * Codes are allocated sequentially starting at `-32020` and proceeding + * toward `-32099`. + * + * Codes defined by earlier protocol versions remain reserved and are never + * reused: `-32002` (resource not found, 2025-11-25 and earlier; replaced by + * `-32602`) and `-32042` (URL elicitation required, 2025-11-25 only). + */ + /** * Error code returned when the HTTP headers of a request do not match the * corresponding values in the request body, or required headers are @@ -387,7 +407,7 @@ export interface InternalError extends Error { * * @category Errors */ -export const HEADER_MISMATCH = -32001; +export const HEADER_MISMATCH = -32020; /** * Error code returned when a server requires a client capability that was @@ -395,7 +415,7 @@ export const HEADER_MISMATCH = -32001; * * @category Errors */ -export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; +export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32021; /** * Error code returned when the request's protocol version is not supported @@ -403,7 +423,7 @@ export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; * * @category Errors */ -export const UNSUPPORTED_PROTOCOL_VERSION = -32004; +export const UNSUPPORTED_PROTOCOL_VERSION = -32022; /** * Returned when a server rejects a request because the values in the HTTP