get_menu
Return the full canonical menu for a restaurant. Input:{ "restaurant_id": "rest_lb_steakhouse" }
Output:
- A restaurant serves all of its
menusas siblings, not as versions of each other. Each has its ownavailabilityWindows(empty/absent = always available). Render one menu at a time. - Display ordering: each item carries three positions —
menuSortOrder(position of the item’s menu within the restaurant, mirroring that menu object’s ownsortOrder, so the two never disagree),categorySortOrder(position of the category within its menu), andsortOrder(position of the item within its category). Sort bymenuSortOrderbeforecategorySortOrderbeforesortOrder. Two menus can each send a category withsortOrder: 0, so category order is only meaningful within a menu. All three are optional on an item — they’re always set on fresh imports, but rows from before menus were tracked may omit them; treat a missing value as0. - Items with no
menuIdare legacy rows imported before menus were tracked — treat them as belonging to the restaurant’s only/first menu. - An item’s
availabilityWindowsare the effective windows (menu opening hours, unless the category or the product overrides them). Combined with the restaurant’sopeningHours, this is what lets a client grey out an item that exists but is not orderable at the selected fulfillment time. Enforced server-side too (item_unavailable_at_time). - Modifiers can nest: a modifier may own
childModifierGroupIdspointing back into the same flat top-level collections.defaultQuantity >= 1means it comes pre-selected (marketplace menus carry the provider’s own merchant defaults this way; send them unless the user removes them, or the provider fills a default into any modifier group the line leaves out). Groups withisBundleSection: truebelong to a meal deal (isComboitems);maxPerModifiercaps repeats of a single modifier independently of the group total.
capabilities.supply: "marketplace") adds these live fields:
store_is_open— the store’s open state right now.falsemeans no ASAP order is possible.supports_order_ahead— present whenstore_is_openisfalse.truemeans the closed store still takes scheduled orders: build the cart and quote a later time.falsemeans item adds and validation fail even for scheduled slots; do not build a cart.store_next_open_time— ISO instant a closed store reopens, when known. It is not the store’s hours or closing time: which times the provider will schedule is only known fromquote_order(see scheduled times on marketplace supply).promotions— the store’s active, cart-independent deals, once per menu:[{ "id": "dae14fad-…", "title": "$3 off orders $15+", "description": "Use code: PEPSI3OFF. Must add eligible item to cart." }].idis the provider’s campaign handle, not a code a person types. Items list the deals they qualify for inpromotionIds(values ofpromotions[].id), present only when non-empty. An item with nopromotionIdscan still be discounted by a subtotal-wide deal. Whether a deal applies, and for how much, is only known fromquote_order(provider discounts arrive as negative line items and inpromoDiscountCents) — never promise an amount from the menu. Absent when the store has no deals and on the single-item (item_id) response. Deals are evaluated at placement for the slot you pass, so a deal active while browsing applies to a scheduled order placed now. In every case observed only one promotion applied per cart and the provider chose which, not necessarily the largest — never add deals up or promise a particular one. Still never promise an amount —quote_orderfor the actual slot is the authority.fulfillment_address(input, optional) — the dropoff you are browsing for (address1and/orlatitude/longitude, plus city/state/postal_code). Deal eligibility is address-dependent and the provider account’s default address may be someone else’s, so the menu is read for this address when it matches one saved on the account; otherwise the account default is used. Pass the same dropoff you will quote with.
get_restaurant_detail
Return restaurant profile, location, timing, busy state, and menu structure. Input:{ "restaurant_id": "rest_lb_steakhouse" }
This call also refreshes opening hours from the POS (the restaurant may have edited hours there); the response waits only briefly on the refresh and falls back to stored hours.
Output:
- The tool returns
restaurant,location,menus,categoriesonly. Timing fields are mirrored ontorestaurantso they survive that trim. categoriesare scoped per menu (menuId/menuName): two menus can each own a “Sides”, and merging them would report one oversized category no single menu actually has.selfDeliversEnabledis already reflected infulfillmentTypesSupported(delivery is stripped when a store neither self-delivers nor has a courier); it is surfaced explicitly so a client can explain why a store is pickup-only. For self-delivery orders the pickup and delivery times are the same instant — the 15-minute courier collection lead only applies when a real courier collects.
check_delivery_availability
Pre-checkout courier probe: is delivery possible from this restaurant to this dropoff at this time, before any order exists? Input:address2 (unit/apartment/suite) is optional and reaches the courier, same as on the order intent’s fulfillment_address.
Deliberately optimistic: only an explicit courier “not available” returns available: false. Transient failures return available: true with checked: false. The ETA/quote fields are null whenever no usable quote came back (probe disabled, no location, or an explicit no).
Rate limit: 60 calls/min per agent, separate from and stricter than the per-agent MCP quota — every call places a live request with the courier. Exceeding it throws a tool error naming the limit, and no courier request is made. Probe the addresses you are actually going to order to, not a grid.