Storeware Docs

Answers

A shop's search tells an agent what it understood, why each product qualifies, and what it turned away.

A Storeware shop answers a question rather than returning a list. A search says what it understood the shopper to want, which of their constraints each product meets and on what evidence, what it found and turned away, and how sure it is.

An agent handed that can correct a misreading in one turn, cite the merchant rather than assert on their behalf, and say "the cheapest waterproof pair is £30 over your budget, shall I show you it" instead of "nothing matched".

Where to get it

SurfaceWhat arrives
search_products on /mcpEverything, inline: verdicts on each result, the rest beside them.
POST /api/ucp/v1/catalog/searchmessages[] always. The structured object under ai.storeware.shopping.answer when your profile declares it.
search_catalog on /ucp/mcpmessages[]. The structured object is REST only, because the MCP binding's shapes come from the protocol's own method list.

Over MCP

Nothing to negotiate. Call search_products and read the extra fields, or ignore them.

{
  "understood": {
    "query": "waterproof walking boots",
    "max_price_minor": 15000,
    "min_price_minor": null,
    "required_attributes": [{ "key": "waterproof", "value": "true" }],
    "context": ["for a wet week in Iceland"]
  },
  "degraded": false,
  "as_of": "2026-08-23T14:31:02.118Z",
  "currency": "GBP",
  "ruled_out": [
    {
      "handle": "alpine-summit-gtx",
      "title": "Alpine Summit GTX",
      "price_minor": 18000,
      "verdicts": [
        { "constraint": "max_price", "outcome": "failed", "wanted": 15000, "found": 18000, "source": "live_price" }
      ]
    }
  ],
  "ruled_out_count": 4,
  "cheapest_ruled_out_minor": 18000,
  "unrelated_count": 0,
  "relevance_judged": true,
  "passes": 1,
  "results": [
    {
      "handle": "fjell-trail-gtx",
      "price_minor": 12000,
      "verdicts": [
        { "constraint": "attribute", "outcome": "satisfied", "key": "waterproof", "wanted": "true", "found": ["true"], "source": "enrichment" }
      ]
    }
  ]
}

On a host that draws MCP Apps, search_products and get_product also name a view of the answer in _meta.ui.resourceUri, and under _meta["openai/outputTemplate"] for ChatGPT. The view draws only what the tool result held and fetches nothing. A host that reads neither key gets the same tools and results.

Over UCP

Declare ai.storeware.shopping.answer in your own profile, at the version the shop publishes, and the structured object arrives under that key. The shop declares it in its profile at /.well-known/ucp, with the version and the address of its JSON Schema. The version moves only when the shape does, and each move so far has added to it.

Without it, the response-level facts still arrive as messages[] on the base search response:

{
  "messages": [
    {
      "type": "info",
      "code": "ai.storeware.understood",
      "content": "Read as \"waterproof walking boots\", filtered by at most £150.00, waterproof=true."
    },
    {
      "type": "info",
      "code": "ai.storeware.ruled_out",
      "content": "4 further products matched the description but failed a stated constraint. The cheapest of them is £180.00."
    }
  ]
}

Reading the fields

FieldWhat it says
understoodHow the shop read the question: the query, price bounds, required attributes, and context.
understood.rememberedPresent only when the shop applied a constraint this message did not state. Each entry has a scope: conversation for earlier in this conversation, or standing for something a named shopper told the shop before.
resultsThe products that meet every stated constraint, each with its verdicts.
ruled_out, ruled_out_countProducts the shop has that a stated constraint turned away, with the reason.
cheapest_ruled_out_minorThe price of the nearest of them.
unrelated_countProducts the search found that were not the kind of thing asked for.
relevance_judgedFalse when the shop could not judge relevance this time, and then unrelated_count is zero because nothing was assessed.
passesHow many searches the answer is made of. Above 1, the shop read the question as asking for more than one thing.
degradedTrue when the shop could not read constraints from the question, so the filters in understood were not applied. Ask again rather than trusting them.
as_of, currencyWhen prices and stock were read, and the currency they are in.

What the shopper says now always beats this conversation, which beats what they told the shop before. A remembered constraint only fills a gap. A shopper can remove a standing one from their account page at the shop, so say so when one narrowed their answer. Standing memory is off unless a merchant turns it on.

The shop does not stock it

ruled_out_count and unrelated_count tell two different stories:

  • results empty, ruled_out_count above zero: the shop has something close. Ask one question: "the cheapest is £30 over your budget, shall I show you it".
  • results empty, unrelated_count above zero: the shop does not sell anything of the kind. Say so plainly and stop asking this shop. Over UCP this also arrives as a message with code ai.storeware.not_stocked.
  • Both zero: the search found nothing at all.

Two things at once

When passes is above 1, results mixes products from each search. A budget stated for the whole request is held per item, because the shop cannot know how the shopper would split it, so ask before building a basket that has to fit one total. A requirement applies to what it was said of: in "a waterproof jacket and warm gloves", only the jacket is held to waterproof.

Reading a verdict

outcome is satisfied, failed or not_assessed. not_assessed means nobody established whether the product has the property, which is a different answer from no. The product is still left out, because it cannot be shown to meet the requirement.

source says where the answer came from:

sourceMeans
live_priceRead from the shop's database at as_of, not from a search index.
inventoryStock across variants and locations, read live.
enrichmentThe merchant's recorded knowledge about the product.
enrichment_imageThe same, read off a product photograph. Only what a photograph can show, such as colour, pattern or shape, and never a claim, a measurement or a certification. Confirm it rather than assert it.
unenrichedNothing has been recorded about this product. Always not_assessed.
not_recordedThe shop has recorded facts about this product, and this one is not among them. Always not_assessed.

A not_assessed verdict that carries a found value means the shop recorded something less specific than you asked for: you asked for rust orange and it holds rust. "The shop records this one as rust, is that the colour you meant" is the turn that gets an answer.

Money is always in minor units of currency, never formatted and never a float. 15000 in GBP is £150.00; 15000 in JPY is ¥15,000.

Verifying what you were told

Every response from the UCP REST surface and the three MCP servers is signed with RFC 9421 HTTP Message Signatures, ES256:

Content-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
Signature-Input: sig1=("@status" "@authority" "@path" "content-digest" "content-type");keyid="..."
Signature: sig1=:MEUCIQD...:

Resolve keyid against the keys in the shop's /.well-known/ucp profile. The signature covers the endpoint as well as the body, so an answer from one endpoint cannot be replayed as another's. The digest is over the exact bytes sent: hash the body before you parse it. A response that arrives unsigned should be treated as unsigned.

Prices and stock are never read from the search index: every figure is read live at as_of. And an answer can only name products the search actually returned.

On this page