Skip to content

Commit 76704d7

Browse files
author
Tarek Mahmoud Sayed
committed
Fill SEP-2243 conformance test coverage gaps
Add missing test cases identified by comparing the branch against the SEP-2243 spec's Conformance Test Cases section: - Mcp-Method on notifications: add notifications/initialized to expectedMethods so clients are checked for header on notifications - Boolean true: add debug=true param to complement verbose=false - Leading-space-only and trailing-space-only: separate checks for Base64 encoding when only one edge has whitespace - Internal spaces only: verify plain ASCII (no Base64) for values like 'us west 1' that have spaces only in the middle - CRLF string: add line1\r\nline2 test (complements existing \n test) - Leading tab: add \tindented test for tab-triggered Base64 encoding - Server rejects invalid Mcp-Param chars: mark as excluded in YAML since HTTP itself prevents these characters in headers Register all new checks in sep-2243.yaml.
1 parent 3291467 commit 76704d7

3 files changed

Lines changed: 113 additions & 0 deletions

File tree

src/scenarios/client/http-custom-headers.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,18 @@ export class HttpCustomHeadersScenario extends BaseHttpScenario {
253253
region: 'us-west1',
254254
priority: 42,
255255
verbose: false,
256+
debug: true,
256257
empty_val: '',
257258
method_val: 'test-method',
258259
float_val: 3.14159,
259260
non_ascii_val: 'Hello, 世界',
260261
whitespace_val: ' padded ',
262+
leading_space_val: ' us-west1',
263+
trailing_space_val: 'us-west1 ',
264+
internal_space_val: 'us west 1',
261265
control_char_val: 'line1\nline2',
266+
crlf_val: 'line1\r\nline2',
267+
tab_val: '\tindented',
262268
query: 'SELECT * FROM users'
263269
}
264270
},
@@ -351,6 +357,11 @@ export class HttpCustomHeadersScenario extends BaseHttpScenario {
351357
description: 'Boolean value',
352358
'x-mcp-header': 'Verbose'
353359
},
360+
debug: {
361+
type: 'boolean',
362+
description: 'Boolean true value',
363+
'x-mcp-header': 'Debug'
364+
},
354365
empty_val: {
355366
type: 'string',
356367
description: 'Empty string value',
@@ -379,12 +390,42 @@ export class HttpCustomHeadersScenario extends BaseHttpScenario {
379390
'String with leading/trailing whitespace — requires Base64 encoding',
380391
'x-mcp-header': 'Whitespace'
381392
},
393+
leading_space_val: {
394+
type: 'string',
395+
description:
396+
'String with leading space only — requires Base64 encoding',
397+
'x-mcp-header': 'LeadingSpace'
398+
},
399+
trailing_space_val: {
400+
type: 'string',
401+
description:
402+
'String with trailing space only — requires Base64 encoding',
403+
'x-mcp-header': 'TrailingSpace'
404+
},
405+
internal_space_val: {
406+
type: 'string',
407+
description:
408+
'String with internal spaces only — plain ASCII, no Base64',
409+
'x-mcp-header': 'InternalSpace'
410+
},
382411
control_char_val: {
383412
type: 'string',
384413
description:
385414
'String with control characters — requires Base64 encoding',
386415
'x-mcp-header': 'ControlChar'
387416
},
417+
crlf_val: {
418+
type: 'string',
419+
description:
420+
'String with carriage return and line feed — requires Base64 encoding',
421+
'x-mcp-header': 'CrLf'
422+
},
423+
tab_val: {
424+
type: 'string',
425+
description:
426+
'String with leading tab — requires Base64 encoding',
427+
'x-mcp-header': 'Tab'
428+
},
388429
query: {
389430
type: 'string',
390431
description:
@@ -474,6 +515,11 @@ export class HttpCustomHeadersScenario extends BaseHttpScenario {
474515
});
475516
}
476517

