With agents: { mcp: true }, the site serves a remote MCP server at /mcp. Any MCP client that speaks Streamable HTTP can connect, without a key, and read the documentation through two tools: list_pages and get_page. The server runs inside the site’s own server output, so there is nothing else to deploy.
The server reads what the build already publishes: the pages and Markdown versions behind llms.txt and site search. There is no search tool; agents list pages and read them, or fetch llms.txt and the .md URLs directly.
Turn it on
The server needs a server adapter, because /mcp answers requests at run time. If the site is static, add the adapter for the host you deploy to: cloudflare, netlify, node or vercel. For a Node server:
npx @cloudflare/nimbus-docs add adapter-nodeyarn nimbus-docs add adapter-nodepnpm nimbus-docs add adapter-nodebunx @cloudflare/nimbus-docs add adapter-nodeOn a site with no rendering policy, add adapter-cloudflare also sets rendering: { default: "request" }, so docs and API pages render on request. The server doesn’t need that: to keep them prerendered, set rendering: { default: "build" }. The server itself only needs /mcp and its card on request.
Install the MCP server SDK in the project. It is an optional dependency of Nimbus: sites without the server never install it.
npm i @modelcontextprotocol/server@2yarn add @modelcontextprotocol/server@2pnpm add @modelcontextprotocol/server@2bun add @modelcontextprotocol/server@2Then turn the server on in the Nimbus config:
const nimbusConfig = defineNimbusConfig({
site: "https://docs.example.com",
title: "Example",
agents: { mcp: true },
});A build with agents.mcp and no server adapter, or without the SDK installed, stops with a message that says what to add; nimbus-docs check reports both before a build.
To turn it off, set agents.mcp to false: /mcp, the card and the Copy MCP URL action go away. You can then remove @modelcontextprotocol/server, and the adapter if nothing else on the site renders on request.
Try it locally
astro dev serves /mcp, without an adapter, from the last content sync. List the tools with the MCP Inspector, at the address astro dev prints, with the base path, and as /mcp/ under trailingSlash: "always":
npx @modelcontextprotocol/inspector --cli http://localhost:4321/mcp --transport http --method tools/listyarn dlx @modelcontextprotocol/inspector --cli http://localhost:4321/mcp --transport http --method tools/listpnpm dlx @modelcontextprotocol/inspector --cli http://localhost:4321/mcp --transport http --method tools/listbunx @modelcontextprotocol/inspector --cli http://localhost:4321/mcp --transport http --method tools/listTo try the production build, run it as the adapter does in production: node dist/server/entry.mjs on Node (set HOST and PORT), or wrangler dev on Cloudflare.
The URLs the server returns, and the Copy MCP URL action, use site, not the local address, so point your client at the local address yourself.
What the site serves
| Path | What it is |
|---|---|
/mcp |
The server. POST a JSON-RPC message; each answer is one application/json body, and a notification gets 202; GET and DELETE answer 405, since the server is stateless and keeps no sessions. A POST whose Content-Type isn’t application/json, or a DELETE without a Content-Type, from anywhere but the site’s own Origin gets Astro’s origin-check 403 instead. OPTIONS answers the browser preflight. |
/mcp/server-card |
The Server Card: the server’s name, the installed Nimbus version, a description and the endpoint URL, served on request as application/mcp-server-card+json with Access-Control-Allow-Origin: *, cacheable for five minutes (Cache-Control: public, max-age=300, s-maxage=300). |
The card is listed in /.well-known/ai-catalog.json (unless agents.discovery is false), and llms.txt names the endpoint, so agents that start from either find the server. The server’s name is the site’s hostname reversed plus /docs: docs.example.com becomes com.example.docs/docs. A site on localhost or an IP address is local.nimbus/docs.
Under a base, both paths sit under it: https://docs.example.com/docs/mcp. With trailingSlash: "always", they’re /mcp/ and /mcp/server-card/.
Use exactly that form. The other one gets a 404 in astro dev, and a redirect in production that MCP clients can’t follow. The card, llms.txt and the Copy MCP URL action all give the right form.
A page, content entry or public file at either path fails the build, since the server would hide it.
The tools
list_pages returns pages 200 at a time, each with its title, url, markdownUrl and section, plus description and version when it has them. Pass nextCursor back as cursor for the next batch.
sectionfilters by the first path segment:guidefor/guide/install/, orindexfor a root page.versionfilters by one of the site’s docs versions. Pages outside the docs versions, such as an API reference, appear only unfiltered.
{ "section": "guide" }get_page returns one page as Markdown: { "title", "url", "markdownUrl", "content" }. Pass the url that list_pages returned, or a site-relative path such as /guide/; only the path of an absolute URL is used. Pages over about 100,000 characters are cut with "truncated": true; the markdownUrl serves the whole page.
{ "url": "https://docs.example.com/guide/" }Both tools return JSON in a text content block. Errors come back in the result with isError set, as { "error": { "code", "message" } }:
| Code | Meaning |
|---|---|
invalid_input |
An unknown version, or a cursor from other filters. Arguments that don’t match the schema get the MCP SDK’s message instead. |
not_found |
No published page at that URL. |
unavailable |
The site’s published files couldn’t be read. |
Which pages
The tools see the pages that have a Markdown version: collections made with docsCollection() and apiCollection(), in every rendering mode. A collection defined by hand with Astro’s defineCollection has no Markdown version and is not listed, the same as in llms.txt.
list_pages matches site search: it leaves out noindex pages unless they set searchable: true, and deprecated versions unless you ask for one. Drafts and hidden versions are never listed or served.
get_page also reads noindex pages by URL. An API URL with ?version= resolves to the default version, the one with Markdown.
A homepage made in src/pages instead of a content entry isn’t a page here: get_page with / finds nothing, and /index.md serves the llms.txt index instead.
Connect a client
Give the client the endpoint URL using its own remote MCP setup instructions. Configuration formats differ between clients.
The starter’s docs layout shows a Copy MCP URL page action whenever the server is on. Clicking it copies the endpoint URL and briefly changes the label to Copied.
Then in src/layouts/DocsLayout.astro, pass it to PageActions, which renders it after its own actions:
---
import ConnectMcp from "@/components/ConnectMcp.astro";
import { capabilities } from "virtual:nimbus/agent-capabilities";
---
<PageActions markdownUrl={markdownUrl} lastUpdated={lastUpdated} class={description ? undefined : "mt-3"}>
{capabilities.mcpUrl && <ConnectMcp />}
</PageActions>The guard keeps the action’s script off sites without the server. A PageActions copied before the slot existed doesn’t render children: update it with nimbus-docs add page-actions --overwrite, or keep <ConnectMcp /> after it.
Limits and safety
The server is read-only and public. It serves what the site already publishes, with Access-Control-Allow-Origin: * so browser clients can connect. There’s no authentication, so keep private docs behind your own access control.
A malformed or null Origin header gets 403, and a body over 64 KB gets 413.
Rate limiting is the host’s job: add a rule for the /mcp path in its firewall, or in a proxy in front of the site. The server itself does none.
Adapters
Nimbus tests the server on the Node and Cloudflare adapters. Node reads the published Markdown from dist/client; Cloudflare reads it through the asset binding.
Other adapters work when the server can read dist/client or fetch it from the site’s URL; test a tools/call on the deployed site. A preview behind access protection answers that fetch with 401, and the tools report unavailable.
With the server on, the server bundle carries the MCP SDK, about 140 KB gzipped, loaded the first time /mcp is called.