Skip to content

How to Connect AI Agents to Xero and QuickBooks: MCP Server Architecture Guide

Learn how to connect AI agents to Xero and QuickBooks using an MCP server architecture. Master OAuth concurrency, rate limits, and schema normalization.

Sidharth Verma Sidharth Verma · · 13 min read
How to Connect AI Agents to Xero and QuickBooks: MCP Server Architecture Guide

If you need an AI agent to read invoices from Xero, create bills in QuickBooks Online, or reconcile transactions across both—without building and maintaining separate API connectors for every AI model your customers use—this is the architecture guide you need.

Connecting AI agents to enterprise accounting systems is a nightmare of state management, strict schemas, and aggressive rate limits. If you are a product manager or engineering leader tasked with giving an LLM read and write access to financial data, you already know that brute-forcing point-to-point accounting integrations is an unscalable approach.

Search intent dictates we answer the core architectural question immediately: To connect AI agents to Xero and QuickBooks securely and at scale, you must deploy an MCP (Model Context Protocol) server backed by a unified accounting schema. This architecture translates the agent's natural language intent into standardized JSON-RPC tool calls, normalizes the disparate data models of different ERPs, and passes strict API rate limits back to the agent so it can manage its own execution backoff.

The demand for this architecture is massive. A January 2026 Deloitte study found that 63% of finance organizations have fully deployed AI in their operations, and nearly 50% of CFOs report having integrated AI-driven agents into parts of the finance function like forecasting or expense management. According to PwC, over 79% of finance leaders believe AI agents are already changing how financial data is analyzed. The accuracy gains are tangible: in recent testing, AI bookkeeping agents demonstrated 97.8% accuracy in automated transaction categorization, compared to 79.1% for human outsourced accountants across real workflows. Goldman Sachs is already deploying agentic workflows for transaction reconciliation and trade accounting.

AI agents can automate up to 95% of the entire bookkeeping workflow—but only if your underlying infrastructure can reliably handle the API layer. This guide breaks down the exact architecture required to build a production-ready MCP server for QuickBooks Online and Xero, covering OAuth concurrency, rate limit normalization, schema mapping, and pagination.

The N×M Problem of Connecting AI Agents to Accounting APIs

Historically, exposing a SaaS product to an AI model meant building custom connectors for OpenAI, Anthropic, Google, and whatever LangChain or LlamaIndex wrapper your enterprise customers decided to use. If you support five AI models and ten accounting systems, your engineering team is responsible for maintaining fifty separate integration paths.

Accounting APIs compound this N×M problem. Double-entry ledgers, strict tax rate dependencies, and multi-currency edge cases mean that a failed write operation doesn't just throw an error—it corrupts a financial record. Each API has its own OAuth flow, its own pagination style, its own field names for the same concept (Xero calls them "Contacts," QuickBooks calls them "Customers" and "Vendors" separately), and its own rate limit scheme.

The Model Context Protocol (MCP) acts as the universal adapter that collapses this complexity. Anthropic launched MCP in November 2024, and the adoption curve has been staggering. By March 2026, the protocol hit 97 million monthly SDK downloads across Python and TypeScript, with first-class client support in Claude, ChatGPT, Gemini, Cursor, and VS Code. In December 2025, Anthropic donated MCP to the Agentic AI Foundation under the Linux Foundation, with OpenAI, Google, Microsoft, and AWS as backing members.

By deploying an MCP server, you build the accounting integration layer exactly once. Any MCP-compatible client—whether it is Claude Desktop, a custom LangGraph agent, or an internal support bot—can immediately discover and execute tools against Xero and QuickBooks without a single line of model-specific glue code.

How an MCP Server Architecture Works for Xero and QuickBooks

An MCP server for accounting is a JSON-RPC 2.0 endpoint that dynamically exposes an integrated account's available API operations as callable tools (e.g., create_a_quickbooks_invoice or list_all_xero_contacts), complete with JSON Schema validation for the agent's inputs.

