Middleware for Express enabling seamless integration of Model Context Protocol (MCP) to facilitate stateful and stateless communication between large language models and external tools with type-safe, configurable session and error management.
Unlock the full potential of Express MCP Handler through LangDB's AI Gateway. Get enterprise-grade security, analytics, and seamless integration with zero configuration.
Free tier available • No credit card required
Discover shared experiences
A middleware for integrating Model Context Protocol (MCP) with Express applications, enabling seamless communication between LLMs and tools.
Model Context Protocol (MCP) is an open protocol for integrating large language models (LLMs) with external data sources and tools. It enables AI assistants to access real-time data, execute operations, and interact with various services through a standardized interface.
Install via npm:
npm install express-mcp-handler
Or yarn:
yarn add express-mcp-handler
Or pnpm:
pnpm add express-mcp-handler
This package requires the following peer dependencies:
express
>= 4.0.0@modelcontextprotocol/sdk
>= 1.10.2zod
>= 3.0.0Install them if you haven't already:
npm install express @modelcontextprotocol/sdk zod
Here's a basic example to get you started:
import express from 'express'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { statelessHandler } from 'express-mcp-handler'; const app = express(); app.use(express.json()); // Create a factory function that returns a new McpServer instance for each request const serverFactory = () => new McpServer({ name: 'my-mcp-server', version: '1.0.0', }); // Mount the stateless handler app.post('/mcp', statelessHandler(serverFactory)); app.listen(3000, () => { console.log('Express MCP server running on port 3000'); });
Express-mcp-handler provides three handler types to suit different use cases:
Use statefulHandler
to establish reusable sessions between client and server, ideal for maintaining context across multiple interactions:
import express from 'express'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { statefulHandler } from 'express-mcp-handler'; import { randomUUID } from 'node:crypto'; const app = express(); app.use(express.json()); // Create an MCP server instance const server = new McpServer({ name: 'my-server', version: '1.0.0', }); // Configure handler options const handlerOptions = { sessionIdGenerator: randomUUID, // Function to generate unique session IDs onSessionInitialized: (sessionId: string) => { console.log(`Session initialized: ${sessionId}`); // You could store session metadata or initialize resources here }, onSessionClosed: (sessionId: string) => { console.log(`Session closed: ${sessionId}`); // Perform cleanup logic here }, onError: (error: Error, sessionId?: string) => { console.error(`Error in session ${sessionId}:`, error); // Handle errors for monitoring or logging } }; // Mount the handlers for different HTTP methods app.post('/mcp', statefulHandler(server, handlerOptions)); app.get('/mcp', statefulHandler(server, handlerOptions)); app.delete('/mcp', statefulHandler(server, handlerOptions)); app.listen(3000, () => { console.log('Express MCP server running on port 3000'); });
The stateful handler:
mcp-session-id
header)mcp-session-id
header that clients must include in subsequent requestsUse statelessHandler
for one-off request handling with no session management, perfect for serverless environments or simple requests:
import express from 'express'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { statelessHandler } from 'express-mcp-handler'; const app = express(); app.use(express.json()); // Function that creates a fresh McpServer for each request const serverFactory = () => new McpServer({ name: 'stateless-mcp-server', version: '1.0.0', }); // Configure with custom error handling const options = { onError: (error: Error) => { console.error('MCP error:', error); // Add custom error reporting logic here } }; app.post('/mcp', statelessHandler(serverFactory, options)); app.listen(3000, () => { console.log('Express Stateless MCP server running on port 3000'); });
Each stateless request:
Use sseHandlers
to handle Model Context Protocol (MCP) over Server-Sent Events (SSE), ideal for real-time streaming responses:
import express from 'express'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { sseHandlers } from 'express-mcp-handler'; const app = express(); app.use(express.json()); // Provide a factory function that returns a fresh McpServer for each SSE connection const serverFactory = () => new McpServer({ name: 'sse-mcp-server', version: '1.0.0', }); // Configure SSE handlers const handlers = sseHandlers(serverFactory, { onError: (error: Error, sessionId?: string) => { console.error(`[SSE][${sessionId || 'unknown'}]`, error); }, onClose: (sessionId: string) => { console.log(`[SSE] transport closed: ${sessionId}`); // Clean up any session resources }, }); // Mount the SSE endpoints app.get('/sse', handlers.getHandler); app.post('/messages', handlers.postHandler); app.listen(3002, () => { console.log('Express MCP SSE server running on port 3002'); });
SSE handlers provide:
mcp-session-id
headermcp-session-id
query parameterfunction statefulHandler( server: McpServer, options: { sessionIdGenerator: () => string; onSessionInitialized?: (sessionId: string) => void; onSessionClosed?: (sessionId: string) => void; onError?: (error: Error, sessionId?: string) => void; onInvalidSession?: (req: express.Request) => void; } ): express.RequestHandler;
Parameter | Type | Description |
---|---|---|
server | McpServer | Instance of McpServer to handle protocol logic |
options.sessionIdGenerator | () => string | Function that returns a unique session ID |
options.onSessionInitialized | (sessionId: string) => void | (optional) Callback invoked with the new session ID |
options.onSessionClosed | (sessionId: string) => void | (optional) Callback invoked when a session is closed |
options.onError | (error: Error, sessionId?: string) => void | (optional) Callback invoked on errors |
options.onInvalidSession | (req: express.Request) => void | (optional) Callback invoked when an invalid session is accessed |
function statelessHandler( serverFactory: () => McpServer, options?: { sessionIdGenerator?: () => string; onClose?: (req: express.Request, res: express.Response) => void; onError?: (error: Error) => void; } ): express.RequestHandler;
Parameter | Type | Description |
---|---|---|
serverFactory | () => McpServer | Function that returns a new server instance for each request |
options.sessionIdGenerator | () => string | (optional) Override transport session ID generation |
options.onClose | (req: express.Request, res: express.Response) => void | (optional) Callback fired when the request/response cycle ends |
options.onError | (error: Error) => void | (optional) Callback fired on errors during handling |
function sseHandlers( serverFactory: ServerFactory, options: SSEHandlerOptions ): { getHandler: express.RequestHandler; postHandler: express.RequestHandler; };
Parameter | Type | Description |
---|---|---|
serverFactory | ServerFactory | Factory function that returns a fresh McpServer for each SSE connection |
options.onError | (error: Error, sessionId?: string) => void | (optional) Callback invoked on errors, receives error and optional sessionId |
options.onClose | (sessionId: string) => void | (optional) Callback invoked when an SSE session is closed, receives sessionId |
All handler types support custom error handling through their options:
// Example of custom error handling for stateful handler const handlerOptions = { // ... other options onError: (error: Error, sessionId?: string) => { console.error(`Error in session ${sessionId}:`, error); // Send error to monitoring service Sentry.captureException(error, { extra: { sessionId } }); } };
This package is written in TypeScript and provides type definitions for all exports. When using TypeScript, you'll get full IntelliSense and type checking.
import { statefulHandler, StatefulHandlerOptions } from 'express-mcp-handler'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; // Type-safe options const options: StatefulHandlerOptions = { sessionIdGenerator: () => Date.now().toString(), onError: (error, sessionId) => { // TypeScript knows the types of these parameters console.error(`Error in session ${sessionId}:`, error); } }; const server = new McpServer({ name: 'typed-server', version: '1.0.0', }); // Type-safe handler app.post('/mcp', statefulHandler(server, options));
To contribute to this project:
git clone https://github.com/jhgaylor/express-mcp-handler.git cd express-mcp-handler npm install npm run build npm test
The project has solid test coverage and promises to maintain it.
All changes are verified through our CI/CD pipeline using Jest for testing and Codecov for coverage reporting.
This project uses GitHub Actions for continuous integration. Every push to the main branch and pull request will:
You can view the current CI status in the badge at the top of this README or on the Actions tab of the GitHub repository.
MIT License
Log in to npm if you haven't already:
npm login
Publish the package to npm (will run your prepublishOnly build):
npm publish
To bump, tag, and push a new version:
npm version patch # or minor, major git push origin main --tags
Handler | Scenario | Sessions | Streaming |
---|---|---|---|
statelessHandler | One-off or serverless workloads | No | No |
statefulHandler | Multi-turn interactions | Yes | Yes |
sseHandlers | Real-time SSE streaming | Yes | Yes |
Missing mcp-session-id
header
Ensure the client includes the mcp-session-id
header returned on the initial request.
Transport connection closed prematurely
Verify network connectivity and ensure the client properly handles SSE events.
All notable changes to this project are documented in the CHANGELOG.md.
Shared threads will appear here, showcasing real-world applications and insights from the community. Check back soon for updates!