Skip to main content
Glama
david-buck

xero-mcp

by david-buck

xero-mcp

A local Xero MCP server for Claude Desktop and other STDIO MCP clients. It reads accounting records and reports, guides evidence-based reviews, and creates or edits drafts when explicitly requested.

Version 0.4.0 adds 16 tools to the previous eight-tool release. See CHANGELOG.md.

What it can change

  • xero_create_draft_invoice creates an ACCREC sales invoice with Status: DRAFT.

  • xero_update_draft_invoice edits reference, dates or lines on an existing DRAFT invoice after checking its revision. It also accepts draft supplier bills (ACCPAY); it does not restrict updates to sales invoices.

  • There are no tools to create, edit, merge or clean up contacts; approve, send, email, pay, void or delete invoices; create payments, credit notes or journals; reconcile bank items; or submit a GST return.

The new review tools make only accounting-data GET requests. Token refresh separately uses Xero's OAuth endpoint. Draft-only restrictions are enforced by this code: the accounting.invoices OAuth scope itself grants broader invoice access. Review authorisation retains that scope, and this release has no read-only mode that removes the two draft tools. Review guidance asks the client to keep investigation separate from explicitly requested writes.

Draft updates reject stale revisions and overlapping updates within one server process. They cannot lock external Xero edits during a request. Local draft creation has no durable billing-event idempotency ledger: after a timeout, check Xero before creating another draft.

Related MCP server: Xero MCP Server

Where it runs

The supported setup runs on your Mac using Node and a local STDIO MCP client. Claude Desktop starts the process on your computer. It is not an HTTPS connector, and cloning it does not make it available to Claude web/mobile or cloud routines. Hosted OAuth/storage work is not part of this supported release. See Claude's local-versus-remote connector requirements.

Tokens are stored locally as plaintext at ~/.xero-mcp/tokens.json, with directory mode 0700 and file mode 0600. Xero API requests go to Xero; returned records are passed to your MCP client/model. Use a private OS account and keep credentials, token files and exported financial data out of chat, Git and public issues. Each Mac user has one token-store location; multiple clones under the same OS user share it. Run one client process at a time to avoid competing refreshes.

Setup for a new user

You need macOS, Git, Node 24 LTS recommended (minimum Node 20.6 for --env-file), Claude Desktop or another STDIO client, and access to your own Xero organisation. Your Xero role must permit the requested records/reports; saved scopes alone do not establish that permission.

1. Create your own Xero developer app

  1. Open Xero Developer → My Apps.

  2. For the existing personal-script flow, create a Web app using Standard auth code.

  3. Register exactly http://localhost:3000/callback, then copy its Client ID and generate its Client Secret.

Use your own app credentials, not the repo author's .env, client secret or saved tokens. The current code requires a secret. It does not implement PKCE or a shared-app onboarding service. Xero requires PKCE for distributed native desktop apps; a future shared desktop integration should use that flow instead of distributing a confidential app secret. See Xero's OAuth guidance.

2. Install and configure privately

git clone https://github.com/david-buck/xero-mcp.git
cd xero-mcp
npm ci
cp .env.example .env
chmod 600 .env

Edit .env locally, replacing XERO_CLIENT_ID and XERO_CLIENT_SECRET with values from your app. Keep XERO_REDIRECT_URI=http://localhost:3000/callback. Do not paste credentials into Claude. These steps describe setup; no credential or grant is supplied by this repository.

XERO_TENANT_ID selects the exact organisation. If one organisation is connected, auth uses that sole connection and prints its name. If several are connected, auth refuses to choose and lists their names/tenant IDs in your terminal. Set XERO_TENANT_ID to your chosen ID in .env and run auth again. A configured ID that is not authorized is rejected. Confirm the resulting organisation before requesting records.

3. Authorize your own Xero connection

For the expanded accounting-review tools, run:

npm run auth:review

For just the original contact/invoice operations, use npm run auth. The listener binds to localhost:3000, opens Xero in your browser and waits up to ten minutes. Sign in as yourself, select only the intended organisation, review the requested access and approve it. The callback checks a random state before exchanging the code. The server then validates the chosen tenant and saves your tokens locally. It refreshes them automatically until revoked or renewal fails.

The base grant requests:

offline_access accounting.contacts.read accounting.invoices accounting.settings.read