In a highly scalable architecture, MCP tools are not hardcoded. Instead, they are dynamically generated from the integration's configuration and documentation records. When an AI agent connects to the MCP server endpoint, the server reads the target accounting platform's OpenAPI specification, filters the available endpoints based on the customer's OAuth scopes, and returns a structured tool list.

A tool only appears if it has a corresponding description and JSON Schema—this acts as both a quality gate and a curation mechanism, ensuring LLMs only see well-documented operations. Each tool includes a descriptive name, a JSON Schema for input parameters (query filters, cursors, request body fields), and auto-injected instructions.

Here is what the architecture looks like in practice:

graph TD
    A[AI Agent / LLM Client] -->|JSON-RPC 2.0 over HTTP| B[MCP Server Router]
    B -->|tools/list| C[Dynamic Tool Generator]
    C -->|Reads Schemas| D[(Integration Config DB)]
    B -->|tools/call| E[Unified API Proxy Layer]
    E -->|JSONata Mapping| F[Unified Accounting Schema]
    F -->|Transformed Request| G[Third-Party ERP]
    G -->|Native API| H[QuickBooks Online]<br>I[Xero]<br>J[NetSuite]

The execution flow between the agent and the underlying API follows a strict sequence:

sequenceDiagram
    participant Agent as AI Agent<br>(Claude, ChatGPT)
    participant MCP as MCP Server
    participant Auth as OAuth Token<br>Manager
    participant API as Xero / QBO API

    Agent->>MCP: tools/list (JSON-RPC)
    MCP-->>Agent: Available tools + JSON schemas
    Agent->>MCP: tools/call: list_invoices
    MCP->>Auth: Refresh token if expiring
    Auth-->>MCP: Fresh access token
    MCP->>API: GET /Invoices (Xero) or<br>GET /query?query=SELECT * FROM Invoice (QBO)
    API-->>MCP: Provider-specific response
    MCP-->>Agent: Normalized result + rate limit headers

The Flat Input Namespace Problem

When an AI agent invokes an MCP tool via the tools/call method, it passes all arguments as a single, flat JSON object. However, accounting APIs require strict separation between URL path parameters, query strings, and JSON request bodies.

Your MCP server router must intelligently split this flat input namespace. It does this by comparing the agent's arguments against the query_schema and body_schema derived from the integration's documentation. If the agent calls get_single_quickbooks_invoice_by_id with {"id": "123", "minorversion": "65"}, the router identifies id as a path parameter and minorversion as a query parameter, constructing the correct upstream HTTP request without the agent needing to understand REST semantics.

For write operations, the architecture is the same. An agent calling create_a_quickbooks_invoice passes a body conforming to the tool's JSON Schema. The MCP server maps that to QuickBooks' native format, applies the stored OAuth credentials, and executes the request. For a deeper look at write-side safety patterns, see our guide on whether AI agents can safely write data back to accounting systems.

Handling the Brutal Reality of Accounting API Rate Limits

This is where most AI-agent-to-accounting integrations break in production. AI agents are chatty by nature—a single user query like "summarize my outstanding receivables" can trigger dozens of API calls across invoices, contacts, and payments. Accounting APIs were not designed for this access pattern.

Here is what you are actually up against when connecting to these systems:

Constraint Xero QuickBooks Online
Per-minute limit 60 calls/min per tenant 500 calls/min per company
Daily limit 5,000 calls/day per tenant No daily cap
Concurrent requests 5 simultaneous 10 simultaneous
App-wide limit 10,000 calls/min across all tenants N/A
Rate limit response HTTP 429 + Retry-After header HTTP 429

Xero's limits are particularly punishing for agent workflows. At 60 calls per minute and 5,000 per day, syncing a single invoice with line items, contacts, and payments can consume 6 API calls. That means you can sync roughly 833 invoices per day before you are locked out. An AI agent that is naively fetching context for a natural-language financial query will blow through that minute limit in seconds.

