AI & LLMs
Integrate AI functionality to Fumadocs.
Docs for LLM
Serve your docs as Markdown for LLMs and AI agents:
npx @fumadocs/cli feature llmsIt enables processed Markdown on your docs collection, adds docsLlms to lib/source.ts, and creates the routes below. The generated files are listed here if you prefer a manual setup.
docsLlms
llms() renders your docs for LLMs. renderPage turns one page into Markdown, from the processed document rather than the raw file content:
import { llms } from 'fumadocs-core/source';
export const docsLlms = llms(source, {
renderPage: async (page) => `# ${page.data.title} (${page.url})
${await page.data.getText('processed')}`,
});
| Method | Output |
|---|---|
index(lang?) | the llms.txt index, built from the page tree |
page(page) | one page, rendered with renderPage |
full(lang?) | every page rendered with renderPage, joined |
renderPage is required
page() and full() are only available when you pass renderPage, since Fumadocs cannot know
how your content source exposes its Markdown.
renderPage reads the processed Markdown of pages, so enable includeProcessedMarkdown:
import { defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
postprocess: {
includeProcessedMarkdown: true,
},
},
});
MDX components appear as JSX syntax by default. Render them into meaningful Markdown with the output option.
Runtime content sources like @fumadocs/local-md resolve on demand. Pass their getSource and the CLI generates the same routes:
export const docsLlms = llms(getSource, {
renderPage: (page) => `# ${page.data.title} (${page.url})
${page.data.content}`,
});With getSource, index() and indexNode() return a promise instead of a string.
llms.txt
An index of all pages, generated from the page tree.
import { createFileRoute } from '@tanstack/react-router';
import { docsLlms } from '@/lib/source';
export const Route = createFileRoute('/llms.txt')({
server: {
handlers: {
GET: async () => new Response(await docsLlms.index()),
},
},
});
llms-full.txt
The content of all pages in a single file.
import { createFileRoute } from '@tanstack/react-router';
import { docsLlms } from '@/lib/source';
export const Route = createFileRoute('/llms-full.txt')({
server: {
handlers: {
GET: async () => new Response(await docsLlms.full()),
},
},
});
*.md
The Markdown of a single page, for AI agents.
import { createGetUrl } from 'fumadocs-core/source';
export const docsRoute = '/docs';
const getDocsUrl = createGetUrl(docsRoute);
export function getPageMarkdownUrl(page: { slugs: string[]; locale?: string }) {
const segments = [...page.slugs];
if (segments.length === 0) {
segments.push('index.md');
} else {
segments[segments.length - 1] += '.md';
}
return { segments, url: getDocsUrl(segments, page.locale) };
}
/** @returns page slugs */
export function decodeMarkdownUrl(segments: string[]) {
if (segments.length === 0) return [];
const out = [...segments];
out[out.length - 1] = out[out.length - 1].replace(/\.md$/, '');
if (out.length === 1 && out[0] === 'index') out.pop();
return out;
}
Accept
AI agents can ask for Markdown with the Accept header. Serve the Markdown of a page when the request prefers it:
import { createMiddleware, createStart } from '@tanstack/react-start';
import { redirect } from '@tanstack/react-router';
import { isMarkdownPreferred } from 'fumadocs-core/negotiation';
import { getPageMarkdownUrl } from '@/lib/shared';
const llmMiddleware = createMiddleware().server(({ next, request }) => {
const url = new URL(request.url);
if (
(url.pathname === '/docs' || url.pathname.startsWith('/docs/')) &&
!url.pathname.endsWith('.md') &&
isMarkdownPreferred(request)
) {
const slugs = url.pathname
.slice('/docs'.length)
.split('/')
.filter((v) => v.length > 0);
url.pathname = getPageMarkdownUrl({ slugs }).url;
throw redirect({ href: url.href, headers: { Vary: 'Accept' } });
}
return next();
});
export const startInstance = createStart(() => ({
requestMiddleware: [llmMiddleware],
}));The same URL now has two representations, so the response needs Vary: Accept to prevent cache collisions.
Page Actions
Page actions let readers copy the Markdown of a page, or open the page in AI apps. They require the *.md route.

