Skip to main content
Glama
Xero Practice Aggregator MCP

Xero Practice Aggregator MCP

by ppiotis

ClientBooks Hub MCP

CI License: MIT

One view across every client's books, for accountants and bookkeepers. Works with Xero. Each client connects their own Xero organisation, read-only. Your agent can then answer practice-wide questions that Xero itself can't: which clients have invoices more than 90 days overdue, who is closest to missing payroll, which clients are trending toward trouble, and what is my practice's total revenue and average DSO. It also produces a ready-to-send Excel month-end pack per client.

v1.2 adds practice-scale exception scanning (unreconciled lines older than N days, duplicate bills/payments, missing tax codes, GST/VAT anomalies, missing receipts, large variances, suspense balances, stale drafts, lock dates, overdue tax), BAS/VAT and month-close readiness across every client in one call, cross-client data pulls (invoices, bills, bank transactions, payments, journals, contacts, accounts, P&L, balance sheet, trial balance, aged AR/AP), multi-client month-end summaries with draft variance commentary, an audit trail, and human-in-the-loop write-back: draft manual journals only, two-step prepare/confirm naming the org, and off unless you opt in.

This is not a general-purpose Xero plugin. By default it is read-only. The only write it can ever make is a DRAFT manual journal, and only when XERO_AGG_ENABLE_WRITES=1 is set, the org was connected with the manual-journals scope, and the user confirmed that exact journal for that exact org.

Why

A practice with 40 clients has 40 Xero logins and no cross-client view. Spotting the client who will bounce payroll next week means opening every org. This server polls every connected org on a schedule, normalizes invoices, bills, contacts, bank balances and reports into one cross-org model held in memory, and runs transparent, explainable rules over it. Every number it reports comes with the formula or rule that produced it.

Related MCP server: zCloak Xero Accounting MCP

Tools

Tool

What it does

Writes?

list_orgs

Connected orgs, sync status, currency, counts. Start here.

no

connect_org_start

Creates a Xero OAuth2 sign-in link (authorization code + PKCE, read-only scopes)

no

connect_org_complete

Exchanges the code from Xero's redirect, stores only the tokens, encrypted

tokens file

sync_now

Re-fetches all orgs into the in-memory cache (also runs on a timer)

no

overdue_invoices

Sales invoices more than 30, 60 or 90 days overdue across all clients, per org and per currency

no

payroll_risk

Ranks orgs by how close they are to missing payroll, with the formula shown

no

set_payroll_profile

Override an org's payroll amount and date (memory only)

memory

month_end_pack

.xlsx for one client: Aged Receivables, Bank Reconciliation, P&L Snapshot

export folder

client_health

0-100 score from transparent rules (expense spike z-score, growing director loan, ...)

no

practice_dashboard

Total revenue across clients (by currency), average DSO, health band per client

no

clients_trending_trouble

Clients under a health threshold, worst first, with reasons

no

disconnect_org

Revokes the Xero connection and deletes its tokens (preview, then confirm=true)

tokens file

v1.2 practice-scale

pull_data

One resource from every org (or named orgs), each row tagged with org + tenant id: invoices, bills, credit_notes, bank_transactions, payments, manual_journals, journals, contacts, accounts, tax_rates, profit_and_loss, balance_sheet, trial_balance, bank_summary, aged_receivables, aged_payables

no

scan_issues

Exception scan across the book (14 checks, below). Each finding: org, tenant id, severity, value at risk, Xero deep link, suggested fix

no

unreconciled_items

"Which clients have unreconciled lines older than N days?" in one call

no

tax_readiness

BAS / VAT / GST readiness per client for the period just ended: ready / needs review / not ready, with blockers and warnings

no

close_readiness

10-point month-close checklist per client with a 0-100 score

no

month_end_summary

One or many clients: cash, AR and AP snapshots, P&L vs prior month, draft variance commentary, exception list, close score; export=true writes a practice workbook

export folder

practice_scan

Whole-book routine (nightly / month-close): re-sync, all checks, ranked digest; use_last returns the last run

no

prepare_manual_journal

Step 1 of write-back: validates a journal (balanced, accounts exist, not a bank account, after lock date), stores it locally, returns a preview and a confirmation phrase naming the org. Sends nothing

audit log

confirm_manual_journal

Step 2: with the exact phrase, creates it in Xero as DRAFT. Refused unless writes are enabled and the org granted the scope

Xero (DRAFT only)

list_proposals

Pending / used / expired journal proposals

no

audit_trail

Local log of every read (endpoint + org + call count), proposal, confirmation, refusal and scheduled scan

no

Four skills ship with the plugin and teach the agent how to use these tools: connect-xero-orgs, practice-review, month-end-pack and practice-scan (exceptions, BAS/VAT readiness, close, safe write-back).

Issue checks (scan_issues)

Check

Flags

Severity

unreconciled_bank

Bank transactions with IsReconciled=false older than stale_days (default 7, XERO_AGG_STALE_DAYS)

high if >30 days or ≥1,000, else medium

unreconciled_payment

Invoice/bill payments not reconciled after stale_days

medium

duplicate_invoice

Same type + contact + amount, and same number/reference within 14 days or any within 3 days. Recurring charges (3+ identical in the window: rent, subscriptions) only count if same number within 3 days

high for bills, medium for invoices

duplicate_payment

Two payments of the same amount on the same invoice within 14 days; same-contact same-amount bank transactions within 3 days (recurring ones excluded)

high / medium

missing_tax_code

Invoice, bill or bank lines with no tax rate

high if the org is tax-registered

tax_anomaly

Income coded NONE/BAS Excluded; tax amount not matching the rate; tax code different from the account default

medium / low

missing_attachment

Bills and spend-money ≥ attachment_threshold (500) with HasAttachments=false

medium ≥2,000, else low

large_variance

P&L line in the last complete month vs its 3-month average, ≥ variance_pct (50%) and ≥ variance_min_amount (1,000)

high ≥200%

stale_draft

Draft/submitted invoices and bills, and draft manual journals, older than 30 days

low / medium

suspense_balance

Trial balance accounts named suspense / clearing / uncategorised with a balance

high

lock_date

No period lock date, or lock date before the end of last month

medium if none or ≥3 months behind

negative_bank

Overdrawn bank account (cards excluded)

medium

overdue_payables

Bills more than 60 days overdue

medium

overdue_tax_liability

Unpaid bills to HMRC / ATO / IRD / SARS / IRS / CRA past due

high

