Skip to content

Commit cd07785

Browse files
committed
feat: add MVP stateless client conformance tests for SEP-2575
1 parent 17f1f93 commit cd07785

2 files changed

Lines changed: 103 additions & 0 deletions

File tree

src/scenarios/client/stateless.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import http from 'http';
2+
import {
3+
Scenario,
4+
ScenarioUrls,
5+
ConformanceCheck,
6+
SpecVersion,
7+
DRAFT_PROTOCOL_VERSION
8+
} from '../../types';
9+
10+
export class StatelessClientScenario implements Scenario {
11+
name = 'stateless-client';
12+
specVersions: SpecVersion[] = ['2026-06-18', DRAFT_PROTOCOL_VERSION];
13+
description = 'Tests stateless MCP client behavior (SEP-2575)';
14+
15+
private server: http.Server | null = null;
16+
private checks: ConformanceCheck[] = [];
17+
18+
async start(): Promise<ScenarioUrls> {
19+
return new Promise((resolve, reject) => {
20+
this.server = http.createServer((req, res) => {
21+
this.handleRequest(req, res);
22+
});
23+
this.server.on('error', reject);
24+
this.server.listen(0, () => {
25+
const address = this.server!.address();
26+
if (address && typeof address === 'object') {
27+
resolve({ serverUrl: `http://localhost:${address.port}` });
28+
}
29+
});
30+
});
31+
}
32+
33+
async stop(): Promise<void> {
34+
return new Promise((resolve) => {
35+
if (this.server) {
36+
this.server.close(() => { resolve(); });
37+
} else {
38+
resolve();
39+
}
40+
});
41+
}
42+
43+
getChecks(): ConformanceCheck[] {
44+
return this.checks;
45+
}
46+
47+
private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {
48+
let body = '';
49+
req.on('data', (chunk) => { body += chunk.toString(); });
50+
req.on('end', () => {
51+
const request = JSON.parse(body);
52+
53+
// TEST 1: Verify client can call server/discover
54+
if (request.method === 'server/discover') {
55+
this.checks.push({
56+
id: 'client-calls-discover',
57+
name: 'ClientCallsDiscover',
58+
description: 'Client is able to successfully call server/discover',
59+
status: 'SUCCESS',
60+
timestamp: new Date().toISOString(),
61+
specReferences: [{ id: 'SEP-2575', url: '' }]
62+
});
63+
64+
// Respond with valid discovery payload to keep client happy
65+
res.writeHead(200, { 'Content-Type': 'application/json' });
66+
res.end(JSON.stringify({
67+
jsonrpc: '2.0',
68+
id: request.id,
69+
result: {
70+
supportedVersions: ['2026-06-18'],
71+
capabilities: {},
72+
serverInfo: { name: 'test', version: '1.0' }
73+
}
74+
}));
75+
return;
76+
}
77+
78+
// TEST 2: Verify inline _meta on every request
79+
const meta = request.params?._meta;
80+
const hasProtocolVersion = meta?.['io.modelcontextprotocol/protocolVersion'];
81+
const hasClientInfo = meta?.['io.modelcontextprotocol/clientInfo'];
82+
const hasCapabilities = meta?.['io.modelcontextprotocol/clientCapabilities'];
83+
84+
const metaIsValid = hasProtocolVersion && hasClientInfo && hasCapabilities;
85+
86+
this.checks.push({
87+
id: 'client-populates-meta',
88+
name: 'ClientPopulatesMeta',
89+
description: 'Client populates _meta on every request with all three required fields',
90+
status: metaIsValid ? 'SUCCESS' : 'FAILURE',
91+
timestamp: new Date().toISOString(),
92+
specReferences: [{ id: 'SEP-2575', url: '' }],
93+
details: { meta }
94+
});
95+
96+
// Return generic response to unblock client
97+
res.writeHead(200, { 'Content-Type': 'application/json' });
98+
res.end(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: {} }));
99+
});
100+
}
101+
}

src/scenarios/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { InitializeScenario } from './client/initialize';
1212
import { ToolsCallScenario } from './client/tools_call';
1313
import { ElicitationClientDefaultsScenario } from './client/elicitation-defaults';
1414
import { SSERetryScenario } from './client/sse-retry';
15+
import { StatelessClientScenario } from './client/stateless';
1516

1617
// Import all new server test scenarios
1718
import { ServerInitializeScenario } from './server/lifecycle';
@@ -182,6 +183,7 @@ const scenariosList: Scenario[] = [
182183
...authScenariosList,
183184
...backcompatScenariosList,
184185
...draftScenariosList,
186+
new StatelessClientScenario(),
185187
...extensionScenariosList
186188
];
187189

0 commit comments

Comments
 (0)