auth:review additionally requests accounting.banktransactions.read, accounting.payments.read, accounting.manualjournals.read, accounting.reports.profitandloss.read, accounting.reports.balancesheet.read, accounting.reports.trialbalance.read and accounting.reports.taxreports.read. Re-run review auth for these scopes; changing the app configuration alone does not update an existing grant. This grant still permits the existing draft tools.

To disconnect, revoke this app's access in Xero and quit the MCP client. Removing the local token file alone does not revoke a grant. To switch organisations, set the intended tenant and re-authorize; the current token store supports one selected tenant at a time.

4. Connect Claude Desktop

In Claude Desktop, open Settings → Developer → Edit Config. On macOS the file is ~/Library/Application Support/Claude/claude_desktop_config.json. Merge this entry into existing mcpServers without removing other servers:

{
  "mcpServers": {
    "xero": {
      "command": "/ABSOLUTE/PATH/TO/node",
      "args": [
        "--env-file=/ABSOLUTE/PATH/TO/xero-mcp/.env",
        "/ABSOLUTE/PATH/TO/xero-mcp/src/index.js"
      ]
    }
  }
}

Use command -v node to find your Node executable and pwd inside the clone for the repo path; substitute both absolute paths. Fully quit and reopen Claude Desktop, then check the Xero tools in the connectors list. See the local MCP setup guide.

For the first read, ask: “Use xero_review_context to show which organisation, currency and financial year this connection uses. Do not create or edit anything.” Confirm it is yours. The response shows scope names/gaps, never credentials. For a review, give exact dates and use xero_review_guide; for example: “Compare profit for 2024-04-01 to 2025-03-31 and 2025-04-01 to 2026-03-31 on the same basis. Keep this read-only and show evidence and gaps.”

Claude can walk you through this README. A useful setup request is: “Help me install this local Xero MCP on my Mac, create my own Xero app, enter secrets privately, select the right tenant and configure Claude Desktop. Explain each step, keep credentials out of chat, and verify the organisation with a read only.” You personally complete Xero sign-in and consent.

Other clients and troubleshooting

For Codex, merge this into ~/.codex/config.toml, replacing the absolute paths, then restart:

[mcp_servers.xero]
command = "/ABSOLUTE/PATH/TO/node"
args = ["--env-file=/ABSOLUTE/PATH/TO/xero-mcp/.env", "/ABSOLUTE/PATH/TO/xero-mcp/src/index.js"]

If Claude cannot start the server, check the absolute Node/repo paths, JSON syntax and node --version. If Xero consent fails, check the exact registered callback and whether port 3000 is already occupied. For missing review access, run npm run auth:review again and check the user's Xero role. Do not paste .env, token files or unredacted financial responses into troubleshooting conversations.

Tools

Version 0.4.0 exposes 24 tools: 22 read/context/guidance tools and the two draft-invoice write tools, plus the xero_review MCP prompt.

Tool

What it does

xero_find_contact

Search contacts by name or email and return their Xero Contact ID.

xero_get_contact_defaults

Get a contact's default sales account code, tax type, currency, and line-amount preference.

xero_list_revenue_accounts

List active revenue/sales account codes that can be used on invoice lines.

xero_list_revenue_tax_rates

List active revenue tax types with their names, codes, and combined labels.

xero_list_invoices

List newest invoices by contact or number; optionally include full line items.

xero_list_period_invoices

Review invoices and supplier bills by invoice date, with optional type, status, and contact filters.

xero_list_accounts

Identify current and archived account codes behind report figures.

xero_list_credit_notes / xero_get_credit_note

Review customer and supplier credit notes, lines, allocations, and payments.

xero_get_invoice

Fetch an invoice including its line items.

xero_create_draft_invoice

Make a sales invoice with Status: DRAFT.

xero_update_draft_invoice

Patch an existing draft invoice or supplier bill with a checked revision and actual before/after summary.

xero_list_bank_transactions / xero_get_bank_transaction

Page through dated spend/receive transactions and inspect line details and reconciliation flags.

xero_list_payments

Page through dated payments with invoice and account identifiers; optionally filter by invoice ID.

xero_list_manual_journals / xero_get_manual_journal

Page through dated manual journals and inspect journal lines.

xero_get_profit_and_loss / xero_get_balance_sheet / xero_get_trial_balance

Read fixed reports for a date range or as-at date.

xero_list_gst_reports / xero_get_gst_report

Find saved GST returns overlapping a date range, then inspect one return's fields by its verified report ID.