Deep links use https://go.xero.com/organisationlogin/default.aspx?shortcode=<org>&redirecturl=..., so clicking one opens the right org first.

BAS/VAT readiness looks at the tax period that has just ended (the one ending on as_of, otherwise the last one before it; quarterly unless Xero says monthly). Blockers: missing tax codes, unreconciled bank lines in the period, suspense balances. Warnings: tax-code anomalies, duplicates, missing receipts, unreconciled payments, drafts dated in the period. It does not prepare or file the return.

Safe write-back (opt-in)

  1. prepare_manual_journal validates the journal and returns a preview plus a phrase like POST DRAFT 3f9a1c2e TO Kookaburra Coffee Co Pty Ltd. Nothing is sent. The proposal is logged and expires in 30 minutes.

  2. The agent shows the preview, and the user approves that journal for that org.

  3. confirm_manual_journal with the exact phrase creates it in Xero with Status: DRAFT (it is not in the ledger until someone posts it inside Xero). It is single use. Wrong phrase, expired, used, writes disabled, or a read-only grant are all refused and logged.

To enable: set XERO_AGG_ENABLE_WRITES=1, which adds accounting.manualjournals (or accounting.transactions with XERO_SCOPE_SET=broad) to the requested scopes. Then reconnect each org you want to write to, because existing grants stay read-only. No other write scope is ever accepted.

Audit trail

XERO_AGG_AUDIT_FILE (default ~/.xero-aggregator/audit.jsonl, mode 0600, append-only JSON Lines) records each tool's reads (endpoint, tenant id, call count, no figures), every proposal (with its lines and reason), confirmation, refusal, the Xero journal id created, and scheduled scans. Read it with audit_trail.

The rules, in full

Ageing. Days overdue = as-of date minus due date. Buckets: Current (not yet due), 1-30, 31-60, 61-90, 90+. Only AUTHORISED invoices with an amount still due are included, and part-payments count at the amount still due. overdue_invoices with min_days: 30 means more than 30 days.

Payroll risk.

coverage = (cash in all bank accounts - bills due on or before next payroll date) / payroll amount
headroom = cash - those bills - payroll amount

Below 1.0 is a SHORTFALL, 1.0 to 1.5 is tight, and anything higher is ok. Cash is the closing balance from Xero's Bank Summary. The payroll amount defaults to the average of the P&L lines named wages, salaries, payroll, PAYE, pension or super over the last 3 months. The next payroll date defaults to the next 25th. Use set_payroll_profile to supply the real figures; the read-only scopes deliberately exclude Xero Payroll.

Health score. Starts at 100 and loses points for each rule that fires:

Rule

Points

More than 10% of receivables are 90+ days overdue

-15

Payroll coverage < 1.0

-25

Payroll coverage 1.0 to 1.5

-10

Expense spike: latest month's total expenses z-score >= 2.0 against the trailing months (sample std dev, needs 3+ trailing months)

-15

Growing director loan: the absolute balance of any balance-sheet account named like "Director ... Loan / Current Account" rose in each of the last 3 months

-15

DSO > 60 days

-10

Unreconciled bank items > 10% of cash

-10

Income fell in each of the last 3 months

-10

A score of 80 or more is healthy, 60-79 is watch, and under 60 is at risk. These are prioritisation heuristics, not credit ratings or advice.

DSO = receivables outstanding / P&L income for the last 3 months × days in those months. The practice average is the simple mean across clients. Revenue is P&L income, summed per currency; different currencies are never added together.

Install

Requires Node 20+.

Try it in 30 seconds (demo mode, no Xero account)

The default .mcp.json runs with XERO_AGG_DEMO=1. Two fixture orgs (Harbour Bakery Ltd and Northwind Joinery Ltd, both synthetic, GBP, as at 2026-09-30) are served through the same normalization code as live data. Add the plugin and ask "list my Xero clients".

Local, with real Xero orgs

{
  "mcpServers": {
    "clientbooks-hub": {
      "command": "npx",
      "args": ["-y", "https://github.com/ppiotis/clientbooks-hub-mcp/archive/refs/tags/v1.2.0.tar.gz"],
      "env": {
        "XERO_CLIENT_ID": "your-xero-app-client-id",
        "XERO_AGG_TOKEN_KEY": "output of: openssl rand -base64 32",
        "XERO_REDIRECT_URI": "http://localhost:5173/oauth/callback"
      }
    }
  }
}

Locally nothing listens on the redirect URI. After approving in Xero, the browser shows a "can't connect" page; copy that page's full URL and give it to the agent, which passes it to connect_org_complete. The code in that URL is single-use, expires in minutes, and is useless without the PKCE verifier held by your server.

Server (Streamable HTTP)

git clone https://github.com/ppiotis/clientbooks-hub-mcp && cd clientbooks-hub-mcp
npm ci && npm run build
export XERO_CLIENT_ID=... XERO_CLIENT_SECRET=... XERO_AGG_TOKEN_KEY="$(openssl rand -base64 32)"
export XERO_REDIRECT_URI=https://your-host/oauth/callback MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
export HOST=0.0.0.0 PORT=3000 XERO_AGG_TOKEN_FILE=/data/tokens.enc XERO_AGG_EXPORT_DIR=/data/exports
npm run start:http        # MCP endpoint: https://your-host/mcp, health: /health

Or use docker build -t clientbooks-hub . && docker run -p 3000:3000 -v xagg:/data -e ... clientbooks-hub. Put it behind HTTPS, and connect your MCP client to https://your-host/mcp with header Authorization: Bearer $MCP_AUTH_TOKEN. The server is single-practice: one deployment holds one practice's connections. Don't share an instance between practices.

Environment variables

Variable

Default

Purpose

XERO_CLIENT_ID

none

Xero app client id (live mode)

XERO_CLIENT_SECRET

none

Only for Xero "Web app" type. Omit for PKCE-only apps

XERO_REDIRECT_URI

http://localhost:5173/oauth/callback

Must exactly match the app's redirect URI

XERO_AGG_TOKEN_KEY

none

Required in live mode. 16+ chars; encrypts the token file

XERO_AGG_TOKEN_FILE

~/.xero-aggregator/tokens.enc

Encrypted token store

XERO_AGG_EXPORT_DIR

~/.xero-aggregator/exports

Where month-end packs are written

XERO_AGG_SYNC_MINUTES

60

Poll interval; 0 disables the timer

XERO_SCOPE_SET

granular

Set broad for Xero apps created before 2 March 2026

XERO_SCOPES

none

