{"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../../src/core/mcp-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { once } from \"node:events\";\nimport { type ClientRequest, request as httpRequest } from \"node:http\";\nimport { request as httpsRequest } from \"node:https\";\nimport { createInterface, type Interface } from \"node:readline\";\nimport { URL } from \"node:url\";\nimport {\n\tclassifyMcpToolEffects,\n\tcreateMcpCapabilitySnapshot,\n\tcreateMcpIdentity,\n\tdetectCapabilityDrift,\n\ttype McpCapabilitySnapshot,\n\ttype McpServerConfig,\n\ttype McpServerIdentity,\n\ttype McpToolEffects,\n\ttype McpToolSchema,\n\tmcpSha256,\n\tvalidateMcpConfig,\n\tvalidateMcpToolSchemas,\n} from \"./mcp.js\";\n\nexport interface McpJsonRpcError {\n\tcode: number;\n\tmessage: string;\n\tdata?: unknown;\n}\nexport interface McpJsonRpcResponse {\n\tjsonrpc: \"2.0\";\n\tid?: string | number | null;\n\tresult?: unknown;\n\terror?: McpJsonRpcError;\n}\nexport interface McpCallResult {\n\tresult: unknown;\n\trequestId: string;\n\teffect: McpToolEffects;\n}\nexport type McpConnectionState = \"starting\" | \"ready\" | \"degraded\" | \"reconnecting\" | \"failed\" | \"stopped\";\nexport interface McpClientOptions {\n\tcwd?: string;\n\tenv?: NodeJS.ProcessEnv;\n\tonEvent?: (event: McpHealthEvent) => void;\n}\nexport interface McpHealthEvent {\n\tstate: McpConnectionState;\n\treason: string;\n\tat: string;\n\tattempt: number;\n}\n\nconst MAX_MESSAGE_BYTES = 1024 * 1024;\nconst MAX_STDERR_BYTES = 64 * 1024;\nconst DEFAULT_TIMEOUT = 30_000;\n\nexport class McpTransportError extends Error {\n\treadonly code:\n\t\t| \"configuration\"\n\t\t| \"timeout\"\n\t\t| \"cancelled\"\n\t\t| \"authentication\"\n\t\t| \"schema\"\n\t\t| \"network\"\n\t\t| \"protocol\"\n\t\t| \"process\";\n\treadonly retryable: boolean;\n\tconstructor(code: McpTransportError[\"code\"], message: string, retryable = false) {\n\t\tsuper(message);\n\t\tthis.name = \"McpTransportError\";\n\t\tthis.code = code;\n\t\tthis.retryable = retryable;\n\t}\n}\n\nfunction bounded(value: string): string {\n\treturn value.length > MAX_MESSAGE_BYTES ? value.slice(0, MAX_MESSAGE_BYTES) : value;\n}\nfunction timeoutOf(config: McpServerConfig): number {\n\treturn config.requestTimeoutMs ?? DEFAULT_TIMEOUT;\n}\nfunction jsonLine(value: unknown): string {\n\treturn `${JSON.stringify(value)}\\n`;\n}\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value && typeof value === \"object\" && !Array.isArray(value));\n}\n\ninterface PendingRequest {\n\tresolve: (response: McpJsonRpcResponse) => void;\n\treject: (error: Error) => void;\n\ttimer: NodeJS.Timeout;\n}\n\nclass StdioTransport {\n\tprivate child?: ChildProcessWithoutNullStreams;\n\tprivate lines?: Interface;\n\tprivate readonly pending = new Map<string, PendingRequest>();\n\tprivate stderr = \"\";\n\tprivate nextId = 1;\n\tprivate processStart?: number;\n\tprivate closed = false;\n\tconstructor(\n\t\tprivate readonly config: McpServerConfig,\n\t\tprivate readonly options: McpClientOptions,\n\t) {}\n\tasync connect(): Promise<McpServerIdentity> {\n\t\tconst issues = validateMcpConfig(this.config);\n\t\tif (issues.length > 0) throw new McpTransportError(\"configuration\", issues.map((issue) => issue.code).join(\", \"));\n\t\tconst env: NodeJS.ProcessEnv = {};\n\t\tfor (const key of Object.keys(this.config.envRefs ?? {})) {\n\t\t\tif (this.config.envRefs?.[key] && this.options.env?.[this.config.envRefs[key]])\n\t\t\t\tenv[key] = this.options.env[this.config.envRefs[key]];\n\t\t}\n\t\tconst command = this.config.command!;\n\t\tthis.child = spawn(command, this.config.args ?? [], {\n\t\t\tcwd: this.options.cwd,\n\t\t\tenv,\n\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\tdetached: process.platform !== \"win32\",\n\t\t\twindowsHide: true,\n\t\t});\n\t\tthis.processStart = Date.now();\n\t\tthis.lines = createInterface({ input: this.child.stdout });\n\t\tthis.lines.on(\"line\", (line) => this.onLine(line));\n\t\tthis.child.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\tthis.stderr = `${this.stderr}${chunk.toString(\"utf8\")}`.slice(-MAX_STDERR_BYTES);\n\t\t});\n\t\tthis.child.once(\"exit\", (code, signal) => {\n\t\t\tif (!this.closed)\n\t\t\t\tthis.rejectPending(new McpTransportError(\"process\", `MCP process exited (${code ?? signal ?? \"unknown\"})`));\n\t\t});\n\t\tconst initialize = await this.request(\n\t\t\t\"initialize\",\n\t\t\t{ protocolVersion: \"2025-03-26\", capabilities: {}, clientInfo: { name: \"jensen\", version: \"1.6.1\" } },\n\t\t\tthis.config.startupTimeoutMs ?? DEFAULT_TIMEOUT,\n\t\t);\n\t\tconst protocolVersion =\n\t\t\tisRecord(initialize.result) && typeof initialize.result.protocolVersion === \"string\"\n\t\t\t\t? initialize.result.protocolVersion\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\t...createMcpIdentity(this.config),\n\t\t\tprocessIdentity: `${this.child.pid}:${this.processStart}:${mcpSha256(command)}`,\n\t\t\tconnectedAt: new Date().toISOString(),\n\t\t\tprotocolVersion,\n\t\t};\n\t}\n\tprivate onLine(line: string): void {\n\t\tif (Buffer.byteLength(line) > MAX_MESSAGE_BYTES) {\n\t\t\tthis.rejectPending(new McpTransportError(\"protocol\", \"MCP message exceeds size limit\"));\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst value = JSON.parse(line) as McpJsonRpcResponse;\n\t\t\tif (value && value.id !== undefined) {\n\t\t\t\tconst pending = this.pending.get(String(value.id));\n\t\t\t\tif (pending) {\n\t\t\t\t\tclearTimeout(pending.timer);\n\t\t\t\t\tthis.pending.delete(String(value.id));\n\t\t\t\t\tpending.resolve(value);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\tthis.rejectPending(new McpTransportError(\"protocol\", \"Invalid MCP JSON-RPC response\"));\n\t\t}\n\t}\n\trequest(\n\t\tmethod: string,\n\t\tparams: unknown,\n\t\ttimeoutMs = timeoutOf(this.config),\n\t\tsignal?: AbortSignal,\n\t): Promise<McpJsonRpcResponse> {\n\t\tif (!this.child?.stdin || this.closed)\n\t\t\treturn Promise.reject(new McpTransportError(\"process\", \"MCP stdio transport is not connected\"));\n\t\tconst id = String(this.nextId++);\n\t\tconst payload = { jsonrpc: \"2.0\", id, method, params };\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst finishReject = (error: Error) => {\n\t\t\t\tthis.pending.delete(id);\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst timer = setTimeout(\n\t\t\t\t() => finishReject(new McpTransportError(\"timeout\", `MCP request timed out: ${method}`, true)),\n\t\t\t\ttimeoutMs,\n\t\t\t);\n\t\t\tthis.pending.set(id, { resolve, reject: finishReject, timer });\n\t\t\tconst abort = () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tfinishReject(new McpTransportError(\"cancelled\", `MCP request cancelled: ${method}`));\n\t\t\t};\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tabort();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsignal.addEventListener(\"abort\", abort, { once: true });\n\t\t\t}\n\t\t\tthis.child!.stdin.write(jsonLine(payload), (error) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tfinishReject(new McpTransportError(\"process\", error.message, true));\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\tprivate rejectPending(error: Error): void {\n\t\tfor (const pending of this.pending.values()) {\n\t\t\tclearTimeout(pending.timer);\n\t\t\tpending.reject(error);\n\t\t}\n\t\tthis.pending.clear();\n\t}\n\tasync close(): Promise<void> {\n\t\tthis.closed = true;\n\t\tthis.rejectPending(new McpTransportError(\"cancelled\", \"MCP transport closed\"));\n\t\tthis.lines?.close();\n\t\tconst child = this.child;\n\t\tif (!child || child.exitCode !== null) return;\n\t\tif (process.platform === \"win32\")\n\t\t\tspawn(\"taskkill\", [\"/PID\", String(child.pid), \"/T\", \"/F\"], { stdio: \"ignore\", windowsHide: true });\n\t\telse if (child.pid) {\n\t\t\ttry {\n\t\t\t\tprocess.kill(-child.pid, \"SIGTERM\");\n\t\t\t} catch {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\t\tawait Promise.race([once(child, \"exit\"), new Promise((resolve) => setTimeout(resolve, 2_000))]);\n\t\tif (child.exitCode === null) {\n\t\t\ttry {\n\t\t\t\tif (process.platform !== \"win32\" && child.pid) process.kill(-child.pid, \"SIGKILL\");\n\t\t\t\telse child.kill(\"SIGKILL\");\n\t\t\t} catch {}\n\t\t}\n\t}\n}\n\nclass HttpTransport {\n\tprivate endpoint?: URL;\n\tprivate closed = false;\n\tconstructor(\n\t\tprivate readonly config: McpServerConfig,\n\t\tprivate readonly options: McpClientOptions,\n\t) {}\n\tasync connect(): Promise<McpServerIdentity> {\n\t\tconst issues = validateMcpConfig(this.config);\n\t\tif (issues.length > 0) throw new McpTransportError(\"configuration\", issues.map((issue) => issue.code).join(\", \"));\n\t\tconst url = new URL(this.config.url!);\n\t\tif (url.protocol === \"http:\" && process.env.JENSEN_ALLOW_INSECURE_MCP !== \"1\")\n\t\t\tthrow new McpTransportError(\"configuration\", \"HTTPS is required\");\n\t\tconst response = await this.send(\"initialize\", {\n\t\t\tprotocolVersion: \"2025-03-26\",\n\t\t\tcapabilities: {},\n\t\t\tclientInfo: { name: \"jensen\", version: \"1.6.1\" },\n\t\t});\n\t\tconst protocolVersion =\n\t\t\tisRecord(response.result) && typeof response.result.protocolVersion === \"string\"\n\t\t\t\t? response.result.protocolVersion\n\t\t\t\t: undefined;\n\t\treturn { ...createMcpIdentity(this.config), connectedAt: new Date().toISOString(), protocolVersion };\n\t}\n\tprivate async send(method: string, params: unknown, signal?: AbortSignal): Promise<McpJsonRpcResponse> {\n\t\tif (this.closed) throw new McpTransportError(\"cancelled\", \"MCP HTTP transport is closed\");\n\t\tconst url = new URL(this.endpoint ?? this.config.url!);\n\t\tconst body = jsonLine({ jsonrpc: \"2.0\", id: randomUUID(), method, params });\n\t\tconst requestFn = url.protocol === \"https:\" ? httpsRequest : httpRequest;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst req: ClientRequest = requestFn(\n\t\t\t\turl,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t\t\taccept: \"application/json, text/event-stream\",\n\t\t\t\t\t\t...this.authHeaders(),\n\t\t\t\t\t},\n\t\t\t\t\ttimeout: timeoutOf(this.config),\n\t\t\t\t},\n\t\t\t\t(res) => {\n\t\t\t\t\tif (res.statusCode === 401 || res.statusCode === 403) {\n\t\t\t\t\t\treject(new McpTransportError(\"authentication\", `MCP authentication failed (${res.statusCode})`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif ((res.statusCode ?? 500) >= 400) {\n\t\t\t\t\t\treject(new McpTransportError(\"network\", `MCP HTTP error (${res.statusCode})`, true));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet data = \"\";\n\t\t\t\t\tres.setEncoding(\"utf8\");\n\t\t\t\t\tres.on(\"data\", (chunk) => {\n\t\t\t\t\t\tdata += chunk;\n\t\t\t\t\t\tif (Buffer.byteLength(data) > MAX_MESSAGE_BYTES)\n\t\t\t\t\t\t\treq.destroy(new McpTransportError(\"protocol\", \"MCP response exceeds size limit\"));\n\t\t\t\t\t});\n\t\t\t\t\tres.on(\"end\", () => {\n\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(data.trim()) as McpJsonRpcResponse;\n\t\t\t\t\t\t\tif (parsed.error) reject(new McpTransportError(\"protocol\", parsed.error.message));\n\t\t\t\t\t\t\telse resolve(parsed);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treject(new McpTransportError(\"protocol\", \"MCP response is not JSON\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t);\n\t\t\treq.on(\"timeout\", () =>\n\t\t\t\treq.destroy(new McpTransportError(\"timeout\", `MCP request timed out: ${method}`, true)),\n\t\t\t);\n\t\t\treq.on(\"error\", (error) => {\n\t\t\t\tif (!settled) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\treject(\n\t\t\t\t\t\terror instanceof McpTransportError ? error : new McpTransportError(\"network\", error.message, true),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) req.destroy(new McpTransportError(\"cancelled\", \"MCP request cancelled\"));\n\t\t\t\telse\n\t\t\t\t\tsignal.addEventListener(\n\t\t\t\t\t\t\"abort\",\n\t\t\t\t\t\t() => req.destroy(new McpTransportError(\"cancelled\", \"MCP request cancelled\")),\n\t\t\t\t\t\t{ once: true },\n\t\t\t\t\t);\n\t\t\t}\n\t\t\treq.end(body);\n\t\t});\n\t}\n\tprivate authHeaders(): Record<string, string> {\n\t\tconst headers: Record<string, string> = {};\n\t\tfor (const [name, ref] of Object.entries(this.config.headerRefs ?? {})) {\n\t\t\tconst value = this.options.env?.[ref];\n\t\t\tif (value) headers[name] = value;\n\t\t}\n\t\treturn headers;\n\t}\n\trequest(method: string, params: unknown, signal?: AbortSignal): Promise<McpJsonRpcResponse> {\n\t\treturn this.send(method, params, signal);\n\t}\n\tclose(): void {\n\t\tthis.closed = true;\n\t}\n}\n\nexport class McpClient {\n\tprivate identity?: McpServerIdentity;\n\tprivate snapshot?: McpCapabilitySnapshot;\n\tprivate state: McpConnectionState = \"starting\";\n\tprivate readonly stdio?: StdioTransport;\n\tprivate readonly http?: HttpTransport;\n\tconstructor(\n\t\tprivate readonly config: McpServerConfig,\n\t\tprivate readonly options: McpClientOptions = {},\n\t) {\n\t\tif (config.transport === \"stdio\") this.stdio = new StdioTransport(config, options);\n\t\telse this.http = new HttpTransport(config, options);\n\t}\n\tget health(): McpConnectionState {\n\t\treturn this.state;\n\t}\n\tget capabilitySnapshot(): McpCapabilitySnapshot | undefined {\n\t\treturn this.snapshot;\n\t}\n\tasync connect(): Promise<McpCapabilitySnapshot> {\n\t\tthis.state = \"starting\";\n\t\tthis.emit(\"starting\", \"connect\", 0);\n\t\tthis.identity = this.stdio ? await this.stdio.connect() : await this.http!.connect();\n\t\tconst response = await this.requestRaw(\"tools/list\", {});\n\t\tconst value = isRecord(response.result) ? response.result : {};\n\t\tconst toolResult = validateMcpToolSchemas(Array.isArray(value.tools) ? (value.tools as McpToolSchema[]) : []);\n\t\tif (!toolResult.valid) {\n\t\t\tthis.state = \"failed\";\n\t\t\tthrow new McpTransportError(\"schema\", toolResult.rejected.map((issue) => issue.code).join(\", \"));\n\t\t}\n\t\tthis.snapshot = createMcpCapabilitySnapshot({\n\t\t\tserverId: this.config.serverId,\n\t\t\tprotocolVersion: this.identity.protocolVersion,\n\t\t\ttools: toolResult.tools,\n\t\t\tresources: [],\n\t\t\tresourceTemplates: [],\n\t\t\tprompts: [],\n\t\t\tcapabilities: {},\n\t\t});\n\t\tthis.state = \"ready\";\n\t\tthis.emit(\"ready\", \"initialized\", 0);\n\t\treturn this.snapshot;\n\t}\n\tasync callTool(name: string, argumentsValue: unknown, signal?: AbortSignal): Promise<McpCallResult> {\n\t\tif (!this.snapshot) throw new McpTransportError(\"configuration\", \"MCP capability snapshot is unavailable\");\n\t\tconst tool = this.snapshot.tools.find((candidate) => candidate.name === name);\n\t\tif (!tool) throw new McpTransportError(\"schema\", `MCP tool is not in current capability snapshot: ${name}`);\n\t\tconst effect = classifyMcpToolEffects(tool);\n\t\tconst response = await this.requestRaw(\"tools/call\", { name, arguments: argumentsValue }, signal);\n\t\tif (!response.result || !isRecord(response.result))\n\t\t\tthrow new McpTransportError(\"schema\", \"MCP tool result is not an object\");\n\t\treturn { result: response.result, requestId: String(response.id ?? \"\"), effect };\n\t}\n\tlistResources(signal?: AbortSignal): Promise<McpJsonRpcResponse> {\n\t\treturn this.requestRaw(\"resources/list\", {}, signal);\n\t}\n\treadResource(uri: string, signal?: AbortSignal): Promise<McpJsonRpcResponse> {\n\t\treturn this.requestRaw(\"resources/read\", { uri }, signal);\n\t}\n\tlistPrompts(signal?: AbortSignal): Promise<McpJsonRpcResponse> {\n\t\treturn this.requestRaw(\"prompts/list\", {}, signal);\n\t}\n\tinspectPrompt(\n\t\tname: string,\n\t\targumentsValue?: Record<string, string>,\n\t\tsignal?: AbortSignal,\n\t): Promise<McpJsonRpcResponse> {\n\t\treturn this.requestRaw(\"prompts/get\", { name, arguments: argumentsValue }, signal);\n\t}\n\tasync refreshCapabilities(): Promise<string[]> {\n\t\tconst previous = this.snapshot;\n\t\tconst next = await this.connect();\n\t\treturn previous ? detectCapabilityDrift(previous, next) : [];\n\t}\n\tasync close(): Promise<void> {\n\t\tthis.state = \"stopped\";\n\t\tif (this.stdio) await this.stdio.close();\n\t\telse this.http?.close();\n\t\tthis.emit(\"stopped\", \"closed\", 0);\n\t}\n\tprivate requestRaw(method: string, params: unknown, signal?: AbortSignal): Promise<McpJsonRpcResponse> {\n\t\treturn this.stdio\n\t\t\t? this.stdio.request(method, params, timeoutOf(this.config), signal)\n\t\t\t: this.http!.request(method, params, signal);\n\t}\n\tprivate emit(state: McpConnectionState, reason: string, attempt: number): void {\n\t\tthis.options.onEvent?.({ state, reason, attempt, at: new Date().toISOString() });\n\t}\n}\n\nexport function createMcpClient(config: McpServerConfig, options?: McpClientOptions): McpClient {\n\treturn new McpClient(config, options);\n}\nexport function redactMcpError(error: unknown): string {\n\treturn bounded(error instanceof Error ? error.message : String(error)).replace(\n\t\t/Bearer\\s+\\S+/gi,\n\t\t\"Bearer [REDACTED]\",\n\t);\n}\n"]}