{"version":3,"sources":["../../src/services/McpService.ts"],"sourcesContent":["import { McpToolCallResponse } from '../types.js';\nimport { BaseService, BaseServiceConfig } from './BaseService.js';\n\nexport interface McpServiceConfig extends BaseServiceConfig {}\n\n/**\n * Calls the Coolhand server's `/mcp` endpoint, which speaks JSON-RPC 2.0. Used by coolhand-cli's\n * optimization commands (list-workloads, search/get/close/update-optimization) to invoke server-side\n * MCP tools.\n *\n * Unlike logging/feedback, this endpoint authenticates with the client's PRIVATE key (passed as the\n * `apiKey` config field and sent as `X-API-Key`), and it surfaces failures by THROWING rather than\n * returning `null`, so callers can show the error to the user. `dryRun: true` still applies here —\n * it skips the request and returns `null`, since some MCP tools (e.g. `close_optimization`) mutate\n * server state.\n */\nexport class McpService extends BaseService {\n  constructor(config: McpServiceConfig) {\n    super(config, '/mcp');\n  }\n\n  /**\n   * Invoke a server-side MCP tool by name and return its `result` payload.\n   *\n   * @param toolName The MCP tool to call (e.g. `\"list_workloads\"`).\n   * @param args The tool arguments object, forwarded verbatim as JSON-RPC `params.arguments`.\n   * @returns The JSON-RPC `result` field on success, or `null` in dry-run mode.\n   * @throws Error on network failure, a non-JSON body, or a JSON-RPC `error`. A non-2xx response\n   *   throws an error whose `status` property holds the HTTP status code.\n   */\n  public async mcpCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {\n    const body = {\n      jsonrpc: '2.0',\n      id: 1,\n      method: 'tools/call',\n      params: { name: toolName, arguments: args },\n    };\n\n    if (this.dryRun) {\n      if (!this.silent) {\n        console.log(`🚫 DRY RUN: Skipping MCP call to ${this.apiEndpoint}`);\n        console.log(`🚫 DRY RUN: Would send:`, JSON.stringify(body, null, 2));\n      }\n      return null;\n    }\n\n    const text = await this.fetchOrThrow(\n      this.apiEndpoint,\n      {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          'X-API-Key': this.apiKey,\n        },\n        body: JSON.stringify(body),\n      },\n      'MCP request failed'\n    );\n\n    let json: McpToolCallResponse;\n    try {\n      json = JSON.parse(text) as McpToolCallResponse;\n    } catch {\n      throw new Error(`MCP response was not valid JSON: ${text.slice(0, 2000)}`);\n    }\n\n    if (json.error) {\n      throw new Error(`MCP error: ${json.error.message ?? JSON.stringify(json.error)}`);\n    }\n\n    // A tool can reject at the execution level (e.g. \"Cannot rename system workloads\") while the\n    // JSON-RPC envelope still reports success. The backend returns that as a `{ error }` result hash.\n    if (json.result !== null && typeof json.result === 'object' && !Array.isArray(json.result)) {\n      const result = json.result as { error?: unknown };\n      if (typeof result.error === 'string' && result.error.length > 0) {\n        throw new Error(result.error);\n      }\n    }\n\n    return json.result;\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,yBAA+C;AAexC,MAAM,mBAAmB,+BAAY;AAAA,EAC1C,YAAY,QAA0B;AACpC,UAAM,QAAQ,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAa,QAAQ,UAAkB,MAAiD;AACtF,UAAM,OAAO;AAAA,MACX,SAAS;AAAA,MACT,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ,EAAE,MAAM,UAAU,WAAW,KAAK;AAAA,IAC5C;AAEA,QAAI,KAAK,QAAQ;AACf,UAAI,CAAC,KAAK,QAAQ;AAChB,gBAAQ,IAAI,2CAAoC,KAAK,WAAW,EAAE;AAClE,gBAAQ,IAAI,kCAA2B,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,MACtE;AACA,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB,KAAK;AAAA,MACL;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B;AAAA,MACA;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,QAAQ;AACN,YAAM,IAAI,MAAM,oCAAoC,KAAK,MAAM,GAAG,GAAI,CAAC,EAAE;AAAA,IAC3E;AAEA,QAAI,KAAK,OAAO;AACd,YAAM,IAAI,MAAM,cAAc,KAAK,MAAM,WAAW,KAAK,UAAU,KAAK,KAAK,CAAC,EAAE;AAAA,IAClF;AAIA,QAAI,KAAK,WAAW,QAAQ,OAAO,KAAK,WAAW,YAAY,CAAC,MAAM,QAAQ,KAAK,MAAM,GAAG;AAC1F,YAAM,SAAS,KAAK;AACpB,UAAI,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,SAAS,GAAG;AAC/D,cAAM,IAAI,MAAM,OAAO,KAAK;AAAA,MAC9B;AAAA,IACF;AAEA,WAAO,KAAK;AAAA,EACd;AACF;","names":[]}