AI と LLM
Fumadocs に AI 機能を組み込みます。
LLM向けドキュメント
LLMやAIエージェント向けに、ドキュメントをMarkdown形式で提供します。
npx @fumadocs/cli feature llmsこれにより、docs コレクションで処理済みの Markdown が有効になり、lib/source.ts に docsLlms が追加されて、以下のファイルが配信されます。手動でセットアップする場合に備えて、生成されるファイルを以下に記載しています。
docsLlms
llms() は、ドキュメントを LLM 向けにレンダリングします。renderPage は、生のファイル内容ではなく処理済みのドキュメントをもとに、1 つのページを Markdown に変換します。
import { llms } from 'fumadocs-core/source';
export const docsLlms = llms(source, {
renderPage: async (page) => `# ${page.data.title} (${page.url})
${await page.data.getText('processed')}`,
});
| メソッド | 出力 |
|---|---|
index(lang?) | ページツリーから構築される llms.txt インデックス |
page(page) | renderPage でレンダリングした 1 つのページ |
full(lang?) | renderPage でレンダリングした全ページを結合したもの |
files() | エージェント向けファイルのパス |
serve(path) | 指定したパスにあるファイルのレスポンス |
renderPage は必須です
コンテンツソースが Markdown をどのように公開しているかを Fumadocs 側では判断できないため、page()、full()、files()、serve() は
renderPage を渡した場合にのみ利用できます。
renderPage はページの処理済み Markdown を読み取るため、includeProcessedMarkdown を有効にしてください。
import { defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
postprocess: {
includeProcessedMarkdown: true,
},
},
});
MDX コンポーネントは JSX 構文のまま出力されます。これらを Markdown 形式にレンダリングするには、MDX コンポーネントをページの props として渡してください。
@fumadocs/local-md のようなランタイムのコンテンツソースは、リクエスト時に解決されます。その getSource を渡すと、CLI が同じファイルを生成します。
export const docsLlms = llms(getSource, {
renderPage: (page) => `# ${page.data.title} (${page.url})
${page.data.content}`,
});getSource を使用する場合、index() と indexNode() は文字列ではなく Promise を返します。
ファイル
CLIは、AIエージェント向けに次のファイルを配信します。
/llms.txt: ドキュメントのインデックスです。各ページのMarkdownへのリンクが含まれます。/llms-full.txt: 全ページのコンテンツです。<page>.md: 各ページのMarkdownです (例:/docs/ui.md) 。/のページは/index.mdになります。<section>/llms-full.txt: セクションのコンテンツです (例:/docs/ui/llms-full.txt) 。
下書きなどのページをこれらのファイルから除外するには、filter を指定します。除外したページも llms.txt には引き続き掲載され、HTMLページへのリンクになります。
export const docsLlms = llms(source, {
filter: (page) => page.slugs[0] !== 'drafts',
renderPage: async (page) => `# ${page.data.title}\n\n${await page.data.getText('processed')}`,
});これらのファイルでは、renderUrl はリンク先のファイルのパス (ページの .md ファイルなど) を受け取ります。
ページの HTML を読み取るエージェントは、<head> 内のリンクからそのページの Markdown を見つけます:
<link rel="alternate" type="text/markdown" href="/docs/ui.md" />セクション
セクションとは、独自の llms-full.txt を持つページツリー内のフォルダーです。これにより、エージェントは大規模なドキュメントを部分ごとに読み込めます。デフォルトでは、すべてのルートフォルダーがセクションになります。llms.txt では、各フォルダーの行がそのセクションの llms-full.txt へのリンクになります。
セクションとするフォルダーを指定するには、section を渡します。この関数はフォルダーを受け取り、そのフォルダーをセクションとするかどうかを返します。
export const docsLlms = llms(source, {
// index ページを持つすべてのフォルダー
section: (folder) => folder.index !== undefined,
renderPage: async (page) => `# ${page.data.title}\n\n${await page.data.getText('processed')}`,
});大規模なサイトでは、セクションを折りたたむと llms.txt を短く保てます。折りたたんだセクションは、llms.txt ではそのセクション専用の <section>/llms.txt への1つのリンクとして記載され、セクション内のページはそのファイルに一覧表示されます。
export const docsLlms = llms(source, {
section: (folder) => (folder.root ? { collapse: true } : false),
renderPage: async (page) => `# ${page.data.title}\n\n${await page.data.getText('processed')}`,
});セクションのファイルは、そのフォルダーのインデックスページの URL 配下で配信されます。インデックスページがないフォルダーでは、代わりに配下のページの URL ディレクトリが使われます。フォルダーグループのように 2 つのフォルダーが同じ URL を共有する場合は、asPath を渡すと、一方を別のパスで配信できます (例:{ asPath: '/docs/guides' }) 。
ルート
files() はファイルのパスを列挙し、serve(path) は指定したパスのファイルをレスポンスとして返します。そのため、各ルートはリクエストパスを serve() に渡すだけで済みます。
/llms.txt と /llms-full.txt はルートハンドラーが配信します。その他のファイルは app/docs/[[...slug]] と同じ階層に置かれるため、Next.js の設定でそれらのパスを /llms.mdx/[...path] ルートへリライトします:
import { docsLlms } from '@/lib/source';
export const revalidate = false;
export async function GET() {
return (await docsLlms.serve('/llms.txt'))!;
}
serve() はファイルが存在しないパスに対して undefined を返すため、ミドルウェアはリクエストを後続の処理に渡せます。hideLocale: 'always' を指定した場合は URL にロケールが含まれないため、言語を serve() の第2引数、および files() の第1引数として渡してください。
静的出力
output: 'export' を使用する場合、Next.js では rewrites を利用できません。/llms.mdx/[...path] ルートは /llms.mdx/docs/ui.md のようなプレフィックス付きのパスでファイルを配信し、renderUrl によって llms.txt 内のリンクがそのパスを指すようにします。
import { llms } from 'fumadocs-core/source';
export const docsLlms = llms(source, {
renderPage: async (page) => `# ${page.data.title} (${page.url})
${await page.data.getText('processed')}`,
// link to the files under `/llms.mdx`
renderUrl: (item) => (/^\/.*\.(md|txt)$/.test(item.url) ? `/llms.mdx${item.url}` : item.url),
});
Accept
AIエージェントは、Accept ヘッダーを使って Markdown を要求できます。
リクエストが Markdown を優先する場合は、ページの Markdown を返すようにします。
import { NextRequest, NextResponse } from 'next/server';
import { isMarkdownPreferred, rewritePath } from 'fumadocs-core/negotiation';
const { rewrite: rewriteDocs } = rewritePath('/docs{/*path}', '/llms.mdx/docs{/*path}.md');
export default function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// `/docs/ui.md` のようなファイルは Next.js の設定の rewrites で配信されます
const result =
isMarkdownPreferred(request) && !/\.(md|txt)$/.test(pathname) && rewriteDocs(pathname);
if (result) {
return NextResponse.rewrite(new URL(result, request.nextUrl), {
headers: { Vary: 'Accept' },
});
}
return NextResponse.next();
}同じ URL に2つの表現が存在することになるため、キャッシュの衝突を防ぐには、レスポンスに Vary: Accept を含める必要があります。
Next.js は App Router のページレスポンスから Vary を削除するため、ドキュメントページの HTML レスポンスにはこのヘッダーを
付与できません。共有キャッシュからドキュメントを配信している場合は、CDN 側で設定してください。
ページアクション
ページアクションを使うと、読者はページの Markdown をコピーしたり、AI アプリでページを開いたりできます。ページアクションは、ページの .md ファイルを読み込みます。

