# ASIN Catalog Agent Guide

> Website / public gateway root: **`http://localhost:3000`**.
> Server / authoritative API root: **`http://localhost:8080`**.
> Recommended MCP endpoint: **`http://localhost:3000/mcp`** (Streamable HTTP; POST).
> Direct backend MCP endpoint: `http://localhost:8080/mcp`.
> Website handbook: `http://localhost:3000/agents.md` (also `http://localhost:3000/mcp.md`).
> Backend handbook: `http://localhost:8080/agents.md`.
> Machine-readable index: `http://localhost:3000/llms.txt`.
> Health: `http://localhost:3000/healthz`.
> A2A 1.0 card: `http://localhost:3000/.well-known/agent-card.json`; JSON-RPC: `http://localhost:3000/a2a`.
> UCP 2026-08-25 discovery: `http://localhost:3000/.well-known/ucp`; REST base: `http://localhost:3000/ucp/v1`.

## Connect Trae

The website provides a same-origin gateway for `/mcp`, `/healthz`,
`/.well-known/*`, `/a2a`, `/ucp/*` and `/v1/*`. Root-relative protocol and
REST paths in this guide resolve against the Website / public gateway root
above: append the path to that root, preserving any configured path prefix.
The default local website is `http://localhost:3000`; the backend is
`http://localhost:8080`. Either MCP endpoint above is supported; the
`/for-agents` introduction page is not an MCP endpoint. Cards and buyer profile
URLs may advertise the authoritative API root.

In Trae's MCP settings, add an HTTP server with:

```json
{
  "mcpServers": {
    "asincatalog-local": {
      "url": "http://localhost:3000/mcp"
    }
  }
}
```

Initially, `connect` and the public product tools can be called without a token.
After `connect` returns `access_token`, add the following `headers` field to
that same server entry to enable quoting, purchasing and order queries:

```json
{
  "headers": {
    "Authorization": "Bearer <access_token>"
  }
}
```

Merge `headers` into the `asincatalog-local` server entry, not into tool
arguments or a URL parameter. A token returned by a tool does not automatically
reconfigure the client's HTTP headers. Keep the API key and token private.

The agent creates a pending OFFER and presents its `trade_link` to the human
owner; the owner must confirm and pay. An accepted quote is not a paid order.
`localhost` only works for a client that can reach this development machine.
A cloud task or another computer needs a reachable website gateway or backend address instead.

## 1. Connect

Use MCP `initialize`, keep the `Mcp-Session-Id` response header, then send
`notifications/initialized`. Include `Accept: application/json, text/event-stream`
on every request.

Register a buyer agent:

```json
{
  "name": "my-buyer-agent",
  "provider": "ChatGPT",
  "agent_type": "shopping",
  "email": "buyer@example.com"
}
```

MCP tool: `connect`.

Response fields:

- `agent_id`
- `api_key` (returned only on first registration; save it securely)
- `access_token` (time-limited visiting JWT)
- `token_type`, `expires_in`, `email`, `next_action`; use the returned `expires_in` for token validity
- `ucp_profile_url` (assigned buyer connector profile for UCP requests)

Existing agent: call `connect` with `{"api_key":"ack_..."}` to get a fresh token.

## 2. Bind email

All order actions require a bound email:

```json
{ "email": "buyer@example.com" }
```

MCP tool: `set_agent_email`. It must be called with `Authorization: Bearer <access_token>`.

If you skip it, `buy_now` / `make_offer` returns:

```text
agent email required: call set_agent_email before buy_now/make_offer
```

## 3. Auth matrix

| Tool | Bearer token | Bound email |
|---|---|---|
| `connect` | no | no |
| `search_products`, `recommend`, `get_product` | no | no |
| `set_agent_email` | yes | - |
| `buy_now`, `make_offer` | yes | yes |
| `resume_offer`, `get_offer`, `list_orders`, `get_order` | yes | no |
| `request_refund`, `submit_review`, `request_brand_disclosure` | yes | no |
| `ask_seller` | yes | no |

Send:

```text
Authorization: Bearer <access_token>
```

## 4. Discover products

- `search_products` `{ "q": "charger", "limit": 20 }`
- `recommend` `{ "limit": 5 }`
- `get_product` `{ "sku": "AC-SPK-BT20" }`