QuickBooks is more generous on throughput at 500 requests per minute, but the 10-concurrent-request ceiling means a multi-tenant application serving several companies simultaneously needs careful request scheduling.

And here is the part that matters for your architecture: the rate limit response formats are completely different between providers. Xero returns X-MinLimit-Remaining and X-DayLimit-Remaining custom headers. QuickBooks returns a bare 429 with no standardized remaining-count headers. Every other accounting API (NetSuite, Zoho Books, FreshBooks) has its own convention.

The Anti-Pattern: Absorbing Rate Limits in the Infrastructure

Many engineering teams make the mistake of implementing automatic retries with exponential backoff directly in their API proxy layer. When the proxy hits a 429, it holds the HTTP connection open, waits 30 seconds, and tries again.

Do not do this with AI agents.

LLMs operate on strict timeout windows. If your infrastructure holds a connection open for 45 seconds while silently retrying a Xero rate limit, the agent will timeout, assume the tool call failed, and likely hallucinate a response to the user.

The Correct Pattern: Normalization and Agent-Side Backoff

A scalable architecture takes a different, highly opinionated approach: The infrastructure does NOT retry, throttle, or apply backoff on rate limit errors.

When an upstream API returns an HTTP 429, the proxy layer passes that error directly back to the caller. There is no automatic retry, no backoff, no absorption of the error. The agent or calling application receives the 429 and is responsible for deciding what to do.

What the proxy layer does provide is standardized rate limit information on every response—successful or not. Regardless of whether the upstream is Xero or QuickBooks, the platform normalizes the provider-specific headers into three consistent response headers based on the IETF RateLimit header fields specification:

  • ratelimit-limit: The maximum number of requests permitted in the current window.
  • ratelimit-remaining: The number of requests remaining in the current window.
  • ratelimit-reset: The number of seconds until the rate limit window resets.

By passing these normalized headers back through the MCP isError: true response, the AI agent's reasoning loop can natively pause its own execution. Your agent's retry logic can be entirely provider-agnostic:

import time
 
def call_with_rate_awareness(client, endpoint, params):
    response = client.request(endpoint, params)
 
    remaining = int(response.headers.get("ratelimit-remaining", 100))
    reset_seconds = int(response.headers.get("ratelimit-reset", 60))
 
    if response.status_code == 429:
        wait_time = reset_seconds + 1  # Add jitter in production
        time.sleep(wait_time)
        return call_with_rate_awareness(client, endpoint, params)
 
    if remaining < 5:
        # Proactively slow down before hitting the wall
        time.sleep(reset_seconds / max(remaining, 1))
 
    return response

The same code works whether the underlying provider is Xero (with its 60/min limit) or QuickBooks (with its 500/min limit). The agent reads the same headers either way. This is the kind of plumbing that saves you from writing per-provider rate limit handling code that inevitably falls behind when providers change their policies.

Solving OAuth Token Management and Concurrency

Giving an AI agent access to an accounting platform requires maintaining a persistent connection via OAuth 2.0. Both Xero and QuickBooks issue short-lived access tokens alongside a refresh token. Xero tokens expire after 30 minutes. QuickBooks tokens expire after 60 minutes.

Managing this lifecycle in a traditional web app is straightforward: when a user clicks a button, you check the token expiry and refresh if necessary. But AI agents operate asynchronously and concurrently. A single agent might spin up five parallel threads to query invoices, contacts, accounts, tax rates, and payments simultaneously.

If your infrastructure attempts to refresh the OAuth token for all five requests at the exact same millisecond, you will trigger a race condition. Two concurrent agent tool calls both check the token, both see it is expired, and both attempt a refresh. One succeeds. The other uses a refresh token that has now been invalidated by the first call (most OAuth providers invalidate old refresh tokens on rotation). Result: the second call fails, the integrated account enters a broken state, and your customer has to re-authenticate manually.