Return the Markdown URL from the loader of your docs page, and render them in the page:
import { MarkdownCopyButton, ViewOptionsPopover } from 'fumadocs-ui/layouts/docs/page';
import { getPageMarkdownUrl } from '@/lib/shared';
// in your loader
return {
path: page.path,
markdownUrl: getPageMarkdownUrl(page).url,
};
// in your page
<div className="flex flex-row gap-2 items-center border-b pt-2 pb-6">
<MarkdownCopyButton markdownUrl={markdownUrl} />
<ViewOptionsPopover
markdownUrl={markdownUrl}
githubUrl={`https://github.com/${owner}/${repo}/blob/main/content/docs/${encodeURI(path)}`}
/>
</div>;MCP Server
Expose your docs to AI agents through a MCP server, with tools to list, search and read pages.
npx @fumadocs/cli feature mcpIt creates a /api/mcp route with the streamable HTTP transport, on top of the LLM routes above.
The tools come from fumadocs-core/mcp.
import { createFileRoute } from '@tanstack/react-router';
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import { registerSearchTool, registerSourceTools } from 'fumadocs-core/mcp';
import { createFromSource } from 'fumadocs-core/search/server';
import { docsLlms, source } from '@/lib/source';
const handler = createMcpHandler(() => {
const mcp = new McpServer(
{
name: 'docs',
version: '1.0.0',
},
// the tools never change, a stream to watch them would hold a serverless function open
{ capabilities: { tools: { listChanged: false } } },
);
registerSourceTools(mcp, source, docsLlms);
registerSearchTool(mcp, createFromSource(source));
return mcp;
});
export const Route = createFileRoute('/api/mcp')({
server: {
handlers: {
GET: async ({ request }) => handler.fetch(request),
POST: async ({ request }) => handler.fetch(request),
DELETE: async ({ request }) => handler.fetch(request),
},
},
});
Connect an agent to it with:
{
"mcpServers": {
"docs": { "url": "https://your-site.com/api/mcp" }
}
}WebMCP
Experimental
WebMCP is an early web standard, available in
Chrome 149+ behind the #enable-webmcp-testing flag and an origin trial. The API may still
change.
Where the MCP server above serves agents connecting to your site, WebMCP exposes tools to the AI agent of the browser, on the page the reader is viewing.
npx @fumadocs/cli feature webmcpIt creates a WebMCP component on top of the LLM routes. Render it inside <RootProvider />.
search_docsqueries the search API route. You can replacefetchClientwith other clients.read_pagefetches the Markdown of a page, from/docs/page.md.
Ask AI

You can install the AI chat dialog using Fumadocs CLI.
npx @fumadocs/cli add ai/openrouterIt's automatically configured for OpenRouter using Vercel AI SDK, with a /search tool for AI.
You can use other models by updating the /api/chat route.
Fumadocs doesn't provide the AI model, it's up to you.
Your AI model can use the llms-full.txt file generated above, or more diversified sources of information when combined with 3rd party solutions.
Docs layouts render the chat from their aiChat option. The option takes client state, so create a client component for your layout and use it in place of the original layout:
'use client';
// or the Notebook, Glass and Spacious layouts
import { DocsLayout as Layout, type DocsLayoutProps } from 'fumadocs-ui/layouts/docs';
// the installed components
import { AIChat, AIChatPanel, useAIChat } from './search';
export function DocsLayout(props: DocsLayoutProps) {
return (
<AIChat>
<ChatLayout {...props} />
</AIChat>
);
}
function ChatLayout(props: DocsLayoutProps) {
const { open, setOpen } = useAIChat();
return (
<Layout
{...props}
aiChat={{ open, onOpenChange: setOpen, panel: <AIChatPanel />, trigger: true }}
/>
);
}How is this guide?
Last updated on
