mcp-servers/erpnext-mcp/src/tools/accounting.ts

234 lines
9.3 KiB
TypeScript

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