Skip to content
 

MCP Usage Guide

How to run a multi-turn shopping conversation on top of aon_search_offers: act on the server's follow-up suggestions, keep the session continuous, retry safely, and understand what happens when arguments are imperfect.

  1. Vague intent? Call aon_get_category_schema (or aon_resolve_category first if you have no category id) and ask the user one clarifying question from its decision factors.
  2. Search. Call aon_search_offers with one natural sentence in intent.content. Add constraints.category_ids only with ids you got from the other two tools — never invent them.
  3. Present offers. Show the offer title and description from offer_info, the merchant or provider from entity.name, and the action.payload.url link verbatim. match_reason tells you why each offer matched — use it in your presentation.
  4. Offer the follow-ups. If the response has data.engagement, its refinements and followup_topics are pre-validated, inventory-backed next questions: the server only suggests moves that have inventory behind them.

The guidance loop

Each entry in engagement.refinements[] and engagement.followup_topics[] carries a label (show it to the user) and a query_helper:

{
  "label": "Under $200",
  "query_helper": {
    "request_patch": { "intent": { "signals": { "budget": { "max": 200, "currency": "USD" } } } },
    "origin": [{ "kind": "query_helper", "id": "qh_..." }]
  }
}

Budget is an intent.signals value, so every budget helper includes both a numeric max and an uppercase ISO 4217 currency. The server may only emit one when the current request has a known currency; it must not infer a currency from the user's language or region.

Category ids remain canonical filters under constraints, not natural-language intent. A category-complement helper therefore patches a different branch of the next request:

{
  "label": "Hiking socks",
  "query_helper": {
    "request_patch": { "constraints": { "category_ids": ["cat_hiking_socks"] } },
    "origin": [{ "kind": "query_helper", "id": "qh_..." }]
  }
}

When the user picks a suggestion, three things go into your next aon_search_offers call:

  1. Merge the patch. request_patch is an RFC 7386 merge-patch whose keys are protocol field paths — merge it straight into your previous request object. It can patch intent or constraints; do not move canonical category ids into intent just to make the JSON shape uniform.
  2. Echo the origin. Copy query_helper.origin into intent.origin[]. This is how adoption is attributed; skip it and the loop's analytics — and your ranking benefit — are lost.
  3. Chain the round. Pass the previous response's data.request_id as context.session.previous_request_id so offers the user already saw are not repeated.

Session continuity

  • context.session_id — send a stable id per conversation. Cross-round offer de-duplication only engages when both a session id and a user identity are present.
  • User identity — hosts that proxy many end users should send the x-aon-user-pseudo-id HTTP header per user. The context.user_profile.user_pseudo_id field is a fallback for clients that cannot set headers; the header always wins. Single-user MCP clients (Claude Code, Cursor, a personal desktop) can skip this entirely.
  • context.session.recent_topics[] — up to 10 short phrases of recent conversation context; helps the server rank follow-up topics.

request_id idempotency

request_id is an idempotency key with a 10-minute window. Two obligations:

  1. Retry = same id, new query = new id. Reusing an old id within the window returns the previous full response without an error — a changed question with a stale id silently gets the stale answer.
  2. Never share ids across end users. Under one API key, two users reusing one id would receive each other's responses — including each other's tracking links, which mis-attributes clicks and conversions.

Omit request_id and every call is treated as new (the server generates one for the response; the generated id does not join the idempotency window).

Tolerant degradation

The search arm is forgiving on purpose: a value-level problem never fails the call. A wrong enum value, an out-of-range number, a malformed field — each is treated as "not sent" and reported in extra.hints[] with a machine-readable code and the closest valid option. If you see hints, read them: they tell you exactly how to fix the next call.

Only four things hard-fail:

  1. Failed authentication (bad or revoked key, wrong X-AON-Test usage)
  2. Invalid JSON
  3. Missing intent.content
  4. Oversized request body

Two more behaviors worth knowing:

  • Unknown category ids in constraints.category_ids come back as an in-band correction listing valid candidates — resolve ids through aon_resolve_category to avoid the round-trip.
  • An empty offers list is a real answer. Check data.empty_reason, relay it honestly, and offer the engagement follow-ups instead of retrying the same query.

Paging through a catalog

For browse-style UIs, pagination (AON private extension) supports limit up to 50 with offset stepping — offset: 0, 50, 100, … The response does not report a total count; keep paging until a short or empty page comes back.

Do / don't

DoDon't
Surface action.payload.url links verbatimRewrite, shorten, or proxy tracking links
Echo query_helper.origin when a suggestion is adoptedApply a patch but drop its origin
Send one natural sentence as intentStuff keyword lists into intent.content
Use ids from aon_resolve_categoryGuess or invent taxonomy ids
Show an honest "nothing relevant" on empty resultsPad with unrelated offers

Rate limits & FAQ →