`get_product` returns neutral facts, Amazon promo anchor, platform Buy Now price,
live shared stock and VeriScore. Brand-protected products omit brand/ASIN.
The hidden seller NYP floor is never returned.

## 5. Create an OFFER

Buy Now:

```json
{ "sku": "AC-SPK-BT20", "qty": 1 }
```

NYP:

```json
{ "sku": "AC-SPK-BT20", "qty": 1, "budget_cents": 7000 }
```

Both return:

```json
{
  "offer": { "id": "...", "status": "offer_pending", "expires_at": "..." },
  "trade_link": "http://localhost:3000/preview/<token>",
  "next_action": "send trade_link to the human owner; owner confirms and pays..."
}
```

`budget_cents` is a **per-unit USD-cent price**, not the whole cart budget.
`make_offer` succeeds only when `budget_cents > hidden seller floor`; equality
is rejected. Total = `budget_cents * qty`. Rejection returns
`budget_below_seller_floor`; the floor is not disclosed.

The OFFER locks price and shared stock until its returned expiry. `resume_offer` only accepts
`offer_pending`; paid/expired/cancelled offers are rejected.

## 6. Owner approval and payment

The agent cannot create a paid order by itself:

1. Agent returns `trade_link` to the human owner.
2. Owner opens `/preview/<token>` and follows the page's verification instructions.
3. Owner confirms checkout and pays.
4. Order is created with status `paid_escrow`.
5. Agent calls `get_offer`, `list_orders` or `get_order` to observe the result.

No owner confirmation = no order.

Present only platform-confirmed product, OFFER and order facts. Share the
approval link and leave the page's verification and checkout actions to the
human owner. Do not request, retrieve or submit owner verification codes.
Do not infer or narrate server configuration, email delivery behavior or
payment implementation. Confirm payment only from the returned order state.

Suggested handoff: "Your offer is ready. Open the confirmation link to review
it and complete checkout. I can check its status after you finish."

## 7. Orders, refunds, reviews

- `list_orders` `{}` -> `{ "orders": [...] }`
- `get_order` `{ "order_id": "..." }`
- `request_refund` `{ "order_id": "...", "reason": "..." }` (30-day window,
  one active refund request, manual ops review)
- `submit_review` `{ "order_id": "...", "rating": 5, "comment": "..." }`
  (1-5, one review per order)

## 8. Brand disclosure

`request_brand_disclosure` `{ "offer_id": "..." }` returns a disclosure request
and `trade_link`. The owner confirms in preview; the grant expires with the OFFER.

## 9. Seller service agents

Sellers can configure three levels of service agent:

| Scope | Configured by | Resolution |
|---|---|---|
| product | seller, per SKU | highest priority when bound |
| store | seller, per store | used when the product has no agent |
| seller | seller | fallback when neither product nor store agent exists |

Products are **unbound by default**. A product agent is optional; removing it
falls back to the store agent, then the seller agent. A bound but disabled agent
returns `sales_agent_disabled` instead of falling through.

`ask_seller`:

```json
{
  "seller_id": "...",
  "store_id": "...",
  "message": "Is this in stock and what warranty do you offer?",
  "sku": "AC-SPK-BT20",
  "offer_id": "...",
  "history": [
    { "role": "user", "content": "hi" },
    { "role": "assistant", "content": "hello" }
  ]
}
```

`seller_id` is required by the current endpoint. `store_id` / `sku` / `offer_id`
are optional targeting context; `sku` and `offer_id` resolve the product's store
and seller automatically. Resolution is product > store > seller. `history` is
limited to 20 turns, 4000 characters each. Replies use verified store, product
and offer facts; the hidden floor and protected brand data are not disclosed.
If the service is unavailable, relay the returned business error rather than
guessing the platform configuration.

## 10. Errors

Tool business errors arrive as MCP `result.isError = true` with a text reason.

Common reasons:

- `unauthorized: ...` — missing/expired token; call `connect`
- `agent email required: ...` — call `set_agent_email`
- `product_not_found`
- `out_of_stock` / `insufficient_shared_inventory`
- `budget_below_seller_floor` / `nyp_not_available`
- `offer_not_found` / `offer_expired` / `offer_already_paid` / `offer_not_resumable`
- `order_not_found`
- `refund_window_closed` / `refund_already_requested` / `refund_not_allowed`
- `order_already_reviewed`
- `sales_agent_not_found` / `sales_agent_disabled` / `sales_agent_unavailable`

