Skip to main content
Glama

MCP Specification Server

by MCPJam
23
6
  • Apple
chunk-MXVPEZER.js•14.2 kB
// src/InMemoryEventStore.ts var InMemoryEventStore = class { events = /* @__PURE__ */ new Map(); /** * Replays events that occurred after a specific event ID * Implements EventStore.replayEventsAfter */ async replayEventsAfter(lastEventId, { send }) { if (!lastEventId || !this.events.has(lastEventId)) { return ""; } const streamId = this.getStreamIdFromEventId(lastEventId); if (!streamId) { return ""; } let foundLastEvent = false; const sortedEvents = [...this.events.entries()].sort( (a, b) => a[0].localeCompare(b[0]) ); for (const [ eventId, { message, streamId: eventStreamId } ] of sortedEvents) { if (eventStreamId !== streamId) { continue; } if (eventId === lastEventId) { foundLastEvent = true; continue; } if (foundLastEvent) { await send(eventId, message); } } return streamId; } /** * Stores an event with a generated event ID * Implements EventStore.storeEvent */ async storeEvent(streamId, message) { const eventId = this.generateEventId(streamId); this.events.set(eventId, { message, streamId }); return eventId; } /** * Generates a unique event ID for a given stream ID */ generateEventId(streamId) { return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`; } /** * Extracts the stream ID from an event ID */ getStreamIdFromEventId(eventId) { const parts = eventId.split("_"); return parts.length > 0 ? parts[0] : ""; } }; // src/proxyServer.ts import { CallToolRequestSchema, CompleteRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, LoggingMessageNotificationSchema, ReadResourceRequestSchema, ResourceUpdatedNotificationSchema, SubscribeRequestSchema, UnsubscribeRequestSchema } from "@modelcontextprotocol/sdk/types.js"; var proxyServer = async ({ client, server, serverCapabilities }) => { if (serverCapabilities?.logging) { server.setNotificationHandler( LoggingMessageNotificationSchema, async (args) => { return client.notification(args); } ); client.setNotificationHandler( LoggingMessageNotificationSchema, async (args) => { return server.notification(args); } ); } if (serverCapabilities?.prompts) { server.setRequestHandler(GetPromptRequestSchema, async (args) => { return client.getPrompt(args.params); }); server.setRequestHandler(ListPromptsRequestSchema, async (args) => { return client.listPrompts(args.params); }); } if (serverCapabilities?.resources) { server.setRequestHandler(ListResourcesRequestSchema, async (args) => { return client.listResources(args.params); }); server.setRequestHandler( ListResourceTemplatesRequestSchema, async (args) => { return client.listResourceTemplates(args.params); } ); server.setRequestHandler(ReadResourceRequestSchema, async (args) => { return client.readResource(args.params); }); if (serverCapabilities?.resources.subscribe) { server.setNotificationHandler( ResourceUpdatedNotificationSchema, async (args) => { return client.notification(args); } ); server.setRequestHandler(SubscribeRequestSchema, async (args) => { return client.subscribeResource(args.params); }); server.setRequestHandler(UnsubscribeRequestSchema, async (args) => { return client.unsubscribeResource(args.params); }); } } if (serverCapabilities?.tools) { server.setRequestHandler(CallToolRequestSchema, async (args) => { return client.callTool(args.params); }); server.setRequestHandler(ListToolsRequestSchema, async (args) => { return client.listTools(args.params); }); } server.setRequestHandler(CompleteRequestSchema, async (args) => { return client.complete(args.params); }); }; // src/startHTTPServer.ts import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import http from "http"; import { randomUUID } from "crypto"; var getBody = (request) => { return new Promise((resolve) => { const bodyParts = []; let body; request.on("data", (chunk) => { bodyParts.push(chunk); }).on("end", () => { body = Buffer.concat(bodyParts).toString(); try { resolve(JSON.parse(body)); } catch (error) { console.error("[mcp-proxy] error parsing body", error); resolve(null); } }); }); }; var handleStreamRequest = async ({ activeTransports, createServer, endpoint, eventStore, onClose, onConnect, req, res }) => { if (req.method === "POST" && new URL(req.url, "http://localhost").pathname === endpoint) { try { const sessionId = Array.isArray(req.headers["mcp-session-id"]) ? req.headers["mcp-session-id"][0] : req.headers["mcp-session-id"]; let transport; let server; const body = await getBody(req); if (sessionId) { const activeTransport = activeTransports[sessionId]; if (!activeTransport) { res.setHeader("Content-Type", "application/json"); res.writeHead(404).end( JSON.stringify({ error: { code: -32001, message: "Session not found" }, id: null, jsonrpc: "2.0" }) ); return true; } transport = activeTransport.transport; server = activeTransport.server; } else if (!sessionId && isInitializeRequest(body)) { transport = new StreamableHTTPServerTransport({ eventStore: eventStore || new InMemoryEventStore(), onsessioninitialized: (_sessionId) => { activeTransports[_sessionId] = { server, transport }; }, sessionIdGenerator: randomUUID }); transport.onclose = async () => { const sid = transport.sessionId; if (sid && activeTransports[sid]) { if (onClose) { await onClose(server); } try { await server.close(); } catch (error) { console.error("[mcp-proxy] error closing server", error); } delete activeTransports[sid]; } }; try { server = await createServer(req); } catch (error) { if (error instanceof Response) { res.writeHead(error.status).end(error.statusText); return true; } res.writeHead(500).end("Error creating server"); return true; } server.connect(transport); if (onConnect) { await onConnect(server); } await transport.handleRequest(req, res, body); return true; } else { res.setHeader("Content-Type", "application/json"); res.writeHead(400).end( JSON.stringify({ error: { code: -32e3, message: "Bad Request: No valid session ID provided" }, id: null, jsonrpc: "2.0" }) ); return true; } await transport.handleRequest(req, res, body); return true; } catch (error) { console.error("[mcp-proxy] error handling request", error); res.setHeader("Content-Type", "application/json"); res.writeHead(500).end( JSON.stringify({ error: { code: -32603, message: "Internal Server Error" }, id: null, jsonrpc: "2.0" }) ); } return true; } if (req.method === "GET" && new URL(req.url, "http://localhost").pathname === endpoint) { const sessionId = req.headers["mcp-session-id"]; const activeTransport = sessionId ? activeTransports[sessionId] : void 0; if (!sessionId) { res.writeHead(400).end("No sessionId"); return true; } if (!activeTransport) { res.writeHead(400).end("No active transport"); return true; } const lastEventId = req.headers["last-event-id"]; if (lastEventId) { console.log( `[mcp-proxy] client reconnecting with Last-Event-ID ${lastEventId} for session ID ${sessionId}` ); } else { console.log( `[mcp-proxy] establishing new SSE stream for session ID ${sessionId}` ); } await activeTransport.transport.handleRequest(req, res); return true; } if (req.method === "DELETE" && new URL(req.url, "http://localhost").pathname === endpoint) { console.log("[mcp-proxy] received delete request"); const sessionId = req.headers["mcp-session-id"]; if (!sessionId) { res.writeHead(400).end("Invalid or missing sessionId"); return true; } console.log("[mcp-proxy] received delete request for session", sessionId); const activeTransport = activeTransports[sessionId]; if (!activeTransport) { res.writeHead(400).end("No active transport"); return true; } try { await activeTransport.transport.handleRequest(req, res); if (onClose) { await onClose(activeTransport.server); } } catch (error) { console.error("[mcp-proxy] error handling delete request", error); res.writeHead(500).end("Error handling delete request"); } return true; } return false; }; var handleSSERequest = async ({ activeTransports, createServer, endpoint, onClose, onConnect, req, res }) => { if (req.method === "GET" && new URL(req.url, "http://localhost").pathname === endpoint) { const transport = new SSEServerTransport("/messages", res); let server; try { server = await createServer(req); } catch (error) { if (error instanceof Response) { res.writeHead(error.status).end(error.statusText); return true; } res.writeHead(500).end("Error creating server"); return true; } activeTransports[transport.sessionId] = transport; let closed = false; res.on("close", async () => { closed = true; try { await server.close(); } catch (error) { console.error("[mcp-proxy] error closing server", error); } delete activeTransports[transport.sessionId]; await onClose?.(server); }); try { await server.connect(transport); await transport.send({ jsonrpc: "2.0", method: "sse/connection", params: { message: "SSE Connection established" } }); if (onConnect) { await onConnect(server); } } catch (error) { if (!closed) { console.error("[mcp-proxy] error connecting to server", error); res.writeHead(500).end("Error connecting to server"); } } return true; } if (req.method === "POST" && req.url?.startsWith("/messages")) { const sessionId = new URL(req.url, "https://example.com").searchParams.get( "sessionId" ); if (!sessionId) { res.writeHead(400).end("No sessionId"); return true; } const activeTransport = activeTransports[sessionId]; if (!activeTransport) { res.writeHead(400).end("No active transport"); return true; } await activeTransport.handlePostMessage(req, res); return true; } return false; }; var startHTTPServer = async ({ createServer, eventStore, host = "::", onClose, onConnect, onUnhandledRequest, port, sseEndpoint = "/sse", streamEndpoint = "/mcp" }) => { const activeSSETransports = {}; const activeStreamTransports = {}; const httpServer = http.createServer(async (req, res) => { if (req.headers.origin) { try { const origin = new URL(req.headers.origin); res.setHeader("Access-Control-Allow-Origin", origin.origin); res.setHeader("Access-Control-Allow-Credentials", "true"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "*"); res.setHeader("Access-Control-Expose-Headers", "mcp-session-id"); } catch (error) { console.error("[mcp-proxy] error parsing origin", error); } } if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } if (req.method === "GET" && req.url === `/ping`) { res.writeHead(200).end("pong"); return; } if (sseEndpoint && await handleSSERequest({ activeTransports: activeSSETransports, createServer, endpoint: sseEndpoint, onClose, onConnect, req, res })) { return; } if (streamEndpoint && await handleStreamRequest({ activeTransports: activeStreamTransports, createServer, endpoint: streamEndpoint, eventStore, onClose, onConnect, req, res })) { return; } if (onUnhandledRequest) { await onUnhandledRequest(req, res); } else { res.writeHead(404).end(); } }); await new Promise((resolve) => { httpServer.listen(port, host, () => { resolve(void 0); }); }); return { close: async () => { for (const transport of Object.values(activeSSETransports)) { await transport.close(); } for (const transport of Object.values(activeStreamTransports)) { await transport.transport.close(); } return new Promise((resolve, reject) => { httpServer.close((error) => { if (error) { reject(error); return; } resolve(); }); }); } }; }; export { InMemoryEventStore, proxyServer, startHTTPServer }; //# sourceMappingURL=chunk-MXVPEZER.js.map

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/MCPJam/mcp-spec'

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