Skip to content

Add read-only Xero Accounting adapter - #943

Merged
keysersoft merged 9 commits into
HelpCode-ai:mainfrom
ryanduguid:adapter/xero
Oct 9, 2026
Merged

keysersoft merged 9 commits into
HelpCode-ai:mainfrom
ryanduguid:adapter/xero

Conversation

@ryanduguid

@ryanduguid ryanduguid commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary / Goal

Add Xero Accounting to the adapter catalogue so an authorised organisation can read invoices, bills, contacts, accounts, Trial Balance and Profit and Loss through the existing REST engine.

Context

Refs #150 and October Challenge #846. @keysersoft

The xero slug was free before implementation. This follows the FreshBooks OAuth2 and existing extraHeaders patterns. Current Xero scope documentation supplies the granular invoice and individual report read scopes.

Changes

  • Add intl/xero.json with nine GET tools: Organisation probe; invoice, contact and account list/get; Trial Balance; Profit and Loss.
  • Configure authorisation code and refresh-token OAuth2 with Basic client authentication and xero-tenant-id. Document required variables, a Postman route to discover the tenant before first installation, both callback URLs, rotating tokens, additive consent, pagination and 429 handling. Every tool includes examples.
  • Add static and mocked REST-engine coverage, plus a live Organisation probe gated by RUN_XERO_LIVE=1.
  • Regenerate catalog.ts with the script, add the Xero SVG logo and update the quoted catalogue counts in the eight files checked by the repository script.

Constraints

All Accounting tools use GET under https://api.xero.com/api.xro/2.0. Scopes grant only the specified accounting reads. Tenant discovery at /connections is documented separately because it sits outside that base URL. Required settings are XERO_CLIENT_ID, XERO_CLIENT_SECRET and XERO_TENANT_ID; XERO_REFRESH_TOKEN is optional. No engine, dependency or ee/ changes, write tools, custom MCP server or issue claim.

Type

  • New feature
  • Documentation

Testing

The catalogue at 95fc33ca7e5f contains 326 listed adapters, 17 keyless and 4,014 tools. Commit 4bb8f6bb741e corrects the README banner from 16 to 17 keyless connectors. Fresh count and adapter validation checks pass after this documentation correction.

Local backend checks and the frontend production build ran on Linux with Node 26.10.0, npm 12.2.0 and generated Prisma 7.10.0, using locked dependencies. Catalogue checks ran on Windows. No live Xero credentials were used.

Working directory Command Result
Root node scripts/regenerate-catalog.mjs 342 total adapters; generated catalogue reviewed at 95fc33ca7e5f
Root node scripts/validate-adapters.mjs --warn 342/342 valid; 1,248 existing warnings, none for Xero; repeated after the README correction
Root node scripts/adapter-count.mjs --check 326 listed adapters, 17 keyless, 4,014 tools; repeated after the README correction
Root npm test -w packages/backend -- --maxWorkers=4 481 suites and 10,458 tests passed; 4 suites/358 tests skipped at 95fc33ca7e5f
packages/backend npm run lint Passed; 12 existing warnings
packages/backend npx tsc --noEmit -p tsconfig.json Passed
packages/backend npm run build Passed
packages/frontend npm run build Passed at bf8dee91ed85; not repeated after the upstream merge
Root git diff --check Passed

Earlier upstream CI at bf8dee91ed85 passes backend and frontend lint/typecheck/build, the backend test suite, adapter validator and generator checks, script tests, runtime dependency checks, release/stuck-users tests, and Docker image build/boot. CodeQL, Playwright, the filesystem scan and the CLA passed at bf8dee91ed85. Hosted workflows on the repaired branch require maintainer approval; the current head has no fresh hosted CI result. The full backend suite and frontend build were not repeated for the one-line README correction.