xero_review_context

Check organisation currency, financial-year settings, and saved grant scope names without exposing tokens.

xero_review_guide

Get a no-API-call workflow for year comparison, tracing a figure, debtors/creditors, or GST.

The date-bounded transaction and credit-note tools require fromDate and toDate (YYYY-MM-DD), and each request covers at most 366 calendar days. They return one page at a time (page starts at 1; pageSize defaults to 100 and cannot exceed 100 where supported), together with returned, any Xero pagination metadata, and nextPage when another page may exist. A full page may need one more call to establish that the list is complete. xero_list_period_invoices uses Xero's fixed 100-item invoice pages. Use the individual-record tools when a list response lacks lines. The new Xero review reads make only GET data requests; OAuth token refresh may separately call Xero's token endpoint.

Profit and Loss takes fromDate and toDate; Balance Sheet and Trial Balance take an as-at date. All three expose paymentsOnly (default false for requested accrual basis; true requests cash-only basis) and return the requested basis alongside Xero's report. Use the same basis on both sides of a comparison. GST report listing takes fromDate and toDate, and the detail tool takes a report ID returned by that listing. The GST date range selects saved returns whose periods overlap it; it does not recalculate an arbitrary period. Financial report tools preserve Xero's nested rows and cells; GST returns preserve their fields and values. The Reports API response does not establish whether a GST return was filed or submitted, so verify that status in Xero.

This server does not expose the full /Journals API. Xero gates it behind its Advanced developer tier, certification and use-case approval, plus Reports access for the authorising user. Manual journals are a separate endpoint and do not replace full general-ledger journals. The bank-transaction endpoint is not a bank statement feed, and a payment's account and reconciliation flag do not by themselves prove which imported statement line it matches.

What a year-end review can answer

Start with xero_review_context to confirm the connected organisation, currency, and financial-year settings. Then call xero_review_guide with topic: "year_comparison", "trace_figure", "debtors_creditors", or "gst". The server also offers an xero_review MCP prompt for clients that surface prompts; the guide tool works without prompt support. Both give the client the question-shaped workflow and evidence limits, not an automatic answer or Xero write.

For a financial-year comparison, read Profit and Loss for both periods on the same basis, then Balance Sheet at each period end. Use xero_list_accounts to decode report account codes; xero_list_period_invoices finds sales invoices (ACCREC) or supplier bills (ACCPAY) in a chosen period, and xero_list_credit_notes finds related reversals. Their fromDate and toDate are document dates, not payment dates. Open material records for lines and allocations. Check payments separately by payment date, then inspect bank transactions and manual journals for other activity. Follow every page before calling a population complete.

An invoice or credit note dated in an old financial year is still returned with its current status and remaining balance. That is not a snapshot of what was owed at that year-end. Use dated reports for as-at balances; this MCP does not yet provide a historical aged debtors/creditors report. Its saved scope list is diagnostic, not proof that the authorising user can read every endpoint.

This is a way to investigate what changed, not an automatic reconciliation or a tax opinion. Report rows do not always expose the underlying record IDs. Manual journals cover only manual entries; the unavailable full Journals API would be needed for a complete journal-level trail. Bank transactions are not raw imported bank statement lines, and the GST reports API gives figures without proving filing status. Bank transfers, payroll, fixed assets, and year-end adjustments may also affect figures without being fully explained by these tools. Treat any mismatch or missing source as a question to verify in Xero or put to an accountant, rather than letting the MCP guess.

To retrieve older invoices, call xero_list_invoices with { "page": 2, "limit": 20 }. Increment page until fewer than limit records return; an exact multiple may require a final empty page. Each call fetches one page and returns an array. Defaults remain page 1 and limit 20 (maximum 100).

For an exact number, use { "invoiceNumber": "INV-0123" }; surrounding whitespace is trimmed, and numbers containing commas are unsupported. You can combine invoiceNumber, contactId, and pagination. Pass a returned invoiceId to xero_get_invoice to fetch its full details.

Monetary invoice lines require a description, quantity, unit amount, and account code, with optional tax type and either discountRate (0–100%) or discountAmount (a nonnegative fixed amount). Set a discount explicitly to 0 to remove it; do not provide both forms. Description-only lines contain just description (for example, { "description": "Project notes" }) and optionally an existing lineItemId; they cannot contain quantity, unit amount, account, item, tax, or discount fields. Monetary lines can also carry an existing Xero item code and, during updates, a line-item ID.

