Skip to content
Merged
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
8 changes: 2 additions & 6 deletions examples/clients/typescript/everything-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,8 @@ async function runRequestMetadataClient(serverUrl: string): Promise<void> {
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...'
);
Expand Down
14 changes: 7 additions & 7 deletions examples/servers/typescript/everything-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
});
}

Expand All @@ -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'],
Expand Down Expand Up @@ -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'] }
}
Expand Down
10 changes: 5 additions & 5 deletions src/mock-server/mock-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand All @@ -124,7 +124,7 @@ describe('validateStatelessRequest', () => {
status: 400,
body: {
error: {
code: -32004,
code: -32022,
data: { supported: [DRAFT_PROTOCOL_VERSION], requested: '2099-01-01' }
}
}
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
]);
Expand Down
8 changes: 4 additions & 4 deletions src/mock-server/stateless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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) {
Expand All @@ -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'
);
}
Expand All @@ -136,7 +136,7 @@ export function validateStatelessRequest(
jsonrpc: '2.0',
id,
error: {
code: -32004,
code: -32022,
message: 'Unsupported protocol version',
data: {
supported: supportedVersions,
Expand Down
2 changes: 1 addition & 1 deletion src/scenarios/client/draft-result-fields.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/scenarios/client/request-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/scenarios/client/request-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
2 changes: 1 addition & 1 deletion src/scenarios/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
];
Expand Down
22 changes: 11 additions & 11 deletions src/scenarios/server/http-standard-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<ConformanceCheck[]> {
const { serverUrl } = ctx;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/scenarios/server/stateless.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'] }
Expand Down
22 changes: 11 additions & 11 deletions src/scenarios/server/stateless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 } };
Expand All @@ -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 {
Expand All @@ -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 }
};
}
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/seps/sep-2243.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.'
Expand Down
2 changes: 1 addition & 1 deletion src/seps/sep-2575.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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].'
Expand Down
2 changes: 1 addition & 1 deletion src/spec-types/SOURCE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
modelcontextprotocol@2fb207da428f43a4981513ec2aef218b7533c5b3
modelcontextprotocol@f817239f4d6b1efff2c4dfc2f7af85c985d73076
Loading
Loading