The Durable Lock Pattern

To solve this, your infrastructure must implement a distributed mutex or durable lock for token refreshes per integrated account. A robust OAuth token refresh architecture operates on two parallel tracks:

  1. Proactive Refresh: A background scheduler monitors all integrated accounts. Approximately 60 to 180 seconds before an access token expires, the scheduler acquires a lock and exchanges the refresh token. This ensures the token is almost always fresh when an API call arrives, imposing no latency penalty on the agent.
  2. On-Demand Refresh with Locking: If an agent fires a request and the token is within 30 seconds of expiration (e.g., after a long idle period), the API proxy intercepts the request. It attempts to acquire the per-account lock.

If Thread A acquires the lock, it performs the HTTP request to QuickBooks to get the new token. Threads B, C, D, and E will see that the lock is held and simply await the completion of Thread A's operation. Once Thread A writes the new token to the relational database, the lock is released, and all five threads proceed using the fresh token. No duplicate refresh attempts occur.

If a refresh fails entirely—because the user revoked access or the provider is down—the integrated account is marked as requiring re-authentication, and a webhook fires so your application can notify the customer.

Unified Accounting Models: One Schema for Xero and QuickBooks

The final piece of the architecture is schema normalization. If you simply expose raw Xero and QuickBooks endpoints via an MCP server, your AI agent still has to understand the proprietary data models of each platform.

Xero and QuickBooks represent the same financial concepts with different field names, different nesting structures, and different enumeration values. An invoice in Xero has Contact.ContactID; in QuickBooks it is CustomerRef.value. Xero uses "AUTHORISED" as an invoice status; QuickBooks uses a numeric Balance field where zero implies paid. Xero calls them Contacts while QuickBooks calls them Customers and Vendors.

If your AI agent is generating tool calls from natural language, it cannot learn two sets of field names for the same operation. Your prompt engineering becomes polluted with platform-specific instructions. The solution is a Unified Accounting API layer sitting between the MCP server and the third-party endpoints. This layer abstracts away provider-specific nuances into a single, standardized data model.

Declarative JSONata Mapping

Instead of writing integration-specific code (e.g., if (provider === 'xero') { ... }), scalable architectures use declarative JSONata expressions to map payloads. JSONata is a Turing-complete query and transformation language for JSON.

When an agent requests a list of unified Contacts, the mapping engine executes a JSONata expression against the raw upstream response.

For QuickBooks Online, the mapping expression might look like this:

response.{
  "id": $string(Id),
  "name": DisplayName,
  "email": PrimaryEmailAddr.Address,
  "phone": PrimaryPhone.FreeFormNumber,
  "status": Active ? "active" : "inactive",
  "created_at": MetaData.CreateTime
}

For Xero, the expression for the exact same unified output looks entirely different:

response.{
  "id": ContactID,
  "name": Name,
  "email": EmailAddress,
  "phone": Phones[PhoneType='DEFAULT'].PhoneNumber,
  "status": ContactStatus = "ACTIVE" ? "active" : "inactive",
  "created_at": UpdatedDateUTC
}

The AI agent only ever sees the unified output: id, name, email, phone, status, created_at. It can execute a write operation back to the accounting system using the exact same unified schema, and the infrastructure will run a reverse JSONata mapping to translate the request body into the format expected by Xero or QuickBooks.

Because the unified schema is consistent across providers, the MCP tool definitions are identical regardless of whether the connected account is Xero or QuickBooks. The LLM sees list_all_contacts with one JSON Schema. Fewer tools means smaller context windows, which means better model performance and lower token costs. For a deeper comparison of unified accounting APIs, see our 2026 guide to the best unified accounting API.

Critically, the original provider-specific response is always preserved as remote_data on the unified response. If an agent needs a Xero-specific field that isn't in the unified schema—like tracking categories or branding themes—it is right there. The unified model doesn't strip data; it normalizes access to the most common fields while keeping everything else available.

