diff --git a/mcp-gateway/wave-mcp/src/services/wave-client.ts b/mcp-gateway/wave-mcp/src/services/wave-client.ts new file mode 100644 index 0000000..0e4eca2 --- /dev/null +++ b/mcp-gateway/wave-mcp/src/services/wave-client.ts @@ -0,0 +1,62 @@ +import { WaveConfig, GraphQLResponse } from "../types.js"; +import { WAVE_GRAPHQL_URL, CHARACTER_LIMIT } from "../constants.js"; + +export class WaveClient { + private accessToken: string; + + constructor(config: WaveConfig) { + this.accessToken = config.accessToken; + } + + // ── Core GraphQL executor ────────────────────────────────────────────────── + + async query( + query: string, + variables?: Record + ): Promise { + const response = await fetch(WAVE_GRAPHQL_URL, { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ query, variables: variables ?? {} }), + }); + + if (!response.ok) { + const text = await response.text().catch(() => "(no body)"); + throw new Error( + `Wave API HTTP error ${response.status} ${response.statusText}: ${text.slice(0, 300)}` + ); + } + + const json = (await response.json()) as GraphQLResponse; + + if (json.errors?.length) { + const messages = json.errors.map((e) => e.message).join("; "); + throw new Error(`Wave GraphQL error: ${messages}`); + } + + if (json.data === undefined) { + throw new Error("Wave API returned no data and no errors."); + } + + return json.data; + } + + // ── Formatting helpers ───────────────────────────────────────────────────── + + /** Truncate a JSON response string if it exceeds CHARACTER_LIMIT */ + static truncate(text: string): string { + if (text.length <= CHARACTER_LIMIT) return text; + return ( + text.slice(0, CHARACTER_LIMIT) + + `\n\n...[TRUNCATED: response exceeded ${CHARACTER_LIMIT} characters. Use pagination (page, pageSize) to retrieve more data.]` + ); + } + + static formatCurrency(value: number, code: string): string { + return `${code} ${value.toFixed(2)}`; + } +}