-
Notifications
You must be signed in to change notification settings - Fork 207
feat(catalog): Catalog capability for product discovery #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Introduces `dev.ucp.shopping.catalog` capability enabling platforms to search
business product catalog and perform targeted product+variant lookups.
Checkout capability assumes the platform already knows what item to buy (via
variant ID). Catalog capability fills the discovery gap—enabling scenarios like
"find me blue running shoes under $150" that lead to cart building and checkout.
Product (catalog entry)
├─ id, title, description, url, category
├─ price: PriceRange (min/max across variants)
├─ media[]: images, videos, 3D models (first = featured)
├─ options[]: dimensions like Size, Color
├─ variants[]: purchasable SKUs (first = featured)
├─ rating: aggregate reviews
└─ metadata: merchant-defined data
Variant (purchasable SKU)
├─ id: used as item.id in checkout
├─ sku, barcode: inventory identifiers
├─ title: "Blue / Large"
├─ price: Price (amount + currency in minor units)
├─ availability: { available: bool }
├─ selected_options[]: option values for this variant
├─ media[], rating, tags, metadata
└─ seller: optional marketplace context
- Free-text query with semantic search support
- Filters: category (string), price (min/max in minor units)
- Context: country, region, postal_code, intent
- Cursor-based pagination (default 10, max 25)
- Accepts product ID OR variant ID
- Always returns parent product with context
- Product ID → variants MAY be representative set
- Variant ID → variants contains only requested variant
- NOT_FOUND returns HTTP 200 with error message (not 404)
Location and market context unified into reusable types/context.json:
{
"country": "US", // ISO 3166-1 alpha-2
"region": "CA", // State/province
"postal_code": "..." // ZIP/postal
}
Catalog extends with 'intent' for semantic search hints.
REST:
POST /catalog/search → search_catalog
GET /catalog/item/{id} → get_catalog_item
MCP (JSON-RPC):
search_catalog
get_catalog_item
catalog.json references `../ucp.json#/$defs/response` for its response envelope, but this $def only existed in spec/ (manually added) - not in source/. This caused source/spec drift: regenerating spec/ would lose the generic response definition. Fix: - Add generic `response` $def to source/schemas/ucp.json - Change `response_checkout` and `response_order` to $ref the generic
Inline object definitions in search_request.filters weren't rendered
in generated docs (showed as plain "object" without properties).
Fix by extracting to referenceable schemas:
- search_filters.json: category + price filter definitions
- price_filter.json: min/max integer bounds (distinct from price_range
which uses full Price objects with currency)
|
Thanks Ilya. I am assuming we will add local store support in a later PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the expected language handling for text content coming from the business (e.g., product/variant descriptions and message content)? We should require an explicit content language provided by the platform/agent (via context) that reflects the user’s selected display language which cannot be inferred from context.country. The agent should render the content as-is and avoid automatic translation, since some fields (e.g., product name, variant name, legal/safety/liability disclaimers) must not be translated and may appear mixed within the same text blob.
|
|
||
| `media` and `variants` are ordered arrays. Businesses SHOULD return the featured | ||
| image and default variant as the first element. Platforms SHOULD treat the first | ||
| element as the featured item for display. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarify that media/variants ordering should be query-aware in search results (e.g., "yellow shoes" → yellow variant first), but default-based for direct lookups?
|
|
||
| Messages communicate business outcomes and provide context: | ||
|
|
||
| | Type | When to Use | Example Codes | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code defined here uses UPPER_SNAKE_CASE for error codes, but the schema examples use snake_case.
|
|
||
| **Empty Search** | ||
|
|
||
| When search finds no matches, return an empty array without messages. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we return a more explicit info message with the proper code (not_found)? This would make it clearer for the agent/platform to handle and avoid retrying the request.
|
|
||
| ### Business Outcomes | ||
|
|
||
| All application-level outcomes return HTTP 200 with the UCP envelope and optional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GET by ID is resource retrieval - if the resource doesn't exist, the URI is invalid → should return HTTP 404.
POST /catalog/searchwith no matches -> 200 OK (query succeeded, empty results)GET /catalog/item/{invalid_id}-> 404 Not Found (resource doesn't exist)GET /catalog/item/{valid_id}that's out of stock -> 200 + message (resource exists, business constraint)
| ```json | ||
| { | ||
| "ucp": {...}, | ||
| "product": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Add required fields description and price.
| "$ref": "price_filter.json" | ||
| } | ||
| }, | ||
| "additionalProperties": true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see not all newly add schema has this set, additionalProperties default is true, just to double check the expect value across all schema file added.
| "required": [ | ||
| "id", | ||
| "title", | ||
| "description", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should description optional, which should inherit from the product it belongs to?
| "format": "uri", | ||
| "description": "Canonical variant page URL." | ||
| }, | ||
| "category": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be product only property?
| }, | ||
| "description": "Variant media (images, videos, 3D models). First item is the featured media for listings." | ||
| }, | ||
| "rating": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be product only property?
| "$ref": "rating.json", | ||
| "description": "Variant rating." | ||
| }, | ||
| "tags": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be product only property?
raginpirate
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again... incredible to see these primitives come to life so "simply"!
Just a couple questions about unification I spotted.
| "response_checkout": { | ||
| "title": "UCP Checkout Response", | ||
| "description": "UCP metadata for checkout responses.", | ||
| "response": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify how this would work once you target the working branch: are we thinking that capabilities exposed through discovery would be the same as cart and checkout, or would it be a unique subset? I'm imagining this should be isolated and not explain how I do fulfillment from product discovery.
Either way, if we choose to include all capabilities, should it really expose their configs like it does at checkout, or just negotiate versioning?
| "currency": { | ||
| "type": "string", | ||
| "description": "ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP').", | ||
| "pattern": "^[A-Z]{3}$" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checkout doesn't push currency on every amount; it uses bare amounts and has a top-level contextual currency. Should we be thinking the same way for catalog?
Summary
Introduces the
dev.ucp.shopping.catalogcapability, enabling platforms to search merchant product catalogs and retrieve product/variant details. This fills the "discovery gap" before checkout—while checkout assumes you already have a variant ID, catalog enables scenarios like "find me blue running shoes under $150" that lead to cart building and purchase.The capability provides two operations: free-text search with filters and pagination, and direct product/variant lookup by ID. Both return a consistent Product structure containing variants, media, options, and pricing that seamlessly connects to checkout's
line_items[].item.id.Motivation
UCP's checkout capability assumes the platform already knows what to buy. But agents and platforms need to discover products first—browsing catalogs, comparing options, filtering by price or category. Even when a product ID is already known (e.g., from a product feed integration), platforms need live lookups to fetch current facts—price, availability, and other details that may have changed. Without a standardized catalog interface, every integration requires custom product APIs or out-of-band discovery mechanisms—N custom integrations instead of one standardized protocol.
Goals
intentcontext fieldline_items[].item.idNon-Goals
Detailed Design
Operations
search_catalogget_catalog_itemData Structures
Product (catalog entry):
Variant (purchasable SKU):
Key Behaviors
Ordering Convention:
media[]andvariants[]are ordered arrays. Merchants SHOULD return featured items first; platforms SHOULD treat first element as featured.ID Resolution:
get_catalog_itemaccepts product OR variant IDs:Error Handling: NOT_FOUND returns HTTP 200 with
messages[].type: "error"(not 404). All catalog errors useseverity: "recoverable"for programmatic handling.Transport Bindings
REST:
MCP (JSON-RPC):
Risks and Mitigations
metadataandadditionalPropertiesenable merchant extensions without protocol changes.intentfield enables semantic hints without prescribing implementation.Type of change
Checklist: