最近、検索やブラウザ、開発ツールの中で、AIがWebサイトの情報を読み取ったり、ユーザーの代わりに操作したりする場面が増えてきた。
それに伴って、「LLMO」「AI検索最適化」「AIエージェント対応」といった言葉も見かけるようになった。
ただ、現時点では何を実装すればAIから評価されるのか、どの仕様が広く利用されるのか、そもそも“最適化”と呼べる共通の正解があるのかは、まだ固まっていないと思っている。
そのため、今回このブログに llms.txt、llms-full.txt、WebMCPを実装したのは、「これを入れればAI検索に強くなる」と考えたからではない。
人間以外がWebサイトを読む可能性が増える中で、HTMLとは別に、内容を機械へ渡しやすい入口を用意してみたかったからだ。
今回は、実装した内容と、まだ効果も将来も決まり切っていない仕組みをなぜ先に試したのかを整理する。
人間向けの画面と、内容そのものは分けて考えられる
通常のWebページは、人間がブラウザで読むことを前提に作られている。
ヘッダー、ナビゲーション、目次、共有ボタン、関連記事、プロフィール、フッターなど、記事本文以外にも多くの要素が含まれている。
それらは人間にとって必要なUIだが、記事の内容だけを取得したいプログラムやAIにとっては、必ずしもすべてが必要とは限らない。
もちろん、現在のAIはHTMLから本文を抽出できる。だから、Markdown版を用意しなければ内容を読めないわけではない。
ただ、HTMLを解析して「どこからどこまでが本文か」を推測させるより、最初から記事タイトル、メタデータ、本文を含むMarkdownを渡せる方が単純だ。
このブログの記事は、もともとMarkdownで管理している。
そのため、人間向けにはHTMLを生成しつつ、必要であれば元の構造に近いMarkdownやJSONも返すという形は、比較的自然に実装できた。
人間向けUIをAI向けに作り替えるのではなく、同じコンテンツに対して複数の表現を用意するという考え方に近い。
今回実装したもの
今回追加したのは、主に次の3つだ。
- サイト全体の案内となる
llms.txt - 全記事をまとめた
llms-full.txt - 現在開いている記事のMarkdownを取得できるWebMCPツール
それぞれ役割が少し異なる。
llms.txtは、AI専用のサイトマップというより「案内板」
このブログでは、ルートに /llms.txt を配置した。
内容には、サイト名と説明に加えて、次の情報を含めている。
- 日本語記事の一覧
- 英語記事の一覧
- 各記事のMarkdown版URL
- Home、About、Works、Archiveなどの主要ページ
- 日本語・英語のRSS
llms-full.txtへのリンク
イメージとしては、サイト内の全URLを機械的に並べるサイトマップよりも、このサイトに何があり、どの情報から読むとよいかを示す案内板に近い。
# ともきちのエンジニア成長記
> 学習、開発、失敗、改善のログを残すための個人技術ブログです。
## Articles (Japanese)
- [記事タイトル](https://example.com/articles/example.md): 記事の説明
## Pages
- [About](https://example.com/about/): Author profile and focus areas.
記事一覧を手作業で更新すると、記事を公開するたびに llms.txt の更新を忘れる可能性がある。
そこで、AstroのContent Collectionsから公開記事を取得し、ビルド時に一覧を生成するようにした。
const articles = await getArticlesWithPaths(locale);
return articles
.map(
({ article, slug }) =>
`- [${article.data.title}](${articleMarkdownUrl(locale, slug)}): ${article.data.description}`,
)
.join("\n");
記事のタイトルや説明は、通常の記事ページと同じfrontmatterを参照する。
これにより、AI向けの情報だけを別管理するのではなく、人間向けページと同じ情報源から生成する形にした。
ただし、llms.txt は現在提案されている形式の一つであり、すべてのAIサービスが読み取ることを保証するものではない。
少なくとも今の段階では、「配置すればAIからの流入が増えるファイル」というより、機械がサイトの構造を把握したいときに利用できる、任意の入口として考えている。
llms-full.txtには、公開記事のMarkdownをまとめた
/llms-full.txt には、日本語と英語の公開記事をMarkdownのまま連結している。
const body = articles
.map(({ article, slug }) => buildArticleMarkdown(article, locale, slug).trim())
.join("\n\n---\n\n");
llms.txt が記事へのリンクをまとめた索引だとすれば、llms-full.txt は本文までまとめたファイルだ。
この形式が必ず必要というわけではない。
むしろ記事数が増えれば、ファイルが大きくなりすぎて扱いにくくなる可能性もある。将来的には、カテゴリや言語ごとに分割した方がよいかもしれない。
それでも、現在のように記事数がまだ多くない段階では、サイト全体の内容を一度に確認したい用途に使える。
ここでも目的は検索順位を上げることではなく、必要な側が扱いやすい形を一つ増やしておくことだ。
各記事はMarkdownとJSONでも取得できる
llms.txt から参照するため、各記事にはHTML版とは別にMarkdown版を用意している。
たとえば、通常の記事URLが次の形なら、
/articles/example/
Markdown版は次のURLで取得できる。
/articles/example.md
記事ページのheadにも、Markdown版とJSON版を代替表現として記述している。
<link rel="alternate" type="text/markdown" href="..." />
<link rel="alternate" type="application/json" href="..." />
これはAIだけのための機能ではない。
Markdownをそのままコピーしたい人、別のツールへ読み込ませたい人、JSONとしてメタデータを扱いたいプログラムにも利用できる。
「AI向け機能」として閉じるより、コンテンツを再利用しやすくする一般的な出力形式として考えた方が自然だと思っている。
WebMCPでは、現在の記事を取得するツールだけを公開した
WebMCPは、Webページがブラウザ上で構造化されたツールを公開し、対応するAIエージェントがそのツールを呼び出せるようにするための仕様案だ。
このブログでは、記事ページを開いたときに get_current_article_markdown というツールを登録している。
役割は、現在表示している記事のMarkdownを返すことだけだ。
実装を単純化すると、次のようになる。
const modelContext = document.modelContext;
modelContext.registerTool({
name: "get_current_article_markdown",
description: "Return the full Markdown source of the article currently open in this tab.",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
annotations: {
readOnlyHint: true,
},
async execute() {
const response = await fetch(markdownUrl);
const text = await response.text();
return {
content: [{ type: "text", text }],
};
},
});
検索、投稿、編集といった複数のツールを公開することも考えられるが、今回はそこまで広げなかった。
まずは、
- 入力を必要としない
- サーバー上の状態を変更しない
- 個人情報を扱わない
- すでに公開している記事を読み取るだけ
という、影響の小さいツールだけを実装した。
readOnlyHint: true も設定し、このツールが読み取り専用であることを示している。
また、WebMCPのAPIが存在しないブラウザでは、そのまま何もせず終了する。
if (!modelContext || !root) return;
そのため、未対応環境でも通常の記事閲覧には影響しない。
Astroのページ遷移で古い記事のツールが残らないように、ページを離れる前にAbortControllerで登録を解除し、次のページで改めて登録するようにもした。
このように、WebMCPをサイトの前提にはせず、利用できる環境でだけ追加される段階的な機能として扱っている。
なぜ、まだ普及が決まっていない段階で実装したのか
正直なところ、llms.txtやWebMCPが今後どこまで普及するかは分からない。
仕様が変わる可能性もあるし、別の方法が主流になる可能性もある。
それでも実装した理由は、大きく4つある。
1. Webの利用者が人間だけとは限らなくなってきたから
これまでのWeb制作では、主に人間がブラウザで閲覧することを考えていればよかった。
しかし現在は、検索エンジンだけでなく、AIアシスタント、ブラウザエージェント、開発ツールなどがWeb上の情報を取得する場面も増えている。
将来の形を断定することはできないが、Webサイトが人間向けの画面だけを提供すれば十分なのかは、考えておく価値があると思った。
2. 元データがMarkdownなので、小さなコストで試せたから
このブログは、MarkdownをAstroでHTMLへ変換する構成になっている。
そのため、機械向けに内容を一から作り直す必要はなかった。
同じMarkdownからHTML、Markdown、JSON、llms.txtを生成し、WebMCPからもそのMarkdownを返せる。
大きなシステムを追加せず、既存のコンテンツモデルを利用して試せることは、個人ブログとの相性がよかった。
3. 効果を予想するより、実装して観察したかったから
新しい技術について考えるとき、普及してから対応するという選択肢もある。
一方で、小さく実装できるのであれば、実際に触った方が分かることも多い。
- どのような情報を構造化すればよいのか
- 人間向けページとの情報の重複をどう避けるか
- 仕様変更へ追従しやすい設計にできるか
- 読み取り専用と状態変更をどう分けるべきか
こうしたことは、記事を読むだけより、実装した方が具体的に考えられる。
今回の対応は将来への賭けというより、変化を理解するための小さな実験に近い。
4. 後から外しやすい形にできたから
新しい仕様を試すときは、導入しやすさだけでなく、外しやすさも大切だと思っている。
今回の実装は、既存の記事表示やURL構造を置き換えていない。
llms.txtとllms-full.txtは追加の出力であり、WebMCPもAPIが存在するときだけ動く。
仮に使われないまま終わったとしても、通常のブログ機能への影響は小さい。
普及が読めない技術だからこそ、中心に据えるのではなく、疎結合な追加機能として試した。
これは「LLMO対策が完了した」という話ではない
今回の実装を、LLMO対策やAI検索最適化と呼び切ることには少し違和感がある。
そもそもLLMOという言葉が指す範囲も一定ではなく、AIサービスがWebサイトをどのように発見し、取得し、回答へ利用するかもサービスごとに異なる。
llms.txtを置いたからといって、AIの回答に引用されるとは限らない。
WebMCPを実装したからといって、一般的なブラウザやAIエージェントがすぐに利用できるわけでもない。
現時点では、どちらも広く確立した成果を約束するものではない。
そのため、このブログでは「AI向けに最適化した」というより、
AIやプログラムが内容を取得するときに使える、機械可読な経路を試験的に用意した
と表現する方が正確だと思っている。
llms.txtはrobots.txtや利用許諾の代わりではない
もう一つ注意したいのは、llms.txtはアクセス制御の仕組みではないということだ。
robots.txtのようにクローラーへアクセス方針を伝えるものとも、コンテンツの著作権やAI学習への利用条件を定めるライセンスとも役割が異なる。
このブログのllms.txtは、サイトの内容と取得しやすい形式を案内するためのものだ。
何をクロールしてよいか、記事をどのように利用してよいかという問題は、robots.txt、利用規約、ライセンス、各サービスの挙動などを分けて考える必要がある。
名前だけを見るとAIに関するあらゆる方針を記述するファイルにも見えるが、少なくとも今回の実装では、権限を与えるものではなく、公開済み情報への案内として扱っている。
WebMCPで状態を変更するなら、話は大きく変わる
今回公開したWebMCPツールは、記事のMarkdownを返すだけなので、できることは限定されている。
しかし、WebMCPでフォーム送信、購入、予約、投稿、設定変更などを扱う場合は、単にツールを登録するだけでは不十分だ。
通常のWeb UIと同じように、あるいはそれ以上に、
- 認証と認可
- 入力値の検証
- CSRFや不正リクエストへの対策
- 重要操作の確認
- 二重実行の防止
- レート制限
- 操作ログ
- ツールの説明と実際の動作の一致
などを考える必要がある。
AIエージェントが呼び出すからといって、信頼できるクライアントになるわけではない。
今回はまず、公開コンテンツを読むだけの機能に限定した。今後ツールを増やすとしても、便利そうだからという理由だけで状態変更を公開せず、通常のAPI設計と同じように境界を考えたい。
今後確認していきたいこと
実装した時点では、まだ「対応完了」ではない。
今後は、次のような点を確認していきたい。
llms.txtやMarkdown版へのアクセスが実際に発生するか- 記事数が増えたときに
llms-full.txtが大きくなりすぎないか - WebMCPの仕様変更へ無理なく追従できるか
- ブラウザやエージェント側の対応がどこまで広がるか
- 機械向けの説明が、人間向けの説明と矛盾していないか
ただし、アクセスログに記録があっても、それがAIによる利用なのか、単なる確認やクローラーなのかを正確に判断できるとは限らない。
数字が取れたとしても、すぐに効果と結びつけず、長い目で観察する必要があると思っている。
おわりに
今回、技術ブログにllms.txt、llms-full.txt、WebMCPを実装した。
しかし、これは「これからはこの方法が正解になる」と断定したものではない。
AI検索やAIエージェント向けWebの形は、まだ変化の途中にある。
だからこそ、将来を決めつけて大きく作り込むのではなく、既存の人間向け体験を壊さない範囲で、機械にも情報を渡しやすい入口を追加した。
HTMLを読む人がいる。 Markdownを直接使いたい人がいる。 プログラムがJSONを取得することもある。 そして将来は、ブラウザ上のAIエージェントがWebMCPのツールを利用するかもしれない。
そのすべてを今から予測することはできない。
それでも、コンテンツと表示を分け、同じ情報を複数の形で安全に提供できる設計は、AIの流行に関係なく意味があると思っている。
今回の実装は、未知のランキングを攻略するための施策ではない。
人間だけが利用者とは限らないWebを考えるための、小さな実験である。