feat: get product operation for catalog.lookup#195
feat: get product operation for catalog.lookup#195igrigorik wants to merge 1 commit intofeat/catalog-capabilityfrom
Conversation
Add a dedicated single-product retrieval operation (get_product / POST /catalog/product) to the Catalog Lookup capability, separating two fundamentally different access patterns: - lookup_catalog: batch identifier resolution (cart validation, wishlists, list display) - get_product: single-product detail with interactive variant narrowing (PDP rendering) The core addition is interactive option selection via `selected` and `preferences` parameters, modeling the standard product detail page interaction where a user progressively picks options (Color, Size) and the UI updates availability in real time. Option selection: - `selected`: partial option selections the user has made so far - `preferences`: relaxation priority order — when no variant matches all selections, the server drops options from the end of this list first, preserving higher-priority choices - Response always includes `product.selected`: the effective option selections that determine the featured variant, the variant subset, and all availability signals - Clients detect relaxation by diffing their request against product.selected Availability signals on option values (relative to product.selected): - available=true, exists=true → purchasable (selectable) - available=false, exists=true → out of stock (disabled/strikethrough) - available=false, exists=false → no variant for this combination (hidden) Variant semantics: - All returned variants match product.selected — this is the filtering anchor, not just the availability anchor - Rename variant `selected_options` → `options` to separate variant identity (what a variant IS) from user selection state (what the user CHOSE) - Move `input` correlation from base Variant into operation-specific `lookup_variant` extension via allOf, since correlation is a lookup concern not intrinsic to variants
|
|
||
| #### Product Not Found | ||
|
|
||
| When the identifier does not resolve to a product, return HTTP 404: |
There was a problem hiding this comment.
REST docs specify POST /catalog/product returns HTTP 404 for not found, but rest.openapi.json only declares a 200 response for this operation (no 404, no 400 for invalid selections/preferences, no 401/403).
Suggested fix: update OpenAPI responses for /catalog/product to include at least 400, 401, 403, 404, and reference the standard error schema.
| | **Batch Lookup** | `lookup_catalog` / `POST /catalog/lookup` | Retrieve multiple products by identifier. | | ||
| | **Get Product** | `get_product` / `POST /catalog/product` | Retrieve full detail for a single product. | | ||
|
|
||
| Both operations accept product and variant identifiers. They differ in cardinality |
There was a problem hiding this comment.
The “Both operations accept product and variant identifiers” table implies get_product supports the same identifier surface as lookup_catalog (SKU, URL, handle, etc.), but the later Get Product → Supported Identifiers section says “The id parameter accepts a single product ID or variant ID.” These are materially different guarantees.
Suggested fix: explicitly state:
“MUST support product ID + variant ID”
“MAY support SKU/URL/handle/etc.” (list optional identifiers)
and add a normative note: identifiers are opaque strings and MUST NOT be dereferenced or fetched as URLs.
| "type": "object", | ||
| "allOf": [{ "$ref": "types/product.json" }], | ||
| "properties": { | ||
| "selected": { |
There was a problem hiding this comment.
detail_product.selected is optional and the description says “Present when the request includes selected options,” but the docs in lookup.md say the response MUST include product.selected and that it determines featured variant + availability anchor.
search_catalogandlookup_catalogare discovery operations — they return multiple products with featured variant(s). But once a user picks a product, the agent needs a different interaction: full product detail with all options, real-time availability as the user selects options (Color, Size), and the exact variant to purchase. This is the product detail page (PDP) flow, which is best modelled as a distinct operation — this pattern conforms to common API shapes in the wild.get_productis a single-resource operation (part ofdev.ucp.shopping.catalog.lookup) for servicing purchase flow decisions. It returns one product with a relevant subset of variants, option-level availability signals, and support for interactive variant narrowing.REST:
POST /catalog/productMCP:
get_producttoolExample request
{ "id": "prod_abc123", "selected": [ { "name": "Color", "label": "Blue" } ], "preferences": ["Color", "Size"], "context": { "country": "US" } }Only
idis required.selectedandpreferencesare for interactive narrowing.Example (redacted) response
{ "product": { "id": "prod_abc123", "title": "Runner Pro", "price_range": { "min": { "amount": 12000, "currency": "USD" }, "max": { "amount": 15000, "currency": "USD" } }, "options": [ { "name": "Color", "values": [ { "label": "Blue", "available": true, "exists": true }, { "label": "Green", "available": false, "exists": true } ] }, { "name": "Size", "values": [ { "label": "10", "available": true, "exists": true }, { "label": "11", "available": false, "exists": false } ] } ], "selected": [{ "name": "Color", "label": "Blue" }], "variants": [ { "id": "var_abc123_blue_10", "sku": "RP-BLU-10", "title": "Blue / Size 10", "price": { "amount": 12000, "currency": "USD" }, "availability": { "available": true }, "options": [ { "name": "Color", "label": "Blue" }, { "name": "Size", "label": "10" } ], "media": [{ "type": "image", "url": "https://cdn.example.com/runner-pro-blue.jpg" }] } ] } }Iterative Flow
get_product(id: "prod_abc123")— no selections. Server returns the product with featured variant and option map.get_product(id: "prod_abc123", selected: [{name: "Color", label: "Blue"}]). Response narrows:product.selectedconfirms Blue, variants are all Blue, availability on Size values updates to reflect Blue inventory.selected: [{Color: Red}, {Size: 15}]withpreferences: ["Color", "Size"]. No Red/15 exists. Server relaxes from the end ofpreferences— drops Size, keeps Color. Responseproduct.selectedis[{Color: Red}]. Agent diffs request vs responseselected, sees Size was dropped, and can surface that to the user.Each round-trip is stateless. The agent sends the full selection state, the server returns the full product state.
Key Design Decisions
product.selectedis the response anchor. It determines the featured variant, the variant subset, and all availability signals. One concept, not three.product.selected. No "adjacent" or "contextually relevant" variants outside the selection. Availability context for non-matching options is carried byoptions[].values[].available/exists, not the variant array.selected_options→optionson variants. Separates variant identity (what a variant is) from user selection state (what the user chose). These were previously conflated under the same name.inputcorrelation moved tolookup_variant. Correlation is a batch-lookup concern, not intrinsic to variants. Base variant type stays clean; operation-specific extensions viaallOf.product) not array. Single-resource semantics — not found is an error (404 /-32602), not an empty result.Checklist