Explicit scope list; anything not *.read (plus OIDC/offline_access) is refused

XERO_AGG_DEMO

off

1 serves the bundled fixtures

XERO_AGG_ENABLE_WRITES

off

1 allows DRAFT manual journals (adds the manual-journals scope; reconnect orgs)

XERO_AGG_AUDIT_FILE

~/.xero-aggregator/audit.jsonl

Local audit trail

XERO_AGG_STALE_DAYS

7

Default age for "unreconciled older than N days"

XERO_AGG_SCAN_MINUTES

0 (off)

In-process schedule: re-sync and re-scan the whole book every N minutes (practice_scan use_last=true reads it)

XERO_AGG_DAILY_LIMIT

5000

Local per-org daily call cap; lower it to match your Xero plan tier

MCP_AUTH_TOKEN

none

Bearer token required on /mcp (HTTP mode). Always set this on a server

Xero app setup

  1. Go to developer.xero.com/app/manage, then New app.

  2. For local use choose Mobile or desktop app (PKCE, no secret). For a server choose Web app and set XERO_CLIENT_SECRET.

  3. Set the redirect URI to exactly your XERO_REDIRECT_URI.

  4. Copy the client id into XERO_CLIENT_ID.

  5. Scopes requested (read-only, granular, as required for apps created on or after 2 March 2026): openid profile email offline_access accounting.settings.read accounting.contacts.read accounting.invoices.read accounting.banktransactions.read accounting.reports.profitandloss.read accounting.reports.balancesheet.read accounting.reports.banksummary.read accounting.payments.read accounting.manualjournals.read accounting.reports.trialbalance.read accounting.reports.aged.read. The last four are new in v1.2 (payments, manual journals, trial balance, aged reports); orgs connected with v1.1 keep working, and the checks that need them report a data_gaps entry until the org is reconnected. With XERO_AGG_ENABLE_WRITES=1, accounting.manualjournals is added. Older apps can use XERO_SCOPE_SET=broad (accounting.transactions.read accounting.reports.read), which works until Xero's 13 September 2027 deadline. If Xero's consent screen reports an invalid scope, set XERO_SCOPES explicitly.

  6. To try it, connect Xero's Demo Company (in Xero: My Xero → Try the Demo Company), then ask the agent to "connect a client Xero org".

Many orgs, one login. On Xero's consent screen an adviser can tick several organisations at once; every ticked org is stored under the same grant, and all tools then work across all of them in one session with no re-authentication. Before each sync the server re-reads GET /connections (at most every 15 minutes) to pick up renamed orgs and drop revoked ones; it never adds orgs on its own. Every result carries the org name and tenant id. Xero's partner-only Bulk Connections flow (connect hundreds of orgs at once) needs Xero partner approval and is not used.

Rate limits. Every call goes through a per-org limiter: at most 5 concurrent, 60 per rolling minute, and a daily cap (5,000, or XERO_AGG_DAILY_LIMIT). It reads X-DayLimit-Remaining, waits for Retry-After on a 429 (minute limit), and stops cleanly when Xero reports the daily limit. Scans fan out across 3 orgs at a time. Tokens refresh 60 s before expiry (rotating refresh tokens, de-duplicated) and once on a 401.

Security & privacy

  • Read-only by default. The server only requests .read scopes; config that asks for anything else is rejected at startup unless XERO_AGG_ENABLE_WRITES=1, and even then only the manual-journals scope is accepted. Every sync and scan call is a GET (tested). The only non-GETs are DELETE /connections/{id} (disconnect_org revoking its own access) and, when opted in, a PUT /ManualJournals with Status: DRAFT from confirm_manual_journal after an exact, org-naming confirmation (tested: prepare never writes, and the tenant header matches the proposal).

  • Tokens only on disk. The token file holds OAuth tokens plus tenant ids and names, encrypted with AES-256-GCM under a key derived by scrypt from XERO_AGG_TOKEN_KEY. It is written atomically with mode 0600. A wrong key or a tampered file fails closed.

  • No financial data persisted. Invoices, bills, contacts, balances and reports are normalized into an in-memory cache only, so a restart clears them. A test asserts that after a full sync the only file on disk is the encrypted token file, and that it contains no customer names or invoice numbers. The audit log stores call counts, not figures, plus the journal lines you asked to propose. The other exceptions are the month-end packs you explicitly ask for, written to XERO_AGG_EXPORT_DIR (filenames are slugged; path traversal is tested).

  • OAuth done properly. Authorization code with PKCE S256, a single-use state that expires after 10 minutes, and refresh-token rotation: every refresh stores the new refresh token, and concurrent refreshes are de-duplicated so a rotated token is never reused. The client secret is sent via HTTP Basic only when configured.

  • HTTP mode. /mcp requires Authorization: Bearer $MCP_AUTH_TOKEN, compared in constant time, with a 1 MB body limit. /oauth/callback is protected by the PKCE state, and it never echoes codes or tokens back.

  • No telemetry, no third parties. The server only contacts login.xero.com, identity.xero.com and api.xero.com.

  • Secrets never go in chat. Configure them as environment variables; the skills tell the agent never to ask for tokens.

Reviewer test plan

Everything below runs in demo mode (the default .mcp.json), as an accountant opening the plugin for the first time. The data is as at 2026-09-30. Two UK orgs are in GBP, and v1.2 adds Kookaburra Coffee Co Pty Ltd (AUD, GST-registered, BAS quarterly), which is full of planted month-end problems.

1. "List my Xero clients." Expect list_orgs to show 3 orgs: Harbour Bakery Ltd (5 open invoices, 3 open bills, 2 bank accounts), Kookaburra Coffee Co Pty Ltd (4, 5, 1) and Northwind Joinery Ltd (6, 4, 1), mode: "demo".

2. "Show me every invoice more than 30 days overdue across all my clients." Then "...and over 90?" Expect 7 invoices: £20,435.50 and A$4,400.00, never added together. In GBP: Harbour Bakery 2 (£3,310.00), Northwind 4 (£17,125.50). In AUD: Kookaburra KC-1001, 61 days. The most overdue is INV-B101, Oakridge Homes, 112 days, £6,800.00 due. It's a part-paid £8,800 invoice, so check the amount due is used, not the total. Over 60 days: 5 invoices, £15,810.00 + A$4,400.00. Over 90 days: 2 invoices (INV-B101, INV-B102), £11,050.00, both Northwind. Draft and voided invoices in the fixtures (INV-A006, INV-B107) must not appear.

