# satotek.dev > nosuke の個人ブログ・技術メモ。以下は全公開記事の本文をまとめたものです。 # Pixel 11 で 5G の通信状態を可視化するアプリを作った URL: https://satotek.dev/posts/cellscope-pixel-5g Date: 2026-10-24 Category: 技術 Tags: Android, Kotlin, Jetpack Compose, Material 3 Expressive, 5G, Pixel Pixel 11 を買ってから、5G の接続状態をもう少し詳しく見たくなりました。 Android には NetMonster のように電波情報を確認できるアプリがあります。ただ、実際にどの周波数帯を使っているのか、Carrier Aggregation(CA)がどう組まれているのかまで見ようとすると、少し物足りません。 こうした用途では Network Signal Guru(NSG)が有名ですが、残念ながら Pixel 11 では使えません。 そこで、Android から取れる情報をできる限り可視化するアプリ「CellScope」を作りました。 ソースコードは GitHub で公開しています: [satotek/cellscope](https://github.com/satotek/cellscope) ## Network Signal Guru が使えない Network Signal Guru は、スマートフォンのモデムから詳細な無線情報を取得できる Android アプリです。 一般的な電波情報アプリでも RSRP や SINR、接続バンドくらいは確認できます。NSG は対応端末であれば、RRC や NAS のシグナリングメッセージといった、さらに下のレイヤーの情報まで見られます。バンド固定のような操作もできます。 これは、Qualcomm の DIAG のようなモデムの診断インターフェースを使って、モデム内部の情報を直接読んでいるからです。NSG は Qualcomm のほか、MediaTek Dimensity などにも対応していますが、どれもチップや端末ごとの対応が前提です。 一方、Pixel 11 は Google Tensor G6 を搭載していて、モデムは MediaTek 製です(Pixel 10 までは Samsung の Exynos モデムでした)。執筆時点では、Pixel 11 は NSG の対応端末に入っていません。 モデムの診断インターフェースに届かない限り、root 化しても NSG と同じ情報は取れません。アプリ側の工夫ではどうにもならない部分です。 NSG と同等のものを一から作るのは現実的ではありません。ただ、Android の Telephony API と、root 権限で読めるシステム情報を組み合わせれば、日常的に電波を観察するのに必要な情報はそれなりに取れそうです。 それなら自分で作ってみよう、というのが今回のきっかけです。 ## CellScope でできること CellScope は、Android のセルラー通信の情報を表示・記録するアプリです。 Pixel 11 で使うことを前提にしていて、接続中のセルの情報だけでなく、CA 構成や電波品質の推移、通信速度までまとめて確認できます。 主な機能は次のとおりです。 - **セル情報**: LTE / NR の RSRP、RSRQ、SINR、PCI、バンド、周波数など - **CA 構成**: LTE と NR それぞれの Primary / Secondary Component Carrier と帯域幅 - **電波の履歴**: RSRP、RSRQ、SINR、近隣セルの時系列グラフと、セル変更イベント - **通信**: スループットと Ping の推移、NDT7 による速度測定 - **統計**: 在圏バンドの比率や、電波品質の分布 - **ログ**: CSV / JSONL への記録と、あとからの再生 - **スナップショット**: 地図と通信状態を 1 枚にまとめたカード画像の保存 - **訪問セル**: これまでに接続したセルを端末内に記録 - **PiP・オーバーレイ**: 他のアプリを使いながら通信状態を確認 - **無線制御**: 5G SA のみ、LTE のみといった通信方式の固定 デュアル SIM にも対応していて、ホーム画面で両方の SIM の状態を並べて見られます。 ただし、これらすべてが通常の Android アプリの権限で取れるわけではありません。後で書くように、使える権限によって表示できる項目が変わります。 {/* 画像2(任意): ホーム画面をライトテーマで 1 枚。あれば機能紹介のここに置く */} ## Kotlin + Jetpack Compose でネイティブアプリとして作る CellScope は Kotlin と Jetpack Compose で書いた、Android のネイティブアプリです。 普段は Web まわりの技術を触ることが多いので、React Native や Flutter のようなクロスプラットフォームのフレームワークを使う選択肢もありました。 ただ、今回は最初から Pixel 11 で使うことが目的で、iOS 対応は必要ありません。それに、CellScope は `TelephonyManager` や `TelephonyCallback` といった Android 固有の API を大量に使います。特権 API や root 権限を使う処理もあるので、クロスプラットフォームのフレームワークを選んでも、結局ネイティブのコードを書く部分が大半になりそうでした。 普段あまり触らない Kotlin でも踏み切れたのは、AI コーディングツールのおかげです。慣れている技術より、作りたいものに合った技術を選べるようになりました。ただ、非公開 API や端末依存の挙動は AI に聞いても分からないことが多く、結局は実機で確かめるしかありませんでした(このあたりは後述のハマりどころにも出てきます)。 ### UI は Jetpack Compose UI は Jetpack Compose で書いています。 以前の Android 開発では XML でレイアウトを定義するのが一般的でしたが、Compose では Kotlin のコードで UI を宣言的に書けます。状態に応じて UI が再構成されるという考え方は React と似ていて、Web 側から来た身としては馴染みやすいものでした。 CellScope は、接続方式や電波品質、接続セルなど、時間とともに変わる情報を大量に扱います。取得した通信情報を状態として持ち、それに合わせて UI が組み直される Compose とは相性が良いと感じました。 一方で、電波情報はほぼ毎秒更新されるので、何も考えないと不要な再コンポジションが走ります。状態をどこで分割するか、いつ更新するかには気を配る必要がありました。 ## Material 3 Expressive で Pixel らしい UI にする せっかく Pixel 向けに作るなら、見た目もできるだけ Pixel に馴染ませたいと考えました。 そこで採用したのが Material 3 Expressive(M3E)です。Material Design 3 を発展させたデザインシステムで、形状やタイポグラフィ、モーションがより表現豊かになっています。Pixel のシステム UI とも揃っているので、標準アプリに近い雰囲気にできます。 CellScope では、トーナルなカードや `ShortNavigationBar`、`ButtonGroup` を使ったトグルなどを Compose の Material 3 コンポーネントで組んでいます。M3E の部品は執筆時点ではまだ Material 3 のベータ版にしか入っていないので、Compose BOM もベータ版を使っています。 配色は Material You のダイナミックカラーに対応させていて、壁紙に合わせてアプリの色が変わります。ただ、RAT(通信方式)の色や、電波品質の良い / 普通 / 悪いを表す色は、意味を持つ色として固定しています。  個人的には、ツール系のアプリでも見た目や操作感は大事だと思っています。 電波情報アプリはどうしても数値だらけになりがちですが、すべての値を同じ大きさで並べると、かえって必要な情報が見つけにくくなります。そこで、RSRP のように頻繁に見る値は大きく表示し、詳細なセル情報や統計は別の画面に分けました。数値も単に並べるだけでなく、色やグラフで状態がひと目で分かるようにしています。 M3E のコンポーネントを使うことで、独自のデザインシステムを一から作らずに、Pixel らしい UI に仕上げられました。 ## Android からどこまで通信情報を取得できるのか ### 公開 API で取れるもの Android には、モバイル通信の情報を取得するための `TelephonyManager` があります。 たとえば `CellInfo` を使うと、接続中のセルや周辺セルの情報が取れます。LTE なら RSRP、RSRQ、RSSNR、PCI、TAC、EARFCN などが代表的です。5G NR でも、SS-RSRP、SS-RSRQ、SS-SINR、NR-ARFCN などが取れる場合があります。 CellScope では、EARFCN や NR-ARFCN から 3GPP の規格表をもとにバンドと周波数を計算して表示しています。モデムがバンドを直接返してくれる場合はそちらを優先します。 更新は `TelephonyCallback` で受け取っています。セル情報、電波強度、`ServiceState`、表示用のネットワーク種別などのリスナーを登録して、変化があれば UI に反映します。 ちなみに、`CellInfo` は位置情報の権限がないと空のリストが返ってきます。電波情報アプリなのに位置情報の許可を求めるのはこのためです。 ### ステータスバーの「5G」と実際の接続は別物 5G には大きく分けて NSA(Non-Standalone)と SA(Standalone)があります。NSA は LTE をアンカーにして NR を組み合わせる方式、SA は LTE を使わずに NR と 5G Core だけで通信する方式です。 少しややこしいのが、ステータスバーの「5G」表示と、実際の NR の接続状態が必ずしも一致しないことです。 ステータスバーの表示に使われる `TelephonyDisplayInfo` は、あくまでユーザー向けの表示のための情報です。`NR_NSA` になっていても、それは「このエリアで 5G が使える」という意味でしかなく、いま NR で通信していることの根拠にはなりません。 そこで CellScope では、データ通信の RAT が NR なら SA、鮮度のある NR のセル情報や物理チャネルの情報があれば NSA で接続中、と判定しています。どちらの根拠もない場合は、「5G 利用可」「制限あり」「不明」のどれかとして表示します。 下の 2 枚は、どちらも povo の SIM を表示しているホーム画面です。左は LTE の B3 をアンカーに n77 を束ねた NSA、右は n77 だけで繋がっている SA です。CellScope ではこのように、NSA なのか SA なのか、どのバンドを束ねているのかまで区別して表示しています。 、右: SA(n77)") また、`ServiceState` の `toString()` には、`nrState` や `isEnDcAvailable` といった非公開フィールドが含まれています。root なしでこれらを読む方法は、この文字列をパースするくらいしかありません。しかも Android 17 では `nrState` がマスクされるようになったので、他の値から推定しています。OS のアップデートで壊れやすい部分です。 ### Carrier Aggregation の構成を取る 今回、特に見たかったのが CA の構成です。 たとえば LTE の B3 に接続していると分かっても、それだけでは他にどの Component Carrier を束ねているのかは分かりません。 Android には `PhysicalChannelConfig` という、物理チャネルの構成を表すクラスがあります。チャネル番号や帯域幅、Primary / Secondary の区別など、CA 構成を知るのに必要な情報がそろっています。 ただし、これを取得する API には権限の制限があり、普通のサードパーティアプリからは使えません。 そこで root 権限を使うことにしました。 ## root と priv-app で取れる範囲を広げる CellScope は、使える権限に応じて 3 段階で動作します。 | 段階 | 権限 | 主に取れる情報 | | -------- | --------------------- | ------------------------------------------------------------------------------ | | 通常 | 電話の状態 + 位置情報 | セル情報、電波品質、接続バンド、5G の状態など | | root | 上記 + `su` | `dumpsys` 経由の物理チャネル構成(CA)、IMS の登録状態、PDN ごとの接続情報など | | priv-app | 上記 + 特権権限 | 物理チャネル構成の更新通知、VoNR の状態、通信方式の固定 | 通常モードは Android の公開 API だけを使います。 root モードでは `su` 経由で `dumpsys telephony.registry` などを実行し、その出力をパースして、通常のアプリでは取れない情報を読みます。CA の構成もここから取っています。 さらに、KernelSU Next のモジュールとしてアプリを priv-app 化する仕組みも作りました。アプリ内の設定から有効化して再起動すると、CellScope が `/system/priv-app` に入った状態で起動します。 モジュールはアプリ自身が書き出すので、KernelSU Next のモジュール一覧にはこのように並びます。  priv-app になると `READ_PRIVILEGED_PHONE_STATE` などの特権権限が使えるので、`PhysicalChannelConfigListener` で CA 構成の変化をイベントとして受け取れます。`dumpsys` を定期的に叩いてポーリングする必要がなくなるのが利点です。 また、`setAllowedNetworkTypesForReason` を使った通信方式の固定もできるようになります。5G SA だけを使いたいとき(NSA は LTE のアンカーが必要なので、このモードでは使われなくなります)や、LTE に固定して比べたいときに便利です。ただし、実際にどの方式で繋がるかは、モデムやキャリア側の設定にも左右されます。 ### priv-app 化でハマったところ priv-app 化まわりは、思ったよりハマりました。 1 つ目は KernelSU Next のモジュールの仕組みです。KernelSU Next 3.x では `system/` のマウントが「メタモジュール」に任されていて、メタモジュールを入れていないとモジュールの中身がマウントされません。そこで、モジュール側で自前のマウント処理を持たせ、tmpfs に置いてから overlayfs で `/system/priv-app` に重ねるようにしました。`/data` が casefold の f2fs なので、overlayfs の下層に直接使えなかったのが tmpfs を挟んでいる理由です。 2 つ目は `PhysicalChannelConfigListener` です。必要な権限が足りないと、登録が黙って失敗し、最初の 1 回分の値だけが届いて以降は更新されない、という分かりにくい挙動になります。CA の表示が変わらないので、しばらく原因が分かりませんでした。 さらに、このリスナーを単独で登録すると、PCI が「不明」に伏せられた状態で届きます。位置情報の権限チェックが、位置に関係するイベントを含む登録のときにしか行われないためで、`CellInfoListener` と同じコールバックに同居させることで解決しました。 どちらもドキュメントには書かれておらず、実機で動かしてログを見ながら探るしかない部分でした。 ## root 化しても取れない情報はある ここまで作ってみて分かったのは、root 権限があっても取れない情報があることです。 たとえば、NSG のように RRC や NAS の L3 メッセージを見ることはできません。バンドやセルの固定も、Android にそのための API がなく、Pixel 11 では実現できません。 Android Framework から見える情報と、モデムの中だけで管理されている情報の間には、はっきりした境界があります。root 化で Android 側の権限制限は回避できても、モデムが外に出していない情報が見えるようになるわけではありません。 ここが CellScope の限界です。 ## 実際に Pixel 11 で使ってみる CellScope では、いまの通信状態だけでなく、その変化を時系列で追えるようにしています。 同じ場所にいても、接続セルの切り替えや CA 構成の変化で通信品質は変わります。RSRP や SINR をグラフにし、セル変更のタイミングを縦線で重ねることで、どこで状態が変わったのかを追いやすくしました。グラフをタップすると、その時点の接続セル、NR の Secondary Cell、スループット、近隣セルなどを詳しく見られます。 下は povo で記録した 2 分間です。15:10 台の後半、B21 から n77 の SA に移ったあと、数秒おきに n77 と LTE の B1 を行ったり来たりしているのが黄色の破線で分かります。右はグラフをタップして開いた詳細で、前後 30 秒のセル切り替えが一覧で見られます。  通信速度や Ping も一緒に記録しているので、電波の品質と実際の通信性能を見比べることもできます。 速度測定には M-Lab の NDT7 を使っています。下は IIJ の SIM で測ったときの画面で、下りはおよそ 110 Mbps でした。測定中のスループットはグラフにもそのまま残ります。  ログは CSV と JSONL で書き出していて、あとから読み込んで再生できます。再生画面ではピンチで時間軸を拡大できるので、移動中に記録したログを見返すときに便利です。 気になる場所では、スナップショットで地図と通信状態を 1 枚の画像に残せます。これは IIJ(ドコモ回線)の SIM で撮ったもので、LTE の B1・B21・B42・B42 の 4 波、合計 70 MHz の CA が組まれていました。  ただし、Android が返すセル情報には更新周期や端末側の実装による制限があるので、常にモデムの最新の状態が見えているとは限りません。特に NR の Secondary Cell は、情報が取れなかったり、実際の接続状態と表示がずれたりすることもあります。 {/* TODO: povo やスリランカでの測定結果を別記事にする場合は、ここからリンクする */} ## NSG の代わりになるのか 結論として、CellScope は NSG の完全な代わりにはなりません。 NetMonster と NSG に並べると、だいたい次のような位置づけです(CellScope は Pixel 11 で使った場合、ほかの 2 つは対応端末で使った場合の、執筆時点で私が把握している範囲です)。 | 機能 | NetMonster | CellScope | NSG | | ------------------------------ | ------------------ | -------------------------------------------- | ------------ | | root | 不要 | 不要(root / priv-app で取れる範囲が広がる) | 基本的に必要 | | Pixel 11 で使えるか | 使える | 使える | 非対応 | | 接続セル・近隣セル・電波品質 | ○ | ○ | ○ | | CA 構成 | 端末が公開する範囲 | root 以上で PCC / SCC と帯域幅まで | ○ | | 時系列グラフ | ○ | ○ | ○ | | ログの記録 | 接続したセルの記録 | CSV / JSONL に記録して再生 | ○ | | 基地局の地図 | ○ | ×(現在地のスナップショットのみ) | — | | 速度測定 | × | ○(NDT7) | ○ | | 通信方式(RAT)の固定 | × | ○(priv-app) | ○ | | バンド・セルの固定 | × | × | ○ | | RRC / NAS などの L3 メッセージ | × | × | ○ | NSG が対応モデムの診断インターフェースから取る情報と、CellScope が Android Framework を通して取る情報には大きな差があります。RRC / NAS のシグナリングメッセージのように、CellScope では取れない情報もあります。 一方で、普段使っている Pixel 11 で接続バンドや CA 構成、電波品質の推移を確認する、という用途であれば、必要な情報はかなり集められるようになりました。 電波状態の表示から履歴の記録、速度測定までを一つのアプリで完結できるのは、自作したからこそのメリットだと感じています。 ## Play ストアには出していない 本当は Play ストアで公開したいのですが、今のところは GitHub でソースを公開するだけにしています。個人開発者にとって、公開までのハードルが上がっているためです。 2023 年 11 月以降に作った個人の開発者アカウントでは、本番公開の前に、12 人以上のテスターに 14 日間続けてクローズドテストに参加してもらう必要があります。自分用に作ったツールで、テスターを 12 人集めて 2 週間付き合ってもらうのはなかなか大変です。 アプリの性質の問題もあります。CellScope は電話の状態や位置情報の権限を使うので、審査ではそれぞれの用途の説明が求められます。root や priv-app を前提にした機能も、ストアで配るアプリとしてはかなり扱いにくい部類です。 さらに Android では、Play ストア以外から入れるアプリにも開発者の本人確認を求める仕組みが始まっていて、2027 年以降は世界的に広がる予定です。サイドロードで配る場合でも、いずれ同じような手続きは避けられなさそうです。 {/* TODO: Play ストア公開について自分の言葉で一言(今後の予定など)あれば足す */} ## おわりに Pixel 11 で Network Signal Guru が使えないことをきっかけに作り始めた CellScope ですが、Android の Telephony API や root 権限、モデムと Android Framework の関係など、想像していた以上にいろいろなことを調べる機会になりました。 普段使っている Pixel に馴染む見た目で、自分が欲しかった機能を形にできるのは、個人開発ならではの面白さだと思います。 まだ取れない情報や端末依存の挙動もありますが、引き続き改善していくつもりです。 ソースコードはこちらです: [satotek/cellscope](https://github.com/satotek/cellscope) ## 参考 - [TelephonyManager | Android Developers](https://developer.android.com/reference/android/telephony/TelephonyManager) - [PhysicalChannelConfig | Android Developers](https://developer.android.com/reference/android/telephony/PhysicalChannelConfig) - [Material 3 Expressive | Material Design](https://m3.material.io/blog/building-with-m3-expressive) - [Jetpack Compose の Material Design 3 | Android Developers](https://developer.android.com/develop/ui/compose/designsystems/material3) - [Network Signal Guru](https://play.google.com/store/apps/details?id=com.qtrun.QuickTest) --- # WebMCP というものを試してみた URL: https://satotek.dev/posts/webmcp-blog-tools Date: 2026-08-30 Category: 技術 Tags: WebMCP, AI, Chrome, TypeScript, MCP 最近、WebMCP というものを知りました。Web サイトが AI エージェント向けにツールを公開できる仕組みだ、というところまでは分かったのですが、実際に組み込んだときの使い心地や、どのくらいの実装で動くのかは、触ってみないと分かりません。 そこで、このブログにツールを登録して、Chrome と Codex から呼び出してみました。目指したのは、エージェントにこう頼めるようにすることです。 > このブログで WebMCP について書いた記事を探して、最初の記事を開いて 画面を見て検索フォームを探し、文字を入力し、リンクをクリックする代わりに、ページが公開したツールを直接呼び出す。これが本当に動くのかを確かめます。 ブログを AI 対応サービスに仕立てるのが目的ではありません。どのくらいの実装で何ができるのか、DOM を直接操作する場合と何が変わるのかを知ることが目的です。 ## WebMCP とは [WebMCP](https://developer.chrome.com/docs/ai/webmcp?hl=ja) は、Web サイトが AI エージェント向けの構造化されたツールを公開するための、提案段階の Web 標準です。 普通のブラウザ操作では、エージェントはまず DOM を読みます。ボタンのラベルや入力欄の位置から「これは検索ボタンだろう」と推測して操作するので、画面が少し変わっただけで失敗することがあります。 WebMCP では、ページ側が「このページには `search_posts` というツールがあり、`query` という文字列を受け取る」と宣言できます。エージェントは見た目ではなく、ツール名・説明・JSON Schema を見て機能を呼び出します。 ### 通常の MCP との違い 一番の違いは、ツールがどこにあるかです。 ```text 通常の MCP AI エージェント ── MCP ── 外部サーバー / API WebMCP AI エージェント ── ブラウザ ── Web ページが公開したツール ``` Chrome の [MCP と WebMCP の比較](https://developer.chrome.com/docs/ai/webmcp/compare-mcp?hl=ja)にまとまっていますが、そこから派生する違いがいくつかあります。 | | 通常の MCP | WebMCP | | ------------ | --------------------------- | ----------------------------------- | | 実行場所 | 任意のプラットフォーム | ブラウザのみ | | 定義方法 | JSON-RPC と各言語の SDK | JavaScript または HTML 属性 | | 寿命 | 常駐(サーバー / デーモン) | タブに紐づき一時的 | | コンテキスト | ヘッドレス・外部から | DOM、Cookie、ログイン中のセッション | | ツールの発見 | エージェント側の登録フロー | ユーザーがページを開いた時点 | このうち、実装していて意識させられたのは下の 2 つです。 **ツールはタブと一緒に消えます。** サーバーとして常駐する MCP と違い、WebMCP のツールはページが開いている間しか存在しません。ユーザーがタブを閉じたり別のサイトへ移動したりすれば、エージェントはもう何も呼び出せません。 これは今回の `open_post` で実際に起きます。実装は `window.location.assign()` によるフル遷移なので、記事を開いた瞬間に元のページはアンロードされ、ツールは一度破棄されます。遷移先で登録モジュールが再実行され、あらためて 3 つのツールが登録される。エージェントから見れば同じツールが並んでいるだけですが、実体は別のページのものです。 **認証を別に用意する必要がありません。** ツールはユーザーが開いているそのページで動くので、ログイン済みのセッションや Cookie をそのまま使えます。MCP サーバーのように API キーを発行して権限を設計する、という手順が要りません。 便利な反面、これは「エージェントがユーザーの権限で操作できる」ということでもあります。だからこそ、入力の検証をページ側できちんと行う必要があります(後述)。 そして、この 2 つは対立するものではありません。Chrome のドキュメントも両者を組み合わせる形を勧めています。バックグラウンドで完結する処理や、ブラウザを開いていなくても動いてほしい処理は MCP サーバーへ。画面を開いている文脈でこそ意味がある操作は WebMCP へ、という住み分けになります。 Web サイトをバックエンドの MCP サーバーに置き換えるものではなく、ページ自身が、そのページで今できることをブラウザへ公開する仕組みです。 ### RSS や llms.txt との違い WebMCP を入れたからといって、RSS や Markdown の配信が不要になるわけではありません。役割が違います。 | 仕組み | 役割 | | ---------------------------------- | --------------------------- | | HTML / RSS / Markdown / `llms.txt` | AI がサイトの**情報を読む** | | WebMCP | AI がサイトの**機能を使う** | 記事本文を要約したいだけなら、RSS や Markdown、`llms.txt` のほうが扱いやすい場面もあります。反対に「このサイトの中から条件に合う記事を探して開く」という操作は、ツールとして表現したほうが意図を伝えやすい。このサイトでは、コンテンツの配信とエージェント向けの操作を別の層として持たせるのがよさそうです。 なお、まだ実験的な API なので、通常のサイト機能を置き換えるのではなく、対応ブラウザやエージェントがあるときだけ機能が増える progressive enhancement として扱うのが現実的です。 ## ブログで試す題材を決める WebMCP の例としては、予約フォームやショッピングカートのような「人間が操作すると複数の手順が必要な機能」が分かりやすいと思います。個人ブログに同じ必然性はありません。記事を読むだけなら HTML でも RSS でも足ります。 それでも題材として選んだのは、ブログには「記事を探す」「条件で絞り込む」「見つけた記事を開く」という、ツールにしやすい操作が揃っているからです。検索結果から記事を開くところまで一つの会話でつながれば、感触をつかむには十分です。WebMCP の記事を WebMCP で試せるのも面白そうでした。 試す機能は、読み取り系を中心に 3 つへ絞りました。最初から本文の要約や推薦まで入れると、仕組みを試しているのかツールを作り込んでいるのか分からなくなるからです。 | ツール | 引数 | 何をするか | | -------------- | ----------------------------- | ---------------------------------------------- | | `search_posts` | `query`, `limit?` | 記事のタイトル・説明・カテゴリ・タグを検索する | | `list_posts` | `category?`, `tag?`, `limit?` | カテゴリやタグで絞り込んだ記事一覧を返す | | `open_post` | `slug` | 現在のタブで記事を開く | `search_posts` と `list_posts` は、記事本文全体ではなく、エージェントが次の判断に使いやすい短いメタデータだけを返します。 ```json { "slug": "nix-flakes-dotfiles", "title": "dotfiles を Nix Flakes で管理している話", "url": "http://localhost:3000/posts/nix-flakes-dotfiles", "date": "2026-07-04", "category": "技術", "tags": ["Nix", "Home Manager", "nix-darwin"] } ``` `open_post` は、検索や一覧で返された slug を受け取って現在のタブで記事を開きます。任意の URL を受け取って遷移するのではなく、公開済み記事の一覧に slug が存在するか確認してから遷移します。 記事検索は、記事本文の Markdown を毎回解析しているわけではありません。このサイトではビルド時に Markdown から記事のサマリを生成していて、ツールはそのサマリをブラウザ上で検索します。 ```text Markdown ↓ ビルド時 記事サマリ(summaries.json) ↓ postRepository ↓ WebMCP tools ├─ search_posts ├─ list_posts └─ open_post ``` ## 宣言的 API と命令的 API WebMCP がツールを公開する方法は 2 つあります。 **宣言的 API(Declarative API)** は、既存の HTML フォームに属性を足すだけでツールにします。JavaScript は書きません。 ```html
``` `