787 lines
24 KiB
JavaScript
787 lines
24 KiB
JavaScript
"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
|