3. "Which client is closest to missing payroll? Show the working." Expect Kookaburra Coffee Co Pty Ltd, SHORTFALL: (cash 12340.55 - bills due by 2026-10-25 20895) / payroll 5200 = -1.65. The duplicate Bean Brokers bill (A$5,500) is part of the problem, and scan_issues flags it. Next is Northwind Joinery Ltd, SHORTFALL: coverage = (cash 7212.4 - bills due on or before 2026-10-25 9530) / payroll 12000 = -0.19, headroom -£14,317.60. Harbour Bakery is ok: (28,420.35 - 4,150) / 9,000 = 2.70. Then "Harbour's payroll is actually £20,000 on 8 October": expect coverage 1.31, tight (only the £2,300 VAT bill falls before 8 Oct).

4. "Make me Northwind's month-end pack." Expect an .xlsx named northwind-joinery-ltd-month-end-2026-09-30.xlsx with 3 sheets. Open it:

  • Aged Receivables: 6 invoice rows, then a Total row of Current 2,600.00 | 1-30 1,480.00 | 31-60 2,175.50 | 61-90 3,900.00 | 90+ 11,050.00 | Total 21,205.50.

  • Bank Reconciliation: Business Current Account, closing 7,212.40, 7 unreconciled items, 6,495.24.

  • P&L Snapshot: Sep 26 income 23,300.00, expenses 33,600.00, net -10,300.00. The 6-month total net is -900.00. For comparison, Harbour Bakery's pack shows an aged total of 8,150.00, 3 unreconciled items for 475.50, and a Sep net profit of 7,350.00.

5. "Which of my clients are trending toward trouble, and why?" Expect Northwind Joinery Ltd, score 10/100, at risk, then Kookaburra, 50 (payroll coverage, expense spike z = 9.58, unreconciled > 10% of cash). Northwind has six reasons. 90+ receivables are 52.1% of the total (-15). Payroll coverage is -0.19 (-25). Sep expenses of 33,600 are a spike against a trailing mean of 26,080 (sd 192.35, z = 39.09) (-15). The director's loan account grew 5,200 → 7,800 → 10,400 → 14,100 (-15). Unreconciled items of 6,495.24 compare with cash of 7,212.40 (-10). Income fell 27,200 → 26,400 → 24,900 → 23,300 (-10). Harbour Bakery scores 100, healthy (expense z-score 0.34).

6. "Give me the practice dashboard." Expect 3 clients, total revenue (last 3 months) £145,100.00 (Harbour 70,500 + Northwind 74,600) and A$52,500.00, with average DSO 18.9 days (Harbour 10.6, Kookaburra 19.9, Northwind 26.2). Asking for 6 months gives £300,100.00 and A$100,400.00.

7. Safety checks. "Make a pack for Ltd" should fail with "matches several orgs". overdue_invoices with min_days: 45 should be rejected. "Disconnect Northwind" should be refused in demo mode. In live mode, disconnect_org previews first and acts only with confirm=true.

8. "Which clients have unreconciled lines older than 7 days?" unreconciled_items: all three. Northwind has 7 lines (£6,495.24, oldest 3 Sep), Harbour 3 (£475.50) and Kookaburra 2 bank lines (A$2,620) plus 1 payment (A$1,100). With older_than_days: 30, only Kookaburra's A$1,980 Officeworks spend (51 days) is left.

9. "Scan my client book for problems." scan_issues: 31 findings, worth £14,537.41 and A$29,709.87 at risk. Kookaburra has 18, including: duplicate bill BB-7781 (A$5,500, high); KC-0990 paid twice (A$1,100); KC-1003 with no tax code (A$2,000); KC-1004 income coded BAS Excluded (A$330 GST); TEL-0925 GST 45 instead of 30; Suspense A$734.20; Repairs up 645%; ATO BAS bill 33 days overdue (A$2,950); a draft July accrual journal; KC-1005 draft for 41 days; three spends with no receipt; and the lock date stuck at 31 Mar. Northwind also shows "Materials up 72%".

10. "Show me BAS/VAT readiness across the practice." Then "Are we ready to close September?" tax_readiness for Jul-Sep 2026: all three are not ready. Kookaburra's BAS has 4 blockers (missing tax code, 2 unreconciled lines, suspense), and both UK VAT returns are blocked by unreconciled lines. close_readiness scores Kookaburra 0, Northwind 70 and Harbour 80.

11. "Prepare a journal clearing Kookaburra's suspense to rent." prepare_manual_journal (899 Dr 734.20 / 469 Cr 734.20) returns a preview and a phrase naming Kookaburra. confirm_manual_journal is refused ("Write-back is disabled") in the default install. With XERO_AGG_ENABLE_WRITES=1 in demo mode it is simulated and shows the exact DRAFT payload. An unbalanced journal, an unknown account, a bank account, or a date on or before the 31 Mar lock date are all rejected.

Live mode (optional). Create a Xero app as above, set XERO_CLIENT_ID and XERO_AGG_TOKEN_KEY, and remove XERO_AGG_DEMO. Then ask "connect a client Xero org", approve the Demo Company, paste the redirect URL, and ask "list my Xero clients".

Tests

npm ci
npm test                    # build + all suites
npm run test:integration    # real Xero API, see below

Suite

Covers

normalize.test.ts

Xero date formats, numbers, invoice status filtering, part-payments, report parsing (P&L, Bank Summary, Balance Sheet), unreconciled grouping

analytics.test.ts

Ageing buckets and boundaries, cross-org overdue (incl. mixed currency), payroll formula and overrides, health rules, DSO, dashboard

security-oauth.test.ts

AES-GCM round-trip, wrong key, tampering, 0600 mode, PKCE S256, single-use state, Basic auth, rotation persisted, concurrent-refresh de-dup, read-only scope enforcement, live code path against recorded responses (GET-only, nothing financial on disk), 401/429 handling, revoke on disconnect

xlsx.test.ts

Opens the generated workbooks and checks sheet names, every row, the bucket totals (and that they equal the column sums), bank rec and P&L totals, and path traversal

e2e.test.ts

The reviewer scenarios above, over stdio and Streamable HTTP (plus 401s, /health and a forged OAuth callback)

practice.test.ts

v1.2: every planted issue found with exact values and links, configurable stale days, tax period and BAS/close readiness, all 16 pull_data resources, month-end workbook, practice_scan/use_last, write-back (validation, phrase must name the org, single use, simulated in demo, refused for read-only grants, live DRAFT PUT with the right tenant), audit file 0600, write scopes opt-in, /connections rename/revoke (never adds), rate limiter (5 concurrent, 60/min per tenant, daily cap, Retry-After, day-limit header), and false positives found live (recurring rent/cleaning/parking, part-month variance)

