mcpField
S
service-discoverability

service-discoverability

github.com/dfinity开发3980 次安装今天更新
代码仓库

关于 service-discoverability

帮助开发者让 Internet Computer canister 应用可由 AI Agent 从应用 URL 发现并使用。内容涵盖应用组成、接口、数据和身份等五层发布机制;参与 ICP MCP 前必须取得运营方同意。

主要功能

  1. 01发布 canister 组成清单
  2. 02公开 Candid 接口元数据
  3. 03用 getApiDoc 说明行为
  4. 04提供 OQL 模式与查询方法
  5. 05在部署时生成网络专属文件
  6. 06支持发现 Internet Identity 派生信息

适用场景

  1. 01让 Agent 从 URL 发现 canister 应用
  2. 02为 Agent 记录 canister 方法
  3. 03支持对应用数据进行结构化查询
  4. 04为应用参与 ICP 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 Agent 只拿到你应用的 URL(例如 https://yourapp.com)时,它应该能够自行了解其余信息:应用由哪些 canister 组成、每个 canister 的用途、如何调用它们、如何查询其数据,以及如何以已登录用户的身份进行操作。无需人工提供 canister ID,也无需专门集成。

本技能涵盖 canister 应用为实现这一目标而在五个层面上发布的内容。第 1 层设有门槛:这是应用参与 ICP MCP 时发布的内容,发布即代表运营方接受一项协议——编写该文件前,请先阅读第 1 层之前。第 2 至第 5 层没有门槛,可独立采用;每一层都各有用处,合在一起则能让应用做好供 Agent 使用的准备。

层级解答的问题机制
1. 组成结构哪些 canister 构成此应用?每个 canister 的用途是什么?/.well-known/ic-architecture JSON 清单
2. 接口canister 提供哪些方法和类型?candid:service 元数据
3. 行为canister 的实际行为如何(单位、生命周期、注意事项)?getApiDoc 查询方法
4. 数据如何查询 canister 的数据,而无需针对每个问题编写一个方法?OQL schema + execute 查询方法
5. 身份如何以已登录用户的身份,并使用正确的主体进行操作?/.well-known/ii-derivation-origin 文件

第 1 层和第 5 层的关键做法是在部署时生成 well-known 文件,因为不同网络的 canister 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”)与应用运营者之间的协议——而且,发布有效清单本身就是接受条款并选择加入的行为。不需要签署,不需要勾选,也不需要其他操作。擅自将清单加入构建流程的 Agent,会在未经询问的情况下让用户签订合同。

向用户说明该版本号:这是他们将要接受的版本。操作前先检查条款页面顶部——条款版本独立于本技能(第 14 节)更新,因此,如果页面显示的版本晚于上面的版本,以页面为准,说明本技能已过时。告知用户页面显示的版本,并说明本技能中的版本已过时,需要修正。

在用户确认以下两点之前,不要发布。 用自己的话向用户说明,附上条款链接,并让用户先阅读再作答。

1. 他们是运营者,或获得了代表运营者作出承诺的授权。 运营者是依法获准运营应用并声明其 canister 的个人或实体。构建应用不等于运营应用——第 1 节规定:“如果你开发了应用但不运营它,则应由该应用的运营者接受本条款,而不是你。” 如果你是为他人应用工作的承包商、代理机构或员工,应将决定交由运营者,而不是直接发布后继续。

2. 他们接受发布所允许的操作。 根据第 2 节,在用户已通过 Internet Identity 授权其 AI 助手的情况下,参与意味着 ICP MCP 服务可以代表这些用户:

  • 读取清单、清单所声明的各 canister的 candid:service 元数据,以及 getApiDoc / get_api_doc 返回的内容;
  • 为助手及其用户编制索引并缓存这些元数据,并介绍应用及其 API,包括在任何公开的参与应用列表中;
  • 向这些已声明的 canister 提交查询调用;对于已授权操作的用户,还可提交会更改状态的(更新)调用,并使用这些用户各自的应用专属 Internet Identity 身份签名;以及
  • 从应用源站获取其已发布文件,以保持上述信息为最新。

参与对双方均免费:DFINITY 不收取任何费用,也不承担任何义务。

