Skip to main content
Glama
rgellis

quickbooks-online-mcp

by rgellis

quickbooks-online-mcp

MCP server with complete coverage of the QuickBooks Online Accounting API v3.

Built on quickbooks-online-sdk, which generates its entity registry and models from Intuit's published documentation and verifies that coverage mechanically.

What this is not

It holds no credentials of its own and knows nothing about whose books it reads. Which Intuit app, which company, and how the authorization is performed are decisions for whoever deploys it. Point it at one QuickBooks company; to serve several, run it more than once.

Related MCP server: quickbooks-online-mcp-server

Access is a property of the deployment, not the caller

QuickBooks issues one OAuth token per app per company. Only an admin can authorize an app, and a second admin connecting disconnects the first. Intuit's own guidance is to authorize once as master admin and map your users to that single token. There is no per-user token and no per-user enforcement.

So every call this server makes carries the same company-wide access, whoever asked. This server does not pretend otherwise. There is no role file and no per-caller gate, because a gate here would resemble QuickBooks permissions without being them — and something that looks like "Jason has read-only access to the books" while the server holds full access is worse than no control at all.

What genuinely differs is what a deployment can do:

QBO_READ_ONLY

refuses every write before a request is built

--groups

which tools exist; omit writes and there is nothing to call

MCP_AUTH

who may reach this server at all

If two people need different access, run two deployments. Separate Intuit apps, separate tokens, separate configuration — a read-only instance and a read-write one, and people connect to whichever matches their authority. That is how the API is built to work, and it is the only arrangement where the restriction is real rather than advisory.

Identity is still recorded: every write is logged with the caller from the verified token. Knowing who asked is worth having even when everyone who can ask could have asked for anything.

Who may connect

MCP_AUTH picks the provider: none (default, right for stdio), jwt, or oidc against any OpenID Connect issuer.

Signing in is not the same as being allowed in, and which of those you get depends on the provider.

An organisational provider answers both at once. If someone is assigned to the app in Okta or Entra, they are permitted; membership is the decision.

A consumer provider answers only the first. Anyone in the world with an Intuit account completes a Sign in with Intuit flow successfully, and Intuit exposes no way to ask whether they have anything to do with the company whose books this server reads. Intuit's own single sign-on documentation is explicit that the app maps Intuit identities to its own users — authorization is yours to decide, not theirs to report.

So with a consumer provider, this is not optional:

MCP_OIDC_ALLOWED_SUBJECTS=1182d6ec-2a1f-4aa3-af3f-bb3b95db45af,...

Unlisted callers are refused at the token verification boundary, before any tool runs. The server warns at startup if oidc is configured with no list.

Key it on the subject, not an email. Intuit's own guidance: "an Intuit account can have multiple emails at various points in time. However, the sub value never changes." MCP_OIDC_ALLOWED_EMAILS exists too, and an address is refused when the provider reports it unverified — an unverified address may belong to somebody else, which is the whole reason subjects are the better key.

Signing in with QuickBooks credentials

Intuit is itself an OpenID Connect provider, so users can sign in with the QuickBooks credentials they already have and you need no identity provider of your own:

MCP_AUTH=oidc
MCP_OIDC_CONFIG_URL=https://developer.api.intuit.com/.well-known/openid_configuration
MCP_OIDC_SCOPES=openid,email,profile
MCP_OIDC_ALLOWED_SUBJECTS=<sub>,<sub>

Request only those scopes. Adding com.intuit.quickbooks.accounting would make every sign-in a fresh company authorization, and Intuit disconnects the previous one when that happens — each person signing in would break this server's own connection to the ledger.

A caveat worth stating plainly: the ID token's realmId claim looks like it would let the server verify a user belongs to this company, which would be better than any list. It does not work. Intuit documents that realmId "is returned whenever apps specify the QuickBooks Online API or Payments API scopes in authorization requests" — so obtaining it requires exactly the scope that breaks the connection. The list is the answer.

Tools

24 tools in seven groups, selected with --groups.

Group

Tools

core

check_connection, query_quickbooks, get_entity, list_entities, describe_entity, list_reports

accounts

list_accounts

reports

get_report, get_profit_and_loss, get_balance_sheet, get_general_ledger, get_trial_balance

sales

list_invoices, list_customers, list_payments

expenses

