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}` }] }; } } ); }