# Agent commerce with mock.shop

mock.shop supports [Universal Commerce Protocol (UCP)](https://ucp.dev)
version **2026-08-25** for catalog search and mock carts. Each store has its
own catalog and carts, selected by the request hostname. The apex `mock.shop`
serves the default demo store.

## Discover a store

Read `https://mock.shop/llms.txt` to choose a store. For example:

```sh
curl https://pets.mock.shop/.well-known/ucp
curl https://pets.mock.shop/api/ucp/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```

The discovery document advertises `dev.ucp.shopping.catalog.search` and
`dev.ucp.shopping.cart`, plus the MCP endpoint on that same host. The endpoint
is anonymous, accepts one JSON-RPC request per POST, and uses the MCP SDK's
standard `initialize`, `ping`, `tools/list`, and `tools/call` methods. It does
not need a session ID or a persistent event stream.

## Publish your agent profile

UCP tool calls require a profile describing your agent's supported protocol
version and capabilities. Host this JSON on a public HTTPS URL, such as
`https://your-agent.example/ucp-profile.json`, with
`Content-Type: application/json` and `Cache-Control: public, max-age=60`:

```json
{
  "ucp": {
    "version": "2026-08-25",
    "services": {},
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [{
        "version": "2026-08-25",
        "spec": "https://ucp.dev/2026-08-25/specification/shopping/catalog/search",
        "schema": "https://ucp.dev/2026-08-25/schemas/shopping/catalog_search.json"
      }],
      "dev.ucp.shopping.cart": [{
        "version": "2026-08-25",
        "spec": "https://ucp.dev/2026-08-25/specification/shopping/cart",
        "schema": "https://ucp.dev/2026-08-25/schemas/shopping/cart.json"
      }]
    },
    "payment_handlers": {}
  },
  "keys": []
}
```

`initialize` and `tools/list` can run without a profile. Every `tools/call`
request includes its URL at `params.arguments.meta["ucp-agent"].profile`.
Only capabilities present in both the store's and your agent's profile can
run. Profiles are cached for one minute, must use HTTPS on port 443, and
cannot redirect or resolve to private addresses.

You can run mock.shop locally and call its MCP endpoint using the configured
development shop host. The public-address restriction applies to the agent
profile that mock.shop fetches, not to the incoming MCP request. Use a public
HTTPS profile for local integration testing; profiles hosted on localhost or
private networks are rejected. Unit tests use a stubbed profile fetch.

## Search the catalog

Send this JSON body to the store's `/api/ucp/mcp` endpoint, replacing the
profile URL with your own:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "search_catalog",
    "arguments": {
      "meta": {"ucp-agent": {"profile": "https://your-agent.example/ucp-profile.json"}},
      "catalog": {"query": "dog", "pagination": {"limit": 10}}
    }
  }
}
```

Results appear in `result.structuredContent`, with a JSON text copy in
`result.content` for MCP clients that use text. Product variants have IDs
that can be used as cart item IDs. Search uses mock.shop's existing text
matching; an empty `catalog` object browses the store.

Supported filters are `catalog.filters.categories` (Shopify taxonomy IDs
returned in product categories) and `catalog.filters.price.min` / `max`.
Prices and cart totals are integers in ISO 4217 **minor units**: USD 18.00
is `1800`; JPY 1800 is `1800`. The store's currency is fixed. If supplied,
`context.currency` must match it. Other context hints do not change mock prices
or localization. Search and cart requests accept optional `attribution` and
`signals` objects; carts also accept `buyer`. They are validated but are not
saved to the cart or used for personalization, tracking, or checkout. These
objects are filtered from request parameter logs.

Search returns up to 25 products per page and up to 100 variants per product.
Use the returned `pagination.cursor` with the same query and filters for the
next page. JSON key order, empty options, and nonbinding context hints do not
affect pagination. Cursors expire after one hour. The Storefront GraphQL API remains
available for larger variant lists and other catalog operations.

## Build and manage a cart

Copy a variant's `id` from search into `item.id`:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "create_cart",
    "arguments": {
      "meta": {
        "ucp-agent": {"profile": "https://your-agent.example/ucp-profile.json"},
        "idempotency-key": "550e8400-e29b-41d4-a716-446655440000"
      },
      "cart": {
        "line_items": [{"item": {"id": "VARIANT_ID_FROM_SEARCH"}, "quantity": 2}]
      }
    }
  }
}
```

| Tool | Arguments besides `meta` | Behavior |
| --- | --- | --- |
| `create_cart` | `cart.line_items` | Creates a mock cart. |
| `get_cart` | `id` | Reads the cart and estimated totals. |
| `update_cart` | `id`, `cart.line_items` | Replaces the entire item list. An empty list clears it. |
| `cancel_cart` | `id` | Removes the cart. Further reads return `not_found`. |

Cart writes require a random `meta["idempotency-key"]` with at least 128 bits
of entropy, such as UUIDv4 (22–255 characters). Retry with the **identical JSON
request bytes**, including the JSON-RPC `id`, metadata, whitespace, and key
order. Use a new key whenever the body changes. A reused key with a different
body returns JSON-RPC error `-32000` with `data.code: idempotency_conflict`.
Successful write responses are retained for 24 hours, scoped to the store,
agent profile URL, and operation. Storage failures return HTTP 503 without
committing cart changes; retry the identical request.

Profile discovery or version failures return JSON-RPC error `-32001`. An
incompatible capability returns a UCP tool result with `isError: true` and
`capabilities_incompatible`. Optional metadata and nonbinding context hints
are accepted; only `context.currency` affects validation. Other context
fields do not change mock prices, language, availability, or checkout.

Carts use the existing mock.shop storage, IDs, 10-day renewable expiry,
validation, and limits. Up to 250 lines can be supplied in one request.
Updates are atomic: invalid items leave the previous cart intact. A cart or
variant from another store cannot be used here. The same cart IDs work with
the store's Storefront GraphQL API.

Quantities count whole items. An optional `item.quantity_unit` can explicitly
assert `unit: "C62"`, with `scale` omitted or set to `0`, and a `display_text`
string. Its display text and optional `increment` do not change mock quantities.
Other sale units are rejected with a recoverable `invalid_input` result.
Line `id` and `parent_id` strings are accepted on updates, but the mock rebuilds
flat cart lines and does not retain those identifiers or nested relationships.

## Existing mock checkout

Checkout stays mocked: no payment is taken, nothing ships, and no real order
is placed. The existing storefront checkout uses the browser's cart cookie;
these UCP tools do not set that cookie or transfer an API cart into the
browser. Use the storefront normally to walk through its mock checkout.

There are no UCP `create_checkout`, `update_checkout`, `complete_checkout`,
payment, fulfillment, or order tools. The discovery profile does not
advertise those capabilities or payment handlers.
