express-protocol-middleware
A set of Express routing handlers designed to seamlessly embed the Model Context Protocol (MCP) within Node.js web servers, offering unified management for both persistent, context-aware sessions and isolated, single-invocation communications with Large Language Models (LLMs). Features built-in Server-Sent Events (SSE) support for real-time interaction.
Author

jhgaylor
Quick Info
Actions
Tags
express-protocol-middleware
A collection of middleware utilities for integrating the Model Context Protocol (MCP) specification directly into Express-based applications, streamlining the bidirectional data flow between LLMs and external tooling.
Understanding Model Context Protocol (MCP)
Model Context Protocol (MCP) establishes an open, structured methodology for interfacing Artificial Intelligence models (LLMs) with external services, data reservoirs, and executable tools. This standardization allows AI agents to dynamically retrieve current information and perform defined actions via a consistent communication layer.
Core Capabilities
- Session-Based Operation: Supports sustained, long-lived conversational contexts identified by session tokens, utilizing Server-Sent Events (SSE) for push notifications.
- Non-Persistent Handling: Offers an isolated execution path where every incoming request is treated as brand new, without any stored prior state.
- SSE Conduit: Specialized endpoints facilitating MCP communication over Server-Sent Events, supporting both initialization (GET) and message transmission (POST).
- Type Safety: Engineered entirely in TypeScript to ensure robust, compile-time verification of interfaces.
- Adaptable Configuration: Allows granular customization over session lifecycle management, error interception, and hook execution.
- Express Native: Integrates seamlessly within the Express request pipeline via standard middleware attachment.
Acquisition
Install via npm package manager:
bash npm install express-mcp-handler
Or using yarn:
bash yarn add express-mcp-handler
Or pnpm:
bash pnpm add express-mcp-handler
Mandatory Prerequisites
This library necessitates the following peer dependencies to function correctly:
expressversion 4.0.0 or newer@modelcontextprotocol/sdkversion 1.10.2 or newerzodversion 3.0.0 or newer
Ensure these are installed alongside this package:
bash npm install express @modelcontextprotocol/sdk zod
Rapid Initialization Example
This basic blueprint demonstrates mounting the non-persistent handler:
ts 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());
// Define a function that produces a fresh McpServer instance for every incoming transaction const serverFactory = () => new McpServer({ name: 'my-mcp-server', version: '1.0.0', });
// Deploy the stateless processor middleware app.post('/mcp', statelessHandler(serverFactory));
app.listen(3000, () => { console.log('Express MCP processor operational on port 3000'); });
Deployment Strategies
express-mcp-handler provides three distinct processing strategies tailored to various operational requirements:
Persistent Context Handling (statefulHandler)
Utilize statefulHandler to forge and maintain continuous contexts (sessions) between the client and server, vital for multi-turn AI workflows:
ts 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());
// Instantiate the core MCP server engine const server = new McpServer({ name: 'my-server', version: '1.0.0', });
// Setup operational parameters
const handlerConfiguration = {
sessionIdGenerator: randomUUID, // Utility to fabricate unique session identifiers
onSessionInitialized: (sessionId: string) => {
console.log(Session established: ${sessionId});
// Logic for pre-allocating resources per session
},
onSessionClosed: (sessionId: string) => {
console.log(Session terminated: ${sessionId});
// Resource deallocation and cleanup routines
},
onError: (error: Error, sessionId?: string) => {
console.error(Transaction failure in session ${sessionId}:, error);
// Integration with error logging/alerting systems
}
};
// Register handlers for various HTTP verbs app.post('/mcp', statefulHandler(server, handlerConfiguration)); app.get('/mcp', statefulHandler(server, handlerConfiguration)); app.delete('/mcp', statefulHandler(server, handlerConfiguration));
app.listen(3000, () => { console.log('Express Persistent MCP engine active on port 3000'); });
Key behaviors of the stateful processor:
- Originates a new session context if no
mcp-session-idheader is present. - Echoes a
mcp-session-idheader in responses for subsequent message correlation. - Manages the bidirectional flow over Server-Sent Events (SSE).
- Implements automatic garbage collection for inactive session resources.
Non-Persistent Context Handling (statelessHandler)
Choose statelessHandler for transactional interactions demanding absolute isolation, highly suitable for serverless functions or simple, discrete queries:
ts 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());
// A factory providing a distinct McpServer instance for every HTTP invocation const serverFactory = () => new McpServer({ name: 'stateless-mcp-server', version: '1.0.0', });
// Configuration focusing purely on error interception const settings = { onError: (error: Error) => { console.error('Critical MCP error:', error); // Implement external metric tracking here } };
app.post('/mcp', statelessHandler(serverFactory, settings));
app.listen(3000, () => { console.log('Express Stateless MCP node active on port 3000'); });
Every stateless transaction adheres to:
- Instantiating a fresh transport and server entity.
- Guaranteeing zero context leakage between transactions.
- Optimal deployment choice for ephemeral environments.
Real-Time Streaming (sseHandlers)
Use sseHandlers to establish dedicated communication conduits using Server-Sent Events (SSE) for MCP operations, perfect for streaming responses:
ts 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());
// Factory to provision an McpServer instance per persistent SSE channel const serverFactory = () => new McpServer({ name: 'sse-mcp-server', version: '1.0.0', });
// Configure the SSE transport handlers
const handlers = sseHandlers(serverFactory, {
onError: (error: Error, sessionId?: string) => {
console.error([SSE Stream][${sessionId || 'unidentified'}], error);
},
onClose: (sessionId: string) => {
console.log([SSE Stream] Connection drain: ${sessionId});
// Necessary cleanup for session resources
},
});
// Assign handlers to distinct paths app.get('/sse', handlers.getHandler); // Stream establishment app.post('/messages', handlers.postHandler); // Message input over the stream
app.listen(3002, () => { console.log('Express MCP Real-Time Stream server operational on port 3002'); });
SSE handlers expose dual endpoints:
- GET /sse: Initializes the persistent SSE connection and returns the session identifier.
- POST /messages: Transmits MCP payloads using the session ID supplied via the query parameters.
Programmatic Interface Reference
statefulHandler
ts function 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;
| Argument | Type | Rationale |
|---|---|---|
server |
McpServer |
The core protocol processing engine instance. |
options.sessionIdGenerator |
() => string |
Mandatory routine for generating session keys. |
options.onSessionInitialized |
(sessionId: string) => void |
Optional: Hook executed immediately upon session creation. |
options.onSessionClosed |
(sessionId: string) => void |
Optional: Hook executed when session resources are reclaimed. |
options.onError |
(error: Error, sessionId?: string) => void |
Optional: Centralized error logging hook. |
options.onInvalidSession |
(req: express.Request) => void |
Optional: Invoked if an incoming request references a non-existent session. |
statelessHandler
ts function statelessHandler( serverFactory: () => McpServer, options?: { sessionIdGenerator?: () => string; onClose?: (req: express.Request, res: express.Response) => void; onError?: (error: Error) => void; } ): express.RequestHandler;
| Argument | Type | Rationale |
|---|---|---|
serverFactory |
() => McpServer |
A callable function that instantiates a new server object per request. |
options.sessionIdGenerator |
() => string |
Optional: Overrides default session ID generation logic for the transport layer. |
options.onClose |
(req: express.Request, res: express.Response) => void |
Optional: Executed when the HTTP transaction concludes. |
options.onError |
(error: Error) => void |
Optional: Handler for processing exceptions during stateless execution. |
sseHandlers
ts function sseHandlers( serverFactory: ServerFactory, options: SSEHandlerOptions ): { getHandler: express.RequestHandler; postHandler: express.RequestHandler; };
| Argument | Type | Rationale |
|---|---|---|
serverFactory |
ServerFactory |
The factory function responsible for providing isolated McpServer instances per SSE connection. |
options.onError |
(error: Error, sessionId?: string) => void |
Optional: Error interception for the SSE transport, logging both the issue and the associated session ID. |
options.onClose |
(sessionId: string) => void |
Optional: Notification handler triggered upon the disconnection of an established SSE session, receiving the sessionId. |
Exception Management
Custom error interception is uniformly available across all handler mechanisms via their respective configuration objects:
ts
// Example configuration for stateful endpoint error interception
const handlerConfiguration = {
// ... other settings
onError: (error: Error, sessionId?: string) => {
console.error(Contextual failure in session ${sessionId}:, error);
// Forward error telemetry to an external observability platform
Sentry.captureException(error, {
extra: { sessionId }
});
}
};
TypeScript Interoperability
Since the package is fundamentally built with TypeScript, consumers benefit from full type inference and static analysis:
ts import { statefulHandler, StatefulHandlerOptions } from 'express-mcp-handler'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
// Strongly typed configuration object
const typedConfiguration: StatefulHandlerOptions = {
sessionIdGenerator: () => String(Date.now()),
onError: (exception, id) => {
// The IDE understands the exact types for exception and id
console.error(Error processing session ${id}:, exception);
}
};
const engine = new McpServer({ name: 'typed-server', version: '1.0.0', });
// Type-checked middleware mounting app.post('/mcp', statefulHandler(engine, typedConfiguration));
Development Guidelines
To contribute code or enhancements:
bash git clone https://github.com/jhgaylor/express-mcp-handler.git cd express-mcp-handler npm install npm run build npm test
Test Integrity
The project prioritizes comprehensive automated testing, enforced via CI pipelines.
Testing is executed using Jest, with coverage metrics reported via Codecov, ensuring sustained quality assurance across all feature branches.
Continuous Integration Flow
GitHub Actions orchestrates the CI/CD process. Each commit pushed to the main branch or submitted via a Pull Request will trigger:
- Code style and lint validation.
- Project compilation.
- Execution of the full test suite along with coverage reporting.
- Upload of coverage reports to Codecov.
The current CI status is visible via the badge at the document's apex or directly on the Actions dashboard.
Legal Disclaimer
Distributed under the terms of the MIT License.
Publishing Procedure
To release an update to the npm registry:
Authenticate with npm if necessary: bash npm login
Publish the package (this automatically invokes the prepublishOnly build script):
bash
npm publish
To increment the version, create a git tag, and push the changes: bash npm version patch # Or minor, major selector git push origin main --tags
Handler Functionality Summary
| Processing Mode | Typical Use Case | State Persistence | Real-Time Streaming |
|---|---|---|---|
statelessHandler |
Simple, transactional calls | Absent | Absent |
statefulHandler |
Multi-turn dialogues, context retention | Present | Supported |
sseHandlers |
Continuous, server-initiated feeds | Present | Primary Focus |
Debugging Tips
Missing mcp-session-id Header
Verify the client logic correctly captures and retransmits the session ID returned in the initial response header.
Transport Link Failure
Investigate network latency or ensure the consuming client implements robust handling for dropped SSE streams.
Revision History
Detailed chronological documentation of modifications resides in CHANGELOG.md.
WIKIPEDIA CONTEXT: XMLHttpRequest (XHR) represents a fundamental JavaScript interface enabling asynchronous HTTP requests between a client-side script and a remote server. This mechanism is central to the AJAX programming paradigm, moving past the legacy reliance on full page reloads for data retrieval or submission. Its inception dates back to 2000, originating within Microsoft Outlook development, leading to its first implementation in Internet Explorer 5.
== Historical Milestones ==
The foundational concept was pioneered by Microsoft developers around 2000. IE5 (1999) was the first browser to support this functionality, albeit using proprietary ActiveXObject constructors (Msxml2.XMLHTTP or Microsoft.XMLHTTP). By the release of Internet Explorer 7 (2006), the standardized XMLHttpRequest identifier achieved universal adoption across major browsers, including Mozilla's Gecko (2002), Safari 1.2 (2004), and Opera 8.0 (2005).
=== Standardization Evolution === The W3C formalized the specification, publishing its first Working Draft on April 5, 2006. Level 2 specifications, introduced February 25, 2008, enhanced XHR capabilities by adding support for progress monitoring, enabling cross-origin data fetching, and facilitating raw byte stream processing. In late 2011, the Level 2 features were merged back into the primary document. Development responsibility transitioned to WHATWG near the end of 2012, where it is currently maintained as a living standard described using Web IDL.
== Operational Flow == Utilizing XMLHttpRequest typically involves a sequence of distinct programming calls:
- Instantiation: Creation of an
XMLHttpRequestobject instance via its constructor. - Configuration (
open): Invoking.open()to define the request method (GET, POST, etc.), target Uniform Resource Identifier (URI), and whether the operation should execute synchronously or asynchronously. - Event Binding (Async): For asynchronous calls, setting up event handlers (e.g.,
onreadystatechange) to process status changes. - Transmission (
send): Dispatching the request to the server using the.send()method, optionally including a payload body. - Response Processing: Monitoring state transitions. Upon reaching state 4 (the 'done' state), the retrieved data is typically accessible in the
responseTextproperty.
Beyond these core steps, XHR offers extensive control over request formatting (custom headers), data uploading, parsing the server response (e.g., automatic JSON deserialization), and implementing timeouts or abort mechanisms.
