---
title: "WebMCP agent tools"
description: "Let an AI agent in the buyer's browser search, select, and optionally hold seats on the real map through the picker's off-by-default tools."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.seatlayer.io/llms.txt
> Use this file to discover all available pages before exploring further.

# WebMCP agent tools

WebMCP is a draft browser API that lets a page hand callable tools to an AI
agent running in the same browser (`document.modelContext`). With one option
the SeatPicker registers a small set of tools, so an assistant asked for "two
seats together under $90" drives the real map instead of guessing from pixels:
the seats light up in the buyer's basket, availability and price come from the
live event, and the buyer completes checkout exactly as before.

> **Released in browser SDK 0.71.5**
>
> `webMcp` is available in the public SeatLayer browser SDK. Pin 0.71.5 or a
> later compatible release, and keep the option off for events that should not
> expose browser-agent seat tools.

```ts title="webmcp.ts"
const picker = new SeatPicker({
  container: '#picker',
  event: eventKey,
  publicKey,
  webMcp: true,               // search + select tools
  // webMcp: { holds: true }  // also allow the agent to place a hold
});
await picker.render();
```

- **Off by default.** Nothing is registered unless `webMcp` is set.
- **Feature-detected.** If the browser has no `document.modelContext` (WebViews,
  older browsers), the option is inert. No code path touches the picker.
- **Torn down with the picker.** `destroy()` unregisters every tool.
- **Checkout stays human.** There is no checkout or payment tool.
- **Organizer text is marked untrusted.** Tool annotations tell compatible
  agents that event, category and seat labels need untrusted-content handling.

## Tools the picker registers

| Tool | Reads or acts | What it returns |
| --- | --- | --- |
| `seatlayer_describe_event` | reads | event name and time, currency, sales state, ticket types with price and live availability, zones, selection limits, whether holds are allowed |
| `seatlayer_find_seats` | reads | up to `limit` candidate groups of `count` seats, adjacent by default, within `max_price`, optionally filtered by ticket type or zone. **Never selects or holds.** |
| `seatlayer_select_seats` | acts | puts the given seats in the basket; the buyer sees them on the map; respects `maxSelection` and your `selectionValidators` |
| `seatlayer_get_selection` | reads | current seats, count, total, currency and the active hold if any |
| `seatlayer_hold_selection` | acts | only with `webMcp: { holds: true }`; the same action as the buyer tapping **Select seats**; `onCheckout` fires as usual |

Every tool callback returns a plain object; the browser's `executeTool()` API
serializes it to JSON text. Failures come back as
`{ ok: false, reason, message }` (`bad_count`, `no_match`,
`not_enough_together`, `sold_out`, `event_closed`, `nothing_selected`,
`not_allowed`) so callers receive a typed failure result.

## Holds: who decides

By default an agent can search and select, and the buyer taps to hold. Set
`webMcp: { holds: true }` when you want the agent to place the timed hold
itself. The hold uses the ordinary picker TTL, `onHoldChange` and `onCheckout`
callbacks, and release rules. Nothing downstream changes.

> **Inventory is yours**
>
> A hold locks seats for other buyers. Keep holds tap-only unless your
> organisers want a fully agentic flow on that event.

## Iframe and hosted embeds

Tools are exposed to their own document by default. If an in-page agent in a
cross-origin host needs the picker's tools, both sides opt in. The picker names
the host's exact, trustworthy origin:

```ts title="picker-frame.ts"
const picker = new SeatPicker({
  container: '#picker',
  event: eventKey,
  publicKey,
  webMcp: {
    exposedTo: ['https://tickets.desipass.com'],
  },
});
```

The host grants the frame the `tools` permission:

```html
<iframe src="https://picker.example.com/event/123" allow="tools"></iframe>
```

An in-page agent in the host requests tools from the frame origin:

```js
const tools = await document.modelContext.getTools({
  fromOrigins: ['https://picker.example.com'],
});
```

Do not build `exposedTo` from a query string or request header. Same-origin
agents do not need it, and browser-provided agents use their browser's own tool
discovery path.

## Integrator pattern: DesiPass agent-assisted booking

Keep seat work and commerce as two explicit trust boundaries:

1. SeatLayer's browser tools describe the event, find seats and select them.
2. With `holds: true`, the agent may place the same temporary hold as the
   buyer's **Select seats** action, after confirming the choice with the buyer.
3. `onCheckout` hands the opaque hold ID to DesiPass. The DesiPass server
   inspects that hold, calculates the authoritative total, takes payment and
   books once with an idempotent booking reference.

```ts title="desipass-event-page.ts"
const picker = new SeatPicker({
  container: '#picker',
  event: eventKey,
  publicKey,
  webMcp: { holds: true },
  onCheckout: async (_hold, _seats, handoff) => {
    // Send identity, not browser-posted prices, tiers or seat labels.
    const response = await fetch('/api/seat-checkout', {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ holdId: handoff.holdId }),
    });
    const checkout = await response.json();
    openDesiPassCheckout(checkout.id);
  },
});
```

On the server, follow the ordinary [hold inspection and checkout
flow](/buyer-sdk/holds-and-checkout/): authenticate the buyer, inspect the hold
with a server credential, calculate from the returned items, and handle expiry
or a `409` booking conflict. An agent-facing checkout tool must accept only a
server-issued checkout or confirmation ID — never price, currency, seat IDs or
payment-card data — and must keep the buyer's confirmation UI in the loop.

A remote DesiPass MCP client, such as a WhatsApp assistant, cannot call tools
that exist only inside a browser page. For a seated event it should return the
event deep link and a structured `booking_mode: "browser_handoff"`. Once the
buyer opens that page, the browser agent can use the SeatLayer tools; DesiPass
then resumes its normal checkout and booking-status flow. A host can expose
`desipass_open_checkout`, which opens a server-created checkout session
and reports `requires_buyer_confirmation: true` rather than attempting to book
from chat-supplied seat or price data.

## Trying it

WebMCP ships behind a flag in current Chrome
(`chrome://flags/#enable-webmcp-testing`). With it on, open your page and in
DevTools:

```js
const tools = await document.modelContext.getTools();
tools.map(t => t.name);
// ['seatlayer_describe_event', 'seatlayer_find_seats', …]

const find = tools.find(t => t.name === 'seatlayer_find_seats');
JSON.parse(await document.modelContext.executeTool(find,
  { count: 2, max_price: 90 }));
```

## Related

- [Best available seats](/buyer-sdk/best-available/) — the same search from
  your own UI, with a hold.
- [Holds and checkout](/buyer-sdk/holds-and-checkout/) — what happens after
  `seatlayer_hold_selection`.
- [Agents](/agents/overview/) — the public knowledge MCP and the Designer MCP.

Source: https://docs.seatlayer.io/buyer-sdk/webmcp-agent-tools/index.mdx
