UNPKG

chaite

Version:

core for chatgpt-plugin and karin-plugin-chatgpt

104 lines (103 loc) 2.96 kB
//#region src/agent/tool-executors/mcp.ts /** * MCP client-side ToolExecutor. * Communicates with an MCP server over JSON-RPC 2.0 (HTTP transport). * Optional fallback executor is called when the remote call fails. * * Install @modelcontextprotocol/sdk for full lifecycle support; * this implementation uses raw fetch for minimal overhead. */ var McpToolExecutor = class { toolCache = null; constructor(endpoint, fallback) { this.endpoint = endpoint; this.fallback = fallback; } async execute(call, ctx) { const timeoutMs = ctx.timeoutMs ?? this.endpoint.defaultTimeoutMs ?? 15e3; try { const outputText = ((await this.rpcCall("tools/call", { name: call.name, arguments: call.arguments }, call.id, timeoutMs))?.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n").trim(); return { id: call.id, name: call.name, ok: true, outputText }; } catch (err) { const isFatal = err instanceof McpError && err.code !== "TIMEOUT" && err.code !== "NETWORK"; if (!isFatal && this.fallback) return this.fallback.execute(call, ctx); return { id: call.id, name: call.name, ok: false, outputText: "", error: { kind: isFatal ? "FATAL" : "RETRYABLE", message: err instanceof Error ? err.message : String(err), cause: err } }; } } async listAvailableTools(_ctx) { if (this.toolCache) return this.toolCache; try { const tools = ((await this.rpcCall("tools/list", {}, "list", 1e4))?.tools ?? []).map((t) => ({ name: t.name, description: t.description, schema: t.inputSchema })); this.toolCache = tools; return tools; } catch { return []; } } /** Invalidate tool list cache (call after server reconnect) */ invalidateCache() { this.toolCache = null; } async rpcCall(method, params, id, timeoutMs) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const headers = { "content-type": "application/json" }; if (this.endpoint.authHeader) headers["authorization"] = this.endpoint.authHeader; const res = await fetch(this.endpoint.baseUrl, { method: "POST", headers, body: JSON.stringify({ jsonrpc: "2.0", id, method, params }), signal: controller.signal }); if (!res.ok) { const kind = res.status >= 500 ? "NETWORK" : "FATAL"; throw new McpError(`MCP HTTP ${res.status}`, kind); } const json = await res.json(); if (json.error) throw new McpError(json.error.message, "FATAL"); return json.result; } catch (err) { if (err.name === "AbortError") throw new McpError(`MCP request timeout after ${timeoutMs}ms`, "TIMEOUT"); throw err; } finally { clearTimeout(timer); } } }; var McpError = class extends Error { constructor(message, code) { super(message); this.code = code; this.name = "McpError"; } }; //#endregion export { McpToolExecutor as t };