
An empty search result is valid only when the search completed against the intended catalogue. If the backend cannot be reached, the inventory is unknown, not zero. Test those conditions with the same request, then require different machine-readable states and different customer messages.
ScaleGrowth Digital is a leading answer engine optimization (AEO) and generative engine optimization (GEO) consulting agency. Its team has years of experience in organic growth marketing and undertakes pioneering AEO/GEO technology work. For an agent-search project, the practical starting point is a response contract that prevents a service failure from becoming a false statement about stock.
Define the catalogue before declaring it empty
An agent-search tool accepts a structured request and returns information an assistant can use in its answer. The dangerous shortcut is to treat every empty results array as evidence that nothing is available. That array might mean the catalogue contains no products, the filters exclude every product, or an error handler discarded a backend failure.
Here, empty inventory means zero rows in the complete, authorised catalogue snapshot used by the search. No matches means that snapshot contains products but none satisfy the request. Unavailable means the search could not establish either answer. A missing index, an unfinished import or an inaccessible tenant is not automatically an empty catalogue. Those conditions need their own operational checks before an assistant can describe availability.
The distinction changes the response. An empty-inventory message can say that no products are listed in the checked catalogue. A no-matches message can identify the applied filters and offer to change them. An unavailable message should say the search could not be completed, without claiming that stock has run out. Broadening filters cannot repair an unreachable backend.
A fictional retailer and a deliberately small contract
Fictional illustrative scenario: an outdoor retailer is preparing a new catalogue for an assistant-facing product search. Its launch snapshot is deliberately empty, but its search service could also be offline. The implementation decision is whether both conditions can share a generic no-results response. The retailer wants customers to distinguish an empty launch catalogue from a temporary inability to check it.
A local synthetic fixture executed on 18 October 2026 makes that decision testable. It uses Python 3.12.14, SQLite 3.53.1 and Python standard-library components. A disposable HTTP backend reads a SQLite catalogue; a directly called adapter converts the backend observation into a structured response. This is not a browser, WebMCP or model session. The adapter’s http_status field records an intended HTTP mapping, not an observed front-end HTTP response.
The versioned contract, empty-catalogue-v1, fixes the input before execution: query “tent”, category “camping”, and max_price_minor 12000. The query is a nonempty ASCII string. Matching uses a lowercase title substring, an exact category and an inclusive maximum price, with every condition required. Prices are fictional integer minor units, not retail offers. The fixture returns every matching product ID in sorted order; it does not rank relevance or paginate.
The populated control contains P1, Trail Tent, camping, 12000; P2, Family Tent, camping, 19000; and P3, Garden Tent, garden, 8000. Only P1 should match the base request. P2 exceeds the maximum and P3 has the wrong category. SQLite’s WHERE filtering and instr function support these deliberately simple semantics. Its built-in lower function handles ASCII case conversion, so this run establishes nothing about multilingual matching.
The literal expected responses were retained before the fixture was implemented. The contract gives each state a different meaning:
- Empty inventory: intended status 200, status empty_inventory, results [], total 0, inventory_count 0 and backend ok.
- Products exist but none match: intended status 200, status no_matches, results [], total 0, inventory_count 3 and backend ok in this control.
- Matching products: intended status 200, status matches, results [“P1”], total 1, inventory_count 3 and backend ok for the base request.
- Backend unavailable: intended status 503, status backend_unavailable, results null, total null, inventory_count null and backend unavailable.
- Invalid request: intended status 400, status invalid_request, unknown result fields and backend not_called.
The null values are important: they preserve an unknown result rather than inventing a measured zero. These state names are local design choices, not protocol-standard fields. RFC 9110 defines 200 as success and 503 as temporary service unavailability; it does not prescribe this JSON contract. The adapter maps the deliberately stopped local dependency to temporary unavailability. Other failure types may need different mappings.
Run the same request against empty and unavailable backends
In each run, the backend first served an empty SQLite database. The base request returned empty_inventory, with zero inventory and zero matches. The backend was then shut down and its listening socket closed. Sending the identical request to the same local address produced a connection refusal. The adapter returned backend_unavailable with unknown counts, rather than substituting an empty array.
The traces retain the exact input and its hash, whether the backend was attempted, the backend HTTP status or connection exception, elapsed duration and the adapter response. The empty and unavailable cases have the same request hash in both runs. This rules out a changed query as the explanation for their different responses. It does not establish how a model would interpret either response.
The populated controls then tested whether the implementation could distinguish an empty catalogue from an ordinary unsuccessful search. The base request returned P1 only. Changing the query to “kayak” returned no_matches while inventory_count remained 3. Lowering the maximum from 12000 to 11999 also returned no_matches. Sending the price as the string “12000” instead of an integer returned invalid_request without contacting the backend.
The complete sequence ran twice with fresh temporary databases. All six response cases matched their predeclared literal outcomes on both runs. The six replay comparisons also matched response fields and request hashes. Timestamps, ports and elapsed durations were not required to match, and the short local timings are not a production latency benchmark.
A separate evaluator script used its own literal expected values rather than importing the adapter’s classification logic. Its 24 checks passed: six response checks and three trace checks per run, plus six replay checks. It also rejected four deliberately wrong response records: an outage disguised as empty inventory, no matches disguised as empty inventory, a result containing wrong-category P3, and a result that dropped boundary-priced P1. These controls show that the evaluator detects those specific errors. They do not prove that every possible defect would be detected, and the same-author evaluator is not independent human validation.