list_bills, list_vendors, list_purchases

sync

get_changes

writes

create_entity, update_entity, delete_entity, void_transaction, create_journal_entry

Named tools cover the common path; query_quickbooks reaches everything else, so anything a structured tool does not support goes there rather than accreting another parameter.

Omitting writes is stronger than any flag: the tools are not registered, so there is nothing to call.

Enabling writes

Two independent brakes, and they work at different levels.

QBO_READ_ONLY decides whether writes are permitted. It defaults to true, and the write tools still appear — calling one returns:

Refusing POST journalentry: this client is in read-only mode. Set read_only=False (QBO_READ_ONLY=false) to permit writes.

The tools stay visible on purpose, so the refusal can say what to change. Hiding them would leave you wondering why the server cannot do something this README says it does.

--groups decides whether the write tools exist. It defaults to all, which includes them.

To permit writes, set the variable:

QBO_READ_ONLY=false

To remove them entirely instead, leave the group out:

./main.py --groups core,accounts,reports,sales,expenses,sync

The second is the stronger control, and the two fail differently. A mistyped variable — QBO_READ_ONLY=flase — silently permits writes; a tool that was never registered cannot be called whatever the environment says. A deployment with no business writing should do both.

Setting one without the other does nothing useful: the group without the flag gives you tools that always refuse, and the flag without the group gives you nothing to permit.

Defaults differ by layer, deliberately. The SDK is a library and lets its caller write unless told otherwise (QboClient(..., read_only=True)). This server refuses by default, because a model calling tools is a different proposition from code someone wrote on purpose. A deployment pointed at a live general ledger should be stricter still — see the deployment wrapper, which turns both brakes on and expects you to turn them off deliberately.

Every response says where it came from

source, asOf, and derived on everything; period where one applies; count/returned/omitted on anything list-shaped, so a truncated answer cannot be mistaken for a complete one. Money is a decimal string, never a float.

Reports are checked before they are returned

A report whose stated total its own line items disprove raises instead of being handed back, as does a balance sheet that does not balance. The figure is withheld, because a number that fails its own arithmetic is wrong rather than uncertain.

A QuickBooks behaviour worth knowing

Some fields cannot be filtered or sorted on, and QuickBooks does not consistently say so. ORDER BY AcctNum returns HTTP 400. Filtering Purchase by PaymentType returns zero rows — against a company with 202 credit card purchases, with no error anywhere.

A silently empty result reads as a fact, which makes it worse than a rejection. So the documented capability of every field is checked before a query is sent, and a refusal names the fields that can be used instead.

Requirements

Docker. Nothing else — no Python, uv or Homebrew on the host.

./scripts/dev check                  # ruff + pyright (strict) + pytest
./scripts/dev run ./main.py --list-tools
./scripts/dev shell

Configuration

Copy .env.example to .env.

QBO_CLIENT_ID / QBO_CLIENT_SECRET / QBO_REALM_ID

required; one company

QBO_TOKEN_STORE

where the rotating refresh token lives

QBO_READ_ONLY

defaults true; refuses writes before any request is built. See Enabling writes

QBO_MAX_ROWS

rows returned in full before a response is summarised

MCP_TRANSPORT

stdio (default) or http

MCP_AUTH

none (default), jwt, or oidc — who may connect

Seed the token store with the SDK's scripts/get_refresh_token.py. This server never runs the authorization step.

Testing

220 tests · 100% statement and branch coverage · pyright strict, 0 errors

No test reaches QuickBooks.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    Enables interaction with the QuickBooks Online Accounting API to manage customers, invoices, expenses, and payments through MCP-compatible clients. It supports comprehensive financial workflows and the generation of reports like Profit and Loss or Balance Sheets.
    133
    2
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    A comprehensive Model Context Protocol server providing full CRUD operations for 29 QuickBooks Online entities and 11 financial reports.
    387
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables full CRUD operations on 29 QuickBooks Online entity types and 11 financial reports via natural language, allowing users to manage customers, invoices, payments, and more through MCP-compatible clients.
    Apache 2.0
  • A
    license
    C
    quality
    D
    maintenance
    Provides complete QuickBooks Online API integration for Claude Code and other MCP-compatible clients, enabling full CRUD operations on 29 entity types and 11 financial reports.
    100
    Apache 2.0