@openeryc/pi-coding-agent 0.75.54 → 0.75.55

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 (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +4 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/agent-session.d.ts.map +1 -1
  6. package/dist/core/agent-session.js +8 -26
  7. package/dist/core/agent-session.js.map +1 -1
  8. package/dist/core/mcp/client.d.ts +16 -7
  9. package/dist/core/mcp/client.d.ts.map +1 -1
  10. package/dist/core/mcp/client.js +135 -107
  11. package/dist/core/mcp/client.js.map +1 -1
  12. package/dist/core/mcp/manager.d.ts +8 -1
  13. package/dist/core/mcp/manager.d.ts.map +1 -1
  14. package/dist/core/mcp/manager.js +56 -33
  15. package/dist/core/mcp/manager.js.map +1 -1
  16. package/dist/core/mcp/types.d.ts +0 -13
  17. package/dist/core/mcp/types.d.ts.map +1 -1
  18. package/dist/core/mcp/types.js.map +1 -1
  19. package/dist/core/package-manager.d.ts.map +1 -1
  20. package/dist/core/package-manager.js +3 -1
  21. package/dist/core/package-manager.js.map +1 -1
  22. package/dist/core/session-manager.d.ts.map +1 -1
  23. package/dist/core/session-manager.js +1 -1
  24. package/dist/core/session-manager.js.map +1 -1
  25. package/dist/modes/interactive/interactive-mode.d.ts +18 -0
  26. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  27. package/dist/modes/interactive/interactive-mode.js +359 -426
  28. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  29. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  30. package/dist/modes/rpc/rpc-client.js +5 -1
  31. package/dist/modes/rpc/rpc-client.js.map +1 -1
  32. package/dist/utils/sleep.d.ts.map +1 -1
  33. package/dist/utils/sleep.js +7 -3
  34. package/dist/utils/sleep.js.map +1 -1
  35. package/dist/utils/tools-manager.d.ts.map +1 -1
  36. package/dist/utils/tools-manager.js.map +1 -1
  37. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  38. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  39. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  40. package/examples/extensions/sandbox/package-lock.json +2 -2
  41. package/examples/extensions/sandbox/package.json +1 -1
  42. package/examples/extensions/with-deps/package-lock.json +2 -2
  43. package/examples/extensions/with-deps/package.json +1 -1
  44. package/npm-shrinkwrap.json +12 -12
  45. package/package.json +4 -4
@@ -31,9 +31,16 @@ export declare class MCPManager {
31
31
  /** Manually trigger reconnection for a specific server */
32
32
  reconnect(serverName: string): Promise<void>;
33
33
  getToolDefinitions(): ToolDefinition[];
34
+ /** Get all MCP tool descriptions grouped by server (for system prompt). */
35
+ getMcpToolDescriptions(): Array<{
36
+ serverName: string;
37
+ tools: Array<{
38
+ name: string;
39
+ description: string;
40
+ }>;
41
+ }>;
34
42
  getServerNames(): string[];
35
43
  getResourceUris(): string[];
36
- readResource(uri: string): Promise<string>;
37
44
  stop(): Promise<void>;
38
45
  }
39
46
  //# 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;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;IAC3D,OAAO,CAAC,cAAc,CAA0C;IAEhE,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,CAezE;IAED,qEAAqE;IAC/D,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAM5E;IAED,qDAAqD;IAC/C,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBlD;YAEa,aAAa;IAsC3B,OAAO,CAAC,aAAa;IAkFrB,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\tprivate toolTimeoutMap = new Map<string, Map<string, number>>();\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\tconst pending: Array<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\tif (config.toolTimeouts) {\n\t\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t\t}\n\t\t\tpending.push(this.connectServer(serverName, config));\n\t\t}\n\t\t// Connect all servers in parallel for faster startup\n\t\tawait Promise.allSettled(pending);\n\t}\n\n\t/** Start a single server (used by reloadMcp for targeted toggle). */\n\tasync startServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.serverConfigs.set(serverName, config);\n\t\tif (config.toolTimeouts) {\n\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t}\n\t\tawait this.connectServer(serverName, config);\n\t}\n\n\t/** Stop a single server without affecting others. */\n\tasync stopServer(serverName: string): Promise<void> {\n\t\t// Cancel reconnect timer for this server\n\t\tconst timer = this.reconnectTimers.get(serverName);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t}\n\t\tthis.reconnectAttempts.delete(serverName);\n\t\tthis.serverConfigs.delete(serverName);\n\t\tthis.toolTimeoutMap.delete(serverName);\n\t\tthis.tools.delete(serverName);\n\n\t\tconst client = this.clients.get(serverName);\n\t\tif (client) {\n\t\t\tclient.onDisconnect = null;\n\t\t\tthis.clients.delete(serverName);\n\t\t\tawait client.disconnect().catch(() => {});\n\t\t}\n\t\tthis.setStatus(serverName, \"disabled\");\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 perToolTimeout = this.toolTimeoutMap.get(serverName);\n\n\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => {\n\t\t\t// Resolve effective timeout: per-tool > server default > client default (120s)\n\t\t\tconst toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);\n\n\t\t\t// Add optional _timeoutMs parameter so the AI can override timeout per-call\n\t\t\tconst baseParams = toTypeBox(tool.inputSchema);\n\t\t\t// Merge _timeoutMs into the existing schema (preserving required fields)\n\t\t\tconst existingProps = (baseParams as any).properties ?? {};\n\t\t\tconst existingRequired = (baseParams as any).required ?? [];\n\t\t\tconst parameters = Type.Object(\n\t\t\t\t{\n\t\t\t\t\t...existingProps,\n\t\t\t\t\t_timeoutMs: Type.Optional(\n\t\t\t\t\t\tType.Number({\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\"Optional timeout override in milliseconds for this specific call (e.g. 300000 for 5 min, 5000 for 5s). Overrides the server default timeout.\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\t{ required: existingRequired },\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\t\tdescription: tool.description\n\t\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\t\tpromptSnippet: tool.description\n\t\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\t\tparameters,\n\t\t\t\trenderShell: \"default\" as const,\n\t\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\t\tif (!client.isConnected) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\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\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { error: \"disconnected\" },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Extract _timeoutMs from params (AI-specified timeout override)\n\t\t\t\t\t\tconst rawParams = params as Record<string, unknown>;\n\t\t\t\t\t\tconst callTimeoutMs =\n\t\t\t\t\t\t\ttypeof rawParams._timeoutMs === \"number\" ? rawParams._timeoutMs : toolSpecificTimeoutMs;\n\t\t\t\t\t\tconst { _timeoutMs: _ignored, ...mcpParams } = rawParams;\n\n\t\t\t\t\t\tconst effectiveSignal =\n\t\t\t\t\t\t\tcallTimeoutMs !== undefined\n\t\t\t\t\t\t\t\t? AbortSignal.any([AbortSignal.timeout(callTimeoutMs), ...(signal ? [signal] : [])])\n\t\t\t\t\t\t\t\t: signal;\n\t\t\t\t\t\tconst result = await client.callTool(tool.name, mcpParams, effectiveSignal);\n\t\t\t\t\t\tconst text = result.content\n\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.join(\"\\n\");\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\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"]}
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;IAC3D,OAAO,CAAC,cAAc,CAA0C;IAEhE,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,CAezE;IAED,qEAAqE;IAC/D,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAM5E;IAED,qDAAqD;IAC/C,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBlD;YAEa,aAAa;IA0D3B,OAAO,CAAC,aAAa;IAgFrB,OAAO,CAAC,iBAAiB;IA+CzB,0DAA0D;IACpD,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjD;IAED,kBAAkB,IAAI,cAAc,EAAE,CAErC;IAED,2EAA2E;IAC3E,sBAAsB,IAAI,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAU3G;IAED,cAAc,IAAI,MAAM,EAAE,CAEzB;IAED,eAAe,IAAI,MAAM,EAAE,CAE1B;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\tprivate toolTimeoutMap = new Map<string, Map<string, number>>();\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\tconst pending: Array<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\tif (config.toolTimeouts) {\n\t\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t\t}\n\t\t\tpending.push(this.connectServer(serverName, config));\n\t\t}\n\t\t// Connect all servers in parallel for faster startup\n\t\tawait Promise.allSettled(pending);\n\t}\n\n\t/** Start a single server (used by reloadMcp for targeted toggle). */\n\tasync startServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.serverConfigs.set(serverName, config);\n\t\tif (config.toolTimeouts) {\n\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t}\n\t\tawait this.connectServer(serverName, config);\n\t}\n\n\t/** Stop a single server without affecting others. */\n\tasync stopServer(serverName: string): Promise<void> {\n\t\t// Cancel reconnect timer for this server\n\t\tconst timer = this.reconnectTimers.get(serverName);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t}\n\t\tthis.reconnectAttempts.delete(serverName);\n\t\tthis.serverConfigs.delete(serverName);\n\t\tthis.toolTimeoutMap.delete(serverName);\n\t\tthis.tools.delete(serverName);\n\t\t// Also clean up resource URIs for this server\n\t\tthis.resourceUris = this.resourceUris.filter((u) => !u.startsWith(`mcp://${serverName}`));\n\n\t\tconst client = this.clients.get(serverName);\n\t\tif (client) {\n\t\t\tclient.onDisconnect = null;\n\t\t\tthis.clients.delete(serverName);\n\t\t\tawait client.disconnect().catch(() => {});\n\t\t}\n\t\tthis.setStatus(serverName, \"disabled\");\n\t}\n\n\tprivate async connectServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\t\tlet disconnected = false;\n\t\tconst onDisconnect = () => {\n\t\t\tif (disconnected) return;\n\t\t\tdisconnected = true;\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\tthis.tools.delete(serverName);\n\t\t\tthis.scheduleReconnect(serverName);\n\t\t};\n\n\t\ttry {\n\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\tclient.onDisconnect = () => onDisconnect();\n\n\t\t\tawait client.connect(config);\n\t\t\t// If disconnect happened during connect(), bail out\n\t\t\tif (disconnected) {\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\tif (disconnected) {\n\t\t\t\tclient.disconnect().catch(() => {});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.clients.set(serverName, client);\n\n\t\t\tthis.buildToolDefs(serverName, mcpTools);\n\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\tif (disconnected) {\n\t\t\t\t// Was disconnected during tool/resource discovery\n\t\t\t\tclient.disconnect().catch(() => {});\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t\tthis.tools.delete(serverName);\n\t\t\t\treturn;\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\tonDisconnect();\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}\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 perToolTimeout = this.toolTimeoutMap.get(serverName);\n\n\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => {\n\t\t\t// Resolve effective timeout: per-tool > server default > client default (120s)\n\t\t\tconst toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);\n\n\t\t\t// Add optional _timeoutMs parameter so the AI can override timeout per-call.\n\t\t\t// We must produce a flat properties schema (not allOf) because LLM providers\n\t\t\t// access tool.parameters.properties directly.\n\t\t\tconst baseParams = toTypeBox(tool.inputSchema);\n\t\t\tconst baseSchema = baseParams as unknown as Record<string, unknown>;\n\t\t\tconst mergedProps = {\n\t\t\t\t...((baseSchema.properties ?? {}) as Record<string, unknown>),\n\t\t\t\t_timeoutMs: {\n\t\t\t\t\ttype: \"number\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Optional timeout override in milliseconds for this specific call (e.g. 300000 for 5 min, 5000 for 5s). Overrides the server default timeout.\",\n\t\t\t\t},\n\t\t\t};\n\t\t\tconst mergedRequired = [...((baseSchema.required ?? []) as string[])];\n\t\t\tconst parameters = Type.Object(mergedProps, { required: mergedRequired });\n\n\t\t\treturn {\n\t\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\t\tdescription: tool.description\n\t\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\t\tpromptSnippet: tool.description\n\t\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\t\tparameters,\n\t\t\t\trenderShell: \"default\" as const,\n\t\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\t\tif (!client.isConnected) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\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\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { error: \"disconnected\" },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Extract _timeoutMs from params (AI-specified timeout override)\n\t\t\t\t\t\tconst rawParams = params as Record<string, unknown>;\n\t\t\t\t\t\tconst callTimeoutMs =\n\t\t\t\t\t\t\ttypeof rawParams._timeoutMs === \"number\" ? rawParams._timeoutMs : toolSpecificTimeoutMs;\n\t\t\t\t\t\tconst { _timeoutMs: _ignored, ...mcpParams } = rawParams;\n\n\t\t\t\t\t\tconst effectiveSignal =\n\t\t\t\t\t\t\tcallTimeoutMs !== undefined\n\t\t\t\t\t\t\t\t? AbortSignal.any([AbortSignal.timeout(callTimeoutMs), ...(signal ? [signal] : [])])\n\t\t\t\t\t\t\t\t: signal;\n\t\t\t\t\t\tconst result = await client.callTool(tool.name, mcpParams, effectiveSignal);\n\t\t\t\t\t\tconst text = result.content\n\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.join(\"\\n\");\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\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(() => {\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\toldClient.disconnect().catch(() => {});\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t}\n\n\t\t\tthis.connectServer(serverName, cfg).catch((err) =>\n\t\t\t\tconsole.warn(`MCP reconnect failed for \"${serverName}\":`, err),\n\t\t\t);\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\tgetToolDefinitions(): ToolDefinition[] {\n\t\treturn Array.from(this.tools.values()).flat();\n\t}\n\n\t/** Get all MCP tool descriptions grouped by server (for system prompt). */\n\tgetMcpToolDescriptions(): Array<{ serverName: string; tools: Array<{ name: string; description: string }> }> {\n\t\tconst result: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }> = [];\n\t\tfor (const [serverName, defs] of this.tools) {\n\t\t\tconst tools = defs.map((d) => ({\n\t\t\t\tname: d.name,\n\t\t\t\tdescription: d.description ?? `Tool from MCP server \"${serverName}\"`,\n\t\t\t}));\n\t\t\tresult.push({ serverName, tools });\n\t\t}\n\t\treturn result;\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\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"]}
@@ -103,6 +103,8 @@ export class MCPManager {
103
103
  this.serverConfigs.delete(serverName);
104
104
  this.toolTimeoutMap.delete(serverName);
105
105
  this.tools.delete(serverName);
106
+ // Also clean up resource URIs for this server
107
+ this.resourceUris = this.resourceUris.filter((u) => !u.startsWith(`mcp://${serverName}`));
106
108
  const client = this.clients.get(serverName);
107
109
  if (client) {
108
110
  client.onDisconnect = null;
@@ -113,18 +115,31 @@ export class MCPManager {
113
115
  }
114
116
  async connectServer(serverName, config) {
115
117
  this.setStatus(serverName, "reconnecting");
118
+ let disconnected = false;
119
+ const onDisconnect = () => {
120
+ if (disconnected)
121
+ return;
122
+ disconnected = true;
123
+ this.setStatus(serverName, "disconnected");
124
+ this.tools.delete(serverName);
125
+ this.scheduleReconnect(serverName);
126
+ };
116
127
  try {
117
128
  const client = new MCPClient(config.timeoutMs);
118
- client.onDisconnect = (_reason) => {
119
- this.setStatus(serverName, "disconnected");
120
- this.tools.delete(serverName);
121
- this.scheduleReconnect(serverName);
122
- };
129
+ client.onDisconnect = () => onDisconnect();
123
130
  await client.connect(config);
131
+ // If disconnect happened during connect(), bail out
132
+ if (disconnected) {
133
+ this.clients.delete(serverName);
134
+ return;
135
+ }
124
136
  const { tools: mcpTools } = await client.listTools();
137
+ if (disconnected) {
138
+ client.disconnect().catch(() => { });
139
+ return;
140
+ }
125
141
  this.clients.set(serverName, client);
126
142
  this.buildToolDefs(serverName, mcpTools);
127
- // Discover resources
128
143
  try {
129
144
  const { resources } = await client.listResources();
130
145
  for (const r of resources) {
@@ -134,14 +149,19 @@ export class MCPManager {
134
149
  catch {
135
150
  // Server may not support resources
136
151
  }
152
+ if (disconnected) {
153
+ // Was disconnected during tool/resource discovery
154
+ client.disconnect().catch(() => { });
155
+ this.clients.delete(serverName);
156
+ this.tools.delete(serverName);
157
+ return;
158
+ }
137
159
  this.setStatus(serverName, "connected");
138
160
  this.reconnectAttempts.delete(serverName);
139
161
  }
140
162
  catch (error) {
141
- this.clients.delete(serverName);
163
+ onDisconnect();
142
164
  console.warn(`Failed to connect to MCP server "${serverName}": ${error instanceof Error ? error.message : String(error)}`);
143
- this.setStatus(serverName, "disconnected");
144
- this.scheduleReconnect(serverName);
145
165
  }
146
166
  }
147
167
  buildToolDefs(serverName, mcpTools) {
@@ -151,17 +171,20 @@ export class MCPManager {
151
171
  const defs = mcpTools.map((tool) => {
152
172
  // Resolve effective timeout: per-tool > server default > client default (120s)
153
173
  const toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);
154
- // Add optional _timeoutMs parameter so the AI can override timeout per-call
174
+ // Add optional _timeoutMs parameter so the AI can override timeout per-call.
175
+ // We must produce a flat properties schema (not allOf) because LLM providers
176
+ // access tool.parameters.properties directly.
155
177
  const baseParams = toTypeBox(tool.inputSchema);
156
- // Merge _timeoutMs into the existing schema (preserving required fields)
157
- const existingProps = baseParams.properties ?? {};
158
- const existingRequired = baseParams.required ?? [];
159
- const parameters = Type.Object({
160
- ...existingProps,
161
- _timeoutMs: Type.Optional(Type.Number({
178
+ const baseSchema = baseParams;
179
+ const mergedProps = {
180
+ ...(baseSchema.properties ?? {}),
181
+ _timeoutMs: {
182
+ type: "number",
162
183
  description: "Optional timeout override in milliseconds for this specific call (e.g. 300000 for 5 min, 5000 for 5s). Overrides the server default timeout.",
163
- })),
164
- }, { required: existingRequired });
184
+ },
185
+ };
186
+ const mergedRequired = [...(baseSchema.required ?? [])];
187
+ const parameters = Type.Object(mergedProps, { required: mergedRequired });
165
188
  return {
166
189
  name: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,
167
190
  label: `mcp.${serverName}.${tool.name}`,
@@ -236,7 +259,7 @@ export class MCPManager {
236
259
  }
237
260
  this.reconnectAttempts.set(serverName, attempt);
238
261
  this.setStatus(serverName, "reconnecting");
239
- const timer = setTimeout(async () => {
262
+ const timer = setTimeout(() => {
240
263
  this.reconnectTimers.delete(serverName);
241
264
  const cfg = this.serverConfigs.get(serverName);
242
265
  if (!cfg)
@@ -245,10 +268,10 @@ export class MCPManager {
245
268
  const oldClient = this.clients.get(serverName);
246
269
  if (oldClient) {
247
270
  oldClient.onDisconnect = null;
248
- await oldClient.disconnect().catch(() => { });
271
+ oldClient.disconnect().catch(() => { });
249
272
  this.clients.delete(serverName);
250
273
  }
251
- await this.connectServer(serverName, cfg);
274
+ this.connectServer(serverName, cfg).catch((err) => console.warn(`MCP reconnect failed for "${serverName}":`, err));
252
275
  }, intervalMs * Math.min(attempt, 5)); // exponential backoff, cap at 5x
253
276
  this.reconnectTimers.set(serverName, timer);
254
277
  }
@@ -273,27 +296,27 @@ export class MCPManager {
273
296
  this.reconnectAttempts.delete(serverName);
274
297
  await this.connectServer(serverName, config);
275
298
  }
276
- // Rebuild tool definitions for a server (used after reconnect to pick up any changes)
277
299
  getToolDefinitions() {
278
300
  return Array.from(this.tools.values()).flat();
279
301
  }
302
+ /** Get all MCP tool descriptions grouped by server (for system prompt). */
303
+ getMcpToolDescriptions() {
304
+ const result = [];
305
+ for (const [serverName, defs] of this.tools) {
306
+ const tools = defs.map((d) => ({
307
+ name: d.name,
308
+ description: d.description ?? `Tool from MCP server "${serverName}"`,
309
+ }));
310
+ result.push({ serverName, tools });
311
+ }
312
+ return result;
313
+ }
280
314
  getServerNames() {
281
315
  return [...this.clients.keys()];
282
316
  }
283
317
  getResourceUris() {
284
318
  return this.resourceUris;
285
319
  }
286
- async readResource(uri) {
287
- const serverName = this.clients.keys().next().value;
288
- if (!serverName)
289
- throw new Error("No MCP servers connected");
290
- const mcpUri = uri.startsWith(`mcp://${serverName}`) ? uri.slice(`mcp://${serverName}`.length) : uri;
291
- const client = this.clients.get(serverName);
292
- if (!client)
293
- throw new Error(`MCP server "${serverName}" not connected`);
294
- const result = await client.readResource(mcpUri);
295
- return result.contents.map((c) => c.text ?? "").join("\n");
296
- }
297
320
  async stop() {
298
321
  // Cancel all reconnect timers
299
322
  for (const [, timer] of this.reconnectTimers) {
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/core/mcp/manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,OAAO,EAAE,SAAS,EAAwB,MAAM,aAAa,CAAC;AAG9D,MAAM,8BAA8B,GAAG,CAAC,CAAC;AACzC,MAAM,6BAA6B,GAAG,IAAI,CAAC;AAE3C,SAAS,SAAS,CAAC,MAAwC,EAAkC;IAC5F,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvE,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7D,MAAM,CAAC,GAAG,IAAgE,CAAC;QAC3E,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,QAAQ,CAAC;QAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,IAAI,CAAU,CAAC;QACf,QAAQ,CAAC,EAAE,CAAC;YACX,KAAK,QAAQ;gBACZ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAChG,MAAM;YACP,KAAK,QAAQ,CAAC;YACd,KAAK,SAAS;gBACb,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACtB,MAAM;YACP,KAAK,SAAS;gBACb,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBACvB,MAAM;YACP;gBACC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAU,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,MAAwD,CAAC,CAAC;AAAA,CAC7E;AAED,SAAS,eAAe,CAAC,IAAY,EAAU;IAC9C,OAAO,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;AAAA,CAC5C;AAID,MAAM,OAAO,UAAU;IACd,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAC;IACvC,aAAa,GAAG,IAAI,GAAG,EAA2B,CAAC;IACnD,KAAK,GAAG,IAAI,GAAG,EAA4B,CAAC;IAC5C,YAAY,GAAa,EAAE,CAAC;IAC5B,eAAe,GAAG,IAAI,GAAG,EAAyC,CAAC;IACnE,iBAAiB,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC9C,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAC;IACnD,cAAc,GAAG,IAAI,GAAG,EAA+B,CAAC;IAEhE,yDAAyD;IACzD,cAAc,GAAkC,IAAI,CAAC;IAErD,eAAe,CAAC,UAAkB,EAAuB;QACxD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,cAAc,CAAC;IAAA,CACxD;IAED,cAAc,GAAkF;QAC/F,OAAO,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;YACnE,UAAU;YACV,MAAM;YACN,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;SAClD,CAAC,CAAC,CAAC;IAAA,CACJ;IAEO,SAAS,CAAC,UAAkB,EAAE,MAA2B,EAAQ;QACxE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO;QAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC;YACJ,IAAI,CAAC,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACR,SAAS;QACV,CAAC;IAAA,CACD;IAED,KAAK,CAAC,KAAK,CAAC,aAA8C,EAAiB;QAC1E,MAAM,OAAO,GAAyB,EAAE,CAAC;QACzC,KAAK,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;YAClE,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;gBAC9B,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;gBACvC,SAAS;YACV,CAAC;YACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAC3C,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;gBACzB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACnF,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QACtD,CAAC;QACD,qDAAqD;QACrD,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAAA,CAClC;IAED,qEAAqE;IACrE,KAAK,CAAC,WAAW,CAAC,UAAkB,EAAE,MAAuB,EAAiB;QAC7E,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC3C,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACzB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACnF,CAAC;QACD,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAAA,CAC7C;IAED,qDAAqD;IACrD,KAAK,CAAC,UAAU,CAAC,UAAkB,EAAiB;QACnD,yCAAyC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACnD,IAAI,KAAK,EAAE,CAAC;YACX,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACzC,CAAC;QACD,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACtC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC;YAC3B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAChC,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAAA,CACvC;IAEO,KAAK,CAAC,aAAa,CAAC,UAAkB,EAAE,MAAuB,EAAiB;QACvF,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QAC3C,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC/C,MAAM,CAAC,YAAY,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;gBAClC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;gBAC3C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBAC9B,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;YAAA,CACnC,CAAC;YAEF,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC7B,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;YACrD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAErC,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YAEzC,qBAAqB;YACrB,IAAI,CAAC;gBACJ,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC;gBACnD,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;oBAC3B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,UAAU,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;gBACvD,CAAC;YACF,CAAC;YAAC,MAAM,CAAC;gBACR,mCAAmC;YACpC,CAAC;YAED,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;YACxC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAChC,OAAO,CAAC,IAAI,CACX,oCAAoC,UAAU,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC5G,CAAC;YACF,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;YAC3C,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;QACpC,CAAC;IAAA,CACD;IAEO,aAAa,CAAC,UAAkB,EAAE,QAA6B,EAAQ;QAC9E,MAAM,cAAc,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAE,CAAC;QAC7C,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAE3D,MAAM,IAAI,GAAqB,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACrD,+EAA+E;YAC/E,MAAM,qBAAqB,GAAG,cAAc,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAE7D,4EAA4E;YAC5E,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/C,yEAAyE;YACzE,MAAM,aAAa,GAAI,UAAkB,CAAC,UAAU,IAAI,EAAE,CAAC;YAC3D,MAAM,gBAAgB,GAAI,UAAkB,CAAC,QAAQ,IAAI,EAAE,CAAC;YAC5D,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAC7B;gBACC,GAAG,aAAa;gBAChB,UAAU,EAAE,IAAI,CAAC,QAAQ,CACxB,IAAI,CAAC,MAAM,CAAC;oBACX,WAAW,EACV,8IAA8I;iBAC/I,CAAC,CACF;aACD,EACD,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAC9B,CAAC;YAEF,OAAO;gBACN,IAAI,EAAE,OAAO,cAAc,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAC3D,KAAK,EAAE,OAAO,UAAU,IAAI,IAAI,CAAC,IAAI,EAAE;gBACvC,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC5B,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,sBAAsB,UAAU,IAAI;oBACzD,CAAC,CAAC,yBAAyB,UAAU,GAAG;gBACzC,aAAa,EAAE,IAAI,CAAC,WAAW;oBAC9B,CAAC,CAAC,IAAI,UAAU,KAAK,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;oBACtD,CAAC,CAAC,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,EAAE;gBACjC,UAAU;gBACV,WAAW,EAAE,SAAkB;gBAC/B,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;oBACvC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;wBACzB,OAAO;4BACN,OAAO,EAAE;gCACR;oCACC,IAAI,EAAE,MAAe;oCACrB,IAAI,EAAE,eAAe,UAAU,8CAA8C;iCAC7E;6BACD;4BACD,OAAO,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE;yBACC,CAAC;oBACtC,CAAC;oBACD,IAAI,CAAC;wBACJ,iEAAiE;wBACjE,MAAM,SAAS,GAAG,MAAiC,CAAC;wBACpD,MAAM,aAAa,GAClB,OAAO,SAAS,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,qBAAqB,CAAC;wBACzF,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,GAAG,SAAS,CAAC;wBAEzD,MAAM,eAAe,GACpB,aAAa,KAAK,SAAS;4BAC1B,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;4BACpF,CAAC,CAAC,MAAM,CAAC;wBACX,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;wBAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO;6BACzB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;6BAC5F,IAAI,CAAC,IAAI,CAAC,CAAC;wBACb,OAAO;4BACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC;4BAC1C,OAAO,EAAE,MAAM;yBACoB,CAAC;oBACtC,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBAChB,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;wBACnE,OAAO;4BACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,mBAAmB,GAAG,EAAE,EAAE,CAAC;4BACpE,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;yBACY,CAAC;oBACtC,CAAC;gBAAA,CACD;aACD,CAAC;QAAA,CACF,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAAA,CACjC;IAEO,iBAAiB,CAAC,UAAkB,EAAQ;QACnD,sDAAsD;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACtD,IAAI,QAAQ,EAAE,CAAC;YACd,YAAY,CAAC,QAAQ,CAAC,CAAC;QACxB,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;QAC5C,IAAI,YAAY,CAAC,OAAO,KAAK,KAAK;YAAE,OAAO;QAE3C,MAAM,WAAW,GAAG,YAAY,CAAC,WAAW,IAAI,8BAA8B,CAAC;QAC/E,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,IAAI,6BAA6B,CAAC;QAC5E,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAElE,IAAI,OAAO,GAAG,WAAW,EAAE,CAAC;YAC3B,OAAO,CAAC,IAAI,CAAC,eAAe,UAAU,iCAAiC,WAAW,uBAAuB,CAAC,CAAC;YAC3G,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAC1C,OAAO;QACR,CAAC;QAED,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAChD,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QAE3C,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE,CAAC;YACpC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACxC,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC/C,IAAI,CAAC,GAAG;gBAAE,OAAO;YAEjB,2BAA2B;YAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC/C,IAAI,SAAS,EAAE,CAAC;gBACf,SAAS,CAAC,YAAY,GAAG,IAAI,CAAC;gBAC9B,MAAM,SAAS,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;gBAC7C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACjC,CAAC;YAED,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAAA,CAC1C,EAAE,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,iCAAiC;QAExE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAAA,CAC5C;IAED,0DAA0D;IAC1D,KAAK,CAAC,SAAS,CAAC,UAAkB,EAAiB;QAClD,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC3D,IAAI,aAAa,EAAE,CAAC;YACnB,YAAY,CAAC,aAAa,CAAC,CAAC;YAC5B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACzC,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC/C,IAAI,SAAS,EAAE,CAAC;YACf,SAAS,CAAC,YAAY,GAAG,IAAI,CAAC;YAC9B,MAAM,SAAS,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;YAC7C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;YAC3C,OAAO;QACR,CAAC;QAED,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAAA,CAC7C;IAED,sFAAsF;IACtF,kBAAkB,GAAqB;QACtC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAAA,CAC9C;IAED,cAAc,GAAa;QAC1B,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAAA,CAChC;IAED,eAAe,GAAa;QAC3B,OAAO,IAAI,CAAC,YAAY,CAAC;IAAA,CACzB;IAED,KAAK,CAAC,YAAY,CAAC,GAAW,EAAmB;QAChD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QACpD,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,SAAS,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,UAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACrG,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,eAAe,UAAU,iBAAiB,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACjD,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAAA,CAC3D;IAED,KAAK,CAAC,IAAI,GAAkB;QAC3B,8BAA8B;QAC9B,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC9C,YAAY,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;QAE/B,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;YACzE,IAAI,CAAC;gBACJ,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;YAC3B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,OAAO,CAAC,IAAI,CACX,mCAAmC,IAAI,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACrG,CAAC;YACH,CAAC;QAAA,CACD,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAAA,CAC3B;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\tprivate toolTimeoutMap = new Map<string, Map<string, number>>();\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\tconst pending: Array<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\tif (config.toolTimeouts) {\n\t\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t\t}\n\t\t\tpending.push(this.connectServer(serverName, config));\n\t\t}\n\t\t// Connect all servers in parallel for faster startup\n\t\tawait Promise.allSettled(pending);\n\t}\n\n\t/** Start a single server (used by reloadMcp for targeted toggle). */\n\tasync startServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.serverConfigs.set(serverName, config);\n\t\tif (config.toolTimeouts) {\n\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t}\n\t\tawait this.connectServer(serverName, config);\n\t}\n\n\t/** Stop a single server without affecting others. */\n\tasync stopServer(serverName: string): Promise<void> {\n\t\t// Cancel reconnect timer for this server\n\t\tconst timer = this.reconnectTimers.get(serverName);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t}\n\t\tthis.reconnectAttempts.delete(serverName);\n\t\tthis.serverConfigs.delete(serverName);\n\t\tthis.toolTimeoutMap.delete(serverName);\n\t\tthis.tools.delete(serverName);\n\n\t\tconst client = this.clients.get(serverName);\n\t\tif (client) {\n\t\t\tclient.onDisconnect = null;\n\t\t\tthis.clients.delete(serverName);\n\t\t\tawait client.disconnect().catch(() => {});\n\t\t}\n\t\tthis.setStatus(serverName, \"disabled\");\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 perToolTimeout = this.toolTimeoutMap.get(serverName);\n\n\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => {\n\t\t\t// Resolve effective timeout: per-tool > server default > client default (120s)\n\t\t\tconst toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);\n\n\t\t\t// Add optional _timeoutMs parameter so the AI can override timeout per-call\n\t\t\tconst baseParams = toTypeBox(tool.inputSchema);\n\t\t\t// Merge _timeoutMs into the existing schema (preserving required fields)\n\t\t\tconst existingProps = (baseParams as any).properties ?? {};\n\t\t\tconst existingRequired = (baseParams as any).required ?? [];\n\t\t\tconst parameters = Type.Object(\n\t\t\t\t{\n\t\t\t\t\t...existingProps,\n\t\t\t\t\t_timeoutMs: Type.Optional(\n\t\t\t\t\t\tType.Number({\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\"Optional timeout override in milliseconds for this specific call (e.g. 300000 for 5 min, 5000 for 5s). Overrides the server default timeout.\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\t{ required: existingRequired },\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\t\tdescription: tool.description\n\t\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\t\tpromptSnippet: tool.description\n\t\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\t\tparameters,\n\t\t\t\trenderShell: \"default\" as const,\n\t\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\t\tif (!client.isConnected) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\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\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { error: \"disconnected\" },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Extract _timeoutMs from params (AI-specified timeout override)\n\t\t\t\t\t\tconst rawParams = params as Record<string, unknown>;\n\t\t\t\t\t\tconst callTimeoutMs =\n\t\t\t\t\t\t\ttypeof rawParams._timeoutMs === \"number\" ? rawParams._timeoutMs : toolSpecificTimeoutMs;\n\t\t\t\t\t\tconst { _timeoutMs: _ignored, ...mcpParams } = rawParams;\n\n\t\t\t\t\t\tconst effectiveSignal =\n\t\t\t\t\t\t\tcallTimeoutMs !== undefined\n\t\t\t\t\t\t\t\t? AbortSignal.any([AbortSignal.timeout(callTimeoutMs), ...(signal ? [signal] : [])])\n\t\t\t\t\t\t\t\t: signal;\n\t\t\t\t\t\tconst result = await client.callTool(tool.name, mcpParams, effectiveSignal);\n\t\t\t\t\t\tconst text = result.content\n\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.join(\"\\n\");\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\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"]}
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/core/mcp/manager.ts"],"names":[],"mappings":"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","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\tprivate toolTimeoutMap = new Map<string, Map<string, number>>();\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\tconst pending: Array<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\tif (config.toolTimeouts) {\n\t\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t\t}\n\t\t\tpending.push(this.connectServer(serverName, config));\n\t\t}\n\t\t// Connect all servers in parallel for faster startup\n\t\tawait Promise.allSettled(pending);\n\t}\n\n\t/** Start a single server (used by reloadMcp for targeted toggle). */\n\tasync startServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.serverConfigs.set(serverName, config);\n\t\tif (config.toolTimeouts) {\n\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t}\n\t\tawait this.connectServer(serverName, config);\n\t}\n\n\t/** Stop a single server without affecting others. */\n\tasync stopServer(serverName: string): Promise<void> {\n\t\t// Cancel reconnect timer for this server\n\t\tconst timer = this.reconnectTimers.get(serverName);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t}\n\t\tthis.reconnectAttempts.delete(serverName);\n\t\tthis.serverConfigs.delete(serverName);\n\t\tthis.toolTimeoutMap.delete(serverName);\n\t\tthis.tools.delete(serverName);\n\t\t// Also clean up resource URIs for this server\n\t\tthis.resourceUris = this.resourceUris.filter((u) => !u.startsWith(`mcp://${serverName}`));\n\n\t\tconst client = this.clients.get(serverName);\n\t\tif (client) {\n\t\t\tclient.onDisconnect = null;\n\t\t\tthis.clients.delete(serverName);\n\t\t\tawait client.disconnect().catch(() => {});\n\t\t}\n\t\tthis.setStatus(serverName, \"disabled\");\n\t}\n\n\tprivate async connectServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\t\tlet disconnected = false;\n\t\tconst onDisconnect = () => {\n\t\t\tif (disconnected) return;\n\t\t\tdisconnected = true;\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\tthis.tools.delete(serverName);\n\t\t\tthis.scheduleReconnect(serverName);\n\t\t};\n\n\t\ttry {\n\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\tclient.onDisconnect = () => onDisconnect();\n\n\t\t\tawait client.connect(config);\n\t\t\t// If disconnect happened during connect(), bail out\n\t\t\tif (disconnected) {\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\tif (disconnected) {\n\t\t\t\tclient.disconnect().catch(() => {});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.clients.set(serverName, client);\n\n\t\t\tthis.buildToolDefs(serverName, mcpTools);\n\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\tif (disconnected) {\n\t\t\t\t// Was disconnected during tool/resource discovery\n\t\t\t\tclient.disconnect().catch(() => {});\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t\tthis.tools.delete(serverName);\n\t\t\t\treturn;\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\tonDisconnect();\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}\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 perToolTimeout = this.toolTimeoutMap.get(serverName);\n\n\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => {\n\t\t\t// Resolve effective timeout: per-tool > server default > client default (120s)\n\t\t\tconst toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);\n\n\t\t\t// Add optional _timeoutMs parameter so the AI can override timeout per-call.\n\t\t\t// We must produce a flat properties schema (not allOf) because LLM providers\n\t\t\t// access tool.parameters.properties directly.\n\t\t\tconst baseParams = toTypeBox(tool.inputSchema);\n\t\t\tconst baseSchema = baseParams as unknown as Record<string, unknown>;\n\t\t\tconst mergedProps = {\n\t\t\t\t...((baseSchema.properties ?? {}) as Record<string, unknown>),\n\t\t\t\t_timeoutMs: {\n\t\t\t\t\ttype: \"number\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Optional timeout override in milliseconds for this specific call (e.g. 300000 for 5 min, 5000 for 5s). Overrides the server default timeout.\",\n\t\t\t\t},\n\t\t\t};\n\t\t\tconst mergedRequired = [...((baseSchema.required ?? []) as string[])];\n\t\t\tconst parameters = Type.Object(mergedProps, { required: mergedRequired });\n\n\t\t\treturn {\n\t\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\t\tdescription: tool.description\n\t\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\t\tpromptSnippet: tool.description\n\t\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\t\tparameters,\n\t\t\t\trenderShell: \"default\" as const,\n\t\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\t\tif (!client.isConnected) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\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\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { error: \"disconnected\" },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Extract _timeoutMs from params (AI-specified timeout override)\n\t\t\t\t\t\tconst rawParams = params as Record<string, unknown>;\n\t\t\t\t\t\tconst callTimeoutMs =\n\t\t\t\t\t\t\ttypeof rawParams._timeoutMs === \"number\" ? rawParams._timeoutMs : toolSpecificTimeoutMs;\n\t\t\t\t\t\tconst { _timeoutMs: _ignored, ...mcpParams } = rawParams;\n\n\t\t\t\t\t\tconst effectiveSignal =\n\t\t\t\t\t\t\tcallTimeoutMs !== undefined\n\t\t\t\t\t\t\t\t? AbortSignal.any([AbortSignal.timeout(callTimeoutMs), ...(signal ? [signal] : [])])\n\t\t\t\t\t\t\t\t: signal;\n\t\t\t\t\t\tconst result = await client.callTool(tool.name, mcpParams, effectiveSignal);\n\t\t\t\t\t\tconst text = result.content\n\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.join(\"\\n\");\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\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(() => {\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\toldClient.disconnect().catch(() => {});\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t}\n\n\t\t\tthis.connectServer(serverName, cfg).catch((err) =>\n\t\t\t\tconsole.warn(`MCP reconnect failed for \"${serverName}\":`, err),\n\t\t\t);\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\tgetToolDefinitions(): ToolDefinition[] {\n\t\treturn Array.from(this.tools.values()).flat();\n\t}\n\n\t/** Get all MCP tool descriptions grouped by server (for system prompt). */\n\tgetMcpToolDescriptions(): Array<{ serverName: string; tools: Array<{ name: string; description: string }> }> {\n\t\tconst result: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }> = [];\n\t\tfor (const [serverName, defs] of this.tools) {\n\t\t\tconst tools = defs.map((d) => ({\n\t\t\t\tname: d.name,\n\t\t\t\tdescription: d.description ?? `Tool from MCP server \"${serverName}\"`,\n\t\t\t}));\n\t\t\tresult.push({ serverName, tools });\n\t\t}\n\t\treturn result;\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\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"]}
@@ -32,19 +32,6 @@ export interface MCPResource {
32
32
  description?: string;
33
33
  mimeType?: string;
34
34
  }
35
- export interface MCPResourceTemplate {
36
- uriTemplate: string;
37
- name: string;
38
- description?: string;
39
- }
40
- export interface MCPReadResourceResult {
41
- contents: Array<{
42
- uri: string;
43
- mimeType?: string;
44
- text?: string;
45
- blob?: string;
46
- }>;
47
- }
48
35
  export interface MCPToolDefinition {
49
36
  name: string;
50
37
  description?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,kBAAkB;IAClC,wDAAwD;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0GAA0G;IAC1G,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,mCAAmC;IACnC,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAC/B;AAED,0CAA0C;AAC1C,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,GAAG,UAAU,CAAC;AAE7F,MAAM,WAAW,WAAW;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,KAAK,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACF;AAED,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB","sourcesContent":["/**\n * MCP (Model Context Protocol) client types.\n */\n\nexport interface MCPReconnectConfig {\n\t/** Whether auto-reconnect is enabled (default: true) */\n\tenabled?: boolean;\n\t/** Maximum reconnection attempts before giving up (default: 5) */\n\tmaxAttempts?: number;\n\t/** Delay between reconnection attempts in milliseconds (default: 5000) */\n\tintervalMs?: number;\n}\n\nexport interface MCPServerConfig {\n\tenabled?: boolean;\n\tcommand?: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\turl?: string;\n\ttransport?: \"stdio\" | \"sse\" | \"http\";\n\theaders?: Record<string, string>;\n\t/** Request timeout in milliseconds (default: 120000). 0 = no timeout. */\n\ttimeoutMs?: number;\n\t/** Per-tool timeout overrides (key: tool name, value: timeout in ms). Takes precedence over timeoutMs. */\n\ttoolTimeouts?: Record<string, number>;\n\t/** Auto-reconnect configuration */\n\treconnect?: MCPReconnectConfig;\n}\n\n/** Connection status for an MCP server */\nexport type MCPConnectionStatus = \"connected\" | \"disconnected\" | \"reconnecting\" | \"disabled\";\n\nexport interface MCPResource {\n\turi: string;\n\tname: string;\n\tdescription?: string;\n\tmimeType?: string;\n}\n\nexport interface MCPResourceTemplate {\n\turiTemplate: string;\n\tname: string;\n\tdescription?: string;\n}\n\nexport interface MCPReadResourceResult {\n\tcontents: Array<{\n\t\turi: string;\n\t\tmimeType?: string;\n\t\ttext?: string;\n\t\tblob?: string;\n\t}>;\n}\n\nexport interface MCPToolDefinition {\n\tname: string;\n\tdescription?: string;\n\tinputSchema: {\n\t\ttype: string;\n\t\tproperties?: Record<string, unknown>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface MCPCallToolResult {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tisError?: boolean;\n}\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,kBAAkB;IAClC,wDAAwD;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0GAA0G;IAC1G,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,mCAAmC;IACnC,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAC/B;AAED,0CAA0C;AAC1C,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,GAAG,UAAU,CAAC;AAE7F,MAAM,WAAW,WAAW;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACF;AAED,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB","sourcesContent":["/**\n * MCP (Model Context Protocol) client types.\n */\n\nexport interface MCPReconnectConfig {\n\t/** Whether auto-reconnect is enabled (default: true) */\n\tenabled?: boolean;\n\t/** Maximum reconnection attempts before giving up (default: 5) */\n\tmaxAttempts?: number;\n\t/** Delay between reconnection attempts in milliseconds (default: 5000) */\n\tintervalMs?: number;\n}\n\nexport interface MCPServerConfig {\n\tenabled?: boolean;\n\tcommand?: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\turl?: string;\n\ttransport?: \"stdio\" | \"sse\" | \"http\";\n\theaders?: Record<string, string>;\n\t/** Request timeout in milliseconds (default: 120000). 0 = no timeout. */\n\ttimeoutMs?: number;\n\t/** Per-tool timeout overrides (key: tool name, value: timeout in ms). Takes precedence over timeoutMs. */\n\ttoolTimeouts?: Record<string, number>;\n\t/** Auto-reconnect configuration */\n\treconnect?: MCPReconnectConfig;\n}\n\n/** Connection status for an MCP server */\nexport type MCPConnectionStatus = \"connected\" | \"disconnected\" | \"reconnecting\" | \"disabled\";\n\nexport interface MCPResource {\n\turi: string;\n\tname: string;\n\tdescription?: string;\n\tmimeType?: string;\n}\n\nexport interface MCPToolDefinition {\n\tname: string;\n\tdescription?: string;\n\tinputSchema: {\n\t\ttype: string;\n\t\tproperties?: Record<string, unknown>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface MCPCallToolResult {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tisError?: boolean;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/mcp/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * MCP (Model Context Protocol) client types.\n */\n\nexport interface MCPReconnectConfig {\n\t/** Whether auto-reconnect is enabled (default: true) */\n\tenabled?: boolean;\n\t/** Maximum reconnection attempts before giving up (default: 5) */\n\tmaxAttempts?: number;\n\t/** Delay between reconnection attempts in milliseconds (default: 5000) */\n\tintervalMs?: number;\n}\n\nexport interface MCPServerConfig {\n\tenabled?: boolean;\n\tcommand?: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\turl?: string;\n\ttransport?: \"stdio\" | \"sse\" | \"http\";\n\theaders?: Record<string, string>;\n\t/** Request timeout in milliseconds (default: 120000). 0 = no timeout. */\n\ttimeoutMs?: number;\n\t/** Per-tool timeout overrides (key: tool name, value: timeout in ms). Takes precedence over timeoutMs. */\n\ttoolTimeouts?: Record<string, number>;\n\t/** Auto-reconnect configuration */\n\treconnect?: MCPReconnectConfig;\n}\n\n/** Connection status for an MCP server */\nexport type MCPConnectionStatus = \"connected\" | \"disconnected\" | \"reconnecting\" | \"disabled\";\n\nexport interface MCPResource {\n\turi: string;\n\tname: string;\n\tdescription?: string;\n\tmimeType?: string;\n}\n\nexport interface MCPResourceTemplate {\n\turiTemplate: string;\n\tname: string;\n\tdescription?: string;\n}\n\nexport interface MCPReadResourceResult {\n\tcontents: Array<{\n\t\turi: string;\n\t\tmimeType?: string;\n\t\ttext?: string;\n\t\tblob?: string;\n\t}>;\n}\n\nexport interface MCPToolDefinition {\n\tname: string;\n\tdescription?: string;\n\tinputSchema: {\n\t\ttype: string;\n\t\tproperties?: Record<string, unknown>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface MCPCallToolResult {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tisError?: boolean;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/mcp/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * MCP (Model Context Protocol) client types.\n */\n\nexport interface MCPReconnectConfig {\n\t/** Whether auto-reconnect is enabled (default: true) */\n\tenabled?: boolean;\n\t/** Maximum reconnection attempts before giving up (default: 5) */\n\tmaxAttempts?: number;\n\t/** Delay between reconnection attempts in milliseconds (default: 5000) */\n\tintervalMs?: number;\n}\n\nexport interface MCPServerConfig {\n\tenabled?: boolean;\n\tcommand?: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\turl?: string;\n\ttransport?: \"stdio\" | \"sse\" | \"http\";\n\theaders?: Record<string, string>;\n\t/** Request timeout in milliseconds (default: 120000). 0 = no timeout. */\n\ttimeoutMs?: number;\n\t/** Per-tool timeout overrides (key: tool name, value: timeout in ms). Takes precedence over timeoutMs. */\n\ttoolTimeouts?: Record<string, number>;\n\t/** Auto-reconnect configuration */\n\treconnect?: MCPReconnectConfig;\n}\n\n/** Connection status for an MCP server */\nexport type MCPConnectionStatus = \"connected\" | \"disconnected\" | \"reconnecting\" | \"disabled\";\n\nexport interface MCPResource {\n\turi: string;\n\tname: string;\n\tdescription?: string;\n\tmimeType?: string;\n}\n\nexport interface MCPToolDefinition {\n\tname: string;\n\tdescription?: string;\n\tinputSchema: {\n\t\ttype: string;\n\t\tproperties?: Record<string, unknown>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface MCPCallToolResult {\n\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\tisError?: boolean;\n}\n"]}