@openeryc/pi-coding-agent 0.75.49 → 0.75.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/core/agent-session.d.ts +9 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +20 -1
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/mcp/client.d.ts +6 -0
  7. package/dist/core/mcp/client.d.ts.map +1 -1
  8. package/dist/core/mcp/client.js +63 -30
  9. package/dist/core/mcp/client.js.map +1 -1
  10. package/dist/core/mcp/manager.d.ts +21 -1
  11. package/dist/core/mcp/manager.d.ts.map +1 -1
  12. package/dist/core/mcp/manager.js +186 -57
  13. package/dist/core/mcp/manager.js.map +1 -1
  14. package/dist/core/mcp/types.d.ts +12 -0
  15. package/dist/core/mcp/types.d.ts.map +1 -1
  16. package/dist/core/mcp/types.js +3 -0
  17. package/dist/core/mcp/types.js.map +1 -1
  18. package/dist/core/settings-manager.d.ts +5 -0
  19. package/dist/core/settings-manager.d.ts.map +1 -1
  20. package/dist/core/settings-manager.js.map +1 -1
  21. package/dist/modes/interactive/components/mcp-selector.d.ts +1 -1
  22. package/dist/modes/interactive/components/mcp-selector.d.ts.map +1 -1
  23. package/dist/modes/interactive/components/mcp-selector.js +3 -1
  24. package/dist/modes/interactive/components/mcp-selector.js.map +1 -1
  25. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  26. package/dist/modes/interactive/interactive-mode.js +9 -15
  27. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  28. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  29. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  30. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  31. package/examples/extensions/sandbox/package-lock.json +2 -2
  32. package/examples/extensions/sandbox/package.json +1 -1
  33. package/examples/extensions/with-deps/package-lock.json +2 -2
  34. package/examples/extensions/with-deps/package.json +1 -1
  35. package/npm-shrinkwrap.json +12 -12
  36. package/package.json +4 -4
@@ -1,13 +1,19 @@
1
1
  import type { MCPCallToolResult, MCPResource, MCPServerConfig, MCPToolDefinition } from "./types.ts";
2
2
  export type { MCPServerConfig };
