{"version":3,"file":"mcp-client-service.d.ts","sourceRoot":"","sources":["../../../src/core/mcp-foundation/mcp-client-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,eAAe,EAA2B,MAAM,wCAAwC,CAAC;AAGvG,OAAO,KAAK,EACX,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,MAAM,gBAAgB,CAAC;AAMxB,MAAM,WAAW,uBAAuB;IACvC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAOD,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAE5D,YAAY,OAAO,GAAE,uBAA4B,EAGhD;IAEK,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CASpE;IAEK,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKjD;IAEK,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAE/D;IAEK,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAiC9E;IAEK,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiBzD;IAED,yDAAyD;IACzD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAE9C;IAED,OAAO,CAAC,cAAc;CAOtB","sourcesContent":["/**\n * MCP Client Foundation — client service (2.13.0).\n *\n * Implements the Jensen-facing `McpClientPort` over the SDK adapter and\n * integrates tool calls with the Evidence Archive. Sessions are process-lifetime\n * objects: no fake cross-process persistence is implied.\n */\n\nimport { type EvidenceArchive, InMemoryEvidenceArchive } from \"../context-runtime/evidence-archive.js\";\nimport { McpClientError } from \"./mcp-error.js\";\nimport { buildMcpEvidence, recordMcpEvidence } from \"./mcp-evidence.js\";\nimport type {\n\tMcpServerDefinition,\n\tMcpServerSession,\n\tMcpSessionHealth,\n\tMcpToolCall,\n\tMcpToolDescriptor,\n\tMcpToolResult,\n} from \"./mcp-types.js\";\nimport { McpSdkAdapter } from \"./sdk-adapter.js\";\nimport { safeServerCommand } from \"./server-config.js\";\n\nlet sessionCounter = 0;\n\nexport interface McpClientServiceOptions {\n\tevidenceArchive?: EvidenceArchive;\n\tnow?: () => number;\n}\n\ninterface SessionEntry {\n\tadapter: McpSdkAdapter;\n\tdefinition: McpServerDefinition;\n}\n\nexport class McpClientService {\n\tprivate readonly evidenceArchive: EvidenceArchive;\n\tprivate readonly now: () => number;\n\tprivate readonly sessions = new Map<string, SessionEntry>();\n\n\tconstructor(options: McpClientServiceOptions = {}) {\n\t\tthis.evidenceArchive = options.evidenceArchive ?? new InMemoryEvidenceArchive();\n\t\tthis.now = options.now ?? Date.now;\n\t}\n\n\tasync connect(server: McpServerDefinition): Promise<McpServerSession> {\n\t\tsessionCounter += 1;\n\t\tconst sessionId = `mcp-${sessionCounter}-${server.id}`;\n\t\tconst adapter = new McpSdkAdapter({ sessionId, serverId: server.id, now: this.now });\n\n\t\tawait adapter.connect(server);\n\n\t\tthis.sessions.set(sessionId, { adapter, definition: server });\n\t\treturn adapter.snapshot();\n\t}\n\n\tasync disconnect(sessionId: string): Promise<void> {\n\t\tconst entry = this.requireSession(sessionId);\n\t\tawait entry.adapter.disconnect();\n\t\t// Keep the (now-disconnected) adapter so disconnect remains idempotent and\n\t\t// health() can still report the terminal state of a known session.\n\t}\n\n\tasync listTools(sessionId: string): Promise<McpToolDescriptor[]> {\n\t\treturn this.requireSession(sessionId).adapter.listTools();\n\t}\n\n\tasync callTool(sessionId: string, request: McpToolCall): Promise<McpToolResult> {\n\t\tconst entry = this.requireSession(sessionId);\n\t\tif (typeof request.toolName !== \"string\" || request.toolName.length === 0) {\n\t\t\tthrow new McpClientError(\"MCP_INVALID_TOOL_CALL\", \"toolName must be a non-empty string\", {\n\t\t\t\tserverId: entry.definition.id,\n\t\t\t\tsessionId,\n\t\t\t});\n\t\t}\n\t\tif (request.arguments === null || typeof request.arguments !== \"object\" || Array.isArray(request.arguments)) {\n\t\t\tthrow new McpClientError(\"MCP_INVALID_TOOL_CALL\", \"arguments must be a JSON object\", {\n\t\t\t\tserverId: entry.definition.id,\n\t\t\t\tsessionId,\n\t\t\t\ttoolName: request.toolName,\n\t\t\t});\n\t\t}\n\n\t\tconst result = await entry.adapter.callTool(request);\n\n\t\t// Evidence is first-class but best-effort: never let archive failure turn a\n\t\t// real tool outcome into an error, and never fabricate success evidence.\n\t\ttry {\n\t\t\tconst evidence = buildMcpEvidence({\n\t\t\t\tresult,\n\t\t\t\tserverCommand: safeServerCommand(entry.definition),\n\t\t\t\tsession: entry.adapter.snapshot(),\n\t\t\t\targuments: request.arguments,\n\t\t\t});\n\t\t\tresult.evidenceId = await recordMcpEvidence(this.evidenceArchive, evidence);\n\t\t} catch {\n\t\t\tresult.evidenceId = undefined;\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync health(sessionId: string): Promise<McpSessionHealth> {\n\t\tconst entry = this.requireSession(sessionId);\n\t\tconst snapshot = entry.adapter.snapshot();\n\t\treturn {\n\t\t\tsessionId: snapshot.sessionId,\n\t\t\tserverId: snapshot.serverId,\n\t\t\tstate: snapshot.state,\n\t\t\tconnected: snapshot.state === \"CONNECTED\",\n\t\t\tprocessAlive: entry.adapter.processAlive(),\n\t\t\tpid: snapshot.pid,\n\t\t\tconnectedAtMs: snapshot.connectedAtMs,\n\t\t\tfailed: snapshot.state === \"FAILED\",\n\t\t\tfailure: snapshot.failure,\n\t\t\tcapabilitiesKnown: snapshot.capabilities !== undefined,\n\t\t\tprotocolVersion: snapshot.protocolVersion,\n\t\t\tprotocolEra: snapshot.protocolEra,\n\t\t};\n\t}\n\n\t/** Snapshot of a known session (used by CLI inspect). */\n\tgetSession(sessionId: string): McpServerSession {\n\t\treturn this.requireSession(sessionId).adapter.snapshot();\n\t}\n\n\tprivate requireSession(sessionId: string): SessionEntry {\n\t\tconst entry = this.sessions.get(sessionId);\n\t\tif (!entry) {\n\t\t\tthrow new McpClientError(\"MCP_SESSION_NOT_FOUND\", `no MCP session with id \"${sessionId}\"`, { sessionId });\n\t\t}\n\t\treturn entry;\n\t}\n}\n"]}