live-regressions.test.ts

Bugs found against the real Xero API in v1.0.1/v1.0.2: live Bank Summary accountID attribute, whole-month P&L periods (incl. 30-day month-ends), mixed live + sample orgs

integration.xero.test.ts

Real Xero API, skipped unless credentials are set

Integration tests (real Xero). These are skipped unless XERO_CLIENT_ID and XERO_AGG_TOKEN_KEY are set, together with either XERO_REFRESH_TOKEN or an XERO_AGG_TOKEN_FILE created by connecting via connect_org_start/connect_org_complete. Web apps also need XERO_CLIENT_SECRET. The tests sync every connected org (e.g. the Demo Company), check that the normalized model is internally consistent, and write a pack. They only use GET requests. Xero rotates the refresh token on every use, so keep XERO_AGG_TOKEN_FILE on a persistent path between runs. The Demo Company's data changes when Xero resets it, so these tests check invariants rather than fixed numbers.

Mixed live + sample orgs. Set XERO_AGG_EXTRA_FIXTURES=<path to fixtures/> (with real Xero credentials) to show your connected orgs alongside the three sample orgs, labelled "(sample)". Useful when you only have one real org, such as the Xero Demo Company, and want to try the cross-org tools. Sample orgs are read from disk and never sent to Xero.

Verified live (v1.0.1, 15 Oct 2026). Every tool was run over MCP against a real Xero Demo Company (Global). Overdue totals, ageing buckets, bank closing balance and P&L totals in the .xlsx pack matched the raw Xero API responses to the cent.

Verified live (v1.2.0, 15 Oct 2026). Every read tool ran against the Demo Company (Global): see the changelog for what live testing changed. Write-back was only tested up to prepare live. No journal was sent to Xero.

The fixtures are synthetic data in Xero API response shape, generated by fixtures/generate.mjs (two UK orgs) and fixtures/generate-v12.mjs (Kookaburra, plus charts of accounts, tax rates, payments, manual journals and trial balances).