3
+ export type MCPDisconnectHandler = (reason: string) => void;
3
4
  export declare class MCPClient {
4
5
  private process;
5
6
  private requestId;
6
7
  private pending;
7
8
  private sseEndpoint;
8
9
  private timeoutMs;
10
+ private _connected;
11
+ /** Called when the connection is lost unexpectedly */
12
+ onDisconnect: MCPDisconnectHandler | null;
9
13
  constructor(timeoutMs?: number);
14
+ get isConnected(): boolean;
10
15
  connect(config: MCPServerConfig): Promise<void>;
16
+ private handleDisconnect;
11
17
  private connectStdio;
12
18
  private connectHttp;
13
19
  private readSSEEvent;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAErG,YAAY,EAAE,eAAe,EAAE,CAAC;AAgBhC,qBAAa,SAAS;IACrB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,SAAS,CAAC,EAAE,MAAM,EAE7B;IAEK,OAAO,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BpD;YAEa,YAAY;YAkDZ,WAAW;YAmEX,YAAY;YA4BZ,IAAI;IAgDlB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAuDjB,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,iBAAiB,EAAE,CAAA;KAAE,CAAC,CAE7E;IAEK,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC,CAE/E;IAEK,YAAY,CACjB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC;QAAE,QAAQ,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAIjF;IAEK,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE5G;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAahC;CACD","sourcesContent":["import { type ChildProcess, spawn } from \"child_process\";\nimport { createInterface } from \"readline\";\nimport type { MCPCallToolResult, MCPResource, MCPServerConfig, MCPToolDefinition } from \"./types.ts\";\n\nexport type { MCPServerConfig };\n\n/** Default MCP tool call timeout: 2 minutes */\nconst DEFAULT_MCP_TIMEOUT_MS = 120_000;\n/** MCP connection handshake timeout: 30 seconds */\nconst MCP_CONNECT_TIMEOUT_MS = 30_000;\n\ntype PendingRequest = { resolve: (v: unknown) => void; reject: (e: Error) => void };\n\nfunction resolveTransport(config: MCPServerConfig): \"stdio\" | \"sse\" | \"http\" {\n\tif (config.transport) return config.transport;\n\tif (config.command) return \"stdio\";\n\tif (config.url) return \"sse\";\n\treturn \"stdio\";\n}\n\nexport class MCPClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate requestId = 0;\n\tprivate pending = new Map<number, PendingRequest>();\n\tprivate sseEndpoint: string | null = null;\n\tprivate timeoutMs: number;\n\n\tconstructor(timeoutMs?: number) {\n\t\tthis.timeoutMs = timeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;\n\t}\n\n\tasync connect(config: MCPServerConfig): Promise<void> {\n\t\tif (config.timeoutMs !== undefined) {\n\t\t\tthis.timeoutMs = config.timeoutMs;\n\t\t}\n\n\t\tconst transport = resolveTransport(config);\n\n\t\t// Use a shorter timeout for the connection handshake\n\t\t// (server startup via npx, dependency downloads, etc.)\n\t\tconst savedTimeout = this.timeoutMs;\n\t\tthis.timeoutMs = Math.min(this.timeoutMs, MCP_CONNECT_TIMEOUT_MS);\n\t\ttry {\n\t\t\tif (transport === \"stdio\") {\n\t\t\t\tawait this.connectStdio(config);\n\t\t\t} else {\n\t\t\t\tawait this.connectHttp(config, transport);\n\t\t\t}\n\t\t\tawait this.send(\"initialize\", {\n\t\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\t\tcapabilities: {},\n\t\t\t\tclientInfo: { name: \"pi\", version: \"1.0.0\" },\n\t\t\t});\n\t\t} finally {\n\t\t\t// Restore the configured timeout for subsequent tool calls\n\t\t\tthis.timeoutMs = savedTimeout;\n\t\t}\n\t}\n\n\tprivate async connectStdio(config: MCPServerConfig): Promise<void> {\n\t\tconst { command, args = [], env } = config;\n\t\tif (!command) throw new Error(\"MCP stdio server requires 'command'\");\n\n\t\tconst childEnv = env ? { ...process.env, ...env } : process.env;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst proc = spawn(command, args, { env: childEnv, stdio: [\"pipe\", \"pipe\", \"pipe\"], shell: false });\n\t\t\tthis.process = proc;\n\n\t\t\tconst onError = (error: Error) => {\n\t\t\t\treject(new Error(`Failed to spawn MCP server \"${command}\": ${error.message}`));\n\t\t\t};\n\t\t\tproc.on(\"error\", onError);\n\t\t\tproc.once(\"spawn\", () => proc.removeListener(\"error\", onError));\n\n\t\t\tif (!proc.stdout) {\n\t\t\t\treject(new Error(`MCP server \"${command}\" has no stdout`));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rl = createInterface({ input: proc.stdout, crlfDelay: Infinity });\n\t\t\trl.on(\"line\", (line: string) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst res = JSON.parse(line);\n\t\t\t\t\tif (res.id !== undefined) {\n\t\t\t\t\t\tconst handler = this.pending.get(res.id);\n\t\t\t\t\t\tif (!handler) return;\n\t\t\t\t\t\tthis.pending.delete(res.id);\n\t\t\t\t\t\tif (res.error) {\n\t\t\t\t\t\t\thandler.reject(new Error(`MCP error: ${res.error.message}`));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thandler.resolve(res.result);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t/* non-JSON lines */\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tproc.on(\"exit\", (code) => {\n\t\t\t\tfor (const [, h] of this.pending) {\n\t\t\t\t\th.reject(new Error(`MCP server \"${command}\" exited with code ${code}`));\n\t\t\t\t}\n\t\t\t\tthis.pending.clear();\n\t\t\t});\n\n\t\t\tresolve();\n\t\t});\n\t}\n\n\tprivate async connectHttp(config: MCPServerConfig, transport: \"sse\" | \"http\"): Promise<void> {\n\t\tconst { url, headers = {} } = config;\n\t\tif (!url) throw new Error(\"MCP SSE/HTTP server requires 'url'\");\n\n\t\tif (transport === \"sse\") {\n\t\t\tconst res = await fetch(`${url}/sse`, { headers: { ...headers, Accept: \"text/event-stream\" } });\n\t\t\tif (!res.ok) throw new Error(`SSE connection failed: ${res.status} ${res.statusText}`);\n\n\t\t\tconst reader = res.body!.getReader();\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet buffer = \"\";\n\n\t\t\tconst processChunk = async () => {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\tif (line.startsWith(\"data: \")) {\n\t\t\t\t\t\t\tconst data = line.slice(6);\n\t\t\t\t\t\t\tif (data.startsWith(\"{\") && this.sseEndpoint === null) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\t\t\t\t\t\tif (parsed.result) {\n\t\t\t\t\t\t\t\t\t\tconst handler = this.pending.get(parsed.id);\n\t\t\t\t\t\t\t\t\t\tif (handler) {\n\t\t\t\t\t\t\t\t\t\t\tthis.pending.delete(parsed.id);\n\t\t\t\t\t\t\t\t\t\t\tif (parsed.error) {\n\t\t\t\t\t\t\t\t\t\t\t\thandler.reject(new Error(`MCP error: ${parsed.error.message}`));\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\thandler.resolve(parsed.result);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t/* skip */\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (data.startsWith(\"http\")) {\n\t\t\t\t\t\t\t\t// This is the endpoint URL\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (line.startsWith(\"event: endpoint\")) {\n\t\t\t\t\t\t\t// Next data line will be the endpoint URL\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Wait for endpoint event\n\t\t\tconst endpointLine = await this.readSSEEvent(reader, decoder);\n\t\t\tif (endpointLine) {\n\t\t\t\tthis.sseEndpoint = endpointLine.startsWith(\"/\") ? new URL(endpointLine, url).href : endpointLine;\n\t\t\t} else {\n\t\t\t\tthis.sseEndpoint = url;\n\t\t\t}\n\n\t\t\t// Start background reader\n\t\t\tprocessChunk().catch(() => {});\n\t\t} else {\n\t\t\t// Streamable HTTP - POST to URL directly, server responds with JSON or upgrades to SSE\n\t\t\tthis.sseEndpoint = url;\n\t\t}\n\t}\n\n\tprivate async readSSEEvent(\n\t\treader: ReadableStreamDefaultReader<Uint8Array>,\n\t\tdecoder: { decode(b?: Uint8Array, o?: { stream?: boolean }): string },\n\t): Promise<string | null> {\n\t\tlet buffer = \"\";\n\t\tconst deadline = Date.now() + 10000;\n\n\t\twhile (Date.now() < deadline) {\n\t\t\tconst { value, done } = await reader.read();\n\t\t\tif (done) break;\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\tconst line = lines[i];\n\t\t\t\tif (line.startsWith(\"data: \") && lines[i - 1]?.startsWith(\"event: endpoint\")) {\n\t\t\t\t\treturn line.slice(6).trim();\n\t\t\t\t}\n\t\t\t\tif (line.startsWith(\"event: endpoint\") && lines[i + 1]?.startsWith(\"data: \")) {\n\t\t\t\t\treturn lines[i + 1].slice(6).trim();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate async send(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {\n\t\tconst id = ++this.requestId;\n\t\tconst body = JSON.stringify({ jsonrpc: \"2.0\", id, method, params });\n\n\t\tif (this.process?.stdin) {\n\t\t\treturn this.sendWithTimeout(id, body, signal, (_resolve, reject) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.process!.stdin!.write(`${body}\\n`);\n\t\t\t\t} catch (err) {\n\t\t\t\t\treject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (this.sseEndpoint) {\n\t\t\treturn this.sendWithTimeout(id, body, signal, async (resolve, reject) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst timeoutMs = this.timeoutMs > 0 ? this.timeoutMs : undefined;\n\t\t\t\t\tconst res = await fetch(this.sseEndpoint!, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\tsignal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,\n\t\t\t\t\t});\n\t\t\t\t\tconst text = await res.text();\n\t\t\t\t\tif (res.headers.get(\"content-type\")?.includes(\"text/event-stream\")) {\n\t\t\t\t\t\t// SSE upgrade - response comes via event stream\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst parsed = JSON.parse(text);\n\t\t\t\t\tif (parsed.error) {\n\t\t\t\t\t\treject(new Error(`MCP error: ${parsed.error.message}`));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresolve(parsed.result);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof Error && err.name === \"TimeoutError\") {\n\t\t\t\t\t\treject(new Error(`MCP request timed out after ${this.timeoutMs}ms`));\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tthrow new Error(\"MCP client not connected\");\n\t}\n\n\t/**\n\t * Wraps a request with timeout and AbortSignal support.\n\t * Registers the pending handler so that response lines resolve it,\n\t * and rejects if the timeout fires or the caller's signal aborts.\n\t */\n\tprivate sendWithTimeout(\n\t\tid: number,\n\t\t_body: string,\n\t\tsignal: AbortSignal | undefined,\n\t\twriter: (resolve: (v: unknown) => void, reject: (e: Error) => void) => void,\n\t): Promise<unknown> {\n\t\treturn new Promise<unknown>((outerResolve, outerReject) => {\n\t\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\t\tlet done = false;\n\n\t\t\tconst cleanup = () => {\n\t\t\t\tif (done) return;\n\t\t\t\tdone = true;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tthis.pending.delete(id);\n\t\t\t};\n\n\t\t\tconst resolve = (v: unknown) => {\n\t\t\t\tcleanup();\n\t\t\t\touterResolve(v);\n\t\t\t};\n\n\t\t\tconst reject = (e: Error) => {\n\t\t\t\tcleanup();\n\t\t\t\touterReject(e);\n\t\t\t};\n\n\t\t\t// Timeout (unless disabled by setting timeoutMs = 0)\n\t\t\tif (this.timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\treject(new Error(`MCP request timed out after ${this.timeoutMs}ms`));\n\t\t\t\t}, this.timeoutMs);\n\t\t\t}\n\n\t\t\t// Caller abort signal\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\treject(new DOMException(\"MCP request cancelled\", \"AbortError\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsignal.addEventListener(\n\t\t\t\t\t\"abort\",\n\t\t\t\t\t() => {\n\t\t\t\t\t\treject(new DOMException(\"MCP request cancelled\", \"AbortError\"));\n\t\t\t\t\t},\n\t\t\t\t\t{ once: true },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.pending.set(id, { resolve, reject });\n\n\t\t\twriter(resolve, reject);\n\t\t});\n\t}\n\n\tasync listTools(signal?: AbortSignal): Promise<{ tools: MCPToolDefinition[] }> {\n\t\treturn (await this.send(\"tools/list\", undefined, signal)) as { tools: MCPToolDefinition[] };\n\t}\n\n\tasync listResources(signal?: AbortSignal): Promise<{ resources: MCPResource[] }> {\n\t\treturn (await this.send(\"resources/list\", undefined, signal)) as { resources: MCPResource[] };\n\t}\n\n\tasync readResource(\n\t\turi: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<{ contents: Array<{ uri: string; mimeType?: string; text?: string }> }> {\n\t\treturn (await this.send(\"resources/read\", { uri }, signal)) as {\n\t\t\tcontents: Array<{ uri: string; mimeType?: string; text?: string }>;\n\t\t};\n\t}\n\n\tasync callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<MCPCallToolResult> {\n\t\treturn (await this.send(\"tools/call\", { name, arguments: args }, signal)) as MCPCallToolResult;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tconst proc = this.process;\n\t\tthis.process = null;\n\t\tthis.pending.clear();\n\t\tthis.sseEndpoint = null;\n\t\tif (proc && !proc.killed) {\n\t\t\tproc.kill();\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tif (proc.exitCode !== null) return resolve();\n\t\t\t\tproc.on(\"exit\", resolve);\n\t\t\t\tsetTimeout(resolve, 3000);\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAErG,YAAY,EAAE,eAAe,EAAE,CAAC;AAgBhC,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5D,qBAAa,SAAS;IACrB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAE3B,sDAAsD;IACtD,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IAEjD,YAAY,SAAS,CAAC,EAAE,MAAM,EAE7B;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAEK,OAAO,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BpD;IAED,OAAO,CAAC,gBAAgB;YAcV,YAAY;YAmDZ,WAAW;YA2EX,YAAY;YA4BZ,IAAI;IAgDlB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAuDjB,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,iBAAiB,EAAE,CAAA;KAAE,CAAC,CAE7E;IAEK,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC,CAE/E;IAEK,YAAY,CACjB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC;QAAE,QAAQ,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAIjF;IAEK,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE5G;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAehC;CACD","sourcesContent":["import { type ChildProcess, spawn } from \"child_process\";\nimport { createInterface } from \"readline\";\nimport type { MCPCallToolResult, MCPResource, MCPServerConfig, MCPToolDefinition } from \"./types.ts\";\n\nexport type { MCPServerConfig };\n\n/** Default MCP tool call timeout: 2 minutes */\nconst DEFAULT_MCP_TIMEOUT_MS = 120_000;\n/** MCP connection handshake timeout: 30 seconds */\nconst MCP_CONNECT_TIMEOUT_MS = 30_000;\n\ntype PendingRequest = { resolve: (v: unknown) => void; reject: (e: Error) => void };\n\nfunction resolveTransport(config: MCPServerConfig): \"stdio\" | \"sse\" | \"http\" {\n\tif (config.transport) return config.transport;\n\tif (config.command) return \"stdio\";\n\tif (config.url) return \"sse\";\n\treturn \"stdio\";\n}\n\nexport type MCPDisconnectHandler = (reason: string) => void;\n\nexport class MCPClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate requestId = 0;\n\tprivate pending = new Map<number, PendingRequest>();\n\tprivate sseEndpoint: string | null = null;\n\tprivate timeoutMs: number;\n\tprivate _connected = false;\n\n\t/** Called when the connection is lost unexpectedly */\n\tonDisconnect: MCPDisconnectHandler | null = null;\n\n\tconstructor(timeoutMs?: number) {\n\t\tthis.timeoutMs = timeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;\n\t}\n\n\tget isConnected(): boolean {\n\t\treturn this._connected;\n\t}\n\n\tasync connect(config: MCPServerConfig): Promise<void> {\n\t\tif (config.timeoutMs !== undefined) {\n\t\t\tthis.timeoutMs = config.timeoutMs;\n\t\t}\n\n\t\tconst transport = resolveTransport(config);\n\n\t\t// Use a shorter timeout for the connection handshake\n\t\t// (server startup via npx, dependency downloads, etc.)\n\t\tconst savedTimeout = this.timeoutMs;\n\t\tthis.timeoutMs = Math.min(this.timeoutMs, MCP_CONNECT_TIMEOUT_MS);\n\t\ttry {\n\t\t\tif (transport === \"stdio\") {\n\t\t\t\tawait this.connectStdio(config);\n\t\t\t} else {\n\t\t\t\tawait this.connectHttp(config, transport);\n\t\t\t}\n\t\t\tawait this.send(\"initialize\", {\n\t\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\t\tcapabilities: {},\n\t\t\t\tclientInfo: { name: \"pi\", version: \"1.0.0\" },\n\t\t\t});\n\t\t\tthis._connected = true;\n\t\t} finally {\n\t\t\t// Restore the configured timeout for subsequent tool calls\n\t\t\tthis.timeoutMs = savedTimeout;\n\t\t}\n\t}\n\n\tprivate handleDisconnect(reason: string): void {\n\t\tif (!this._connected) return;\n\t\tthis._connected = false;\n\t\tthis.pending.clear();\n\t\tthis.sseEndpoint = null;\n\t\tthis.process = null;\n\n\t\ttry {\n\t\t\tthis.onDisconnect?.(reason);\n\t\t} catch {\n\t\t\t// ignore errors from disconnect handler\n\t\t}\n\t}\n\n\tprivate async connectStdio(config: MCPServerConfig): Promise<void> {\n\t\tconst { command, args = [], env } = config;\n\t\tif (!command) throw new Error(\"MCP stdio server requires 'command'\");\n\n\t\tconst childEnv = env ? { ...process.env, ...env } : process.env;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst proc = spawn(command, args, { env: childEnv, stdio: [\"pipe\", \"pipe\", \"pipe\"], shell: false });\n\t\t\tthis.process = proc;\n\n\t\t\tconst onError = (error: Error) => {\n\t\t\t\treject(new Error(`Failed to spawn MCP server \"${command}\": ${error.message}`));\n\t\t\t};\n\t\t\tproc.on(\"error\", onError);\n\t\t\tproc.once(\"spawn\", () => proc.removeListener(\"error\", onError));\n\n\t\t\tif (!proc.stdout) {\n\t\t\t\treject(new Error(`MCP server \"${command}\" has no stdout`));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rl = createInterface({ input: proc.stdout, crlfDelay: Infinity });\n\t\t\trl.on(\"line\", (line: string) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst res = JSON.parse(line);\n\t\t\t\t\tif (res.id !== undefined) {\n\t\t\t\t\t\tconst handler = this.pending.get(res.id);\n\t\t\t\t\t\tif (!handler) return;\n\t\t\t\t\t\tthis.pending.delete(res.id);\n\t\t\t\t\t\tif (res.error) {\n\t\t\t\t\t\t\thandler.reject(new Error(`MCP error: ${res.error.message}`));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thandler.resolve(res.result);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t/* non-JSON lines */\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tproc.on(\"exit\", (code) => {\n\t\t\t\tfor (const [, h] of this.pending) {\n\t\t\t\t\th.reject(new Error(`MCP server \"${command}\" exited with code ${code}`));\n\t\t\t\t}\n\t\t\t\tthis.pending.clear();\n\t\t\t\tthis.handleDisconnect(`process exited with code ${code}`);\n\t\t\t});\n\n\t\t\tresolve();\n\t\t});\n\t}\n\n\tprivate async connectHttp(config: MCPServerConfig, transport: \"sse\" | \"http\"): Promise<void> {\n\t\tconst { url, headers = {} } = config;\n\t\tif (!url) throw new Error(\"MCP SSE/HTTP server requires 'url'\");\n\n\t\tif (transport === \"sse\") {\n\t\t\tconst res = await fetch(`${url}/sse`, { headers: { ...headers, Accept: \"text/event-stream\" } });\n\t\t\tif (!res.ok) throw new Error(`SSE connection failed: ${res.status} ${res.statusText}`);\n\n\t\t\tconst reader = res.body!.getReader();\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet buffer = \"\";\n\n\t\t\tconst processChunk = async () => {\n\t\t\t\ttry {\n\t\t\t\t\twhile (true) {\n\t\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\t\tif (done) break;\n\t\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\t\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\t\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\t\tif (line.startsWith(\"data: \")) {\n\t\t\t\t\t\t\t\tconst data = line.slice(6);\n\t\t\t\t\t\t\t\tif (data.startsWith(\"{\") && this.sseEndpoint === null) {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\t\t\t\t\t\t\tif (parsed.result) {\n\t\t\t\t\t\t\t\t\t\t\tconst handler = this.pending.get(parsed.id);\n\t\t\t\t\t\t\t\t\t\t\tif (handler) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.pending.delete(parsed.id);\n\t\t\t\t\t\t\t\t\t\t\t\tif (parsed.error) {\n\t\t\t\t\t\t\t\t\t\t\t\t\thandler.reject(new Error(`MCP error: ${parsed.error.message}`));\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\thandler.resolve(parsed.result);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t\t/* skip */\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (data.startsWith(\"http\")) {\n\t\t\t\t\t\t\t\t\t// This is the endpoint URL\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (line.startsWith(\"event: endpoint\")) {\n\t\t\t\t\t\t\t\t// Next data line will be the endpoint URL\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Stream error means connection lost\n\t\t\t\t\tthis.handleDisconnect(err instanceof Error ? err.message : String(err));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Stream ended normally\n\t\t\t\tthis.handleDisconnect(\"SSE stream ended\");\n\t\t\t};\n\n\t\t\t// Wait for endpoint event\n\t\t\tconst endpointLine = await this.readSSEEvent(reader, decoder);\n\t\t\tif (endpointLine) {\n\t\t\t\tthis.sseEndpoint = endpointLine.startsWith(\"/\") ? new URL(endpointLine, url).href : endpointLine;\n\t\t\t} else {\n\t\t\t\tthis.sseEndpoint = url;\n\t\t\t}\n\n\t\t\t// Start background reader\n\t\t\tprocessChunk();\n\t\t} else {\n\t\t\t// Streamable HTTP - POST to URL directly, server responds with JSON or upgrades to SSE\n\t\t\tthis.sseEndpoint = url;\n\t\t}\n\t}\n\n\tprivate async readSSEEvent(\n\t\treader: ReadableStreamDefaultReader<Uint8Array>,\n\t\tdecoder: { decode(b?: Uint8Array, o?: { stream?: boolean }): string },\n\t): Promise<string | null> {\n\t\tlet buffer = \"\";\n\t\tconst deadline = Date.now() + 10000;\n\n\t\twhile (Date.now() < deadline) {\n\t\t\tconst { value, done } = await reader.read();\n\t\t\tif (done) break;\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\tconst line = lines[i];\n\t\t\t\tif (line.startsWith(\"data: \") && lines[i - 1]?.startsWith(\"event: endpoint\")) {\n\t\t\t\t\treturn line.slice(6).trim();\n\t\t\t\t}\n\t\t\t\tif (line.startsWith(\"event: endpoint\") && lines[i + 1]?.startsWith(\"data: \")) {\n\t\t\t\t\treturn lines[i + 1].slice(6).trim();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate async send(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {\n\t\tconst id = ++this.requestId;\n\t\tconst body = JSON.stringify({ jsonrpc: \"2.0\", id, method, params });\n\n\t\tif (this.process?.stdin) {\n\t\t\treturn this.sendWithTimeout(id, body, signal, (_resolve, reject) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.process!.stdin!.write(`${body}\\n`);\n\t\t\t\t} catch (err) {\n\t\t\t\t\treject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (this.sseEndpoint) {\n\t\t\treturn this.sendWithTimeout(id, body, signal, async (resolve, reject) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst timeoutMs = this.timeoutMs > 0 ? this.timeoutMs : undefined;\n\t\t\t\t\tconst res = await fetch(this.sseEndpoint!, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\tsignal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,\n\t\t\t\t\t});\n\t\t\t\t\tconst text = await res.text();\n\t\t\t\t\tif (res.headers.get(\"content-type\")?.includes(\"text/event-stream\")) {\n\t\t\t\t\t\t// SSE upgrade - response comes via event stream\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst parsed = JSON.parse(text);\n\t\t\t\t\tif (parsed.error) {\n\t\t\t\t\t\treject(new Error(`MCP error: ${parsed.error.message}`));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresolve(parsed.result);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof Error && err.name === \"TimeoutError\") {\n\t\t\t\t\t\treject(new Error(`MCP request timed out after ${this.timeoutMs}ms`));\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tthrow new Error(\"MCP client not connected\");\n\t}\n\n\t/**\n\t * Wraps a request with timeout and AbortSignal support.\n\t * Registers the pending handler so that response lines resolve it,\n\t * and rejects if the timeout fires or the caller's signal aborts.\n\t */\n\tprivate sendWithTimeout(\n\t\tid: number,\n\t\t_body: string,\n\t\tsignal: AbortSignal | undefined,\n\t\twriter: (resolve: (v: unknown) => void, reject: (e: Error) => void) => void,\n\t): Promise<unknown> {\n\t\treturn new Promise<unknown>((outerResolve, outerReject) => {\n\t\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\t\tlet done = false;\n\n\t\t\tconst cleanup = () => {\n\t\t\t\tif (done) return;\n\t\t\t\tdone = true;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tthis.pending.delete(id);\n\t\t\t};\n\n\t\t\tconst resolve = (v: unknown) => {\n\t\t\t\tcleanup();\n\t\t\t\touterResolve(v);\n\t\t\t};\n\n\t\t\tconst reject = (e: Error) => {\n\t\t\t\tcleanup();\n\t\t\t\touterReject(e);\n\t\t\t};\n\n\t\t\t// Timeout (unless disabled by setting timeoutMs = 0)\n\t\t\tif (this.timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\treject(new Error(`MCP request timed out after ${this.timeoutMs}ms`));\n\t\t\t\t}, this.timeoutMs);\n\t\t\t}\n\n\t\t\t// Caller abort signal\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\treject(new DOMException(\"MCP request cancelled\", \"AbortError\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsignal.addEventListener(\n\t\t\t\t\t\"abort\",\n\t\t\t\t\t() => {\n\t\t\t\t\t\treject(new DOMException(\"MCP request cancelled\", \"AbortError\"));\n\t\t\t\t\t},\n\t\t\t\t\t{ once: true },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.pending.set(id, { resolve, reject });\n\n\t\t\twriter(resolve, reject);\n\t\t});\n\t}\n\n\tasync listTools(signal?: AbortSignal): Promise<{ tools: MCPToolDefinition[] }> {\n\t\treturn (await this.send(\"tools/list\", undefined, signal)) as { tools: MCPToolDefinition[] };\n\t}\n\n\tasync listResources(signal?: AbortSignal): Promise<{ resources: MCPResource[] }> {\n\t\treturn (await this.send(\"resources/list\", undefined, signal)) as { resources: MCPResource[] };\n\t}\n\n\tasync readResource(\n\t\turi: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<{ contents: Array<{ uri: string; mimeType?: string; text?: string }> }> {\n\t\treturn (await this.send(\"resources/read\", { uri }, signal)) as {\n\t\t\tcontents: Array<{ uri: string; mimeType?: string; text?: string }>;\n\t\t};\n\t}\n\n\tasync callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<MCPCallToolResult> {\n\t\treturn (await this.send(\"tools/call\", { name, arguments: args }, signal)) as MCPCallToolResult;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tthis._connected = false;\n\t\tthis.onDisconnect = null;\n\t\tconst proc = this.process;\n\t\tthis.process = null;\n\t\tthis.pending.clear();\n\t\tthis.sseEndpoint = null;\n\t\tif (proc && !proc.killed) {\n\t\t\tproc.kill();\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tif (proc.exitCode !== null) return resolve();\n\t\t\t\tproc.on(\"exit\", resolve);\n\t\t\t\tsetTimeout(resolve, 3000);\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
@@ -19,9 +19,15 @@ export class MCPClient {
19
19
  pending = new Map();
20
20
  sseEndpoint = null;
21
21
  timeoutMs;
22
+ _connected = false;
23
+ /** Called when the connection is lost unexpectedly */
24
+ onDisconnect = null;
22
25
  constructor(timeoutMs) {
23
26
  this.timeoutMs = timeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
24
27
  }
28
+ get isConnected() {
29
+ return this._connected;
30
+ }
25
31
  async connect(config) {
26
32
  if (config.timeoutMs !== undefined) {
27
33
  this.timeoutMs = config.timeoutMs;
@@ -43,12 +49,27 @@ export class MCPClient {
43
49
  capabilities: {},
44
50
  clientInfo: { name: "pi", version: "1.0.0" },
45
51
  });
52
+ this._connected = true;
46
53
  }
47
54
  finally {
48
55
  // Restore the configured timeout for subsequent tool calls
49
56
  this.timeoutMs = savedTimeout;
50
57
  }
51
58
  }
59
+ handleDisconnect(reason) {
60
+ if (!this._connected)
61
+ return;
62
+ this._connected = false;
63
+ this.pending.clear();
64
+ this.sseEndpoint = null;
65
+ this.process = null;
66
+ try {
67
+ this.onDisconnect?.(reason);
68
+ }
69
+ catch {
70
+ // ignore errors from disconnect handler
71
+ }
72
+ }
52
73
  async connectStdio(config) {
53
74
  const { command, args = [], env } = config;
54
75
  if (!command)
@@ -92,6 +113,7 @@ export class MCPClient {
92
113
  h.reject(new Error(`MCP server "${command}" exited with code ${code}`));
93
114
  }
94
115
  this.pending.clear();
116
+ this.handleDisconnect(`process exited with code ${code}`);
95
117
  });
96
118
  resolve();
97
119
  });
