/** * Cliente HTTP mínimo para o gateway MCP (JSON-RPC 2.0 sobre HTTP). * * Suporta resposta em JSON puro ou SSE (text/event-stream). Partilhado entre * os scripts de Observabilidade (patterns + worklog import). */ export interface MCPToolCallResult { content?: Array<{ type: string; text: string }> isError?: boolean } export async function callMcpTool( tool: string, args: Record, ): Promise { const url = process.env.MCP_GATEWAY_URL ?? 'https://gateway.descomplicar.pt/v1/desk-crm/mcp' const token = process.env.MCP_GATEWAY_TOKEN if (!token) throw new Error('MCP_GATEWAY_TOKEN não definido') const body = { jsonrpc: '2.0', id: Date.now(), method: 'tools/call', params: { name: tool, arguments: args }, } const res = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream', }, body: JSON.stringify(body), }) if (!res.ok) { const txt = await res.text().catch(() => '') throw new Error(`MCP gateway ${res.status}: ${txt.slice(0, 300)}`) } const raw = await res.text() let payload: string | null = null for (const line of raw.split(/\r?\n/)) { const trimmed = line.trim() if (!trimmed) continue if (trimmed.startsWith('data: ')) { payload = trimmed.slice(6) break } if (trimmed.startsWith('{')) { payload = trimmed break } } if (!payload) throw new Error(`MCP resposta sem payload JSON: ${raw.slice(0, 200)}`) const parsed = JSON.parse(payload) as { error?: unknown; result?: MCPToolCallResult } if (parsed.error) throw new Error(`MCP error: ${JSON.stringify(parsed.error)}`) const result = parsed.result as MCPToolCallResult | undefined if (result?.isError) { const txt = result.content?.map((c) => c.text).join('\n') ?? '' throw new Error(`MCP tool ${tool} devolveu isError: ${txt.slice(0, 300)}`) } return result ?? {} } /** Extrai o primeiro bloco de texto JSON-encoded do resultado MCP. */ export function extractMcpJsonPayload(r: MCPToolCallResult): T { const text = r.content?.find((c) => c.type === 'text')?.text if (!text) throw new Error('MCP result sem content text') return JSON.parse(text) as T }