docs ページ内でレンダリングしてください。
import { MarkdownCopyButton, ViewOptionsPopover } from 'fumadocs-ui/layouts/docs/page';
const markdownUrl = `${page.url}.md`;
<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(page.path)}`}
/>
</div>;静的エクスポートを使用する場合は、URL が `/llms.mdx${page.url}.md` になります。
MCP サーバー
MCP サーバーを通じてドキュメントをAIエージェントに公開できます。ページの一覧表示・検索・読み取りを行うツールも提供されます。
npx @fumadocs/cli feature mcp上記の LLM ルート に加えて、Streamable HTTP トランスポートを使用する /api/mcp ルートを作成します。
ツールは fumadocs-core/mcp によって提供されます。
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 async function GET(request: Request) {
return handler.fetch(request);
}
export async function POST(request: Request) {
return handler.fetch(request);
}
export async function DELETE(request: Request) {
return handler.fetch(request);
}
エージェントを接続するには、次のようにします。
{
"mcpServers": {
"docs": { "url": "https://your-site.com/api/mcp" }
}
}WebMCP
実験的機能
WebMCP は策定初期段階の Web 標準で、Chrome 149 以降で
#enable-webmcp-testing フラグを有効にするか、オリジントライアルを通じて利用できます。API は今後
変更される可能性があります。
前述の MCP サーバーがサイトに接続してくるエージェント向けであるのに対し、WebMCP は読者が閲覧しているページ上で、ブラウザに組み込まれた AIエージェント にツールを公開します。
npx @fumadocs/cli feature webmcpLLM ルートをベースにした WebMCP コンポーネントが作成されます。これを <RootProvider /> の内側でレンダリングしてください。
search_docsは検索 API ルートにクエリを送信します。fetchClientは他のクライアントに置き換えることもできます。read_pageは、/docs/page.mdからページの Markdown を取得します。
AIに質問

Fumadocs CLI を使用して、AI チャットダイアログをインストールできます。
npx @fumadocs/cli add ai/openrouterVercel AI SDK を使用して OpenRouter 向けに自動で設定され、AI 用の /search ツールも含まれます。
/api/chat ルートを更新すれば、他のモデルも使用できます。
Fumadocs は AI モデルを提供していません。モデルはご自身で用意してください。
AI モデルには、上記で生成した llms-full.txt ファイルを利用できます。また、サードパーティのソリューションと組み合わせれば、より多様な情報ソースを活用することもできます。
Docs レイアウトは、aiChat オプションをもとにチャットをレンダリングします。このオプションはクライアントの状態を受け取るため、レイアウト用のクライアントコンポーネントを作成し、元のレイアウトの代わりに使用してください。
'use client';
// Notebook、Glass、Spacious レイアウトも使用できます
import { DocsLayout as Layout, type DocsLayoutProps } from 'fumadocs-ui/layouts/docs';
// インストールしたコンポーネント
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?
最終更新日
