To expose a Model Context Protocol (MCP) server securely using a token or JWT, you need to use the Server-Sent Events (SSE) transport instead of the default standard input/output (stdio). Because SSE operates over standard HTTP, you can leverage standard Node.js web frameworks like Express alongside standard JWT middleware to secure the endpoints.
Quick Start: Secure MCP Server with Express and JWT
Here is the complete implementation of a secure MCP server using Express and the jsonwebtoken library.
import express from 'express';
import jwt from 'jsonwebtoken';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
const app = express();
app.use(express.json()); // Required to parse incoming JSON messages
// ---------------------------------------------------------
// 1. Authentication Middleware
// ---------------------------------------------------------
const SECRET_KEY = process.env.JWT_SECRET || 'your-super-secret-key';
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
// Expecting header format: "Bearer <token>"
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Authentication token required' });
}
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.user = user;
next();
});
};
// ---------------------------------------------------------
// 2. Initialize MCP Server
// ---------------------------------------------------------
const mcpServer = new Server(
{
name: "secure-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {}, // Add your tools here
resources: {} // Add your resources here
},
}
);
// Note: In a production environment with multiple clients,
// you need to manage transports per client connection.
let transport;
// ---------------------------------------------------------
// 3. Secure MCP Endpoints
// ---------------------------------------------------------
// Endpoint to establish the SSE connection
app.get('/sse', authenticateToken, async (req, res) => {
// Create a new SSE transport instance.
// The first argument is the endpoint where POST messages will be sent.
transport = new SSEServerTransport('/message', res);
// Connect the MCP server to this transport
await mcpServer.connect(transport);
console.log(`Client connected via SSE: ${req.user.id || 'Unknown User'}`);
});
// Endpoint to handle incoming messages from the client
app.post('/message', authenticateToken, async (req, res) => {
if (!transport) {
return res.status(400).send('SSE connection not initialized');
}
try {
await transport.handlePostMessage(req, res);
} catch (error) {
console.error('Error handling message:', error);
res.status(500).send('Internal Server Error');
}
});
// ---------------------------------------------------------
// 4. Start the Server
// ---------------------------------------------------------
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Secure MCP Server listening on port ${PORT}`);
});
Prerequisites
You will need the MCP SDK, Express, and a JWT library.
npm install @modelcontextprotocol/sdk express jsonwebtoken
Client-Side Connection
When a client (like an LLM application) attempts to connect to your MCP server, it must construct an SSEClientTransport and pass the JWT in the headers.
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
const token = "YOUR_GENERATED_JWT_TOKEN";
const transport = new SSEClientTransport(
new URL("http://localhost:3000/sse"),
{
headers: {
"Authorization": `Bearer ${token}`
}
}
);
const client = new Client(
{ name: "mcp-client", version: "1.0.0" },
{ capabilities: {} }
);
await client.connect(transport);
// Connection successful, authentication passed
