# MCP builds match REST

October 21, 2026 · MCP · Breaking

## 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](https://developer.layout.link/reference/mcp#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](https://developer.layout.link/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](https://developer.layout.link/reference/mcp#confirm-with-a-relayed-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](https://developer.layout.link/reference/mcp#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](https://developer.layout.link/reference/mcp#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](https://developer.layout.link/reference/authentication#mcp-refusals).

## Smaller changes

- [mcp-tools.json](https://developer.layout.link/mcp-tools.json) adds `buildSessionTools`, the tools as a build grant's `tools/list` returns them, rendered on [MCP tools](https://developer.layout.link/reference/mcp-tools#build-session-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.
