FCHubFCHub.co

Proof

93 questions a real merchant asks, run against a real store, every answer checked against the database. Here is what broke and what it costs now.

Every MCP server's documentation says it works. Mine used to say that too, and it was wrong in eleven interesting ways.

So I stopped claiming and started measuring. 93 questions a merchant would actually ask, run against a live FluentCart store, with every number checked against MySQL rather than against another endpoint — because two endpoints agreeing proves only that they agree. Seven of FluentCart's report routes returned three different totals for the same store, and every one of them answered HTTP 200.

What "works" has to mean

A tool returning 200 OK proves nothing. tax_rate_list returned 200 on nothing at all. shipping_zone_states returned 200 with an empty list for every country on earth. report_product returns 200 with a revenue figure that is wrong by however many multi-line orders you have taken.

So each scenario is scored on three things, and it only passes if all three hold.

Discovery

Does search surface the right tool in the top five? In the default mode, search IS the interface — a tool ranked sixth may as well not exist.

Answer

Does the chain return the facts asked for, checked against the database directly? Not against another endpoint. Not against a fixture.

Cost

What did the whole chain spend? A correct answer at 50,000 characters fails on any real context budget.

The tests are in the repository

tests/integration/scenarios-*.test.ts, nine files, run against local Docker. The harness is tests/integration/support/scenario.ts. Nothing here is a screenshot of a good day.

Eleven things that were broken

Not typos. Every one of these returned a plausible answer that was wrong, which is the expensive kind — an agent reports it rather than retrying.

What it looked likeWhat was actually happening
"Find repeat customers" returned nobodyThe report could never return a row, on any store. FluentCart filters the underlying orders by order_status unconditionally, so with none supplied the predicate is status IN (NULL). My tool did not expose the key — and even if it had, the parameter allowlist held orderStatus in camelCase while the route reads snake_case. Three faults stacked.
Store revenue reported as 4,962.70The store took 4,738.69. The query joins order items grouped by (order_id, object_id) and then sums the order-level total, so an order is counted once per variation it contains, and an order with no line items is dropped entirely. The overstatement grows with every multi-line order you take.
"What tax do I charge in Poland?" → a tidy 23/8/0Read from a static file bundled inside the plugin, not from the store. The store's actual Polish rates pointed at tax classes that had been deleted.
State-level shipping: "your country has no states"The tool sent country; the route reads country_code. Every country on earth came back empty. The US has 54.
"This customer's orders" returned every order in the shop/orders ignores customer_id in every spelling — customer_id, params[customer_id], customerId, filters[customer_id] — and does not reject it.
Storefront search returned the same ten products for everythingIt sent search; the controller reads post_title. "shirt" and "zzzznothing" returned byte-identical payloads.
"The most expensive thing I sell" → the wrong productThe route's real paging parameter is current_page; page and per_page are read by nothing. The true answer was on page two, at more than four times the price reported.
"Which shipping methods can reach Germany?" → noneThe tool declared no parameters at all, so it could only take the branch where no destination was given — filing every working method under "does not reach here".
Sorting customers by spend hid the biggest spenderpurchase_value is a documented sort key and is empty on every row. The store accepts it, sorts by nothing, and returns a plausible page. The top spender was absent by a factor of 45.
A missing product said "Permission denied"An ORM not-found escaping through a permission callback. An agent reads that as a credentials failure and sends you to check your application password.
Asking for a subscription that does not existCost 573 characters to say four words, printed the body twice, and shipped env: "dev" plus your full store URL on every 404.

Some of these are upstream

Several live in FluentCart itself, not in this server. I cannot fix those from here — so the tools say so, in the description, in the words that matter. A tool that knows it is lying and admits it beats a tool that does not know.

What it costs now

Same questions, same store, measured before and after. Every figure is a real payload, not an estimate.

QuestionBeforeAfter
"Show me my tax setup"failed — 51,773 chars, over the cap688
"What are the default VAT rates?"failed — 59,814708
"What does a shopper see?"failed — 53,762396
"Which products have no SKU?"19,739457
"What has sold out?"13,088222
"This customer's order history"13,0504,432
"What labels can I use?"12,5814,761 — or 544 for ten
"Show me order 59"11,3035,510
"Find products matching 'shirt'"2,446484
"Show me the latest orders"2,086378
A not-found error573103

Where the weight went

Mostly repetition. One order carried its two addresses three times over, each address restating itself twice more, and every line item carrying the variant's entire catalogue record. A print template shipped 38,669 characters of HTML nobody asked for. A product search returned three separate descriptions of the same image and every price twice, once as the HTML entity 8.00€.

The full run

93 scenarios, all passing, 193,413 characters across every sweep. search is the rank of the answering tool (— means the scenario is reached from another tool's output rather than by searching).

Three answers worth reading twice

"What are my total sales?" costs zero characters and makes zero calls. A store taking EUR and PLN has no single total, so the tool refuses rather than adding them together. A number that means nothing is worse than a question you have to ask more precisely.

"Which customers have bought more than once?" costs four calls, because FluentCart takes one order status at a time. The scenario says so in its output, and points out that the plain customer list answers the same question in one call for less. I would rather write that down than hide it behind a tidier number.

"How many t-shirts do I have left in each colour?" is two calls and 1,493 characters. It used to be a coin toss: 27 of the store's 76 variants reported in-stock beside total_stock: 0, because the projection returned an inert counter and dropped the flag explaining it. Both readings — "in stock" and "none left" — were available, and one of them was wrong.

Known boundaries

Because a proof page that only lists wins is a brochure.

  • Diagnostic reports stay labelled as diagnostic. If FluentCart ignores a range, mixes currencies or returns an empty figure despite existing data, the tool says so rather than promoting the response to a metric.
  • The release does not execute destructive or money-moving work. Refunds, subscription cancellation, deletions, bulk actions, order-status changes and marking paid are absent.
  • Compatibility is discovered, not guessed from a version string. A route missing from the connected store produces a smaller registry, not a tool that fails after registration.

Run it yourself

Everything above is reproducible: npm run test:integration:local against your own store. If your numbers differ from mine, I would genuinely like to know — open an issue.

On this page