@sei-js/mcp-server
Version:
Model Context Protocol (MCP) server for interacting with EVM-compatible networks
105 lines (104 loc) • 4.04 kB
JavaScript
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
import { createCorsMiddleware, validateSecurityConfig } from './security.js';
export class HttpSseTransport {
constructor(port, host, path, walletMode = 'disabled') {
this.port = port;
this.host = host;
this.path = path;
this.mode = 'http-sse';
this.httpServer = null;
this.connections = new Map();
this.mcpServer = null;
this.walletMode = walletMode;
this.app = express();
this.setupMiddleware();
this.setupRoutes();
}
setupMiddleware() {
this.app.use(express.json());
// Secure CORS - no cross-origin allowed by default
this.app.use(createCorsMiddleware());
}
setupRoutes() {
// Health check endpoint
this.app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
this.app.get(this.path, (req, res) => {
console.error(`SSE connection from ${req.ip}`);
// Create SSE transport - it will handle headers automatically
const transport = new SSEServerTransport(`${this.path}/message`, res);
const sessionId = transport.sessionId;
this.connections.set(sessionId, transport);
// Connect transport to MCP server
if (this.mcpServer) {
this.mcpServer.connect(transport);
}
// Clean up on disconnect
req.on('close', () => {
this.connections.delete(sessionId);
console.error(`SSE connection closed for session ${sessionId}`);
});
});
// Message endpoint for SSE transport
this.app.post(`${this.path}/message`, async (req, res) => {
try {
const sessionId = typeof req.query.sessionId === 'string' ? req.query.sessionId : undefined;
if (!sessionId) {
res.status(400).json({ error: 'Missing sessionId' });
return;
}
const transport = this.connections.get(sessionId);
if (!transport) {
res.status(404).json({ error: 'Session not found' });
return;
}
await transport.handleMessage(req.body);
res.status(200).end();
}
catch (error) {
console.error('Error handling message:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
}
async start(server) {
// Block wallet mode on HTTP transports
validateSecurityConfig(this.mode, this.walletMode);
this.mcpServer = server;
return new Promise((resolve, reject) => {
this.httpServer = this.app.listen(this.port, this.host, () => {
console.error(`MCP Server ready (http-sse transport on ${this.host}:${this.port}${this.path})`);
resolve();
});
this.httpServer.on('error', (error) => {
console.error('Error starting server:', error);
reject(error);
});
// Handle graceful shutdown
const cleanup = () => {
console.error('Shutting down HTTP SSE server...');
this.connections.clear();
if (this.httpServer) {
this.httpServer.close();
}
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
});
}
async stop() {
return new Promise((resolve) => {
if (this.httpServer) {
this.httpServer.close(() => {
console.error('HTTP SSE server stopped');
resolve();
});
}
else {
resolve();
}
});
}
}