At bf8dee91ed85, the Xero spec contributed 27 passing static, request-mapping and callback integration tests, plus one skipped live probe. Mocked calls cover every path, bearer and tenant headers, defaults, filters, report dates, false booleans, encoded identifiers and retained response envelopes. Both omitted and blank optional refresh tokens are tested through import, authorisation state, the callback/encrypted merge and authenticated healthcheck/probe requests. Existing OAuth tests cover Basic authentication and persistence of rotated refresh tokens. At that earlier revision, Aikido scanned five changed code/configuration/asset files with no findings. All 1,503 tracked TS/TSX/JSON/SVG files checked locally matched the Git snapshot after checks.

Done-when / Checklist

  • Xero validates and catalog.ts is regenerated by script.
  • All requested reads and a probe requiring no parameters are present, with examples.
  • OAuth2, the tenant header and setup instructions are wired.
  • Backend tests pass; no ee/ changes or secrets are included.
  • Existing code style and repository adapter requirements are followed.
  • User-facing documentation is included in the connector instructions.

Risks and unverified checks

Live Xero authorisation, granular-scope acceptance, rotating refresh and provider responses are unverified because no live credentials were supplied. Clean-room Cloud/self-host installation is also unverified. Tenant discovery requires an authorised session for the same app before installation; the instructions include a Postman Desktop procedure for a new app. Existing Xero consent can retain broader scopes; the instructions explain revoking and reauthorising to reduce permissions.

Trivy's Docker image vulnerability scan was skipped for this PR; GitHub marks its aggregate check neutral.

Related Issues

Refs #150. Part of #846.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

👋 Welcome, @ryanduguid, and thanks for opening your first PR on AnythingMCP!

A few quick pointers:

  • Make sure CI is green before requesting review (Backend, Frontend, Playwright, CodeQL, Trivy).
  • If this is a new adapter, the parametrised catalog.spec.ts test will validate it automatically.
  • Sign off your commits if you can — it's not blocking, just nice to have.

Someone from the core team will look at this within ~48h. If you don't hear back, please ping us in Discussions / Q&A.

⭐ While you wait — if you find AnythingMCP useful, a star helps others discover it.

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@ryanduguid

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Oct 7, 2026
The tenant is chosen after authorisation, so it moves from the OAuth
extraHeaders to each Accounting tool's headers, and the new tool reads
https://api.xero.com/connections without it. A tool header that is only
an empty variable now keeps its placeholder, so the call is refused with
the variable's name instead of sending a blank header.
@ryanduguid

Copy link
Copy Markdown
Contributor Author

Done in bd2ae4d. XERO_TENANT_ID is now optional, and xero_list_connections reads https://api.xero.com/connections, so setup is: install with the tenant empty, Authorize with Provider, run xero_list_connections, paste the tenantId. Postman stays in the instructions as a fallback.

Two changes were needed to make that work:

  • The tenant header moved from authConfig.extraHeaders to each Accounting tool's headers. An unresolved placeholder in authConfig makes computeSetupState report needs_input, which hides every tool, including the new one. The probe and healthcheck now use the connections URL, which needs no tenant.
  • The setup form saves an optional field left empty as "", so {{XERO_TENANT_ID}} resolved to a blank header and the existing missing-variable check never fired. interpolateConnectorConfig now keeps the placeholder when a tool header is exactly one variable and that variable is blank, so the call is refused with "The connector behind xero_get_organisation is missing a value for XERO_TENANT_ID. The request was not sent…". The only other adapter with that header shape is Statsig's Console tools (STATSIG_CONSOLE_API_KEY), where a blank key is now refused the same way instead of sent.

Tests: xero.live.spec.ts installs with an empty tenant and authorises. It then checks that xero_list_connections goes out without the tenant header, that xero_get_organisation is refused without a request for both an empty and a missing tenant, and that the header is sent once the tenant is set. That test fails with the interpolation change reverted. env-interpolation.util.spec.ts covers the header rule. Backend lint (no new warnings), tsc --noEmit, npm test (470 suites) and adapter-count.mjs --check pass locally on Node 26. Live Xero calls are still unverified.

@keysersoft
keysersoft merged commit 684f343 into HelpCode-ai:main Oct 9, 2026
13 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant