From 199fc3468483750ed201fff01ef2d2be9a1a8ab8 Mon Sep 17 00:00:00 2001 From: zgaetano Date: Tue, 31 Mar 2026 15:33:18 -0400 Subject: [PATCH] Remove mcp-gateway/wave-mcp/src/tools/accounting.ts --- mcp-gateway/wave-mcp/src/tools/accounting.ts | 406 ------------------- 1 file changed, 406 deletions(-) delete mode 100644 mcp-gateway/wave-mcp/src/tools/accounting.ts diff --git a/mcp-gateway/wave-mcp/src/tools/accounting.ts b/mcp-gateway/wave-mcp/src/tools/accounting.ts deleted file mode 100644 index 5a7921e..0000000 --- a/mcp-gateway/wave-mcp/src/tools/accounting.ts +++ /dev/null @@ -1,406 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { z } from "zod"; -import { WaveClient } from "../services/wave-client.js"; -import { WaveAccount, WaveTransaction } from "../types.js"; -import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from "../constants.js"; - -interface AccountsData { - business: { - accounts: { - pageInfo: { currentPage: number; totalPages: number; totalCount: number }; - edges: Array<{ node: WaveAccount }>; - }; - }; -} - -interface TransactionCreateData { - moneyTransactionCreate: { - didSucceed: boolean; - inputErrors: Array<{ code: string; message: string; path: string[] }>; - transaction?: WaveTransaction; - }; -} - -interface UserData { - user: { id: string; defaultEmail: string }; -} - -const ACCOUNT_FIELDS = ` - id name description displayId - type { name value normalBalanceType } - subtype { name value } - currency { code } - isArchived sequence normalBalanceType -`; - -function formatAccountMarkdown(a: WaveAccount): string[] { - const lines = [ - `## ${a.name} (${a.displayId})`, - `- **ID**: ${a.id}`, - `- **Type**: ${a.type.name} / ${a.subtype.name}`, - `- **Currency**: ${a.currency.code}`, - ]; - if (a.isArchived) lines.push("- **Status**: Archived"); - if (a.description) lines.push(`- **Description**: ${a.description}`); - lines.push(""); - return lines; -} - -export function registerAccountingTools(server: McpServer, client: WaveClient): void { - // ── wave_get_user ───────────────────────────────────────────────────────── - - server.registerTool( - "wave_get_user", - { - title: "Get Wave User", - description: `Get the authenticated Wave user's ID and email. Useful for verifying token validity and identifying the logged-in user. - -Args: - - response_format ('markdown' | 'json'): Output format (default: 'markdown') - -Returns: - User ID and default email address.`, - inputSchema: z.object({ - response_format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"), - }), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async ({ response_format }) => { - try { - const data = await client.query(`query { user { id defaultEmail } }`); - const user = data.user; - - let text: string; - if (response_format === "json") { - text = JSON.stringify(user, null, 2); - } else { - text = `# Wave User\n\n- **ID**: ${user.id}\n- **Email**: ${user.defaultEmail}`; - } - - return { content: [{ type: "text", text }], structuredContent: user }; - } catch (error) { - return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] }; - } - } - ); - - // ── wave_list_accounts ──────────────────────────────────────────────────── - - server.registerTool( - "wave_list_accounts", - { - title: "List Wave Accounts", - description: `List chart of accounts for a Wave business, optionally filtered by type. - -Args: - - businessId (string): Wave business ID - - page (number): Page number (default: 1) - - pageSize (number): Items per page, 1-100 (default: 20) - - type ('ASSET' | 'LIABILITY' | 'EQUITY' | 'INCOME' | 'EXPENSE'): Filter by account type (optional) - - includeArchived (boolean): Include archived accounts (default: false) - - response_format ('markdown' | 'json'): Output format (default: 'markdown') - -Returns: - Paginated chart of accounts with type, subtype, currency, and display ID.`, - inputSchema: z.object({ - businessId: z.string().describe("Wave business ID"), - page: z.number().int().min(1).default(1).describe("Page number"), - pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Items per page"), - type: z.enum(["ASSET", "LIABILITY", "EQUITY", "INCOME", "EXPENSE"]).optional().describe("Filter by account type"), - includeArchived: z.boolean().default(false).describe("Include archived accounts"), - response_format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"), - }), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async ({ businessId, page, pageSize, type, includeArchived, response_format }) => { - try { - const data = await client.query(` - query($businessId: ID!, $page: Int!, $pageSize: Int!) { - business(id: $businessId) { - accounts(page: $page, pageSize: $pageSize) { - pageInfo { currentPage totalPages totalCount } - edges { node { ${ACCOUNT_FIELDS} } } - } - } - } - `, { businessId, page, pageSize }); - - const { pageInfo, edges } = data.business.accounts; - let accounts = edges.map((e) => e.node); - - // Client-side filtering - if (type) accounts = accounts.filter((a) => a.type.value === type); - if (!includeArchived) accounts = accounts.filter((a) => !a.isArchived); - - if (!accounts.length) { - return { content: [{ type: "text", text: "No accounts found matching the criteria." }] }; - } - - const result = { - total: pageInfo.totalCount, - count: accounts.length, - page: pageInfo.currentPage, - totalPages: pageInfo.totalPages, - has_more: pageInfo.currentPage < pageInfo.totalPages, - next_page: pageInfo.currentPage < pageInfo.totalPages ? pageInfo.currentPage + 1 : undefined, - accounts, - }; - - let text: string; - if (response_format === "json") { - text = JSON.stringify(result, null, 2); - } else { - const lines = [`# Chart of Accounts (${pageInfo.totalCount} total, page ${pageInfo.currentPage}/${pageInfo.totalPages})`, ""]; - // Group by type - const grouped: Record = {}; - for (const a of accounts) { - const t = a.type.name; - if (!grouped[t]) grouped[t] = []; - grouped[t].push(a); - } - for (const [typeName, typeAccounts] of Object.entries(grouped)) { - lines.push(`### ${typeName}`, ""); - for (const a of typeAccounts) lines.push(...formatAccountMarkdown(a)); - } - text = lines.join("\n"); - } - - return { - content: [{ type: "text", text: WaveClient.truncate(text) }], - structuredContent: result, - }; - } catch (error) { - return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] }; - } - } - ); - - // ── wave_create_transaction ─────────────────────────────────────────────── - - server.registerTool( - "wave_create_transaction", - { - title: "Create Wave Transaction", - description: `Create a money transaction (deposit or withdrawal) in Wave accounting. - -A transaction requires: -1. An anchor account (typically a bank or credit card account) with a direction -2. One or more line items that categorize the transaction - -The anchor is the primary account (e.g., your bank account). Line items describe what the money was for (e.g., an expense account). - -Direction values: - - For anchor: 'DEPOSIT' (money in) or 'WITHDRAWAL' (money out) - - For line items: 'INCREASE' or 'DECREASE' relative to the account's normal balance - -Args: - - businessId (string): Wave business ID - - description (string): Transaction description/memo - - anchorAccountId (string): Bank or credit card account ID - - anchorAmount (number): Transaction amount (positive) - - anchorDirection ('DEPOSIT' | 'WITHDRAWAL'): Money in or out of anchor account - - anchorDate (string): Date in YYYY-MM-DD format - - lineItems (array): Categorization items - - accountId (string): Account to categorize against - - amount (number): Amount for this line item - - balance ('INCREASE' | 'DECREASE'): Effect on the line item account - - description (string): Line item description (optional) - - response_format ('markdown' | 'json'): Output format (default: 'markdown') - -Returns: - Created transaction details or validation errors.`, - inputSchema: z.object({ - businessId: z.string().describe("Wave business ID"), - description: z.string().optional().describe("Transaction description/memo"), - anchorAccountId: z.string().describe("Bank or credit card account ID"), - anchorAmount: z.number().positive().describe("Transaction amount (positive number)"), - anchorDirection: z.enum(["DEPOSIT", "WITHDRAWAL"]).describe("DEPOSIT = money in, WITHDRAWAL = money out"), - anchorDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Transaction date (YYYY-MM-DD)"), - lineItems: z.array(z.object({ - accountId: z.string().describe("Account ID for categorization"), - amount: z.number().positive().describe("Amount for this line item"), - balance: z.enum(["INCREASE", "DECREASE"]).describe("Effect on this account's balance"), - description: z.string().optional().describe("Line item description"), - })).min(1).describe("Line items categorizing the transaction"), - response_format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"), - }), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, - }, - async ({ businessId, description, anchorAccountId, anchorAmount, anchorDirection, anchorDate, lineItems, response_format }) => { - try { - const input: Record = { - businessId, - externalId: `mcp-${Date.now()}`, - date: anchorDate, - income: anchorDirection === "DEPOSIT" ? { - accountId: anchorAccountId, - amount: anchorAmount, - description, - } : undefined, - expense: anchorDirection === "WITHDRAWAL" ? { - accountId: anchorAccountId, - amount: anchorAmount, - description, - } : undefined, - lineItems: lineItems.map((li) => ({ - accountId: li.accountId, - amount: li.amount, - balance: li.balance, - description: li.description, - })), - }; - - // Remove undefined keys - if (!input.income) delete input.income; - if (!input.expense) delete input.expense; - - const data = await client.query(` - mutation($input: MoneyTransactionCreateInput!) { - moneyTransactionCreate(input: $input) { - didSucceed - inputErrors { code message path } - transaction { - id description notes - createdAt modifiedAt - } - } - } - `, { input }); - - const result = data.moneyTransactionCreate; - if (!result.didSucceed) { - const errs = result.inputErrors.map((e) => ` - ${e.path?.join(".") ?? "field"}: ${e.message}`).join("\n"); - return { content: [{ type: "text", text: `Failed to create transaction:\n${errs}` }] }; - } - - let text: string; - if (response_format === "json") { - text = JSON.stringify(result.transaction, null, 2); - } else { - text = [ - `Transaction created successfully!`, - `- **ID**: ${result.transaction?.id}`, - `- **Description**: ${result.transaction?.description ?? description ?? "N/A"}`, - `- **Amount**: ${WaveClient.formatCurrency(anchorAmount, "")} (${anchorDirection})`, - `- **Date**: ${anchorDate}`, - ].join("\n"); - } - - return { content: [{ type: "text", text }], structuredContent: result.transaction }; - } catch (error) { - return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] }; - } - } - ); - - // ── wave_list_currencies ────────────────────────────────────────────────── - - server.registerTool( - "wave_list_currencies", - { - title: "List Wave Currencies", - description: `List all currencies supported by Wave (ISO 4217). Useful for finding valid currency codes to use when creating customers or invoices. - -Args: - - response_format ('markdown' | 'json'): Output format (default: 'markdown') - -Returns: - List of all supported currencies with code, symbol, and name.`, - inputSchema: z.object({ - response_format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"), - }), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async ({ response_format }) => { - try { - const data = await client.query<{ - currencies: Array<{ code: string; symbol: string; name: string; plural: string }>; - }>(` - query { - currencies { - code symbol name plural - } - } - `); - - const currencies = data.currencies; - - let text: string; - if (response_format === "json") { - text = JSON.stringify({ count: currencies.length, currencies }, null, 2); - } else { - const lines = [`# Supported Currencies (${currencies.length})`, ""]; - for (const c of currencies) { - lines.push(`- **${c.code}** ${c.symbol} — ${c.name}`); - } - text = lines.join("\n"); - } - - return { - content: [{ type: "text", text: WaveClient.truncate(text) }], - structuredContent: { count: currencies.length, currencies }, - }; - } catch (error) { - return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] }; - } - } - ); - - // ── wave_list_countries ─────────────────────────────────────────────────── - - server.registerTool( - "wave_list_countries", - { - title: "List Wave Countries", - description: `List all countries and their provinces supported by Wave. Use this to find valid country and province codes for addresses. - -Args: - - response_format ('markdown' | 'json'): Output format (default: 'markdown') - -Returns: - List of countries with ISO codes and their provinces/states.`, - inputSchema: z.object({ - response_format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"), - }), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async ({ response_format }) => { - try { - const data = await client.query<{ - countries: Array<{ - code: string; - name: string; - provinces: Array<{ code: string; name: string }>; - }>; - }>(` - query { - countries { - code name - provinces { code name } - } - } - `); - - const countries = data.countries; - - let text: string; - if (response_format === "json") { - text = JSON.stringify({ count: countries.length, countries }, null, 2); - } else { - const lines = [`# Supported Countries (${countries.length})`, ""]; - for (const c of countries) { - lines.push(`- **${c.code}** — ${c.name} (${c.provinces.length} provinces/states)`); - } - text = lines.join("\n"); - } - - return { - content: [{ type: "text", text: WaveClient.truncate(text) }], - structuredContent: { count: countries.length, countries }, - }; - } catch (error) { - return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] }; - } - } - ); -}