Limitations

  • "Unreconciled" uses Xero bank transactions and payments with IsReconciled=false. The public Accounting API does not expose imported bank statement lines that have not been matched or coded yet (Xero's bank-statement feeds are partner-only), so the real reconcile backlog can be larger than reported.

  • journals (the full system journal) needs Xero's Advanced tier plus use-case approval. Without it, pull_data reports a 401/403 for that resource. Manual journals, the trial balance and payments use ordinary scopes.

  • Xero's Aged Receivables/Payables reports take one contact at a time, so aged AR/AP are computed from open invoices and bills (they reconcile to the balance sheet, as verified live).

  • Missing-document checks rely on Xero's HasAttachments flag. Documents kept in another system (Dext, Hubdoc) won't be seen.

  • BAS/VAT readiness is a data-quality check. It does not prepare or file the return, and it assumes calendar-aligned periods.

  • XERO_AGG_SCAN_MINUTES schedules scans only while the server process runs. For a dependable nightly scan, use your agent's routine/scheduler to call practice_scan.

  • Payroll is estimated from P&L lines unless you set it, because Xero Payroll scopes are intentionally not requested.

  • Reports cover the as-of month plus the 5 months before it. Expense z-scores therefore use up to 5 trailing months.

  • Multi-currency orgs are reported in each org's base currency, and practice totals are kept per currency.

Changelog

  • 1.2.0: Practice-scale release. Adds 11 tools: pull_data, scan_issues (14 checks), unreconciled_items, tax_readiness, close_readiness, month_end_summary, practice_scan, prepare_manual_journal, confirm_manual_journal, list_proposals and audit_trail. Also adds the practice-scan skill and the Kookaburra Coffee Co sample org. Per-org rate limiter (5 concurrent, 60/min, daily cap, Retry-After, X-DayLimit-Remaining); /connections refresh before syncs; local audit trail; opt-in DRAFT-only write-back. New read scopes: payments, manual journals, trial balance and aged reports. Fixed: the default scope list was being mutated in place. Live testing against the Demo Company changed three things. Recurring charges (monthly rent, weekly cleaning, daily parking) are no longer reported as duplicates, which cut findings from 66 to 23. Variances now use the last complete month, not the part month. BAS/VAT readiness now checks the period that just ended, not the open one. Demo totals now include the AUD org (see the reviewer plan).

  • 1.1.0: Renamed from xero-aggregator-mcp to ClientBooks Hub (repo now clientbooks-hub-mcp; the old GitHub URL redirects). Xero does not allow its name in third-party product names, so the product is now described as working with Xero. Logo is now a 1:1 square with a solid background plate. Env vars (XERO_*, XERO_AGG_*) and the default token folder ~/.xero-aggregator are unchanged, so existing connections keep working.

  • 1.0.2: Fix from a second round of live testing. For a month-end on a 30-day month (or February), Xero cut every prior 31-day month in the P&L comparison to the 1st-30th, so the September pack under-reported 6-month income by 547.80. The P&L is now anchored on the next 31-day month and the extra column dropped. Regression test added.

  • 1.0.1: Fixes from live Xero testing. Bank accounts now pick up the accountID attribute live Xero returns, so unreconciled items are matched to the right account. P&L is requested for whole calendar months, because a mid-month as-of date made Xero compare half-months. Adds XERO_AGG_EXTRA_FIXTURES mixed mode. The integration test now cleans up its exported pack.

  • 1.0.0: First release.

Development

npm ci && npm run build && npm test
npm run fixtures   # regenerate demo fixtures

License

MIT © 2026 Paris Piotis

Available Tools

12 tools
client_healthClient health scoreA
Read-onlyIdempotent

0-100 health score per client from transparent rules (each rule and its points is listed): 90+ day receivables share, payroll coverage, expense spike (z-score of latest month vs trailing months), growing director loan, DSO, unreconciled items, falling income. >=80 healthy, 60-79 watch, <60 at risk. Omit org for all clients.

NameRequiredDescriptionDefault
orgNoClient org: tenant id or (part of) the organisation name, e.g. 'Northwind'.
as_ofNoReport date (YYYY-MM-DD). Defaults to today (demo mode: 2026-09-30).

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare this read-only, idempotent and non-destructive, so safety needs no restating. The description adds genuinely useful behavioral context beyond that: the score is built from transparent rules with per-rule points listed, and the omit-org behavior fans out to all clients. Not a 5 because no output shape or volume of returned clients is indicated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One dense but well-ordered paragraph: score definition first, then the rule inputs, then the band thresholds, then the omit-org behavior. Every clause carries information, though the rule list reads as a run-on.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

A two-parameter read tool with 100% schema coverage and no output schema: the description is complete enough to call correctly, explaining the metric composition and all-client behavior. Minor gap is the absence of return-shape detail, which is more tolerable for a scoring tool with stated bands.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents org ('tenant id or part of organisation name') and as_of (YYYY-MM-DD, defaults today). The description's 'Omit org for all clients' is useful secondary context but largely overlaps the schema, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific artifact, a 0-100 health score per client, and enumerates the exact inputs driving it (receivables share, payroll coverage, expense spike z-score, director loan, DSO, unreconciled items, falling income). Distinct from siblings like payroll_risk or overdue_invoices, which cover only single signals.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The scoring bands (>=80 healthy, 60-79 watch, <60 at risk) imply interpretation, but there is no explicit when-to-use guidance and no routing to alternatives such as overdue_invoices or payroll_risk when only one signal is needed. Usage is inferable rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

connect_org_completeFinish connecting a Xero orgA

Exchanges the authorization code from Xero's redirect for tokens, encrypts them at rest, and syncs the new org. Pass the full redirect URL (contains code and state).

NameRequiredDescriptionDefault
redirect_urlYesThe full URL the browser was redirected to after approving in Xero.

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare the safety profile (not read-only, not idempotent, open-world), but the description adds substantive side-effect detail beyond them: it encrypts tokens at rest and syncs the new org, which tells an agent what state changes to expect. It stops short of covering failure modes such as an expired or already-used code.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no waste. The operation is front-loaded and the parameter instruction follows immediately. Every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter connection-finishing tool with no output schema, the description covers the operation and the required input adequately. It could be slightly more complete by noting the dependency on connect_org_start, but nothing essential to invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents the single parameter. The description nonetheless adds meaning by explaining why the full URL is needed — it 'contains code and state' — which clarifies that a bare code will not suffice.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific multi-step operation: exchanging a Xero authorization code for tokens, encrypting them, and syncing the org. This clearly distinguishes it from sync_now or list_orgs. However, it does not explicitly name connect_org_start as the required predecessor, leaving the sibling relationship to inference from the name.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'from Xero's redirect' and 'Pass the full redirect URL' implies this runs after the user approves in Xero, but the description never states the prerequisite (call connect_org_start first) or any when-not conditions. Usage is implied rather than specified.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

connect_org_startStart connecting a client's Xero orgA
Read-only

Creates a Xero OAuth2 (authorization code + PKCE) sign-in link with READ-ONLY scopes. Give the link to the client (or open it yourself if you are their adviser user); they pick their org and approve. Then call connect_org_complete with the URL they were redirected to. Not needed in demo mode.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, destructiveHint=false and idempotentHint=false, but the description adds real value: the link carries READ-ONLY scopes, the flow is human-in-the-loop, and it is inert in demo mode. It stops short of saying whether the link expires or whether repeated calls create multiple pending links, which matters for a non-idempotent tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tightly packed sentences that front-load what is created, then the handoff, then the required follow-up call and the demo-mode caveat. No filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and no parameters, the description carries the workflow burden and does so well, including the downstream call. It omits link lifetime/retry semantics, the only material gap for an agent that may be asked to re-run it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has zero parameters, so there is nothing to document and the baseline is 4. The description correctly implies no inputs are required before the link is produced.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and artifact: creates a Xero OAuth2 authorization-code + PKCE sign-in link. It also scopes the operation (READ-ONLY scopes) so the agent cannot confuse it with connect_org_complete, the sibling that finishes the flow.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly describes the human step (hand the link to the client or open it as their adviser), the follow-up call (connect_org_complete with the redirect URL), and an exclusion (not needed in demo mode). Both the trigger and the next action are spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

disconnect_orgDisconnect a client's Xero orgA
DestructiveIdempotent

Revokes this app's Xero connection for one org and deletes its stored tokens and cached data. Call first with confirm=false to see what will happen, ask the user, then confirm=true.

NameRequiredDescriptionDefault
orgYesClient org: tenant id or (part of) the organisation name, e.g. 'Northwind'.
confirmNo

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, so the risk class is known; the description adds the concrete blast radius (tokens and cached data are deleted) and the non-obvious two-phase preview-then-confirm behavior, which annotations cannot express. It does not mention that reconnection requires the connect_org flow, leaving reversibility implicit.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no filler, and the destructive effect is front-loaded before the procedural instruction. Every clause carries information the agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive two-parameter tool with no output schema, the description covers impact, safety sequencing, and the meaning of both parameters. The only minor omission is what the confirm=false preview actually returns, but the agent has enough to act safely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 50%: 'org' is well documented in-schema, but 'confirm' has only a type and default with no description. The description compensates by explaining what each confirm value does, so the undocumented parameter is covered in prose.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (revokes/disconnect) plus the exact resource (this app's Xero connection for one org) and enumerates the side effects (deletes stored tokens and cached data). It is clearly distinguishable from the sibling connect_org_start / connect_org_complete tools, which do the opposite.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit operating procedure: call with confirm=false first, ask the user, then call with confirm=true. That is strong when-to-use guidance. It stops short of naming alternatives (e.g. use list_orgs to resolve the org identifier) or saying when not to disconnect.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_orgsList connected client orgsA
Read-onlyIdempotent

Lists every connected Xero org with sync status, currency and counts. Start here.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, and openWorldHint=true, carrying the safety and idempotency profile. The description adds what data is surfaced (sync status, currency, counts), but does not discuss pagination, freshness, or auth. Adequate against the lower bar annotations set.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the verb and resource, and the "Start here" call to action is placed last for emphasis. Nothing wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-argument read of an open-world list with no output schema, the description covers what is returned and where it fits in the workflow. Slightly more detail on result shape or ordering would make it complete, but it is sufficient to call correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters, so the baseline is 4. The description correctly implies a no-argument enumeration and adds nothing to mislead.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (lists) and resource (connected Xero orgs), plus the fields returned (sync status, currency, counts). Among siblings like connect_org_complete, sync_now, and disconnect_org, this is clearly the enumeration/discovery tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Start here" gives a clear usage cue that this is the entry point before connecting, syncing, or disconnecting orgs. It stops short of explicit when-not-to or naming the alternatives, but the context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

month_end_packExport a client's month-end pack (.xlsx)A
Idempotent

Writes an Excel workbook for ONE client with three sheets: Aged Receivables (per invoice, bucketed current/1-30/31-60/61-90/90+ with totals), Bank Reconciliation (closing balance, unreconciled count and amount per account) and P&L Snapshot (latest month and period total). Saved in XERO_AGG_EXPORT_DIR; returns the path and the totals written so you can quote them.

NameRequiredDescriptionDefault
orgYesClient org: tenant id or (part of) the organisation name, e.g. 'Northwind'.
as_ofNoReport date (YYYY-MM-DD). Defaults to today (demo mode: 2026-09-30).

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare write-not-read (readOnlyHint=false), openWorld, idempotent and non-destructive. The description adds real context beyond that: what is written (three specific sheets with their aggregates), where it is written (XERO_AGG_EXPORT_DIR), and what the caller receives back (path plus totals). It does not spell out overwrite/re-run semantics or permissions, which keeps it off a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single dense sentence, front-loaded with the verb and scope, followed by the sheet breakdown and the return payload. Every clause carries information, though the sheet enumeration is long enough that it could be trimmed in favour of the arc.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description correctly carries the return contract (file path and totals to quote) and names the destination directory and the exact sheets produced. An agent has everything needed to call it and interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both params are already documented in the schema, and the description adds little syntax detail. Its only contribution is confirming the single-org scope ('ONE client'), implying org must identify exactly one tenant — baseline 3 for full schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (writes an Excel workbook) and a precisely scoped resource (ONE client, three named sheets with their contents). No sibling tool produces a file export, so the agent can immediately tell this apart from sync/log/report siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clearly frames the operating context: one client per call, output lands in XERO_AGG_EXPORT_DIR, and the totals come back in the response. It stops short of naming when an agent should prefer this over e.g. overdue_invoices or client_health for the same data, so no explicit alternatives or exclusions are given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

overdue_invoicesOverdue invoices across all clientsA
Read-onlyIdempotent

Every client invoice (receivable) more than 30, 60 or 90 days past its due date, across all connected orgs, with per-org and per-currency totals. Uses amount still due (part-payments excluded).

NameRequiredDescriptionDefault
as_ofNoReport date (YYYY-MM-DD). Defaults to today (demo mode: 2026-09-30).
min_daysNoOverdue by MORE than this many days.

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, openWorld and non-destructive, so the safety profile is covered. The description adds real domain behavior: receivables only (not payables), aging measured against due date with strict 'more than' semantics, part-payments excluded via amount-still-due, and per-org/per-currency totals in the output.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no filler; scope and aggregation are front-loaded and the calculation caveat (part-payments excluded) is placed last where it belongs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only report with no output schema, the description covers scope, aging basis, calculation rule and result granularity adequately. Minor gaps remain: no mention of sorting/pagination or how multi-currency totals are consolidated, which an agent producing a report might want to know.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both parameters are documented in the schema, including the YYYY-MM-DD pattern and the demo-mode default. The description restates the 30/60/90 thresholds and the due-date basis but adds no syntax or edge-case detail beyond the schema — the baseline 3 for full coverage applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific resource (client invoices / receivables) and a precise scope (more than 30/60/90 days past due, across all connected orgs), which cleanly separates it from per-client siblings like client_health or practice_dashboard.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The 'across all connected orgs' phrasing implies a portfolio-wide reporting use case, but the description never says when to pick this over client_health, practice_dashboard or clients_trending_trouble, and names no alternatives or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

payroll_riskPayroll risk rankingA
Read-onlyIdempotent

Ranks orgs by how close they are to missing their next payroll. coverage = (cash in bank - bills due on or before payroll date) / payroll amount. Below 1.0 means a shortfall. Payroll amount defaults to the average of wages/salaries P&L lines over the last 3 months, next payroll date to the next 25th; override with set_payroll_profile. Shows the formula with numbers for each org.

NameRequiredDescriptionDefault
as_ofNoReport date (YYYY-MM-DD). Defaults to today (demo mode: 2026-09-30).

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so the safety profile is covered. The description adds genuinely useful behavior beyond that: how coverage is computed, the exact default assumptions (3-month average of wages/salaries P&L lines, next 25th payroll date), and that the formula is returned with numbers per org.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four tight sentences: the purpose and threshold first, then the formula, then the defaults and the override path, then the output note. Nothing is redundant and the most decision-relevant facts are front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only, single-optional-parameter ranking tool with no output schema, the description covers purpose, metric definition, default assumptions, and output shape adequately. Minor gaps remain: it doesn't say whether results cover all connected orgs or how the ranking is ordered/limited.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is a single optional parameter (as_of) with 100% schema description coverage, including its format pattern and demo-mode default, so the schema carries the load. The description adds nothing about as_of semantics (e.g. whether historical dates recompute defaults), which is the baseline-3 expectation when schema coverage is complete.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb and resource ('Ranks orgs') plus the exact quantity ranked ('how close they are to missing their next payroll'). It also defines the metric outright (coverage formula, 1.0 threshold), so an agent can distinguish it from siblings like client_health or overdue_invoices without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives concrete context for invocation (assessing payroll shortfall risk) and explicitly routes the agent to set_payroll_profile when the default payroll amount or date assumptions must be overridden. It does not state when NOT to use this versus other risk-oriented siblings, so it falls short of a full when/when-not/alternatives treatment.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

practice_dashboardPractice dashboardA
Read-onlyIdempotent

Practice-level totals: revenue (P&L income) across all clients for the last N months, grouped by currency (never mixes currencies), average DSO, and each client's health band.

NameRequiredDescriptionDefault
as_ofNoReport date (YYYY-MM-DD). Defaults to today (demo mode: 2026-09-30).
monthsNo

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish the safe read profile (readOnly, idempotent, non-destructive), so the description's burden is light. It does add genuine behavioral detail beyond the annotations: currency grouping is enforced so results are 'never mixed', and the last-N-months window is fixed. It does not mention row counts, performance, or result shape.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence that names the resource first and then enumerates the returned measures. Dense but each clause carries information; the parenthetical '(P&L income)' and '(never mixes currencies)' are the only marginal additions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description effectively enumerates the returned measures (revenue by currency, average DSO, health band), which is what an agent needs to decide if this fits. Annotations cover the safety profile, so only minor gaps remain around result size/format.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 50%: as_of is documented in the schema, months is not. The description compensates for the undocumented months parameter by defining it as the lookback window ('last N months'), but adds nothing about as_of beyond what the schema already says.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific resource (practice-level totals) and enumerates exactly what is returned: revenue/P&L income across all clients, DSO, and client health bands. The stated scope ('across all clients', 'practice-level') implicitly separates it from per-client siblings like client_health or overdue_invoices, though no sibling is named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The practice-level framing implies when to reach for it (aggregate overview rather than per-client drill-down), but there is no explicit when-to-use statement, no exclusion of scenarios better served by client_health or month_end_pack, and no prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

set_payroll_profileSet an org's payroll amount/dateA
Idempotent

Overrides the estimated payroll amount and/or next payroll date for one org. Held in memory only (cleared on restart); nothing is sent to Xero.

NameRequiredDescriptionDefault
orgYesClient org: tenant id or (part of) the organisation name, e.g. 'Northwind'.
amountNo
next_payroll_dateNo

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only mark this as a non-readonly, idempotent, non-destructive write. The description adds genuinely useful behavior the annotations cannot convey: the override is held in memory only, cleared on restart, and nothing is sent to Xero. That persistence scope and absence of external side effects are exactly what an agent needs, though it omits what happens if the org is not found or whether at least one field must be supplied.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two compact sentences, no filler. The core action is front-loaded and the ephemerality caveat immediately follows it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema and only three parameters, with annotations carrying the mutation/safety profile, so the description is close to sufficient. It misses edge-case behavior (org not found, whether amount and date are independently optional) but covers the two things an agent would most likely get wrong: persistence and Xero side effects.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 33% — 'org' is documented but 'amount' and 'next_payroll_date' have no schema descriptions. The description compensates partially by framing both as optional ('and/or') overrides of estimates, but gives no currency/units for amount or any semantics beyond the regex pattern for the date. Adequate but leaves gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Overrides'), the exact resources modified (estimated payroll amount and/or next payroll date), and the scope ('for one org'). This is distinguishable from siblings like payroll_risk or practice_dashboard without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The word 'Overrides' and 'estimated' imply the use case (correcting an estimate the system computed), but the description never states when to reach for this versus payroll_risk or other payroll siblings, nor any prerequisites or exclusions. Usage is inferable but not spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sync_nowSync all orgs nowA
Read-onlyIdempotent

Re-fetches invoices, bills, contacts, bank balances and reports for every connected org into the in-memory cache (nothing financial is written to disk). The poller also does this automatically every XERO_AGG_SYNC_MINUTES.

NameRequiredDescriptionDefault
as_ofNoReport date (YYYY-MM-DD). Defaults to today (demo mode: 2026-09-30).

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover readOnly, idempotent and non-destructive, but the description adds genuinely new behavioral facts: the write target is an in-memory cache and 'nothing financial is written to disk', and the operation spans all orgs rather than one. It does not disclose whether the call blocks until completion or how long a full multi-org refetch takes, which would matter for a sync trigger.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero filler. Scope and effect come first, the no-disk guarantee is parenthetical, and the poller cadence is appended as context rather than padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With rich annotations and a fully described single parameter, the definition covers scope, side effects and the automatic-update alternative. The one remaining gap is that no output schema exists and the description says nothing about what a sync call returns or whether it is synchronous, which an agent invoking a long-running org-wide refetch would want to know.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is a single optional parameter and schema coverage is 100%, so the schema already documents as_of fully, including the YYYY-MM-DD pattern and the demo-mode default. The description's mention of 'reports' loosely corresponds to that date knob but adds no syntax or defaulting detail, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Re-fetches') and enumerates the resources (invoices, bills, contacts, bank balances, reports) across an explicit scope ('every connected org'). No sibling tool performs a sync, so the agent can distinguish it immediately from list_orgs, overdue_invoices, or the connect_org_* tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The sentence about the poller running automatically every XERO_AGG_SYNC_MINUTES gives the agent a clear reason to call this only when it needs data refreshed immediately rather than waiting. It stops short of an explicit when/when-not statement or naming an alternative data source, so it is strong context rather than full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 12 tool updatesv1.0.0
    • First observedclient_health
    • First observedclients_trending_trouble
    • First observedconnect_org_complete
    • First observedconnect_org_start
    • First observeddisconnect_org
    • First observedlist_orgs
    • First observedmonth_end_pack
    • First observedoverdue_invoices
    • First observedpayroll_risk
    • First observedpractice_dashboard
    • First observedset_payroll_profile
    • First observedsync_now

TDQS

A3.9/5.0

Scored across 12 tools

Disambiguation4/5

Most tools target clearly distinct resources or analyses (overdue aging, payroll risk, health scoring, exports), and descriptions are unusually detailed. Minor overlap exists among the health/analytics cluster — client_health, clients_trending_trouble and practice_dashboard all surface client health — but their scopes (per-client score, filtered list, practice totals) are explained well enough to route correctly.

Naming Consistency3/5

All names are snake_case, which keeps them readable, but the pattern is mixed: some are verb_noun (list_orgs, sync_now, disconnect_org, set_payroll_profile) while others are bare noun phrases (overdue_invoices, payroll_risk, month_end_pack, client_health, practice_dashboard, clients_trending_trouble). No predictable verb convention ties the set together.

Tool Count5/5

12 tools sits squarely in the well-scoped range for a multi-org finance aggregator. Each tool maps to a distinct capability (auth, sync, org management, several reports, export) and none appear redundant filler.

Completeness4/5

The lifecycle is well covered: OAuth connect start/complete, list, sync, disconnect, plus a rich analytics/reporting surface and an Excel export. Gaps are minor — there's no per-client drill-down or raw invoice/contact retrieval, but agents can work around this via the existing reports.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Enables interaction with the Xero Accounting API to manage contacts, invoices, payments, accounts, and financial reports. It provides a suite of tools for natural language access to accounting records and business performance data.
    20
    1
    Apache 2.0
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables accountants to connect Xero, read accounting data, analyze, and execute controlled draft operations after explicit confirmation, with Xero as the source of truth.
    -
  • A
    license
    A
    quality
    C
    maintenance
    Provides read-only access to daily trading figures for multi-site restaurant groups, detecting anomalies in control blocks such as void rates and labor percentages to uncover subtle financial leaks.
    5
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables subscribers to explore their own Xero and QuickBooks backups in plain language from any MCP client, with tools for browsing, previewing, aggregating, comparing, and live data access.
    MIT