From eeda5d630a57d45b04c5522cb733c00e9f2a6552 Mon Sep 17 00:00:00 2001 From: ihrpr Date: Mon, 21 Apr 2025 15:13:25 +0100 Subject: [PATCH] readme update --- README.md | 32 +- .../server/jsonResponseStreamableHttp.ts | 124 +++---- src/examples/server/simpleSseServer.ts | 107 +++--- .../server/simpleStatelessStreamableHttp.ts | 3 - src/examples/server/simpleStreamableHttp.ts | 311 +++++++++--------- .../sseAndStreamableHttpCompatibleServer.ts | 91 ++--- 6 files changed, 337 insertions(+), 331 deletions(-) diff --git a/README.md b/README.md index 200cfab6..605cc725 100644 --- a/README.md +++ b/README.md @@ -312,19 +312,19 @@ For simpler use cases where session management isn't needed: const app = express(); app.use(express.json()); -const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, // set to undefined for stateless servers -}); - -// Setup routes for the server -const setupServer = async () => { - await server.connect(transport); -}; - app.post('/mcp', async (req: Request, res: Response) => { - console.log('Received MCP request:', req.body); try { - await transport.handleRequest(req, res, req.body); + const server = getServer(); + const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + res.on('close', () => { + console.log('Request closed'); + transport.close(); + server.close(); + }); } catch (error) { console.error('Error handling MCP request:', error); if (!res.headersSent) { @@ -364,15 +364,11 @@ app.delete('/mcp', async (req: Request, res: Response) => { })); }); + // Start the server const PORT = 3000; -setupServer().then(() => { - app.listen(PORT, () => { - console.log(`MCP Streamable HTTP Server listening on port ${PORT}`); - }); -}).catch(error => { - console.error('Failed to set up the server:', error); - process.exit(1); +app.listen(PORT, () => { + console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`); }); ``` diff --git a/src/examples/server/jsonResponseStreamableHttp.ts b/src/examples/server/jsonResponseStreamableHttp.ts index bcd6a960..02d8c2de 100644 --- a/src/examples/server/jsonResponseStreamableHttp.ts +++ b/src/examples/server/jsonResponseStreamableHttp.ts @@ -5,74 +5,78 @@ import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js'; import { z } from 'zod'; import { CallToolResult, isInitializeRequest } from '../../types.js'; + // Create an MCP server with implementation details -const server = new McpServer({ - name: 'json-response-streamable-http-server', - version: '1.0.0', -}, { - capabilities: { - logging: {}, - } -}); +const getServer = () => { + const server = new McpServer({ + name: 'json-response-streamable-http-server', + version: '1.0.0', + }, { + capabilities: { + logging: {}, + } + }); -// Register a simple tool that returns a greeting -server.tool( - 'greet', - 'A simple greeting tool', - { - name: z.string().describe('Name to greet'), - }, - async ({ name }): Promise => { - return { - content: [ - { - type: 'text', - text: `Hello, ${name}!`, - }, - ], - }; - } -); + // Register a simple tool that returns a greeting + server.tool( + 'greet', + 'A simple greeting tool', + { + name: z.string().describe('Name to greet'), + }, + async ({ name }): Promise => { + return { + content: [ + { + type: 'text', + text: `Hello, ${name}!`, + }, + ], + }; + } + ); -// Register a tool that sends multiple greetings with notifications -server.tool( - 'multi-greet', - 'A tool that sends different greetings with delays between them', - { - name: z.string().describe('Name to greet'), - }, - async ({ name }, { sendNotification }): Promise => { - const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + // Register a tool that sends multiple greetings with notifications + server.tool( + 'multi-greet', + 'A tool that sends different greetings with delays between them', + { + name: z.string().describe('Name to greet'), + }, + async ({ name }, { sendNotification }): Promise => { + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - await sendNotification({ - method: "notifications/message", - params: { level: "debug", data: `Starting multi-greet for ${name}` } - }); + await sendNotification({ + method: "notifications/message", + params: { level: "debug", data: `Starting multi-greet for ${name}` } + }); - await sleep(1000); // Wait 1 second before first greeting + await sleep(1000); // Wait 1 second before first greeting - await sendNotification({ - method: "notifications/message", - params: { level: "info", data: `Sending first greeting to ${name}` } - }); + await sendNotification({ + method: "notifications/message", + params: { level: "info", data: `Sending first greeting to ${name}` } + }); - await sleep(1000); // Wait another second before second greeting + await sleep(1000); // Wait another second before second greeting - await sendNotification({ - method: "notifications/message", - params: { level: "info", data: `Sending second greeting to ${name}` } - }); + await sendNotification({ + method: "notifications/message", + params: { level: "info", data: `Sending second greeting to ${name}` } + }); - return { - content: [ - { - type: 'text', - text: `Good morning, ${name}!`, - } - ], - }; - } -); + return { + content: [ + { + type: 'text', + text: `Good morning, ${name}!`, + } + ], + }; + } + ); + return server; +} const app = express(); app.use(express.json()); @@ -104,6 +108,7 @@ app.post('/mcp', async (req: Request, res: Response) => { }); // Connect the transport to the MCP server BEFORE handling the request + const server = getServer(); await server.connect(transport); await transport.handleRequest(req, res, req.body); return; // Already handled @@ -153,6 +158,5 @@ app.listen(PORT, () => { // Handle server shutdown process.on('SIGINT', async () => { console.log('Shutting down server...'); - await server.close(); process.exit(0); }); \ No newline at end of file diff --git a/src/examples/server/simpleSseServer.ts b/src/examples/server/simpleSseServer.ts index 74cdcaac..cae3be30 100644 --- a/src/examples/server/simpleSseServer.ts +++ b/src/examples/server/simpleSseServer.ts @@ -15,60 +15,63 @@ import { CallToolResult } from '../../types.js'; */ // Create an MCP server instance -const server = new McpServer({ - name: 'simple-sse-server', - version: '1.0.0', -}, { capabilities: { logging: {} } }); +const getServer = () => { + const server = new McpServer({ + name: 'simple-sse-server', + version: '1.0.0', + }, { capabilities: { logging: {} } }); -server.tool( - 'start-notification-stream', - 'Starts sending periodic notifications', - { - interval: z.number().describe('Interval in milliseconds between notifications').default(1000), - count: z.number().describe('Number of notifications to send').default(10), - }, - async ({ interval, count }, { sendNotification }): Promise => { - const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - let counter = 0; + server.tool( + 'start-notification-stream', + 'Starts sending periodic notifications', + { + interval: z.number().describe('Interval in milliseconds between notifications').default(1000), + count: z.number().describe('Number of notifications to send').default(10), + }, + async ({ interval, count }, { sendNotification }): Promise => { + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + let counter = 0; - // Send the initial notification - await sendNotification({ - method: "notifications/message", - params: { - level: "info", - data: `Starting notification stream with ${count} messages every ${interval}ms` - } - }); - - // Send periodic notifications - while (counter < count) { - counter++; - await sleep(interval); - - try { - await sendNotification({ - method: "notifications/message", - params: { - level: "info", - data: `Notification #${counter} at ${new Date().toISOString()}` - } - }); - } - catch (error) { - console.error("Error sending notification:", error); - } - } - - return { - content: [ - { - type: 'text', - text: `Completed sending ${count} notifications every ${interval}ms`, + // Send the initial notification + await sendNotification({ + method: "notifications/message", + params: { + level: "info", + data: `Starting notification stream with ${count} messages every ${interval}ms` } - ], - }; - } -); + }); + + // Send periodic notifications + while (counter < count) { + counter++; + await sleep(interval); + + try { + await sendNotification({ + method: "notifications/message", + params: { + level: "info", + data: `Notification #${counter} at ${new Date().toISOString()}` + } + }); + } + catch (error) { + console.error("Error sending notification:", error); + } + } + + return { + content: [ + { + type: 'text', + text: `Completed sending ${count} notifications every ${interval}ms`, + } + ], + }; + } + ); + return server; +}; const app = express(); app.use(express.json()); @@ -96,6 +99,7 @@ app.get('/mcp', async (req: Request, res: Response) => { }; // Connect the transport to the MCP server + const server = getServer(); await server.connect(transport); // Start the SSE transport to begin streaming @@ -163,7 +167,6 @@ process.on('SIGINT', async () => { console.error(`Error closing transport for session ${sessionId}:`, error); } } - await server.close(); console.log('Server shutdown complete'); process.exit(0); }); \ No newline at end of file diff --git a/src/examples/server/simpleStatelessStreamableHttp.ts b/src/examples/server/simpleStatelessStreamableHttp.ts index 1fbdbddc..6fb2ae83 100644 --- a/src/examples/server/simpleStatelessStreamableHttp.ts +++ b/src/examples/server/simpleStatelessStreamableHttp.ts @@ -96,9 +96,6 @@ const getServer = () => { const app = express(); app.use(express.json()); - - - app.post('/mcp', async (req: Request, res: Response) => { const server = getServer(); try { diff --git a/src/examples/server/simpleStreamableHttp.ts b/src/examples/server/simpleStreamableHttp.ts index 2b25f4b2..d94908bd 100644 --- a/src/examples/server/simpleStreamableHttp.ts +++ b/src/examples/server/simpleStreamableHttp.ts @@ -7,164 +7,167 @@ import { CallToolResult, GetPromptResult, isInitializeRequest, ReadResourceResul import { InMemoryEventStore } from '../shared/inMemoryEventStore.js'; // Create an MCP server with implementation details -const server = new McpServer({ - name: 'simple-streamable-http-server', - version: '1.0.0', -}, { capabilities: { logging: {} } }); +const getServer = () => { + const server = new McpServer({ + name: 'simple-streamable-http-server', + version: '1.0.0', + }, { capabilities: { logging: {} } }); -// Log the capability invocation details -server.onCapabilityChange((event) => { - switch (event.action) { - case 'invoked': - console.log(`${event.capabilityType} invocation ${event.invocationIndex}: '${event.capabilityName}' started`); - break; - case 'completed': - console.log(`${event.capabilityType} invocation ${event.invocationIndex}: '${event.capabilityName}' completed in ${event.durationMs}ms`); - break; - case 'error': - console.log(`${event.capabilityType} invocation ${event.invocationIndex}: '${event.capabilityName}' failed in ${event.durationMs}ms: ${event.error}`); - break; - } -}); - -// Register a simple tool that returns a greeting -server.tool( - 'greet', - 'A simple greeting tool', - { - name: z.string().describe('Name to greet'), - }, - async ({ name }): Promise => { - return { - content: [ - { - type: 'text', - text: `Hello, ${name}!`, - }, - ], - }; - } -); - -// Register a tool that sends multiple greetings with notifications -server.tool( - 'multi-greet', - 'A tool that sends different greetings with delays between them', - { - name: z.string().describe('Name to greet'), - }, - async ({ name }, { sendNotification }): Promise => { - const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - - await sendNotification({ - method: "notifications/message", - params: { level: "debug", data: `Starting multi-greet for ${name}` } - }); - - await sleep(1000); // Wait 1 second before first greeting - - await sendNotification({ - method: "notifications/message", - params: { level: "info", data: `Sending first greeting to ${name}` } - }); - - await sleep(1000); // Wait another second before second greeting - - await sendNotification({ - method: "notifications/message", - params: { level: "info", data: `Sending second greeting to ${name}` } - }); - - return { - content: [ - { - type: 'text', - text: `Good morning, ${name}!`, - } - ], - }; - } -); - -// Register a simple prompt -server.prompt( - 'greeting-template', - 'A simple greeting prompt template', - { - name: z.string().describe('Name to include in greeting'), - }, - async ({ name }): Promise => { - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: `Please greet ${name} in a friendly manner.`, - }, - }, - ], - }; - } -); - -// Register a tool specifically for testing resumability -server.tool( - 'start-notification-stream', - 'Starts sending periodic notifications for testing resumability', - { - interval: z.number().describe('Interval in milliseconds between notifications').default(100), - count: z.number().describe('Number of notifications to send (0 for 100)').default(50), - }, - async ({ interval, count }, { sendNotification }): Promise => { - const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - let counter = 0; - - while (count === 0 || counter < count) { - counter++; - try { - await sendNotification({ - method: "notifications/message", - params: { - level: "info", - data: `Periodic notification #${counter} at ${new Date().toISOString()}` - } - }); - } - catch (error) { - console.error("Error sending notification:", error); - } - // Wait for the specified interval - await sleep(interval); + // Log the capability invocation details + server.onCapabilityChange((event) => { + switch (event.action) { + case 'invoked': + console.log(`${event.capabilityType} invocation ${event.invocationIndex}: '${event.capabilityName}' started`); + break; + case 'completed': + console.log(`${event.capabilityType} invocation ${event.invocationIndex}: '${event.capabilityName}' completed in ${event.durationMs}ms`); + break; + case 'error': + console.log(`${event.capabilityType} invocation ${event.invocationIndex}: '${event.capabilityName}' failed in ${event.durationMs}ms: ${event.error}`); + break; } + }); - return { - content: [ - { - type: 'text', - text: `Started sending periodic notifications every ${interval}ms`, + // Register a simple tool that returns a greeting + server.tool( + 'greet', + 'A simple greeting tool', + { + name: z.string().describe('Name to greet'), + }, + async ({ name }): Promise => { + return { + content: [ + { + type: 'text', + text: `Hello, ${name}!`, + }, + ], + }; + } + ); + + // Register a tool that sends multiple greetings with notifications + server.tool( + 'multi-greet', + 'A tool that sends different greetings with delays between them', + { + name: z.string().describe('Name to greet'), + }, + async ({ name }, { sendNotification }): Promise => { + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + + await sendNotification({ + method: "notifications/message", + params: { level: "debug", data: `Starting multi-greet for ${name}` } + }); + + await sleep(1000); // Wait 1 second before first greeting + + await sendNotification({ + method: "notifications/message", + params: { level: "info", data: `Sending first greeting to ${name}` } + }); + + await sleep(1000); // Wait another second before second greeting + + await sendNotification({ + method: "notifications/message", + params: { level: "info", data: `Sending second greeting to ${name}` } + }); + + return { + content: [ + { + type: 'text', + text: `Good morning, ${name}!`, + } + ], + }; + } + ); + + // Register a simple prompt + server.prompt( + 'greeting-template', + 'A simple greeting prompt template', + { + name: z.string().describe('Name to include in greeting'), + }, + async ({ name }): Promise => { + return { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `Please greet ${name} in a friendly manner.`, + }, + }, + ], + }; + } + ); + + // Register a tool specifically for testing resumability + server.tool( + 'start-notification-stream', + 'Starts sending periodic notifications for testing resumability', + { + interval: z.number().describe('Interval in milliseconds between notifications').default(100), + count: z.number().describe('Number of notifications to send (0 for 100)').default(50), + }, + async ({ interval, count }, { sendNotification }): Promise => { + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + let counter = 0; + + while (count === 0 || counter < count) { + counter++; + try { + await sendNotification({ + method: "notifications/message", + params: { + level: "info", + data: `Periodic notification #${counter} at ${new Date().toISOString()}` + } + }); } - ], - }; - } -); + catch (error) { + console.error("Error sending notification:", error); + } + // Wait for the specified interval + await sleep(interval); + } -// Create a simple resource at a fixed URI -server.resource( - 'greeting-resource', - 'https://example.com/greetings/default', - { mimeType: 'text/plain' }, - async (): Promise => { - return { - contents: [ - { - uri: 'https://example.com/greetings/default', - text: 'Hello, world!', - }, - ], - }; - } -); + return { + content: [ + { + type: 'text', + text: `Started sending periodic notifications every ${interval}ms`, + } + ], + }; + } + ); + + // Create a simple resource at a fixed URI + server.resource( + 'greeting-resource', + 'https://example.com/greetings/default', + { mimeType: 'text/plain' }, + async (): Promise => { + return { + contents: [ + { + uri: 'https://example.com/greetings/default', + text: 'Hello, world!', + }, + ], + }; + } + ); + return server; +}; const app = express(); app.use(express.json()); @@ -207,6 +210,7 @@ app.post('/mcp', async (req: Request, res: Response) => { // Connect the transport to the MCP server BEFORE handling the request // so responses can flow back through the same transport + const server = getServer(); await server.connect(transport); await transport.handleRequest(req, res, req.body); @@ -303,7 +307,6 @@ process.on('SIGINT', async () => { console.error(`Error closing transport for session ${sessionId}:`, error); } } - await server.close(); console.log('Server shutdown complete'); process.exit(0); }); diff --git a/src/examples/server/sseAndStreamableHttpCompatibleServer.ts b/src/examples/server/sseAndStreamableHttpCompatibleServer.ts index d644dd07..7a0ab04e 100644 --- a/src/examples/server/sseAndStreamableHttpCompatibleServer.ts +++ b/src/examples/server/sseAndStreamableHttpCompatibleServer.ts @@ -18,52 +18,54 @@ import { InMemoryEventStore } from '../shared/inMemoryEventStore.js'; * - /messages: The deprecated POST endpoint for older clients (POST to send messages) */ +const getServer = () => { + const server = new McpServer({ + name: 'backwards-compatible-server', + version: '1.0.0', + }, { capabilities: { logging: {} } }); -const server = new McpServer({ - name: 'backwards-compatible-server', - version: '1.0.0', -}, { capabilities: { logging: {} } }); + // Register a simple tool that sends notifications over time + server.tool( + 'start-notification-stream', + 'Starts sending periodic notifications for testing resumability', + { + interval: z.number().describe('Interval in milliseconds between notifications').default(100), + count: z.number().describe('Number of notifications to send (0 for 100)').default(50), + }, + async ({ interval, count }, { sendNotification }): Promise => { + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + let counter = 0; -// Register a simple tool that sends notifications over time -server.tool( - 'start-notification-stream', - 'Starts sending periodic notifications for testing resumability', - { - interval: z.number().describe('Interval in milliseconds between notifications').default(100), - count: z.number().describe('Number of notifications to send (0 for 100)').default(50), - }, - async ({ interval, count }, { sendNotification }): Promise => { - const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - let counter = 0; - - while (count === 0 || counter < count) { - counter++; - try { - await sendNotification({ - method: "notifications/message", - params: { - level: "info", - data: `Periodic notification #${counter} at ${new Date().toISOString()}` - } - }); - } - catch (error) { - console.error("Error sending notification:", error); - } - // Wait for the specified interval - await sleep(interval); - } - - return { - content: [ - { - type: 'text', - text: `Started sending periodic notifications every ${interval}ms`, + while (count === 0 || counter < count) { + counter++; + try { + await sendNotification({ + method: "notifications/message", + params: { + level: "info", + data: `Periodic notification #${counter} at ${new Date().toISOString()}` + } + }); } - ], - }; - } -); + catch (error) { + console.error("Error sending notification:", error); + } + // Wait for the specified interval + await sleep(interval); + } + + return { + content: [ + { + type: 'text', + text: `Started sending periodic notifications every ${interval}ms`, + } + ], + }; + } + ); + return server; +}; // Create Express application const app = express(); @@ -125,6 +127,7 @@ app.all('/mcp', async (req: Request, res: Response) => { }; // Connect the transport to the MCP server + const server = getServer(); await server.connect(transport); } else { // Invalid request - no session ID or not initialization request @@ -167,6 +170,7 @@ app.get('/sse', async (req: Request, res: Response) => { res.on("close", () => { delete transports[transport.sessionId]; }); + const server = getServer(); await server.connect(transport); }); @@ -237,7 +241,6 @@ process.on('SIGINT', async () => { console.error(`Error closing transport for session ${sessionId}:`, error); } } - await server.close(); console.log('Server shutdown complete'); process.exit(0); }); \ No newline at end of file