@openeryc/pi-coding-agent 0.75.53 → 0.75.54

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.
@@ -21,6 +21,10 @@ export declare class MCPManager {
21
21
  }>;
22
22
  private setStatus;
23
23
  start(serverConfigs: Record<string, MCPServerConfig>): Promise<void>;
24
+ /** Start a single server (used by reloadMcp for targeted toggle). */
25
+ startServer(serverName: string, config: MCPServerConfig): Promise<void>;
26
+ /** Stop a single server without affecting others. */
27
+ stopServer(serverName: string): Promise<void>;
24
28
  private connectServer;
25
29
  private buildToolDefs;
26
30
  private scheduleReconnect;
@@ -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,CAYzE;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\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\tawait this.connectServer(serverName, config);\n\t\t}\n\t}\n\n\tprivate async connectServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\t\ttry {\n\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\tclient.onDisconnect = (_reason) => {\n\t\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\t\tthis.tools.delete(serverName);\n\t\t\t\tthis.scheduleReconnect(serverName);\n\t\t\t};\n\n\t\t\tawait client.connect(config);\n\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\tthis.clients.set(serverName, client);\n\n\t\t\tthis.buildToolDefs(serverName, mcpTools);\n\n\t\t\t// Discover resources\n\t\t\ttry {\n\t\t\t\tconst { resources } = await client.listResources();\n\t\t\t\tfor (const r of resources) {\n\t\t\t\t\tthis.resourceUris.push(`mcp://${serverName}${r.uri}`);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Server may not support resources\n\t\t\t}\n\n\t\t\tthis.setStatus(serverName, \"connected\");\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t} catch (error) {\n\t\t\tthis.clients.delete(serverName);\n\t\t\tconsole.warn(\n\t\t\t\t`Failed to connect to MCP server \"${serverName}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\tthis.scheduleReconnect(serverName);\n\t\t}\n\t}\n\n\tprivate buildToolDefs(serverName: string, mcpTools: MCPToolDefinition[]): void {\n\t\tconst safeServerName = sanitizeMcpName(serverName);\n\t\tconst client = this.clients.get(serverName)!;\n\t\tconst 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,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"]}
@@ -68,6 +68,7 @@ export class MCPManager {
68
68
  }
69
69
  }
70
70
  async start(serverConfigs) {
71
+ const pending = [];
71
72
  for (const [serverName, config] of Object.entries(serverConfigs)) {
72
73
  if (config.enabled === false) {
73
74
  this.setStatus(serverName, "disabled");
@@ -77,8 +78,38 @@ export class MCPManager {
77
78
  if (config.toolTimeouts) {
78
79
  this.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));
79
80
  }
80
- await this.connectServer(serverName, config);
81
+ pending.push(this.connectServer(serverName, config));
81
82
  }
83
+ // Connect all servers in parallel for faster startup
84
+ await Promise.allSettled(pending);
85
+ }
86
+ /** Start a single server (used by reloadMcp for targeted toggle). */
87
+ async startServer(serverName, config) {
88
+ this.serverConfigs.set(serverName, config);
89
+ if (config.toolTimeouts) {
90
+ this.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));
91
+ }
92
+ await this.connectServer(serverName, config);
93
+ }
94
+ /** Stop a single server without affecting others. */
95
+ async stopServer(serverName) {
96
+ // Cancel reconnect timer for this server
97
+ const timer = this.reconnectTimers.get(serverName);
98
+ if (timer) {
99
+ clearTimeout(timer);
100
+ this.reconnectTimers.delete(serverName);
101
+ }
102
+ this.reconnectAttempts.delete(serverName);
103
+ this.serverConfigs.delete(serverName);
104
+ this.toolTimeoutMap.delete(serverName);
105
+ this.tools.delete(serverName);
106
+ const client = this.clients.get(serverName);
107
+ if (client) {
108
+ client.onDisconnect = null;
109
+ this.clients.delete(serverName);
110
+ await client.disconnect().catch(() => { });
111
+ }
112
+ this.setStatus(serverName, "disabled");
82
113
  }
83
114
  async connectServer(serverName, config) {
84
115
  this.setStatus(serverName, "reconnecting");
@@ -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,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,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC9C,CAAC;IAAA,CACD;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\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\tawait this.connectServer(serverName, config);\n\t\t}\n\t}\n\n\tprivate async connectServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\t\ttry {\n\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\tclient.onDisconnect = (_reason) => {\n\t\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\t\tthis.tools.delete(serverName);\n\t\t\t\tthis.scheduleReconnect(serverName);\n\t\t\t};\n\n\t\t\tawait client.connect(config);\n\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\tthis.clients.set(serverName, client);\n\n\t\t\tthis.buildToolDefs(serverName, mcpTools);\n\n\t\t\t// Discover resources\n\t\t\ttry {\n\t\t\t\tconst { resources } = await client.listResources();\n\t\t\t\tfor (const r of resources) {\n\t\t\t\t\tthis.resourceUris.push(`mcp://${serverName}${r.uri}`);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Server may not support resources\n\t\t\t}\n\n\t\t\tthis.setStatus(serverName, \"connected\");\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t} catch (error) {\n\t\t\tthis.clients.delete(serverName);\n\t\t\tconsole.warn(\n\t\t\t\t`Failed to connect to MCP server \"${serverName}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\tthis.scheduleReconnect(serverName);\n\t\t}\n\t}\n\n\tprivate buildToolDefs(serverName: string, mcpTools: MCPToolDefinition[]): void {\n\t\tconst safeServerName = sanitizeMcpName(serverName);\n\t\tconst client = this.clients.get(serverName)!;\n\t\tconst 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\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"]}