---
title: "WebMCP"
description: "Register browser tools for AI agents with document.modelContext."
canonical_url: "https://nuxtseo.com/docs/ai-ready/guides/webmcp"
last_updated: "2026-08-04T04:15:39.805Z"
---

[WebMCP](https://developer.chrome.com/docs/ai/webmcp) lets a page register tools for an AI agent in the browser.

[MCP](/docs/ai-ready/guides/mcp) gives remote agents access to your server. WebMCP gives the agent on your page named functions for searching content, filling forms and working with UI state. The agent calls those functions instead of guessing at the DOM.

<caution>

WebMCP is experimental and its browser API may change. Nuxt AI Ready skips registration when `document.modelContext` is unavailable.

</caution>

## Site Tools

Turn on `webmcp` to register three read-only tools for your indexed content:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  aiReady: {
    webmcp: true,
  },
})
```

<table>
<thead>
  <tr>
    <th>
      Tool
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        list_pages
      </code>
    </td>
    
    <td>
      Routes, titles and descriptions, with paging
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        search_pages
      </code>
    </td>
    
    <td>
      Full-text search across page content
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        get_page_markdown
      </code>
    </td>
    
    <td>
      Reads a route as markdown
    </td>
  </tr>
</tbody>
</table>

These tools read the same page index as the MCP server. Prerender your pages or enable [runtime indexing](/docs/ai-ready/guides/runtime-indexing) before using them. The public `/__ai-ready/pages` endpoint only returns content already published through `llms.txt` and your `.md` routes.

Nuxt AI Ready marks all three tools with `readOnlyHint` and `untrustedContentHint`. The annotations tell the agent that the tools do not change state and that page content may contain unsafe instructions.

Tool settings live under `aiReady.tools` because the same definitions can run through MCP Toolkit and WebMCP. Each tool has separate attachment settings:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  aiReady: {
    tools: {
      listPages: {
        webmcp: { enabled: false },
      },
      searchPages: {
        defaultLimit: 20,
        webmcp: { maxOutputChars: 3000 },
      },
    },
    webmcp: true,
  },
})
```

This keeps `list_pages` on the regular MCP server but leaves it out of WebMCP. `defaultLimit` applies to both transports.

## Your Own Tools

`useWebMcpTool()` registers a tool for the lifetime of the current component. Turn on `webmcp` to auto-import the composable, with or without the built-in site tools.

```vue [app/pages/products.vue]
<script setup lang="ts">
const maxPrice = ref(Infinity)
const canFilter = ref(true)

const registration = useWebMcpTool({
  name: 'filter_products',
  title: 'Filter products',
  description: 'Filters the visible product list by a maximum price.',
  inputSchema: {
    type: 'object',
    properties: {
      maxPrice: { type: 'number', description: 'Highest price to show, in dollars.' },
    },
    required: ['maxPrice'],
  },
  annotations: { readOnlyHint: true },
  execute: ({ maxPrice: price }) => {
    maxPrice.value = price
    return `Showing products under $${price}.`
  },
}, {
  enabled: canFilter,
})

watchEffect(() => {
  if (registration.state.value._tag === 'Failed')
    console.error('WebMCP registration failed', registration.state.value.error)
})
</script>
```

The schema infers the `execute` input type. The composable removes the tool when its component unmounts or a cached component deactivates, then restores it when the component activates. Pass a ref or getter as `enabled` when availability depends on local state.

The returned `supported` ref stays false through server rendering and the first client render, avoiding hydration differences. `state` reports `Unsupported`, `Inactive`, `Registering`, `Registered` or `Failed`. `unregister()` permanently removes this composable instance.