## 11. Limits

| Item | Value |
|---|---|
| visiting token | Use the returned `expires_in` |
| OFFER lifetime | Use the OFFER's returned expiry |
| refund window | 30 days after order creation |
| preview link | Time-limited; use the link promptly |
| search limit | 20 default / 50 max |
| ask_seller history | 20 messages / 4000 chars each |

## 12. A2A seller negotiation

Use A2A **1.0** (specification release v1.0.1), not the incompatible 0.3
`message/send` format. Discover the marketplace card at
`/.well-known/agent-card.json`, or a seller card at
`/v1/sales-agents/{seller_id}/agent-card.json?sku={sku}`. Its advertised
interface pins the seller; product > store > seller configuration is reused.
The selected seller agent must be configured and enabled. Structured bidding
does not need an LLM; `ask_seller` remains the separate natural-language tool.

Every JSON-RPC call requires:

```http
Content-Type: application/json
Authorization: Bearer <visiting-token>
A2A-Version: 1.0
```

Submit one application/json DataPart. This commerce payload is our application
contract carried by A2A; it is not a standardized UCP bargaining schema.

```json
{
  "jsonrpc": "2.0",
  "id": "rpc-1",
  "method": "SendMessage",
  "params": {
    "message": {
      "messageId": "buyer-message-1",
      "role": "ROLE_USER",
      "parts": [{
        "mediaType": "application/json",
        "data": {
          "action": "propose",
          "sku": "AC-SPK-BT20",
          "quantity": 2,
          "unit_price_cents": 7000,
          "currency": "USD"
        }
      }]
    }
  }
}
```

- `SendMessage` returns `result.task`; `GetTask` and `CancelTask` return a
  Task directly in `result`. Preserve its server-generated `id` and `contextId`.
- Below/equal-floor bids create no OFFER and reserve no inventory. The seller
  counters at its **public Buy Now unit price**, never a floor-derived price.
- A new bid uses a new `messageId`, the existing `taskId`/`contextId`, and the
  same SKU/quantity. `{"action":"accept"}` explicitly accepts the last public
  counter on that task; changed public prices require another confirmation.
- A message ID is deduplicated per buyer; reusing it with different semantic
  input is rejected. JSON-RPC correlation `id` is not the deduplication key.
- Accepted bids atomically create one price-locked OFFER/UCP checkout. Task
  state remains `TASK_STATE_INPUT_REQUIRED` until owner payment. Paid becomes
  `TASK_STATE_COMPLETED`; expiry becomes `TASK_STATE_FAILED`.
- `GetTask` params: `{"id":"<task-id>","historyLength":0}`. Omit
  `historyLength` for full retained history; a positive limit counts messages.
- `CancelTask` params: `{"id":"<task-id>"}`. Cancellation restores inventory
  once; paid tasks cannot be canceled. Terminal tasks cannot accept new bids.
- `ListTasks` supports `contextId`, `status` (`TASK_STATE_*`), `pageSize`
  (default 50, maximum 100), `pageToken`, `historyLength`,
  `statusTimestampAfter` (RFC 3339), and `includeArtifacts`. Responses include
  `tasks`, `nextPageToken`, `pageSize`, `totalSize`; cursors are buyer/filter-bound.
- A new task may reuse an existing context owned by the same buyer/seller.
  Contexts, tasks, checkout reads and cancellation remain buyer-isolated.
- Maximum 50 bidding rounds per task. Only structured JSON is supported:
  no text price extraction, file parts, streaming, push, or task references.

Base A2A returns checkout references and the owner continue link. To receive
the full standard UCP Checkout, activate the card's optional extension:

```http
A2A-Extensions: https://ucp.dev/2026-08-25/specification/reference
UCP-Agent: profile="<assigned ucp_profile_url>"
```

`X-A2A-Extensions` is also accepted for the UCP binding. The Checkout appears
under `a2a.ucp.checkout` in DataParts/artifacts only when activated.

## 13. UCP checkout

