---
title: "Remote MCP server"
description: "Serve your documentation to agents over MCP from the site's own server, with list_pages and get_page."
---

> Documentation Index
> Fetch the complete documentation index at: https://nimbus-docs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Remote MCP server

With `agents: { mcp: true }`, the site serves a remote [MCP](https://modelcontextprotocol.io/) 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:

```sh
npx @cloudflare/nimbus-docs add adapter-node
yarn nimbus-docs add adapter-node
pnpm nimbus-docs add adapter-node
bunx @cloudflare/nimbus-docs add adapter-node
```

On 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.

```sh
npm i @modelcontextprotocol/server@2
yarn add @modelcontextprotocol/server@2
pnpm add @modelcontextprotocol/server@2
bun add @modelcontextprotocol/server@2
```

Then turn the server on in the Nimbus config:

```ts title="astro.config.ts"
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"`:

```sh
npx @modelcontextprotocol/inspector --cli http://localhost:4321/mcp --transport http --method tools/list
yarn dlx @modelcontextprotocol/inspector --cli http://localhost:4321/mcp --transport http --method tools/list
pnpm dlx @modelcontextprotocol/inspector --cli http://localhost:4321/mcp --transport http --method tools/list
bunx @modelcontextprotocol/inspector --cli http://localhost:4321/mcp --transport http --method tools/list
```

To 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](https://github.com/modelcontextprotocol/ext-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.

- **`section`** filters by the first path segment: `guide` for `/guide/install/`, or `index` for a root page.
- **`version`** filters by one of the site's [docs versions](/reference/configuration#site-config). Pages outside the docs versions, such as an API reference, appear only unfiltered.

```json
{ "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.

```json
{ "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:

```astro title="src/layouts/DocsLayout.astro"
---
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.

Source: https://nimbus-docs.com/ai/remote-mcp/index.mdx