Keep the active tool set small. Every definition uses agent context. See [Chrome's tool design guidance](https://developer.chrome.com/docs/ai/webmcp/best-practices).

An `execute` function can return any serializable value. Return an explicit result for expected failures:

```ts
execute: async ({ sku }) => {
  const added = await cart.add(sku)
  if (!added) {
    return {
      ok: false,
      reason: `${sku} is out of stock.`,
      nextTool: 'search_products',
    }
  }
  return { ok: true, message: `Added ${sku} to the cart.` }
}
```

Use the error text to suggest what the model should try next. Keep schemas broad enough for harmless variations, then normalize input in `execute`. A route tool, for example, can accept both `about` and `/about`.

Use `useWebMcpSupported()` when you want to show agent-specific UI:

```ts
const showAgentHints = useWebMcpSupported()
```

## Customizing Built-in Tools

The `ai-ready:webmcp:tools` runtime hook runs before registration. You can add, remove or replace definitions, then change shared or per-tool registration options:

```ts [app/plugins/webmcp.ts]
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('ai-ready:webmcp:tools', ({ tools, registerOptions, toolRegisterOptions }) => {
    const listPages = tools.findIndex(tool => tool.name === 'list_pages')
    if (listPages !== -1)
      tools.splice(listPages, 1)

    registerOptions.exposedTo = ['https://agent.example.com']
    toolRegisterOptions.search_pages = {
      exposedTo: ['https://search-agent.example.com'],
    }
  })
})
```

Nuxt generates the hook type when you enable WebMCP. `nuxt-ai-ready/webmcp` also exports `createSiteTools()`, `defineWebMcpTool()` and the WebMCP types.

## Forms

The declarative API exposes an annotated form as a tool. You do not need JavaScript to register it. Turning on `webmcp` adds the attribute types to your templates.

```vue
<template>
  <form
    toolname="add_to_timesheet"
    tooldescription="Reports a billable task and its hours to the timesheet."
    toolautosubmit
    @submit="onSubmit"
  >
    <input name="task" toolparamdescription="What was worked on.">
    <input name="hours" type="number" toolparamdescription="Hours spent, such as 3.5.">
    <button type="submit">
      Add
    </button>
  </form>
</template>
```

Use `respondWith()` to send a result back to the agent. Cancel the native submit first:

```ts
function onSubmit(event: SubmitEvent) {
  if (!event.agentInvoked)
    return
  event.preventDefault()
  event.respondWith?.(save(new FormData(event.target as HTMLFormElement)))
}
```

The browser emits `toolactivated` and `toolcancel` on `window` when an agent fills in or abandons the form. Use the `:tool-form-active` and `:tool-submit-active` pseudo-classes to show that activity.

## Security

By default, only same-origin agents can discover your tools. To expose them to another origin, list it in `exposedTo`:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  aiReady: {
    webmcp: {
      exposedTo: ['https://partner.example.com'],
    },
  },
})
```

This is the default for built-in tools and tools registered by `useWebMcpTool()`. Override it for one tool through the composable options:

```ts
useWebMcpTool(tool, {
  exposedTo: ['https://specialist.example.com'],
})
```

Built-in tools use the same per-tool override:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  aiReady: {
    tools: {
      searchPages: {
        webmcp: {
          exposedTo: ['https://specialist.example.com'],
        },
      },
    },
    webmcp: true,
  },
})
```

Treat `exposedTo` like an API permission. A trusted origin can run the tool with the user's access, so follow [Chrome's security guidance](https://developer.chrome.com/docs/ai/webmcp/secure-tools).

Page content can also contain prompt injection aimed at the agent. Mark tools that return user-generated or third-party content with `untrustedContentHint`. Only set `readOnlyHint` when a tool cannot change state.

Built-in tools cap output at 1,500 characters and include a link to the full source. Change the limit if your pages need more:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  aiReady: {
    tools: {
      getPageMarkdown: {
        webmcp: { maxOutputChars: 4000 },
      },
    },
    webmcp: true,
  },
})
```

Chrome recommends tool names of 30 characters or fewer, descriptions of 500 or fewer and parameter descriptions of 150 or fewer. `useWebMcpTool()` warns in development when your tool exceeds one of these budgets.

## Testing

Tool selection varies between model runs. Unit tests can verify registration and handlers, but they cannot prove that an agent will choose the right tool. Use Chrome's [evals guide](https://developer.chrome.com/docs/ai/webmcp/evals) to test tool selection, arguments and multi-step tasks. The [Model Context Tool Inspector](https://developer.chrome.com/docs/ai/webmcp) extension shows which tools a page registered.
