{
  "server": "https://mcp.layout.link",
  "transport": "Streamable HTTP, stateless: every request is a JSON-RPC 2.0 POST that carries its own credential.",
  "sessions": {
    "oauth": {
      "description": "A person's own OAuth connection, through an OAuth client your application created. Every tool and every action.",
      "tools": {
        "order": "all",
        "order_status": "all",
        "get_location": "all",
        "profile": "all",
        "discover": "all",
        "places": "all",
        "place_facts": "all",
        "help": "all",
        "menu": "all",
        "item_photo": "all"
      }
    },
    "buildGrant": {
      "description": "A production build grant (lgb_) from POST /v1/users/{userId}/grant.",
      "tools": {
        "order": [
          "preview",
          "build",
          "status",
          "request_code",
          "confirm",
          "cancel"
        ],
        "order_status": "all",
        "places": "all"
      }
    },
    "sandboxBuildGrant": {
      "description": "A sandbox build grant. Its confirm is always simulated.",
      "tools": {
        "order": [
          "preview",
          "build",
          "status",
          "request_code",
          "confirm",
          "cancel"
        ],
        "order_status": "all",
        "places": "all"
      }
    }
  },
  "tools": [
    {
      "name": "order",
      "title": "Place Order",
      "description": "Place or preview a food order for the user. CALL THIS TOOL IMMEDIATELY when the user asks to order food/coffee/a drink — your FIRST action in that turn is a tool call, NOT a text reply. NEVER answer with an intent-announcement like 'I'll look up…' / 'I'll start building…' / 'Let me find…' and then stop: a sentence that promises to do it but doesn't call the tool leaves the user with nothing. If you're about to say you'll do it, CALL 'preview' (or 'build') in the SAME turn instead. DON'T OVER-CLARIFY: when the user names a food and a place (‘a bag of chips from Don Quixote near me’), just START — call 'preview'/'build' with their words; Layout resolves the restaurant, the location, and the item. Do NOT ask about sizes, variants or which location out of your own head — you do not know what this restaurant actually offers, and a question you invented costs the user a turn and can be about something their menu does not even have. ASK ONLY FROM LAYOUT'S OWN DATA: when a build comes back 'needs_choices', the restaurant REQUIRES a choice on that item and `choiceDecision` carries THEIR words for it — then you ask, offering exactly the options in `choiceDecision.groups` and nothing else, and you build again passing the answer as `choices`. Same rule for the `menu` tool: an item it returns with `optionGroups` where required is true is a real choice you may raise; an item with NO optionGroups field means Layout could not read whether it has any, which is not permission to invent one. TO PLACE: once the user approves the confirmationCard (‘place it’, ‘yes’, ‘go’), you MUST call 'order' with action:'confirm' (with the texted code if one was requested) — that IS the placement path. NEVER tell the user you ‘don’t have a working endpoint’, can’t submit, or can’t charge the card: if a call errors, relay its message and retry the confirm; the ability to place is always the 'confirm' action, never absent. FLOW: 'preview' resolves a restaurant + estimates cost. 'build' builds a real priced cart. It waits briefly (~15s) and returns the finished cart ({status:'carted'}) directly from this one call when the site is fast or the restaurant is a returning one. Otherwise it returns {status:'building'} quickly (the build keeps running in the background) — that is the NORMAL answer for a new or slow site, not an error. WHEN it returns 'building': RELAY its `userMessage` (which carries the live `trackUrl`) to the user FIRST, then poll 'status' with the SAME idempotencyKey until it returns the cart. 'status' LONG-POLLS (~25s per call), so just call it again right after it returns 'building' — poll quietly, don't add your own delay, don't announce every poll. If your client doesn't keep polling on its own, tell the user it's still building, give them the `trackUrl`, and ask them to say 'is it ready?' — their next message picks the cart up with one 'status' call. When status is 'carted', the response includes a `confirmationCard` — Layout's branded confirmation text (store, items + modifiers, total, card last-4, pickup time, and a confirm prompt). PRESENT `confirmationCard` TO THE USER VERBATIM, exactly as written — do NOT rewrite, summarize, reorder, shorten, or add to it; it is Layout's interface and you relay it, you do not author it. WHETHER A CODE IS NEEDED depends on the response: ONLY if the carted response's `confirmation.method` is 'sms_code' is a code texted to the user — then 'confirm' finalizes with the code they read back (stage 'confirm'; stage 'resend' for a new code). If there is NO `confirmation.method:'sms_code'` (the user has code confirmation off), NO code is sent and NONE is needed — when they approve, call 'confirm' WITHOUT a code to place it (stage may be omitted; it defaults to 'confirm'). Do NOT wait for, ask for, or expect a code that the response didn't say was sent. PLACEMENT (after confirm): when 'confirm' returns {status:'placing'}, the order is being PLACED live — tell the user you're placing it, then IMMEDIATELY poll 'status' passing the `orderId` (NOT the idempotencyKey) and keep polling until `placement.status` is 'placed', 'failed', or 'challenge'. Each orderId poll long-polls ~25s — call it again right away, quietly. Report ONLY what placement returns: 'placed' → the order is in (relay its userMessage); 'failed' → relay its userMessage, nothing was charged; 'challenge' → the user was TEXTED a secure verification link — tell them to tap it, then keep polling. NEVER say the order is placed or charged before placement.status says 'placed'. If status is 'reached_bag_only', we could NOT confirm the cart contents and nothing was placed — relay its `userMessage` and do NOT claim an order was placed or priced. If status is 'paused', this purchase needed an extra confirmation step and we TEXTED the user a secure link — present the `confirmationCard` (item + total) and tell them to tap the texted link to approve; do NOT ask for a 6-digit code (the link is the confirmation, not a code). PICKUP THAT ISN'T TODAY: if status is 'pickup_next_day', the earliest pickup that site offers falls on a DIFFERENT day than the user's today. NOTHING was ordered, no cart exists and there is nothing to confirm — that is deliberate, because nobody agreed to eat tomorrow. Relay `userMessage` and STOP. Do NOT show a confirmation card, do NOT call confirm, do NOT ask for a 6-digit code; this is a yes/no DECISION. If the user says yes, start a FRESH build with a NEW idempotencyKey and pass `acceptPickupTime` set to `pickupDecision.stated` verbatim. Quote that time exactly as Layout gave it: it is the store's own clock off the store's own page, so never convert it to another timezone or reword it. CHOICES THE RESTAURANT REQUIRES: if status is 'needs_choices', the item resolved to a real menu item that CANNOT be ordered until the user makes a choice their site demands (plate or bowl, which rice). NOTHING was ordered, no cart exists and there is nothing to confirm — that is deliberate, because a choice we guessed is a meal they did not order. Relay `userMessage` (Layout's question, built from their menu) and offer EXACTLY the options in `choiceDecision.groups`, which are the restaurant's own words. Never add an option from memory and never pick one for them. Call the item by `choiceDecision.item` — that is what the restaurant calls it. The moment they answer, start a FRESH build with a NEW idempotencyKey, the SAME query and store, and `choices` set to their answer in their own words. Do NOT show a confirmation card, do NOT call confirm, and do NOT ask for a 6-digit code. If a carted response includes a `pickupWarning`, the pickup time is still worth stating: present the `confirmationCard` AND relay `pickupWarning.message` so the user sees the pickup time before deciding. If status is 'closed_next_day' (no cart was built yet), relay its `userMessage` and ask whether they still want it or would rather find an open spot — this is a yes/no DECISION, NOT a 6-digit code; on a 'yes' start a fresh build (new idempotencyKey) as they asked. When `pickupWarning.openAlternatives` is present, those are REAL places VERIFIED open right now — offer THOSE by name, and if the user picks one, build there. NEVER suggest a restaurant from your own memory: you cannot know whether it is open, still in business, or even nearby, and sending someone to a closed or long-gone spot is worse than saying you have nothing to suggest. If any action returns status 'platform_offline', Layout is in scheduled maintenance right now — relay its `userMessage` to the user VERBATIM (it states when we'll be back + an info link). Do NOT retry, do NOT start a build; nothing was ordered. If build/status returns order_in_progress, the user already has ONE order building (only one at a time) — relay its userMessage and do NOT start another build. When Layout can see the build that is running, status answers with THAT build and names its idempotencyKey: poll that key from then on. CRITICAL — DO NOT AUTO-RETRY: each build spends a real, billed browsing session. If a build ends in error/timeout/reached_bag_only, STOP and tell the user what happened; do NOT start a new build on your own. Only build again if the USER explicitly asks you to try again — and only then set userInitiatedRetry:true. If build returns retry_blocked, a recent attempt just finished and an automatic retry was refused — relay its userMessage and wait for the user. Never loop builds hoping one works. None of these place or pay (that comes later). LOCATION — SEND EVERYTHING YOU HAVE (highest confidence first): (1) if you already have the user's DEVICE coordinates, pass `geo` {lat,lng} — the strongest signal. It does NOT skip the store question: Layout still asks, but the question comes back naming the nearest store as a one-tap default instead of a list of addresses. Only a store address the USER states resolves without asking; (2) if you do NOT have coordinates but your client CAN obtain them, ASK THE USER FOR DEVICE LOCATION PERMISSION NOW — trigger your location capability before calling this tool and pass the result as `geo`. Do this proactively on the FIRST order of a conversation: it turns 'which of these three?' into 'this one?', which is the whole point of ordering by asking. Ask ONCE — if the user declines, or your client has no location capability, do not ask again in this conversation; fall through to (3). If your client CANNOT read GPS at all (a location-blind host), OR the user explicitly asks you to get their location, call the `get_location` tool FIRST — it gives them a one-tap share link, waits for the tap, and returns coordinates you then pass here as `geo`; do not guess a city in its place. (3) else if you know WHERE THE USER IS (a street address, a city, an area — from this chat, their profile, or your own location context), ALWAYS pass it in `near`, exact words — do NOT omit it just because it's approximate; a rough area still helps Layout guess. (4) only OMIT everything when you truly have no location — Layout then falls back to their saved account address. IMPORTANT: whenever the store isn't settled, preview returns `needsLocation` with a `nearby` list of the 2-3 closest stores (nearest first) — RELAY `sayToUser` as written and let them pick (do NOT silently choose one). AFTER THEY PICK, go STRAIGHT TO 'build' — pass `restaurantName` (e.g. 'Starbucks'), `near` = the chosen store's full address, `restaurantAddress` = that same address, and `placeId` = that option's placeId, plus the item as `query`. You do NOT need to re-run preview and you do NOT need an orderUrl — build resolves the exact store from the name + that address itself. When the user names a specific store address up front, ALWAYS pass it through in `near` and `restaurantAddress` — never swap in a different location. KEYS: generate a FRESH RANDOM idempotencyKey for every NEW order attempt (include 8+ random characters — a key derived only from the date/food collides with earlier sessions). Reuse a key ONLY to poll or confirm that SAME attempt. If build says the key already exists, that's a PREVIOUS attempt — start a fresh build with a NEW random key. PRICE INTEGRITY: the ONLY total you may pass to 'confirm' is the Total on the confirmationCard the user approved. If confirm returns price_changed, NEVER retry with a different expectedTotalMinor — relay its userMessage and rebuild fresh (new key) so the user approves the current price on a new card. LANGUAGE: preview returns `sayToUser` — RELAY IT AS-IS and keep YOUR OWN words to ONE short sentence. Be terse and concrete: no preamble, no restating the request, no explaining what you're about to do. When preview needs a location it returns `sayToUser` like 'Which Starbucks? 1) … 2) … 3) …' plus a `nearby` list — present exactly that as a quick numbered pick and nothing else; the user replies with a number or a different address. If your client renders the `nearby` options itself, as its own choice UI or tappable rows, do NOT repeat them in your text: ask which one in one short line and stop, because the person is already looking at the list. NEVER use internal terms with the user: no 'cold/warm site', 'mapped', 'lane', 'spec', 'agent', or internal cost numbers. NEVER STATE A FEE OR A PRICE BEFORE THE CONFIRMATION CARD. The fee depends on the order total, which does not exist until the cart is built — quoting one earlier (e.g. 'there's a $0.49 service fee') is a money claim you cannot support, and the user may be charged something different. Say nothing about cost until `confirmationCard` arrives; it carries the real total and is the ONE place cost is stated. Prefer the `sayToUser` framing (first visit ≈ 'around 2–3 minutes, hang tight'; return visit 'should be quick'). DO NOT PROMISE TO MESSAGE THEM LATER. Nothing sends a message on its own — the cart reaches the user only because YOU called `status` again and are still there to relay it. A turn that ends on 'I'll let you know when it's ready' has told somebody to wait for something that is never coming. If you truly cannot poll again, say it is still building and to ask you whether it's ready. CANCEL: call this tool with cancel:true (any action value; orderId if you have it) and relay the outcome (1) when the user asks to cancel, and (2) when the user DECLINES a built cart — says 'no', 'never mind', or changes their mind after you presented the confirmationCard. Do NOT just leave a declined cart unconfirmed: it holds a live browsing session open until it times out. Never say an order was canceled unless the response's cancel.status is 'canceled' — a cancel you did not call did not happen.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "preview",
              "build",
              "confirm",
              "status"
            ],
            "description": "'preview' = resolve + estimate. 'build' = START building a cart (returns 'building'; poll status). 'status' = poll a build by idempotencyKey, OR poll PLACEMENT by orderId after confirm returns 'placing'. 'confirm' = finalize with the code (or resend)."
          },
          "restaurant": {
            "type": "string",
            "maxLength": 200,
            "description": "The restaurant, cafe, shop or brand the user named to order FROM, in their exact words ('602 Coffee', 'Starbucks', 'Jack in the Box'). It is a BUSINESS, never a place on a map: a name that starts with a number ('602 Coffee', '7 Leaves') is still a business — do not read it as an address, do not fold it into `near`, and never replace it with the city it is in. Fill this on preview AND on build whenever the user said where to order from; omit it only when they named no restaurant at all."
          },
          "query": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "preview: the RESTAURANT or brand name the user named ('Starbucks', '602 Coffee') — the same name you put in `restaurant`. NOT the item, size or 'decaf', and NOT a city: 'an iced americano at 602 coffee in Huntington Beach' is restaurant='602 Coffee', query='602 Coffee', near='Huntington Beach'; the drink waits for build. Only when they named NO restaurant is query the food they want ('iced americano'), and Layout finds places that serve it. build: EVERYTHING they asked for, in their own words, WITH every size, cup, temperature, and prep detail kept — 'a grande iced brown sugar oatmilk shaken espresso in a venti cup with extra ice' is passed WHOLE, never shortened to 'iced brown sugar oatmilk shaken espresso'. If they named two drinks, both belong here ('a grande latte and a grande cold brew'). Never drop a word of what they asked for. (Unused for confirm.)"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "maxItems": 10,
            "description": "build, optional but STRONGLY preferred when they asked for more than one thing: the same order split into ONE ENTRY PER ITEM, each with its own size and customizations — [\"grande iced pumpkin cream shaken espresso in a venti cup, extra ice\", \"grande iced brown sugar oatmilk shaken espresso\"]. This does NOT replace `query` — `query` must still name everything. The list is what lets Layout check that every item actually reached the cart and tell the user by name if one did not."
          },
          "near": {
            "type": "string",
            "description": "Where the user is — a street address, city, or area. Pass whatever you know (even approximate) so Layout can find the right store; it will ask the user to pick if it's not precise enough. Prefer `geo` when you have exact coordinates."
          },
          "geo": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              }
            },
            "required": [
              "lat",
              "lng"
            ],
            "additionalProperties": false,
            "description": "preview: the user's DEVICE coordinates (lat/lng) — the highest-confidence locality. It does not skip the which-store question (only a store address the user states does that); it makes the question name the nearest store as a one-tap default. If you do not have them and your client CAN get them, request device location permission from the user first, then pass the result here. Ask once per conversation; if declined, use `near` instead."
          },
          "platform": {
            "type": "string",
            "maxLength": 100,
            "description": "build only: the site/spec id to build the cart on (e.g. 'carlsjr')."
          },
          "orderUrl": {
            "type": "string",
            "maxLength": 2000,
            "description": "build only: the orderable URL for an unmapped (cold) site. One of platform|orderUrl required for build."
          },
          "restaurantName": {
            "type": "string",
            "maxLength": 500,
            "description": "build only: the resolved restaurant name (from preview), shown in the recap."
          },
          "restaurantAddress": {
            "type": "string",
            "maxLength": 500,
            "description": "build only: the resolved restaurant address (from preview), shown in the recap."
          },
          "placeId": {
            "type": "string",
            "maxLength": 255,
            "description": "the store's placeId — from preview's `resolved.placeId`, or from the `nearby` option the user picked. Send it on BUILD and on any follow-up PREVIEW after a pick. Pass it whenever you have it: it identifies WHICH BRANCH, so Layout checks that store's opening hours instead of matching a name and an address."
          },
          "blacklistHost": {
            "type": "string",
            "maxLength": 500,
            "description": "build only: the resolved site host (from preview's openUrl), for the eligibility check."
          },
          "modifiers": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ],
            "description": "build only: the customizations to apply — size, cup, temperature, extras, substitutions ('grande', 'in a venti cup', 'extra ice', 'oat milk', 'no whip'), free text or a list. `query` already carries them in the person's words; fill this too when they customized, so the runner has the customizations as an explicit checklist and not only buried in the item text. Never a reason to shorten `query`."
          },
          "knownStoreId": {
            "type": "string",
            "description": "build only: a resolved store id, if a location is already chosen."
          },
          "timeZone": {
            "type": "string",
            "maxLength": 60,
            "description": "build only: the user's IANA timezone (e.g. 'America/Los_Angeles') if you know it. It decides whether a pickup slot falls on THEIR today or a different day; without it Layout falls back to the store's own zone."
          },
          "acceptPickupTime": {
            "type": "string",
            "maxLength": 120,
            "description": "build only: pass ONLY after a previous build returned status 'pickup_next_day' AND the user said yes. Set it to `pickupDecision.stated` VERBATIM. Never invent it and never set it to skip the question."
          },
          "choices": {
            "type": "string",
            "maxLength": 300,
            "description": "build only: the user's answer to a 'needs_choices' question ('bowl, white rice'), in THEIR own words. Pass it on the fresh build that follows their answer. Never invent choices they did not make and never set it to skip the question."
          },
          "userInitiatedRetry": {
            "type": "boolean",
            "description": "build only: set TRUE ONLY when the HUMAN explicitly asked to try again after a failed/blocked attempt. NEVER set it yourself to bypass a retry_blocked — it exists so a real user re-request isn't mistaken for an automatic retry."
          },
          "cancel": {
            "type": "boolean",
            "description": "Set TRUE when the USER asks to cancel their order or cart. Works with any action value; pass orderId when you have it (omitted = the active order). The response's `cancel.status` is the only truth: 'canceled' = stopped, nothing charged; 'already_placing' = too late, the placement is in flight (poll order_status); 'placed' = it already went to the restaurant; 'no_active_order' = nothing to cancel. NEVER tell the user an order was canceled unless status 'canceled' actually came back."
          },
          "idempotencyKey": {
            "type": "string",
            "maxLength": 200,
            "description": "build + confirm (REQUIRED): FRESH RANDOM id per NEW attempt (8+ random chars — never only date/food-derived, that collides across sessions). Reuse only to poll/confirm the SAME attempt."
          },
          "orderId": {
            "type": "string",
            "maxLength": 100,
            "description": "confirm (REQUIRED): the order to confirm. status: pass it (WITHOUT idempotencyKey semantics) to poll PLACEMENT after a confirm returned 'placing' — keep polling until placement.status is placed/failed/challenge."
          },
          "expectedTotalMinor": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "description": "confirm only (REQUIRED): the total the user approved (from the build recap)."
          },
          "stage": {
            "type": "string",
            "enum": [
              "confirm",
              "resend"
            ],
            "description": "confirm only (optional, defaults to 'confirm'): omit it to finalize; pass 'resend' to text a new code."
          },
          "code": {
            "type": "string",
            "description": "confirm + stage 'confirm': the 6-digit code the user read back (omit if their toggle is off)."
          },
          "declineCredit": {
            "type": "boolean",
            "description": "confirm only (optional): set true ONLY if the user explicitly says to place WITHOUT their Layout credit. Credit auto-applies and is shown on the card; omit this to keep it."
          }
        },
        "required": [
          "action"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "description": "Echo of the action performed."
          },
          "preview": {
            "type": "object",
            "properties": {
              "sayToUser": {
                "type": "string",
                "description": "Layout's own words for the user — relay this framing."
              },
              "needsLocation": {
                "type": "boolean",
                "description": "TRUE means this is a QUESTION, not a result: nothing is being built. Ask which store using sayToUser, and the moment the user picks one, call `order` again with action:'build'."
              },
              "nextAction": {
                "type": "string",
                "description": "The tool to call once the user answers (e.g. 'order' with action:'build')."
              },
              "assistantInstruction": {
                "type": "string",
                "description": "Written for YOU, not the user. Says exactly what to call once they pick a store. Never read it aloud."
              },
              "allClosed": {
                "type": "boolean",
                "description": "Every branch Layout can see is CLOSED right now. The which-one question has no true answer — do not ask it. Offer `openAlternatives` instead."
              },
              "openAlternatives": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "address": {
                      "type": "string"
                    },
                    "placeId": {
                      "type": "string"
                    },
                    "distanceKm": {
                      "type": "number"
                    },
                    "openNow": {
                      "type": "boolean"
                    },
                    "closingSoon": {
                      "type": "boolean"
                    }
                  },
                  "additionalProperties": true
                },
                "description": "REAL places Layout CHECKED are open right now, nearest first — offered when the restaurant they asked for is closed, or when its nearest open branch is a drive away. Offer these BY NAME and build at whichever the user picks. NEVER substitute a restaurant from your own memory: you cannot know whether it is open, still in business, or even nearby."
              },
              "closedNote": {
                "type": "string",
                "description": "Layout's words for a resolved store that is closed right now. Relay it and STOP — do not build there unless the user says to anyway."
              },
              "closingSoonNote": {
                "type": "string",
                "description": "The store is OPEN and shuts too soon for an order to be placed and made in time, so Layout will refuse the build. Relay it and STOP. Do NOT say they are closed: they are open, and the limit is Layout's. Do not take an item for this store; offer to find somewhere open longer."
              },
              "notOrderableNote": {
                "type": "string",
                "description": "Layout READ this restaurant's own website and there is no way to place an order on it. Relay it and STOP. Unlike a closure this is final — do not build there even if the user asks again, and do not ask what they would like from it. Offer to find somewhere nearby that does instead."
              },
              "nearestOpen": {
                "$ref": "#/properties/preview/properties/openAlternatives/items",
                "description": "The nearest branch of the SAME brand that Layout CHECKED is open right now, when the one they named is closed. Offer this BEFORE `openAlternatives` and say the distance: they asked for this restaurant, and another branch of it beats a different one."
              }
            },
            "additionalProperties": true
          },
          "build": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "'building' = still working, keep polling. 'carted' = a REAL priced cart is ready — show confirmationCard verbatim. 'reached_bag_only' = the cart could NOT be verified: nothing was ordered or charged; never present items/prices as confirmed. 'closed_next_day' = their site isn't offering pickup today; relay userMessage and let the user decide. 'pickup_next_day' = the earliest pickup is a DIFFERENT DAY than the user's today, so the build STOPPED: nothing was ordered, there is no cart and nothing to confirm — relay userMessage, and only if they say yes, build again passing pickupDecision.stated as acceptPickupTime. 'needs_choices' = the restaurant REQUIRES a choice on this item (plate or bowl, which rice) that nobody has made, so the build stopped before spending anything: relay userMessage, offer exactly the options in choiceDecision.groups, and when the user answers, build again with a NEW idempotencyKey passing their answer as `choices`. Any other value: relay userMessage — do not improvise an explanation."
              },
              "choiceDecision": {
                "type": "object",
                "properties": {
                  "needed": {
                    "type": "boolean",
                    "description": "True when the restaurant requires a choice before this item can be ordered."
                  },
                  "item": {
                    "type": "string",
                    "description": "The RESTAURANT's own name for what they asked for. Use this name, not the user's words."
                  },
                  "itemNamedBySite": {
                    "type": "boolean",
                    "description": "True when `item` is the restaurant's own name for it, false when it is the user's words."
                  },
                  "question": {
                    "type": "string",
                    "description": "Layout's question, built from their menu. Relay it rather than composing your own."
                  },
                  "groups": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "description": "What the restaurant calls this choice."
                        },
                        "required": {
                          "type": "boolean"
                        },
                        "options": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Their own option names. Offer ONLY these; never options you remember."
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "answerWith": {
                    "type": "object",
                    "properties": {
                      "action": {
                        "type": "string",
                        "description": "The action to call with their answer. Always 'build'."
                      },
                      "field": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true,
                    "description": "How an answer is expressed: a NEW build passing the user's reply as `choices`."
                  }
                },
                "additionalProperties": true,
                "description": "Present on 'needs_choices'. The choices this restaurant requires before the item can be ordered."
              },
              "pickupDecision": {
                "type": "object",
                "properties": {
                  "needed": {
                    "type": "boolean",
                    "description": "True when the user has to agree to this pickup time before anything is ordered."
                  },
                  "stated": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The earliest pickup, in the STORE's own words. Quote it verbatim; never convert the time."
                  },
                  "day": {
                    "type": "string",
                    "description": "'different' when the earliest pickup is not the user's today."
                  },
                  "basis": {
                    "type": "string",
                    "description": "How the pickup time was established. Diagnostic; not something to read out."
                  },
                  "timeZone": {
                    "type": "string",
                    "description": "The STORE's zone. Present so nobody converts `stated` into the user's."
                  },
                  "question": {
                    "type": "string",
                    "description": "Layout's question for the user. Relay it rather than composing your own."
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": true
                    },
                    "description": "The choices Layout's own clients draw as buttons."
                  },
                  "acceptWith": {
                    "type": "object",
                    "properties": {
                      "action": {
                        "type": "string"
                      },
                      "field": {
                        "type": "string"
                      },
                      "value": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "additionalProperties": true,
                    "description": "How a 'yes' is expressed: a NEW build passing this value as acceptPickupTime. There is no order to confirm."
                  },
                  "openAlternatives": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "address": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": true
                    },
                    "description": "Places VERIFIED open right now. Offer only these; never a restaurant from your own memory."
                  }
                },
                "additionalProperties": true,
                "description": "Present on 'pickup_next_day'. The pickup time the user has to agree to before anything is ordered."
              },
              "userMessage": {
                "type": "string",
                "description": "Layout's message for the user. Relay it rather than paraphrasing."
              },
              "confirmationCard": {
                "type": "string",
                "description": "Layout's branded confirmation. Show VERBATIM — never rewrite, reorder, or summarize."
              },
              "complete": {
                "type": "boolean",
                "description": "FALSE means this is not the final answer and no cart exists yet. Do not present a false result as an outcome: follow `nextAction`/`assistantInstruction` instead of stopping here."
              },
              "nextAction": {
                "type": "string",
                "description": "The tool to call next (e.g. 'order_status'), with the SAME idempotencyKey."
              },
              "assistantInstruction": {
                "type": "string",
                "description": "Written for YOU, not the user. When present, it says exactly what to do next and takes precedence over anything you would infer from `status`. Never read it aloud to the user."
              },
              "trackUrl": {
                "type": "string",
                "description": "Layout's live tracking page for THIS order. Give it to the user while they wait — it works even if this conversation ends, and it is the only thing they can watch on their own."
              },
              "actionUrl": {
                "type": "string",
                "description": "A link the USER must open to unblock the order (e.g. update a payment method). When present, give it to them — the order cannot continue until they do."
              }
            },
            "additionalProperties": true
          },
          "placement": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "'placed' is the ONLY status that means the order exists — never tell the user an order was placed on any other value. 'placing' = in progress, keep polling. 'failed' = NOT placed, nothing charged (relay userMessage). 'challenge' = the user was texted a secure approval link. 'unconfirmed' = Layout could not verify either way — tell the user to check before re-ordering; claim neither success nor failure."
              },
              "userMessage": {
                "type": "string"
              },
              "orderNumber": {
                "type": [
                  "string",
                  "number"
                ]
              },
              "complete": {
                "type": "boolean",
                "description": "FALSE means the placement is still running and this is NOT the outcome. Follow `nextAction`/`assistantInstruction`; never describe a false result as placed or failed."
              },
              "nextAction": {
                "type": "string",
                "description": "The tool to call next (e.g. 'order_status'), with the SAME orderId."
              },
              "assistantInstruction": {
                "type": "string",
                "description": "Written for YOU, not the user. Takes precedence over anything you would infer from `status`."
              },
              "trackUrl": {
                "type": "string",
                "description": "Layout's live tracking page for this order — give it to the user."
              }
            },
            "additionalProperties": true
          },
          "publicOrderId": {
            "type": "string",
            "description": "The developer-facing `ord_` id of this order, for the app's own `GET /v1/orders/:id` and webhooks. Present only when an application's own client drove the order. Never read it aloud."
          },
          "refusal": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "build_limit",
                  "unavailable",
                  "forbidden"
                ],
                "description": "The same code the REST API answers with."
              },
              "retryable": {
                "type": "boolean",
                "description": "True when the same call may succeed shortly. False on a spent daily ceiling: do not retry before limit.resetsAt."
              },
              "limit": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "const": "builds"
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "person",
                      "application",
                      "account",
                      "platform"
                    ]
                  },
                  "max": {
                    "type": "integer"
                  },
                  "resetsAt": {
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "scope",
                  "max",
                  "resetsAt"
                ],
                "additionalProperties": false,
                "description": "The daily ceiling that refused the build."
              }
            },
            "required": [
              "code",
              "retryable"
            ],
            "additionalProperties": false,
            "description": "Present when Layout refused to start a build: nothing was built or charged. For code that does not read userMessage. Never read it aloud."
          },
          "cancel": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "'canceled' = the stop is real and complete; relay userMessage and do NOT confirm or poll this order again. 'already_placing' = TOO LATE to cancel — the placement is in flight; poll `order_status` and report only what it returns. 'placed' = the order already went to the restaurant; never describe it as canceled. 'no_active_order' = there was nothing to cancel."
              },
              "userMessage": {
                "type": "string",
                "description": "Layout's message for the user. Relay it rather than paraphrasing."
              },
              "assistantInstruction": {
                "type": "string",
                "description": "Written for YOU, not the user. Takes precedence over inference."
              }
            },
            "additionalProperties": true
          },
          "confirm": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "'placing' = the order is being placed RIGHT NOW; it is NOT placed — poll `order_status` with the orderId. 'code_sent' = a code was texted; ask the user to read it back. 'confirmed' = the cart is locked in. 'confirm_expired' / 'place_error' = relay userMessage; nothing was placed."
              },
              "userMessage": {
                "type": "string",
                "description": "Layout's message for the user. Relay it rather than paraphrasing."
              },
              "orderId": {
                "type": "string",
                "description": "Pass this to `order_status` to follow the placement."
              },
              "complete": {
                "type": "boolean",
                "description": "FALSE means the placement is still running — keep polling; do not report an outcome."
              },
              "nextAction": {
                "type": "string",
                "description": "The tool to call next (e.g. 'order_status')."
              },
              "assistantInstruction": {
                "type": "string",
                "description": "Written for YOU, not the user. Takes precedence over inference."
              },
              "trackUrl": {
                "type": "string",
                "description": "Layout's live tracking page for this order — give it to the user."
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": false,
        "openWorldHint": true
      }
    },
    {
      "name": "order_status",
      "title": "Check Order Status",
      "description": "Check on an order that is already running. READ-ONLY and free: it never places an order, never charges anything, never starts or retries a build. Call it as often as you need. NOT FOR NEW REQUESTS: when the user just ASKED for food, call `order` (preview/build) — this tool only reports on work already started, and a 'cart_expired' answer here means the old cart is DEAD: never present its card; start a fresh build with a NEW random idempotencyKey. HANDLES: pass `idempotencyKey` to follow a CART BEING BUILT (the key you used for 'build'), or `orderId` to follow a PLACEMENT (after 'confirm' returned status 'placing'). IF YOU NO LONGER HAVE EITHER — a new turn, a lost key — CALL IT WITH NO ARGUMENTS AT ALL: that means 'the build this user has running', which is almost always the right question. Never tell the user you cannot check because you lost a key; just call this with no arguments. When nothing is running, the same no-argument call returns `recent`: the user's own order attempts from the last two days, each with a `summary` to relay. Use it when they ask about an earlier order you cannot see, and never raise an attempt they did not ask about. THIS CALL LONG-POLLS: it waits up to ~25 seconds and returns the moment something changes, so when it comes back still 'building' or 'placing', call it again right away — do NOT add a delay of your own, and do not narrate every poll. Relay the FIRST 'building' message to the user (with its trackUrl), then poll quietly. WHEN TO STOP: a build is done at 'carted' — the response carries `confirmationCard`, which you present to the user VERBATIM. A placement is done at 'placed' or 'failed'. 'placed' is the ONLY status that means the order exists; never tell the user it was placed on anything else. 'challenge' means the purchase needs the cardholder's own approval. READ `smsSent` BEFORE SAYING ANYTHING ABOUT A TEXT: true means a secure link was sent, so tell them to tap it and keep polling; false means it could NOT be sent, so say exactly that and tell them to open the Layout app to approve. Never say a link was texted without `smsSent === true` — a person told to tap a link that was never sent is being asked to wait for something that is not coming. 'unconfirmed' means Layout could not verify either way: say exactly that, and do NOT re-order. If you cannot keep polling on your own, tell the user it is still running, give them the `trackUrl`, and ask them to check back — their next message picks it up with one call.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "idempotencyKey": {
            "type": "string",
            "maxLength": 200,
            "description": "The key used for the 'build' call, to follow that cart."
          },
          "orderId": {
            "type": "string",
            "maxLength": 100,
            "description": "The order id from a 'confirm' that returned 'placing', to follow the placement."
          }
        },
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "description": "Echo of the action performed."
          },
          "build": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "'building' = still working, keep polling. 'carted' = a REAL priced cart is ready — show confirmationCard verbatim. 'reached_bag_only' = the cart could NOT be verified: nothing was ordered or charged; never present items/prices as confirmed. 'closed_next_day' = their site isn't offering pickup today; relay userMessage and let the user decide. 'pickup_next_day' = the earliest pickup is a DIFFERENT DAY than the user's today, so the build STOPPED: nothing was ordered, there is no cart and nothing to confirm — relay userMessage, and only if they say yes, build again passing pickupDecision.stated as acceptPickupTime. 'needs_choices' = the restaurant REQUIRES a choice on this item (plate or bowl, which rice) that nobody has made, so the build stopped before spending anything: relay userMessage, offer exactly the options in choiceDecision.groups, and when the user answers, build again with a NEW idempotencyKey passing their answer as `choices`. Any other value: relay userMessage — do not improvise an explanation."
              },
              "choiceDecision": {
                "type": "object",
                "properties": {
                  "needed": {
                    "type": "boolean",
                    "description": "True when the restaurant requires a choice before this item can be ordered."
                  },
                  "item": {
                    "type": "string",
                    "description": "The RESTAURANT's own name for what they asked for. Use this name, not the user's words."
                  },
                  "itemNamedBySite": {
                    "type": "boolean",
                    "description": "True when `item` is the restaurant's own name for it, false when it is the user's words."
                  },
                  "question": {
                    "type": "string",
                    "description": "Layout's question, built from their menu. Relay it rather than composing your own."
                  },
                  "groups": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "description": "What the restaurant calls this choice."
                        },
                        "required": {
                          "type": "boolean"
                        },
                        "options": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Their own option names. Offer ONLY these; never options you remember."
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "answerWith": {
                    "type": "object",
                    "properties": {
                      "action": {
                        "type": "string",
                        "description": "The action to call with their answer. Always 'build'."
                      },
                      "field": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true,
                    "description": "How an answer is expressed: a NEW build passing the user's reply as `choices`."
                  }
                },
                "additionalProperties": true,
                "description": "Present on 'needs_choices'. The choices this restaurant requires before the item can be ordered."
              },
              "pickupDecision": {
                "type": "object",
                "properties": {
                  "needed": {
                    "type": "boolean",
                    "description": "True when the user has to agree to this pickup time before anything is ordered."
                  },
                  "stated": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The earliest pickup, in the STORE's own words. Quote it verbatim; never convert the time."
                  },
                  "day": {
                    "type": "string",
                    "description": "'different' when the earliest pickup is not the user's today."
                  },
                  "basis": {
                    "type": "string",
                    "description": "How the pickup time was established. Diagnostic; not something to read out."
                  },
                  "timeZone": {
                    "type": "string",
                    "description": "The STORE's zone. Present so nobody converts `stated` into the user's."
                  },
                  "question": {
                    "type": "string",
                    "description": "Layout's question for the user. Relay it rather than composing your own."
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": true
                    },
                    "description": "The choices Layout's own clients draw as buttons."
                  },
                  "acceptWith": {
                    "type": "object",
                    "properties": {
                      "action": {
                        "type": "string"
                      },
                      "field": {
                        "type": "string"
                      },
                      "value": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "additionalProperties": true,
                    "description": "How a 'yes' is expressed: a NEW build passing this value as acceptPickupTime. There is no order to confirm."
                  },
                  "openAlternatives": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "address": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": true
                    },
                    "description": "Places VERIFIED open right now. Offer only these; never a restaurant from your own memory."
                  }
                },
                "additionalProperties": true,
                "description": "Present on 'pickup_next_day'. The pickup time the user has to agree to before anything is ordered."
              },
              "userMessage": {
                "type": "string",
                "description": "Layout's message for the user. Relay it rather than paraphrasing."
              },
              "confirmationCard": {
                "type": "string",
                "description": "Layout's branded confirmation. Show VERBATIM — never rewrite, reorder, or summarize."
              },
              "complete": {
                "type": "boolean",
                "description": "FALSE means this is not the final answer and no cart exists yet. Do not present a false result as an outcome: follow `nextAction`/`assistantInstruction` instead of stopping here."
              },
              "nextAction": {
                "type": "string",
                "description": "The tool to call next (e.g. 'order_status'), with the SAME idempotencyKey."
              },
              "assistantInstruction": {
                "type": "string",
                "description": "Written for YOU, not the user. When present, it says exactly what to do next and takes precedence over anything you would infer from `status`. Never read it aloud to the user."
              },
              "trackUrl": {
                "type": "string",
                "description": "Layout's live tracking page for THIS order. Give it to the user while they wait — it works even if this conversation ends, and it is the only thing they can watch on their own."
              },
              "actionUrl": {
                "type": "string",
                "description": "A link the USER must open to unblock the order (e.g. update a payment method). When present, give it to them — the order cannot continue until they do."
              }
            },
            "additionalProperties": true
          },
          "placement": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "'placed' is the ONLY status that means the order exists — never tell the user an order was placed on any other value. 'placing' = in progress, keep polling. 'failed' = NOT placed, nothing charged (relay userMessage). 'challenge' = the user was texted a secure approval link. 'unconfirmed' = Layout could not verify either way — tell the user to check before re-ordering; claim neither success nor failure."
              },
              "userMessage": {
                "type": "string"
              },
              "orderNumber": {
                "type": [
                  "string",
                  "number"
                ]
              },
              "complete": {
                "type": "boolean",
                "description": "FALSE means the placement is still running and this is NOT the outcome. Follow `nextAction`/`assistantInstruction`; never describe a false result as placed or failed."
              },
              "nextAction": {
                "type": "string",
                "description": "The tool to call next (e.g. 'order_status'), with the SAME orderId."
              },
              "assistantInstruction": {
                "type": "string",
                "description": "Written for YOU, not the user. Takes precedence over anything you would infer from `status`."
              },
              "trackUrl": {
                "type": "string",
                "description": "Layout's live tracking page for this order — give it to the user."
              }
            },
            "additionalProperties": true
          },
          "publicOrderId": {
            "type": "string",
            "description": "The developer-facing `ord_` id of this order, for the app's own `GET /v1/orders/:id` and webhooks. Present only when an application's own client drove the order. Never read it aloud."
          }
        },
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "get_location",
      "title": "Share Location",
      "description": "Get the user's location when you don't already have it — use this the moment they ask for something NEARBY ('near me', 'around here', 'closest one') and you have no coordinates and they named no place. It returns coordinates you then pass to `places`/`order`; you never see or handle the raw fix yourself. IF THE USER'S HOST ALREADY GIVES YOU THEIR LOCATION (some phone apps do), you do NOT need this — just search. Call this only when you are location-blind. WHAT COMES BACK: if `located` is true, you have their location — continue silently, do NOT read the coordinates out loud. If `located` is false and there is a `shareUrl`, RELAY the `userMessage` (with its link) VERBATIM and STOP: they tap it once to share, and this call already waited for that tap, so a false result means they haven't tapped yet. When they say they've shared it, call this again (or just retry what they asked). NEVER guess a city and NEVER name places from memory when you are blind.",
      "inputSchema": {
        "type": "object",
        "properties": {},
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "located": {
            "type": "boolean"
          },
          "geo": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              }
            },
            "required": [
              "lat",
              "lng"
            ],
            "additionalProperties": false
          },
          "shareUrl": {
            "type": "string"
          },
          "userMessage": {
            "type": "string"
          },
          "assistantInstruction": {
            "type": "string"
          }
        },
        "required": [
          "located"
        ],
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true
      }
    },
    {
      "name": "profile",
      "title": "Manage Profile",
      "description": "Read or update the user's Layout food profile — the preferences Layout applies to every order: dietary restrictions ('vegetarian', 'no shellfish'), favorite restaurants, usual orders, the first name orders go under, and whether dietary restrictions are applied automatically. Use 'get' to read it; use 'update' when the user states a LASTING food preference ('I'm allergic to peanuts', 'my usual at Chipotle is a chicken bowl') so future orders respect it. Ask before saving anything the user didn't clearly state, and tell them what you saved. Remove a note by its id from 'get'. This tool never orders and never stores payment or contact info. 'get' also returns `techWeek` while the SF Tech Week promotion runs: when the user asks about Tech Week or a free coffee, call 'get' and answer with `techWeek.sayToUser`, keeping every fact in it. A NOTE SHAPES HOW AN ITEM IS MADE, NEVER WHICH BRANCH AN ORDER GOES TO. Layout has no preferred location: the store is resolved per order from where the user is, the place they name, their saved address, or the branch they pick in that conversation. When somebody says a branch is their usual, say plainly that Layout cannot make it a default yet and that naming it works every time. Never tell them a location was saved. Phone number, email, addresses and cards can NOT be viewed or changed here by design — when the user asks about EXACTLY THOSE FOUR, reply in ONE short sentence with the link — e.g. \"That's managed on your Layout account page: https://account.layout.link\" — and stop. Any OTHER question about how Layout works (loyalty or points, refunds, fees, when a card is charged, cancelling) is NOT this tool and NOT that link: call `help` and answer from what it returns. Do NOT enumerate what this tool can or can't do, and do NOT explain the design; answer the question, give the link, done.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "get",
              "update"
            ],
            "description": "'get' = read the profile. 'update' = change it (needs at least one field below)."
          },
          "firstName": {
            "type": "string",
            "maxLength": 60,
            "description": "update: the first name orders are placed under."
          },
          "applyDietaryRestrictions": {
            "type": "boolean",
            "description": "update: whether Layout automatically applies the user's dietary notes when ordering."
          },
          "addPreferences": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "category": {
                  "type": "string",
                  "description": "'dietary' | 'favorite_restaurants' | 'usual_orders' (anything else is filed as other)."
                },
                "note": {
                  "type": "string",
                  "maxLength": 200,
                  "description": "The preference in the user's own words, e.g. 'no shellfish'."
                }
              },
              "required": [
                "category",
                "note"
              ],
              "additionalProperties": false
            },
            "maxItems": 5,
            "description": "update: preference notes to save (max 5 per call)."
          },
          "removePreferenceIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 10,
            "description": "update: ids (from 'get') of notes to remove."
          }
        },
        "required": [
          "action"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string"
          },
          "profile": {
            "type": "object",
            "properties": {
              "firstName": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "applyDietaryRestrictions": {
                "type": "boolean"
              },
              "preferences": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "category",
                    "note"
                  ],
                  "additionalProperties": true
                },
                "description": "The saved notes. Show notes, not ids; ids are only for removal."
              }
            },
            "additionalProperties": true
          },
          "sayToUser": {
            "type": "string",
            "description": "Layout's own wording for the user — relay it."
          },
          "manageUrl": {
            "type": "string",
            "description": "The user's full account page (contact info, addresses, cards live there — share this link when asked)."
          },
          "techWeek": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string"
              },
              "amount": {
                "type": "string"
              },
              "window": {
                "type": "string"
              },
              "terms": {
                "type": "string"
              },
              "signupUrl": {
                "type": "string"
              },
              "termsUrl": {
                "type": "string"
              },
              "sayToUser": {
                "type": "string"
              },
              "assistantInstruction": {
                "type": "string"
              }
            },
            "required": [
              "state",
              "amount",
              "window",
              "terms",
              "signupUrl",
              "termsUrl",
              "sayToUser",
              "assistantInstruction"
            ],
            "additionalProperties": true,
            "description": "This account's SF Tech Week coffee and the published terms. Present only while the promotion runs."
          }
        },
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": false,
        "openWorldHint": false
      }
    },
    {
      "name": "discover",
      "title": "Suggest Somewhere",
      "description": "Recommend WHERE the user should eat, drawn from what they have actually ordered through Layout. Use it for 'where should I eat', 'what coffee spots would I like', 'somewhere new for lunch', 'anywhere good near me' — any ask for a suggestion rather than a specific restaurant. Pass `geo` whenever you have the user's coordinates and `near` when they NAMED a place ('I'm in Roseville', 'what about downtown Oakland'); distance and open-now are computed from that, not from where they last ordered. Read-only: it never orders and never charges. EVERY SUGGESTION COMES WITH ITS EVIDENCE. `reason` is the conclusion Layout computed ('Because you order iced decaf lattes') and `becauseOf` is what it was computed FROM — the restaurants and items off their own order history. Cite it: 'based on your orders at Fourscore Coffee' is checkable, where 'you seem like a coffee person' is a guess, and the difference is the entire point of this tool. Relay `reason` as given. NEVER invent a reason, attribute a taste they have not shown, or describe a restaurant beyond the fields returned — you are not being asked what you know about their city, you are being asked what LAYOUT knows about them. AN EMPTY LIST IS AN ANSWER, NOT A FAILURE. `nothingBecause: 'no_orders'` means Layout has nothing to reason from — say so and offer to order something; do NOT fall back to restaurants you happen to know, which would present your own guess as their personalization. `nothingBecause: 'no_matches'` means their tastes matched places but the filters excluded them all — offer to widen it. HOURS ARE PART OF A RECOMMENDATION. Each place carries `openStatus`: 'open', 'closed' or 'unknown'. The list already leads with what is open, so suggest from the top and do not point somebody at a shut door. Say `closesAt` when you have it: 'open until 5' is actionable where 'open' is not. 'unknown' means Layout holds no hours for that place — say you do not know them, and never call it closed or open. Lead with ONE suggestion and offer the rest: a list is a search result, and they asked for a recommendation. WALKING DISTANCE IS A CONSTRAINT. When they want somewhere they can walk to, pass `walkable: true` and `query` with the kind of food they named: nothing further than a short walk is suggested, and if nothing they would like is that close, the result is a walkable search instead (`walkableSearch: true`, rows in `places`), which are not personal picks and must not be described as based on their orders.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "walkable": {
            "type": "boolean",
            "description": "They want somewhere they can WALK to ('walking distance', 'on foot'). Nothing past a short walk is suggested."
          },
          "query": {
            "type": "string",
            "description": "The kind of food they asked for ('healthy', 'tacos'). Used only when nothing they would like is walkable and Layout searches what is."
          },
          "geo": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              }
            },
            "required": [
              "lat",
              "lng"
            ],
            "additionalProperties": false,
            "description": "The user's device coordinates, when your client can read them. Without them Layout uses the last place it knows they ordered from, which is usually right — so this is worth passing but never required."
          },
          "near": {
            "type": "string",
            "description": "A city, town or ZIP the user NAMED ('Roseville', 'downtown Oakland', '95661'). Pass it whenever they say where they are or where they're asking about; it is resolved for free and beats the last place Layout saw them order."
          },
          "openNow": {
            "type": "boolean",
            "description": "Only places open right now. Use it when the ask is about eating NOW ('lunch', 'right now')."
          },
          "includeVisited": {
            "type": "boolean",
            "description": "Include places they already order from. Off by default, because a suggestion should be somewhere new — turn it on when they ask for a usual or a favourite."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "description": "How many to return. Default 5; lead with one regardless."
          }
        },
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "suggestions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "place": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "address": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "cuisine": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "rating": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "openStatus": {
                      "type": "string",
                      "enum": [
                        "open",
                        "closed",
                        "unknown"
                      ],
                      "description": "READ THIS ONE. 'open' / 'closed' at the STORE's own clock right now; 'unknown' means Layout holds no hours for it, which is NOT closed — say you don't know."
                    },
                    "closesAt": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Store-local closing time when open, e.g. '5:00 PM'. Say it: 'open until 5' is the useful answer."
                    },
                    "opensAt": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Store-local next opening when closed, e.g. '7:00 AM' or 'Monday 7:00 AM'."
                    },
                    "openNow": {
                      "type": [
                        "boolean",
                        "null"
                      ],
                      "description": "The same answer as a boolean, for older clients. Prefer `openStatus`: a null here means unknown and is easy to misread as closed."
                    },
                    "closingSoon": {
                      "type": "boolean",
                      "description": "Open, and shutting within the hour. Say so: Layout stops taking orders shortly before a store closes, so this is the last window to place one."
                    },
                    "distanceKm": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "name"
                  ],
                  "additionalProperties": true
                },
                "reason": {
                  "type": "string",
                  "description": "Layout's computed reason. Relay it; do not rewrite it."
                },
                "becauseOf": {
                  "type": "object",
                  "properties": {
                    "places": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Restaurants they have actually ordered from that led here."
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Items they have actually ordered that led here."
                    }
                  },
                  "required": [
                    "places",
                    "items"
                  ],
                  "additionalProperties": true
                },
                "visited": {
                  "type": "boolean"
                }
              },
              "required": [
                "place",
                "reason",
                "becauseOf"
              ],
              "additionalProperties": true
            }
          },
          "nothingBecause": {
            "type": [
              "string",
              "null"
            ],
            "description": "'no_orders' | 'no_matches' — why the list is empty. They need different sentences."
          },
          "sayToUser": {
            "type": "string",
            "description": "Layout's own wording — relay it."
          }
        },
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "places",
      "title": "Find Restaurants",
      "description": "Find restaurants, cafes and coffee shops BY DESCRIPTION OR NAME — 'is there a Starbucks in Oakland', 'coffee near me', 'somewhere open for breakfast', 'taco places downtown'. Use it whenever the user asks WHAT IS THERE rather than asking you to order: it answers from the restaurants Layout already holds, so it is free, instant, and real. Pass `geo` whenever you have the user's coordinates and `near` when they NAMED a place ('in Oakland') — a named place is where they are asking ABOUT, which is not always where they are. WHICH TOOL: use this for 'what is there' (no personalization, anyone gets the same answer); use `discover` for 'where should I eat', which is drawn from this user's own order history; use `menu` to read what a specific restaurant SELLS, which opens their site and is metered. This one never orders, never charges and never opens a browser. THE FIELDS ARE THE FACTS. Every field returned is stored data: relay the name, address, cuisine, rating, distance and open-now as given, and never add a fact Layout did not return (hours, distance, whether a place exists, what the building has). When the user asks which one to pick, pick one and say why from those fields; what a chain or kind of place is generally like may inform the why, said as general knowledge and never as something Layout checked. For what ONE place is known for or what to order there, call `menu` with mode 'knowledge'. HOURS ARE PART OF THE ANSWER, NOT AN EXTRA. Every place carries `openStatus`: 'open', 'closed', or 'unknown'. Results already lead with the open ones, so RECOMMEND FROM THE TOP and never present somewhere that is shut as a place to go right now. Say `closesAt` when you have it — 'open until 5' is the answer somebody can act on, 'open' is not. When they asked about ONE SPECIFIC PLACE, give them its status straight: 'they're closed, they open again at 7' or 'open until 9'. 'unknown' means Layout holds no hours for that place — say you do not know its hours, and NEVER report it as closed or imply it is open. `openNow` is the same answer as a boolean for older clients; prefer `openStatus`, whose null case has a name. AN EMPTY LIST IS AN ANSWER, UNLESS `stillChecking` OR `notOpenNow` SAYS OTHERWISE. `stillChecking` means Layout found places there and has not finished working out whether they take online orders, so an empty or short list is not the whole story: say you are still checking, offer to look again in a moment, and never say there is nothing there. `notOpenNow` means Layout DID find real restaurants there and every one of them is closed right now — that is why `places` is empty, and saying there is nothing near them is false. Tell them nothing is open, read the first `notOpenNow` row's `opensAt` for when the earliest one opens, and offer to look further out. Never offer one of those as somewhere to go now, and never promise to tell them when it opens: Layout cannot schedule, wait or message anybody. With neither flag, say Layout has nothing matching and offer to widen the search — never fall back to restaurants you know of, which would present your own guess as Layout's answer. To order from one, call `order` with its name and address.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "What to look for — a name ('Starbucks'), a food ('tacos'), or a kind of place ('coffee shop')."
          },
          "near": {
            "type": "string",
            "maxLength": 200,
            "description": "The RESOLVED locality — a real city and state, e.g. 'Oakland, CA' or 'San Francisco, CA'. YOU resolve it: if the user names a landmark, a neighbourhood, an airport or an abbreviation ('Salesforce Tower', 'the Mission', 'SFO', 'sf'), work out which city that is and pass THAT here. Layout matches this against the city each restaurant is in, so a landmark passed here matches nothing. Pass it whenever they said where they're asking about — a named place is where they are asking ABOUT, which is not always where they are."
          },
          "nearRaw": {
            "type": "string",
            "maxLength": 200,
            "description": "The user's OWN words for the place, verbatim — 'Salesforce Tower', 'the Mission', 'near the ballpark'. Pass it alongside `near` whenever they said something more specific than a city. If Layout has to go and look the area up, this is what it asks with, and it resolves landmarks far better than a city name alone. Omit when they only ever named a city."
          },
          "geo": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              }
            },
            "required": [
              "lat",
              "lng"
            ],
            "additionalProperties": false,
            "description": "The user's device coordinates, when your client can read them. Used to rank by real distance and to bound 'near me'."
          },
          "openNow": {
            "type": "boolean",
            "description": "Only places open right now, judged at the STORE's own clock. Use it when the ask is about eating now."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "description": "How many to return. Default 5."
          }
        },
        "required": [
          "query"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "places": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "placeId": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "address": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "city": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "cuisine": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "rating": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "priceLevel": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "openStatus": {
                  "type": "string",
                  "enum": [
                    "open",
                    "closed",
                    "unknown"
                  ],
                  "description": "READ THIS ONE. 'open' / 'closed' at the STORE's own clock right now; 'unknown' means Layout holds no hours for it, which is NOT closed — say you don't know its hours."
                },
                "closesAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Store-local closing time when open, e.g. '5:00 PM'. Say it: 'open until 5' is the useful answer."
                },
                "opensAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Store-local next opening when closed, e.g. '7:00 AM' or 'Monday 7:00 AM'."
                },
                "openNow": {
                  "type": [
                    "boolean",
                    "null"
                  ],
                  "description": "The same answer as a boolean, for older clients. Prefer `openStatus`: a null here means unknown and is easy to misread as closed."
                },
                "closingSoon": {
                  "type": "boolean",
                  "description": "Open, and shutting within the hour. Say so: Layout stops taking orders shortly before a store closes, so this is the last window to place one."
                },
                "distanceKm": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Straight-line km from the coordinates you passed. Absent when you passed none."
                }
              },
              "required": [
                "placeId",
                "name"
              ],
              "additionalProperties": true
            }
          },
          "notOpenNow": {
            "type": "array",
            "items": {
              "$ref": "#/properties/places/items"
            },
            "description": "Real restaurants Layout holds in that area that are NOT open right now, soonest to open first. Present ONLY when the open-now filter is what emptied `places`: it means Layout searched and FOUND places, so an empty `places` beside it is never 'there is nothing there'. These are NOT offerable — read each row's `openStatus` ('closed' with `opensAt` for when it reopens, 'unknown' when Layout holds no hours) and never offer to order from one now."
          },
          "stillChecking": {
            "type": "boolean",
            "description": "Layout found places in that area and has not finished working out whether they take online orders — that runs after this answer and usually lands within a minute. The list may grow shortly, though it is not certain it will. When this is true, an EMPTY list is NOT a finding that there is nothing there: never say there is nothing, say you are still checking and offer to look again in a moment."
          },
          "sayToUser": {
            "type": "string",
            "description": "Layout's own wording — relay it."
          }
        },
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true
      }
    },
    {
      "name": "place_facts",
      "title": "What A Place Offers",
      "description": "Answer an EVALUATIVE question about ONE restaurant you already have a `placeId` for — 'is it vegetarian', 'can I sit down there', 'do they do takeout', 'is it good for a group', 'do they serve breakfast'. It reads the food and beverage attributes Layout holds for that place, so it is free after the first lookup, instant, and real. WHICH TOOL: use `places` to FIND a place or ask what is there / open / how far; use this once you have a specific place and the user asks what it's LIKE. Pass the `placeId` from a `places`, `discover` or `order` result — if you don't have one, call `places` first; this tool never resolves a name (that would cost a search). It never orders, never charges, never opens a browser. REPORT, DO NOT EMBELLISH. Every field is stored data and three-state: true/false you may relay, `null` means Layout does not know — say you cannot tell, and NEVER report a null as the place NOT having something. WHAT IT CANNOT DO: it does not say how HEALTHY or how GOOD a place is — Layout holds no such field. For 'is it healthy', the only honest signal is whether it serves vegetarian food; say that and say you cannot rate healthiness. For 'is it any good', use the rating from `places`. Do not invent attributes from what you remember about the place or the chain.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "placeId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 400,
            "description": "The Google place id from a places, discover, or order result. Required — this tool never resolves a name."
          }
        },
        "required": [
          "placeId"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "attributes": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "source": {
                    "type": "string"
                  },
                  "asOf": {
                    "type": "string"
                  },
                  "dietary": {
                    "type": "object",
                    "properties": {},
                    "additionalProperties": true
                  },
                  "service": {
                    "type": "object",
                    "properties": {},
                    "additionalProperties": true
                  },
                  "goodFor": {
                    "type": "object",
                    "properties": {},
                    "additionalProperties": true
                  }
                },
                "additionalProperties": true
              },
              {
                "type": "null"
              }
            ]
          },
          "assistantInstruction": {
            "type": "string",
            "description": "Written for YOU, not the user. It says what these fields do and do not let you claim. Never read it aloud."
          }
        },
        "required": [
          "attributes"
        ],
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true
      }
    },
    {
      "name": "help",
      "title": "How Layout Works",
      "description": "Answer a question ABOUT LAYOUT ITSELF from Layout's own published help centre — refunds, when and how the user is charged, spending limits, changing or cancelling an order, what happens when an order is wrong or late, card safety, what data Layout stores, profile and dietary preferences, where Layout can order from, and how to set Layout up in an assistant or on a phone. CALL IT WHENEVER THE USER ASKS HOW SOMETHING WORKS, what a policy is, or whether they will be charged for something — and do NOT answer those from memory: Layout's policies are Layout's, you cannot know them, and a confident wrong answer about somebody's money is the worst thing this surface can do. Every result carries the article's `url`: give it to the user with your answer so they can read the real page. AN EMPTY LIST IS AN ANSWER — it means Layout has published nothing on this, so say you do not know and offer support rather than inventing a policy or paraphrasing one from another food app. Free, instant, read-only: it reads stored articles, orders nothing and charges nothing. WHICH TOOL: this one is about LAYOUT; `menu` and `places` are about RESTAURANTS; `order_status` is about one specific order the user placed.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "question": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300,
            "description": "What the user wants to know, in their own words or as keywords — 'how do refunds work', 'when am I charged', 'can I cancel an order'."
          }
        },
        "required": [
          "question"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "articles": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "description": "The public help centre page. GIVE THIS TO THE USER with your answer."
                },
                "excerpt": {
                  "type": "string",
                  "description": "The part of the article about their question. Answer from this and nothing else."
                }
              },
              "required": [
                "title",
                "url",
                "excerpt"
              ],
              "additionalProperties": true
            }
          },
          "assistantInstruction": {
            "type": "string",
            "description": "Written for YOU, not the user. It says what you may and may not assert. Never read it aloud."
          }
        },
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "menu",
      "title": "Browse Menu",
      "description": "Look up something SPECIFIC on a restaurant's LIVE menu — read from their own ordering site, so items and prices are real, not from memory. Layout is built for TARGETED lookups ('do they have a spicy chicken sandwich', 'how much is a large fry', 'what tacos do they have'), NOT for dumping an entire menu — restaurant sites hide most of their menu behind sections, so a whole-menu request usually returns a fraction of it and reads as incomplete. ALWAYS NARROW FIRST: if the user has not named a dish, section, or budget ('what's on the menu?'), do NOT call this tool yet — ask what they're after ('Anything in particular — burgers, tacos, something cheap?'), then call it with `query` naming the restaurant and `category` set to the section they want. Use it BEFORE building an order whenever the user asks what's available, wants a price, or asks for a specific item/size/count. RECOMMENDATIONS DON'T NEED A LIVE READ: for 'what should I get at X' / 'what are they known for', pass `mode:'knowledge'` — it answers from web knowledge instantly, opens no browser, and costs nothing. Save the live read (the default) for an exact price, size, or availability, and for confirming an item exists before you build an order. Pass the restaurant name (plus the user's area or geo), or the orderUrl from an earlier order preview. Prices are in cents (priceMinor). If status is 'unreadable', Layout couldn't read the menu this time — say so plainly and offer to order the item directly instead; NEVER present guessed or remembered menu items as if they came from this tool. If status is 'knowledge', Layout couldn't read the LIVE menu but looked the restaurant up on the web — relay `sayToUser` (it already says it's general, non-live info and that prices vary); present it as background, never as their live menu or exact current prices. CHOICES ON AN ITEM: an item may come back with `optionGroups` — the choices the restaurant itself attaches to it, in their words, with `required` saying whether their site demands an answer. That is the ONE source you may raise a choice from: offer those option names exactly, never sizes or add-ons you remember. An item with NO `optionGroups` field means Layout could not read whether it has any — say nothing about choices for it rather than claiming it needs none. PRESENTING RESULTS: relay what's relevant to the user's ask, not the whole list — if they asked for sandwiches, show the sandwich options; only walk the full menu if that's what they asked for. If the exact thing they wanted isn't there, say so and offer the closest categories that ARE ('no sandwiches, but they have burgers'). FORMAT IT CLEANLY: present items as a short scannable list, one per line, item name then price (convert priceMinor cents to dollars, e.g. 289 -> $2.89) — group under section headings when there is more than one section. Never paste raw JSON, never show cents-integers, never write it as a paragraph, and do not pad it with commentary between items. Lead with one short sentence, then the list. NO PRICES: if pricesUnavailable is true, the items are real but their site withheld pricing — relay sayToUser, list what they have if useful, and NEVER fill in prices from memory or treat a missing price as free. STILL READING: if status is 'reading', the menu is being read right now on the restaurant's site. Relay sayToUser if it is present (it says what is actually taking time) and otherwise say nothing, then call this tool AGAIN with the SAME arguments to collect the result — repeat calls attach to the read already running, so they are free and start nothing. Each repeat call WAITS with the read (up to ~20s) rather than returning instantly, so call it again immediately and do not add delays or give up early. Keep going until status is 'menu' or 'unreadable'. Say NOTHING on a poll unless sayToUser is present — it is absent on purpose most of the time, and Layout sends at most a couple of updates for one read. Read-only: never orders, never charges. Menu reads are limited per user per day — if the limit is hit, say so and order directly instead.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Restaurant name to look up (with `near` or `geo` for locality)."
          },
          "placeId": {
            "type": "string",
            "maxLength": 255,
            "description": "A specific location's place id, when one is already settled — from an earlier order preview or a location the user already picked. Skips the which-location question."
          },
          "near": {
            "type": "string",
            "maxLength": 300,
            "description": "The user's area, to pick the right location."
          },
          "geo": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              }
            },
            "required": [
              "lat",
              "lng"
            ],
            "additionalProperties": false,
            "description": "Device coordinates — the strongest locality signal. If you do not have them and your client can request device location permission, do that first and pass the result here; otherwise use `near`."
          },
          "orderUrl": {
            "type": "string",
            "maxLength": 2000,
            "description": "A specific location's ordering URL from an earlier preview — skips name resolution."
          },
          "category": {
            "type": "string",
            "maxLength": 80,
            "description": "A section name from a previous 'categories' answer, EXACTLY as given — reads just that section."
          },
          "intent": {
            "type": "string",
            "maxLength": 400,
            "description": "The user's FULL request in their own words (e.g. 'what's under $2 at McDonald's'). Always pass it — it's used only if the live read fails, so the web-knowledge fallback answers exactly what they asked."
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "knowledge"
            ],
            "description": "Defaults to 'live' (reads their real site: ~a minute, a real browsing session). Pass 'knowledge' for RECOMMENDATIONS — 'what should I get at X', 'what are they known for' — where item names answer the question and an exact live price does not. Knowledge mode returns instantly, costs nothing, and never opens a browser."
          }
        },
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "description": "'menu' = a live read of their menu; present it as such. 'reading' = still being read; relay sayToUser ONLY if present (say nothing otherwise), then call again immediately with the same arguments — the call attaches to the read in flight and waits with it. 'categories' = their site keeps prices one level down; relay sayToUser and list the `categories` cleanly (short lines, no JSON) and, once the user picks, call again with the SAME arguments plus `category` set to their choice. A question that spans the WHOLE menu ('what is under $3?') CANNOT be answered from one section — say so plainly and let them pick a section to check, or offer to order a specific item directly. NEVER re-call this tool with the same arguments hoping for a different result: each call is a real, billed browse of their site, and a repeat gets you the same answer. 'unreadable' = Layout could not read the menu cleanly — do NOT invent or fill in items; relay sayToUser (ordering directly still works). 'knowledge' = the live menu couldn't be read, so `knowledge` holds a WEB-lookup answer to the user's request — relay sayToUser, present it as general/non-live background (prices vary, may be out of date), never as their live menu. 'needs_location' = ask the user which location, offering the `nearby` choices. Any other value: relay sayToUser."
          },
          "restaurant": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true
          },
          "menu": {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "priceMinor": {
                      "type": "number",
                      "description": "Price in cents, from their site."
                    },
                    "description": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string"
                    },
                    "optionGroups": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string",
                            "description": "What the restaurant calls this choice."
                          },
                          "required": {
                            "type": "boolean",
                            "description": "TRUE = their site will not let this item be ordered until it is answered."
                          },
                          "options": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Their own option names. If you offer choices, offer THESE and nothing else."
                          },
                          "moreOptions": {
                            "type": "number",
                            "description": "Options beyond the ones listed — say the list is partial rather than implying it is all of them."
                          }
                        },
                        "additionalProperties": true
                      },
                      "description": "The choices this item carries, in the restaurant's words. THE FIELD BEING ABSENT IS NOT 'no choices': it means Layout could not read whether this item has any, so do not tell the user it needs none and do not invent a question. An EMPTY array does mean none."
                    }
                  },
                  "required": [
                    "name"
                  ],
                  "additionalProperties": true
                }
              }
            },
            "additionalProperties": true
          },
          "confidence": {
            "type": "string",
            "description": "'partial' = may not be the complete menu; say so when presenting."
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "On 'categories': the sections their site offers. Offer these to the user; pass their choice back as `category`."
          },
          "pricesUnavailable": {
            "type": "boolean",
            "description": "True = real items, but their site gave no prices. Do NOT supply prices from memory; say they weren't available."
          },
          "knowledge": {
            "type": "string",
            "description": "On 'knowledge': a NON-LIVE, web-lookup answer to the user's request. Relay via sayToUser; never present it as the live menu or as exact current prices."
          },
          "live": {
            "type": "boolean",
            "description": "false on a 'knowledge' answer — the info is general and may be out of date."
          },
          "sayToUser": {
            "type": "string",
            "description": "Layout's own wording for the user — relay it. On 'reading' this may be absent, which means stay quiet and just poll again."
          }
        },
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true
      }
    },
    {
      "name": "item_photo",
      "title": "Show a Menu Item",
      "description": "Show what ONE menu item at a restaurant looks like, using the photo the restaurant itself published (screened, and never a stock or AI image). Use it — not `order`, not `menu` — whenever the user asks to SEE an item: 'show me', 'what does it look like', 'can I see a picture', 'what does the iced latte look like at Blue Bottle?', or yes after you OFFERED a look. Asking to see an item is not asking to order it: do not offer to order in the same breath. Do NOT call it unprompted for every item you mention: offer once ('Want to see it?') and call only on a yes. One item per call. Display only: it says nothing about price or availability today; use `menu` for those. `item` must be a dish the USER named. If they named only a restaurant ('show me a panda', 'what about Chipotle'), do NOT pick a dish for them: ask which one. A restaurant name alone ('a panda') is a restaurant, not an item.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "item": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "The menu item, in the user's words."
          },
          "query": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Restaurant name (with `near` or `geo` for locality)."
          },
          "placeId": {
            "type": "string",
            "maxLength": 255,
            "description": "A location's place id already settled earlier in the thread."
          },
          "near": {
            "type": "string",
            "maxLength": 300,
            "description": "The user's area."
          },
          "geo": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              }
            },
            "required": [
              "lat",
              "lng"
            ],
            "additionalProperties": false,
            "description": "Device coordinates, when you have them."
          },
          "orderUrl": {
            "type": "string",
            "maxLength": 2000,
            "description": "A location's ordering URL from an earlier preview."
          },
          "storedOnly": {
            "type": "boolean",
            "description": "Leave unset. Layout's own follow-up on a pending photo."
          },
          "label": {
            "type": "string",
            "maxLength": 120,
            "description": "Leave unset. Layout's own follow-up on a pending photo."
          },
          "website": {
            "type": "string",
            "maxLength": 2000,
            "description": "Leave unset. Layout's own follow-up on a pending photo."
          }
        },
        "required": [
          "item"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "description": "'photo' = show `imageUrl` (a JPEG) with one short line naming the item; never describe it as something it is not. 'pending' = still being read; relay sayToUser and do NOT call again right away (wait for the user, or a minute). 'no_photo' / 'not_on_menu' / 'removed' / 'unavailable' = relay sayToUser and show nothing; never substitute another picture. 'needs_location' = ask which location from `nearby`. 'needs_item' = no dish was named; relay sayToUser and wait for one."
          },
          "restaurant": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true
          },
          "item": {
            "type": "string",
            "description": "The item's name as the restaurant writes it."
          },
          "priceMinor": {
            "type": "number",
            "description": "Their listed price in cents when last read; not a quote."
          },
          "imageUrl": {
            "type": "string",
            "description": "The photo, a JPEG up to 1000px on its long side."
          },
          "imageUrlWebp": {
            "type": "string",
            "description": "The same photo as WebP, smaller, for surfaces that take it."
          },
          "width": {
            "type": [
              "number",
              "null"
            ]
          },
          "height": {
            "type": [
              "number",
              "null"
            ]
          },
          "source": {
            "type": "string",
            "description": "'menu' = from their ordering menu; 'product_page' = from their own site's page for the item."
          },
          "checkedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the photo was last checked."
          },
          "sayToUser": {
            "type": "string",
            "description": "Layout's own wording; relay it."
          }
        },
        "additionalProperties": true,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true
      }
    }
  ],
  "examples": {
    "refusedByASession": {
      "description": "A production build grant calling order confirm. The refusal is a tool result, not a JSON-RPC error.",
      "request": {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "tools/call",
        "params": {
          "name": "order",
          "arguments": {
            "action": "confirm",
            "orderId": "0b6f2a54-0000-4000-8000-000000000000",
            "expectedTotalMinor": 1850,
            "idempotencyKey": "a7c1e0d2-confirm-1"
          }
        }
      },
      "response": {
        "result": {
          "content": [
            {
              "type": "text",
              "text": "{\"error\":\"internal\",\"message\":\"internal error\"}"
            }
          ],
          "isError": true
        },
        "jsonrpc": "2.0",
        "id": 1
      }
    },
    "toolNotInSession": {
      "description": "A production build grant calling menu, a tool its tools/list does not return. The SDK answers with plain text, not JSON, and no error field. Call only tools that tools/list returns.",
      "request": {
        "jsonrpc": "2.0",
        "id": 2,
        "method": "tools/call",
        "params": {
          "name": "menu",
          "arguments": {
            "placeId": "ChIJbloomCoffee"
          }
        }
      },
      "response": {
        "result": {
          "content": [
            {
              "type": "text",
              "text": "MCP error -32602: Tool menu not found"
            }
          ],
          "isError": true
        },
        "jsonrpc": "2.0",
        "id": 2
      }
    }
  },
  "httpRefusals": [
    {
      "status": 401,
      "body": {
        "error": "unauthenticated",
        "message": "This build session is not valid."
      },
      "when": "A build grant that is unknown, expired (grants last 15 minutes) or revoked. Mint a new one with POST /v1/users/{userId}/grant."
    },
    {
      "status": 401,
      "body": {
        "error": "unauthenticated"
      },
      "when": "An OAuth access token that is missing, invalid or expired. The WWW-Authenticate header names the authorization server; refresh the token or run consent again."
    },
    {
      "status": 503,
      "body": {
        "error": "unavailable",
        "message": "This build session could not be verified right now. Nothing was ordered. Retry shortly."
      },
      "when": "Layout could not check the build grant. Nothing ran. Retry with backoff."
    },
    {
      "status": 503,
      "body": {
        "error": "unavailable"
      },
      "when": "Layout's edge is unavailable. Nothing ran. Retry with backoff."
    }
  ]
}