清单是开关,也是全部范围
  • 只有已声明的 canister 才属于范围。 ICP MCP 仅会检索有效清单中列出的 canister 的元数据,并向这些 canister 提交查询或更新调用。切勿列出运营方并未运行或无权公开的 canister——第 5 节是一项持续有效的声明,每次提供该文件时都再次作出,声明每个已声明的 canister 均由运营方运行或由运营方授权运行。
  • 没有有效清单,就不会发生上述任何操作。 ICP MCP 仍可能获取并介绍应用的公开网站,但会拒绝检索元数据和调用 canister。
  • 移除清单就是关闭开关,无论是否已注册。一旦 ICP MCP 检测到清单已移除,就会停止新的元数据读取以及查询/更新调用;如果源站或已声明的 canister 遭到入侵,这也是最快的缓解措施(第 7 节)。这不会阻止 ICP MCP 获取公开网站或介绍公开信息;如需停止这些操作,请联系 DFINITY,要求其暂停参与:[email protected]。
  • 停用不等于终止。 移除清单即结束参与;结束协议则是另一项单独操作——需向 [email protected] 发送通知(第 11 节)。这两者都无法撤销已在 Internet Computer 上执行的调用,而且第 8、12、13 和 15 节(个人数据、保证、责任、适用法律)在终止后仍然有效。运营方询问如何退出时,请同时告知这两方面,而不只是删除文件。
  • 持续发布清单即表示持续接受条款,包括条款的更新版本。不接受新版本的运营方,应在新版本生效前结束参与。
注册是可选的

注册不是参与的条件,也不是让应用选择加入的方式——清单已经完成了这一步。注册的目的是让 DFINITY 知道运营方的身份:以便根据条款规定向其发送通知(第 10、11、14 节),并记录是谁接受了哪个版本,而仅靠发布清单无法得知这些信息。

如需注册,运营方请发送电子邮件至 [email protected],并提供:运营方的法定名称和所在国家/地区;接受条款的代表的姓名、职务和电子邮件地址;应用的源站域名;清单中声明的 canister ID;应用自身隐私政策的网址,以便 ICP MCP 向正在考虑使用该应用的用户展示;以及所接受条款的版本——请以当时条款页面顶部显示的版本为准,第 3 节说明版本显示位置。Before Layer 1 中注明的版本是撰写本技能时的当前版本;如页面信息与之不一致,以页面为准,并在电子邮件中填写页面显示的版本。上述信息如有变更,请及时更新注册信息。

部署后仍需履行的义务

只要清单仍处于发布状态,这些义务就持续有效。因此,在让应用可被发现时就应告知用户,而不是等到之后:

  • 准确性(第 6 节)。 清单、Candid 接口和 getApiDoc 文本必须正确标识应用,准确描述每个方法的实际功能,并随应用变更保持同步。绝不能将会改变状态的方法说成只读或无害,也绝不能隐瞒费用或其执行的转账。
  • 不得操纵助手(第 6 节)。 已发布的元数据和 API 文档只能描述 API,不得包含任何指示助手忽略用户、改变安全行为、泄露数据、回避或贬低其他应用的内容。这是对 getApiDoc 内容的硬性限制。
  • 安全(第 7 节)。 运营者负责控制源站、保障 canister 的安全及升级路径,并确保可发现性文件的完整性。
  • 个人数据(第 8 节)。 到达应用的请求包含个人数据——调用参数、每个应用的身份信息,以及 canister 返回的任何内容。各方都是独立的控制者;运营者必须依法处理这些数据,并向应用用户提供准确的隐私政策。ICP MCP 隐私政策涵盖 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/ 文件,并运行 presync 钩子,此时已可获取已部署的 canister ID。
  • 使用 GNU gettext 中的 envsubst 模板化清单。其他模板化方式也可行(例如一个小型 Node/jq 脚本);envsubst 只是最简单的选择。

第 1 层:组合 — ic-architecture 清单

需经确认。 发布此文件即表示代表运营者让应用加入 ICP MCP,并接受 ICP MCP 应用运营者条款。请先与运营者确认——参见上文 第 1 层之前。

在源站的 /.well-known/ic-architecture 发布一份 JSON 文档,列出每个 canister 及其角色:

