AIエージェントに「使われたか」を測る——ポータルに計測層と新しい露出面を足した話

「見つけてもらう」の次は「使われたかを測る」。AIエージェント向けポータルに、取得を計測する analytics-api と、agents.txt / ai.txt / Citation Pack などの新しい露出面を足した実装記録。

llms.txtとMCPで作る、AIエージェント向けニュースポータル実践記
AIエージェントを一級の読者としてニュース/CVEポータルを設計・公開した実践記。llms.txt・MCP・.well-known などの discovery 層の実装と…

前回、llms.txt と Model Context Protocol(MCP)で「AIエージェントを読者に想定したニュースポータル」を自宅サーバーに組んだ話を書いた。Markdown を正典に、非同期パイプラインで要約・翻訳・グラフ照会をこなし、robots.txt や llms.txt、MCP Server Card といった discovery(機械可読な入口)を実体から自動生成する——ここまでで「エージェントに見つけてもらう」土台はできていた。

AIエージェント向けニュース・脆弱性ポータル / Agent-Readiness Directory
ニュース記事とCVE/脆弱性アドバイザリをMCP/REST APIでメタデータ中心に提供し、他サイトのAgent-Readiness(llms.txt/MCP/API catalog対応度)を採点するディレクトリ。

だが公開して運用を始めると、すぐに次の欠落が見えてきた。見つけてもらえたとして、実際に取りに来ているのか。誰(どのエージェント)が、どの記事を、どれだけ引いているのか。それがまったく分からない。 discovery を頑張って整えても、その効果を測る目盛りがなければ、次の改善はただの勘になる。

そこで前回以降のこの1週間で足したのが、大きく2つ。ひとつは取得を計測する analytics-api、もうひとつは agents.txt / ai.txt / Citation Pack といった 新しい露出面だ。本記事はその実装記録である。

背景:discovery は「入口」、でも「通ったか」は別の話

前回作った discovery 層は、いわば看板と地図だ。「ここに記事がある」「MCP でこう取れる」「このライセンスで再配布してよい」——エージェントが迷わず入ってこられるよう、機械可読な標識を敷き詰めた。

問題は、看板を出すことと、人(エージェント)が実際にその道を通ったかは別だということ。従来の Web なら Google Analytics のようなアクセス解析がその役目を果たすが、あれは JavaScript を実行するブラウザ、つまり人間の閲覧を前提にした仕組みだ。MCP でコンテンツを取りに来るエージェントも、Markdown を直に GET していくクローラも、JS など実行しない。エージェント向けに作ったサイトほど、既存の解析ツールでは「無人島」に見えてしまう。

これは自分だけの悩みではない。2026年に入って GEO(Generative Engine Optimization=生成AI検索への最適化)という言葉が定着し、ChatGPT・Claude・Perplexity・Gemini といった AI クローラを User-Agent 単位で識別し、自サイトの引用率(citation rate)を測るのが定石になりつつある。露出を整える(discovery)、挙動を制御する(robots や新興の Agent Control Standard)、そして効果を測る——この3層目が、個人運用のポータルにも必要になったということだ。

詳細①:計測層 analytics-api——責務を分けて後付けする

新設した analytics-api(コンテナ名 portal-analytics)は、コンテンツ本体からきっぱり切り離した独立サービスにした。理由はこのポータルの一貫した設計思想、責務分離にある。

記事の正典は Markdown、その導出インデックスが portal.db(再構築可能な SQLite)。計測イベントはそのどちらとも性質が違う——消えても記事は復元できるし、逆にイベント台帳の都合でコンテンツ DB を汚したくない。だから計測は別の analytics.db に完全分離した。両方とも LXC ローカルの ext4 に置く(NFS 上の SQLite は WAL ロックが不安定なため、これも既存規約どおり)。

計測点は次の4つに絞った。

  • HTTP 200 で /<category>/<item-id>.md(Markdown 直配信)が返った → retrieval
  • MCP の portal_get が成功した → retrieval
  • 公開 /api/search が叩かれた → search
  • 記事 Markdown が 404 → not_found

肝は「どうやってイベントを拾うか」だ。ここでも本体を汚さない方針を貫いた。nginx のアクセスログを JSON 形式で出し、それを syslog 経由で analytics-api に流す。受け取った側は parse_nginx_message でパスから記事 ID とカテゴリを抜き、ステータスからイベント種別を決める。MCP の portal_get は、内部限定エンドポイント /internal/events(X-Analytics-Token 必須、Cloudflare / nginx には一切公開しない)へ自分でイベントを投げる。こうして「Markdown を直に読まれた」も「MCP で取られた」も、同じ台帳に集約される。

取り出し口は3系統に分けた。

  • 公開・匿名集計:GET /api/popular?window=24h&limit=10。人気記事ランキングだけを、認証なしで(nginx 経由でも)返す。
  • LAN 限定・Bearer 必須(ANALYTICS_API_TOKENS):/v1/analytics/summary /pages /pages/<id> /agents /timeseries。運用者だけが見る詳細。
  • 内部受付:前述の /internal/events。Docker 内の MCP からのみ。

そして MCP 側にも新しいツール portal_popular(window, category, limit) を足した。エージェント自身が「いまこのポータルでよく読まれている記事は?」を 24h / 7d / 30d / 90d の窓で問い合わせられる。計測して終わりではなく、その結果をまたエージェント向け API として還元する——ポータルらしい閉じ方だと思う。

なお retrieval は「回答用にコンテンツを取得した回数」であって、最終的な回答に実際に引用された保証ではない。ここは正直に定義を分けている。取得は測れても、引用は相手の頭の中の話なので測りようがない。

