Browser Agents (WebMCP)
When a reader asks their browser's AI assistant a question about your project, it can search your docs the same way they would — instead of guessing.
AI assistants are moving into the browser itself: sidebars, extensions, and built-in agents that can see the page a reader is on. WebMCP is the draft web standard that lets a page hand such an agent real tools instead of leaving it to scrape rendered HTML.
undocs sites do this out of the box. Your docs show up to the agent as six tools — search, list, read, project info, current page, navigate — so a question like "how do I deploy this to Vercel?" gets answered from your actual pages, with a link the reader can click.
There is nothing to install and nothing to configure.
#What a reader gets
Say someone is on your docs with an agent-enabled browser and asks:
"Does this support custom themes, and where do I read about it?"
Instead of answering from whatever it remembers about your project, the agent:
search_docs("custom theme") — the same index behind ⌘K,read_page("/guide/custom-theme") for the full Markdown,navigate there.The answer is grounded in the page you actually wrote, at the version you actually shipped.
#The tools
| Tool | What it does |
|---|---|
search_docs | Full-text search across every page and heading. Returns titles, previews and linkable URLs |
list_pages | Every page in navigation order, with path and description — the table of contents |
read_page | The Markdown source of one page. Long pages arrive in slices with a cursor to continue |
get_current_page | The page the reader is looking at right now, plus its heading outline |
get_project_info | Your repository, issues, releases, social links, llms.txt bundles and versions |
navigate | Opens a page in the reader's tab, optionally scrolled to a heading |
Each one is a thin wrapper over machinery the page already has. search_docs
queries the same MiniSearch index as the command palette — including its
strict-then-fuzzy fallback — and read_page returns the same Markdown that
/llms.txt points to. An agent searches exactly what a visitor searches, so
there is no second index to build and nothing to keep in sync.
Results are structured data. A search hit looks like this:
{
"title": "Custom Theme",
"breadcrumb": "Guide > Custom Theme",
"path": "/guide/custom-theme",
"url": "https://docs.example.com/guide/custom-theme#tokens",
"preview": "Override the design tokens in your own CSS…"
}The two tools that answer with prose — search_docs and read_page — use
MCP's own result shape, with the text in a content block and the fields above
in structuredContent beside it. A browser hands a tool's return value to the
agent as JSON, so a page of Markdown returned as a plain field would arrive with
a literal \n for every newline and a backslash before every quote. This way an
MCP-aware client unwraps it and the model reads your page as your page.
A long one is capped at 40,000 characters per call. An agent working with a
smaller context window can pass maxLength to take it in finer slices, walking
the same nextOffset cursor.
Every page result also carries markdownUrl (its /raw/*.md source) and
editUrl (the "edit this page" link on GitHub), so an agent can quote a page
verbatim or send a contributor straight to the file. Generated routes — the
landing page and the blog listing — have no Markdown behind them, so those
fields are simply left out rather than pointing at a URL that 404s.
Agents arrive with old links — a URL from a chat last month, a path someone
pasted, a page you moved two releases ago. So read_page and navigate follow
the redirects in your .config/docs exactly as a visitor's browser does: ask
for a moved path and the tool answers with the page it moved to, noting the
redirectedFrom path it started from. A redirect that points off your site
(/changelog → your GitHub releases) is reported as the external URL it is,
rather than as a page that failed to load.
get_project_info covers the rest: where the source lives, where to file a bug,
the release feed, your community links, and the llms.txt bundles for an agent
that would rather ingest the whole corpus at once. It reads all of this from
.config/docs using the same conventions your header and footer render, so an
agent and a visitor are never looking at different links.
#Your reader stays in control
Five of the six tools only read. navigate is the one that changes what the
reader sees, and it is marked as such, so a well-behaved agent asks before
moving anyone. It also refuses paths that aren't real routes — a hallucinated
link can't strand your reader on a 404.
What comes back is your own content: your pages, your nav and your
.config/docs, all from the one repository the site is built from. So nothing is
flagged as untrusted — that marker is for a tool reaching something the page's
author does not control, and spending it on every result here would just teach an
agent to ignore it.
#Trying it out
WebMCP is early: a native document.modelContext exists only in browsers
implementing the draft, and only in a secure context (HTTPS or localhost).
Waiting for that would mean the tools are almost never live, so undocs also
ships a polyfill — a small port of
Google's reference polyfill,
which is what today's agents, extensions and inspectors are written against. It
is installed only when the browser has nothing of its own, and a real
implementation always wins.
The tools stay in their own lazy chunk either way. Behind the polyfill it isn't fetched until an agent first asks for tools, so a visitor who never brings one downloads the polyfill — under a kilobyte, gzipped — and nothing more.
To check whether the tools registered, open the console on your site:
await document.modelContext.getTools(); // → the six docs tools
// `executeTool` is the polyfill's own (a native browser calls tools itself):
await document.modelContext.executeTool("search_docs", { query: "deploy" });Tip
Not every agent speaks WebMCP yet, and some never will. Your docs are also
published as /llms.txt and /llms-full.txt — plain-text
bundles any model can read — so agents outside the browser are covered too.
#Turning it off
export default {
webmcp: false,
};No polyfill is installed, nothing is registered, and the chunk is never loaded.