UNPKG

@tmcp/transport-http

Version:
440 lines (325 loc) 12.2 kB
# @tmcp/transport-http An HTTP transport implementation for TMCP (TypeScript Model Context Protocol) servers. This package provides HTTP Streaming based communication for MCP servers over HTTP, enabling web-based clients to interact with your MCP server through standard HTTP requests. ## Installation ```bash pnpm add @tmcp/transport-http tmcp ``` ## Usage ### Basic Setup ```javascript import { McpServer } from 'tmcp'; import { HttpTransport } from '@tmcp/transport-http'; // Create your MCP server const server = new McpServer( { name: 'my-http-server', version: '1.0.0', description: 'My HTTP MCP server', }, { adapter: new YourSchemaAdapter(), capabilities: { tools: { listChanged: true }, prompts: { listChanged: true }, resources: { listChanged: true }, }, }, ); // Add your tools, prompts, and resources server.tool( { name: 'example_tool', description: 'An example tool', }, async () => { return { content: [{ type: 'text', text: 'Hello from HTTP!' }], }; }, ); // Create the HTTP transport (defaults to '/mcp' path) const transport = new HttpTransport(server); // Use with your preferred HTTP server // Example with Node.js built-in server + @remix-run/ import * as http from 'node:http'; import { createRequestListener } from '@remix-run/node-fetch-server'; let httpServer = http.createServer(createRequestListener((request)=>{ const response = await transport.respond(request); if(response){ return response; } return new Response(null, { status: 404 }); })); httpServer.listen(3000, () => { console.log('MCP HTTP server listening on port 3000'); }); ``` ### With Custom Configuration ```javascript const transport = new HttpTransport(server, { // Custom MCP endpoint path (default: '/mcp', use null to respond on every path) path: '/api/mcp', // Custom session ID generation getSessionId: () => { return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; }, }); > [!NOTE] > When the transport runs in development mode and you omit the `path` option, a warning is emitted. Future releases will treat an `undefined` path as "respond on every path", so set the property explicitly (for example `path: '/mcp'` or `path: null`) to lock in the behavior you want today. ``` ### With Custom Context You can pass custom context data to your MCP server for each request. This is useful for authentication, user information, database connections, etc. ```javascript // Define your custom context type interface MyContext { userId: string; permissions: string[]; database: DatabaseConnection; } // Create server with custom context const server = new McpServer(serverInfo, options).withContext<MyContext>(); server.tool( { name: 'get-user-profile', description: 'Get the current user profile', }, async () => { // Access custom context in your handler const { userId, database } = server.ctx.custom!; const profile = await database.users.findById(userId); return { content: [ { type: 'text', text: `User profile: ${JSON.stringify(profile)}` } ], }; }, ); // Create transport (it will be typed to accept your custom context) const transport = new HttpTransport(server); // then in the handler const response = await transport.respond(req, { userId, permissions, database: req.locals.db, }); ``` ### Session Management The HTTP transport supports custom session managers for different deployment scenarios: #### In-Memory Sessions (Default) ```javascript import { InMemoryStreamSessionManager, InMemoryInfoSessionManager, } from '@tmcp/session-manager'; const transport = new HttpTransport(server, { sessionManager: { streams: new InMemoryStreamSessionManager(), info: new InMemoryInfoSessionManager(), }, }); ``` #### Redis Sessions (Multi-Server/Serverless) For deployments across multiple servers or serverless environments where sessions need to be shared: ```javascript import { RedisStreamSessionManager, RedisInfoSessionManager, } from '@tmcp/session-manager-redis'; const transport = new HttpTransport(server, { sessionManager: { streams: new RedisStreamSessionManager('redis://localhost:6379'), info: new RedisInfoSessionManager('redis://localhost:6379'), }, }); ``` **When to use Redis sessions:** - **Multi-server deployments**: When your application runs on multiple servers and clients might connect to different instances - **Serverless deployments**: When your transport is deployed on serverless platforms where instances are ephemeral (attention, serverless environment generally kills SSE request after a not-so-long amount of time, this means notification from the server will not reach the client after the shutdown) - **Load balancing**: When using load balancers that might route requests to different server instances ## Features - **🌐 HTTP/SSE Communication**: Uses Server-Sent Events for real-time bidirectional communication - **🔄 Session Management**: Maintains client sessions with automatic session ID generation - **📡 Streaming Responses**: Supports streaming responses through SSE - **🛤️ Configurable Path**: Customizable MCP endpoint path with automatic filtering (set `path` to `null` to respond everywhere) - **🔧 Framework Agnostic**: Works with any HTTP server framework (Fastify, Bun, Deno, etc.) - **⚡ Real-time Updates**: Server can push notifications and updates to connected clients - **🛡️ Error Handling**: Graceful error handling for malformed requests - **🔀 Multiple HTTP Methods**: Supports GET (notifications), POST (messages), and DELETE (disconnect) - **🧠 Session Metadata**: Automatically persists client capabilities, info, and log levels and exposes them via `server.ctx.sessionInfo` ## API ### `HttpTransport` #### Constructor ```typescript new HttpTransport(server: McpServer, options?: HttpTransportOptions) ``` Creates a new HTTP transport instance. **Parameters:** - `server` - A TMCP server instance to handle incoming requests - `options` - Optional configuration for the transport **Options:** ```typescript interface HttpTransportOptions { getSessionId: () => string; // Custom session ID generator path?: string | null; // MCP endpoint path (default: '/mcp', null responds on every path) oauth?: OAuth; // an oauth provider generated from @tmcp/auth sessionManager?: { streams?: StreamSessionManager; info?: InfoSessionManager; }; // Provide custom managers; defaults to in-memory implementations disableSse?: boolean; // Disable SSE stream endpoint (GET returns 405) } ``` If you omit `sessionManager` the transport creates `InMemoryStreamSessionManager` and `InMemoryInfoSessionManager` instances for you. You can override either field independently (for example, Redis streams with in-memory metadata during development). ### Disabling SSE Streams If your deployment doesn't support long-lived SSE connections (e.g., some serverless environments), you can disable the GET endpoint: ```javascript const transport = new HttpTransport(server, { disableSse: true, }); ``` When `disableSse` is `true`, GET requests to the MCP endpoint will return a `405 Method Not Allowed` response. Clients can still communicate via POST requests, but won't receive server-initiated notifications. #### Methods ##### `respond(request: Request, customContext?: T): Promise<Response | null>` Processes an HTTP request and returns a Response with Server-Sent Events, or null if the request path doesn't match the configured MCP path. **Parameters:** - `request` - A Web API Request object containing the JSON-RPC message - `customContext` - Optional custom context data to pass to the MCP server for this request **Returns:** - A Response object with SSE stream for ongoing communication, or null if the request path doesn't match the MCP endpoint **HTTP Methods:** - **POST**: Processes MCP messages and returns short-lived event stream responses - **GET**: Establishes long-lived connections for server notifications - **DELETE**: Disconnects sessions and cleans up resources ## Protocol Details ### HTTP Methods The transport supports three HTTP methods: #### POST - Message Processing Clients send JSON-RPC messages via HTTP POST requests: ```http POST /mcp HTTP/1.1 Content-Type: application/json mcp-session-id: optional-session-id { "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} } ``` Response: Short-lived event stream that closes after sending the response: ```http HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache mcp-session-id: generated-or-provided-session-id data: {"jsonrpc":"2.0","id":1,"result":{"tools":[...]}} ``` #### GET - Notification Stream Establishes long-lived connections for server notifications: ```http GET /mcp HTTP/1.1 mcp-session-id: optional-session-id ``` Response: Long-lived event stream for server notifications: ```http HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive mcp-session-id: generated-or-provided-session-id data: {"jsonrpc":"2.0","method":"notifications/initialized","params":{}} ``` #### DELETE - Session Disconnect Disconnects a session and cleans up resources: ```http DELETE /mcp HTTP/1.1 mcp-session-id: session-to-disconnect ``` Response: ```http HTTP/1.1 204 No Content mcp-session-id: session-to-disconnect ``` ### Session Management - **Session ID Header**: `mcp-session-id` - **Automatic Generation**: If no session ID is provided, one is generated automatically - **Session Persistence**: Sessions persist across multiple requests until the client disconnects - **Server Notifications**: Server can send notifications to all active sessions ## Framework Examples ### Bun ```javascript import { McpServer } from 'tmcp'; import { HttpTransport } from '@tmcp/transport-http'; const server = new McpServer(/* ... */); const transport = new HttpTransport(server); Bun.serve({ port: 3000, async fetch(req) { const response = await transport.respond(req); if (response === null) { return new Response('Not Found', { status: 404 }); } return response; }, }); ``` ### Deno ```javascript import { McpServer } from 'tmcp'; import { HttpTransport } from '@tmcp/transport-http'; const server = new McpServer(/* ... */); const transport = new HttpTransport(server); Deno.serve({ port: 3000 }, async (req) => { const response = await transport.respond(req); if (response === null) { return new Response('Not Found', { status: 404 }); } return response; }); ``` ### `srvx` If you want the same experience across Deno, Bun, and Node.js, you can use [srvx](https://srvx.h3.dev/). ```js import { McpServer } from 'tmcp'; import { HttpTransport } from '@tmcp/transport-http'; import { serve } from 'srvx'; const server = new McpServer(/* ... */); const transport = new HttpTransport(server); serve({ async fetch(req) { const response = await transport.respond(req); if (response === null) { return new Response('Not Found', { status: 404 }); } return response; }, }); ``` ## Error Handling The transport includes comprehensive error handling: - **Malformed JSON**: Invalid JSON requests return appropriate error responses - **Session Management**: Automatic cleanup of disconnected sessions - **Server Errors**: Server processing errors are propagated to clients ## Development ```bash # Install dependencies pnpm install # Generate TypeScript declarations pnpm generate:types # Lint the code pnpm lint ``` ## Requirements - Node.js 16+ (for native ES modules and Web API support) - A TMCP server instance - An HTTP server framework or runtime - A schema adapter (Zod, Valibot, etc.) ## Related Packages - [`tmcp`](../tmcp) - Core TMCP server implementation - [`@tmcp/transport-stdio`](../transport-stdio) - Standard I/O transport - [`@tmcp/adapter-zod`](../adapter-zod) - Zod schema adapter - [`@tmcp/adapter-valibot`](../adapter-valibot) - Valibot schema adapter ## Acknowledgments Huge thanks to Sean O'Bannon that provided us with the `@tmcp` scope on npm. ## License MIT