"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/edge/index.ts var _typesjs = require('@modelcontextprotocol/sdk/types.js'); var _hono = require('hono'); var _zod = require('zod'); var _zodtojsonschema = require('zod-to-json-schema'); // src/edge/WebStreamableHTTPServerTransport.ts var MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024; var WebStreamableHTTPServerTransport = (_class = class { __init() {this._enableJsonResponse = false} __init2() {this._encoder = new TextEncoder()} __init3() {this._pendingResponses = []} __init4() {this._requestToStreamMapping = /* @__PURE__ */ new Map()} __init5() {this._standaloneSseStreamId = "_GET_stream"} __init6() {this._started = false} __init7() {this._streamMapping = /* @__PURE__ */ new Map()} constructor(options) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this); this.sessionIdGenerator = options.sessionIdGenerator; this._enableJsonResponse = _nullishCoalesce(options.enableJsonResponse, () => ( false)); this._eventStore = options.eventStore; this._onsessioninitialized = options.onsessioninitialized; this._onsessionclosed = options.onsessionclosed; } /** * Close the transport */ async close() { for (const writer of this._streamMapping.values()) { try { await writer.close(); } catch (e) { } } this._streamMapping.clear(); this._started = false; _optionalChain([this, 'access', _ => _.onclose, 'optionalCall', _2 => _2()]); } /** * Handles an incoming web Request and returns a Response */ async handleRequest(request, parsedBody) { const method = request.method; if (method === "POST") { return this.handlePostRequest(request, parsedBody); } else if (method === "GET") { return this.handleGetRequest(request); } else if (method === "DELETE") { return this.handleDeleteRequest(request); } else { return this.handleUnsupportedRequest(); } } /** * Send a message to connected clients */ async send(message, options) { this._pendingResponses.push(message); const streamId = _optionalChain([options, 'optionalAccess', _3 => _3.relatedRequestId]) ? this._requestToStreamMapping.get(options.relatedRequestId) : this._standaloneSseStreamId; if (streamId) { const writer = this._streamMapping.get(streamId); if (writer) { try { if (this._eventStore) { const eventId = await this._eventStore.storeEvent( streamId, message ); await this.writeSSEEventWithId(writer, eventId, message); } else { await this.writeSSEEvent(writer, message); } } catch (error) { _optionalChain([this, 'access', _4 => _4.onerror, 'optionalCall', _5 => _5( error instanceof Error ? error : new Error(String(error)) )]); } } } } async start() { if (this._started) { throw new Error("Transport already started"); } this._started = true; } /** * Create an error response */ createErrorResponse(status, code, message) { return new Response( JSON.stringify({ error: { code, message }, id: null, jsonrpc: "2.0" }), { headers: { ...this.getResponseHeaders(), "Content-Type": "application/json" }, status } ); } /** * Get common response headers */ getResponseHeaders() { const headers = {}; if (this.sessionId) { headers["mcp-session-id"] = this.sessionId; } return headers; } /** * Handles DELETE requests to terminate sessions */ async handleDeleteRequest(request) { const sessionId = request.headers.get("mcp-session-id"); if (this.sessionIdGenerator) { if (!sessionId) { return this.createErrorResponse( 400, -32e3, "Bad Request: Mcp-Session-Id header is required" ); } if (this.sessionId !== sessionId) { return this.createErrorResponse(404, -32001, "Session not found"); } } for (const writer of this._streamMapping.values()) { try { await writer.close(); } catch (e2) { } } this._streamMapping.clear(); await _optionalChain([this, 'access', _6 => _6._onsessionclosed, 'optionalCall', _7 => _7(_nullishCoalesce(this.sessionId, () => ( "")))]); this.sessionId = void 0; return new Response(null, { headers: this.getResponseHeaders(), status: 204 }); } /** * Handles GET requests for SSE stream */ async handleGetRequest(request) { const acceptHeader = request.headers.get("accept"); if (!_optionalChain([acceptHeader, 'optionalAccess', _8 => _8.includes, 'call', _9 => _9("text/event-stream")])) { return this.createErrorResponse( 406, -32e3, "Not Acceptable: Client must accept text/event-stream" ); } const sessionId = request.headers.get("mcp-session-id"); if (this.sessionIdGenerator && !sessionId) { return this.createErrorResponse( 400, -32e3, "Bad Request: Mcp-Session-Id header is required" ); } if (this.sessionIdGenerator && this.sessionId !== sessionId) { return this.createErrorResponse(404, -32001, "Session not found"); } if (this._streamMapping.has(this._standaloneSseStreamId)) { return this.createErrorResponse( 409, -32e3, "Conflict: SSE stream already exists for this session" ); } if (this._eventStore) { const lastEventId = request.headers.get("last-event-id"); if (lastEventId) { return this.handleReplayEvents(lastEventId); } } const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); this._streamMapping.set(this._standaloneSseStreamId, writer); return new Response(readable, { headers: { ...this.getResponseHeaders(), "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", "Content-Type": "text/event-stream" }, status: 200 }); } /** * Handles POST requests containing JSON-RPC messages */ async handlePostRequest(request, parsedBody) { const acceptHeader = request.headers.get("accept"); if (!_optionalChain([acceptHeader, 'optionalAccess', _10 => _10.includes, 'call', _11 => _11("application/json")]) && !_optionalChain([acceptHeader, 'optionalAccess', _12 => _12.includes, 'call', _13 => _13("text/event-stream")])) { return this.createErrorResponse( 406, -32e3, "Not Acceptable: Client must accept application/json or text/event-stream" ); } const contentType = request.headers.get("content-type"); if (!_optionalChain([contentType, 'optionalAccess', _14 => _14.includes, 'call', _15 => _15("application/json")])) { return this.createErrorResponse( 415, -32e3, "Unsupported Media Type: Content-Type must be application/json" ); } const contentLength = parseInt( _nullishCoalesce(request.headers.get("content-length"), () => ( "0")), 10 ); if (contentLength > MAXIMUM_MESSAGE_SIZE) { return this.createErrorResponse( 413, -32e3, `Request body too large. Maximum size is ${MAXIMUM_MESSAGE_SIZE} bytes` ); } let rawMessage; try { rawMessage = await _asyncNullishCoalesce(parsedBody, async () => ( await request.json())); } catch (e3) { return this.createErrorResponse(400, -32700, "Parse error: Invalid JSON"); } const arrayMessage = Array.isArray(rawMessage) ? rawMessage : [rawMessage]; const messages = []; for (const msg of arrayMessage) { const result = _typesjs.JSONRPCMessageSchema.safeParse(msg); if (!result.success) { return this.createErrorResponse( 400, -32700, "Parse error: Invalid JSON-RPC message" ); } messages.push(result.data); } const requestSessionId = request.headers.get("mcp-session-id"); const hasInitRequest = messages.some((msg) => _typesjs.isInitializeRequest.call(void 0, msg)); if (hasInitRequest && requestSessionId) { return this.createErrorResponse( 400, -32600, "Invalid Request: Initialization requests must not include a sessionId" ); } if (hasInitRequest && messages.length > 1) { return this.createErrorResponse( 400, -32600, "Invalid Request: Only one initialization request is allowed" ); } if (!hasInitRequest && !requestSessionId && this.sessionIdGenerator) { return this.createErrorResponse( 400, -32e3, "Bad Request: Mcp-Session-Id header is required" ); } if (hasInitRequest && this.sessionIdGenerator) { this.sessionId = this.sessionIdGenerator(); await _optionalChain([this, 'access', _16 => _16._onsessioninitialized, 'optionalCall', _17 => _17(this.sessionId)]); } else if (requestSessionId) { if (this.sessionIdGenerator && this.sessionId !== requestSessionId) { return this.createErrorResponse(404, -32001, "Session not found"); } } this._pendingResponses = []; for (const message of messages) { _optionalChain([this, 'access', _18 => _18.onmessage, 'optionalCall', _19 => _19(message, { authInfo: void 0 })]); } if (messages.every( (msg) => _typesjs.isJSONRPCNotification.call(void 0, msg) || _typesjs.isJSONRPCResponse.call(void 0, msg) )) { return new Response(null, { headers: this.getResponseHeaders(), status: 202 }); } if (this._enableJsonResponse && _optionalChain([acceptHeader, 'optionalAccess', _20 => _20.includes, 'call', _21 => _21("application/json")])) { await new Promise((resolve) => setTimeout(resolve, 0)); const responseBody = this._pendingResponses.length === 1 ? JSON.stringify(this._pendingResponses[0]) : JSON.stringify(this._pendingResponses); return new Response(responseBody, { headers: { ...this.getResponseHeaders(), "Content-Type": "application/json" }, status: 200 }); } const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); const streamId = `post_${Date.now()}`; this._streamMapping.set(streamId, writer); (async () => { try { for (const response of this._pendingResponses) { await this.writeSSEEvent(writer, response); } } catch (error) { _optionalChain([this, 'access', _22 => _22.onerror, 'optionalCall', _23 => _23( error instanceof Error ? error : new Error(String(error)) )]); } })(); return new Response(readable, { headers: { ...this.getResponseHeaders(), "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", "Content-Type": "text/event-stream" }, status: 200 }); } /** * Replay events for resumability */ async handleReplayEvents(lastEventId) { if (!this._eventStore) { return this.createErrorResponse( 400, -32e3, "Resumability not supported" ); } const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); try { const streamId = await this._eventStore.replayEventsAfter(lastEventId, { send: async (eventId, message) => { await this.writeSSEEventWithId(writer, eventId, message); } }); this._streamMapping.set(streamId, writer); } catch (error) { await writer.close(); return this.createErrorResponse(500, -32e3, `Replay failed: ${error}`); } return new Response(readable, { headers: { ...this.getResponseHeaders(), "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", "Content-Type": "text/event-stream" }, status: 200 }); } /** * Handles unsupported HTTP methods */ handleUnsupportedRequest() { return this.createErrorResponse(405, -32e3, "Method not allowed"); } /** * Write an SSE event to the stream */ async writeSSEEvent(writer, message) { const data = `data: ${JSON.stringify(message)} `; await writer.write(this._encoder.encode(data)); } /** * Write an SSE event with ID to the stream */ async writeSSEEventWithId(writer, eventId, message) { const data = `id: ${eventId} data: ${JSON.stringify(message)} `; await writer.write(this._encoder.encode(data)); } }, _class); // src/edge/index.ts var EdgeFastMCP = class { #honoApp = new (0, _hono.Hono)(); #logger; #mcpPath; #name; #prompts = []; #resources = []; #tools = []; #version; constructor(options) { this.#name = options.name; this.#version = options.version; this.#logger = _nullishCoalesce(options.logger, () => ( console)); this.#mcpPath = _nullishCoalesce(options.mcpPath, () => ( "/mcp")); this.#setupRoutes(); } /** * Add a prompt to the server */ addPrompt(prompt) { this.#prompts.push(prompt); return this; } /** * Add a resource to the server */ addResource(resource) { this.#resources.push(resource); return this; } /** * Add a tool to the server */ addTool(tool) { this.#tools.push(tool); return this; } /** * Handle an incoming request (main entry point for edge runtimes) */ async fetch(request) { return this.#honoApp.fetch(request); } /** * Get the Hono app for adding custom routes */ getApp() { return this.#honoApp; } /** * Create an error HTTP response */ #errorResponse(status, code, message) { return new Response( JSON.stringify({ error: { code, message }, id: null, jsonrpc: "2.0" }), { headers: { "Content-Type": "application/json" }, status } ); } /** * Handle initialize request */ #handleInitialize(id) { return { id, jsonrpc: "2.0", result: { capabilities: { prompts: this.#prompts.length > 0 ? {} : void 0, resources: this.#resources.length > 0 ? {} : void 0, tools: this.#tools.length > 0 ? {} : void 0 }, protocolVersion: _typesjs.LATEST_PROTOCOL_VERSION, serverInfo: { name: this.#name, version: this.#version } } }; } /** * Handle MCP POST requests */ async #handleMcpRequest(request) { const acceptHeader = request.headers.get("accept"); if (!_optionalChain([acceptHeader, 'optionalAccess', _24 => _24.includes, 'call', _25 => _25("application/json")]) && !_optionalChain([acceptHeader, 'optionalAccess', _26 => _26.includes, 'call', _27 => _27("text/event-stream")])) { return this.#errorResponse( 406, -32e3, "Not Acceptable: Client must accept application/json or text/event-stream" ); } const contentType = request.headers.get("content-type"); if (!_optionalChain([contentType, 'optionalAccess', _28 => _28.includes, 'call', _29 => _29("application/json")])) { return this.#errorResponse( 415, -32e3, "Unsupported Media Type: Content-Type must be application/json" ); } let body; try { body = await request.json(); } catch (e4) { return this.#errorResponse(400, -32700, "Parse error: Invalid JSON"); } const messages = Array.isArray(body) ? body : [body]; const responses = []; for (const message of messages) { const response = await this.#handleMessage(message); if (response) { responses.push(response); } } if (responses.length === 0) { return new Response(null, { status: 202 }); } const responseBody = responses.length === 1 ? JSON.stringify(responses[0]) : JSON.stringify(responses); return new Response(responseBody, { headers: { "Content-Type": "application/json" }, status: 200 }); } /** * Handle SSE GET requests */ async #handleMcpSseRequest(request) { const acceptHeader = request.headers.get("accept"); if (!_optionalChain([acceptHeader, 'optionalAccess', _30 => _30.includes, 'call', _31 => _31("text/event-stream")])) { return this.#errorResponse( 406, -32e3, "Not Acceptable: Client must accept text/event-stream" ); } return this.#errorResponse( 405, -32e3, "Method Not Allowed: SSE streams not supported in stateless mode" ); } /** * Handle individual MCP messages */ async #handleMessage(message) { if (!message || typeof message !== "object") { return this.#rpcError(null, -32700, "Parse error: Invalid message"); } const msg = message; if (msg.jsonrpc !== "2.0") { return this.#rpcError( _nullishCoalesce(msg.id, () => ( null)), -32600, "Invalid Request: jsonrpc must be 2.0" ); } if (!("id" in msg) || msg.id === void 0) { return null; } const method = msg.method; const id = msg.id; const params = msg.params; try { switch (method) { case "initialize": return this.#handleInitialize(id); case "ping": return { id, jsonrpc: "2.0", result: {} }; case "prompts/get": return this.#handlePromptsGet(id, params); case "prompts/list": return this.#handlePromptsList(id); case "resources/list": return this.#handleResourcesList(id); case "resources/read": return this.#handleResourcesRead(id, params); case "tools/call": return this.#handleToolsCall(id, params); case "tools/list": return this.#handleToolsList(id); default: return this.#rpcError(id, -32601, `Method not found: ${method}`); } } catch (error) { this.#logger.error(`Error handling ${method}:`, error); return this.#rpcError( id, -32603, `Internal error: ${error instanceof Error ? error.message : String(error)}` ); } } /** * Handle prompts/get request */ async #handlePromptsGet(id, params) { const promptName = _optionalChain([params, 'optionalAccess', _32 => _32.name]); const promptArgs = _optionalChain([params, 'optionalAccess', _33 => _33.arguments]); const prompt = this.#prompts.find((p) => p.name === promptName); if (!prompt) { return this.#rpcError( id, _typesjs.ErrorCode.InvalidParams, `Prompt not found: ${promptName}` ); } try { const result = await prompt.load(_nullishCoalesce(promptArgs, () => ( {}))); const messages = typeof result === "string" ? [{ content: { text: result, type: "text" }, role: "user" }] : result.messages; return { id, jsonrpc: "2.0", result: { messages } }; } catch (error) { return this.#rpcError( id, _typesjs.ErrorCode.InternalError, `Prompt load failed: ${error instanceof Error ? error.message : String(error)}` ); } } /** * Handle prompts/list request */ #handlePromptsList(id) { return { id, jsonrpc: "2.0", result: { prompts: this.#prompts.map((p) => ({ arguments: p.arguments, description: p.description, name: p.name })) } }; } /** * Handle resources/list request */ #handleResourcesList(id) { return { id, jsonrpc: "2.0", result: { resources: this.#resources.map((r) => ({ description: r.description, mimeType: r.mimeType, name: r.name, uri: r.uri })) } }; } /** * Handle resources/read request */ async #handleResourcesRead(id, params) { const uri = _optionalChain([params, 'optionalAccess', _34 => _34.uri]); const resource = this.#resources.find((r) => r.uri === uri); if (!resource) { return this.#rpcError( id, _typesjs.ErrorCode.InvalidParams, `Resource not found: ${uri}` ); } try { const result = await resource.load(); const content = typeof result === "string" ? { mimeType: _nullishCoalesce(resource.mimeType, () => ( "text/plain")), text: result, uri } : { uri, ...result }; return { id, jsonrpc: "2.0", result: { contents: [content] } }; } catch (error) { return this.#rpcError( id, _typesjs.ErrorCode.InternalError, `Resource load failed: ${error instanceof Error ? error.message : String(error)}` ); } } /** * Handle tools/call request */ async #handleToolsCall(id, params) { const toolName = _optionalChain([params, 'optionalAccess', _35 => _35.name]); const toolArgs = _optionalChain([params, 'optionalAccess', _36 => _36.arguments]); const tool = this.#tools.find((t) => t.name === toolName); if (!tool) { return this.#rpcError( id, _typesjs.ErrorCode.InvalidParams, `Tool not found: ${toolName}` ); } try { const result = await tool.execute(_nullishCoalesce(toolArgs, () => ( {}))); const content = typeof result === "string" ? [{ text: result, type: "text" }] : result.content; return { id, jsonrpc: "2.0", result: { content } }; } catch (error) { return this.#rpcError( id, _typesjs.ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}` ); } } /** * Handle tools/list request */ #handleToolsList(id) { return { id, jsonrpc: "2.0", result: { tools: this.#tools.map((tool) => ({ description: tool.description, inputSchema: tool.parameters ? this.#schemaToJsonSchema(tool.parameters) : { type: "object" }, name: tool.name })) } }; } /** * Create an RPC error message */ #rpcError(id, code, message) { return { error: { code, message }, id, jsonrpc: "2.0" }; } /** * Convert schema to JSON Schema */ #schemaToJsonSchema(schema) { try { if (typeof _zod.z.toJSONSchema === "function") { return _zod.z.toJSONSchema(schema); } if ("_def" in schema || schema instanceof _zod.z.ZodType) { return _zodtojsonschema.zodToJsonSchema.call(void 0, schema, { target: "openApi3" }); } return { type: "object" }; } catch (e5) { return { type: "object" }; } } /** * Set up MCP and health routes */ #setupRoutes() { this.#honoApp.get("/health", (c) => c.text("\u2713 Ok")); this.#honoApp.post(this.#mcpPath, async (c) => { return this.#handleMcpRequest(c.req.raw); }); this.#honoApp.get(this.#mcpPath, async (c) => { return this.#handleMcpSseRequest(c.req.raw); }); this.#honoApp.delete(this.#mcpPath, async () => { return new Response(null, { status: 204 }); }); } }; exports.EdgeFastMCP = EdgeFastMCP; exports.WebStreamableHTTPServerTransport = WebStreamableHTTPServerTransport; //# sourceMappingURL=index.cjs.map