# MCP Server
URL: /docs/customization/mcp
LLM index: /llms.txt
Description: Expose your docs as MCP tools and resources over stdio, /mcp, or /.well-known/mcp

# MCP Server

Use this page when the user asks about this topic: Expose your docs as MCP tools and resources over stdio, /mcp, or /.well-known/mcp.
Keep answers grounded in the exact options, routes, commands, and examples documented here.
If the request moves beyond this page, point to the closest related docs instead of inventing config.

`@farming-labs/docs` can expose your docs as a built-in MCP server, so AI clients can search the docs, read pages, list sections, inspect code examples, and understand config options without scraping HTML.

The current built-in surface includes:

- `list_docs`
- `list_pages`
- `get_navigation`
- `search_docs`
- `read_page`
- `get_code_examples`
- `get_config_schema`

It also exposes resources for:

- `docs://navigation`
- one `docs://…` resource per page

## Default behavior

MCP is enabled by default.

```ts title="docs.config.ts"
import { defineDocs } from "@farming-labs/docs";
import { pixelBorder } from "@farming-labs/theme/pixel-border";

export default defineDocs({
  entry: "docs",
  theme: pixelBorder(),
});
```

That gives you the built-in MCP surface with the default public Streamable HTTP routes:

```txt title="Public MCP routes"
/mcp
/.well-known/mcp
```

<Callout type="info" title="This docs site already exposes it live">
  The hosted docs site has MCP enabled at `https://docs.farming-labs.dev/mcp` and
  `https://docs.farming-labs.dev/.well-known/mcp`.
</Callout>

Opt out explicitly:

```ts title="docs.config.ts"
export default defineDocs({
  entry: "docs",
  mcp: {
    enabled: false,
  },
});
```

## Default HTTP Routes

`/mcp` is the short public MCP endpoint in Next.js, and `/.well-known/mcp` is the discovery-friendly
public MCP endpoint. Both rewrite to the canonical framework route at `/api/docs/mcp`, so the MCP
handler still lives in one place.

<Callout type="info" title="Minimal route behavior">
  **Next.js** auto-generates the default `/api/docs/mcp` route when you use `withDocs()`, and also
  adds public `/mcp` and `/.well-known/mcp` rewrites unless you explicitly disable MCP.

  **TanStack Start**, **SvelteKit**, **Astro**, and **Nuxt** use one public forwarder each so
  `/api/docs/mcp`, `/mcp`, and `/.well-known/mcp` share the same built-in handler without creating
  one route file per alias.
</Callout>

### Next.js

With `withDocs()`, no extra route file is needed for the default path.

```ts title="next.config.ts"
import { withDocs } from "@farming-labs/next/config";

export default withDocs();
```

### TanStack Start

```ts title="src/routes/$.ts"
import { createFileRoute } from "@tanstack/react-router";
import { isDocsMcpRequest, isDocsPublicGetRequest } from "@farming-labs/docs";
import { docsServer } from "@/lib/docs.server";

export const Route = createFileRoute("/$")({
  server: {
    handlers: {
      GET: async ({ request }) => {
        const url = new URL(request.url);
        if (isDocsMcpRequest(url)) return docsServer.MCP.GET({ request });
        if (isDocsPublicGetRequest("docs", url, request)) return docsServer.GET({ request });
        return new Response("Not Found", { status: 404 });
      },
      POST: async ({ request }) =>
        isDocsMcpRequest(new URL(request.url))
          ? docsServer.MCP.POST({ request })
          : new Response("Not Found", { status: 404 }),
      DELETE: async ({ request }) =>
        isDocsMcpRequest(new URL(request.url))
          ? docsServer.MCP.DELETE({ request })
          : new Response("Not Found", { status: 404 }),
    },
  },
});
```

### SvelteKit

```ts title="src/hooks.server.ts"
import { isDocsMcpRequest, isDocsPublicGetRequest } from "@farming-labs/docs";
import { GET, MCP } from "$lib/docs.server";

export async function handle({ event, resolve }) {
  const method = event.request.method.toUpperCase();

  if (isDocsMcpRequest(event.url)) {
    if (method === "POST") return MCP.POST({ request: event.request });
    if (method === "DELETE") return MCP.DELETE({ request: event.request });
    return MCP.GET({ request: event.request });
  }

  if ((method === "GET" || method === "HEAD") && isDocsPublicGetRequest("docs", event.url, event.request)) {
    return GET({ url: event.url, request: event.request });
  }

  return resolve(event);
}
```

Your `src/lib/docs.server.ts` can keep using the normal helper:

```ts title="src/lib/docs.server.ts"
import { createDocsServer } from "@farming-labs/svelte/server";
import config from "./docs.config";

export const { load, GET, POST, MCP } = createDocsServer({
  ...config,
});
```

