typescript-sdk/examples/sse-polling/client.ts
Felix Weinberger 1823aae891
Implement MCP 2026-07-28 (#2286)
Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com>
Co-authored-by: Konstantin Konstantinov <KKonstantinov@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-26 14:01:10 +01:00

48 lines
2.1 KiB
TypeScript

/**
* SSE Polling Example Client (SEP-1699)
*
* Connects (2025-era), calls `long-operation`, and asserts the result arrives
* AFTER the server's mid-stream `closeSSE()` — i.e. the client transport
* automatically reconnects with `Last-Event-ID` and replays the events the
* `eventStore` buffered while disconnected. Also asserts every progress log
* (including the one emitted while disconnected) was delivered.
*/
import { check, parseExampleArgs } from '@mcp-examples/shared';
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
const { url } = parseExampleArgs();
// `closeSSE`/`eventStore` live on the sessionful-2025 transport, so this
// story is legacy-only by design — it was previously reaching 2025 by
// negotiation fallback; pin it.
const client = new Client({ name: 'sse-polling-client', version: '1.0.0' }, { versionNegotiation: { mode: 'legacy' } });
const logs: string[] = [];
client.setNotificationHandler('notifications/message', n => {
logs.push(String(n.params.data));
});
const transport = new StreamableHTTPClientTransport(new URL(url));
// The mid-stream disconnect surfaces as a transport error before the
// automatic reconnect; that is the EXPECTED flow, not a failure.
transport.onerror = () => {};
await client.connect(transport);
let lastEventId: string | undefined;
const result = await client.request(
{ method: 'tools/call', params: { name: 'long-operation', arguments: {} } },
{ onresumptiontoken: token => (lastEventId = token) }
);
const text = (result as { content?: Array<{ type: string; text?: string }> }).content?.[0]?.text ?? '';
check.match(text, /completed successfully/);
check.ok(lastEventId, 'resumption tokens should have been observed');
// The 75% line is emitted WHILE the client is disconnected; receiving it
// proves the event store replayed it on reconnect. (Replay ordering relative
// to the terminal result is not asserted — the result resolving is the
// signal the disconnect was survived.)
check.ok(
logs.some(l => l.includes('75%')),
`events emitted while disconnected should be replayed (got: ${logs.join(' | ')})`
);
await client.close();