fix(examples): create fresh McpServer per client in three examples (#1610)

This commit is contained in:
Niels Kaspers 2026-03-02 12:49:56 +02:00 • committed by GitHub
commit 52186d1884
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -23,64 +23,68 @@ import type { Request, Response } from 'express';
import { InMemoryEventStore } from './inMemoryEventStore.js';
// Create the MCP server
const server = new McpServer(
{
name: 'sse-polling-example',
version: '1.0.0'
},
{
capabilities: { logging: {} }
}
);
// Register a long-running tool that demonstrates server-initiated disconnect
server.registerTool(
'long-task',
{
description: 'A long-running task that sends progress updates. Server will disconnect mid-task to demonstrate polling.'
},
async (ctx): Promise<CallToolResult> => {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
console.log(`[${ctx.sessionId}] Starting long-task...`);
// Send first progress notification
await ctx.mcpReq.log('info', 'Progress: 25% - Starting work...');
await sleep(1000);
// Send second progress notification
await ctx.mcpReq.log('info', 'Progress: 50% - Halfway there...');
await sleep(1000);
// Server decides to disconnect the client to free resources
// Client will reconnect via GET with Last-Event-ID after the transport's retryInterval
// Use ctx.http?.closeSSE callback - available when eventStore is configured
if (ctx.http?.closeSSE) {
console.log(`[${ctx.sessionId}] Closing SSE stream to trigger client polling...`);
ctx.http?.closeSSE();
// Create a fresh MCP server per client connection to avoid shared state between clients
const getServer = () => {
const server = new McpServer(
{
name: 'sse-polling-example',
version: '1.0.0'
},
{
capabilities: { logging: {} }
}
);
// Continue processing while client is disconnected
// Events are stored in eventStore and will be replayed on reconnect
await sleep(500);
await ctx.mcpReq.log('info', 'Progress: 75% - Almost done (sent while client disconnected)...');
// Register a long-running tool that demonstrates server-initiated disconnect
server.registerTool(
'long-task',
{
description: 'A long-running task that sends progress updates. Server will disconnect mid-task to demonstrate polling.'
},
async (ctx): Promise<CallToolResult> => {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
await sleep(500);
await ctx.mcpReq.log('info', 'Progress: 100% - Complete!');
console.log(`[${ctx.sessionId}] Starting long-task...`);
console.log(`[${ctx.sessionId}] Task complete`);
// Send first progress notification
await ctx.mcpReq.log('info', 'Progress: 25% - Starting work...');
await sleep(1000);
return {
content: [
{
type: 'text',
text: 'Long task completed successfully!'
}
]
};
}
);
// Send second progress notification
await ctx.mcpReq.log('info', 'Progress: 50% - Halfway there...');
await sleep(1000);
// Server decides to disconnect the client to free resources
// Client will reconnect via GET with Last-Event-ID after the transport's retryInterval
// Use ctx.http?.closeSSE callback - available when eventStore is configured
if (ctx.http?.closeSSE) {
console.log(`[${ctx.sessionId}] Closing SSE stream to trigger client polling...`);
ctx.http?.closeSSE();
}
// Continue processing while client is disconnected
// Events are stored in eventStore and will be replayed on reconnect
await sleep(500);
await ctx.mcpReq.log('info', 'Progress: 75% - Almost done (sent while client disconnected)...');
await sleep(500);
await ctx.mcpReq.log('info', 'Progress: 100% - Complete!');
console.log(`[${ctx.sessionId}] Task complete`);
return {
content: [
{
type: 'text',
text: 'Long task completed successfully!'
}
]
};
}
);
return server;
};
// Set up Express app
const app = createMcpExpressApp();
@ -110,7 +114,8 @@ app.all('/mcp', async (req: Request, res: Response) => {
}
});
// Connect the MCP server to the transport
// Connect a fresh MCP server to the transport
const server = getServer();
await server.connect(transport);
}