TLDR: The official Intuit QuickBooks Online MCP server (the Node/TypeScript one, intuit/quickbooks-online-mcp-server) had two silent bugs in its search handlers — both landed in PR #18. If you're using it and searches look "successful" but return nonsense, this is why.

The Setup

I've been building a full QuickBooks automation for a cancer education business — using the official Intuit MCP server (MCP = Model Context Protocol, the open standard that lets Claude plug into external services), running locally via stdio and wired into Claude Code.

144 tools. Full CRUD across 29 entity types. It's genuinely impressive.

Or it's supposed to be.

The Wall I Hit

I switched to the official server specifically because my old custom Python tool had been lying to me for months — it silently only searched Purchase objects, ignored invoices, deposits, journal entries, and then confidently told me it "reviewed 325 transactions."

So I pulled the Intuit repo, built it, connected it.

And search_purchases started returning... an object. Not an error. No crash. Just an object with a QueryResponse key sitting inside the result field.

Claude would receive it, try to iterate over transactions, and produce garbage.

Why It Happened

The node-quickbooks SDK uses a callback pattern. When findPurchases succeeds, the callback gets the full API response envelope:

{ QueryResponse: { Purchase: [...], maxResults: N, startPosition: 1 }, time: '...' }

The handler was returning that whole object as the result — not the array inside it.

Two lines. That's all that was wrong:

- result: purchases,
+ result: purchases?.QueryResponse?.Purchase || [],

Same bug in search_employees. Two handlers, identical mistake.

The Bonus Bug

There was a second one hiding right next to it.

The buildQuickbooksSearchCriteria helper only recognized filters as the key for search criteria. But several tool schemas passed them as criteria instead. So when you called search with { criteria: [{field: 'TxnDate', value: '2026-01-01', operator: '>='}], limit: 10 }, the criteria were silently dropped — you'd search with pagination but no filter. Every transaction came back.

No error. No warning. Just… everything.

The fix was a one-liner alias: const filterList = options.filters ?? options.criteria.

Why This Matters to Me

This is the HTTP 200 ≠ success lesson, again, in a new costume.

The search worked. It completed. It returned data. Claude saw isError: false. And the result was completely wrong.

I've started baking this into how I think about any SDK wrapper in an MCP handler: the SDK's callback is almost always an envelope, and your handler's job is to peel it before handing data up the chain. Don't return what the callback gave you. Return what the caller actually asked for.

If you're running the intuit/quickbooks-online-mcp-server, grab PR #18 — it adds 16 tests covering all three fixes and brings coverage to 100%. Worth the pull.

P.S. If search_vendors or search_bills are also misbehaving, check whether those handlers have the same envelope issue — the pattern is consistent enough that I wouldn't be surprised if it shows up elsewhere.