{
  "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 — 必填,canister 主体。
  • name、role — 面向用户的标签;description 为可选项。这些字段不可信,因此使用前必须由使用方进行清理。
  • 必须忽略未知字段,以便格式可以扩展(例如添加每个 canister 的网络提示或 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
  • 静态站点配方会将各项目 Canister 的 ID 导出到 presync,变量名为 ICP_CLI_CID_<NAME>(名称转为大写,非字母数字字符替换为 _;例如,backend → ICP_CLI_CID_BACKEND)。其他变量:ICP_CLI_CID(当前 Canister)、ICP_CLI_NETWORK。
  • $ICP_CLI_ENVIRONMENT 是正在部署的环境(例如 local、ic),会导出到 presync shell 中。它用于选择匹配的模板,因此同一个钩子适用于所有网络。
  • presync 以 Canister 目录作为工作目录运行。 因此,相对路径 ic-architecture/... 会在 frontend Canister 目录中解析——请将模板放在 frontend/ic-architecture/ 下,与 canister.yaml 同级(示例项目采用这种布局)。如果改为从仓库根目录解析路径,envsubst 将读取不到内容,并悄悄写入空清单(陷阱 7)。
  • 将配方固定到当前版本(此处为 @dfinity/[email protected]);请查看静态站点配方的版本发布情况和 static-site 技能以了解最新版本。

在 frontend/ic-architecture/ 下为每个环境保留一个模板,并使用 envsubst 占位符表示 ID。

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/(它会遍历 .well-known/,但会跳过其他点文件)。dist/.well-known/ic-architecture 文件会通过 /.well-known/ic-architecture 提供,无需额外配置——不需要 .ic-assets.json5,也不需要 SPA 豁免规则。
  • 真实文件优先于 SPA 回退。 使用静态站点的 /* /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 配方默认会将其嵌入其中。这样,Agent 就能获取准确的方法签名和类型,并正确编码/解码调用。不要在构建过程中移除它。

Agent(或你自己,用于验证)可以通过以下方式获取:

# 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)指南,说明 Agent 无法从类型推断的信息。为它命名时,要确保它会显示在 candid:service 中——这样,Agent 无需任何外部知识(无需引导提示或旁路信息)就能找到它。

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 缩放的整数金额、分数与十分之一基点、时间戳单位)、哪些调用需要经过签名的主体,以及匿名访问有何不同、哪些分阶段或异步操作会在完成前就返回且必须轮询、哪些操作不可逆以及是否有死手开关,还有那些经常让新集成者踩坑的注意事项。

应用参与 ICP MCP 后,App Operator Terms 第 6 节会对这段文本作出限制:不得将有状态变更的方法描述为只读或无害,也不得隐瞒其收取的费用;并且只能描述 API,绝不能指示调用助手忽略用户、更改安全行为、外传数据或贬低其他应用。Agent 会根据 getApiDoc 返回的内容采取行动,因此其中包含类似提示注入的内容属于违规,而非功能特性。

第 4 层:数据 — OQL(可选,适用于数据丰富的应用)

对于拥有大量可查询数据的应用,暴露一个自描述式查询接口,让 Agent 无需针对每个问题都编写一个方法,就能回答开放式问题。OQL 是其中一种约定——使用两个以 JSON 文本交互的查询方法:

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

schema 返回一个 JSON 目录,其中包含实体、实体的字段(带类型和角色)以及实体之间的边;Agent 获取一次即可。execute 运行一个 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

Agent 始终按名称而非位置读取单元格,并在 hasMore 为 true 时继续分页。优先在服务端进行筛选和聚合,这样只有所需数据会进入 Agent 的上下文。任何 Candid 接口都适用;OQL 只是让开放式问题的处理更高效。

第 5 层:身份 — ii-derivation-origin

若要让 Agent 以用户自身的主体和权限行事,请发布前端固定使用的 Internet Identity 派生源。已持有用户 II 授权的 Agent 会为该派生源派生一个短期、按应用隔离的委派,从而得到与用户在浏览器中相同的主体——现有访问控制无需任何更改即可继续生效。(使用该功能的 Agent/CLI 端是 agent-web-identity skill:icp identity link web --app <domain>。)

用户获得的主体取决于三个输入:

  1. 用户的 Internet Identity。
  2. 该 Internet Identity 中的账户。
  3. 你的应用派生源。

只有第三项由你的应用控制,因此应用必须声明它。派生源默认为可见源:即要求用户的 Agent 执行操作时所针对的源(Agent 流程),或浏览器地址栏中的源(传统流程)。如果应用可通过多个源访问(例如品牌重塑或次级前端),发布此文件就很重要:此时可见源可能与派生身份所用的源不同,而该文件会告知 Agent 应向哪个源请求派生。

在每个受支持的前端源发布 /.well-known/ii-derivation-origin,其正文为单行规范 https://host 源:

https://hcv4s-uaaaa-aaabq-qaaba-cai.icp.net
  • 如果派生源就是应用自身的可见源,可以省略此文件;文件不存在表示“为可见源/请求源本身派生”。
  • 如果源是按网络变化的 canister URL,请使用相同的 presync 模式在部署时生成;对于自定义域名,它是单行静态文件(例如 public/.well-known/ii-derivation-origin)。
  • **不要将其与 ii-alternative-origins 混淆。**自定义源需要两个相互配合的文件才能启用:应用在其 II 配置中固定 derivationOrigin,而派生源自身发布 /.well-known/ii-alternative-origins,列出允许针对该派生源进行派生的源。该列表表示反向关系——“谁可以指向此处”,而非“此应用指向哪里”(陷阱 8)。对于默认的 *.icp0.io / ic0.app canister 源,不要设置自定义 derivationOrigin(internet-identity skill 的错误做法 #8 解释了这会导致认证失效的原因);自定义派生源应与自定义域名配套使用——请参阅 custom-domains skill。

Agent 如何遍历这些层

这些层分别发布,但 Agent 按固定顺序使用——每一步都让 Agent 能在下一步省去一些工作:

  1. GET /.well-known/ic-architecture → 获取每个 canister ID,以及应调用哪个 canister,并根据标识它的标签(name、role、description)进行判断。
  2. 该 canister 上的 candid:service → 获取准确的签名和类型,以及 getApiDoc / schema / execute 这些名称;因此它们必须位于接口中,而不能通过旁路提供。
  3. getApiDoc() → 获取类型无法表达的语义,包括哪些调用需要签名主体。
  4. /.well-known/ii-derivation-origin,若不存在则使用可见源 → 派生用户的委派,并以已登录用户身份行事,使现有访问控制无需更改即可继续生效。
  5. 调用一次 schema(),然后针对每个问题调用一次 execute(...),并在服务端进行筛选和聚合。

身份层编号为第 5 层,但它会在首次受保护调用之前发生:第 3 步中,Agent 会得知哪些方法需要签名主体,因此任何受保护的操作——受保护的 execute 或任何更新调用——都必须等待委派完成。只有未经身份验证的读取可以提前执行。

步骤 1-3 只需三次往返,就能让 Agent 从一个裸 URL 发起经过正确编码、且理解无误的调用。每少发布一层,就会有一步被猜测取代——如果清单没有标签,每个 canister 都要单独获取一次 candid:service(陷阱 10)。

部署检查清单

  • 已接受条款: 应用的运营方(而非代表其行事的开发者或 Agent)已阅读 ICP MCP App 运营方条款,并在清单发布之前确认接受。发布清单即表示接受,无需单独执行其他步骤。
  • 注册(可选): 如果运营方希望收到通知,并留存谁接受了哪个版本的记录,可发送邮件至 [email protected],并提供第 1 层之前列出的详细信息,包括应用自身的隐私政策 URL。
  • 组合: 部署流水线会生成 /.well-known/ic-architecture(真实的 JSON 文件,路径不带扩展名),其中包含各环境实际的 canister ID。
  • 内容类型: _headers 规则会将清单作为 application/json 提供。
  • 可访问性(仅限非静态网站主机): /.well-known/ic-architecture 确实可以访问。静态网站 canister 会自动处理;对于旧版 @dfinity/asset-canister,必须上传目录,而这需要在 .ic-assets.json5 中添加取消忽略规则;对于非 IC 主机,如果该主机的重写规则会遮蔽真实文件,则需将 /.well-known/* 排除在 SPA 回退规则之外。
  • 接口: 存在 candid:service 元数据(未被剥离)。
  • 行为: 后端提供 getApiDoc / get_api_doc,并返回 Markdown。
  • 数据(如适用): 数据丰富的 canister 提供 OQL schema + execute。
  • 身份(如为自定义): 在 /.well-known/ii-derivation-origin 中发布生效的源(规范格式为 https://host,单行)。

验证(验收测试)

针对已部署的源运行(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. 在 build 而非 presync 中解析 ID。 build 运行时 canister 尚未创建,因此没有可用的 ID(ICP_CLI_CID_* 未设置,icp canister status 也不会返回任何结果)。只有 presync 会在创建完成且 ID 可用后运行。

  3. 以为清单会自动提供,却没有设置其内容类型。 不带扩展名的文件虽然会被提供,但除非 _headers 规则指定,否则不会作为 application/json 提供。请添加上文的 Content-Type: application/json 区块。

  4. 以为 .ic-assets.json5 会起作用。 它是旧版 asset 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 就会读取不到内容并写入空清单。请在前端 canister 目录下为每个环境保留一个模板。

  8. 读取 ii-alternative-origins 以查找派生来源。 它是反向关系(谁可以从该来源派生),而不是指向该来源的指针。它没有反向查找功能;反向使用会悄无声息地得到错误的主体——这是一个看似合理的错误答案,而不是错误。要获取正向事实,请发布并读取 ii-derivation-origin。

  9. 以无法发现的方式命名行为方法。 方法名称必须出现在 candid:service 中,因此请使用 getApiDoc / get_api_doc。只能通过额外提示访问的方法会破坏零知识发现。

  10. 列出无法识别的 Canister。 id 是唯一必填字段,因此仅包含 ID 的清单是有效的,而且仍然很有用:Agent 无需通过额外方式发现应用的 Canister。它失去的是路由信息——如果没有任何信息标明后端和前端,Agent 就只能为每个条目获取一次 candid:service,并从方法名称推断角色。请为每个条目添加能识别它的信息(name 通常就够了;再添加 role,如果用途无法从名称中明显看出,则添加 description)。

  11. 剥除 candid:service。 某些经过压缩或优化体积的构建会丢弃 wasm 元数据。请保留它——它能让接口可供获取。使用 icp canister metadata <id> candid:service --network ic 验证。

  12. 未经运营者同意就发布清单。 此处最可能出现的 Agent 错误,是把 /.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. 声明运营者无权控制的 Canister。 将应用调用的第三方账本或生态系统 Canister 列入清单很诱人。但提供清单即构成反复陈述(第 5 节),即每个声明的 Canister 都由运营者运营或受其授权,并使这些 Canister 有资格在应用参与期间接受更新调用。只列出你自己的 Canister。

其他参考资料

  • 加载 static-site,了解 @dfinity/static-site 配方:presync/build、_redirects/_headers、自动上传 .well-known/,以及注入 canister ID。
  • 加载 agent-web-identity,了解使用派生源的 Agent/命令行端(icp identity link web --app <domain>)。
  • 加载 internet-identity,了解如何为前端添加 II 登录(其中也说明了何时不应设置 derivationOrigin)。
  • 加载 custom-domains,了解如何从自己的域名提供服务,该域名随后会成为你的派生源。
  • 加载 icp-cli,了解 icp.yaml / canister.yaml、环境和配方系统。
  • 加载 canister-security,了解 Agent 调用的方法的访问控制。
  • 权威人工编写指南:服务可发现性。
  • 发布清单时接受的条款:ICP MCP App Operator Terms,以及 ICP MCP 的介绍:ICP MCP。
  • ICP MCP 向参与应用披露的内容,以及它在注册时处理的内容:ICP MCP Privacy Policy。
  • Agent 读取的类型化接口的 Candid 接口参考:Candid interface。
  • 社区提供的 Layer 1 生成示例(个人仓库,仅供参考,并非稳定依赖):raymondk/demo-ic-architecture。

评论

支持 Markdown