UNPKG

@sei-js/mcp-server

Version:

Model Context Protocol (MCP) server for interacting with EVM-compatible networks

105 lines (104 loc) 4.04 kB
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(); } }); } }