Skip to main content
Glama
streamable-http.ts5.49 kB
/** * StreamableHTTP server setup for HTTP-based MCP communication using Hono */ import { Hono } from 'hono'; import { cors } from 'hono/cors'; import { serve } from '@hono/node-server'; import { v4 as uuid } from 'uuid'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { JSONRPCError } from '@modelcontextprotocol/sdk/types.js'; import { toReqRes, toFetchResponse } from 'fetch-to-node'; import { Config } from './config.js'; // Constants const JSON_RPC = '2.0'; /** * StreamableHTTP MCP Server handler */ class MCPStreamableHttpServer { server: Server; constructor(server: Server) { this.server = server; } /** * Handle GET requests (typically used for static files) */ async handleGetRequest(c: any) { console.error( 'GET request received - StreamableHTTP transport only supports POST', ); return c.text('Method Not Allowed', 405, { Allow: 'POST', }); } /** * Handle POST requests (all MCP communication) */ async handlePostRequest(c: any) { try { const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); await this.server.connect(transport); const body = await c.req.json(); // Convert Fetch Request to Node.js req/res const { req, res } = toReqRes(c.req.raw); await transport.handleRequest(req, res, body); res.on('close', () => { console.log('Request closed'); transport.close(); this.server.close(); }); // Convert Node.js response back to Fetch Response return toFetchResponse(res); } catch (error) { console.error('Error handling MCP request:', error); return c.json(this.createErrorResponse('Internal server error.'), 500); } } /** * Create a JSON-RPC error response */ private createErrorResponse(message: string): JSONRPCError { return { jsonrpc: JSON_RPC, error: { code: -32000, message: message, }, id: uuid(), }; } } /** * Sets up a web server for the MCP server using StreamableHTTP transport * * @param server The MCP Server instance * @param port The port to listen on (default: 3000) * @returns The Hono app instance */ export async function setupStreamableHttpServer(server: Server, port = 3000) { // Create Hono app const app = new Hono(); // Enable CORS app.use('*', cors()); // Create MCP handler const mcpHandler = new MCPStreamableHttpServer(server); // Add a simple health check endpoint app.get('/health', (c) => { return c.json({ status: 'OK', server: Config.SERVER_NAME, version: Config.SERVER_VERSION, }); }); // Main MCP endpoint supporting both GET and POST app.get('/mcp', (c) => mcpHandler.handleGetRequest(c)); app.post('/mcp', (c) => mcpHandler.handlePostRequest(c)); // Static files for the web client (if any) app.get('/*', async (c) => { const filePath = c.req.path === '/' ? '/index.html' : c.req.path; try { // Use Node.js fs to serve static files const fs = await import('node:fs'); const path = await import('node:path'); const { fileURLToPath } = await import('node:url'); const __dirname = path.dirname(fileURLToPath(import.meta.url)); const publicPath = path.join(__dirname, '..', '..', 'public'); const fullPath = path.join(publicPath, filePath); // Simple security check to prevent directory traversal if (!fullPath.startsWith(publicPath)) { return c.text('Forbidden', 403); } try { const stat = fs.statSync(fullPath); if (stat.isFile()) { const content = fs.readFileSync(fullPath); // Set content type based on file extension const ext = path.extname(fullPath).toLowerCase(); let contentType = 'text/plain'; switch (ext) { case '.html': contentType = 'text/html'; break; case '.css': contentType = 'text/css'; break; case '.js': contentType = 'text/javascript'; break; case '.json': contentType = 'application/json'; break; case '.png': contentType = 'image/png'; break; case '.jpg': contentType = 'image/jpeg'; break; case '.svg': contentType = 'image/svg+xml'; break; } return new Response(content, { headers: { 'Content-Type': contentType }, }); } } catch (err) { // File not found or other error return c.text('Not Found', 404); } } catch (err) { console.error('Error serving static file:', err); return c.text('Internal Server Error', 500); } return c.text('Not Found', 404); }); // Start the server serve( { fetch: app.fetch, port, }, (info) => { console.error( `MCP StreamableHTTP Server running at http://localhost:${info.port}`, ); console.error(`- MCP Endpoint: http://localhost:${info.port}/mcp`); console.error(`- Health Check: http://localhost:${info.port}/health`); }, ); return app; }

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/MoralisWeb3/moralis-mcp-server'

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