MCP builds match REST

MCPBreaking

Store names

  • A build session’s build with a placeId and a restaurant or restaurantName now compares every word of the name against the store’s name and address, as POST /v1/carts does. A name the store does not carry answers storeMismatch and builds nothing.
  • A build or preview for a name Layout finds no restaurant by answers store_not_found, on a preview as preview.outcome: "store_not_found" with preview.storeNotFound.candidates. Its candidates are restaurants with other names within about 30 miles. Offer them by their own names, never as branches, and build at one only if the person picks it, with its placeId and its own name as restaurant.
  • In sandbox, a name or test_place_ id that no test restaurant carries answers store_not_found, with the test restaurants as candidates.

See What a preview concluded.

Cancel while building

A build grant can now cancel a cart that is still building: order with action: "status", cancel: true and that build's idempotencyKey answers cancel.status: "canceling", and Layout releases the cart the moment the build lands and never texts the person about it. It works only for a build your application started; any other key answers no_active_order. In mcp-tools.json it is the session action cancel_build. A cancel naming neither an orderId nor an idempotencyKey is still refused as forbidden. See Confirm with the person's code.

ord_ ids on every action

orderId takes the ord_ publicOrderId on order_status, confirm and a cancel, and every answer is the same as for the id the build returned. A build grant reads only its own application’s orders. See The tools.

Carted, expired and canceled carts

  • A ready cart nobody has confirmed, read by orderId, answers placement.status: "carted" with totalMinor, price and expiresAt, where it answered building. It waits on the person, so stop polling it.
  • A cart past its confirm window reads cart_expired by key, and failed with expired: true by orderId. Its answer carries failureCode: "CART_EXPIRED", where it was OUR_TIMEOUT, test restaurants included.
  • Reading an expired cart with order_status closes it, as a REST read does, so order.failed is sent at once.
  • A cart the person canceled reads canceled by key, and by orderId its message says it was canceled.

Confirm totals

A build session’s confirm carries totalMinor, the total you confirmed with, fee included; merchantTotalMinor, the restaurant’s own total; and subtotalMinor, the food before tax, absent when Layout cannot read it. They are on every answer about an order that went ahead: placing, placed, confirmed, already_placing and an alreadyConfirmed answer. See Confirm in sandbox.

Waits you can act on

A confirm or resend answered rate_limited with no reason now carries retryAfterSec. too_many_attempts and limiter_unavailable carry retryAfterSec too, and too_many_attempts carries lock: person, requester or spacing.

grant_expired

An expired build grant sent to mcp.layout.link now answers 401 with error.code: "grant_expired" and WWW-Authenticate: Bearer error="invalid_token", error_description="grant_expired", as on api.layout.link. Mint a new grant. Any other refused grant still answers unauthorized. A secret key sent to mcp.layout.link is 401 unauthorized with WWW-Authenticate: Bearer error="invalid_token" and a message naming the build grant. See MCP refusals.

Smaller changes

  • mcp-tools.json adds buildSessionTools, the tools as a build grant’s tools/list returns them, rendered on MCP tools.
  • The order and order_status output schemas in a build grant’s tools/list no longer list build.confirmationCard, which a build session never gets.
  • A simulated challenge carries smsSent: false.

Breaking changes

  • A build session’s build whose name has a word the store does not carry now answers storeMismatch, where it could build.
  • A build session’s build for a name Layout cannot find now answers store_not_found, where it could ask which of several other restaurants.
  • A build session’s confirm subtotalMinor is now the food before tax, not the restaurant’s total. Read merchantTotalMinor for that, and totalMinor for what was confirmed.
  • Code that polls a carted cart by orderId until it leaves building now gets carted at once, and a poll of an expired cart answers cart_expired or failed.
  • Code that matches OUR_TIMEOUT for an expired cart over MCP now gets CART_EXPIRED.
  • Code that matches unauthorized for an expired build grant now gets grant_expired.
  • A build session’s build that asks for delivery builds nothing and answers deliveryUnavailable: { reason: "not_enabled" }, where it used to build a pickup.

Everything else here is a new field, value or action, or a call that used to be refused, and code that ignores what it does not know keeps working.

All changes