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>
This commit is contained in:
parent
e4227d135c
commit
1823aae891
1 changed files with 69131 additions and 9001 deletions
48
examples/sse-polling/client.ts
Normal file
48
examples/sse-polling/client.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* 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();
|
||||
Loading…
Reference in a new issue