UNPKG

xero-mcp

Version:

A Model Context Protocol server allows Clients to interact with Xero

159 lines (158 loc) 7.03 kB
import { XeroClientSession } from "../../XeroApiClient.js"; import { XeroAccountingApiSchema } from "../../Resources/xero_accounting.js"; import { parseArrayValues } from "../../Utils/parseArrayValues.js"; import { convertToCamelCase } from "../../Utils/convertToCamelCase.js"; import { sanitizeObject } from "../../Utils/sanitizeValues.js"; export const ListInvoicesTool = { requestSchema: { name: "list_invoices", description: "Retrieves sales invoices or purchase bills", inputSchema: { type: "object", properties: { where: { type: "string", description: "Filter invoices by any element", example: 'Status=="DRAFT"', }, order: { type: "string", description: "Order by any element", example: "InvoiceNumber ASC", }, contactIDs: { type: "array", items: { type: "string" }, description: "Filter by a comma-separated list of ContactIDs", example: ["00000000-0000-0000-0000-000000000000"], }, invoiceNumbers: { type: "array", items: { type: "string" }, description: "Filter by a comma-separated list of InvoiceNumbers", example: ["INV-001", "INV-002"], }, statuses: { type: "array", items: { type: "string" }, description: "Filter by a comma-separated list of Statuses (DRAFT, SUBMITTED, AUTHORISED, PAID, VOIDED)", example: ["DRAFT", "SUBMITTED"], }, page: { type: "integer", description: "Up to 100 invoices will be returned per page with line items shown", example: 1, }, includeArchived: { type: "boolean", description: "Include invoices with a status of ARCHIVED", example: true, }, }, }, }, requestHandler: async (request) => { const where = request.params.arguments?.where; const order = request.params.arguments?.order; const contactIDs = request.params.arguments?.contactIDs; const invoiceNumbers = request.params.arguments?.invoiceNumbers; const statuses = request.params.arguments?.statuses; const page = request.params.arguments?.page; const includeArchived = request.params.arguments?.includeArchived; const response = await XeroClientSession.xeroClient.accountingApi.getInvoices(XeroClientSession.activeTenantId(), undefined, where, order, undefined, invoiceNumbers, contactIDs, statuses, page, includeArchived); const invoices = response.body.invoices || []; return { content: [ { type: "text", text: JSON.stringify(invoices), }, ], }; }, }; export const GetInvoiceTool = { requestSchema: { name: "get_invoice", description: "Retrieves a single sales invoice or purchase bill by its Xero invoice ID", inputSchema: { type: "object", properties: { invoiceID: { type: "string", description: "Xero-generated unique identifier for the invoice (UUID)", }, unitdp: { type: "number", description: "Optional. Unit decimal places (e.g. 4) for unit amounts on line items", }, }, required: ["invoiceID"], }, }, requestHandler: async (request) => { const invoiceID = request.params.arguments?.invoiceID; const unitdp = request.params.arguments?.unitdp; const response = await XeroClientSession.xeroClient.accountingApi.getInvoice(XeroClientSession.activeTenantId(), invoiceID, unitdp); return { content: [ { type: "text", text: JSON.stringify(response.body.invoices ?? []), }, ], }; }, }; export const UpdateInvoiceTool = { requestSchema: { name: "update_invoice", description: "Updates an existing sales invoice or purchase bill (typically a draft) to change fields like line items and account codes", inputSchema: { type: "object", properties: { invoiceID: { type: "string", description: "Xero generated unique identifier for the invoice (UUID)", }, invoices: { type: "object", description: "Invoices payload containing an array of invoice objects", properties: XeroAccountingApiSchema.components.schemas.Invoices.properties, example: '{ invoices: [{ type: "ACCREC", contact: { contactId: "00000000-0000-0000-0000-000000000000" }, date: "2026-01-01", dueDate: "2026-01-15", lineItems: [{ description: "Service", quantity: 1, unitAmount: 100, accountCode: "400", tracking: [] }], reference: "Website Design", status: "DRAFT" }]}', }, unitdp: { type: "number", description: "Optional. Unit decimal places (e.g. 4) for unit amounts on line items", }, idempotencyKey: { type: "string", description: "Optional idempotency key. Allows safe retries without duplicating processing", }, }, required: ["invoiceID", "invoices"], }, }, requestHandler: async (request) => { const rawInputData = request.params.arguments; const parsedData = parseArrayValues(rawInputData); const invoiceID = parsedData?.invoiceID; const unitdp = parsedData?.unitdp; const idempotencyKey = parsedData?.idempotencyKey; const rawInvoicesPayload = parsedData?.invoices; const invoicesPayload = sanitizeObject(convertToCamelCase(rawInvoicesPayload)); if (!invoiceID) { // Should be prevented by request schema, but keep a hard guard. throw new Error("Missing required parameter: invoiceID"); } const response = await XeroClientSession.xeroClient.accountingApi.updateInvoice(XeroClientSession.activeTenantId(), invoiceID, invoicesPayload, unitdp, idempotencyKey); return { content: [ { type: "text", text: JSON.stringify(response.body ?? response.response?.status), }, ], }; }, };