Unit prices support at most four decimal places; more precise values are rejected without rounding.

Dates use YYYY-MM-DD. New drafts accept lineAmountTypes: Exclusive, Inclusive, or NoTax, defaulting to Exclusive. Contact preferences are not applied automatically. Full invoice responses include the tax basis returned by Xero as lineAmountTypes; updates preserve the existing basis by omitting it from the request. Inputs are schema-validated, and Xero API errors are returned intact instead of being papered over.

New drafts also accept optional currencyCode, for example "USD"; surrounding whitespace is trimmed and letters are uppercased. Omitting it leaves Xero's organisation base-currency default. The currency must be enabled in the organisation; Xero validates this. Unit amounts are denominated in the chosen currency and this server does not convert them or set an exchange rate. You may use contact defaults to choose an explicit code; they are not applied automatically. Existing invoice currency cannot be changed through this server.

For a narrow edit, first call xero_get_invoice, then pass its revision as expectedRevision to xero_update_draft_invoice. Use lineItemPatches with an existing line ID and only the fields you intend to change:

{
  "invoiceId": "<invoiceId from the read>",
  "expectedRevision": "<revision from the read>",
  "lineItemPatches": [
    { "lineItemId": "<LineItemID from the read>", "taxType": "NONE" }
  ]
}

Patches preserve omitted quantities, unit prices, discounts, item codes, tracking, and untouched lines. Changing tax or pricing fields omits the edited line's old TaxAmount and LineAmount so Xero recalculates them. Description-only edits preserve existing amounts and tax overrides. Missing/duplicate line targets, empty patches, unknown patch fields, and mixing patches with complete replacement are rejected.

Every update requires expectedRevision, including reference/date changes. The server re-reads the draft and compares a fingerprint of the whole Xero record before sending the write. A stale revision is refused; read again and review the change. Overlapping updates within this server process are also refused. This is a preflight check, not an atomic external lock: another Xero client can still change the record between the check and the write. Older clients must fetch a revision before updating.

The updated invoice includes changeSummary.fields, with field paths and actual before/after values (null represents a missing value), plus changeSummary.before and .after totals and currency. These values come from Xero's read/write responses, not local total estimates. The response also contains a new revision for subsequent edits.

Updating lineItems intentionally replaces the invoice's complete line set. Preserve existing lines with their lineItemId; omit it only when creating a new line. For retained monetary line IDs, omitted discounts are inherited from the current invoice; explicit zero removes them. Other replacement fields must still be supplied, and omitted lines are removed. Prefer patches for tax-only edits.

For a compact contact billing lookup, call xero_list_invoices with { "contactId": "<ContactID>", "limit": 5, "includeLineItems": true }. It returns the latest invoices by invoice date, with dates, status, currency, totals, revisions, and line items. Missing list details are fetched by invoice ID inside the tool; a page of N records may require up to N extra GET requests. Default summary-only listing and pagination remain unchanged.

Tax-rate responses include name, taxType, rate, and a combined label, such as No GST (NONE) or Zero Rated (ZERORATED) when those are the organisation's Xero names. Never choose a tax type from its percentage alone. Names are returned by Xero rather than inferred from the code. The supported line fields follow Xero's invoice API.

When drafting, start with the contact defaults, then use the account and tax-rate helpers if you need to choose or verify a code. If a line has no taxType, Xero uses the selected account's default tax rate.

A quick local check

npm run check
npm test
npm start

npm test runs offline behavioral tests with synthetic credentials and owned temporary token directories. It does not use your .env, saved tokens, or a live Xero account.

npm start waits for MCP input. Hit Ctrl-C when you are done.

Keep the private bits private

.env and node_modules are ignored by Git. Tokens live outside the repository at ~/.xero-mcp/tokens.json with local-only file permissions. Do not commit your .env file or paste your Client Secret into an issue.

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    F
    maintenance
    A Model Context Protocol server implementation providing standardized access to Xero's accounting and business features, enabling operations like contact management, invoice creation, and chart of accounts management through MCP.
    51
    13,518 npm
    376
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    An enhanced MCP server for Xero, enabling natural language interaction with your Xero organisation for accounting tasks like invoices, contacts, bank transactions, and payroll.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that enables AI assistants to read and write Xero accounting data, including invoices, contacts, accounts, and payments, through natural language.
    MIT