# Browser agent tools (WebMCP)

A Lakebed app can let a browser agent use it. Tag a query, mutation, or action with a `tool` option. The app page then registers that handler as a [WebMCP](https://webmachinelearning.github.io/webmcp/) tool. An agent in the user's browser can call the tool instead of clicking through the UI.

This feature is experimental. WebMCP is a draft web standard. Today a user must turn on `chrome://flags/#enable-webmcp-testing` in Chrome for an agent to see an app's tools. Other browsers ignore tools, and the app works as before.

## Tag a handler

Pass `{ tool: { description, args } }` as the second argument to `query`, `mutation`, or `action`:

```ts
import { boolean, capsule, id, mutation, query, string, table, userId } from "lakebed/server";

export default capsule({
  schema: {
    todos: table({ text: string(), done: boolean().default(false), ownerId: userId() }).index("by_owner", ["ownerId"])
  },

  queries: {
    todos: query(
      async (ctx) => {
        const { userId } = ctx.auth.requireIdentity();
        return ctx.db.todos.withIndex("by_owner", (q) => q.eq("ownerId", userId)).collect();
      },
      { tool: { description: "List the current user's todos." } }
    )
  },

  mutations: {
    setTodoDone: mutation(
      async (ctx, args) => {
        const { userId } = ctx.auth.requireIdentity();
        const todo = await ctx.db.todos.get(args.todoId);
        if (!todo || todo.ownerId !== userId) {
          throw new Error("Todo not found.");
        }
        await ctx.db.todos.update(args.todoId, { done: args.done });
      },
      {
        tool: {
          description: "Mark one of the current user's todos as done or not done. Get todo IDs from the todos tool.",
          args: { todoId: id("todos"), done: boolean() }
        }
      }
    )
  }
});
```

The tool name is the handler name. Two tools cannot share a name, so a query and a mutation with the same name cannot both be tools.

## Arguments

Build `args` from `string()`, `number()`, `boolean()`, and `id("table")`. Chain `.optional()` for an argument the agent can leave out. Tool arguments cannot use `.default()` or `userId()`. Read the caller from `ctx.auth`, never from an argument.

With `args`, the handler takes one object, typed from those fields. Client code calls it with the same object:

```ts
const setTodoDone = client.useMutation("setTodoDone");
await setTodoDone({ todoId: todo.id, done: true });
```

Without `args`, the tool takes no input and the handler takes no argument after `ctx`.

Read fields from the object, as in `args.todoId`. Optimistic updates cannot predict a mutation that destructures its argument, such as `async (ctx, { todoId }) => ...`.

## How a tool call runs

A tool call runs in the user's tab, through the same session as the app's UI. The server applies the same checks as any other call:

- `ctx.auth` is the user who has the tab open, guest or signed in.
- `auth: { requireSignIn: true }` rejects the call until the user signs in.
- Your handler's ownership checks and validation still run.

Lakebed checks the agent's input against `args` before the call. It rejects unknown inputs, missing required inputs, and wrong types. Other callers, such as your client code, are not checked against `args`. Validate input in the handler as you do for any query or mutation.

A query tool reads the current result once and returns it. Lakebed marks query tools as read-only for the browser. Mutation and action tools return the handler's result.

## Write descriptions for an agent

The agent picks tools from their descriptions. Say what the tool does, what it returns, and where its inputs come from. "Get todo IDs from the todos tool" tells the agent how to chain calls.

Tool names, descriptions, and arguments ship in the app's client bundle. Do not put secrets in them.

Treat tool results as data that an agent reads. If a tool returns text that other users wrote, that text can try to steer the agent. Return only the fields the agent needs.

## Inspect tools

`lakebed inspect` lists the tools, with their kinds, arguments, and descriptions. Use `--json` for the full `tools` object. This works for `lakebed dev` and for hosted deploys.

```sh
lakebed inspect --port 3000
lakebed inspect <deploy-id-or-url>
```