Pagination and Cursor Instructions

Accounting APIs return massive datasets, making pagination critical. Xero uses page numbers, while QuickBooks uses SQL-like offset queries (STARTPOSITION).

The unified API normalizes these into standard next_cursor strings. However, LLMs are notorious for trying to parse, decode, or alter pagination cursors. To prevent this, the MCP server dynamically injects strict prompt instructions into the tool's query_schema description:

Tip

LLM Prompt Engineering Tip: In your dynamically generated tool schema, explicitly define the cursor behavior: "The cursor to fetch the next set of records. Always send back exactly the cursor value you received (nextCursor) without decoding, modifying, or parsing it."

Warning

Xero policy change (March 2, 2026): Xero's updated Developer Terms now explicitly prohibit using API data to train AI/ML models. If your agent architecture involves fine-tuning on customer financial data pulled from Xero, this is a compliance blocker. Read-time inference (where an agent queries data, reasons over it, and takes action) is a different pattern and is not affected—but you should always check with your legal team.

Strategic Wrap-Up and Next Steps

Connecting AI agents to Xero and QuickBooks requires far more than just wrapping REST endpoints in an MCP server. You are building distributed systems infrastructure.

To achieve production-grade reliability, your architecture must solve three hard problems: rate limit awareness, OAuth lifecycle management, and schema normalization. Specifically, your system must:

  1. Eliminate custom code by dynamically generating MCP tools from API schemas.
  2. Normalize rate limit headers and pass 429 errors back to the agent so it can handle its own backoff logic using standard IETF headers.
  3. Implement a durable lock to prevent OAuth token refresh race conditions during concurrent agent tool calls.
  4. Deploy a unified accounting schema using declarative transformations so your agents only need one set of prompts to interact with any ERP.

If you are evaluating this stack, here is the honest trade-off matrix:

Approach Time to first integration Multi-provider support Maintenance burden
Build direct connectors 2-4 weeks per provider Each provider is a separate codebase High (OAuth, rate limits, schema drift)
Use a unified API + MCP Days One schema, one tool set Low (managed by the platform)
Single-provider MCP server 1-2 weeks Only covers one provider Medium (you own the OAuth and rate limit logic)

The math gets worse as you add providers. QuickBooks and Xero cover a large share of the SMB market, but enterprise deals will ask for NetSuite, Sage Intacct, and Dynamics 365. Each new direct connector multiplies your maintenance surface.

Start by reading the QuickBooks Online API integration guide and the Xero API integration guide to understand the provider-specific quirks. Then evaluate whether abstracting those quirks behind a unified API and MCP server layer saves your team enough time to justify the dependency.

Building this infrastructure in-house requires months of dedicated engineering time—time that should be spent improving your agent's reasoning capabilities, not debugging QuickBooks API quirks.

Frequently Asked Questions

What is an MCP server for accounting APIs?
An MCP (Model Context Protocol) server translates standardized JSON-RPC requests from AI models into specific API calls for accounting platforms like Xero and QuickBooks, eliminating the need for custom connectors.
How do you handle QuickBooks and Xero API rate limits with AI agents?
Xero strictly limits traffic to 60 requests per minute, and QuickBooks to 500. Instead of absorbing 429 errors in the infrastructure, you should pass normalized IETF rate limit headers back to the AI agent so it can natively pause its execution loop.
Can AI agents write data back to QuickBooks and Xero?
Yes. Using a unified accounting schema and an MCP server, agents can safely execute write operations like generating invoices or creating expense records across different platforms using a single standardized data model.
How do you prevent OAuth token race conditions with concurrent AI agents?
Because AI agents often make multiple concurrent API calls, you must implement a durable lock (mutex) per integrated account. This ensures that if a short-lived token expires, only one thread refreshes it while the others wait, preventing the provider from invalidating the connection.

More from our Blog