mcpField
S
service-discoverability

service-discoverability

github.com/dfinity開発3980 回インストール今日更新
リポジトリ

service-discoverability について

Internet Computer の canister アプリを、AI エージェントがアプリ URL から発見して利用できるようにする開発スキルです。アプリ構成、インターフェース、データ、本人性など5層の公開方法を扱い、ICP MCP への参加には運営者の同意が必要です。

主な機能

  1. 01canister 構成マニフェストの公開
  2. 02Candid インターフェースメタデータの提示
  3. 03getApiDoc による動作の文書化
  4. 04OQL スキーマとクエリメソッドの提供
  5. 05デプロイ時のネットワーク別ファイル生成
  6. 06Internet Identity 派生情報の発見支援

活用シーン

  1. 01URL からの canister アプリ発見
  2. 02エージェント向け canister メソッドの文書化
  3. 03アプリデータの構造化クエリ
  4. 04ICP MCP 参加に向けたアプリ準備

MCP 対応

有効な `/.well-known/ic-architecture` マニフェストによってアプリが ICP MCP に参加し、読み取りや呼び出しの対象となる canister が定まる仕組みを説明します。公開前に、運営者が ICP MCP App Operator Terms に同意する必要があります。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dfinity/icskills --skill service-discoverability

インストール後、Claude Code で/service-discoverabilityを入力して読み込まれたか確認します。

詳細

サービスの発見可能性

概要

