UNPKG

@sei-js/mcp-server

Version:

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

181 lines (180 loc) 7.3 kB
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import cors from 'cors'; import express from 'express'; import startServer from './server.js'; // Environment variables - hardcoded values const PORT = 3001; const HOST = '0.0.0.0'; console.error(`Configured to listen on ${HOST}:${PORT}`); // Setup Express const app = express(); app.use(express.json()); app.use(cors({ origin: '*', methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, exposedHeaders: ['Content-Type', 'Access-Control-Allow-Origin'] })); // Add OPTIONS handling for preflight requests app.options('*', cors()); // Keep track of active connections with session IDs const connections = new Map(); // Initialize the server let server = null; startServer() .then((s) => { server = s; console.error('MCP Server initialized successfully'); }) .catch((error) => { console.error('Failed to initialize server:', error); process.exit(1); }); // Define routes // @ts-ignore app.get('/sse', (req, res) => { console.error(`Received SSE connection request from ${req.ip}`); console.error(`Query parameters: ${JSON.stringify(req.query)}`); // Set CORS headers explicitly res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (!server) { console.error('Server not initialized yet, rejecting SSE connection'); return res.status(503).send('Server not initialized'); } // Generate a unique session ID if one is not provided // The sessionId is crucial for mapping SSE connections to message handlers const sessionId = generateSessionId(); console.error(`Creating SSE session with ID: ${sessionId}`); // Set SSE headers res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache, no-transform'); res.setHeader('Connection', 'keep-alive'); // Create transport - handle before writing to response try { console.error(`Creating SSE transport for session: ${sessionId}`); // Create and store the transport keyed by session ID // Note: The path must match what the client expects (typically "/messages") const transport = new SSEServerTransport('/messages', res); connections.set(sessionId, transport); // Handle connection close req.on('close', () => { console.error(`SSE connection closed for session: ${sessionId}`); connections.delete(sessionId); }); // Connect transport to server - this must happen before sending any data server .connect(transport) .then(() => { // Send an initial event with the session ID for the client to use in messages // Only send this after the connection is established console.error(`SSE connection established for session: ${sessionId}`); // Send the session ID to the client res.write(`data: ${JSON.stringify({ type: 'session_init', sessionId })}\n\n`); }) .catch((error) => { console.error(`Error connecting transport to server: ${error}`); connections.delete(sessionId); }); } catch (error) { console.error(`Error creating SSE transport: ${error}`); connections.delete(sessionId); res.status(500).send(`Internal server error: ${error}`); } }); // @ts-ignore app.post('/messages', (req, res) => { // Extract the session ID from the URL query parameters let sessionId = req.query.sessionId?.toString(); // If no sessionId is provided and there's only one connection, use that if (!sessionId && connections.size === 1) { sessionId = Array.from(connections.keys())[0]; console.error(`No sessionId provided, using the only active session: ${sessionId}`); } console.error(`Received message for sessionId ${sessionId}`); console.error(`Message body: ${JSON.stringify(req.body)}`); // Set CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (!server) { console.error('Server not initialized yet'); return res.status(503).json({ error: 'Server not initialized' }); } if (!sessionId) { console.error('No session ID provided and multiple connections exist'); return res.status(400).json({ error: 'No session ID provided. Please provide a sessionId query parameter or connect to /sse first.', activeConnections: connections.size }); } const transport = connections.get(sessionId); if (!transport) { console.error(`Session not found: ${sessionId}`); return res.status(404).json({ error: 'Session not found' }); } console.error(`Handling message for session: ${sessionId}`); try { transport.handlePostMessage(req, res).catch((error) => { console.error(`Error handling post message: ${error}`); res.status(500).json({ error: `Internal server error: ${error.message}` }); }); } catch (error) { console.error(`Exception handling post message: ${error}`); res.status(500).json({ error: `Internal server error: ${error}` }); } }); // Add a simple health check endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'ok', server: server ? 'initialized' : 'initializing', activeConnections: connections.size, connectedSessionIds: Array.from(connections.keys()) }); }); // Add a root endpoint for basic info app.get('/', (req, res) => { res.status(200).json({ name: 'MCP Server', version: '1.0.0', endpoints: { sse: '/sse', messages: '/messages', health: '/health' }, status: server ? 'ready' : 'initializing', activeConnections: connections.size }); }); // Helper function to generate a UUID-like session ID function generateSessionId() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } // Handle process termination gracefully process.on('SIGINT', () => { console.error('Shutting down server...'); connections.forEach((transport, sessionId) => { console.error(`Closing connection for session: ${sessionId}`); }); process.exit(0); }); // Start the HTTP server on a different port (3001) to avoid conflicts const httpServer = app .listen(PORT, HOST, () => { console.error(`Template MCP Server running at http://${HOST}:${PORT}`); console.error(`SSE endpoint: http://${HOST}:${PORT}/sse`); console.error(`Messages endpoint: http://${HOST}:${PORT}/messages (sessionId optional if only one connection)`); console.error(`Health check: http://${HOST}:${PORT}/health`); }) .on('error', (err) => { console.error(`Server error: ${err}`); });