ページ上のエージェントツールはSPAマウント前に同期登録し、リソースidの解決は共有promiseで遅延する
WebMCPのnavigator.modelContextのように、ページ自体がAIエージェント向けツールを公開するWebアプリでは、ツール登録をReact/Vue等のフレームワークのmount完了後(例: useEffect内)に行うと、ホスト(ブラウザ/エージェントアプリ)がページ読込み直後にツール一覧を問い合わせた時点でまだ何も登録されておらず、「No tools available」と判定される。
原因: フレームワークのmountはネットワーク往復(例: リソース作成API呼び出し)を含む非同期処理の後にやっと完了することがあり、ホストの「ツールは存在するか」の判定タイミング(例: DOMContentLoaded直後)と一致しないことがある。ツール登録自体は本来即時にできる同期処理なのに、フレームワークのライフサイクルに引きずられて遅されてしまう。
適用条件: 「ページ読込み直後の商進判定に間に合う必要がある」ツール/能力公開に限って重要。単なるUI部品の初期描画ならマウント後で問題ない。
対処方法: (1) ツール登録自体はフレームワークのmountより前のモジュール評価タイミング(例ReactならcreateRoot(...).render()を呼ぶ前)で同期実行し、ネットワーク/マウントを一切待たない。(2) 登録時に必要な実行時専用リソース(例: ページ固有のID)は登録時に確定させず、各ツールの実行時に遅延解決する。(3) その遅延解決はモジュールスコープの単一promiseでメモ化し、ツール実行とフレームワーク自身の初期化ロジックの両方が同じpromiseを待つように一元化する(別々の経路が別々にリソースを作成する二重作成バグを構造的に防ぐ)。(4) ツール実行結果をフレームワークのUI状態へ反映するコールバックは、登録時点では未接続でよく、後からsetter関数で接続する方式にし、未接続時の実行はUI更新をスキップするだけで成功扱いにする。
確認方法: ホスト側の最小シム(registerToolを受け取って配列に記録するだけのモックオブジェクト)をpage.addInitScript相当で事前注入し、domcontentloadedイベントの瞬間に登録済みツール数を問い合わせる自動テストで検証できる。さらに、ルート直開き直後にツールを直接呼び出してリソース作成系のエンドポイントを叩き、バックエンドの行数が(1回のページ読込みで)ちょうど+1だけ増えることを確認すれば、ツール実行経路とフレームワークの自己初期化経路が二重作成していないことを実証できる。
教訓: 「WebMCPツールをReactコンポーネントの一部として登録すればよい」という直感は、フレームワークのmountタイミングとホストのツール存在判定タイミングが一致する保証がない限り成り立たない。即時性が求められる公開面(登録)と、遅延してよい実行面(リソース解決・UI接続)を分けて設計するのが安全。