An MCP (Model Context Protocol) server for testing and validating Xero integrations without requiring live Xero credentials.
# Clone and start with Docker
git clone https://github.com/ninonline/xerodev-mcp.git
cd xerodev-mcp
docker compose upThat's it. No Xero credentials needed. You now have:
- 3 test tenants (AU/GST, UK/VAT, US)
- 93 Chart of Accounts entries
- 60 contacts (customers and suppliers)
- 60 invoices, 30 quotes, 24 credit notes
- 30 payments and 45 bank transactions
- 26 tools for validation, simulation, CRUD, and OAuth operations
The official Xero MCP server is designed for AI assistants querying live Xero data. xerodev-mcp is for developers building integrations who need to test and validate before touching production.
| Feature | Official MCP | xerodev-mcp |
|---|---|---|
| Testing | Production only | Sandbox with mock data |
| Validation | Fails at runtime | Pre-validates before API calls |
| Errors | Generic messages | Educational with recovery suggestions |
| CI/CD | Not designed for it | Built for automated testing |
Returns server capabilities and workflow guidelines. Always call this first.
{
"include_tenants": true,
"verbosity": "diagnostic"
}Switch to a specific tenant and load its configuration.
{
"tenant_id": "acme-au-001",
"verbosity": "diagnostic"
}Retrieve audit log entries for debugging and compliance.
{
"tenant_id": "acme-au-001",
"tool_name": "create_invoice",
"success": true,
"include_stats": true,
"limit": 20,
"verbosity": "diagnostic"
}Validates payloads against the tenant's configuration. Returns educational errors with recovery suggestions.
{
"tenant_id": "acme-au-001",
"entity_type": "Invoice",
"payload": {
"type": "ACCREC",
"contact_id": "contact-001",
"line_items": [{
"description": "Consulting Services",
"quantity": 10,
"unit_amount": 150.00,
"account_code": "200",
"tax_type": "OUTPUT"
}]
},
"verbosity": "diagnostic"
}Supported entity types: Invoice, Contact, Quote, CreditNote, Payment, BankTransaction
When validation fails, the response includes:
- Detailed diff showing what's wrong
- Compliance score (0.0 to 1.0)
- Recovery suggestions with
next_tool_call
Get valid values for AccountCodes, TaxTypes, or ContactIDs.
{
"tenant_id": "acme-au-001",
"entity_type": "Account",
"filter": { "type": "REVENUE", "status": "ACTIVE" },
"verbosity": "compact"
}Simulate batch operations without executing them.
{
"tenant_id": "acme-au-001",
"operation": "create_invoices",
"payloads": [...],
"verbosity": "diagnostic"
}Returns: number that would succeed/fail, estimated total amount, issues summary, recovery suggestions.
Generate realistic test data for various scenarios.
{
"tenant_id": "acme-au-001",
"entity": "INVOICES",
"count": 10,
"scenario": "OVERDUE_BILLS",
"verbosity": "diagnostic"
}Scenarios: DEFAULT, OVERDUE_BILLS, MIXED_STATUS, HIGH_VALUE
Transitions an entity through its lifecycle states.
{
"tenant_id": "acme-au-001",
"entity_type": "Invoice",
"entity_id": "inv-001",
"target_state": "AUTHORISED",
"verbosity": "diagnostic"
}Invoice states: DRAFT → SUBMITTED → AUTHORISED → PAID (terminal). Any state → VOIDED (except PAID).
Quote states: DRAFT → SENT → ACCEPTED → INVOICED (terminal). DECLINED → DRAFT for re-editing.
Credit note states: Same as invoices.
For PAID transitions, include payment_amount and payment_account_id.
Test how your integration handles network failures.
{
"tenant_id": "acme-au-001",
"condition": "RATE_LIMIT",
"duration_seconds": 60,
"verbosity": "diagnostic"
}Conditions:
RATE_LIMIT- Simulates Xero's 60 req/min limit (429)TIMEOUT- Simulates slow/hanging connectionsSERVER_ERROR- Simulates 500/502/503 errorsTOKEN_EXPIRED- Simulates OAuth expiration (401)INTERMITTENT- Random failures at specified rate
Test idempotency key behaviour.
{
"tenant_id": "acme-au-001",
"operation": "create_invoice",
"idempotency_key": "my-unique-key",
"payload": { ... },
"replay_count": 5,
"verbosity": "diagnostic"
}Create a new contact in the tenant.
{
"tenant_id": "acme-au-001",
"name": "Example Customer",
"email": "customer@example.com",
"is_customer": true,
"is_supplier": false,
"idempotency_key": "unique-key",
"verbosity": "diagnostic"
}Update an existing contact in the tenant.
{
"tenant_id": "acme-au-001",
"contact_id": "contact-001",
"name": "Updated Company Name",
"email": "newemail@example.com",
"is_customer": true,
"is_supplier": true,
"verbosity": "diagnostic"
}Only fields provided will be updated. Unprovided fields remain unchanged. Use status: "ARCHIVED" to archive a contact.
Create a new invoice in the tenant.
{
"tenant_id": "acme-au-001",
"type": "ACCREC",
"contact_id": "contact-001",
"line_items": [{
"description": "Consulting",
"quantity": 10,
"unit_amount": 150.00,
"account_code": "200",
"tax_type": "OUTPUT"
}],
"status": "DRAFT",
"idempotency_key": "unique-key",
"verbosity": "diagnostic"
}Invoice types: ACCREC (sales invoice), ACCPAY (bill from supplier).
Create a new quote/proposal.
{
"tenant_id": "acme-au-001",
"contact_id": "contact-001",
"line_items": [{
"description": "Consulting Services",
"quantity": 10,
"unit_amount": 150.00,
"account_code": "200"
}],
"title": "Project Proposal",
"expiry_date": "2025-02-28",
"idempotency_key": "unique-key",
"verbosity": "diagnostic"
}Create a new credit note.
{
"tenant_id": "acme-au-001",
"type": "ACCRECCREDIT",
"contact_id": "contact-001",
"line_items": [{
"description": "Refund for returned items",
"quantity": 1,
"unit_amount": 100.00,
"account_code": "200"
}],
"idempotency_key": "unique-key",
"verbosity": "diagnostic"
}Credit note types: ACCRECCREDIT (credit to customer), ACCPAYCREDIT (credit from supplier).
Create a new payment against an invoice or credit note.
{
"tenant_id": "acme-au-001",
"invoice_id": "inv-001",
"account_id": "acc-027",
"amount": 1650.00,
"date": "2025-01-15",
"idempotency_key": "unique-key",
"verbosity": "diagnostic"
}Create a new bank transaction (receive or spend).
{
"tenant_id": "acme-au-001",
"type": "RECEIVE",
"bank_account_id": "acc-027",
"contact_id": "contact-001",
"line_items": [{
"description": "Payment received",
"quantity": 1,
"unit_amount": 500.00,
"account_code": "200"
}],
"idempotency_key": "unique-key",
"verbosity": "diagnostic"
}Transaction types: RECEIVE, SPEND, RECEIVE-OVERPAYMENT, RECEIVE-PREPAYMENT, SPEND-OVERPAYMENT, SPEND-PREPAYMENT.
The recommended workflow for AI agents:
- Call
get_mcp_capabilitiesto understand the server - Call
switch_tenant_contextto select a tenant - Call
validate_schema_matchbefore any write operation - If validation fails, follow
recovery.next_tool_call - Call
introspect_enumsto find valid values - Fix payload and validate again
- Call
dry_run_syncto test batch operations - Call the appropriate create tool
- Use
drive_lifecycleto transition entities through states - Use
get_audit_logto review operations
All tools return responses with progressive verbosity:
{
"success": true,
"data": { ... },
"meta": {
"timestamp": "2025-01-15T10:30:00.000Z",
"request_id": "uuid",
"execution_time_ms": 42,
"score": 1.0
},
"diagnostics": {
"narrative": "Human-readable explanation",
"warnings": []
},
"recovery": {
"suggested_action_id": "find_valid_accounts",
"next_tool_call": {
"name": "introspect_enums",
"arguments": { ... }
}
}
}Verbosity levels:
silent- Data onlycompact- Data + metadatadiagnostic- Full details with narrativedebug- Everything including logs
# Production mode (mock data)
git clone https://github.com/ninonline/xerodev-mcp.git
cd xerodev-mcp
docker compose up
# Development mode with hot-reload
docker compose -f docker-compose.dev.yml up
# Live Xero mode (requires credentials)
docker compose -f docker-compose.yml -f docker-compose.live.yml upnpm install
npm run build
npm test
npm start| Variable | Default | Description |
|---|---|---|
MCP_MODE |
mock |
Server mode (mock or live) |
MCP_DATABASE_PATH |
./data/xerodev.db |
SQLite database path |
MCP_ENCRYPTION_KEY |
- | 64-char hex for token encryption (live mode) |
LOG_LEVEL |
info |
Logging level |
For live mode, copy .env.example to .env and configure your Xero OAuth credentials.
The server includes realistic multi-region test data:
- AU: Acme Corporation Pty Ltd (GST tax system, AUD)
- UK: Acme Technologies Ltd (VAT tax system, GBP)
- US: TechStart Inc (Sales tax, USD)
- Accounts: 93 entries (Chart of Accounts per region)
- Contacts: 60 contacts (customers and suppliers)
- Invoices: 60 sample invoices
- Quotes: 30 quotes in various states
- Credit Notes: 24 credit notes
- Payments: 30 payments
- Bank Transactions: 45 bank transactions
- Tax Types: OUTPUT, INPUT (AU), A, E, Z (UK), NONE, TX (US)
# Generate new fixtures
npm run generate:fixtures
# Validate fixtures
npm run validate:fixturessrc/
├── index.ts # MCP server entry point
├── adapters/ # Mock/live adapters
├── core/ # Response formatting, security, database
└── tools/ # MCP tool implementations
├── core/ # get_mcp_capabilities, switch_tenant, get_audit_log
├── validation/ # validate_schema, introspect_enums
├── simulation/ # dry_run, seed_sandbox, drive_lifecycle
├── chaos/ # simulate_network, replay_idempotency
└── crud/ # create_contact, create_invoice, etc.
test/
├── fixtures/ # JSON test data
└── unit/ # Unit tests (492 tests)
# All tests
npm test
# Watch mode
npm run test:watch
# Coverage
npm run test:coverage- AES-256-GCM encryption for tokens (12-byte IV)
- Non-root Docker user
- SQLite with tenant isolation
- No secrets in responses
- Node.js 20.x or higher
- Docker (for containerised deployment)
MIT
- All tests must pass (
npm test) - Fixtures must validate (
npm run validate:fixtures) - Error messages should be educational with recovery suggestions