### Astro

```ts title="src/middleware.ts"
import { isDocsMcpRequest, isDocsPublicGetRequest } from "@farming-labs/docs";
import { GET, MCP } from "./lib/docs.server";

export async function onRequest(context, next) {
  const method = context.request.method.toUpperCase();

  if (isDocsMcpRequest(context.url)) {
    if (method === "POST") return MCP.POST({ request: context.request });
    if (method === "DELETE") return MCP.DELETE({ request: context.request });
    return MCP.GET({ request: context.request });
  }

  if ((method === "GET" || method === "HEAD") && isDocsPublicGetRequest("docs", context.url, context.request)) {
    return GET({ request: context.request });
  }

  return next();
}
```

### Nuxt

```ts title="server/middleware/docs-public.ts"
import { defineDocsPublicHandler } from "@farming-labs/nuxt/server";
import config from "../../docs.config";

export default defineDocsPublicHandler(config, useStorage);
```

## Custom route

If you want a custom MCP route, set it in `docs.config` and add the route file yourself.

```ts title="docs.config.ts"
export default defineDocs({
  entry: "docs",
  mcp: {
    enabled: true,
    route: "/api/internal/docs/mcp",
  },
});
```

<Callout type="warning" title="Custom routes are explicit">
  The package only auto-generates the **default** Next.js route at `/api/docs/mcp`. If you choose a
  custom route, keep that path in `docs.config` and update the framework public forwarder so the app
  and the MCP client point at the same endpoint.
</Callout>

Example custom Next.js route:

```ts title="app/api/internal/docs/mcp/route.ts"
import docsConfig from "@/docs.config";
import { createDocsMCPAPI } from "@farming-labs/next/api";

export const { GET, POST, DELETE } = createDocsMCPAPI(docsConfig);

export const revalidate = false;
```

<Callout type="warning" title="@farming-labs/theme/api compatibility">
  `@farming-labs/theme/api` still works if you already rely on it, but prefer
  `@farming-labs/next/api` for Next.js routes going forward. The theme-level path will be
  deprecated.
</Callout>

When `search` is configured, the MCP `search_docs` tool uses that same adapter pipeline too. That
means your docs UI search and MCP search can share the same provider, chunking strategy, and custom
ranking logic instead of drifting apart.

That also means you can flip docs search itself over to MCP:

```ts title="docs.config.ts"
export default defineDocs({
  entry: "docs",
  search: {
    provider: "mcp",
    endpoint: "/mcp",
  },
  mcp: {
    enabled: true,
  },
});
```

For Ask AI only, keep the top-level search provider as-is and enable MCP retrieval in the AI config.
This uses the MCP server the docs site already exposes:

```ts title="docs.config.ts"
export default defineDocs({
  ai: {
    enabled: true,
    useMcp: true,
  },
});
```

If you changed the canonical MCP route, `useMcp: true` follows it automatically:

```ts title="docs.config.ts"
export default defineDocs({
  mcp: {
    route: "/custom/docs/mcp",
  },
  ai: {
    enabled: true,
    useMcp: true,
  },
});
```

For a hosted or external MCP endpoint, configure the endpoint on `ai.useMcp` instead. This does not
replace your LLM provider config; the MCP endpoint retrieves docs context, then Ask AI still sends
the final prompt to your configured model provider.

```ts title="docs.config.ts"
export default defineDocs({
  ai: {
    enabled: true,
    model: "gpt-4o-mini",
    apiKey: process.env.OPENAI_API_KEY,
    useMcp: {
      endpoint: "https://docs.example.com/mcp",
      headers: {
        Authorization: `Bearer ${process.env.DOCS_MCP_TOKEN}`,
      },
      toolName: "search_docs",
    },
  },
});
```

The custom MCP endpoint must support Streamable HTTP MCP and expose a search tool that returns docs
results. The default tool name is `search_docs`; override `toolName` only if your server uses a
different name. Stateful MCP servers may return an `mcp-session-id` on `initialize`; stateless
servers can omit it, and the docs client will only send the session header when one is provided.

For local self-hosted setups, relative MCP endpoints like `/mcp` and `/.well-known/mcp` are
supported. The canonical `/api/docs/mcp` route remains available too. The built-in `search_docs`
tool automatically falls back to simple search internally when it detects that same-route loop, so
the route stays usable for testing and local examples.

## Stdio transport

The same docs graph is available locally over stdio:

```bash title="terminal"
pnpx @farming-labs/docs mcp
```

Or with pnpm in an installed project:

```bash title="terminal"
pnpm exec docs mcp
```

By default the CLI reads `docs.config.ts[x]` from the project root. If your config lives somewhere else:

