エージェントによる検出
エージェントが推測に頼らず、機械可読なサーフェスを見つける方法 — エージェント可読性マニフェスト、Link ヘッダー、RFC 9727 API カタログ、WebMCP、サイト用に生成されるスキルと公開されたスキル、DNS ベースの検出、そして Web Bot Auth キー。
llms.txt、Markdown ミラー、JSON API、MCP サーバーを公開しても、それは仕事の半分に過ぎません — エージェントはそれらを見つけなければならないからです。Blume は、エージェントが実際に探索する規約を通じてサーフェス全体を告知します。サイトルートのマニフェスト、Link ヘッダーと <link> タグ、well-known ファイル、そしてブラウザ自身のモデルコンテキストです。ここに書かれているものはすべてデフォルトで有効になっており、すでに有効にしている機能から導出されます。
エージェント可読性
Blume はサイトルートに /agent-readability.json マニフェストを書き出し、このセクション全体で説明されているエージェント向けサーフェスをインデックス化します — これにより、エージェントは規約を推測したり HTML をスクレイピングしたりせず、単一のフェッチで検出できます。llms.txt と同様に、デフォルトで有効です:
agents: {
agentReadability: true,
}
マニフェストには有効にしたものだけが列挙されます — 生の Markdown ミラーパターン、JSON API とその OpenAPI 記述、llms.txt と llms-full.txt、MCP サーバー とその検出ドキュメント、アシスタント エンドポイント、サイトマップ、RSS フィード — に加えて、サイト名、説明、ソースリポジトリ、content-signal 利用ポリシーが含まれます。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@2.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 ミラーパターンのみを告知します。
agents.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; profile=\"https://www.rfc-editor.org/info/rfc9727\"",
</.well-known/ai-catalog.json>; rel="ai-catalog"; type="application/ai-catalog+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 カタログを、ai-catalog は AI カタログを指します。このヘッダーは Blume が制御するすべてのサーフェスに載ります: 出力される _headers ファイル経由の静的ビルド(Netlify と Cloudflare)、デプロイのルーティングルール経由の Vercel サーバービルド、そして開発サーバー(curl -I localhost:4321 で確認できます)です。開発サーバーのヘッダーには、開発サーバー自身が配信するもの — service-desc と alternate リンク — だけが列挙されます。カタログ、agent-readability.json、llms.txt は blume build がビルド出力に書き出すものだからです。
とはいえ、すべてのエージェントがルートから入ってくるわけではありません — 検索結果や共有リンクをたどるエージェントは深い階層のページに着地し、ホームページのヘッダーを目にすることはありません。そのため、レンダリングされるすべてのページも、同じ IANA 登録済みリレーションを使って、HTML の <head> に同じ検出リンクを含めます:
<link
rel="describedby"
href="/agent-readability.json"
type="application/json"
/>
<link rel="describedby" href="/llms.txt" type="text/plain" />
<link
rel="ai-catalog"
href="/.well-known/ai-catalog.json"
type="application/ai-catalog+json"
/>
<link rel="ard" href="/.well-known/ard.json" type="application/json" />
<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 メディアタイプと RFC の profile="https://www.rfc-editor.org/info/rfc9727" パラメータとともに配信されます。設定は不要です: カタログは blume.config.ts にすでにある内容から導出されます。各 OpenAPI、AsyncAPI、または GraphQL リファレンスは、レンダリングされたドキュメントのルートをアンカーとするエントリになり、service-doc がそのドキュメントを指し、仕様がフェッチ可能な URL にある場合は service-desc がその仕様を指します。サイト自身の 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 ファイルが生成されたものより優先されます。
AI カタログ
API カタログは API を列挙します。AI カタログは、エージェントがサイトから取り込みうるものすべて — MCP サーバー、公開された各スキル、JSON API、レンダリングされた各 API リファレンス、そして llms.txt — を、エージェントレジストリがインデックス化する形式で列挙します: /.well-known/ai-catalog.json にある AI Catalog ドキュメントであり、これは Agentic Resource Discovery(ARD)のコンシューマーが解決するマニフェストでもあります。各エントリは、ドメインをアンカーとする urn:air:<host>:<namespace>:<name> 識別子、表示名、1 行の説明、成果物のメディアタイプ、その URL、そしていくつかの representativeQueries — そのリソースが答えられるサンプル質問で、レジストリがセマンティック検索のために埋め込むもの — を持ちます。以下は、MCP サーバーを有効にし、スキルを 1 つ公開している「Acme」というタイトルのサイトのカタログです:
{
"specVersion": "1.0",
"host": {
"displayName": "Acme",
"identifier": "did:web:docs.example.com",
"documentationUrl": "https://docs.example.com/"
},
"entries": [
{
"identifier": "urn:air:docs.example.com:mcp:acme",
"displayName": "Acme",
"description": "Model Context Protocol server over the Acme documentation: full-text search, page Markdown, the page index, and the navigation tree.",
"type": "application/mcp-server-card+json",
"url": "https://docs.example.com/.well-known/mcp/server-card.json",
"capabilities": [
"search_docs",
"get_page",
"list_pages",
"get_navigation"
],
"representativeQueries": [
"search the Acme documentation",
"get a page of the Acme docs as Markdown",
"list every page in the Acme docs"
]
},
{
"identifier": "urn:air:docs.example.com:skill:acme",
"displayName": "acme",
"description": "Set up an Acme project and call its API.",
"type": "application/agent-skills+md",
"url": "https://docs.example.com/.well-known/agent-skills/acme/SKILL.md",
"representativeQueries": [
"load the acme agent skill",
"how do I use acme"
]
},
{
"identifier": "urn:air:docs.example.com:api:docs",
"displayName": "Acme docs API",
"description": "REST API over the Acme documentation: the page index, each page as JSON or Markdown, and the navigation tree, described by this OpenAPI document.",
"type": "application/vnd.oai.openapi+json",
"url": "https://docs.example.com/openapi.json",
"representativeQueries": [
"fetch a page of the Acme docs as JSON",
"list the pages in the Acme docs",
"get the navigation tree of the Acme docs"
]
},
{
"identifier": "urn:air:docs.example.com:docs:llms-txt",
"displayName": "Acme llms.txt",
"description": "llms.txt index of the Acme documentation: every page with a one-line summary, plus the agent-facing resources on this site.",
"type": "text/plain",
"url": "https://docs.example.com/llms.txt",
"representativeQueries": [
"what is Acme",
"overview of the Acme documentation"
]
}
]
}
ARD の現行リビジョンはマニフェストを /.well-known/ard.json から読み取り、ai-catalog.json を先行パスと呼んでいます。そのため Blume は同じドキュメントを両方に書き出し、すべてのページの head で両方のリンクリレーション(ai-catalog と ard)のもとに告知し、llms.txt と agent-readability.json にも列挙します。このカタログと、その .well-known の隣人たち(API カタログ、MCP 検出ファイル)は、すべてのビルドサーフェスで Access-Control-Allow-Origin: * とともに配信されるため、別オリジンからそれらを読み取るレジストリがブロックされることはありません。
エントリ識別子はあなたのドメインをアンカーとするため、カタログには deployment.site が必要です — 設定されていない場合は何も出力されません。デフォルトで有効で、agents.catalog: false で無効にできます。生成されるクエリは、サイトタイトルと各エントリ自身の名前から導出されます。あるエントリに独自のクエリを書くには、識別子の末尾(<namespace>:<name>)をキーにしてください:
export default defineConfig({
agents: {
catalog: {
queries: {
"mcp:acme": ["how do I install Acme", "search the Acme docs"],
"skill:acme": ["set up an Acme project", "write an Acme plugin"],
},
},
},
});
リストを指定すると、そのエントリの生成済みクエリが置き換えられます。指定しなかったエントリは元のクエリを保持します。他の箇所と同様に、自分で配置した public/.well-known/ai-catalog.json(または ard.json)が生成されたものより優先されます。
WebMCP
WebMCP は、ページがエージェント型ブラウザに直接ツールを登録できるようにする新興のブラウザ API です — 別途サーバー接続を用意する必要はありません。Blume の各ページは、ドキュメントの読み取り専用サーフェスをページのモデルコンテキストに登録します: search_docs(サイト検索)、get_page(ページの生の Markdown)、list_pages(llms.txt インデックス)です。スクリプトは非常に小さく、ツールが実際に呼び出されるまで検索機構を読み込まず、この API を持たないブラウザでは静かに何もしません — 今日それは Chrome の早期プレビュー以外のすべてのブラウザです。変動中の仕様が公開するいずれかのサーフェス(navigator.modelContext または document.modelContext)に対して、provideContext またはツールごとの registerTool 経由で登録します。
デフォルトで有効です。オプトアウトするには webmcp: false を設定します:
agents: {
webmcp: false,
}
サイトのスキル
すべてのビルドは、ドキュメント用のエージェントスキルを書き出します — コーディングエージェントがインストールしてあなたの製品を扱えるようにする SKILL.md です。これは /skill.md で配信され、スキルインデックスに列挙されます。Blume はモデルを呼び出すことなく、サイトについてすでに把握している情報からこれを構築するため、コストはかからず、ドキュメントが変わるたびに更新されます。内容は次のとおりです:
- サイトのタイトルと説明、そしてエージェントがそのスキルを使うべきタイミング
- 任意のページを Markdown として読む方法に加え、サイトが配信している場合は
llms.txt、llms-full.txt、MCP サーバー、JSON API、変更履歴 - API リファレンス
- サイドバー順のドキュメントマップ。各ページの Markdown へのリンクと 1 行の説明(
llms.txtと同様に、ページのメタディスクリプション)付きです
スキルの名前はサイトの title から付けられます(Acme Docs は acme-docs になります)。対象はデフォルト言語の現行バージョンで、API オペレーションページと変更履歴エントリは除外され(それぞれマップの上に専用の 1 行があります)、llms.txt が除外するページ(下書き、非表示ページと noindex ページ、そして ai.exclude が指定されたページ)もスキップし、最大 200 ページまでを列挙し、残りについては llms.txt を案内します。リンクは絶対 URL のため、スキルには deployment.site が必要です。設定されていないビルドではスキップされます。
生成されるスキルはドキュメントへのガイドであり、製品の要約ではありません。エージェントに製品そのもの — セットアップ、コアコンセプト、よくあるタスク、注意点 — を教えるスキルが必要な場合は、コーディングエージェントにドキュメントから書かせてください:
blume skill --claude # or --codex
blume skill は Claude Code または Codex を blume-write-skill スキルで起動します。このスキルは agents.skills フォルダ(未設定の場合は ./skills を使い、設定にも追加します)の下に、サイト名にちなんだ名前の SKILL.md を書き出すため、生成されたスキルを置き換えます。他のファイルと同様にレビューしてコミットしてください。エージェントのトークンを消費するのは一度だけで、ビルドのたびではありません。手書きのスキルは自動では更新されないため、ドキュメントを大きく変更した後はもう一度実行してください。フラグなしの場合、blume skill はスキルの配置先と、別のエージェントにそれを指示する方法を出力します。
agents.skills 内に同じ名前のスキルがあれば、どのように書かれたものであっても、インデックス内の生成されたスキルを置き換えます。それが SKILL.md 単体のスキルであれば、/skill.md でも置き換えます。自分で配置した public/skill.md はその両方より優先されます。スキルを無効にするには:
agents: {
skillMd: false,
}
スキルの検出
プロジェクトがエージェントスキルを同梱している場合 — Blume リポジトリ自体がそうです — agents.skills をそれらを含むディレクトリに向けると、ビルドは Agent Skills Discovery RFC に従ってそれらを検出用に公開します:
agents: {
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 はサーフェス全体を引き継ぎます。公開されたスキルは、サイト独自のスキルも含めて、llms.txt にも列挙されます。
DNS ベースの検出(DNS-AID)
DNS for AI Discovery は、well-known な DNS エントリポイントで ServiceMode の SVCB/HTTPS レコードを照会することで、HTTP リクエストを一度も送らずにエージェントがサイトの AI サーフェスを検出できるようにする、新興の IETF ドラフトです。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 サイトがそのエージェントが名乗るドメインにある場合は、その公開鍵を公開してください:
agents: {
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({
agents: {
webBotAuth: {
keys: webBotAuthKey ? [JSON.parse(webBotAuthKey)] : [],
},
},
});
この変数がない環境ではディレクトリは公開されません。また、この方法で読み込まれた鍵はインラインの鍵とまったく同様に検証されます — 秘密要素のチェックも含みます。(公開鍵は秘密情報ではないため、インラインでコミットしても同じく問題ありません。環境変数は使い勝手のための選択であって、セキュリティ上の選択ではありません。)