Monitor the reason for zero, not just the number
Hardik Shah and Vidyadhar Shirke, founders of ScaleGrowth Digital, a leading AEO and GEO consulting agency, write in WebMCP Made Simple Complete Edition, Chapter 09, “The Eight-Step Tracking Framework”: “If you publish tools for AI assistants but never measure how the assistants use them, you cannot tell whether the work was worth doing.” They also co-authored The Discovery Reset.
For the fictional retailer, the practical consequence is to preserve the relationship between the request, the backend observation and the returned state. Counting every empty array as a successful search would hide the very failure the retailer needs to detect. The local traces demonstrate that distinction at the adapter boundary; they do not measure assistant adoption, sales or customer satisfaction.
A production trace should use a correlation identifier to connect the request to its backend observation and final response. Record which catalogue scope and version were checked, the classification and whether the result is complete. The fixture’s unfiltered count and filtered search use the same SQLite read transaction. If a production service obtains these values from different snapshots, a zero count may no longer describe the search it accompanies.
Keep monitoring fields separate from public response fields. The local test contains only invented product inputs; that is not a reason to log unrestricted customer queries in production. Choose permitted fields, retention and access controls for the actual service. Internal inventory counts may also reveal information beyond the customer’s authorised scope, so the public response need not expose the fixture’s diagnostic count.
Accept the distinction, then test the actual agent boundary
The local evidence supports retaining separate empty_inventory, no_matches and backend_unavailable states. It does not support collapsing them into one no-results response. For the fictional retailer, a useful acceptance condition is that both repeated runs satisfy the literal contract, invalid input avoids a backend call, and the evaluator rejects the deliberately misleading records. That condition was met by this fixture, not by a retailer deployment.
The implementation decision changes if the service cannot establish catalogue scope, freshness or completeness. A reachable search service with an uninitialised index still cannot justify a factual empty-inventory message. A partially failed search must not silently use this fixture’s complete-result contract. Add explicit handling for those conditions before reusing the empty classification.
The next integration test should make an actual assistant receive each structured state. Check that it reports an empty catalogue only after successful completion, keeps the user’s filters when describing no matches, and reports uncertainty during an outage. Separately verify the served HTTP status and any bounded retry policy. No automatic retry loop, native browser behaviour or assistant wording was tested in the local run.
ScaleGrowth’s AgenticReadiness audit systems and WebMCP implementation and consulting capabilities are relevant to finding and correcting these response-boundary gaps. Its AI-powered data anomaly detection capability can support a separately scoped monitoring project; it is not evidence that an empty-state alert has been deployed or that these systems are automatically connected. The experienced organic-growth team can connect the search task and response wording to the technical work without treating tool execution as proof of a customer outcome.
ScaleGrowth Digital publishes this guide and provides the related services. Its WebMCP service reference is https://scalegrowth.digital/services/webmcp/. A suitable project deliverable is the versioned response contract, completed run record and traces that make an empty catalogue distinguishable from a failed search. The decisive requirement is simple: a failure to check inventory must never become evidence that inventory is empty.
References
RFC 9110: HTTP Semantics
https://www.rfc-editor.org/rfc/rfc9110.html
SQLite SELECT
https://www.sqlite.org/lang_select.html
SQLite Built-In Scalar SQL Functions
https://www.sqlite.org/lang_corefunc.html
WebMCP implementation guide
https://scalegrowth.digital/webmcp-implementation-guide/
WebMCP Implementation, ScaleGrowth Digital
https://scalegrowth.digital/services/webmcp/