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
50 changes: 50 additions & 0 deletions src/scenarios/client/http-custom-headers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ async function post(
});
}

async function postJson(serverUrl: string, body: object): Promise<any> {
const res = await fetch(serverUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json, text/event-stream'
},
body: JSON.stringify(body)
});
return res.json();
}

function idsOf(checks: { id: string }[]): Set<string> {
return new Set(checks.map((c) => c.id));
}
Expand Down Expand Up @@ -198,4 +210,42 @@ describe('HttpInvalidToolHeadersScenario (SEP-2243) check IDs', () => {
await scenario.stop();
}
});

it('FAILs primitive-only when the client calls the number-typed tool', async () => {
const scenario = new HttpInvalidToolHeadersScenario();
const { serverUrl } = await scenario.start(testScenarioContext());
try {
const listed = await postJson(serverUrl, {
jsonrpc: '2.0',
id: 1,
method: 'tools/list'
});
// SEP-2243 permits x-mcp-header only on integer/string/boolean, so the
// number-typed tool must be served for the client to reject it.
const numberTool = listed.result.tools.find(
(t: { name: string }) => t.name === 'invalid_number_header'
);
expect(numberTool?.inputSchema.properties.score).toEqual({
type: 'number',
'x-mcp-header': 'Score'
});

await post(serverUrl, {
jsonrpc: '2.0',
id: 2,
method: 'tools/call',
params: { name: 'invalid_number_header', arguments: { score: 1.5 } }
});
const checks = scenario.getChecks();
expect(
statusesFor(checks, 'sep-2243-x-mcp-header-primitive-only')
).toContain('FAILURE');
// The other constraints were not violated.
expect(
statusesFor(checks, 'sep-2243-x-mcp-header-not-empty')
).not.toContain('FAILURE');
} finally {
await scenario.stop();
}
});
});
18 changes: 18 additions & 0 deletions src/scenarios/client/http-custom-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const INVALID_TOOL_CONSTRAINT_IDS: Record<string, string> = {
invalid_object_header: 'sep-2243-x-mcp-header-primitive-only',
invalid_array_header: 'sep-2243-x-mcp-header-primitive-only',
invalid_null_header: 'sep-2243-x-mcp-header-primitive-only',
invalid_number_header: 'sep-2243-x-mcp-header-primitive-only',
invalid_duplicate_same_case: 'sep-2243-x-mcp-header-unique',
invalid_duplicate_diff_case: 'sep-2243-x-mcp-header-unique',
invalid_space_in_name: 'sep-2243-x-mcp-header-charset',
Expand Down Expand Up @@ -861,6 +862,23 @@ export class HttpInvalidToolHeadersScenario extends BaseHttpScenario {
}
},

// ── Invalid: x-mcp-header on number type ──
// `number` is a JSON Schema primitive but SEP-2243 excludes it from
// the permitted set: "Parameters with type `number` are not
// permitted." Only integer, string and boolean may be annotated.
{
name: 'invalid_number_header',
description:
'x-mcp-header MUST NOT be on number type (MUST be rejected)',
inputSchema: {
type: 'object',
properties: {
score: { type: 'number', 'x-mcp-header': 'Score' }
},
required: ['score']
}
},

// ── Invalid: duplicate same-case x-mcp-header values ──
{
name: 'invalid_duplicate_same_case',
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 @@ -23,13 +23,13 @@ requirements:
text: 'The x-mcp-header value MUST be case-insensitively unique within a single tool definition.'
url: https://modelcontextprotocol.io/specification/draft/server/tools#custom-headers
- check: sep-2243-x-mcp-header-primitive-only
text: 'x-mcp-header MUST only be applied to parameters with primitive types (number, string, or boolean).'
text: 'x-mcp-header MUST only be applied to parameters with primitive types (integer, string, boolean). Parameters with type `number` are not permitted.'
url: https://modelcontextprotocol.io/specification/draft/server/tools#custom-headers
- check: sep-2243-client-reject-invalid-tool
text: 'Clients MUST reject tool definitions where any x-mcp-header value violates these constraints. Rejection means the client MUST exclude the invalid tool from the set of tools returned by tools/list.'
url: https://modelcontextprotocol.io/specification/draft/server/tools#custom-headers
- check: sep-2243-client-encode-values
text: 'Clients MUST encode parameter values before including them in HTTP headers: number values MUST be converted to their decimal string representation; boolean values MUST be converted to the lowercase strings "true" or "false".'
text: 'Clients MUST encode parameter values before including them in HTTP headers: integer values MUST be converted to their decimal string representation; boolean values MUST be converted to the lowercase strings "true" or "false".'
- check: sep-2243-client-base64-unsafe
text: 'When a value cannot be safely represented as plain ASCII (e.g., contains non-ASCII characters, control characters, or leading/trailing whitespace), clients MUST use Base64 encoding of the UTF-8 representation, wrapped as =?base64?{encoded}?=.'
- check: sep-2243-server-decode-base64
Expand Down
4 changes: 2 additions & 2 deletions src/seps/traceability.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@
{
"check": "sep-2243-x-mcp-header-primitive-only",
"status": "tested",
"text": "x-mcp-header MUST only be applied to parameters with primitive types (number, string, or boolean).",
"text": "x-mcp-header MUST only be applied to parameters with primitive types (integer, string, boolean). Parameters with type `number` are not permitted.",
"url": "https://modelcontextprotocol.io/specification/draft/server/tools#custom-headers"
},
{
Expand All @@ -221,7 +221,7 @@
{
"check": "sep-2243-client-encode-values",
"status": "tested",
"text": "Clients MUST encode parameter values before including them in HTTP headers: number values MUST be converted to their decimal string representation; boolean values MUST be converted to the lowercase strings \"true\" or \"false\"."
"text": "Clients MUST encode parameter values before including them in HTTP headers: integer values MUST be converted to their decimal string representation; boolean values MUST be converted to the lowercase strings \"true\" or \"false\"."
},
{
"check": "sep-2243-client-base64-unsafe",
Expand Down
Loading