Disclosure: I built LedgerCrossing, the product this article is about. It's paid software, so I'm biased. The design problems are real either way, and I hope they're useful if you're wrapping a big legacy API in MCP.
QuickBooks Desktop is the Windows version of QuickBooks that a lot of businesses still run on Pro, Premier or Enterprise. It has no cloud API. The only way in is qbXML, an XML request format, through a COM interface on the machine where QuickBooks runs. That's why most QuickBooks MCP servers are for QuickBooks Online, which has a cloud API to wrap.
I built an MCP server for Desktop. These are the four problems that took the most work.
1. 240 operations don't fit in a tool list
qbXML has 240 request types: CustomerQuery, InvoiceAdd, ItemInventoryMod and so on. Each one has strict element ordering, required fields, maximum lengths, enums, and minimum QuickBooks versions. Get the element order wrong and QuickBooks rejects the whole request.
The two obvious designs both fail:
- One tool per operation. 240 tool definitions bury the model before it has read the user's question, and some clients cap how many tools they'll load at all.
- One generic "run qbXML" tool. The model has to know qbXML by heart, so it guesses field names and element order, and QuickBooks says no.
What worked was on-demand discovery with six tools:
| Tool | Job |
|---|---|
list_operations |
Search the catalog of 240 by name or category |
get_operation |
The full field reference for one operation: order, required, max lengths, enums, version gates |
validate_qbxml |
Check a request against the catalog without touching QuickBooks |
run_qbxml |
Execute it |
explain_error |
Turn a QuickBooks status code into what went wrong and what to change |
get_status |
Is QuickBooks up, which company file is open |
The catalog is built from Intuit's own qbXML reference (field names, types, order, lengths and version gates), so it's facts, not prose. The model pulls only what the current task needs: find the operation, read its rules, write the request, validate it, run it. A question like "which customers are 60 days overdue?" costs a handful of small lookups instead of the whole spec.
2. Validate before you write
validate_qbxml turned out to be the most important tool. It checks element order, required fields and lengths against the catalog and returns specific errors, so the model can fix a malformed request before QuickBooks ever sees it. A rejected request inside QuickBooks is slow and its error codes are cryptic. A rejected request in validation is instant and says exactly which field is wrong.
When QuickBooks does reject something, explain_error closes the loop: status code in, plain explanation and the likely fix out.
3. It can write to the books, so say so and add guardrails
An agent that can create invoices can create wrong invoices. I didn't want to pretend the server is read-only, because it isn't: run_qbxml has the same power as the HTTP API underneath it.
What we do instead:
-
Unbounded queries are refused by default. A request like "every transaction since 2004" on a 2 GB company file would become a long COM call that can't be cancelled. The server rejects queries without a bound (
MaxReturned, a date range, a modified-since filter) before they reach QuickBooks, unless the owner opts out. - One request at a time. QuickBooks Desktop handles one request at a time, so every client (MCP, HTTP, several people) goes through a single queue. Calls wait briefly instead of colliding.
- QuickBooks' own Audit Trail records every posted transaction, and the docs say plainly that agents can write.
- Per-person access from outside the office: each person signs in with their own account, and the owner can make someone read-only, limit their company files, or remove them.
4. Reaching a Windows box from wherever the AI runs
Most hosted APIs for QuickBooks Desktop, Conductor's MCP server among them, go through Intuit's QuickBooks Web Connector, which runs on the QuickBooks machine and checks in with the hosted service on a schedule (polling). We went the other way: the server runs beside QuickBooks and calls it directly, so there's no Web Connector and no polling between the agent and the books. That choice shaped the rest of this section.
The server has to run on the QuickBooks machine, because that's where the COM interface is. The AI usually doesn't. That gave us three paths:
-
Same machine: Claude Desktop can't call a localhost HTTP URL as a custom connector, because custom connectors are brokered through Anthropic's cloud. So there's a stdio mode (
LedgerCrossing.exe --mcp-stdio) and a.mcpbbundle that wraps it. -
Office network: the same server over streamable HTTP at
http://<machine>:8284/mcp, with an API key header. Claude Code, Cursor and VS Code use this:
claude mcp add --transport http quickbooks http://qb-server:8284/mcp --header "X-Api-Key: YOUR_KEY"
- Anywhere: claude.ai and ChatGPT custom connectors can't send an API key header. The choices are OAuth sign-in or no authentication at all, and no authentication isn't acceptable in front of someone's books. A self-run tunnel can't offer sign-in, so there's a managed relay (LedgerCrossing Remote) that gives the machine an HTTPS address with per-person sign-in. HTTP clients that can send a header can also use any tunnel you run yourself.
What I'd tell anyone wrapping a big legacy API
- Expose the catalog, not the API. Let the model look things up as it goes.
- Make validation a tool. Cheap, specific errors beat expensive, cryptic ones.
- Put guardrails where the danger is (unbounded reads, concurrent writes), and be honest that the agent can act.
- Decide early where the server runs and how clients will reach it. That shaped more of our design than the tools did.
LedgerCrossing also exposes the same thing as a plain HTTP API (POST qbXML, get qbXML back), and it works with anything that can make a web request. If you rely on an off-the-shelf app that syncs through the Web Connector, that vendor controls the connection, and this doesn't slip in underneath it.
Setup and client configs: https://ledgercrossing.com/docs/mcp. If you're comparing ways to connect Claude to QuickBooks Desktop, every option we know of, with prices, is here: https://ledgercrossing.com/connect-claude-to-quickbooks-desktop
Happy to answer questions about the design, or about qbXML's quirks, which are many.
I wrote this with help from an AI assistant; the experience and the design decisions are mine.
Top comments (0)