AI エージェントにアプリの URL(例:https://yourapp.com)だけを渡した場合、エージェントが残りを自力で把握できる必要があります。つまり、アプリを構成するカナリ、各カナリの役割、呼び出し方法、データのクエリ方法、サインイン済みユーザーとして操作する方法を理解できる必要があります。人間がカナリ ID を提供したり、専用の統合を用意したりする必要はありません。

このスキルでは、カナリアプリがこれを可能にするために公開する情報を、5 つのレイヤーにわたって扱います。レイヤー 1 は制限付きです。これはアプリが ICP MCP に参加するために公開する情報であり、公開すると運営者に代わって合意を受諾することになります。このファイルを作成する前に、レイヤー 1 の前にをお読みください。レイヤー 2〜5 には制限がなく、個別に導入できます。それぞれ単独でも役立ち、組み合わせることでアプリをエージェント対応にできます。

レイヤー答える質問仕組み
1. 構成このアプリを構成するカナリはどれで、それぞれの役割は何ですか?/.well-known/ic-architecture JSON マニフェスト
2. インターフェースカナリが公開しているメソッドと型は何ですか?candid:service メタデータ
3. 動作実際にはどのように動作しますか(単位、ライフサイクル、注意点など)?getApiDoc クエリメソッド
4. データ質問ごとにメソッドを用意せずに、データをクエリするにはどうすればよいですか?OQL の schema + execute クエリメソッド
5. ID適切なプリンシパルで、サインイン済みユーザーとして操作するにはどうすればよいですか?/.well-known/ii-derivation-origin ファイル

レイヤー 1 と 5 で重要なのは、デプロイ時に well-known ファイルを生成することです。カナリ ID(およびメインネットのオリジン)はネットワークごとに異なるためです。このスキルでは、@dfinity/static-site レシピの presync フックを使って生成します。これは、コミュニティのサンプルである raymondk/demo-ic-architecture で紹介されているのと同じパターンです。

レイヤー 1 の前に:オペレーターは ICP MCP アプリオペレーター規約に同意する必要があります

/.well-known/ic-architecture を作成、生成、またはデプロイする前に、ここでいったん止まってください。 このファイルは単なる設定ではありません。ICP MCP を通じた利用は、ICP MCP アプリオペレーター規約(バージョン 1.1、発効日 2026-09-02、このスキルの作成時点での最新版)に準拠します。この規約は、DFINITY Stiftung(「DFINITY Foundation」)とアプリのオペレーターとの間の合意です。そして、有効なマニフェストを公開する行為そのものが、規約への同意となり、アプリを参加対象にする行為です。署名の手続きも、チェックボックスも、それ以外の手続きもありません。エージェントが独自の判断でビルドにマニフェストを追加すると、ユーザーに確認せず、ユーザーを契約に同意させることになります。

ユーザーには、同意する規約のバージョンを伝えてください。規約のページ上部を確認してから伝えてください。規約のバージョンはこのスキルとは別に管理されているため(セクション 14)、ページに上記より新しいバージョンが表示されている場合は、ページの情報が正しく、このスキルは古くなっています。ページに表示されているバージョンをユーザーに伝え、このスキルの記載が古いため修正が必要だと説明してください。

以下の両方についてユーザーの確認を得るまで、公開してはいけません。 それぞれの内容を自分の言葉で説明し、規約へのリンクを示し、ユーザーが読んでから回答できるようにしてください。

1. ユーザーがオペレーターである、またはオペレーターを法的に拘束する権限を持っている。 オペレーターとは、アプリを運用し、そのキャニスターを申告する法的権限を持つ個人または法人です。アプリを構築することと、運用することは同じではありません。セクション 1 には次のように記載されています。「アプリケーションを開発しても運用していない場合、これらの規約に同意するのはあなたではなく、そのオペレーターです。」 他者のアプリに携わる請負業者、代理店、従業員は、公開して先に進むのではなく、判断をオペレーターに委ねてください。

2. 公開によって許可される内容を受け入れる。 セクション 2 に基づき、参加すると、ICP MCP サーバーは、Internet Identity を通じて AI アシスタントを認可したユーザーに代わって、次のことを行えるようになります。

  • マニフェスト、マニフェストで宣言されたキャニスターの candid:service メタデータ、および getApiDoc / get_api_doc が返す情報を読み取る。
  • そのメタデータをインデックス化してキャッシュし、参加アプリの公開一覧などで、アプリとその API についてアシスタントやそのユーザーに説明する。
  • 宣言されたキャニスターに対して、クエリー呼び出し、および操作を認可したユーザーについては状態を変更する(更新)呼び出しを送信する。これらの呼び出しには、各ユーザーのアプリケーションごとの Internet Identity ID を使用して署名する。
  • 上記の情報を最新の状態に保つため、アプリの公開ファイルをその配信元から取得する。

参加は双方向とも無料です。DFINITY は参加料金を請求せず、参加に対して何も支払いません。

マニフェストが切り替えの役割を果たし、その対象範囲のすべてです
  • 対象となるのは、宣言されたキャニスターのみです。 ICP MCP は、有効なマニフェストに記載されたキャニスターに対してのみ、キャニスターのメタデータを取得し、クエリまたは更新呼び出しを送信します。運用者が運用していない、または公開する権限のないキャニスターは、決して記載しないでください。セクション 5 は、ファイルが提供されるたびに繰り返し示される継続的な表明であり、宣言されたすべてのキャニスターが運用者自身によって、または運用者の権限の下で運用されていることを示します。
  • 有効なマニフェストがなければ、これらの処理は一切行われません。 ICP MCP は引き続きアプリの公開ウェブサイトを取得して説明することがありますが、メタデータの取得とキャニスターへの呼び出しは拒否されます。
  • マニフェストを削除すれば、登録の有無にかかわらず、停止スイッチになります。 ICP MCP が削除を確認すると、新たなメタデータの読み取りとクエリ/更新呼び出しは停止します。これは、オリジンまたは宣言されたキャニスターが侵害された場合の、最も迅速な緩和策でもあります(セクション 7)。ただし、ICP MCP による公開ウェブサイトの取得や公開情報の説明は停止しません。それらについては、[email protected] まで連絡し、参加の一時停止を DFINITY に依頼してください。
  • 無効化は、契約の終了とは異なります。 マニフェストを削除すると参加は終了しますが、契約の終了には別途、[email protected] への通知が必要です(セクション 11)。いずれも、Internet Computer 上ですでに実行された呼び出しを取り消すものではありません。また、セクション 8、12、13、15(個人データ、保証、責任、準拠法)は終了後も有効です。参加をやめる方法を尋ねる運用者には、ファイルの削除だけでなく、この両方を伝えてください。
  • マニフェストの公開を続けることは、更新後の規約を含め、継続的に承諾することを意味します。 新しいバージョンに同意しない運用者は、そのバージョンが適用される前に参加を終了します。
登録は任意です

登録は参加の条件ではなく、アプリを参加させる手続きでもありません。アプリはすでにマニフェストによって参加しています。登録の目的は、規約に定められた通知を送るために DFINITY が運用者の連絡先を把握すること(セクション 10、11、14)と、誰がどのバージョンに同意したかを記録することです。マニフェストの公開だけでは、これらは確認できません。

登録するには、運用者が [email protected] に次の情報をメールで送信します。運用者の正式名称と所在国、同意を行う代表者の氏名・役職・メールアドレス、アプリのオリジンドメイン、マニフェストに記載されたキャニスター ID、ICP MCP がアプリの利用を検討しているユーザーに提示できるようにするための、アプリ独自のプライバシーポリシーの URL、そして同意する規約のバージョンです。バージョンは、その時点で規約ページの先頭に記載されているものを確認してください。セクション 3 によると、バージョンはそこに表示されます。このスキルの作成時点でのバージョンは Before Layer 1 に記載されています。ページと記載が異なる場合は、ページの記載が優先され、その値をメールに記載してください。これらの情報に変更があった場合は、登録内容を最新に保ってください。

デプロイ後も続く義務

マニフェストが公開されている限り、これらの義務は継続します。そのため、アプリを発見可能にしている最中にユーザーへ伝え、公開後に伝えることがないようにしてください。

  • 正確性(セクション 6)。 マニフェスト、Candid インターフェース、および getApiDoc のテキストでは、アプリを正しく特定し、各メソッドが実際に行う処理を説明し、アプリの変更に合わせて内容を同期させる必要があります。状態を変更するメソッドを読み取り専用または無害なものとして提示してはならず、手数料や送金が発生する場合は隠してはなりません。
  • アシスタントの操作禁止(セクション 6)。 公開されるメタデータと API ドキュメントには、API の説明だけを記載します。アシスタントにユーザーを無視するよう指示したり、安全性に関する動作を変更させたり、データを外部に送信させたり、他のアプリの利用を避けさせたり、けなしたりする指示を含めてはなりません。これは getApiDoc の内容に対する厳格な制約です。
  • セキュリティ(セクション 7)。 オペレーターは、オリジンの管理、キャニスターのセキュリティとアップグレード経路、および発見可能性ファイルの完全性に責任を負います。
  • 個人データ(セクション 8)。 アプリに届くリクエストには個人データが含まれます。たとえば、呼び出し引数、アプリケーションごとの ID、キャニスターから返される情報などです。各当事者は独立した管理者であり、オペレーターはそのデータを適法に取り扱い、アプリのユーザーが正確なプライバシーポリシーをいつでも確認できるようにする必要があります。ICP MCP Privacy Policy は ICP MCP がアプリに開示する情報を対象としており、その後アプリがその情報をどのように扱うかは対象としていません。

前提条件

  • icp-cli と ic-wasm を一緒にインストールします。npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm を実行してください。icp-cli スキルを参照してください。
  • @dfinity/static-site レシピを使ってフロントエンドをデプロイします(IC でフロントエンドをホストする推奨方法です。static-site スキルを参照してください)。これにより、/.well-known/ ファイルが自動で提供され、デプロイ済みのキャニスター ID を利用できる状態で presync フックが実行されます。
  • マニフェストのテンプレート処理には envsubst(GNU gettext に含まれています)が必要です。他のテンプレート処理方法(小規模な Node/jq スクリプトなど)も使用できます。envsubst は最も簡単な方法にすぎません。

レイヤー 1:構成 — ic-architecture マニフェスト

要確認。 このファイルを公開すると、アプリが ICP MCP に参加し、オペレーターに代わって ICP MCP App Operator Terms に同意することになります。まずオペレーターに確認してください。上記のレイヤー 1 の前にを参照してください。

オリジンの /.well-known/ic-architecture に、すべてのキャニスターとそれぞれの役割を列挙した JSON ドキュメントを公開します。

{
  "version": "1.0.0",
  "canisters": [
    {
      "id": "hcv4s-uaaaa-aaabq-qaaba-cai",
      "name": "frontend",
      "role": "the frontend"
    },
    {
      "id": "hmxr2-pqaaa-aaabq-qaaaa-cai",
      "name": "backend",
      "role": "the backend",
      "description": "orders + inventory API; call getApiDoc() first"
    }
  ]
}

フィールドのルール:

  • version — マニフェストのスキーマバージョンです。
  • id — 必須。キャニスターのプリンシパルです。
  • name、role — 人が読めるラベルです。description は任意です。これらのフィールドは信頼できないため、利用する前にコンシューマーがサニタイズします。
  • 不明なフィールドは無視する必要があります。これにより、形式を拡張しても(たとえば、キャニスターごとのネットワークヒントや API ドキュメントへのポインターを追加しても)、古いリーダーとの互換性が保たれます。
デプロイ時に生成する(presync パターン)

Canister ID はネットワーク(ローカル、ステージング、メインネット)ごとに異なるため、ハードコードした ID は決してコミットしないでください。ID をすでに把握しているデプロイパイプラインでファイルを生成します。@dfinity/static-site のレシピでは、これに presync フックを使います。これは同期時、つまり canister の作成後に実行されるため、ID を解決できます。

frontend/canister.yaml(トップレベルの icp.yaml から参照される canister ごとの設定。プロジェクトの構成については static-site スキルを参照してください):

name: frontend
recipe:
  type: "@dfinity/[email protected]"
  configuration:
    build:
      # Runs BEFORE the canister exists — no canister IDs available here.
      - npm ci
      - npm run build
    presync:
      # Runs AFTER canisters exist, with each canister's ID exported as ICP_CLI_CID_<NAME>.
      - mkdir -p dist/.well-known
      - envsubst < ic-architecture/tmpl."$ICP_CLI_ENVIRONMENT".json > dist/.well-known/ic-architecture
    dir: dist
  • static-site レシピは、各プロジェクト canister の ID を ICP_CLI_CID_<NAME> として presync にエクスポートします(名前は大文字に変換され、英数字以外は _ に置換されます。例:backend → ICP_CLI_CID_BACKEND)。その他の変数:ICP_CLI_CID(この canister の ID)、ICP_CLI_NETWORK。
  • $ICP_CLI_ENVIRONMENT はデプロイ対象の環境(例:local、ic)で、presync のシェルにエクスポートされます。環境に対応するテンプレートを選択するため、同じフックですべてのネットワークに対応できます。
  • presync は canister ディレクトリを作業ディレクトリとして実行されます。 そのため、相対パス ic-architecture/... は frontend canister ディレクトリ内で 解決されます。テンプレートは frontend/canister.yaml と同じ場所にある frontend/ic-architecture/ に配置してください(サンプルプロジェクトも同様です)。リポジトリのルートから解決されるパスを使うと、envsubst は何も読み込めず、マニフェストが何も入っていない状態で気付かないまま書き込まれます(落とし穴 7)。
  • レシピは現行リリース(ここでは @dfinity/[email protected])に固定してください。static-site レシピのリリースと static-site スキルを確認し、最新版を使用してください。

環境ごとに 1 つのテンプレートを frontend/ic-architecture/ に置き、ID のプレースホルダーには envsubst を使います。

frontend/ic-architecture/tmpl.local.json:

{
  "version": "1.0.0",
  "canisters": [
    { "id": "${ICP_CLI_CID_FRONTEND}", "name": "frontend", "role": "the frontend" },
    { "id": "${ICP_CLI_CID_BACKEND}", "name": "backend", "role": "the backend", "description": "orders + inventory API; call getApiDoc() first" }
  ]
}

frontend/ic-architecture/tmpl.ic.json(メインネット)も同じ形式です。静的な外部依存関係など、固定フィールドのみ異なります。

代替案 — 参照デモの方法。 raymondk/demo-ic-architecture では、エクスポートされた変数ではなく icp canister status を使って ID を明示的に解決します。レシピがエクスポートしない ID が必要な場合に便利です。その presync は次のとおりです。

    presync:
      - mkdir -p dist/.well-known
      - >-
        FRONTEND_ID=$(icp canister status frontend --id-only -e "$ICP_CLI_ENVIRONMENT")
        BACKEND_ID=$(icp canister status backend --id-only -e "$ICP_CLI_ENVIRONMENT")
        envsubst < ic-architecture/tmpl."$ICP_CLI_ENVIRONMENT".json > dist/.well-known/ic-architecture

テンプレートでは、代わりに ${FRONTEND_ID} / ${BACKEND_ID} を使用します。icp canister status <name> --id-only -e <env> は canister ID のみを出力します(icp canister id コマンドはありません)。

正しく配信する
  • .well-known/ は static-site レシピによって自動的にアップロードされます(ほかのドットファイルはスキップされますが、.well-known/ は走査されます)。dist/.well-known/ic-architecture にファイルを置くと、追加設定なしで /.well-known/ic-architecture として配信されます。.ic-assets.json5 も SPA の例外ルールも不要です。
  • 実ファイルは SPA フォールバックより優先されます。 static-site の /* /index.html 200 リライトを設定していても、マニフェストは実ファイルなので直接配信されます。リライトの対象となるのは、一致するファイルがないパスだけです。レガシーな @dfinity/asset-canister も同じように解決します。index.html フォールバックも、一致するファイルがない場合にのみ機能します。ただし、そちらではマニフェストをまずアップロードする必要があります。.ic-assets.json5 に { "match": ".well-known", "ignore": false } を指定しないと、隠しディレクトリはアップロードされません。IC 以外のホストでは、そのホスト独自のルーティング優先順位を確認してください。リライトが実ファイルより優先される場合は、/.well-known/* を除外してください。
  • コンテンツタイプを設定します。 拡張子のないファイルには application/json が自動設定されません。_headers ファイルを dir のルートに追加し(例:public/_headers)、マニフェストが JSON として配信されるようにします。
/.well-known/ic-architecture
  Content-Type: application/json

_headers / _redirects の詳細は static-site スキルを参照してください。

レイヤー 2:インターフェース — candid:service

Candid インターフェースを、Canister の公開 candid:service メタデータとして公開します。これは標準的な IC の仕組みであり、@dfinity/motoko と @dfinity/rust のレシピではデフォルトで埋め込まれます。これにより、エージェントは正確なメソッドシグネチャと型を取得し、呼び出しを正しくエンコード/デコードできます。ビルドから削除しないでください。

エージェント(または検証のためのあなた自身)は、次の方法でこれを取得します。

# By raw canister ID against mainnet (the agent's case: no project context) — target the network.
icp canister metadata <BACKEND_ID> candid:service --network ic
# By canister name from inside a project — use the project environment instead.
icp canister metadata backend candid:service -e ic

レイヤー 3:振る舞い — getApiDoc

Candid の型が表すのは構造であり、振る舞いではありません。エージェントが型から推測できない内容を説明する文章(Markdown)のガイドを返すクエリメソッドを公開します。candid:service に表示される名前を付けてください。そうすればエージェントは、事前の知識(ブートストラップ用のヒントやサイドチャネル)がなくても、このメソッドを見つけられます。

Motoko:

persistent actor {
  public query func getApiDoc() : async Text {
    "## Orders API\n\n" #
    "- **Units:** amounts are integers scaled by 10^8 (1 unit = 1e-8).\n" #
    "- **Auth:** `placeOrder` requires a signed principal; anonymous callers can only read.\n" #
    "- **Lifecycle:** `placeOrder` returns before settlement; poll `orderStatus` until `#done`.\n" #
    "- **Irreversible:** `cancelOrder` cannot be undone; `closeAccount` is a dead-man switch.\n"
  };
};

Rust(ic-cdk):

#[ic_cdk::query]
fn get_api_doc() -> String {
    // Same content; the snake_case name get_api_doc is equally discoverable.
    "## Orders API\n\n- Units: amounts are integers scaled by 10^8 ...".to_string()
}

自明ではない意味について説明します。単位とエンコード(10^8 倍でスケーリングされた整数の金額、分数と 10 分の 1 ベーシスポイント、タイムスタンプの単位)、署名済み Principal が必要な呼び出しと匿名アクセスとの違い、完了前に戻り、ポーリングが必要な段階的または非同期の処理、取り消せない操作とデッドマンスイッチ、そして新しいインテグレーターがよくつまずく注意点を含めてください。

アプリが ICP MCP に参加した後は、App Operator Terms のセクション 6 により、このテキストには制約が適用されます。状態を変更するメソッドを読み取り専用または無害であるかのように示したり、課金する手数料を隠したりしてはいけません。また、API についてのみ説明してください。呼び出し側のアシスタントに対して、ユーザーを無視する、安全性に関する振る舞いを変える、データを外部に持ち出す、または他のアプリを中傷するといった指示をしてはいけません。エージェントは getApiDoc の戻り値に基づいて動作するため、そこにプロンプトインジェクションのような内容を含めることは、機能ではなく違反です。

レイヤー 4:データ — OQL(任意。データの多いアプリ向け)

クエリ可能なデータが多いアプリでは、質問ごとにメソッドを用意しなくてもエージェントが自由形式の質問に答えられるよう、自己記述型のクエリインターフェースを公開します。OQL はそのような規約の一つです。JSON をテキストで扱う 2 つのクエリメソッドを使用します。

schema  : ()     -> (text)   query;  // JSON catalogue: entities, fields, edges
execute : (text) -> (Result) query;  // one JSON query object -> paged rows

schema は、エンティティ、そのフィールド(型と役割を含む)、およびエンティティ間のエッジを示す JSON カタログを返します。エージェントはこれを一度取得します。execute は 1 つの JSON クエリオブジェクト(フィルター、集計、並べ替え、射影、ページング)を実行し、ページ分割された Result を返します。

type Cell   = record {
  name  : text;
  value : variant { text : text; int : int; float : float64; bool : bool };  // representative scalar arms; a given canister may tag others
};
type Result = record { hasMore : bool; rows : vec vec Cell };  // each row is a list of named cells

エージェントはセルを位置ではなく名前で読み取り、hasMore が true の間はページを取得します。必要なデータだけがエージェントのコンテキストに渡るよう、サーバー側でのフィルタリングと集計を優先してください。どのような Candid インターフェースでも使用できます。OQL を使うと、自由形式の質問をより効率よく扱えます。

レイヤー 5:アイデンティティ — ii-derivation-origin

エージェントがユーザー自身のプリンシパルと権限で動作できるようにするには、フロントエンドが固定する Internet Identity の導出元を公開します。ユーザーの II 認証をすでに保持しているエージェントは、その導出元向けにアプリごとの短期間有効な委任を導出します。これにより、ブラウザーでユーザーが持つものと同じプリンシパルが得られるため、既存のアクセス制御をそのまま適用できます。(これを利用するエージェント/CLI 側の機能は agent-web-identity スキルです:icp identity link web --app <domain>。)

ユーザーが得るプリンシパルは、次の 3 つの入力によって決まります。

  1. ユーザーの Internet Identity。
  2. その Internet Identity 内のアカウント。
  3. アプリの導出元。

アプリが制御できるのは 3 つ目だけなので、アプリがこれを宣言する必要があります。導出元の既定値は表示中のオリジンです。これは、エージェントに動作を依頼したオリジン(エージェント型フローの場合)、またはブラウザーのアドレスバーに表示されているオリジン(従来型フローの場合)です。複数のオリジンからアプリにアクセスできる場合(ブランド変更やセカンダリーフロントエンドなど)は、このファイルを公開することが重要です。その場合、表示中のオリジンと ID の導出先のオリジンが異なることがあるため、このファイルによって、どのオリジン向けの導出を要求すべきかをエージェントに伝えます。

サポートする各フロントエンドのオリジンで、本文に正規の https://host オリジンを 1 行だけ記述した /.well-known/ii-derivation-origin を公開します。

https://hcv4s-uaaaa-aaabq-qaaba-cai.icp.net
  • 導出元がアプリ自体の表示中のオリジンである場合、ファイルは省略できます。ファイルがない場合は「表示中/要求されたオリジン自体を導出先とする」という意味になります。
  • 導出元がネットワークごとのカナスタ URL の場合は、同じ presync パターンを使ってデプロイ時に生成します。カスタムドメインの場合は、1 行の静的ファイルにします(例:public/.well-known/ii-derivation-origin)。
  • ii-alternative-origins と混同しないでください。 カスタムオリジンを有効にするには、連動する 2 つのファイルが必要です。アプリは II 設定で derivationOrigin を固定し、導出元自体は、そのオリジンに対する導出を許可するオリジンを列挙した /.well-known/ii-alternative-origins を公開します。このリストは逆向きの関係、つまり「ここを指してよいのは誰か」であり、「このアプリはどこを指しているか」ではありません(落とし穴 8)。既定の *.icp0.io/ic0.app のカナスタオリジンでは、カスタムの derivationOrigin を設定しないでください(internet-identity スキルの間違い #8 で、なぜ認証が壊れるのかを説明しています)。カスタムの導出元はカスタムドメインと組み合わせて使用します。詳しくは custom-domains スキルを参照してください。

エージェントがこの情報をたどる方法

それぞれ独立して公開されますが、レイヤーは 1 つの順序で利用されます。各ステップは、次のステップでエージェントが作業を省けるように設けられています。

  1. GET /.well-known/ic-architecture → カナスタ ID をすべて取得し、カナスタを識別するラベル(name、role、description)から呼び出す対象を特定します。
  2. そのカナスタの candid:service → 正確なシグネチャと型に加え、getApiDoc/schema/execute という名前を取得します。そのため、これらはサイドチャネルではなくインターフェースに含める必要があります。
  3. getApiDoc() → 型だけでは伝えられない意味情報を取得します。これには、署名済みプリンシパルが必要な呼び出しも含まれます。
  4. /.well-known/ii-derivation-origin。存在しない場合は表示中のオリジン → ユーザーの委任を導出し、サインイン済みユーザーとして動作することで、既存のアクセス制御をそのまま適用します。
  5. schema() を 1 回実行し、質問ごとに execute(...) を実行します。フィルタリングと集約はサーバー側で行われます。

アイデンティティのステップはレイヤーとしては 5 番目ですが、最初の保護された呼び出しより前に行われます。ステップ 3 で、どのメソッドに署名済みプリンシパルが必要かをエージェントが把握するため、保護された execute や更新呼び出しなど、認証が必要な処理は委任を取得するまで待たなければなりません。認証不要の読み取りだけは、それより先に実行できます。

ステップ 1〜3 により、エージェントは素の URL から、正しくエンコードされ、正しく解釈された呼び出しまでを 3 往復で進められます。公開されていないレイヤーがあるたびに、その分を推測で補うことになります。ラベルのないマニフェストだけでは、カナスタごとに candid:service の取得が必要になります(落とし穴 10)。

デプロイのチェックリスト

  • 規約への同意: アプリの運営者は、開発者や代理で操作するエージェントではなく、ICP MCP App Operator Termsを読み、マニフェストを公開する前に同意を確認しています。マニフェストの公開が同意となるため、別途手続きはありません。
  • 登録(任意): 同意に関する通知と、どのバージョンに誰が同意したかの記録を希望する場合、運営者は「Layer 1 の前に」に記載されている詳細と、アプリ独自のプライバシーポリシーの URL を添えて、[email protected] にメールを送信します。
  • 構成: デプロイパイプラインが、環境ごとの実際の Canister ID を含む /.well-known/ic-architecture(実際の JSON、拡張子なしのパス)を出力します。
  • コンテンツタイプ: _headers ルールによって、マニフェストが application/json として配信されます。
  • 到達可能性(静的サイト以外のホストのみ): /.well-known/ic-architecture が実際に解決されます。静的サイト Canister では自動的に対応します。従来の @dfinity/asset-canister では、ディレクトリをアップロードするために .ic-assets.json5 に無視解除ルールが必要です。IC 以外のホストでは、そのホストのリライトによって実ファイルが隠される場合、SPA のフォールバックから /.well-known/* を除外してください。
  • インターフェース: candid:service メタデータが存在し、削除されていません。
  • 動作: バックエンドが Markdown を返す getApiDoc/get_api_doc を公開します。
  • データ(該当する場合): データを豊富に含む Canister が OQL の schema と execute を公開します。
  • ID(カスタムの場合): 有効なオリジンを /.well-known/ii-derivation-origin に公開します(正規形式の https://host、1 行)。

検証(受け入れテスト)

デプロイ済みのオリジンに対して実行します(APP = フロントエンドのホスト):

# 1. Manifest is real JSON listing the canisters (not the SPA shell).
curl -s https://APP/.well-known/ic-architecture | jq '.canisters[].id'

# 2. Backend exposes candid:service; fetch it against a canister ID from step 1
#    (confirm it declares getApiDoc, plus schema/execute if data-rich).
icp canister metadata <BACKEND_ID> candid:service --network ic

# 3. If you pin a CUSTOM derivation origin, it is published as the canonical
#    https://host. Use -f so a 404 is an error and the fallback fires: curl -s
#    alone exits 0 on 404, so the "default" branch would never run.
curl -sf https://APP/.well-known/ii-derivation-origin || echo "default (https://APP)"

ローカルでは、静的サイトのレシピによって同じパスが配信されます。例:curl http://frontend.local.localhost:8000/.well-known/ic-architecture。

注意点

  1. Canister ID をハードコーディングする。 ID はネットワークごとに異なるため、ID をコミットすると、記述した環境以外ではすべて誤ったマニフェストが公開されます。presync の実行時に生成してください。

  2. ID の解決を presync ではなく build に配置する。 build は Canister の作成前に実行されるため、ID を利用できません(ICP_CLI_CID_* は未設定で、icp canister status も何も返しません)。ID を利用できる状態で作成後に実行されるのは presync だけです。

  3. コンテンツタイプを設定せずに、マニフェストが配信されると思い込む。 拡張子なしのファイルは配信されますが、_headers ルールが指定されていない限り、application/json としては配信されません。上記の Content-Type: application/json ブロックを追加してください。

  4. .ic-assets.json5 が適用されると思い込む。 これは従来のアセット Canister の設定です。静的サイト(certified-assets)Canister はこれを無視します。.well-known/ は自動的にアップロードされるため、無視解除ルールは不要です。従来の Canister は逆に、{ "match": ".well-known", "ignore": false } がないとディレクトリがデプロイされません。これはルーティングの問題ではなく、アップロードの問題です。

  5. ファイルに .json 拡張子を付ける。 パスは /.well-known/ic-architecture(および /.well-known/ii-derivation-origin)そのもので、拡張子は付けません。これは IC の .well-known 規約(ic-domains、ii-alternative-origins)に準拠しています。

  6. envsubst によって意図しない ${...} が置換される。 引数なしで envsubst を実行すると、見つかったすべての環境変数が置換されます。置換したくない ${...} がテンプレートに含まれる場合は、対象を制限してください:envsubst '$ICP_CLI_CID_FRONTEND $ICP_CLI_CID_BACKEND'。

  7. tmpl.<env>.json がない、またはテンプレートが正しくないディレクトリにある。 $ICP_CLI_ENVIRONMENT によって名前からテンプレートが選択されます。現在の環境用のファイルがない場合、または presync が Canister ディレクトリから実行されるのにテンプレートがリポジトリのルートにある場合、envsubst は何も読み込まず、空のマニフェストを書き込みます。環境ごとにテンプレートを 1 つ用意し、フロントエンド Canister のディレクトリに配置してください。

  8. ii-alternative-origins を読み取って派生元を特定する。 これは逆方向の関係(このオリジンに対して派生できる主体)であり、派生元を指すものではありません。逆引きはできません。逆向きに使用すると、エラーではなく、もっともらしい誤った結果として、気付かないまま誤ったプリンシパルが返されます。順方向の事実を示すには、ii-derivation-origin を公開して読み取ってください。

  9. 動作メソッドを見つけられない名前にする。 名前は candid:service に記載されている必要があるため、getApiDoc / get_api_doc を使用してください。範囲外のヒントからしかたどれないメソッドでは、ゼロ知識での探索ができません。

  10. 識別用のラベルなしでキャニスターを列挙する。 必須フィールドは id だけなので、ID のみのマニフェストも有効であり、それでも役に立ちます。エージェントは、範囲外の情報から探索することなく、アプリのキャニスターを取得できます。一方で失われるのはルーティング情報です。バックエンドとフロントエンドを見分ける手がかりがなければ、エージェントは各エントリについて candid:service を取得し、メソッド名から役割を推測することになります。各エントリには、それを識別できる情報(多くの場合 name だけで十分です。role を追加し、名前から目的が明らかでない場合は description も追加してください)を付けてください。

  11. candid:service を削除する。** 一部の最小化・サイズ最適化ビルドでは、Wasm メタデータが削除されます。これはインターフェースを取得可能にするものなので、残してください。icp canister metadata <id> candid:service --network ic で確認してください。

  12. オペレーターに確認せずにマニフェストを公開する。 ここでエージェントが失敗する可能性がもっとも高いのは、/.well-known/ic-architecture を通常のビルド設定として扱い、確認なしで追加してしまうことです。これは、ICP MCP App Operator Terms に同意し、アプリが AI アシスタントからのクエリ呼び出しと更新呼び出しを受け付けるようにする行為です。まずユーザーに確認し、ユーザーがオペレーターであることも確認してください。セクション 1 によると、アプリを運営していない開発者は、そのオペレーターに代わって同意できません。

  13. 登録をオプトイン、またはオプトアウトと取り違える。 登録は任意であり、参加状況には何の変更もありません。マニフェストが公開されている未登録のアプリも、完全に参加しており、規約の適用を全面的に受けます。登録によって DFINITY に伝わるのは、誰が同意し、どのバージョンに同意したか、および通知の送信先だけです。同様に、参加を停止するために「登録解除」する必要はありません。参加を停止するスイッチはマニフェストの削除です。マニフェストを削除すると参加は無効になりますが、契約の終了は別の行為であり、[email protected] への通知が必要です(セクション 11)。どちらも Internet Computer 上ですでに実行された呼び出しを取り消すものではなく、セクション 8、12、13、15(個人データ、保証、責任、準拠法)は契約終了後も存続します。「ファイルを削除すれば完了」というのは、不完全なだけでなく、誤りです。

  14. マニフェストを削除すればアプリが完全に掲載対象から外れると思い込む。 削除すると、ICP MCP が削除を認識した後は、新しいメタデータの読み取りとクエリ/更新呼び出しが停止します。ただし ICP MCP は、アプリの公開ウェブサイトを引き続き取得し、公開情報を説明する場合があります。ユーザー向けの表示から除外するには、オペレーターが DFINITY に [email protected] から依頼します。

  15. オペレーターが管理していないキャニスターを宣言する。 サードパーティの台帳や、アプリが呼び出すだけのエコシステムキャニスターを列挙したくなるかもしれません。マニフェストを提供することは、宣言したすべてのキャニスターがオペレーターによって運営されているか、その権限の下にあるという継続的な表明であり(セクション 5)、そのアプリの参加に基づく更新呼び出しの対象にそれらのキャニスターを含めることになります。自分が所有するものだけを列挙してください。

追加の参考資料

  • @dfinity/static-site レシピの static-site を読み込みます。内容は presync/build、_redirects/_headers、.well-known/ の自動アップロード、Canister ID の挿入です。
  • 派生元を「使用する」エージェント/CLI 側については、agent-web-identity を読み込みます(icp identity link web --app <domain>)。
  • フロントエンドに II サインインを追加する方法については、internet-identity を読み込みます(derivationOrigin を設定すべきでない場合についても説明しています)。
  • 独自ドメインから配信し、それを派生元にする方法については、custom-domains を読み込みます。
  • icp.yaml/canister.yaml、環境、レシピシステムについては、icp-cli を読み込みます。
  • エージェントが呼び出すメソッドのアクセス制御については、canister-security を読み込みます。
  • 権威ある人間向けガイド:サービスの検出可能性。
  • マニフェストの公開時に同意する利用規約:ICP MCP アプリ運営者規約、および ICP MCP の概要:ICP MCP。
  • ICP MCP が参加アプリに開示する情報と、登録時に処理する情報:ICP MCP プライバシーポリシー。
  • エージェントが読み取る型付きインターフェースの Candid インターフェースリファレンス:Candid インターフェース。
  • Layer 1 の生成に関するコミュニティの例(個人リポジトリです。説明用であり、安定した依存関係ではありません):raymondk/demo-ic-architecture。

コメント