From 454733e2d0294325d8f9d5fd371f937a2d496dfc Mon Sep 17 00:00:00 2001 From: zgaetano Date: Tue, 31 Mar 2026 15:29:33 -0400 Subject: [PATCH] Add mcp-gateway/erpnext-mcp/src/tools/accounting.ts --- .../erpnext-mcp/src/tools/accounting.ts | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 mcp-gateway/erpnext-mcp/src/tools/accounting.ts diff --git a/mcp-gateway/erpnext-mcp/src/tools/accounting.ts b/mcp-gateway/erpnext-mcp/src/tools/accounting.ts new file mode 100644 index 0000000..b557c61 --- /dev/null +++ b/mcp-gateway/erpnext-mcp/src/tools/accounting.ts @@ -0,0 +1,234 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ERPNextClient, truncateResponse } from "../services/erpnext-client.js"; +import { z } from "zod"; + +export function registerAccountingTools(server: McpServer, client: ERPNextClient): void { + + // ─── 16. Get Account Balance ─────────────────────────── + server.registerTool( + "erpnext_get_account_balance", + { + title: "Get Account Balance", + description: `Get the balance of a specific GL account. + +Args: + - account: Account name + - company: Company name + - date: Optional date for balance as-of (YYYY-MM-DD)`, + inputSchema: z.object({ + account: z.string().describe("Account name"), + company: z.string().describe("Company name"), + date: z.string().optional().describe("Date for as-of balance (YYYY-MM-DD)"), + }).strict(), + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, + }, + async (params) => { + try { + const result = await client.callMethod("erpnext.accounts.utils.get_balance_on", { + account: params.account, + date: params.date, + company: params.company, + }); + return { content: [{ type: "text", text: `Balance of ${params.account}: ${result}` }] }; + } catch (error) { + return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; + } + } + ); + + // ─── 17. Get Exchange Rate ───────────────────────────── + server.registerTool( + "erpnext_get_exchange_rate", + { + title: "Get Exchange Rate", + description: `Get currency exchange rate between two currencies. + +Args: + - from_currency: Source currency code (e.g. "USD") + - to_currency: Target currency code (e.g. "EUR") + - date: Optional transaction date`, + inputSchema: z.object({ + from_currency: z.string().describe("Source currency (e.g. USD)"), + to_currency: z.string().describe("Target currency (e.g. EUR)"), + date: z.string().optional().describe("Transaction date (YYYY-MM-DD)"), + }).strict(), + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, + }, + async (params) => { + try { + const result = await client.callMethod("erpnext.setup.utils.get_exchange_rate", { + from_currency: params.from_currency, + to_currency: params.to_currency, + transaction_date: params.date, + }); + return { content: [{ type: "text", text: `Exchange rate ${params.from_currency} → ${params.to_currency}: ${result}` }] }; + } catch (error) { + return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; + } + } + ); + + // ─── 18. Create Journal Entry ────────────────────────── + server.registerTool( + "erpnext_create_journal_entry", + { + title: "Create Journal Entry", + description: `Create a Journal Entry with debit/credit lines. + +Args: + - company: Company name + - posting_date: Posting date (YYYY-MM-DD) + - accounts: Array of account line objects with {account, debit_in_account_currency, credit_in_account_currency, party_type?, party?, cost_center?} + - user_remark: Optional remark/narration + - voucher_type: Optional (default "Journal Entry"). Options: "Journal Entry", "Bank Entry", "Cash Entry", "Credit Card Entry", "Debit Note", "Credit Note"`, + inputSchema: z.object({ + company: z.string(), + posting_date: z.string(), + accounts: z.array(z.object({ + account: z.string(), + debit_in_account_currency: z.number().default(0), + credit_in_account_currency: z.number().default(0), + party_type: z.string().optional(), + party: z.string().optional(), + cost_center: z.string().optional(), + })), + user_remark: z.string().optional(), + voucher_type: z.string().default("Journal Entry"), + }).strict(), + annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, + }, + async (params) => { + try { + const doc = await client.createDocument("Journal Entry", { + company: params.company, + posting_date: params.posting_date, + voucher_type: params.voucher_type, + user_remark: params.user_remark, + accounts: params.accounts, + }); + return { content: [{ type: "text", text: `Created Journal Entry: ${doc.name}` }] }; + } catch (error) { + return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; + } + } + ); + + // ─── 19. Make Payment Entry ──────────────────────────── + server.registerTool( + "erpnext_make_payment_entry", + { + title: "Make Payment Entry from Invoice", + description: `Create a Payment Entry from a Sales or Purchase Invoice. + +Args: + - source_doctype: "Sales Invoice" or "Purchase Invoice" + - source_name: Invoice name/ID`, + inputSchema: z.object({ + source_doctype: z.enum(["Sales Invoice", "Purchase Invoice"]), + source_name: z.string(), + }).strict(), + annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, + }, + async (params) => { + try { + const method = params.source_doctype === "Sales Invoice" + ? "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry" + : "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; + const result = await client.callMethod(method, { + dt: params.source_doctype, + dn: params.source_name, + }); + return { content: [{ type: "text", text: `Payment Entry draft created:\n${JSON.stringify(result, null, 2)}` }] }; + } catch (error) { + return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; + } + } + ); + + // ─── 20. Get Outstanding Invoices ────────────────────── + server.registerTool( + "erpnext_get_outstanding_invoices", + { + title: "Get Outstanding Invoices", + description: `List outstanding (unpaid) invoices for a party. + +Args: + - party_type: "Customer" or "Supplier" + - party: Party name + - company: Company name`, + inputSchema: z.object({ + party_type: z.enum(["Customer", "Supplier"]), + party: z.string(), + company: z.string(), + }).strict(), + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, + }, + async (params) => { + try { + const result = await client.callMethod( + "erpnext.accounts.doctype.payment_entry.payment_entry.get_outstanding_reference_documents", + { + party_type: params.party_type, + party: params.party, + company: params.company, + } + ); + return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] }; + } catch (error) { + return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; + } + } + ); + + // ─── 21. Get Fiscal Year ─────────────────────────────── + server.registerTool( + "erpnext_get_fiscal_year", + { + title: "Get Fiscal Year", + description: `Get the fiscal year for a given date and company.`, + inputSchema: z.object({ + date: z.string().describe("Date (YYYY-MM-DD)"), + company: z.string().optional(), + }).strict(), + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, + }, + async (params) => { + try { + const result = await client.callMethod("erpnext.accounts.utils.get_fiscal_year", { + date: params.date, + company: params.company, + }); + return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + } catch (error) { + return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; + } + } + ); + + // ─── 22. Get Party Account Balance ───────────────────── + server.registerTool( + "erpnext_get_party_balance", + { + title: "Get Party Account Balance", + description: `Get the account balance for a Customer or Supplier.`, + inputSchema: z.object({ + party_type: z.enum(["Customer", "Supplier"]), + party: z.string(), + company: z.string(), + }).strict(), + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, + }, + async (params) => { + try { + const result = await client.callMethod("erpnext.accounts.utils.get_balance_on", { + party_type: params.party_type, + party: params.party, + company: params.company, + }); + return { content: [{ type: "text", text: `Balance for ${params.party_type} "${params.party}": ${result}` }] }; + } catch (error) { + return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; + } + } + ); +}