Skip to main content
Glama
http.ts6.97 kB
import { Hono } from 'hono'; import { serve } from '@hono/node-server'; import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { MCPToolDefinition } from '../types/mcp-tool.js'; import type { Config } from '../config/schema.js'; import { getToolList, createMcpServer } from './mcp.js'; import { log } from '../utils/logger.js'; import { randomUUID } from 'crypto'; import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; // Store transports by session ID for stateful mode const transports: Map<string, WebStandardStreamableHTTPServerTransport> = new Map(); /** * Filter tools by URL query params (?tools=tool1,tool2) * Returns filtered tools if ?tools param is present, otherwise returns all tools */ function filterToolsByQueryParams( tools: MCPToolDefinition[], url: URL ): MCPToolDefinition[] { const toolsParam = url.searchParams.get('tools'); if (!toolsParam) { return tools; } const requestedTools = new Set( toolsParam.split(',').map((t) => t.trim()).filter((t) => t.length > 0) ); if (requestedTools.size === 0) { return tools; } const filtered = tools.filter((t) => requestedTools.has(t.name)); log.info('Filtered tools by URL params', { requested: Array.from(requestedTools), matched: filtered.map((t) => t.name), }); return filtered; } /** * Create Hono HTTP server with MCP and health endpoints */ export function createHttpServer( _mcpServer: McpServer, // Initial server instance (kept for API compatibility) tools: MCPToolDefinition[], config: Config ) { // Factory function to create fresh MCP server per session with optional URL filtering const createServer = (requestUrl?: URL) => { const filteredTools = requestUrl ? filterToolsByQueryParams(tools, requestUrl) : tools; return createMcpServer(filteredTools, config); }; const app = new Hono(); // Health check endpoint app.get('/health', (c) => { return c.json({ status: 'healthy', server: 'openapi-mcp-ts', version: '1.0.0', tools: { total: tools.length, enabled: tools.filter((t) => t._ui.enabled).length, }, sessions: transports.size, }); }); // List tools endpoint (for debugging) app.get('/tools', (c) => { return c.json({ tools: getToolList(tools), }); }); // MCP endpoint - handles all HTTP methods for MCP protocol app.all(config.server.basePath, async (c) => { const sessionId = c.req.header('mcp-session-id'); try { // For POST requests, we need to handle session management if (c.req.method === 'POST') { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const body = await c.req.json(); let transport: WebStandardStreamableHTTPServerTransport; if (sessionId && transports.has(sessionId)) { // Reuse existing transport for this session transport = transports.get(sessionId)!; log.debug('Reusing transport for session', { sessionId }); } else if (!sessionId && isInitializeRequest(body)) { // New initialization request - create new transport and server transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), enableJsonResponse: true, onsessioninitialized: (newSessionId) => { log.info('Session initialized', { sessionId: newSessionId }); transports.set(newSessionId, transport); }, }); // Set up cleanup on transport close transport.onclose = () => { const sid = transport.sessionId; if (sid && transports.has(sid)) { log.info('Transport closed, removing session', { sessionId: sid }); transports.delete(sid); } }; // Connect a fresh MCP server to the transport BEFORE handling request // Filter tools by ?tools= query param if present const requestUrl = new URL(c.req.url); const server = createServer(requestUrl); await server.connect(transport); log.debug('Created new transport for initialization'); } else if (!sessionId) { // Non-initialization request without session ID return c.json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided', }, id: null, }, 400); } else { // Session ID provided but not found return c.json({ jsonrpc: '2.0', error: { code: -32000, message: 'Session not found', }, id: null, }, 404); } // Handle the request using Web Standard API const response = await transport.handleRequest(c.req.raw, { parsedBody: body }); return response; } // For GET requests (SSE streams) if (c.req.method === 'GET') { if (!sessionId || !transports.has(sessionId)) { return c.text('Invalid or missing session ID', 400); } const transport = transports.get(sessionId)!; return transport.handleRequest(c.req.raw); } // For DELETE requests (session termination) if (c.req.method === 'DELETE') { if (!sessionId || !transports.has(sessionId)) { return c.text('Invalid or missing session ID', 400); } const transport = transports.get(sessionId)!; return transport.handleRequest(c.req.raw); } // Unsupported method return c.text('Method not allowed', 405); } catch (error) { const message = error instanceof Error ? error.message : 'Unknown error'; log.error('MCP endpoint error', { error: message, method: c.req.method }); return c.json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }, 500); } }); // 404 for other routes app.notFound((c) => { return c.json({ error: 'Not Found', message: `Route ${c.req.path} not found`, availableRoutes: ['/health', '/tools', config.server.basePath], }, 404); }); return app; } /** * Start the HTTP server */ export function startServer( app: Hono, config: Config ): void { const { port, host } = config.server; serve({ fetch: app.fetch, port, hostname: host, }, (info) => { log.info('Server started', { host: info.address, port: info.port, health: `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}/health`, mcp: `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}${config.server.basePath}`, }); }); }

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/procoders/openapi-mcp-ts'

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