Skip to content

Commit 4303ddf

Browse files
authored
Fweinberger/sep 1699 test revise (#52)
* some logging things, and fix for warning check for bare GET * switch to tool call to make more representative * server pretty printing, add server tool, adjust server test scenario
1 parent 40d45ea commit 4303ddf

File tree

6 files changed

+549
-141
lines changed

6 files changed

+549
-141
lines changed

examples/clients/typescript/sse-retry-test.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
* SSE Retry Test Client
55
*
66
* Tests that the MCP client respects the SSE retry field when reconnecting.
7-
* This client connects to a test server that sends retry: field and closes
8-
* the connection, then validates that the client waits the appropriate time.
7+
* This client connects to a test server that closes the SSE stream mid-tool-call,
8+
* then waits for the client to reconnect and sends the tool result.
99
*/
1010

1111
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
1212
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
13+
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
1314

1415
async function main(): Promise<void> {
1516
const serverUrl = process.argv[2];
@@ -42,7 +43,6 @@ async function main(): Promise<void> {
4243
}
4344
});
4445

45-
// Track reconnection events
4646
transport.onerror = (error) => {
4747
console.log(`Transport error: ${error.message}`);
4848
};
@@ -55,24 +55,23 @@ async function main(): Promise<void> {
5555
await client.connect(transport);
5656
console.log('Connected to MCP server');
5757

58-
// Keep connection alive to observe reconnection behavior
59-
// The server will close the POST SSE stream and the client should reconnect via GET
60-
console.log('Waiting for reconnection cycle...');
58+
console.log('Calling test_reconnection tool...');
6159
console.log(
62-
'Server will send priming event with retry field, then close POST SSE stream'
63-
);
64-
console.log(
65-
'Client should wait for retry period (2000ms) then reconnect via GET with Last-Event-ID'
60+
'Server will close SSE stream mid-call and send result after reconnection'
6661
);
6762

68-
// Wait long enough for:
69-
// 1. Server to send priming event with retry field on POST SSE stream (100ms)
70-
// 2. Server closes POST stream to trigger reconnection
71-
// 3. Client waits for retry period (2000ms expected)
72-
// 4. Client reconnects via GET with Last-Event-ID header
73-
await new Promise((resolve) => setTimeout(resolve, 6000));
63+
const result = await client.request(
64+
{
65+
method: 'tools/call',
66+
params: {
67+
name: 'test_reconnection',
68+
arguments: {}
69+
}
70+
},
71+
CallToolResultSchema
72+
);
7473

75-
console.log('Test duration complete');
74+
console.log('Tool call completed:', JSON.stringify(result, null, 2));
7675

7776
await transport.close();
7877
console.log('Connection closed successfully');

examples/servers/typescript/everything-server.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,46 @@ function createMcpServer() {
349349
}
350350
);
351351

352+
// SEP-1699: Reconnection test tool - closes SSE stream mid-call to test client reconnection
353+
mcpServer.registerTool(
354+
'test_reconnection',
355+
{
356+
description:
357+
'Tests SSE stream disconnection and client reconnection (SEP-1699). Server will close the stream mid-call and send the result after client reconnects.',
358+
inputSchema: {}
359+
},
360+
async (_args, { sessionId, requestId }) => {
361+
const sleep = (ms: number) =>
362+
new Promise((resolve) => setTimeout(resolve, ms));
363+
364+
console.log(`[${sessionId}] Starting test_reconnection tool...`);
365+
366+
// Get the transport for this session
367+
const transport = sessionId ? transports[sessionId] : undefined;
368+
if (transport && requestId) {
369+
// Close the SSE stream to trigger client reconnection
370+
console.log(
371+
`[${sessionId}] Closing SSE stream to trigger client polling...`
372+
);
373+
transport.closeSSEStream(requestId);
374+
}
375+
376+
// Wait for client to reconnect (should respect retry field)
377+
await sleep(100);
378+
379+
console.log(`[${sessionId}] test_reconnection tool complete`);
380+
381+
return {
382+
content: [
383+
{
384+
type: 'text',
385+
text: 'Reconnection test completed successfully. If you received this, the client properly reconnected after stream closure.'
386+
}
387+
]
388+
};
389+
}
390+
);
391+
352392
// Sampling tool - requests LLM completion from client
353393
mcpServer.registerTool(
354394
'test_sampling',

src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,14 @@ program
204204
'Suite to run: "active" (default, excludes pending), "all", or "pending"',
205205
'active'
206206
)
207+
.option('--verbose', 'Show verbose output (JSON instead of pretty print)')
207208
.action(async (options) => {
208209
try {
209210
// Validate options with Zod
210211
const validated = ServerOptionsSchema.parse(options);
211212

213+
const verbose = options.verbose ?? false;
214+
212215
// If a single scenario is specified, run just that one
213216
if (validated.scenario) {
214217
const result = await runServerConformanceTest(
@@ -218,7 +221,8 @@ program
218221

219222
const { failed } = printServerResults(
220223
result.checks,
221-
result.scenarioDescription
224+
result.scenarioDescription,
225+
verbose
222226
);
223227
process.exit(failed > 0 ? 1 : 0);
224228
} else {

src/runner/server.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
22
import path from 'path';
33
import { ConformanceCheck } from '../types';
44
import { getClientScenario } from '../scenarios';
5-
import { ensureResultsDir, createResultDir } from './utils';
5+
import { ensureResultsDir, createResultDir, formatPrettyChecks } from './utils';
66

77
/**
88
* Format markdown-style text for terminal output using ANSI codes
@@ -54,7 +54,8 @@ export async function runServerConformanceTest(
5454

5555
export function printServerResults(
5656
checks: ConformanceCheck[],
57-
scenarioDescription: string
57+
scenarioDescription: string,
58+
verbose: boolean = false
5859
): {
5960
passed: number;
6061
failed: number;
@@ -68,7 +69,11 @@ export function printServerResults(
6869
const failed = checks.filter((c) => c.status === 'FAILURE').length;
6970
const warnings = checks.filter((c) => c.status === 'WARNING').length;
7071

71-
console.log(`Checks:\n${JSON.stringify(checks, null, 2)}`);
72+
if (verbose) {
73+
console.log(JSON.stringify(checks, null, 2));
74+
} else {
75+
console.log(`Checks:\n${formatPrettyChecks(checks)}`);
76+
}
7277

7378
console.log(`\nTest Results:`);
7479
console.log(

0 commit comments

Comments
 (0)