Skip to main content
Glama
fmangot
by fmangot
server-http.ts7.23 kB
#!/usr/bin/env node /** * Sequential Thinking MCP Server - HTTP Version * Remote HTTP server using Streamable HTTP transport (MCP 2025-03-26 spec) */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { SessionManager } from './core/session-manager.js'; import { ALL_TOOLS } from './tools/definitions.js'; import { handleToolCall } from './tools/handlers.js'; import { setCorsHeaders, handlePreflight } from './middleware/cors.js'; import { RateLimiter, getClientIdentifier, sendRateLimitError } from './middleware/rate-limit.js'; import http from 'http'; import { URL } from 'url'; const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; const NODE_ENV = process.env.NODE_ENV || 'development'; // Initialize session manager and rate limiter const sessionManager = new SessionManager(); const rateLimiter = new RateLimiter(100, 15 * 60 * 1000); // 100 requests per 15 minutes /** * Create an MCP server instance for a specific session */ function createMCPServer(sessionId: string): Server { const { manager } = sessionManager.getOrCreate(sessionId); const server = new Server( { name: 'sequential-thinking-mvp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Handle tool listing server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: ALL_TOOLS }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { return handleToolCall(request.params, manager); }); return server; } /** * Extract session ID from request headers or query params */ function getSessionId(req: http.IncomingMessage, url: URL): string | undefined { // Check header first const headerSessionId = req.headers['x-session-id']; if (headerSessionId && typeof headerSessionId === 'string') { return headerSessionId; } // Check query parameter const querySessionId = url.searchParams.get('sessionId'); if (querySessionId) { return querySessionId; } return undefined; } // Create HTTP server const httpServer = http.createServer(async (req, res) => { const url = new URL(req.url || '', `http://${req.headers.host}`); // Set CORS headers setCorsHeaders(req, res); // Handle CORS preflight if (handlePreflight(req, res)) { return; } // Rate limiting (skip for health check) if (url.pathname !== '/health') { const clientId = getClientIdentifier(req); if (rateLimiter.check(clientId)) { const resetTime = rateLimiter.getResetTime(clientId); sendRateLimitError(res, resetTime); return; } } try { // Health check endpoint if (url.pathname === '/health') { const stats = sessionManager.getStats(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end( JSON.stringify({ status: 'ok', timestamp: Date.now(), uptime: process.uptime(), sessions: stats.totalSessions, environment: NODE_ENV, }) ); return; } // Info endpoint if (url.pathname === '/' || url.pathname === '/info') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end( JSON.stringify({ name: 'Sequential Thinking MCP Server', version: '1.0.0', protocol: 'MCP 2025-03-26', transport: 'Streamable HTTP', description: 'Remote MCP server for sequential thinking and step-by-step reasoning', endpoints: { 'POST /mcp': 'MCP Streamable HTTP endpoint (send x-session-id header or sessionId query param)', '/health': 'Health check endpoint', '/info': 'Server information', }, documentation: 'https://github.com/modelcontextprotocol/specification', }) ); return; } // Streamable HTTP endpoint for MCP protocol if (url.pathname === '/mcp' && req.method === 'POST') { const sessionId = getSessionId(req, url); // Read request body let body = ''; req.on('data', (chunk) => { body += chunk.toString(); }); await new Promise<void>((resolve, reject) => { req.on('end', () => resolve()); req.on('error', reject); }); const requestData = JSON.parse(body); // Create new transport for each request (prevents ID collisions) const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true, }); res.on('close', () => { transport.close(); }); // Create or get existing MCP server for this session const mcpSessionId = sessionId || transport.sessionId || 'default'; const server = createMCPServer(mcpSessionId); await server.connect(transport); await transport.handleRequest(req, res, requestData); return; } // 404 for other paths res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Not found' })); } catch (error) { console.error('Error handling request:', error); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end( JSON.stringify({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error', }) ); } }); // Start the server httpServer.listen(PORT, () => { console.log(`Sequential Thinking MCP Server v1.0.0`); console.log(`Protocol: MCP 2025-03-26 (Streamable HTTP)`); console.log(`Environment: ${NODE_ENV}`); console.log(`Listening on: http://localhost:${PORT}`); console.log(`\nEndpoints:`); console.log(` - POST http://localhost:${PORT}/mcp (MCP protocol)`); console.log(` - GET http://localhost:${PORT}/health`); console.log(` - GET http://localhost:${PORT}/info`); console.log(`\nSend session ID via:`); console.log(` - Header: x-session-id: <your-session-id>`); console.log(` - Query: ?sessionId=<your-session-id>`); console.log(`\nRate limit: 100 requests per 15 minutes per IP`); }); // Graceful shutdown function gracefulShutdown(signal: string): void { console.log(`\n${signal} received. Shutting down gracefully...`); httpServer.close(() => { console.log('HTTP server closed'); // Cleanup sessionManager.destroy(); rateLimiter.destroy(); console.log('Cleanup complete. Exiting.'); process.exit(0); }); // Force exit after 10 seconds setTimeout(() => { console.error('Forced shutdown after timeout'); process.exit(1); }, 10000); } process.on('SIGINT', () => gracefulShutdown('SIGINT')); process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); // Handle uncaught errors process.on('uncaughtException', (error) => { console.error('Uncaught exception:', error); gracefulShutdown('UNCAUGHT_EXCEPTION'); }); process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled rejection at:', promise, 'reason:', reason); });

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fmangot/Mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server