qbo-mcp
Provides read-only access to QuickBooks Online data including customers, vendors, invoices, bills, and chart of accounts, allowing AI assistants to query financial information.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@qbo-mcp Find customers with 'Acme' in their name"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
# qbo-mcp
The Model Context Protocol server for QuickBooks Online. Plug Claude into your books — customers, vendors, invoices, bills, and the chart of accounts — read-only, in five minutes.
Why this exists
Roughly seven million businesses keep their books in QuickBooks Online. Intuit publishes a capable REST API but no official MCP server, so every team that wants Claude (or any MCP-aware AI assistant) to see their books ends up writing the same OAuth-and-pagination glue from scratch.
If you use Claude to operate finance day-to-day — chasing AR, sanity-checking AP, prepping for a board update — that gap is the difference between "how much did Acme owe us at the end of March?" working out of the box and "how much did Acme owe us at the end of March?" requiring a custom integration.
qbo-mcp closes that gap. It's a tiny, well-tested, MIT-licensed MCP server that exposes eight read-only QBO endpoints to any MCP client. Built from years of running production QBO automation against real money flow — every edge that surfaced in production (token rotation, 429 backoff, mid-page expiry, query-string escaping) is handled in client.py so you don't have to learn it the hard way.
Related MCP server: qbo-mcp
What you can do with it
Wire this server into Claude Code, Claude Desktop, or any MCP host, then ask things like:
"Find every customer matching 'Acme' and show me their balances."
"How much do we owe WidgetCo right now? List the open bills."
"Pull all unpaid invoices created this month and group them by customer."
"What does our chart of accounts look like? List every Bank and Other Current Asset account with its current balance."
"Compare last week's bill volume to the same week last month."
Claude reads your books directly. No copy-paste, no spreadsheets, no custom pipelines.
Tools (v0.1, all read-only)
Tool | What it does |
| Find customers by display name (substring, case-insens). |
| Fetch one customer by Id. |
| Find vendors by display name. |
| Fetch one vendor by Id. |
| List invoices in a date window, optionally open/paid. |
| Fetch one invoice by Id, including line items. |
| List bills in a date window, optionally open/paid. |
| Return the active chart of accounts with balances. |
Write endpoints (create invoice, create bill, post journal entry) are intentionally not in v0.1. They are planned for v0.2 once read-only ergonomics settle. We will not ship a write tool that reaches into your books until the read surface has been beaten on for a release cycle.
Install
pip install qbo-mcpv0.1 ships from this repository. PyPI publication is pending — for now, install with
pip install git+https://github.com/alveyautomation/qbo-mcpor clone and runpip install -e .locally.
One-time OAuth setup
QBO uses OAuth 2.0 with rotating refresh tokens. You only do this dance once, then qbo-mcp keeps itself authenticated forever (as long as it runs at least once every 100 days). Total time: about 60 seconds.
Create an app at https://developer.intuit.com/. Pick the Accounting scope. Copy the
client_idandclient_secret.Visit the OAuth Playground at https://developer.intuit.com/app/developer/playground. Select your app, pick the environment (Sandbox or Production), and click Get Authorization Code. Sign in to the QuickBooks company you want to expose.
Exchange the code for tokens — the Playground does this for you. Copy:
refresh_token(long string, lasts 100 days of inactivity)realmId(numeric, identifies your QBO company)
Save them to
.env:
QBO_CLIENT_ID=ABxxxxxxxxxxxxxx
QBO_CLIENT_SECRET=xxxxxxxxxxxxxxxx
QBO_REFRESH_TOKEN=ABxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
QBO_REALM_ID=1234567890123456
QBO_ENVIRONMENT=production # or "sandbox"That's it. The first tool call exchanges the refresh token for an access token; subsequent calls reuse the cached access token until it expires (~55 minutes), at which point the client refreshes silently.
Refresh-token rotation: Intuit issues a new refresh token on every refresh and immediately invalidates the old one. If your deployment runs in a single long-lived process, this is invisible. If your deployment restarts often (containers, serverless), persist the rotated token. Subscribe to
QBOClient(on_refresh_token_rotated=…)to capture every rotation. See SECURITY.md for the full story.
Wire into Claude Code
Add to ~/.claude/claude_code_config.json (or your project's MCP config):
{
"mcpServers": {
"qbo": {
"command": "qbo-mcp",
"env": {
"QBO_CLIENT_ID": "ABxxxxxxxxxxxxxx",
"QBO_CLIENT_SECRET": "xxxxxxxxxxxxxxxx",
"QBO_REFRESH_TOKEN": "ABxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"QBO_REALM_ID": "1234567890123456",
"QBO_ENVIRONMENT": "production"
}
}
}
}Restart Claude Code. The eight qbo_* tools will appear in any new session.
Wire into Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows) and add the same mcpServers block as above. Restart the desktop app.
Tool reference
Every tool returns a JSON envelope:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": "human-readable message" }qbo_search_customers
qbo_search_customers(query: str, limit: int = 50)Substring match against Customer.DisplayName. The query is escaped before embedding into QBO's query language, so apostrophes (O'Brien) and underscores (acme_test) are safe.
Example response:
{
"ok": true,
"data": {
"customers": [
{ "Id": "1001", "DisplayName": "Acme Corp", "Balance": 1250.00 }
],
"count": 1,
"query": "acme",
"limit": 50
}
}qbo_get_customer
qbo_get_customer(customer_id: str)Fetches the full customer record by Id. Returns data: null when the Id does not exist (404).
qbo_search_vendors / qbo_get_vendor
Symmetric to the customer pair, but against the Vendor entity.
qbo_search_invoices
qbo_search_invoices(
date_from: str, # ISO date "YYYY-MM-DD"
date_to: str, # ISO date "YYYY-MM-DD"
status: str | None = None, # "open" | "paid" | None
limit: int = 200, # max 2000
)Window is inclusive on Invoice.TxnDate. The status filter is a convenience over QBO's Balance field — "open" returns invoices with Balance > 0, "paid" returns invoices with Balance = 0.
Pagination is handled transparently: QBO's query endpoint requires explicit STARTPOSITION / MAXRESULTS clauses, and the client walks pages until either limit is reached or the upstream returns a short page. The response includes limit_reached: true when limit was the stopping condition.
qbo_get_invoice
qbo_get_invoice(invoice_id: str)Returns the full invoice record (with Line[]), or data: null for a 404.
qbo_search_bills / qbo_get_invoice parity
qbo_search_bills mirrors qbo_search_invoices but against the Bill entity (vendor-side). Same date semantics, same status filter.
qbo_get_chart_of_accounts
qbo_get_chart_of_accounts()Returns every active account in the realm. Each record includes Id, Name, AccountType, AccountSubType, Classification, and CurrentBalance among other QBO fields. Useful for grounding any "where did this transaction post?" question.
Local development
git clone https://github.com/alveyautomation/qbo-mcp
cd qbo-mcp
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pytest # 50+ tests, ~3sPre-commit hooks (gitleaks, trufflehog, ruff, formatter, tenant-fingerprint scrubber):
pip install pre-commit
pre-commit installIntegration tests against a real QBO sandbox realm are gated behind QBO_INTEGRATION_TESTS=1. They are not required for normal contribution.
Troubleshooting
Failed to refresh QBO access token — refresh token has been rotated out from under you, or the app's client_id / client_secret is wrong. Refresh tokens are invalidated as soon as a new one is issued, so if two processes share a refresh token, whichever one refreshes first wins. Solution: persist rotated tokens (see on_refresh_token_rotated) or run only one server per refresh-token credential.
Missing required environment variables — the server tried to start before its .env was loaded. Either export the vars in the parent shell, or ensure your MCP host config includes them in the env block.
Empty results despite known data — confirm QBO_ENVIRONMENT matches the credential. A sandbox refresh token against the production API host (or vice versa) will authenticate but return an empty company.
Slow large date windows — QBO's query endpoint paginates at a hard cap of 1000 rows per page. The client walks pages transparently, but a 5-year invoice scan still means many round-trips. Consider tightening date_from / date_to or filtering by status.
Transient QBO error: HTTP 429 — Intuit's rate limiter kicked in. The client retries automatically with exponential backoff; if you see this surface in tool output, you've exceeded the configured QBO_MAX_RETRIES. Bump it or slow down your queries.
Contributing
Issues and pull requests welcome. Please:
Run
pytestbefore opening a PR (pip install -e ".[dev]").Run
pre-commit run --all-files.Keep additions to v0.1 scope read-only. Write endpoints land in v0.2.
Synthetic data only in tests — no real customer names, vendor names, or realm IDs.
License
MIT — see LICENSE.
Disclaimer
qbo-mcp is an unofficial, third-party integration. It is not endorsed by, affiliated with, or supported by Intuit Inc. "QuickBooks" and "QuickBooks Online" are trademarks of Intuit Inc. Use at your own risk; verify behavior against your realm before depending on it for production decisions.
Available Tools
8 toolsqbo_get_chart_of_accountsA
Return the full chart of accounts (active only).
Returns:
JSON envelope. data.accounts is the list of account records,
each carrying Id, Name, AccountType, AccountSubType,
Classification, and CurrentBalance among other QBO fields.
Inputs: no named parameters. Returns: result
qbo_get_customerA
Fetch the full record for a single customer.
Args: customer_id: QBO Customer.Id (string-encoded integer per Intuit's API).
Returns:
JSON envelope. data is the customer record, or null on 404.
Inputs: customer_id (required). Returns: result
qbo_get_invoiceA
Fetch full invoice detail including line items.
Args: invoice_id: QBO Invoice.Id.
Returns:
JSON envelope. data is the invoice record, or null on 404.
Inputs: invoice_id (required). Returns: result
qbo_get_vendorA
Fetch the full record for a single vendor.
Args: vendor_id: QBO Vendor.Id.
Returns:
JSON envelope. data is the vendor record, or null on 404.
Inputs: vendor_id (required). Returns: result
qbo_search_billsA
Search vendor bills with TxnDate in [date_from, date_to] inclusive.
Args: date_from: ISO date (YYYY-MM-DD), start of TxnDate window. date_to: ISO date (YYYY-MM-DD), end of TxnDate window. status: Optional balance filter. "open" returns bills with a non-zero balance; "paid" returns bills with Balance == 0. Omit (null) for both. limit: Cap on yielded bills (1-2000, default 200).
Returns:
JSON envelope. data.bills is the list of bill records.
Inputs: date_from (required), date_to (required), status, limit. Returns: result
qbo_search_customersA
Search customers by display name (substring, case-insensitive).
Args:
query: Free-text fragment matched against Customer.DisplayName
via QBO's LIKE '%query%' operator.
limit: Cap on returned customers (1-1000, default 50).
Returns: JSON envelope: {"ok": true, "data": {"customers": [...], "count": N}}.
Inputs: query (required), limit. Returns: result
qbo_search_invoicesA
Search invoices created in [date_from, date_to] inclusive.
Args: date_from: ISO date (YYYY-MM-DD), start of TxnDate window. date_to: ISO date (YYYY-MM-DD), end of TxnDate window. status: Optional balance filter. "open" returns invoices with a non-zero balance; "paid" returns invoices with Balance == 0. Omit (null) for both. limit: Cap on yielded invoices (1-2000, default 200).
Returns:
JSON envelope. data.invoices is the list of invoice records.
Inputs: date_from (required), date_to (required), status, limit. Returns: result
qbo_search_vendorsA
Search vendors by display name (substring, case-insensitive).
Args: query: Free-text fragment matched against Vendor.DisplayName. limit: Cap on returned vendors (1-1000, default 50).
Returns: JSON envelope: {"ok": true, "data": {"vendors": [...], "count": N}}.
Inputs: query (required), limit. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 8 tools
Each tool targets a distinct entity or operation: get tools retrieve single records by ID, search tools list with filters, and chart of accounts is a separate list. No overlap in purpose.
All tools follow the consistent pattern `qbo_<verb>_<noun>` where verb is `get_` or `search_`, and nouns are plural for search (customers, vendors, invoices, bills) and singular or collective for get (customer, invoice, vendor, chart_of_accounts).
8 tools cover core QBO entities (accounts, customers, vendors, invoices, bills) without being overwhelming. The count is well-scoped for a focused accounting server.
Only read operations are provided (get and search). Missing critical mutation tools (create, update, delete) for any entity, which severely limits the server's utility for typical accounting workflows.
Related MCP Connectors
The best QuickBooks Desktop MCP server on the planet: every operation on every record type
QuickBooks Online in Claude and ChatGPT: 221 tools, full ledger, multi-company, Canada + US, FR/EN.
QuickBooks MCP Pack — query customers, invoices, and accounts via QuickBooks Online API.
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceThis read-only MCP Server allows you to connect to QuickBooks Online data from Claude Desktop through CData JDBC Drivers. For full CRUD support, check out the first managed MCP platform: CData Connect AI (https://www.cdata.com/ai/).MIT
- FlicenseNot gradedqualityDmaintenanceAn MCP server for QuickBooks Online that enables managing customers, vendors, invoices, bills, payments, items, and more, along with financial reports, directly from Claude.-
- AlicenseNot gradedqualityCmaintenanceA local MCP server that exposes QuickBooks Online data and actions as callable tools for AI assistants, supporting entities like customers, invoices, bills, and financial reports.4MIT
- AlicenseNot gradedqualityCmaintenanceComprehensive MCP server for QuickBooks Online providing full CRUD operations on 29 entities (customers, invoices, bills, etc.) and 11 financial reports, enabling accounting data management via natural language.Apache 2.0