@@ -108,45 +130,54 @@ export class MCPClient {
108
130
  const decoder = new TextDecoder();
109
131
  let buffer = "";
110
132
  const processChunk = async () => {
111
- while (true) {
112
- const { done, value } = await reader.read();
113
- if (done)
114
- break;
115
- buffer += decoder.decode(value, { stream: true });
116
- const lines = buffer.split("\n");
117
- buffer = lines.pop() ?? "";
118
- for (const line of lines) {
119
- if (line.startsWith("data: ")) {
120
- const data = line.slice(6);
121
- if (data.startsWith("{") && this.sseEndpoint === null) {
122
- try {
123
- const parsed = JSON.parse(data);
124
- if (parsed.result) {
125
- const handler = this.pending.get(parsed.id);
126
- if (handler) {
127
- this.pending.delete(parsed.id);
128
- if (parsed.error) {
129
- handler.reject(new Error(`MCP error: ${parsed.error.message}`));
130
- }
131
- else {
132
- handler.resolve(parsed.result);
133
+ try {
134
+ while (true) {
135
+ const { done, value } = await reader.read();
136
+ if (done)
137
+ break;
138
+ buffer += decoder.decode(value, { stream: true });
139
+ const lines = buffer.split("\n");
140
+ buffer = lines.pop() ?? "";
141
+ for (const line of lines) {
142
+ if (line.startsWith("data: ")) {
143
+ const data = line.slice(6);
144
+ if (data.startsWith("{") && this.sseEndpoint === null) {
145
+ try {
146
+ const parsed = JSON.parse(data);
147
+ if (parsed.result) {
148
+ const handler = this.pending.get(parsed.id);
149
+ if (handler) {
150
+ this.pending.delete(parsed.id);
151
+ if (parsed.error) {
152
+ handler.reject(new Error(`MCP error: ${parsed.error.message}`));
153
+ }
154
+ else {
155
+ handler.resolve(parsed.result);
156
+ }
133
157
  }
134
158
  }
135
159
  }
160
+ catch {
161
+ /* skip */
162
+ }
136
163
  }
137
- catch {
138
- /* skip */
164
+ else if (data.startsWith("http")) {
165
+ // This is the endpoint URL
139
166
  }
140
167
  }
141
- else if (data.startsWith("http")) {
142
- // This is the endpoint URL
168
+ else if (line.startsWith("event: endpoint")) {
169
+ // Next data line will be the endpoint URL
143
170
  }
144
171
  }
145
- else if (line.startsWith("event: endpoint")) {
146
- // Next data line will be the endpoint URL
147
- }
148
172
  }
149
173
  }
174
+ catch (err) {
175
+ // Stream error means connection lost
176
+ this.handleDisconnect(err instanceof Error ? err.message : String(err));
177
+ return;
178
+ }
179
+ // Stream ended normally
180
+ this.handleDisconnect("SSE stream ended");
150
181
  };
151
182
  // Wait for endpoint event
152
183
  const endpointLine = await this.readSSEEvent(reader, decoder);
@@ -157,7 +188,7 @@ export class MCPClient {
157
188
  this.sseEndpoint = url;
158
189
  }
159
190
  // Start background reader
160
- processChunk().catch(() => { });
191
+ processChunk();
161
192
  }
162
193
  else {
163
194
  // Streamable HTTP - POST to URL directly, server responds with JSON or upgrades to SSE
@@ -292,6 +323,8 @@ export class MCPClient {
292
323
  return (await this.send("tools/call", { name, arguments: args }, signal));
293
324
  }
294
325
  async disconnect() {
326
+ this._connected = false;
327
+ this.onDisconnect = null;
295
328
  const proc = this.process;
296
329
  this.process = null;
297
330
  this.pending.clear();
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/core/mcp/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"child_process\";\nimport { createInterface } from \"readline\";\nimport type { MCPCallToolResult, MCPResource, MCPServerConfig, MCPToolDefinition } from \"./types.ts\";\n\nexport type { MCPServerConfig };\n\n/** Default MCP tool call timeout: 2 minutes */\nconst DEFAULT_MCP_TIMEOUT_MS = 120_000;\n/** MCP connection handshake timeout: 30 seconds */\nconst MCP_CONNECT_TIMEOUT_MS = 30_000;\n\ntype PendingRequest = { resolve: (v: unknown) => void; reject: (e: Error) => void };\n\nfunction resolveTransport(config: MCPServerConfig): \"stdio\" | \"sse\" | \"http\" {\n\tif (config.transport) return config.transport;\n\tif (config.command) return \"stdio\";\n\tif (config.url) return \"sse\";\n\treturn \"stdio\";\n}\n\nexport class MCPClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate requestId = 0;\n\tprivate pending = new Map<number, PendingRequest>();\n\tprivate sseEndpoint: string | null = null;\n\tprivate timeoutMs: number;\n\n\tconstructor(timeoutMs?: number) {\n\t\tthis.timeoutMs = timeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;\n\t}\n\n\tasync connect(config: MCPServerConfig): Promise<void> {\n\t\tif (config.timeoutMs !== undefined) {\n\t\t\tthis.timeoutMs = config.timeoutMs;\n\t\t}\n\n\t\tconst transport = resolveTransport(config);\n\n\t\t// Use a shorter timeout for the connection handshake\n\t\t// (server startup via npx, dependency downloads, etc.)\n\t\tconst savedTimeout = this.timeoutMs;\n\t\tthis.timeoutMs = Math.min(this.timeoutMs, MCP_CONNECT_TIMEOUT_MS);\n\t\ttry {\n\t\t\tif (transport === \"stdio\") {\n\t\t\t\tawait this.connectStdio(config);\n\t\t\t} else {\n\t\t\t\tawait this.connectHttp(config, transport);\n\t\t\t}\n\t\t\tawait this.send(\"initialize\", {\n\t\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\t\tcapabilities: {},\n\t\t\t\tclientInfo: { name: \"pi\", version: \"1.0.0\" },\n\t\t\t});\n\t\t} finally {\n\t\t\t// Restore the configured timeout for subsequent tool calls\n\t\t\tthis.timeoutMs = savedTimeout;\n\t\t}\n\t}\n\n\tprivate async connectStdio(config: MCPServerConfig): Promise<void> {\n\t\tconst { command, args = [], env } = config;\n\t\tif (!command) throw new Error(\"MCP stdio server requires 'command'\");\n\n\t\tconst childEnv = env ? { ...process.env, ...env } : process.env;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst proc = spawn(command, args, { env: childEnv, stdio: [\"pipe\", \"pipe\", \"pipe\"], shell: false });\n\t\t\tthis.process = proc;\n\n\t\t\tconst onError = (error: Error) => {\n\t\t\t\treject(new Error(`Failed to spawn MCP server \"${command}\": ${error.message}`));\n\t\t\t};\n\t\t\tproc.on(\"error\", onError);\n\t\t\tproc.once(\"spawn\", () => proc.removeListener(\"error\", onError));\n\n\t\t\tif (!proc.stdout) {\n\t\t\t\treject(new Error(`MCP server \"${command}\" has no stdout`));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rl = createInterface({ input: proc.stdout, crlfDelay: Infinity });\n\t\t\trl.on(\"line\", (line: string) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst res = JSON.parse(line);\n\t\t\t\t\tif (res.id !== undefined) {\n\t\t\t\t\t\tconst handler = this.pending.get(res.id);\n\t\t\t\t\t\tif (!handler) return;\n\t\t\t\t\t\tthis.pending.delete(res.id);\n\t\t\t\t\t\tif (res.error) {\n\t\t\t\t\t\t\thandler.reject(new Error(`MCP error: ${res.error.message}`));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thandler.resolve(res.result);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t/* non-JSON lines */\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tproc.on(\"exit\", (code) => {\n\t\t\t\tfor (const [, h] of this.pending) {\n\t\t\t\t\th.reject(new Error(`MCP server \"${command}\" exited with code ${code}`));\n\t\t\t\t}\n\t\t\t\tthis.pending.clear();\n\t\t\t});\n\n\t\t\tresolve();\n\t\t});\n\t}\n\n\tprivate async connectHttp(config: MCPServerConfig, transport: \"sse\" | \"http\"): Promise<void> {\n\t\tconst { url, headers = {} } = config;\n\t\tif (!url) throw new Error(\"MCP SSE/HTTP server requires 'url'\");\n\n\t\tif (transport === \"sse\") {\n\t\t\tconst res = await fetch(`${url}/sse`, { headers: { ...headers, Accept: \"text/event-stream\" } });\n\t\t\tif (!res.ok) throw new Error(`SSE connection failed: ${res.status} ${res.statusText}`);\n\n\t\t\tconst reader = res.body!.getReader();\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet buffer = \"\";\n\n\t\t\tconst processChunk = async () => {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\tif (line.startsWith(\"data: \")) {\n\t\t\t\t\t\t\tconst data = line.slice(6);\n\t\t\t\t\t\t\tif (data.startsWith(\"{\") && this.sseEndpoint === null) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\t\t\t\t\t\tif (parsed.result) {\n\t\t\t\t\t\t\t\t\t\tconst handler = this.pending.get(parsed.id);\n\t\t\t\t\t\t\t\t\t\tif (handler) {\n\t\t\t\t\t\t\t\t\t\t\tthis.pending.delete(parsed.id);\n\t\t\t\t\t\t\t\t\t\t\tif (parsed.error) {\n\t\t\t\t\t\t\t\t\t\t\t\thandler.reject(new Error(`MCP error: ${parsed.error.message}`));\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\thandler.resolve(parsed.result);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t/* skip */\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (data.startsWith(\"http\")) {\n\t\t\t\t\t\t\t\t// This is the endpoint URL\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (line.startsWith(\"event: endpoint\")) {\n\t\t\t\t\t\t\t// Next data line will be the endpoint URL\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Wait for endpoint event\n\t\t\tconst endpointLine = await this.readSSEEvent(reader, decoder);\n\t\t\tif (endpointLine) {\n\t\t\t\tthis.sseEndpoint = endpointLine.startsWith(\"/\") ? new URL(endpointLine, url).href : endpointLine;\n\t\t\t} else {\n\t\t\t\tthis.sseEndpoint = url;\n\t\t\t}\n\n\t\t\t// Start background reader\n\t\t\tprocessChunk().catch(() => {});\n\t\t} else {\n\t\t\t// Streamable HTTP - POST to URL directly, server responds with JSON or upgrades to SSE\n\t\t\tthis.sseEndpoint = url;\n\t\t}\n\t}\n\n\tprivate async readSSEEvent(\n\t\treader: ReadableStreamDefaultReader<Uint8Array>,\n\t\tdecoder: { decode(b?: Uint8Array, o?: { stream?: boolean }): string },\n\t): Promise<string | null> {\n\t\tlet buffer = \"\";\n\t\tconst deadline = Date.now() + 10000;\n\n\t\twhile (Date.now() < deadline) {\n\t\t\tconst { value, done } = await reader.read();\n\t\t\tif (done) break;\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\tconst line = lines[i];\n\t\t\t\tif (line.startsWith(\"data: \") && lines[i - 1]?.startsWith(\"event: endpoint\")) {\n\t\t\t\t\treturn line.slice(6).trim();\n\t\t\t\t}\n\t\t\t\tif (line.startsWith(\"event: endpoint\") && lines[i + 1]?.startsWith(\"data: \")) {\n\t\t\t\t\treturn lines[i + 1].slice(6).trim();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate async send(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {\n\t\tconst id = ++this.requestId;\n\t\tconst body = JSON.stringify({ jsonrpc: \"2.0\", id, method, params });\n\n\t\tif (this.process?.stdin) {\n\t\t\treturn this.sendWithTimeout(id, body, signal, (_resolve, reject) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.process!.stdin!.write(`${body}\\n`);\n\t\t\t\t} catch (err) {\n\t\t\t\t\treject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (this.sseEndpoint) {\n\t\t\treturn this.sendWithTimeout(id, body, signal, async (resolve, reject) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst timeoutMs = this.timeoutMs > 0 ? this.timeoutMs : undefined;\n\t\t\t\t\tconst res = await fetch(this.sseEndpoint!, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\tsignal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,\n\t\t\t\t\t});\n\t\t\t\t\tconst text = await res.text();\n\t\t\t\t\tif (res.headers.get(\"content-type\")?.includes(\"text/event-stream\")) {\n\t\t\t\t\t\t// SSE upgrade - response comes via event stream\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst parsed = JSON.parse(text);\n\t\t\t\t\tif (parsed.error) {\n\t\t\t\t\t\treject(new Error(`MCP error: ${parsed.error.message}`));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresolve(parsed.result);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof Error && err.name === \"TimeoutError\") {\n\t\t\t\t\t\treject(new Error(`MCP request timed out after ${this.timeoutMs}ms`));\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tthrow new Error(\"MCP client not connected\");\n\t}\n\n\t/**\n\t * Wraps a request with timeout and AbortSignal support.\n\t * Registers the pending handler so that response lines resolve it,\n\t * and rejects if the timeout fires or the caller's signal aborts.\n\t */\n\tprivate sendWithTimeout(\n\t\tid: number,\n\t\t_body: string,\n\t\tsignal: AbortSignal | undefined,\n\t\twriter: (resolve: (v: unknown) => void, reject: (e: Error) => void) => void,\n\t): Promise<unknown> {\n\t\treturn new Promise<unknown>((outerResolve, outerReject) => {\n\t\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\t\tlet done = false;\n\n\t\t\tconst cleanup = () => {\n\t\t\t\tif (done) return;\n\t\t\t\tdone = true;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tthis.pending.delete(id);\n\t\t\t};\n\n\t\t\tconst resolve = (v: unknown) => {\n\t\t\t\tcleanup();\n\t\t\t\touterResolve(v);\n\t\t\t};\n\n\t\t\tconst reject = (e: Error) => {\n\t\t\t\tcleanup();\n\t\t\t\touterReject(e);\n\t\t\t};\n\n\t\t\t// Timeout (unless disabled by setting timeoutMs = 0)\n\t\t\tif (this.timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\treject(new Error(`MCP request timed out after ${this.timeoutMs}ms`));\n\t\t\t\t}, this.timeoutMs);\n\t\t\t}\n\n\t\t\t// Caller abort signal\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\treject(new DOMException(\"MCP request cancelled\", \"AbortError\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsignal.addEventListener(\n\t\t\t\t\t\"abort\",\n\t\t\t\t\t() => {\n\t\t\t\t\t\treject(new DOMException(\"MCP request cancelled\", \"AbortError\"));\n\t\t\t\t\t},\n\t\t\t\t\t{ once: true },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.pending.set(id, { resolve, reject });\n\n\t\t\twriter(resolve, reject);\n\t\t});\n\t}\n\n\tasync listTools(signal?: AbortSignal): Promise<{ tools: MCPToolDefinition[] }> {\n\t\treturn (await this.send(\"tools/list\", undefined, signal)) as { tools: MCPToolDefinition[] };\n\t}\n\n\tasync listResources(signal?: AbortSignal): Promise<{ resources: MCPResource[] }> {\n\t\treturn (await this.send(\"resources/list\", undefined, signal)) as { resources: MCPResource[] };\n\t}\n\n\tasync readResource(\n\t\turi: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<{ contents: Array<{ uri: string; mimeType?: string; text?: string }> }> {\n\t\treturn (await this.send(\"resources/read\", { uri }, signal)) as {\n\t\t\tcontents: Array<{ uri: string; mimeType?: string; text?: string }>;\n\t\t};\n\t}\n\n\tasync callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<MCPCallToolResult> {\n\t\treturn (await this.send(\"tools/call\", { name, arguments: args }, signal)) as MCPCallToolResult;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tconst proc = this.process;\n\t\tthis.process = null;\n\t\tthis.pending.clear();\n\t\tthis.sseEndpoint = null;\n\t\tif (proc && !proc.killed) {\n\t\t\tproc.kill();\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tif (proc.exitCode !== null) return resolve();\n\t\t\t\tproc.on(\"exit\", resolve);\n\t\t\t\tsetTimeout(resolve, 3000);\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/core/mcp/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"child_process\";\nimport { createInterface } from \"readline\";\nimport type { MCPCallToolResult, MCPResource, MCPServerConfig, MCPToolDefinition } from \"./types.ts\";\n\nexport type { MCPServerConfig };\n\n/** Default MCP tool call timeout: 2 minutes */\nconst DEFAULT_MCP_TIMEOUT_MS = 120_000;\n/** MCP connection handshake timeout: 30 seconds */\nconst MCP_CONNECT_TIMEOUT_MS = 30_000;\n\ntype PendingRequest = { resolve: (v: unknown) => void; reject: (e: Error) => void };\n\nfunction resolveTransport(config: MCPServerConfig): \"stdio\" | \"sse\" | \"http\" {\n\tif (config.transport) return config.transport;\n\tif (config.command) return \"stdio\";\n\tif (config.url) return \"sse\";\n\treturn \"stdio\";\n}\n\nexport type MCPDisconnectHandler = (reason: string) => void;\n\nexport class MCPClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate requestId = 0;\n\tprivate pending = new Map<number, PendingRequest>();\n\tprivate sseEndpoint: string | null = null;\n\tprivate timeoutMs: number;\n\tprivate _connected = false;\n\n\t/** Called when the connection is lost unexpectedly */\n\tonDisconnect: MCPDisconnectHandler | null = null;\n\n\tconstructor(timeoutMs?: number) {\n\t\tthis.timeoutMs = timeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;\n\t}\n\n\tget isConnected(): boolean {\n\t\treturn this._connected;\n\t}\n\n\tasync connect(config: MCPServerConfig): Promise<void> {\n\t\tif (config.timeoutMs !== undefined) {\n\t\t\tthis.timeoutMs = config.timeoutMs;\n\t\t}\n\n\t\tconst transport = resolveTransport(config);\n\n\t\t// Use a shorter timeout for the connection handshake\n\t\t// (server startup via npx, dependency downloads, etc.)\n\t\tconst savedTimeout = this.timeoutMs;\n\t\tthis.timeoutMs = Math.min(this.timeoutMs, MCP_CONNECT_TIMEOUT_MS);\n\t\ttry {\n\t\t\tif (transport === \"stdio\") {\n\t\t\t\tawait this.connectStdio(config);\n\t\t\t} else {\n\t\t\t\tawait this.connectHttp(config, transport);\n\t\t\t}\n\t\t\tawait this.send(\"initialize\", {\n\t\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\t\tcapabilities: {},\n\t\t\t\tclientInfo: { name: \"pi\", version: \"1.0.0\" },\n\t\t\t});\n\t\t\tthis._connected = true;\n\t\t} finally {\n\t\t\t// Restore the configured timeout for subsequent tool calls\n\t\t\tthis.timeoutMs = savedTimeout;\n\t\t}\n\t}\n\n\tprivate handleDisconnect(reason: string): void {\n\t\tif (!this._connected) return;\n\t\tthis._connected = false;\n\t\tthis.pending.clear();\n\t\tthis.sseEndpoint = null;\n\t\tthis.process = null;\n\n\t\ttry {\n\t\t\tthis.onDisconnect?.(reason);\n\t\t} catch {\n\t\t\t// ignore errors from disconnect handler\n\t\t}\n\t}\n\n\tprivate async connectStdio(config: MCPServerConfig): Promise<void> {\n\t\tconst { command, args = [], env } = config;\n\t\tif (!command) throw new Error(\"MCP stdio server requires 'command'\");\n\n\t\tconst childEnv = env ? { ...process.env, ...env } : process.env;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst proc = spawn(command, args, { env: childEnv, stdio: [\"pipe\", \"pipe\", \"pipe\"], shell: false });\n\t\t\tthis.process = proc;\n\n\t\t\tconst onError = (error: Error) => {\n\t\t\t\treject(new Error(`Failed to spawn MCP server \"${command}\": ${error.message}`));\n\t\t\t};\n\t\t\tproc.on(\"error\", onError);\n\t\t\tproc.once(\"spawn\", () => proc.removeListener(\"error\", onError));\n\n\t\t\tif (!proc.stdout) {\n\t\t\t\treject(new Error(`MCP server \"${command}\" has no stdout`));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rl = createInterface({ input: proc.stdout, crlfDelay: Infinity });\n\t\t\trl.on(\"line\", (line: string) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst res = JSON.parse(line);\n\t\t\t\t\tif (res.id !== undefined) {\n\t\t\t\t\t\tconst handler = this.pending.get(res.id);\n\t\t\t\t\t\tif (!handler) return;\n\t\t\t\t\t\tthis.pending.delete(res.id);\n\t\t\t\t\t\tif (res.error) {\n\t\t\t\t\t\t\thandler.reject(new Error(`MCP error: ${res.error.message}`));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thandler.resolve(res.result);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t/* non-JSON lines */\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tproc.on(\"exit\", (code) => {\n\t\t\t\tfor (const [, h] of this.pending) {\n\t\t\t\t\th.reject(new Error(`MCP server \"${command}\" exited with code ${code}`));\n\t\t\t\t}\n\t\t\t\tthis.pending.clear();\n\t\t\t\tthis.handleDisconnect(`process exited with code ${code}`);\n\t\t\t});\n\n\t\t\tresolve();\n\t\t});\n\t}\n\n\tprivate async connectHttp(config: MCPServerConfig, transport: \"sse\" | \"http\"): Promise<void> {\n\t\tconst { url, headers = {} } = config;\n\t\tif (!url) throw new Error(\"MCP SSE/HTTP server requires 'url'\");\n\n\t\tif (transport === \"sse\") {\n\t\t\tconst res = await fetch(`${url}/sse`, { headers: { ...headers, Accept: \"text/event-stream\" } });\n\t\t\tif (!res.ok) throw new Error(`SSE connection failed: ${res.status} ${res.statusText}`);\n\n\t\t\tconst reader = res.body!.getReader();\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet buffer = \"\";\n\n\t\t\tconst processChunk = async () => {\n\t\t\t\ttry {\n\t\t\t\t\twhile (true) {\n\t\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\t\tif (done) break;\n\t\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\t\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\t\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\t\tif (line.startsWith(\"data: \")) {\n\t\t\t\t\t\t\t\tconst data = line.slice(6);\n\t\t\t\t\t\t\t\tif (data.startsWith(\"{\") && this.sseEndpoint === null) {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\t\t\t\t\t\t\tif (parsed.result) {\n\t\t\t\t\t\t\t\t\t\t\tconst handler = this.pending.get(parsed.id);\n\t\t\t\t\t\t\t\t\t\t\tif (handler) {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.pending.delete(parsed.id);\n\t\t\t\t\t\t\t\t\t\t\t\tif (parsed.error) {\n\t\t\t\t\t\t\t\t\t\t\t\t\thandler.reject(new Error(`MCP error: ${parsed.error.message}`));\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\thandler.resolve(parsed.result);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t\t/* skip */\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (data.startsWith(\"http\")) {\n\t\t\t\t\t\t\t\t\t// This is the endpoint URL\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (line.startsWith(\"event: endpoint\")) {\n\t\t\t\t\t\t\t\t// Next data line will be the endpoint URL\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Stream error means connection lost\n\t\t\t\t\tthis.handleDisconnect(err instanceof Error ? err.message : String(err));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Stream ended normally\n\t\t\t\tthis.handleDisconnect(\"SSE stream ended\");\n\t\t\t};\n\n\t\t\t// Wait for endpoint event\n\t\t\tconst endpointLine = await this.readSSEEvent(reader, decoder);\n\t\t\tif (endpointLine) {\n\t\t\t\tthis.sseEndpoint = endpointLine.startsWith(\"/\") ? new URL(endpointLine, url).href : endpointLine;\n\t\t\t} else {\n\t\t\t\tthis.sseEndpoint = url;\n\t\t\t}\n\n\t\t\t// Start background reader\n\t\t\tprocessChunk();\n\t\t} else {\n\t\t\t// Streamable HTTP - POST to URL directly, server responds with JSON or upgrades to SSE\n\t\t\tthis.sseEndpoint = url;\n\t\t}\n\t}\n\n\tprivate async readSSEEvent(\n\t\treader: ReadableStreamDefaultReader<Uint8Array>,\n\t\tdecoder: { decode(b?: Uint8Array, o?: { stream?: boolean }): string },\n\t): Promise<string | null> {\n\t\tlet buffer = \"\";\n\t\tconst deadline = Date.now() + 10000;\n\n\t\twhile (Date.now() < deadline) {\n\t\t\tconst { value, done } = await reader.read();\n\t\t\tif (done) break;\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\n\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\tconst line = lines[i];\n\t\t\t\tif (line.startsWith(\"data: \") && lines[i - 1]?.startsWith(\"event: endpoint\")) {\n\t\t\t\t\treturn line.slice(6).trim();\n\t\t\t\t}\n\t\t\t\tif (line.startsWith(\"event: endpoint\") && lines[i + 1]?.startsWith(\"data: \")) {\n\t\t\t\t\treturn lines[i + 1].slice(6).trim();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate async send(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {\n\t\tconst id = ++this.requestId;\n\t\tconst body = JSON.stringify({ jsonrpc: \"2.0\", id, method, params });\n\n\t\tif (this.process?.stdin) {\n\t\t\treturn this.sendWithTimeout(id, body, signal, (_resolve, reject) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.process!.stdin!.write(`${body}\\n`);\n\t\t\t\t} catch (err) {\n\t\t\t\t\treject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (this.sseEndpoint) {\n\t\t\treturn this.sendWithTimeout(id, body, signal, async (resolve, reject) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst timeoutMs = this.timeoutMs > 0 ? this.timeoutMs : undefined;\n\t\t\t\t\tconst res = await fetch(this.sseEndpoint!, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\tsignal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,\n\t\t\t\t\t});\n\t\t\t\t\tconst text = await res.text();\n\t\t\t\t\tif (res.headers.get(\"content-type\")?.includes(\"text/event-stream\")) {\n\t\t\t\t\t\t// SSE upgrade - response comes via event stream\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst parsed = JSON.parse(text);\n\t\t\t\t\tif (parsed.error) {\n\t\t\t\t\t\treject(new Error(`MCP error: ${parsed.error.message}`));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresolve(parsed.result);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof Error && err.name === \"TimeoutError\") {\n\t\t\t\t\t\treject(new Error(`MCP request timed out after ${this.timeoutMs}ms`));\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tthrow new Error(\"MCP client not connected\");\n\t}\n\n\t/**\n\t * Wraps a request with timeout and AbortSignal support.\n\t * Registers the pending handler so that response lines resolve it,\n\t * and rejects if the timeout fires or the caller's signal aborts.\n\t */\n\tprivate sendWithTimeout(\n\t\tid: number,\n\t\t_body: string,\n\t\tsignal: AbortSignal | undefined,\n\t\twriter: (resolve: (v: unknown) => void, reject: (e: Error) => void) => void,\n\t): Promise<unknown> {\n\t\treturn new Promise<unknown>((outerResolve, outerReject) => {\n\t\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\t\tlet done = false;\n\n\t\t\tconst cleanup = () => {\n\t\t\t\tif (done) return;\n\t\t\t\tdone = true;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tthis.pending.delete(id);\n\t\t\t};\n\n\t\t\tconst resolve = (v: unknown) => {\n\t\t\t\tcleanup();\n\t\t\t\touterResolve(v);\n\t\t\t};\n\n\t\t\tconst reject = (e: Error) => {\n\t\t\t\tcleanup();\n\t\t\t\touterReject(e);\n\t\t\t};\n\n\t\t\t// Timeout (unless disabled by setting timeoutMs = 0)\n\t\t\tif (this.timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\treject(new Error(`MCP request timed out after ${this.timeoutMs}ms`));\n\t\t\t\t}, this.timeoutMs);\n\t\t\t}\n\n\t\t\t// Caller abort signal\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\treject(new DOMException(\"MCP request cancelled\", \"AbortError\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsignal.addEventListener(\n\t\t\t\t\t\"abort\",\n\t\t\t\t\t() => {\n\t\t\t\t\t\treject(new DOMException(\"MCP request cancelled\", \"AbortError\"));\n\t\t\t\t\t},\n\t\t\t\t\t{ once: true },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.pending.set(id, { resolve, reject });\n\n\t\t\twriter(resolve, reject);\n\t\t});\n\t}\n\n\tasync listTools(signal?: AbortSignal): Promise<{ tools: MCPToolDefinition[] }> {\n\t\treturn (await this.send(\"tools/list\", undefined, signal)) as { tools: MCPToolDefinition[] };\n\t}\n\n\tasync listResources(signal?: AbortSignal): Promise<{ resources: MCPResource[] }> {\n\t\treturn (await this.send(\"resources/list\", undefined, signal)) as { resources: MCPResource[] };\n\t}\n\n\tasync readResource(\n\t\turi: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<{ contents: Array<{ uri: string; mimeType?: string; text?: string }> }> {\n\t\treturn (await this.send(\"resources/read\", { uri }, signal)) as {\n\t\t\tcontents: Array<{ uri: string; mimeType?: string; text?: string }>;\n\t\t};\n\t}\n\n\tasync callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<MCPCallToolResult> {\n\t\treturn (await this.send(\"tools/call\", { name, arguments: args }, signal)) as MCPCallToolResult;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tthis._connected = false;\n\t\tthis.onDisconnect = null;\n\t\tconst proc = this.process;\n\t\tthis.process = null;\n\t\tthis.pending.clear();\n\t\tthis.sseEndpoint = null;\n\t\tif (proc && !proc.killed) {\n\t\t\tproc.kill();\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tif (proc.exitCode !== null) return resolve();\n\t\t\t\tproc.on(\"exit\", resolve);\n\t\t\t\tsetTimeout(resolve, 3000);\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
@@ -1,14 +1,34 @@
1
1
  import type { ToolDefinition } from "../extensions/types.ts";
2
2
  import { type MCPServerConfig } from "./client.ts";
3
+ import type { MCPConnectionStatus } from "./types.ts";
4
+ export type MCPStatusChangeHandler = (serverName: string, status: MCPConnectionStatus) => void;
3
5
  export declare class MCPManager {
4
6
  private clients;
7
+ private serverConfigs;
5
8
  private tools;
6
9
  private resourceUris;
10
+ private reconnectTimers;
11
+ private reconnectAttempts;
12
+ private _statuses;
13
+ /** Called when any server's connection status changes */
14
+ onStatusChange: MCPStatusChangeHandler | null;
15
+ getServerStatus(serverName: string): MCPConnectionStatus;
16
+ getAllStatuses(): Array<{
17
+ serverName: string;
18
+ status: MCPConnectionStatus;
19
+ toolCount: number;
20
+ }>;
21
+ private setStatus;
7
22
  start(serverConfigs: Record<string, MCPServerConfig>): Promise<void>;
8
- readResource(uri: string): Promise<string>;
23
+ private connectServer;
24
+ private buildToolDefs;
25
+ private scheduleReconnect;
26
+ /** Manually trigger reconnection for a specific server */
27
+ reconnect(serverName: string): Promise<void>;
9
28
  getToolDefinitions(): ToolDefinition[];
10
29
  getServerNames(): string[];
11
30
  getResourceUris(): string[];
31
+ readResource(uri: string): Promise<string>;
12
32
  stop(): Promise<void>;
13
33
  }
14
34
  //# sourceMappingURL=manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAqC9D,qBAAa,UAAU;IACtB,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,YAAY,CAAgB;IAE9B,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA0DzE;IAEK,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ/C;IAED,kBAAkB,IAAI,cAAc,EAAE,CAErC;IAED,cAAc,IAAI,MAAM,EAAE,CAEzB;IAED,eAAe,IAAI,MAAM,EAAE,CAE1B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAc1B;CACD","sourcesContent":["import type { AgentToolResult } from \"@openeryc/pi-agent-core\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { MCPClient, type MCPServerConfig } from \"./client.ts\";\nimport type { MCPToolDefinition } from \"./types.ts\";\n\nfunction toTypeBox(schema: MCPToolDefinition[\"inputSchema\"]): ReturnType<typeof Type.Object> {\n\tif (!schema.properties || Object.keys(schema.properties).length === 0) {\n\t\treturn Type.Object({});\n\t}\n\tconst required = new Set(schema.required ?? []);\n\tconst fields: Record<string, unknown> = {};\n\tfor (const [key, prop] of Object.entries(schema.properties)) {\n\t\tconst p = prop as { type?: string; description?: string; enum?: string[] };\n\t\tconst t = p.type ?? \"string\";\n\t\tconst desc = p.description ? { description: p.description } : {};\n\t\tlet f: unknown;\n\t\tswitch (t) {\n\t\t\tcase \"string\":\n\t\t\t\tf = p.enum ? Type.Unsafe<string>({ type: \"string\", enum: p.enum, ...desc }) : Type.String(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tf = Type.Number(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tf = Type.Boolean(desc);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tf = Type.Any(desc);\n\t\t}\n\t\tfields[key] = required.has(key) ? f : Type.Optional(f as never);\n\t}\n\treturn Type.Object(fields as Record<string, ReturnType<typeof Type.String>>);\n}\n\nfunction sanitizeMcpName(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\nexport class MCPManager {\n\tprivate clients = new Map<string, MCPClient>();\n\tprivate tools = new Map<string, ToolDefinition[]>();\n\tprivate resourceUris: string[] = [];\n\n\tasync start(serverConfigs: Record<string, MCPServerConfig>): Promise<void> {\n\t\tfor (const [serverName, config] of Object.entries(serverConfigs)) {\n\t\t\tif (config.enabled === false) continue;\n\t\t\ttry {\n\t\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\t\tawait client.connect(config);\n\t\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\t\tthis.clients.set(serverName, client);\n\n\t\t\t\tconst safeServerName = sanitizeMcpName(serverName);\n\t\t\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => ({\n\t\t\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\t\t\tdescription: tool.description\n\t\t\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\t\t\tpromptSnippet: tool.description\n\t\t\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\t\t\tparameters: toTypeBox(tool.inputSchema),\n\t\t\t\t\trenderShell: \"default\" as const,\n\t\t\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst result = await client.callTool(tool.name, params as Record<string, unknown>, signal);\n\t\t\t\t\t\t\tconst text = result.content\n\t\t\t\t\t\t\t\t.map((item) => (item.type === \"text\" && item.text ? item.text : `[Image: ${item.mimeType}]`))\n\t\t\t\t\t\t\t\t.join(\"\\n\");\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}));\n\n\t\t\t\tthis.tools.set(serverName, defs);\n\n\t\t\t\t// Discover resources\n\t\t\t\ttry {\n\t\t\t\t\tconst { resources } = await client.listResources();\n\t\t\t\t\tfor (const r of resources) {\n\t\t\t\t\t\tthis.resourceUris.push(`mcp://${serverName}${r.uri}`);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Server may not support resources\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Failed to connect to MCP server \"${serverName}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync readResource(uri: string): Promise<string> {\n\t\tconst serverName = this.clients.keys().next().value;\n\t\tif (!serverName) throw new Error(\"No MCP servers connected\");\n\t\tconst mcpUri = uri.startsWith(`mcp://${serverName}`) ? uri.slice(`mcp://${serverName}`.length) : uri;\n\t\tconst client = this.clients.get(serverName);\n\t\tif (!client) throw new Error(`MCP server \"${serverName}\" not connected`);\n\t\tconst result = await client.readResource(mcpUri);\n\t\treturn result.contents.map((c) => c.text ?? \"\").join(\"\\n\");\n\t}\n\n\tgetToolDefinitions(): ToolDefinition[] {\n\t\treturn Array.from(this.tools.values()).flat();\n\t}\n\n\tgetServerNames(): string[] {\n\t\treturn [...this.clients.keys()];\n\t}\n\n\tgetResourceUris(): string[] {\n\t\treturn this.resourceUris;\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tconst pending = [...this.clients.entries()].map(async ([name, client]) => {\n\t\t\ttry {\n\t\t\t\tawait client.disconnect();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Error disconnecting MCP server \"${name}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t\tthis.clients.clear();\n\t\tthis.tools.clear();\n\t\tthis.resourceUris = [];\n\t\tawait Promise.all(pending);\n\t}\n}\n"]}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAqB,MAAM,YAAY,CAAC;AAuCzE,MAAM,MAAM,sBAAsB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAE/F,qBAAa,UAAU;IACtB,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,aAAa,CAAsC;IAC3D,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,eAAe,CAAoD;IAC3E,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,SAAS,CAA0C;IAE3D,yDAAyD;IACzD,cAAc,EAAE,sBAAsB,GAAG,IAAI,CAAQ;IAErD,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB,CAEvD;IAED,cAAc,IAAI,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,mBAAmB,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAM9F;IAED,OAAO,CAAC,SAAS;IAWX,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CASzE;YAEa,aAAa;IAsC3B,OAAO,CAAC,aAAa;IA+CrB,OAAO,CAAC,iBAAiB;IA6CzB,0DAA0D;IACpD,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjD;IAGD,kBAAkB,IAAI,cAAc,EAAE,CAErC;IAED,cAAc,IAAI,MAAM,EAAE,CAEzB;IAED,eAAe,IAAI,MAAM,EAAE,CAE1B;IAEK,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ/C;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB1B;CACD","sourcesContent":["import type { AgentToolResult } from \"@openeryc/pi-agent-core\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { MCPClient, type MCPServerConfig } from \"./client.ts\";\nimport type { MCPConnectionStatus, MCPToolDefinition } from \"./types.ts\";\n\nconst DEFAULT_RECONNECT_MAX_ATTEMPTS = 5;\nconst DEFAULT_RECONNECT_INTERVAL_MS = 5000;\n\nfunction toTypeBox(schema: MCPToolDefinition[\"inputSchema\"]): ReturnType<typeof Type.Object> {\n\tif (!schema.properties || Object.keys(schema.properties).length === 0) {\n\t\treturn Type.Object({});\n\t}\n\tconst required = new Set(schema.required ?? []);\n\tconst fields: Record<string, unknown> = {};\n\tfor (const [key, prop] of Object.entries(schema.properties)) {\n\t\tconst p = prop as { type?: string; description?: string; enum?: string[] };\n\t\tconst t = p.type ?? \"string\";\n\t\tconst desc = p.description ? { description: p.description } : {};\n\t\tlet f: unknown;\n\t\tswitch (t) {\n\t\t\tcase \"string\":\n\t\t\t\tf = p.enum ? Type.Unsafe<string>({ type: \"string\", enum: p.enum, ...desc }) : Type.String(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tf = Type.Number(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tf = Type.Boolean(desc);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tf = Type.Any(desc);\n\t\t}\n\t\tfields[key] = required.has(key) ? f : Type.Optional(f as never);\n\t}\n\treturn Type.Object(fields as Record<string, ReturnType<typeof Type.String>>);\n}\n\nfunction sanitizeMcpName(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\nexport type MCPStatusChangeHandler = (serverName: string, status: MCPConnectionStatus) => void;\n\nexport class MCPManager {\n\tprivate clients = new Map<string, MCPClient>();\n\tprivate serverConfigs = new Map<string, MCPServerConfig>();\n\tprivate tools = new Map<string, ToolDefinition[]>();\n\tprivate resourceUris: string[] = [];\n\tprivate reconnectTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\tprivate reconnectAttempts = new Map<string, number>();\n\tprivate _statuses = new Map<string, MCPConnectionStatus>();\n\n\t/** Called when any server's connection status changes */\n\tonStatusChange: MCPStatusChangeHandler | null = null;\n\n\tgetServerStatus(serverName: string): MCPConnectionStatus {\n\t\treturn this._statuses.get(serverName) ?? \"disconnected\";\n\t}\n\n\tgetAllStatuses(): Array<{ serverName: string; status: MCPConnectionStatus; toolCount: number }> {\n\t\treturn [...this._statuses.entries()].map(([serverName, status]) => ({\n\t\t\tserverName,\n\t\t\tstatus,\n\t\t\ttoolCount: this.tools.get(serverName)?.length ?? 0,\n\t\t}));\n\t}\n\n\tprivate setStatus(serverName: string, status: MCPConnectionStatus): void {\n\t\tconst prev = this._statuses.get(serverName);\n\t\tif (prev === status) return;\n\t\tthis._statuses.set(serverName, status);\n\t\ttry {\n\t\t\tthis.onStatusChange?.(serverName, status);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\tasync start(serverConfigs: Record<string, MCPServerConfig>): Promise<void> {\n\t\tfor (const [serverName, config] of Object.entries(serverConfigs)) {\n\t\t\tif (config.enabled === false) {\n\t\t\t\tthis.setStatus(serverName, \"disabled\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.serverConfigs.set(serverName, config);\n\t\t\tawait this.connectServer(serverName, config);\n\t\t}\n\t}\n\n\tprivate async connectServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\t\ttry {\n\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\tclient.onDisconnect = (_reason) => {\n\t\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\t\tthis.tools.delete(serverName);\n\t\t\t\tthis.scheduleReconnect(serverName);\n\t\t\t};\n\n\t\t\tawait client.connect(config);\n\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\tthis.clients.set(serverName, client);\n\n\t\t\tthis.buildToolDefs(serverName, mcpTools);\n\n\t\t\t// Discover resources\n\t\t\ttry {\n\t\t\t\tconst { resources } = await client.listResources();\n\t\t\t\tfor (const r of resources) {\n\t\t\t\t\tthis.resourceUris.push(`mcp://${serverName}${r.uri}`);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Server may not support resources\n\t\t\t}\n\n\t\t\tthis.setStatus(serverName, \"connected\");\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t} catch (error) {\n\t\t\tthis.clients.delete(serverName);\n\t\t\tconsole.warn(\n\t\t\t\t`Failed to connect to MCP server \"${serverName}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\tthis.scheduleReconnect(serverName);\n\t\t}\n\t}\n\n\tprivate buildToolDefs(serverName: string, mcpTools: MCPToolDefinition[]): void {\n\t\tconst safeServerName = sanitizeMcpName(serverName);\n\t\tconst client = this.clients.get(serverName)!;\n\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => ({\n\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\tdescription: tool.description\n\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\tpromptSnippet: tool.description\n\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\tparameters: toTypeBox(tool.inputSchema),\n\t\t\trenderShell: \"default\" as const,\n\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\tif (!client.isConnected) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\t\ttext: `MCP server \"${serverName}\" is disconnected. Use /mcp to check status.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: { error: \"disconnected\" },\n\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await client.callTool(tool.name, params as Record<string, unknown>, signal);\n\t\t\t\t\tconst text = result.content\n\t\t\t\t\t\t.map((item) => (item.type === \"text\" && item.text ? item.text : `[Image: ${item.mimeType}]`))\n\t\t\t\t\t\t.join(\"\\n\");\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t}\n\t\t\t},\n\t\t}));\n\t\tthis.tools.set(serverName, defs);\n\t}\n\n\tprivate scheduleReconnect(serverName: string): void {\n\t\t// Cancel any existing reconnect timer for this server\n\t\tconst existing = this.reconnectTimers.get(serverName);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) return;\n\n\t\tconst reconnectCfg = config.reconnect ?? {};\n\t\tif (reconnectCfg.enabled === false) return;\n\n\t\tconst maxAttempts = reconnectCfg.maxAttempts ?? DEFAULT_RECONNECT_MAX_ATTEMPTS;\n\t\tconst intervalMs = reconnectCfg.intervalMs ?? DEFAULT_RECONNECT_INTERVAL_MS;\n\t\tconst attempt = (this.reconnectAttempts.get(serverName) ?? 0) + 1;\n\n\t\tif (attempt > maxAttempts) {\n\t\t\tconsole.warn(`MCP server \"${serverName}\": max reconnection attempts (${maxAttempts}) reached, giving up.`);\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.set(serverName, attempt);\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\n\t\tconst timer = setTimeout(async () => {\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t\tconst cfg = this.serverConfigs.get(serverName);\n\t\t\tif (!cfg) return;\n\n\t\t\t// Remove old client if any\n\t\t\tconst oldClient = this.clients.get(serverName);\n\t\t\tif (oldClient) {\n\t\t\t\toldClient.onDisconnect = null;\n\t\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t}\n\n\t\t\tawait this.connectServer(serverName, cfg);\n\t\t}, intervalMs * Math.min(attempt, 5)); // exponential backoff, cap at 5x\n\n\t\tthis.reconnectTimers.set(serverName, timer);\n\t}\n\n\t/** Manually trigger reconnection for a specific server */\n\tasync reconnect(serverName: string): Promise<void> {\n\t\tconst existingTimer = this.reconnectTimers.get(serverName);\n\t\tif (existingTimer) {\n\t\t\tclearTimeout(existingTimer);\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t}\n\n\t\tconst oldClient = this.clients.get(serverName);\n\t\tif (oldClient) {\n\t\t\toldClient.onDisconnect = null;\n\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\tthis.clients.delete(serverName);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) {\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.delete(serverName);\n\t\tawait this.connectServer(serverName, config);\n\t}\n\n\t// Rebuild tool definitions for a server (used after reconnect to pick up any changes)\n\tgetToolDefinitions(): ToolDefinition[] {\n\t\treturn Array.from(this.tools.values()).flat();\n\t}\n\n\tgetServerNames(): string[] {\n\t\treturn [...this.clients.keys()];\n\t}\n\n\tgetResourceUris(): string[] {\n\t\treturn this.resourceUris;\n\t}\n\n\tasync readResource(uri: string): Promise<string> {\n\t\tconst serverName = this.clients.keys().next().value;\n\t\tif (!serverName) throw new Error(\"No MCP servers connected\");\n\t\tconst mcpUri = uri.startsWith(`mcp://${serverName}`) ? uri.slice(`mcp://${serverName}`.length) : uri;\n\t\tconst client = this.clients.get(serverName);\n\t\tif (!client) throw new Error(`MCP server \"${serverName}\" not connected`);\n\t\tconst result = await client.readResource(mcpUri);\n\t\treturn result.contents.map((c) => c.text ?? \"\").join(\"\\n\");\n\t}\n\n\tasync stop(): Promise<void> {\n\t\t// Cancel all reconnect timers\n\t\tfor (const [, timer] of this.reconnectTimers) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.reconnectTimers.clear();\n\t\tthis.reconnectAttempts.clear();\n\n\t\tconst pending = [...this.clients.entries()].map(async ([name, client]) => {\n\t\t\ttry {\n\t\t\t\tawait client.disconnect();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Error disconnecting MCP server \"${name}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t\tthis.clients.clear();\n\t\tthis.tools.clear();\n\t\tthis.resourceUris = [];\n\t\tthis._statuses.clear();\n\t\tawait Promise.all(pending);\n\t}\n}\n"]}