518+
// Check Mcp-Param-Debug header (boolean true value)
519+
if (args.debug !== undefined && args.debug !== null) {
520+
this.checkParamHeader(req, 'Debug', args.debug, 'boolean');
521+
}
522+
477523
// Check Mcp-Param-EmptyVal header (empty string → empty header value)
478524
if (args.empty_val !== undefined && args.empty_val !== null) {
479525
this.checkParamHeader(req, 'EmptyVal', args.empty_val, 'string');
@@ -499,6 +545,45 @@ export class HttpCustomHeadersScenario extends BaseHttpScenario {
499545
this.checkParamHeader(req, 'Whitespace', args.whitespace_val, 'string');
500546
}
501547

548+
// Check Mcp-Param-LeadingSpace header (leading space only → Base64)
549+
if (
550+
args.leading_space_val !== undefined &&
551+
args.leading_space_val !== null
552+
) {
553+
this.checkParamHeader(
554+
req,
555+
'LeadingSpace',
556+
args.leading_space_val,
557+
'string'
558+
);
559+
}
560+
561+
// Check Mcp-Param-TrailingSpace header (trailing space only → Base64)
562+
if (
563+
args.trailing_space_val !== undefined &&
564+
args.trailing_space_val !== null
565+
) {
566+
this.checkParamHeader(
567+
req,
568+
'TrailingSpace',
569+
args.trailing_space_val,
570+
'string'
571+
);
572+
}
573+
574+
// Check Mcp-Param-InternalSpace header (internal spaces only → plain ASCII, no Base64)
575+
if (
576+
args.internal_space_val !== undefined &&
577+
args.internal_space_val !== null
578+
) {
579+
this.checkParamHeader(
580+
req,
581+
'InternalSpace',
582+
args.internal_space_val,
583+
'string'
584+
);
585+
}
586+
502587
// Check Mcp-Param-ControlChar header (control characters → Base64)
503588
if (
504589
args.control_char_val !== undefined &&
@@ -512,6 +597,16 @@ export class HttpCustomHeadersScenario extends BaseHttpScenario {
512597
);
513598
}
514599

600+
// Check Mcp-Param-CrLf header (carriage return + line feed → Base64)
601+
if (args.crlf_val !== undefined && args.crlf_val !== null) {
602+
this.checkParamHeader(req, 'CrLf', args.crlf_val, 'string');
603+
}
604+
605+
// Check Mcp-Param-Tab header (leading tab → Base64)
606+
if (args.tab_val !== undefined && args.tab_val !== null) {
607+
this.checkParamHeader(req, 'Tab', args.tab_val, 'string');
608+
}
609+
515610
// Check that 'query' (no x-mcp-header) is NOT mirrored
516611
const queryHeader = req.headers['mcp-param-query'] as string | undefined;
517612
if (queryHeader !== undefined) {

src/scenarios/client/http-standard-headers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ export class HttpStandardHeadersScenario implements Scenario {
8080

8181
getChecks(): ConformanceCheck[] {
8282
// Enforce that Mcp-Method was checked for all expected request types
83+
// SEP-2243 requires Mcp-Method on "all requests and notifications"
8384
const expectedMethods = [
8485
'initialize',
86+
'notifications/initialized',
8587
'tools/list',
8688
'tools/call',
8789
'resources/list',

src/scenarios/sep-2243.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ spec_url: https://modelcontextprotocol.io/specification/draft/basic/transports
33
requirements:
44
- text: 'HTTP POST requests MUST include the Mcp-Method header mirrored from the JSON-RPC method for all requests and notifications.'
55
check: client-mcp-method-header-initialize
6+
- text: 'HTTP POST notifications MUST include the Mcp-Method header mirrored from the JSON-RPC method.'
7+
check: client-mcp-method-header-notifications-initialized
68
- text: 'tools/call requests MUST include the Mcp-Name header mirrored from params.name.'
79
check: client-mcp-name-header-tools-call
810
- text: 'resources/read requests MUST include the Mcp-Name header mirrored from params.uri.'
@@ -51,6 +53,8 @@ requirements:
5153
check: client-custom-header-priority
5254
- text: 'Clients MUST convert boolean parameter values to lowercase true/false before mirroring them into Mcp-Param headers.'
5355
check: client-custom-header-verbose
56+
- text: 'Clients MUST convert boolean true to lowercase "true" before mirroring into Mcp-Param headers.'
57+
check: client-custom-header-debug
5458
- text: 'If an x-mcp-header parameter value is provided, the client MUST include the corresponding Mcp-Param header.'
5559
check: client-custom-header-optional-present
5660
- text: 'If an x-mcp-header parameter value is null or omitted, the client MUST omit the corresponding Mcp-Param header.'
@@ -59,8 +63,18 @@ requirements:
5963
check: client-custom-header-nonascii
6064
- text: 'When a value has leading or trailing whitespace, clients MUST use the =?base64?...?= wrapper.'
6165
check: client-custom-header-whitespace
66+
- text: 'When a value has leading whitespace only, clients MUST use the =?base64?...?= wrapper.'
67+
check: client-custom-header-leadingspace
68+
- text: 'When a value has trailing whitespace only, clients MUST use the =?base64?...?= wrapper.'
69+
check: client-custom-header-trailingspace
70+
- text: 'When a value has internal spaces only (no leading/trailing), clients MUST send it as plain ASCII without Base64.'
71+
check: client-custom-header-internalspace
6272
- text: 'When a value contains control characters, clients MUST use the =?base64?...?= wrapper.'
6373
check: client-custom-header-controlchar
74+
- text: 'When a value contains carriage return and line feed, clients MUST use the =?base64?...?= wrapper.'
75+
check: client-custom-header-crlf
76+
- text: 'When a value has a leading tab, clients MUST use the =?base64?...?= wrapper.'
77+
check: client-custom-header-tab
6478
- text: 'Servers that inspect Base64-encoded Mcp-Param values MUST decode them before comparing them with the request body.'
6579
check: server-accepts-valid-base64
6680
- text: 'Servers MUST reject requests with invalid Base64 padding in Mcp-Param values.'
@@ -85,3 +99,5 @@ requirements:
8599
check: client-custom-header-no-mirror-unannotated
86100
- text: 'Intermediaries MUST return an appropriate HTTP error status for validation failures.'
87101
excluded: 'Applies to network intermediaries rather than the MCP client or server implementation under test.'
102+
- text: 'Servers MUST reject requests with Mcp-Param headers that contain invalid characters per RFC 9110.'
103+
excluded: 'HTTP itself prevents invalid characters (CR, LF, null, non-ASCII) in header values; standard HTTP libraries reject them before the server can process them, making this untestable through conformance tests.'

0 commit comments

Comments
 (0)