From 6ac85284c2a60d8d55bdb940d1fc7e644812c163 Mon Sep 17 00:00:00 2001 From: zgaetano Date: Tue, 31 Mar 2026 15:33:48 -0400 Subject: [PATCH] Add wave-mcp/src/index.ts --- wave-mcp/src/index.ts | 143 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 wave-mcp/src/index.ts diff --git a/wave-mcp/src/index.ts b/wave-mcp/src/index.ts new file mode 100644 index 0000000..5708ab5 --- /dev/null +++ b/wave-mcp/src/index.ts @@ -0,0 +1,143 @@ +#!/usr/bin/env node +/** + * Wave Finance MCP Server + * + * Provides tools for interacting with the Wave Finance GraphQL API, + * including businesses, customers, products, invoices, and accounting. + * + * Environment Variables: + * WAVE_ACCESS_TOKEN - Wave OAuth2 access token (required) + * TRANSPORT - 'http' or 'stdio' (default: 'stdio') + * PORT - HTTP port when TRANSPORT=http (default: 8300) + */ + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import express from "express"; + +import { WaveClient } from "./services/wave-client.js"; +import { registerBusinessTools } from "./tools/businesses.js"; +import { registerCustomerTools } from "./tools/customers.js"; +import { registerProductTools } from "./tools/products.js"; +import { registerInvoiceTools } from "./tools/invoices.js"; +import { registerAccountingTools } from "./tools/accounting.js"; + +// ── Configuration ───────────────────────────────────────────────────────────── + +function getConfig(): { accessToken: string } { + const accessToken = process.env.WAVE_ACCESS_TOKEN ?? ""; + + if (!accessToken) { + console.error( + "Missing required environment variable:\n" + + " WAVE_ACCESS_TOKEN - Wave OAuth2 access token\n\n" + + "To obtain a token, see the Wave Developer Portal:\n" + + " https://developer.waveapps.com" + ); + process.exit(1); + } + + return { accessToken }; +} + +// ── Server Setup ────────────────────────────────────────────────────────────── + +function createServer(): { server: McpServer; waveClient: WaveClient } { + const config = getConfig(); + const waveClient = new WaveClient(config); + + const server = new McpServer({ + name: "wave-mcp-server", + version: "1.0.0", + }); + + // Register tool groups + registerAccountingTools(server, waveClient); // user, accounts, transactions, currencies, countries + registerBusinessTools(server, waveClient); // list_businesses, get_business + registerCustomerTools(server, waveClient); // list, get, create, update, delete customers + registerProductTools(server, waveClient); // list, create, update, archive products + registerInvoiceTools(server, waveClient); // list, get, create, approve, send, delete invoices + + console.error("Wave MCP Server initialized with tools:"); + console.error(" Accounting: wave_get_user, wave_list_accounts, wave_create_transaction, wave_list_currencies, wave_list_countries"); + console.error(" Businesses: wave_list_businesses, wave_get_business"); + console.error(" Customers: wave_list_customers, wave_get_customer, wave_create_customer, wave_update_customer, wave_delete_customer"); + console.error(" Products: wave_list_products, wave_create_product, wave_update_product, wave_archive_product"); + console.error(" Invoices: wave_list_invoices, wave_get_invoice, wave_create_invoice, wave_approve_invoice, wave_send_invoice, wave_delete_invoice"); + + return { server, waveClient }; +} + +// ── Transport: stdio ────────────────────────────────────────────────────────── + +async function runStdio(): Promise { + const { server } = createServer(); + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("Wave MCP Server running via stdio"); +} + +// ── Transport: HTTP ─────────────────────────────────────────────────────────── + +async function runHTTP(): Promise { + const { server } = createServer(); + const app = express(); + app.use(express.json()); + + // Health check + app.get("/health", (_req, res) => { + res.json({ status: "ok", server: "wave-mcp-server", version: "1.0.0" }); + }); + + // MCP endpoint + app.post("/mcp", async (req, res) => { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + res.on("close", () => transport.close()); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + }); + + app.get("/mcp", async (_req, res) => { + res.writeHead(405).end( + JSON.stringify({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed. Use POST for MCP requests." }, + id: null, + }) + ); + }); + + app.delete("/mcp", async (_req, res) => { + res.writeHead(405).end( + JSON.stringify({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed." }, + id: null, + }) + ); + }); + + const port = parseInt(process.env.PORT ?? "8300", 10); + app.listen(port, () => { + console.error(`Wave MCP Server running on http://0.0.0.0:${port}/mcp`); + }); +} + +// ── Entrypoint ──────────────────────────────────────────────────────────────── + +const transport = process.env.TRANSPORT ?? "stdio"; +if (transport === "http") { + runHTTP().catch((error) => { + console.error("Server error:", error); + process.exit(1); + }); +} else { + runStdio().catch((error) => { + console.error("Server error:", error); + process.exit(1); + }); +}