AI
llms.txt と OpenAPI で記述された JSON API でドキュメントを機械可読にし、ページ内の Ask AI アシスタント(任意)を追加し、コーディングエージェント向けにホスト型 MCP サーバーを公開します。
Blume にはいくつかの AI 機能があります。外部ツール向けの機械可読ドキュメント(llms.txt と、OpenAPI の記述を伴う JSON API。どちらも既定で有効)、ページ内の Ask AI アシスタント、そしてコーディングエージェント向けのホスト型 MCP サーバー です。Ask AI と MCP はオプトインで、機能を有効にするまで静的ドキュメントは完全に静的なままです。
llms.txt
Blume は、コーディングエージェントやチャットアシスタントが利用できる機械可読版のドキュメントを出力します。これは既定で有効です。無効にするには llmsTxt: false を設定します。
ai: {
llmsTxt: false,
}
有効な間、blume build はサイトのルートに 2 つのファイルを書き出します。
/llms.txt— コンパクトな索引です。サイトのタイトルと説明に続いて、各ページの要約付きリンク一覧を、サイドバーを反映したセクションに整理して並べます。フォルダーやグループが見出しになるため、エージェントは 1 枚のフラットな塊ではなくドキュメントの構造を把握できます。/llms-full.txt— コーパス全体です。各ページの完全な Markdown 本文を、ソース URL とともに 1 つのファイルにまとめます。
下書きページは除外されます。リンクとソース URL が絶対アドレスに解決されるように deployment.site を設定してください。
llmsTxt は、ファイルに含める内容を調整するつまみを備えたオブジェクト形式も受け付けます。API リファレンスがプレースホルダーやサンプルの仕様をドキュメント化している場合は、openapi: false を設定して、生成されたページを両方のファイルから除外できます。
ai: {
llmsTxt: {
enabled: true, // default
openapi: false, // exclude generated API reference pages
},
}
オブジェクト形式は details も受け付けます。これは llms.txt のタイトルと要約の直後、ページセクションの前に配置される Markdown で、llms.txt 仕様における自由形式の「details」ブロックです。読みやすさをスキャンするツールが明示的に探す箇所でもあり、どんなときにあなたの製品を使うべきか、どう呼び出すのかをエージェントに伝える場所です。インストールコマンドやパッケージ名もここに書きます。
ai: {
llmsTxt: {
details: [
"## When to use Acme",
"",
"Reach for Acme when a project needs hosted feature flags. Install the CLI with `npm install -g acme`; the API reference below covers every endpoint.",
].join("\n"),
},
}
llms.txt は、設定不要で生成される 2 つのセクションで締めくくられます。Agent skills は ai.skills を通じて公開された各スキルを、その説明(スキルがいつ使うべきかを述べている箇所)とともに列挙します。Agent resources は、ビルドが出力するあらゆる機械可読の成果物へのリンクを並べます。llms-full.txt、ページごとの生の Markdown ミラー、MCP サーバーとそのディスカバリー文書、スキル索引、API カタログ、agent-readability.json、サイトマップで、いずれも存在する場合にのみ含まれます。そのため llms.txt しか読まないエージェントでも、この面のすべてを見つけられます。
個々のページを両方のファイルから除外するには、そのページのフロントマターで ai.exclude を設定します。
---
title: Internal notes
ai:
exclude: true
---
そのページは引き続きレンダリングされ、検索にも残り、サイトマップ上の位置も保たれます。スキップされるのは llms.txt ファイルだけです。
どちらかのファイルを完全に自分で制御したい場合は、独自の llms.txt または llms-full.txt を public/ フォルダーに追加してください。カスタムファビコンと同様に自動的に検出され、生成されたファイルの代わりに配信されます。一方を上書きしても、もう一方は Blume が生成し続けます。
生の Markdown
任意のページの URL に .md または .mdx を付け加えると、その生の Markdown ソースを取得できます。LLM、コーディングエージェント、「Markdown としてコピー」ワークフローに最適です。設定不要で、開発環境でも本番環境でも、すべてのページで利用できます。
| URL | 返される内容 |
|---|---|
/quickstart |
レンダリングされたページ |
/quickstart.md |
コンポーネントを変換したプレーンな Markdown |
/quickstart.mdx |
記述されたままの生の MDX ソース |
ネストされたルートも同様に動作し(/content/syntax.md)、ホームページは /index.md で配信されます。
.md 版は、JSX を解釈できない利用者のためにコンポーネントをプレーンな Markdown へダウンレベルします。<TypeTable> は Markdown のテーブルに、<Callout> はラベル付き引用に、<Steps> は順序付きリストに、<Tabs> は太字ラベルのセクションに、<Card> は本文の上にタイトルをリンクとして置いたものに(<CardGroup> はそれが保持するカード群に)、<YouTube> はリンクになります。props はページの frontmatter をスコープに入れて評価されるため、title={frontmatter.status} のような prop はレンダリングされたページと同じ値に解決されます。忠実に変換できないもの(カスタムコンポーネントや、import から算出される prop)はそのまま残され、フェンス付きコードブロック内のコンポーネントのマークアップには一切手を加えません。同じ変換は llms-full.txt と MCP サーバーの get_page ツールにも適用されるため、エージェントに向けたあらゆる面がきれいな Markdown として読めます。変換前のソースが必要なときは .mdx 版を使ってください。
コンテントネゴシエーション
エージェントが .md の規約を知っている必要はありません。ページ自身の URL を Accept: text/markdown ヘッダー付きでリクエストすると、同じアドレスで Markdown 版が配信され、キャッシュが両者を区別できるように Vary: Accept が付きます。開発サーバーは標準でこのヘッダーを尊重し、Vercel または Cloudflare のサーバービルドでは同じネゴシエーションがデプロイに自動的に組み込まれます(Vercel ではルーティングルール、Cloudflare では生成された Worker 経由)。設定は不要です。ホームページは、コンテンツページではなくカスタムのランディングページであっても常にネゴシエーションを行います。その Markdown ミラーは llms.txt の索引にフォールバックするため、サイトルートに Markdown を求めたエージェントはサイトの機械可読なマップを得られます。Markdown のレスポンスには x-markdown-tokens ヘッダー(トークン数の推定値。1 トークンあたり約 4 文字)も付きます。これは Cloudflare の Markdown for Agents の慣習に従ったもので、Blume がレスポンスヘッダーを制御できるすべての面(開発サーバー、サーバーレンダリングされたレスポンス、Vercel および Cloudflare 上でネゴシエーションされるホームページ)に付与されます。それ以外のデプロイ先はリクエスト時のフックを持たない静的レイヤーからプリレンダリング済みページを配信するため、そこではエージェントが .md の URL を直接取得します。エージェント可読性マニフェストは、ヘッダーを尊重するデプロイでのみ contentNegotiation を告知します。
存在しないページもネゴシエーションを行います。すべてのビルドは /404.md に Markdown 版の 404 ページを出力し(not-found のメッセージに続いて、すべてのトップレベルセクション、サイトマップ、llms.txt への復帰リンクが並びます)、Vercel では、Markdown を優先する存在しない URL へのリクエスト、または背後にページのない .md URL に対して、HTML のシェルではなく本物の 404 ステータス付きでその本文が返されます。
カスタムコンポーネントのシリアライザー
ai.markdownComponents(JSX 名からシリアライザーへのマップ)を使うと、独自のコンポーネントに Markdown 表現を与えられます。各シリアライザーは、コンポーネントの props(MDX の属性から静的に評価され、ページの frontmatter がスコープに入ります)、children(すでに Markdown へダウンレベル済み)、およびページの frontmatter データを受け取り、置き換える内容を返します。JSX をそのまま残すには null を返します。
import { defineConfig } from "blume";
import type { ComponentMarkdown } from "blume";
const chart: ComponentMarkdown = ({ props }) =>
``;
export default defineConfig({
ai: {
markdownComponents: {
Chart: chart,
},
},
});
コンテナー系のコンポーネントでは、childComponents("Name") がタグごとに直下の子要素を抽出します。組み込みの <Steps> シリアライザーが <Step> 項目を集めるのと同じ仕組みです。また childBlocks() は、コンポーネントか散文かを問わず直下のすべての子要素を順番に返し、それぞれはすでに Markdown のブロックへダウンレベル済みです(組み込みの <CardGroup> シリアライザーは、そのブロック群を空行で連結しているだけです)。同名のエントリーは組み込みシリアライザーを置き換えるため、<Callout> のダウンレベルの見え方を作り替えたり、null を返して完全にオプトアウトしたりできます。
シリアライザーは components.tsx ではなく blume.config.ts に置きます。設定ファイルはビルド時に実行されますが、コンポーネントファイルは静的に解析されるだけだからです(.astro ファイルを import する可能性があり、それはサイトのビルド外では実行できません)。コンポーネント自体はこれまでどおり components.tsx に登録したままです。markdownComponents は、そのエージェント向けの Markdown 表現を追加するだけです。
Copy as Markdown
すべてのページには、目次の下のページアクションに Copy as Markdown アクションがあり、そのページの生の Markdown をクリップボードにコピーします。これは上記の .md URL で配信されるものと同じソースで、LLM や Issue、メモにそのまま貼り付けられます。設定不要で、開発環境でも本番環境でも、すべてのページで利用できます。
Clipboard API が利用できない場合やブラウザーがそれを拒否した場合(アプリ内ブラウザー、WebView、安全でないオリジンなど)、このアクションは従来のコピーコマンドにフォールバックし、それでもクリップボードに何も入らなければ、ボタンは黙ったままにせず Copy failed(actions.copyFailed でローカライズされます)と表示します。同じフォールバックが、Blume がレンダリングするすべてのコピーボタンを支えています。
Open in chat
Open in chat アクションは、現在のページを AI アシスタント(v0、ChatGPT、Claude、T3 Chat、Scira、Cursor)で開き、そのページの生の Markdown を指すプロンプトをあらかじめ入力しておくため、読んでいる内容について質問できます。
Read
https://your-site/this-page.mdso I can ask you questions about this page.
Copy as Markdown と同様、セットアップは不要です。アシスタントは公開 URL 経由でページを取得するため、ページがデプロイされればすぐに機能します。
このプロンプトは UI 辞書の一部(actions.openInChatPrompt)なので、ローカライズされたサイトはその言語で送信し、i18n.ui で文言を上書きできます。{url} プレースホルダーはそのまま残してください。ページの生の Markdown の URL に置き換えられます。
このアクションを調整するには ai.openInChat を設定します。false にすると完全に非表示になり、プロバイダーキーの配列("v0"、"chatgpt"、"claude"、"t3"、"scira"、"cursor")を指定すると、列挙した順序でそれらのプロバイダーだけが表示されます。
ai: {
openInChat: ["claude", "chatgpt", "cursor"],
}
ページ全体のアクションではなく、コンテンツ内にコピーしてすぐ使えるプロンプトを埋め込みたい場合は、Prompt コンポーネントを使ってください。Copy prompt ボタンと、任意で Cursor で開くリンクを備えたラベル付きの行をレンダリングします。
Ask AI
読者の質問にページ内のチャットパネルで答えるアシスタントを追加します。ストリーミング対応のサーバーエンドポイントと AI SDK がバックエンドです。
ai: {
ask: {
enabled: true,
provider: "gateway", // default
model: "openai/gpt-5.5",
},
}
質問候補
空の状態にいくつかのスターター用プロンプトを用意できます。それぞれクリック可能な候補として表示され(クリックすると送信されます)、ラベルの横に任意で Lucide アイコンを添えられます。
ai: {
ask: {
enabled: true,
suggestions: [
{ label: "What is Blume?", icon: "rocket" },
{ label: "How do I write a docs page?", icon: "file-text" },
{ label: "How do I configure the theme?", icon: "settings" },
],
},
}
label は実際に送信される質問で、icon は任意です。suggestions を未設定(または空)のままにすると、パネルはシンプルな入力欄で開きます。
カスタム指示
instructions で独自のシステムプロンプト文を追加できます。アシスタントに意識してほしいアイデンティティ、言語、トーン、その他何でも指定できます。
ai: {
ask: {
enabled: true,
instructions:
"You are Bloomy, the Acme docs assistant. Answer in the language the question was asked in, and keep answers under three paragraphs.",
},
}
指定した文は組み込みの指示を置き換えるのではなく、その後ろに追加されます。組み込み部分はグラウンディングの取り決め(取得したページのみから回答する、Markdown リンクとして引用する)を担っており、チャットパネルの引用表示はこれに依存しているため、何を追加しても損なわれずに残ります。
グラウンディング
Ask AI はドキュメントにグラウンディングされています。質問ごとに最も関連性の高いページを取得し(ページ内検索を支えるのと同じ字句ベースの Orama インデックスを使用)、モデルのシステムプロンプトに注入するため、回答はモデル自身の知識ではなくあなたのコンテンツから生成されます。アシスタントは、取得したページのみから回答すること、カバーされていない事柄はそう伝えること、参照したページを引用することを指示されています。
読者が現在開いているページが最初にコンテキストへ追加され、そのページの言語に検索範囲を絞るために使われるため、回答はドキュメント内の現在地に沿った内容になります。取得処理はビルドに埋め込まれたスナップショットからリクエスト時に実行されるため、検索プロバイダーに関係なく(検索が none に設定されていても)動作し、設定は不要です。
グラウンディングは Inkeep 以外のすべてのバックエンドで有効です。Inkeep はダッシュボードでインデックスしたコンテンツに対して独自の取得処理を行います。
取得サイズ
1 つの質問がどれだけのドキュメントを運ぶかは、読者が最初の 1 語を待つ時間を左右する最大の要因です。モデルは注入された文字をすべて読んでからトークンを出力し始めます。ホスト型のフロンティアモデルではこれは目に見えませんが、セルフホストのバックエンドでは支配的になります。retrieval でそのサイズを調整します。
ai: {
ask: {
enabled: true,
retrieval: {
maxResults: 3, // fewer pages retrieved per question
excerptChars: 1200, // shorter excerpt from each one
contextBudget: 3000, // smaller total injection
},
},
}
| オプション | 既定値 | 説明 |
|---|---|---|
maxResults |
6 |
質問ごとに取得するドキュメント数。 |
excerptChars |
2000 |
取得した各ページから残す文字数。 |
contextBudget |
10000 |
すべての抜粋を合わせた、注入される総文字数。 |
この 3 つは互いに置き換えられるものではありません。contextBudget は注入全体に上限を設け、excerptChars は 1 つの長いページのどこまで深く抜粋するかを決め(1 ページに答えのすべてが収まっているのに抜粋が途中で切れてしまう場合に上げてください)、maxResults は取得処理が追加するページ数に上限を設けます。読者が閲覧しているページは取得したページに加えて注入されるため、回答は maxResults より 1 ページ多く引用することがあります。
既定値はホスト型モデルに適しています。自前のハードウェアから配信していて、再現率よりも最初のトークンまでの時間が重要な場合は、これらを下げてください。いずれの場合も回答はグラウンディングされたままで、アシスタントはカバーされていない事柄について、隙間を埋めるのではなくそう伝えるよう指示されています。
外部エンドポイント
すでに AI 用の API バックエンドをお持ちですか。パネルをそこに向ければ、ドキュメントのビルドは静的なままにできます。
ai: {
ask: {
enabled: true,
endpoint: "https://api.example.com/v1/docs/ask",
},
}
Blume は組み込みのルートと同じ POST ボディを送信します。
{
"messages": [{ "role": "user", "content": "How do I deploy?" }],
"page": { "path": "/deployment" }
}
ボディがプレーンな UTF-8 テキストストリームである成功レスポンスを返してください。エンドポイントが別オリジンにある場合は、CORS でドキュメントのオリジンを許可します。OPTIONS と POST を受け付け、content-type リクエストヘッダーを許可し、プリフライトとストリーミングレスポンスの両方で CORS ヘッダーを返してください。endpoint を設定すると、Blume はチャット UI を生成しますが、サーバールート、グラウンディング用スナップショット、プロバイダーの依存関係、プロバイダーのシークレットに関する警告は生成しません。取得処理、認証、レート制限、モデルへのアクセス、引用はあなたのバックエンドが担います。
サーバー出力が必要
Blume 組み込みの Ask AI バックエンドはサーバールート(POST /api/ask)なので、静的ビルドでは動作しません。サーバー出力に切り替えてアダプターを選択してください。
deployment: {
output: "server",
adapter: "vercel",
}
Ask AI が有効で外部 endpoint がない静的ビルドは、deployment.output を server に設定するよう促すメッセージとともに即座に失敗します。アダプターについてはデプロイを参照してください。
バックエンド
既定では、Ask AI は Vercel AI Gateway を経由します。model は provider/model 形式の文字列なので、それを変更するだけでモデルを切り替えられ(openai/gpt-5.5、anthropic/claude-sonnet-4-5 など)、プロバイダーの SDK をインストールする必要はありません。ゲートウェイは環境から AI_GATEWAY_API_KEY を読み取り、Vercel にデプロイすると自動的に設定されます。
Ask AI を別の場所に向けるには provider を設定します。各バックエンドは環境変数から API キーを読み取り、プロジェクトにインストールしたプロバイダーの SDK を通じてストリーミングします。インストールするのは実際に使うものだけです。
provider |
model |
API キーの環境変数 | インストールする SDK |
|---|---|---|---|
gateway(既定) |
AI Gateway 経由の provider/model 形式の文字列 |
AI_GATEWAY_API_KEY |
不要 — Blume に同梱 |
openrouter |
任意の OpenRouter モデル | OPENROUTER_API_KEY |
@openrouter/ai-sdk-provider |
llmgateway |
任意の LLMGateway モデル | LLMGATEWAY_API_KEY |
@ai-sdk/openai-compatible |
inkeep |
Inkeep の QA モデル | INKEEP_API_KEY |
@ai-sdk/openai-compatible |
openai-compatible |
エンドポイントが提供するもの | apiKeyEnv で設定 |
@ai-sdk/openai-compatible |
これらの SDK は任意のピア依存関係なので、バックエンドに必要なものをプロジェクトに追加してください(例: npm install @openrouter/ai-sdk-provider)。不足している場合は、Vite が import の解決に失敗する前に、ビルドが正確なパッケージ名を添えて警告します。
たとえば OpenRouter を使うには次のようにします。
ai: {
ask: {
enabled: true,
provider: "openrouter",
model: "anthropic/claude-sonnet-4-5",
},
}
OpenAI 互換のエンドポイントはすべて openai-compatible で利用できます。baseUrl と、キーを保持する環境変数を指定してください。
ai: {
ask: {
enabled: true,
provider: "openai-compatible",
baseUrl: "https://my-gateway.example.com/v1",
apiKeyEnv: "MY_GATEWAY_API_KEY",
model: "gpt-4o",
},
}
どのバックエンドでも apiKeyEnv(および、名前付きプロバイダーでは baseUrl)を設定すれば、別の環境変数やプロキシを指せます。
キーは process.env で読み取られ、Node、Vercel、Netlify の各アダプターがこれに該当します。Cloudflare では、プラットフォームのランタイムバインディング経由でキーを公開してください。Ask AI を有効にすると、ページ内アイランドのために React も有効になります。カスタマイズを参照してください。
レート制限
POST /api/ask エンドポイントは認証されていません。ページ内アシスタントが呼び出せる必要があるため、そうならざるを得ません。Blume は各リクエストを検証し(不正なボディを拒否し、メッセージ数を 1〜40 に制限し、user/assistant のロールのみを受け付けることで、呼び出し元が独自のシステムプロンプトを注入してこのルートを汎用の LLM プロキシに転用できないようにします)、1 回の呼び出しがモデルに対して消費できる量を抑えますが、誰かが繰り返しエンドポイントを呼び出すことは防げません。コストの悪用が懸念される場合は、ホスト(たとえば Vercel)のエッジのレート制限、ミドルウェア、あるいはモデルプロバイダーのキー単位の支出上限などで、このルートをレートリミッターの背後に置いてください。
MCP サーバー
Model Context Protocol サーバーをホストすれば、コーディングエージェント(Claude Code、Cursor、VS Code、claude.ai のコネクター)がスクレイピングなしで直接ドキュメントを検索・閲覧できます。
ai: {
mcp: {
enabled: true,
route: "/mcp", // where the server is mounted
},
}
| オプション | 既定値 | 説明 |
|---|---|---|
enabled |
false |
MCP サーバーを生成してホストします。 |
route |
/mcp |
Streamable-HTTP エンドポイントをマウントするパスです。 |
name |
title | クライアントに表示されるサーバー名(既定はタイトル)。 |
instructions |
— | 接続するエージェントに渡す任意のシステムヒント。 |
サーバーは読み取り専用のツール(search_docs、get_page、list_pages、get_navigation)と、すべてのページを MCP リソースとして公開します(resources/list は配信 URL でページを text/markdown タイプとともに列挙し、resources/read はそのページのエージェント向け Markdown、つまり get_page と同じ出力を返します)。そのため、URI でコンテキストを添付するクライアントは、ツールを呼び出さずにドキュメントを閲覧できます。さらに /.well-known/mcp.json と /.well-known/mcp/server-card.json にディスカバリー文書を公開します。サーバーカードは SEP-2127 の Server Card 拡張スキーマ(逆引き DNS 形式の name、remotes トランスポートエンドポイント)に従い、提案の以前の版に合わせて作られたスキャナー向けに initialize 形式の互換フィールド(serverInfo、capabilities、transports)も備えています。各ページの Connect to MCP メニューでは、Claude Code、Cursor、VS Code、Codex 向けにコピーしてすぐ使えるインストール手順を提供します(deployment.site を設定すると表示されます)。
search_docs は独自の全文インデックスで動作するため、検索プロバイダーに関係なく(検索が none に設定されていても)機能します。MCP サーバーはページ内検索とは別の機能です。
search_docs と list_pages はどちらも任意の contentTypes フィルターを受け付け、指定したフロントマターの type を持つページに結果を絞り込めます(["rfc"]、["blog", "changelog"] など)。そのため、ドキュメントに RFC、ランブック、ポリシーが混在するサイトを相手に作業するエージェントは、必要な種類のページに取得範囲を絞れます。すべての結果にはそのコンテントタイプが明記され、list_pages の出力には使用中のタイプが表示されます。
両ツールは、サイトがコンテントタイプごとに宣言するファセット(content.types.<type>.facets)に対して照合する filters オブジェクトも受け付けます。ファセットとは、値がフィルター可能なメタデータになるカスタムのフロントマターキーです。
{
"query": "OpenAPI request schemas",
"contentTypes": ["rfc"],
"filters": { "domain": "architecture", "status": "enforced" }
}
filters の各エントリーはすべて一致する必要があります(結果にはファセットの値が含まれ、list_pages は各ページの値を表示します)。これにより、ナレッジベースは独自のサーバーを一切持たずに、段階的開示のエージェントワークフロー(強制されている標準を列挙し、その中だけを検索する、など)を駆動できます。
サーバー出力が必要
MCP サーバーはライブのエンドポイント(/mcp)なので、静的ビルドでは動作しません。サーバー出力に切り替えてアダプターを選択してください。
deployment: {
output: "server",
adapter: "node", // or "vercel" | "netlify" | "cloudflare"
site: "https://docs.example.com",
}
ai.mcp.enabled が設定された静的ビルドは、deployment.output を server に設定するよう促すメッセージとともに即座に失敗します。アダプターについてはデプロイを参照してください。デプロイ後は、次のコマンドで Claude Code から接続できます。
claude mcp add --transport http my-docs https://docs.example.com/mcp
JSON API
すべての Blume サイトは、ドキュメントを小さな読み取り専用の JSON API としても配信します。これは MCP サーバーのツールの REST 版で、同じページスナップショットの上に構築され、MCP ではなく素の HTTP を話すエージェントや関数呼び出しフレームワーク向けのものです。既定で有効で、設定は不要です。
| エンドポイント | 返される内容 |
|---|---|
/api/docs/pages.json |
すべてのページを、そのルート、タイトル、説明、コンテントタイプ、ロケール、ファセット、そしてレンダリング版・Markdown 版・JSON 版の URL とともに返します。 |
/api/docs/pages/{route}.json |
1 ページ分。索引エントリーに加えてエージェント向け Markdown(get_page が返すのと同じ本文)を返します。{route} は先頭のスラッシュを除いたページのルートで、ホームは index です。 |
/api/docs/navigation.json |
ナビゲーションツリー(ヘッダーのタブとサイドバーの階層)。 |
/api/docs/search?q= |
全文検索。search_docs と同じ limit、contentTypes、locale、version、filters[key] によるスコープ指定に対応します。サーバー出力でのみ利用可能です。 |
/openapi.json |
機械可読な面全体を記述する OpenAPI 3.1 の記述。 |
ページ索引、ページごとのドキュメント、ナビゲーションはプリレンダリングされるため、静的サイトはどのホストからでもそれらをファイルとして配信できます。検索はライブのエンドポイントで、サーバー出力でのみ存在し、そこでは search_docs と同じインデックスで動作します。エラーは RFC 9457 の problem details(application/problem+json)で、安定した code、detail、そしてエージェントに次の行き先を伝える resolution ヒントを備えています。存在しないページ、空の検索クエリ、そしてサーバー出力ではどのエンドポイントも応答しない /api/… の URL が対象です。
{
"code": "API_ROUTE_NOT_FOUND",
"detail": "No API route exists at /api/nope.",
"instance": "/api/nope",
"links": [
{
"href": "https://docs.example.com/openapi.json",
"label": "OpenAPI description"
},
{
"href": "https://docs.example.com/api/docs/pages.json",
"label": "Page index"
}
],
"resolution": "Discover the available operations through the OpenAPI description at https://docs.example.com/openapi.json, or list every page at https://docs.example.com/api/docs/pages.json.",
"status": 404,
"title": "API route not found",
"type": "about:blank"
}
/openapi.json の OpenAPI 文書は設定からビルドごとに生成されるため、デプロイされたサイトが実際に配信するものだけを記述します。一意な operationId、型付きのパラメーター、レスポンススキーマを備えたすべての JSON エンドポイントに加えて、隣り合うテキストの面(.md ミラー、llms.txt と llms-full.txt、agent-readability.json)、そして有効な場合は MCP エンドポイントも含まれます。OpenAPI の記述からツールを組み立てるフレームワークは、MCP クライアントと同じ範囲に到達できます。この文書は API カタログ、可読性マニフェスト、ホームページの Link ヘッダー(rel="service-desc")、そして llms.txt からリンクされます。
これらはあなた自身の API リファレンスには一切影響しません。ドキュメント化された仕様はページとしてレンダリングされ、/openapi.json で配信されることはなく、カタログには両方が掲載されます。自分で用意した public/openapi.json はそのルートを引き継ぎます(JSON エンドポイントはそのまま残ります)。/api/… のキャッチオールは、ドキュメントのセクションが /api 名前空間から配信される場合(content/api/overview.md)や、カスタムページが /api/ 配下の残りのルートを保持している場合には道を譲るため、それらのページが引き続き優先されます。これらを一切公開しないようにするには ai.api を false に設定します。
ai: {
api: false,
}
エージェント可読性
Blume はサイトのルートに /agent-readability.json マニフェストを書き出し、このページで説明したエージェント向けの機能を索引化します。これにより、エージェントは規約を推測したり HTML をスクレイピングしたりせずに、1 回の取得で発見できます。llms.txt と同様、既定で有効です。
seo: {
agentReadability: true,
}
マニフェストには有効にしたものだけが列挙されます。生の Markdown ミラーのパターン、JSON API とその OpenAPI の記述、llms.txt と llms-full.txt、MCP サーバーとそのディスカバリー文書、Ask AI のエンドポイント、サイトマップ、RSS フィードに加えて、サイト名、説明、ソースリポジトリ、コンテントシグナルの利用ポリシーが含まれます。URL は deployment.site が設定されていれば絶対 URL に、そうでなければルート相対になります。
{
"artifacts": {
"markdown": {
"contentNegotiation": "text/markdown",
"pattern": "https://docs.example.com/{route}.md"
},
"api": {
"openapi": "https://docs.example.com/openapi.json",
"pages": "https://docs.example.com/api/docs/pages.json",
"search": "https://docs.example.com/api/docs/search"
},
"llmsFullTxt": "https://docs.example.com/llms-full.txt",
"llmsTxt": "https://docs.example.com/llms.txt",
"mcp": {
"discovery": "https://docs.example.com/.well-known/mcp.json",
"url": "https://docs.example.com/mcp"
}
},
"description": "Docs for the Acme API.",
"generator": "blume@1.0.0",
"name": "Acme Docs",
"site": "https://docs.example.com",
"contentUsage": { "search": true, "ai-input": true, "ai-train": true },
"repository": "https://github.com/acme/docs"
}
contentNegotiation フィールドは、デプロイされたサイトが実際に Accept: text/markdown ヘッダーを尊重する場合にのみ現れます(コンテントネゴシエーションを参照)。それ以外のデプロイでは、マニフェストは .md ミラーのパターンのみを告知します。
seo.agentReadability を false にすると生成をスキップできます。独自の public/agent-readability.json を用意して置き換えることも可能です。Blume が public/ に置いたファイルを上書きすることはありません。
ディスカバリー用 Link ヘッダー
サイトを探索するエージェントは、マニフェストを探すべきだと知りません。そこで Blume は、IANA 登録済みのリレーションタイプを用いて、ホームページの RFC 8288 Link レスポンスヘッダーでもマニフェストを告知します。
Link: </.well-known/api-catalog>; rel="api-catalog"; type="application/linkset+json",
</openapi.json>; rel="service-desc"; type="application/json",
</agent-readability.json>; rel="describedby"; type="application/json",
</llms.txt>; rel="describedby"; type="text/plain",
</index.md>; rel="alternate"; type="text/markdown"
各エントリーは、その機能が有効な場合にのみ現れます。alternate リンクはホームページの Markdown ミラーを指します。ホームのルートがコンテンツページであればそのページ自身の生の Markdown、ランディングページであれば合成された llms.txt フォールバックです。service-desc リンク(RFC 8631)は JSON API の OpenAPI の記述を、api-catalog は生成された API カタログを指します。このヘッダーは、Blume が制御するすべての面に付与されます。開発サーバー(curl -I localhost:4321 で確認できます)、出力される _headers ファイル経由の静的ビルド(Netlify と Cloudflare)、そしてデプロイのルーティングルール経由の Vercel サーバービルドです。
とはいえ、すべてのエージェントがルートから入ってくるわけではありません。検索結果や共有リンクをたどってきたエージェントは深い階層のページに着地し、ホームページのヘッダーを目にすることはありません。そこで、レンダリングされるすべてのページも、同じ IANA 登録済みのリレーションを用いて、HTML の <head> に同じディスカバリーリンクを備えています。
<link
rel="describedby"
href="/agent-readability.json"
type="application/json"
/>
<link rel="describedby" href="/llms.txt" type="text/plain" />
<link rel="alternate" href="/docs/example.md" type="text/markdown" />
ここでの alternate リンクはそのページ自身の生の Markdown ミラーを指すため、エージェントは着地した HTML からトークン効率の良い版へ直接移動できます。head のリンクはプリレンダリングされた HTML とともに配信されるため、_headers を無視しカスタムのレスポンスヘッダーをまったく送信できないホスト(GitHub Pages、S3)でも機能します。エージェントがどのページから入ってきても同じです。
API カタログ
サイトが API を公開している場合、Blume は /.well-known/api-catalog に RFC 9727 の API カタログを生成します。これはリンクセットで、エージェントがドメインだけから API を列挙できるようにするもので、登録済みの application/linkset+json メディアタイプで、あらゆるビルド面において配信されます。設定は不要で、カタログは blume.config.ts にすでにある情報から導出されます。各 OpenAPI または AsyncAPI リファレンスは、レンダリングされたドキュメントのルートをアンカーとするエントリーになり、service-doc がそのドキュメントを、service-desc が仕様(取得可能な URL にある場合)を指します。サイト自身の JSON API は、その /openapi.json によって記述されるエントリーになります。そして MCP サーバーは、ディスカバリー文書をサービス記述とするエントリーになります。
{
"linkset": [
{
"anchor": "https://docs.example.com/reference",
"service-doc": [
{ "href": "https://docs.example.com/reference", "type": "text/html" }
],
"service-desc": [{ "href": "https://api.example.com/openapi.json" }]
},
{
"anchor": "https://docs.example.com/api/docs",
"service-desc": [
{
"href": "https://docs.example.com/openapi.json",
"type": "application/json"
}
],
"service-doc": [
{ "href": "https://docs.example.com/", "type": "text/html" }
]
},
{
"anchor": "https://docs.example.com/mcp",
"service-desc": [
{
"href": "https://docs.example.com/.well-known/mcp.json",
"type": "application/json"
}
],
"service-doc": [
{ "href": "https://docs.example.com/", "type": "text/html" }
]
}
]
}
API リファレンスも MCP サーバーもなく、JSON API も無効にしているサイトではカタログは出力されません。中身が何もないからです。他と同じく、自分で用意した public/.well-known/api-catalog ファイルは生成されたものより優先されます。
WebMCP
WebMCP は、ページがエージェント型ブラウザーに直接ツールを登録できるようにする新しいブラウザー API で、別途サーバー接続を必要としません。Blume のすべてのページは、ドキュメントの読み取り専用の機能をページのモデルコンテキストに登録します。search_docs(サイト検索)、get_page(ページの生の Markdown)、list_pages(llms.txt の索引)です。スクリプトは非常に小さく、ツールが実際に呼び出されるまで検索の仕組みを読み込まず、この API がないブラウザーでは何もしません。現時点では Chrome の早期プレビュー以外のすべてのブラウザーが該当します。登録は、流動的な仕様が公開しているいずれかの面(navigator.modelContext または document.modelContext)に対して、provideContext またはツールごとの registerTool 経由で行われます。
既定で有効です。オプトアウトするには webmcp: false を設定します。
ai: {
webmcp: false,
}
スキルのディスカバリー
プロジェクトがエージェントスキルを同梱している場合(Blume のリポジトリ自体もそうです)、それらを格納しているディレクトリを ai.skills に指定すると、ビルドが Agent Skills Discovery RFC に従ってディスカバリー用に公開します。
ai: {
skills: "./skills",
}
パスはプロジェクトルートからの相対で解決され、SKILL.md を含む各サブディレクトリが公開されるスキルになります。SKILL.md 単体のスキルは /.well-known/agent-skills/<name>/SKILL.md にそのままコピーされます(type: "skill-md")。補助リソース(scripts/、references/、assets/)を伴うスキルは決定的な .tar.gz にまとめられ(type: "archive")、展開後に相対参照が解決されるようにし、スクリプトの実行ビットも保持されます。/.well-known/agent-skills/index.json のディスカバリー索引には v0.2.0 の $schema と、スキルごとの名前、タイプ、説明(SKILL.md のフロントマターから)、成果物の URL、クライアントがダウンロードを検証するための SHA-256 ダイジェストが含まれます。
name/description が欠けていたり仕様に反していたりするスキルは、壊れた状態で公開する代わりにビルド警告を出してスキップされます。また、自分で用意した public/.well-known/agent-skills/index.json があれば、この面全体を置き換えます。
DNS ベースのディスカバリー(DNS-AID)
DNS for AI Discovery は策定中の IETF ドラフトで、well-known な DNS のエントリーポイントにある ServiceMode の SVCB/HTTPS レコードを照会することで、エージェントが HTTP リクエストを 1 度も送らずにサイトの AI 関連機能を発見できるようにします。DNS レコードはビルドではなくあなたのゾーンにあるため、これは Blume が代わりに公開できない唯一のディスカバリー面です。DNS プロバイダー側でレコードを追加してください。
_index._agents.docs.example.com. 3600 IN HTTPS 1 docs.example.com. alpn=h2
プロバイダーが提供していれば HTTPS レコードタイプを使い(Vercel DNS は対応していますが、素の SVCB タイプには対応していません)、そうでなければ alpn と port パラメーターを持つ ServiceMode の SVCB レコードを使ってください。ドラフトは、検証を行うリゾルバーが認証済みの応答を返せるように、DNSSEC でゾーンに署名することも推奨しています。Cloudflare のようなプロバイダーはワンクリックで有効化できますが、まったく対応していないところ(Vercel DNS を含む)もあります。
blume audit --url <origin> がこれを代わりに確認します。deployment.site が設定されている場合、ネットワーク層が DNS-over-HTTPS 経由でエントリーポイントを照会し、レコードが存在しなければ公開すべき正確なレコードを、加えて応答が DNSSEC で認証されているかどうかを報告します。ネットワークがパブリックなリゾルバー(Google、Cloudflare)をブロックしている場合は、BLUME_DOH_URL を設定して独自のリゾルバーを参照させてください。
Web Bot Auth
Web Bot Auth は逆方向に働きます。エージェントがあなたのドキュメントを読むためのものではなく、あなたの組織のエージェントが他所へリクエストを送るときに自らを識別するためのものです。エージェントは HTTP Message Signatures でリクエストに署名し、受信側のサイトはあなたのドメインで公開された公開鍵ディレクトリに照らして検証します。組織がエージェントを運用していて、Blume のサイトがそのエージェントが名乗るドメインにあるなら、公開鍵を公開してください。
ai: {
webBotAuth: {
keys: [{ kty: "OKP", crv: "Ed25519", x: "JrQLj5P_89iXES9-vFgrIy29c…" }],
},
}
すると Blume は、あらゆるビルド面で /.well-known/http-message-signatures-directory に登録済みのメディアタイプで JWKS を配信します。このディレクトリは定義上公開されるものなので、設定は公開鍵しか受け付けません。秘密情報(d、p、q など)を含む JWK は、漏洩した資格情報を配信する代わりにエラーで検証に失敗します。Ed25519 の鍵ペアは次のように生成します。
node -e 'const { generateKeyPairSync } = require("node:crypto"); const { publicKey, privateKey } = generateKeyPairSync("ed25519"); console.log("public: ", JSON.stringify(publicKey.export({ format: "jwk" }))); console.log("private:", JSON.stringify(privateKey.export({ format: "jwk" })))'
公開 JWK は上記の設定に入れ、秘密鍵は署名を行うエージェントが動作する場所(シークレットマネージャー。リポジトリには決して置かないでください)に配置します。組織がエージェントを運用していない場合は、これをスキップしてください。空のディレクトリは、検証する価値のあるものを何も告知しません。
blume.config.ts はビルド時に実行されるため、鍵をハードコードする必要はありません。ビルド時の環境変数から読み込めば、設定ファイルに鍵の塊を含めずに済み、コミットなしでローテーションできます。
const webBotAuthKey = process.env.WEB_BOT_AUTH_PUBLIC_JWK;
export default defineConfig({
ai: {
webBotAuth: {
keys: webBotAuthKey ? [JSON.parse(webBotAuthKey)] : [],
},
},
});
この変数がない環境ではディレクトリは公開されず、この方法で読み込まれた鍵もインラインの鍵とまったく同じように検証されます(秘密情報のチェックを含みます)。(公開鍵は秘密ではないので、インラインでコミットしても同じく問題ありません。環境変数は使い勝手の選択であって、セキュリティ上の選択ではありません。)
エージェントスキル
コーディングエージェントの助けを借りて Blume サイトを構築していますか。Blume のエージェントスキルをインストールすれば、あなたが説明しなくてもエージェントが Blume の仕組みを理解できます。
npx skills add haydenbleasel/blume
このスキルは、Blume とは何か、サイトのスキャフォールド・執筆・設定をどう行うかをエージェントに教え、インストール済みパッケージに同梱された完全なドキュメント(パッケージマネージャーがインストールした場所にある blume 内の docs/ ディレクトリ)を参照させます。
これは Blume が同梱するエージェントスキルの 1 つで、スケジュール実行されるエージェントによってドキュメントを製品と同期させ続けるスキルも併せて提供されています。