コンテンツへスキップ
Fumadocs

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 に変換します。

lib/source.ts
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 を有効にしてください。

source.config.ts
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ページへのリンクになります。

lib/source.ts
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 を渡します。この関数はフォルダーを受け取り、そのフォルダーをセクションとするかどうかを返します。

lib/source.ts
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つのリンクとして記載され、セクション内のページはそのファイルに一覧表示されます。

lib/source.ts
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() に渡すだけで済みます。

API ルートが /llms.mdx 配下でファイルを配信し、renderUrl で llms.txt 内のリンク先をそちらに向けます。サーバー上では、src/middleware 内の Hono ミドルウェア が /docs/ui.md などのファイルも本来のパスで配信します。型定義を利用するため、hono を開発依存関係としてインストールしてください:

import { docsLlms } from '@/lib/source';

export async function GET() {
  return (await docsLlms.serve('/llms.txt'))!;
}

export async function getConfig() {
  return {
    render: 'static' as const,
  } as const;
}

serve() はファイルが存在しないパスに対して undefined を返すため、ミドルウェアはリクエストを後続の処理に渡せます。hideLocale: 'always' を指定した場合は URL にロケールが含まれないため、言語を serve() の第2引数、および files() の第1引数として渡してください。

静的出力

API ルートはプリレンダリングされるため、静的ファイルとしてデプロイした Waku サイトでも /llms.mdx 配下でファイルが配信されます。ただし、ミドルウェアを使うにはサーバーが必要です。

Accept

AIエージェントは、Accept ヘッダーを使って Markdown を要求できます。

上記のミドルウェアは、リクエストが Markdown を優先する場合に、ドキュメントページの Markdown を返します。

プリレンダリングされたページはミドルウェアの実行前に静的ファイルとして配信されるため、常に HTML が返されます。

同じ URL に2つの表現が存在することになるため、キャッシュの衝突を防ぐには、レスポンスに Vary: Accept を含める必要があります。

ページアクション

ページアクションを使うと、読者はページの Markdown をコピーしたり、AI アプリでページを開いたりできます。ページアクションは、ページの .md ファイルを読み込みます。

AI ページアクション

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 によって提供されます。

src/pages/_api/api/mcp.ts
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 webmcp

LLM ルートをベースにした WebMCP コンポーネントが作成されます。これを <RootProvider /> の内側でレンダリングしてください。

  • search_docs は検索 API ルートにクエリを送信します。fetchClient は他のクライアントに置き換えることもできます。
  • read_page は、/llms.mdx/docs/page.md からページの Markdown を取得します。

AIに質問

AI Search

Fumadocs CLI を使用して、AI チャットダイアログをインストールできます。

npx @fumadocs/cli add ai/openrouter

Vercel 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?

最終更新日