`GET /.well-known/ucp` advertises UCP **2026-08-25**, the REST shopping service
at `/ucp/v1`, and only `dev.ucp.shopping.checkout`. No UCP SPT/AP2 payment
handler is advertised; checkout requires the human owner's approval.

Registration (`POST /v1/agents/register`), token refresh, and MCP `connect`
return `ucp_profile_url`. Its public profile is hosted at
`/v1/agents/{agent_id}/.well-known/ucp`, with fixed checkout support.
This is the marketplace-issued connector profile for that authenticated buyer,
not vendor OAuth identity linking. Only this exact assigned URL is authorized;
arbitrary third-party profile URLs, redirects and remote profile fetching are
not supported. `UCP-Agent` accepts the single RFC 8941 dictionary member
`profile="URL"` without parameters or additional members.

All operations require `Authorization: Bearer <visiting-token>`,
`UCP-Agent: profile="<assigned-url>"`, and `Request-Id: <UUID>`.
Every write also requires `Idempotency-Key: <UUID>`. JSON bodies require
`Content-Type: application/json` and
`Content-Digest: sha-256=:<base64 SHA-256 of exact raw body bytes>:`.
An identical key/body replays its durable outcome; changed bytes, including
JSON whitespace, return 409. Use a new key for changed input. GET has no body.

| Operation | Path, relative to `/ucp/v1` | Input |
|---|---|---|
| Create | `POST /checkout-sessions` | `line_items`, optional `buyer`, USD currency preference |
| Read | `GET /checkout-sessions/{id}` | no body |
| Update | `PUT /checkout-sessions/{id}` | complete writable replacement, including `line_items` |
| Complete | `POST /checkout-sessions/{id}/complete` | schema-valid `payment` with exactly one instrument |
| Cancel | `POST /checkout-sessions/{id}/cancel` | `{}` or empty body |

Create input:

```json
{
  "line_items": [{"item": {"id": "AC-SPK-BT20"}, "quantity": 2}],
  "buyer": {"email": "buyer@example.com"},
  "context": {"currency": "USD"}
}
```

The business determines currency, unit prices and totals. Do not submit
`item.price`, root `currency`, `totals`, `status`, or an accepted-price override.
A checkout contains one SKU and locks its SKU/quantity/unit price; PUT may
replace buyer contact fields but cannot reprice/re-reserve the cart. Omitted
buyer fields are cleared on PUT. For a different cart, cancel and create anew.
No discount, fulfillment extension, order-management capability, SPT or AP2 is
advertised. Unsupported fields are rejected rather than silently applied.

An accepted A2A bid already has its UCP checkout: read that ID; do not create
a second session for the same intent. Create/Get/Update return a full Checkout
with `status: "requires_escalation"`, an explanatory message and `continue_url`.
Hand the link to the owner to follow the page and complete checkout. `/complete`
does not charge agent credentials; it returns the same owner approval handoff.
The agent need not call complete for payment through the owner page.
GET then observes `completed` with `order.id`/`order.permalink_url`. Explicit
cancel or expiry yields `canceled` and restores shared inventory exactly once.

Resource-level business failures use HTTP 200 with `ucp.status: "error"` and
messages. Protocol/auth/not-found/idempotency/storage failures use non-2xx
responses with `code`/`content`; storage failure is 503, never fake success.
Request bodies are limited to 64 KiB. Idempotency outcomes are persisted without
automatic expiry; payment credentials are not retained in them.

For remote connections, use the HTTPS roots advertised above.

## 14. Current boundaries

Not implemented: broadcast / Agent plaza / Deal Pool, MCP server-initiated
notifications, enforced order-mgmt token, and per-agent rate limiting.
The `order_mgmt` tier exists in JWT claims, but trading uses the visiting token.

## 15. Links

- LLM index: `http://localhost:3000/llms.txt`
- MCP endpoint: `http://localhost:3000/mcp`
- Direct backend MCP: `http://localhost:8080/mcp`
- Health: `http://localhost:3000/healthz`
- Protocol: https://modelcontextprotocol.io
- A2A specification: https://github.com/a2aproject/A2A/releases/tag/v1.0.1
- UCP specification: https://ucp.dev/2026-08-25/specification/overview/
