(options: HttpServerOptions)
| 55 | }; |
| 56 | |
| 57 | export async function startHttpServer(options: HttpServerOptions): Promise<HttpServerHandle> { |
| 58 | const host = options.host ?? '127.0.0.1'; |
| 59 | const port = options.port ?? 3100; |
| 60 | const sessions = new Map<string, SessionEntry>(); |
| 61 | |
| 62 | function touchSession(sessionId: string): void { |
| 63 | const session = sessions.get(sessionId); |
| 64 | if (session) { |
| 65 | session.lastActivity = Date.now(); |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | function createSessionServer(): { server: Server; transport: StreamableHTTPServerTransport } { |
| 70 | const server = createServer( |
| 71 | { name: options.name, version: options.version }, |
| 72 | options.registerHandlers |
| 73 | ); |
| 74 | |
| 75 | const transport = new StreamableHTTPServerTransport({ |
| 76 | sessionIdGenerator: () => randomUUID() |
| 77 | }); |
| 78 | |
| 79 | return { server, transport }; |
| 80 | } |
| 81 | |
| 82 | // Session timeout reaper — evicts sessions idle for > SESSION_TIMEOUT_MS. |
| 83 | const timeoutCheck = setInterval(() => { |
| 84 | const now = Date.now(); |
| 85 | for (const [sessionId, session] of sessions.entries()) { |
| 86 | if (now - session.lastActivity > SESSION_TIMEOUT_MS) { |
| 87 | console.error(`[HTTP] Session ${sessionId} timed out`); |
| 88 | void session.transport.close().catch(() => { |
| 89 | /* best effort */ |
| 90 | }); |
| 91 | sessions.delete(sessionId); |
| 92 | } |
| 93 | } |
| 94 | }, SESSION_CHECK_INTERVAL_MS); |
| 95 | timeoutCheck.unref(); |
| 96 | |
| 97 | const httpServer = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => { |
| 98 | try { |
| 99 | const url = new URL(req.url ?? '/', `http://${host}:${port}`); |
| 100 | |
| 101 | // Health check on root |
| 102 | if (url.pathname === '/' && req.method === 'GET') { |
| 103 | res.writeHead(200, { 'Content-Type': 'application/json' }); |
| 104 | res.end(JSON.stringify({ status: 'ok', sessions: sessions.size })); |
| 105 | return; |
| 106 | } |
| 107 | |
| 108 | // Only handle /mcp |
| 109 | if (url.pathname !== '/mcp') { |
| 110 | res.writeHead(404, { 'Content-Type': 'application/json' }); |
| 111 | res.end(JSON.stringify({ error: 'Not found' })); |
| 112 | return; |
| 113 | } |
| 114 |
no test coverage detected