詳細②:エージェントを名指ししつつ、プライバシーは削ぐ

計測でいちばん神経を使ったのがプライバシー設計だ。「誰が来たか」を知りたい欲と、「個人を追跡しない」原則は、油断するとすぐ衝突する。

方針はシンプルにした。個人を特定しうる生データは一切保存しない。 生 IP も、認証の Bearer トークンも、検索クエリの中身も台帳に残さない。クライアントの識別が必要な集計(ユニーク数など)には、remote_addr と User-Agent を混ぜて HMAC-SHA256 でハッシュ化した匿名キーだけを使う。生イベントの保持は既定90日で、サービス起動時に期限切れを自動削除する。

一方で「どのエージェントか」は積極的に名指しする。User-Agent 文字列から既知のパターンを分類し、OpenAI / Codex、Anthropic / Claude、Perplexity、Google / Gemini、Mistral、Microsoft、Meta、Amazon、Apple、ByteDance、Cohere、DuckDuckGo、You.com、xAI、Common Crawl……に振り分ける。該当しなければ Other。分類表を増やしたときは、起動時に保存済みの Other / Unknown も過去の User-Agent から再分類し直すので、後から遡って精度を上げられる。

ここでも過信は禁物で、User-Agent は自己申告にすぎない。詳細 API が返すエージェント名も、確定的なモデル識別とはみなさない——名乗りは名乗りとして受け、鵜呑みにはしない、というスタンスをコードのコメントにも残した。

詳細③:露出面を agents.txt / ai.txt / Citation Pack まで広げる

計測と並行して、discovery 生成器(gen_discovery)が吐く入口も増やした。前回の robots / llms.txt / sitemap / MCP Server Card / API Catalog / Agent Skills に、今回は次を加えている。

  • agents.txt:サイト直下に置く「エージェント向けの総合案内」。identity に加え、agent-manifest、llms.txt、MCP、Agent Skills、変更フィード、Citation Pack、ai.txt への導線を1枚に集約する。agents.txt はいま複数の団体がオープン標準として提案中で、robots.txt や llms.txt の「上のレイヤ」——サイトの素性と利用条件、提供する agentic endpoint を宣言する層——として位置づけられている。その流れに乗せた形だ。
  • agent-manifest.json:agents.txt の機械可読な相棒。指示書・スキル・ポリシー(AI 利用方針 = ai.txt、記事ライセンス欄 = license_note)を JSON でまとめる。
  • ai.txt:AI による利用ポリシーの宣言。robots の Content-Signal と連動させている。
  • changes.md / topics/.md / entities/.md:エージェントが定点観測するための安定した入口。「このトピック」「このエンティティ」を継続的に監視したいエージェントに、URL が変わらない見張り台を提供する。
  • Citation Pack(citations/.json):記事ごとの出典・ライセンス情報を束ねた「引用キット」。llms-full も agent-manifest も Agent Skills 本文も、口を揃えて「引用・再利用の前にこの Citation Pack を見よ」と誘導する。再配布の可否をエージェントが機械的に確認できるようにした。

貫いているのは、どの入口でも記事ごとの license_note(full / summary-only / link-only)を最優先する、という原則だ。看板を増やしても、配ってよいものと伏せるべきものの線引きは全レイヤで一致させる。この一貫性は前回からの背骨で、入口が増えるほど効いてくる。

影響・今後:GEO の測定基盤としての自宅ポータル

こうして「露出(discovery)→ 制御(robots / ai.txt)→ 計測(analytics)」の3層がそろった。個人運用の小さなポータルでも、GEO の世界でいう「どの AI クローラに、どのコンテンツが、どれだけ届いているか」を、外部 SaaS に頼らず自前で観測できる土台になった。portal_popular を通せば、その観測結果をエージェント自身にフィードバックする輪も閉じている。

一方で、意図的に見送っているものも変わらない。Web Bot Auth の署名は、公開鍵ディレクトリの生成コードだけ opt-in で用意しつつ、鍵の実生成と有効化は将来のクローラーコンテナ着手時に回している。OAuth / OIDC の類も、実体としての認可基盤を持たないうちにメタデータだけ出すのは不誠実なので出さない。「実装の伴わない標識は掲げない」というのは、計測層を足した今も守りたい線だ。

次に見たいのは、この計測が集めた agents 別・pages 別の実データそのものだ。どのエージェントが本当に来ているのか、link-only に絞ったソースと full 配布のソースで retrieval にどんな差が出るのか。数字が溜まってきたら、その分析をまた記事にしたい。

まとめ

  • 前回の「discovery で見つけてもらう / MCP で配る」に対し、今回は「取得を測る」層(analytics-api)と、露出面の拡張(agents.txt / ai.txt / Citation Pack / topics・entities 監視ページ)を足した。
  • analytics-api はコンテンツ DB と分離した独立サービスで、nginx ログ(syslog)と MCP portal_get を同じ台帳に集約。公開ランキング /api/popular と MCP portal_popular、運用者向けの LAN 限定詳細 API を提供する。
  • プライバシーは「生 IP・トークン・クエリを保存しない / HMAC で匿名化 / 90日で削除」を徹底しつつ、エージェントは User-Agent から名指しで分類(ただし自己申告として過信しない)。
  • 露出は agents.txt / ai.txt / Citation Pack まで広げたが、どの入口も記事ごとの license_note を最優先する一貫性は不変。Web Bot Auth 署名は引き続き意図的に保留。

「見つけてもらう」から「使われたかを測る」へ。エージェント向けサイト運用の重心が、少しだけ次の段階に動いた1週間だった。

参考

参考ソース(外部)

関連記事(当ブログ内)

関連記事