# Tools in the browser

> The WebMCP tools every shop page declares, for an agent already in the shopper's browser.

Source: https://storeware.ai/docs/agents/webmcp

Every other door on this platform is for an agent somewhere else, holding a credential and driving a basket it opened itself. This one is for an agent that is already inside the shopper's browser, such as an assistant built into it.

Every shop page declares tools through [WebMCP](https://github.com/webmachinelearning/webmcp), and the forms on the page carry WebMCP's declarative attributes too. The agent acts as the shopper. It needs no key, because it uses the shopper's own session.

## The registered tools

Every page registers these through `document.modelContext`, and a product page adds `get_current_product`:

| Tool | What it does | Hints |
| --- | --- | --- |
| `search_products` | The shop's own search, in the shopper's currency | `readOnlyHint` |
| `get_product` | One product by handle, with the variant ids a basket takes | `readOnlyHint` |
| `answer_shop_question` | The shop's answers about delivery, returns, sizing and what goes with what | `readOnlyHint`, `untrustedContentHint` |
| `view_cart` | The basket and its totals | `readOnlyHint` |
| `add_to_cart` | Adds a variant. Quantities add to what is there | |
| `set_line_quantity` | Sets a line's quantity, or zero to remove it | |
| `apply_discount`, `apply_gift_card` | A code the shopper is holding | |
| `set_buyer_details` | Contact and delivery details, in one post | `consequentialHint` |
| `choose_delivery_option` | One of the delivery rates the shop quoted for the address | |
| `place_order` | Turns the basket into an order | `consequentialHint` |
| `get_current_product` | The product this page is about | `readOnlyHint` |

Every tool states all three hints. `answer_shop_question` is marked untrusted because its answer is written from the merchant's pages and reviews.

## The declarative forms

The forms carry `toolname`, `tooldescription` and `toolparamdescription`, so an agent that reads the page without running its scripts can use them too:

| Form | `toolname` |
| --- | --- |
| The search box | `search_this_shop` |
| The buy button on a product page | `add_this_to_cart` |
| A basket line's quantity | `set_basket_line_quantity` |
| The discount and gift card boxes | `apply_discount_code`, `apply_gift_card` |
| Store credit, for a shopper who is signed in | `use_store_credit`, `remove_store_credit` |
| The details form | `set_delivery_details` |
| The delivery choice | `choose_delivery_option` |
| Keeping a line whose subscription the shop stopped offering, to buy once | `keep_item_to_buy_once` |
| The place order button | `place_this_order` |

Only the search box carries `toolautosubmit`, because a search changes nothing. Everything that takes money or sends a message waits for a person.

The declarative forms are in the markup from the first byte. The registered tools are declared once the page's scripts have run, so an agent reading a page that has just loaded may find `add_this_to_cart` alone for a moment. Poll for the tool you want rather than reading the list straight after a navigation.

## What a tool can do

No tool takes a basket id, a session id, a customer id or a token, so there is nothing to forge and nothing to leak into a transcript. Each tool posts to the same route a person's form posts to, so the shop enforces exactly what it enforces for a click: quantity rules, stock, and who owns the basket. The worst a confused model can do is what the shopper could do by clicking.

No tool can type a card number. `place_order` either returns a settled order, or returns the address where the shopper confirms the card themselves, and says which.

Every basket form is a plain post that answers with a redirect, so it works with scripts off. Send `Accept: application/json` and the same route answers the outcome as JSON instead.

## Calling one by hand

```js
const tool = (await document.modelContext.getTools()).find((t) => t.name === 'add_to_cart');
const answer = await document.modelContext.executeTool(
  tool,
  JSON.stringify({ variantId, quantity: 2 }),
);
const said = JSON.parse(JSON.parse(answer).content[0].text);
```

`executeTool` takes the tool object, not its name. The arguments go as a JSON string, even for a tool that takes none. The result is a JSON string whose content holds another, so it is parsed twice.

The tools are registered on `document.modelContext`, and on `navigator.modelContext` only where the first is missing. A browser with no model context runs none of this.

## Switching it off

The tools are on for every shop. A merchant turns them off under **AI assistants in a shopper's browser** in the shop's settings.
