#!/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);
});