```bash title="terminal"
pnpm exec docs mcp --config src/lib/docs.config.ts
```

## Use the hosted MCP endpoint

If you just want to try the live docs server, you can point your MCP client at:

```txt title="Live MCP endpoint"
https://docs.farming-labs.dev/mcp
https://docs.farming-labs.dev/.well-known/mcp
```

<DocsMcpAccess />

### More setup docs

- [Cursor MCP docs](https://docs.cursor.com/advanced/model-context-protocol)
- [VS Code MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers)
- [Claude Code MCP docs](https://docs.anthropic.com/en/docs/claude-code/mcp)

### Manual setup

Cursor project or global config:

```json title=".cursor/mcp.json"
{
  "mcpServers": {
    "farming-labs-docs": {
      "url": "https://docs.farming-labs.dev/mcp"
    }
  }
}
```

VS Code workspace config:

```json title=".vscode/mcp.json"
{
  "servers": {
    "farming-labs-docs": {
      "type": "http",
      "url": "https://docs.farming-labs.dev/mcp"
    }
  }
}
```

Claude Code:

```bash title="terminal"
claude mcp add-json farming-labs-docs '{"type":"http","url":"https://docs.farming-labs.dev/mcp"}'
```

<Callout type="tip" title="Local development uses http, not https">
  If you are connecting to a local Next dev server, use `http://127.0.0.1:3000/mcp` or
  `http://127.0.0.1:3000/.well-known/mcp`.
  Using `https://localhost:3000/...` against a non-TLS dev server will fail with SSL errors.
</Callout>

## Test the Next example

This repo's Next example exposes the default MCP endpoint directly.

Start the example:

```bash title="terminal"
pnpm --dir examples/next dev
```

Then connect your MCP client or inspector to:

```txt title="~"
http://127.0.0.1:3000/mcp
http://127.0.0.1:3000/.well-known/mcp
```

The built-in HTTP route exposes the full MCP surface:

- `list_docs`
- `list_pages`
- `get_navigation`
- `search_docs`
- `read_page`
- `get_code_examples`
- `get_config_schema`

## What to ask it

Once your MCP client is connected, these are good first checks:

- `list_docs` for `getting-started` before deciding which page to read
- `search_docs` for `feedback`, `page actions`, or `mcp`
- `read_page` for `/docs/configuration` or `installation`
- `get_navigation` to inspect the docs tree
- `list_pages` to confirm the server is loading the expected docs set
- `get_code_examples` for runnable Next.js or pnpm snippets
- `get_config_schema` for `mcp.tools` before editing `docs.config.ts`

In Cursor or VS Code, natural prompts like these work well too:

- `Search the Farming Labs docs for page feedback setup`
- `Read the configuration page and summarize MCP config`
- `Find the page actions docs and tell me how openDocs works`

## What the tools return

- `list_docs` returns page summaries grouped by section; pass `section` such as `getting-started` to narrow the result
- `list_pages` returns page titles, slugs, and URLs
- `get_navigation` returns the docs tree in a readable text outline
- `search_docs` ranks pages by title, description, and content matches
- `read_page` accepts either a slug like `installation` or a full docs path like `/docs/installation`
- `get_code_examples` returns fenced code blocks with parsed metadata like `title`, `framework`, `packageManager`, and `runnable`
- `get_config_schema` returns `docs.config.ts` option metadata with paths, types, defaults, descriptions, and examples

Example tool calls:

```json title="list_docs"
{
  "name": "list_docs",
  "arguments": {
    "section": "getting-started"
  }
}
```

```json title="get_code_examples"
{
  "name": "get_code_examples",
  "arguments": {
    "topic": "docs.config.ts",
    "framework": "nextjs"
  }
}
```

```json title="get_config_schema"
{
  "name": "get_config_schema",
  "arguments": {
    "option": "mcp.tools.listDocs"
  }
}
```

The built-in resources mirror the navigation and page content data:

- `docs://navigation`
- `docs://docs`
- `docs://docs/installation`
- `docs://docs/guides/quickstart`

## When to use this vs markdown routes and `llms.txt`

- Use **markdown routes** when you want a plain HTTP URL for one page; in Next.js, `Signature-Agent` can return markdown from the canonical page URL too
- Use **`llms.txt`** when you want a static, crawler-friendly docs summary
- Use **`robots.txt`** when crawlers and AI agents need an explicit policy that allows docs and machine-readable routes
- Use **MCP** when you want a structured, queryable docs interface for agents and IDE tools

They complement each other well. Markdown routes are the simplest page-level HTTP surface,
`llms.txt` is lightweight and public, `robots.txt` makes access policy explicit, and MCP is
interactive and tool-based.