关于 service-discoverability
帮助开发者让 Internet Computer canister 应用可由 AI Agent 从应用 URL 发现并使用。内容涵盖应用组成、接口、数据和身份等五层发布机制;参与 ICP MCP 前必须取得运营方同意。
主要功能
- 01发布 canister 组成清单
- 02公开 Candid 接口元数据
- 03用 getApiDoc 说明行为
- 04提供 OQL 模式与查询方法
- 05在部署时生成网络专属文件
- 06支持发现 Internet Identity 派生信息
适用场景
- 01让 Agent 从 URL 发现 canister 应用
- 02为 Agent 记录 canister 方法
- 03支持对应用数据进行结构化查询
- 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),会导出到presyncshell 中。它用于选择匹配的模板,因此同一个钩子适用于所有网络。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>。)
用户获得的主体取决于三个输入:
- 用户的 Internet Identity。
- 该 Internet Identity 中的账户。
- 你的应用派生源。
只有第三项由你的应用控制,因此应用必须声明它。派生源默认为可见源:即要求用户的 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.appcanister 源,不要设置自定义derivationOrigin(internet-identityskill 的错误做法 #8 解释了这会导致认证失效的原因);自定义派生源应与自定义域名配套使用——请参阅custom-domainsskill。
Agent 如何遍历这些层
这些层分别发布,但 Agent 按固定顺序使用——每一步都让 Agent 能在下一步省去一些工作:
GET /.well-known/ic-architecture→ 获取每个 canister ID,以及应调用哪个 canister,并根据标识它的标签(name、role、description)进行判断。- 该 canister 上的
candid:service→ 获取准确的签名和类型,以及getApiDoc/schema/execute这些名称;因此它们必须位于接口中,而不能通过旁路提供。 getApiDoc()→ 获取类型无法表达的语义,包括哪些调用需要签名主体。/.well-known/ii-derivation-origin,若不存在则使用可见源 → 派生用户的委派,并以已登录用户身份行事,使现有访问控制无需更改即可继续生效。- 调用一次
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。
易错点
-
将 canister ID 硬编码。 不同网络的 ID 各不相同,因此提交这些 ID 会导致清单在除编写时对应的环境之外的所有环境中都出错。应在部署时的
presync中生成。 -
在
build而非presync中解析 ID。build运行时 canister 尚未创建,因此没有可用的 ID(ICP_CLI_CID_*未设置,icp canister status也不会返回任何结果)。只有presync会在创建完成且 ID 可用后运行。 -
以为清单会自动提供,却没有设置其内容类型。 不带扩展名的文件虽然会被提供,但除非
_headers规则指定,否则不会作为application/json提供。请添加上文的Content-Type: application/json区块。 -
以为
.ic-assets.json5会起作用。 它是旧版 asset canister 的配置;静态网站(certified-assets)canister 会忽略它。.well-known/会自动上传,无需添加取消忽略规则。旧版 canister 则恰好相反:必须添加{ "match": ".well-known", "ignore": false },否则该目录永远不会部署——这是上传问题,而非路由问题。 -
给文件添加
.json扩展名。 路径必须恰好是/.well-known/ic-architecture(以及/.well-known/ii-derivation-origin),不带扩展名,符合 IC 的.well-known约定(ic-domains、ii-alternative-origins)。 -
envsubst意外替换了不该替换的${...}。 不带参数时,envsubst会替换它找到的所有环境变量。如果模板中有不想替换的${...},请限制替换范围:envsubst '$ICP_CLI_CID_FRONTEND $ICP_CLI_CID_BACKEND'。 -
缺少
tmpl.<env>.json,或模板位于错误目录。$ICP_CLI_ENVIRONMENT会按名称选择模板;如果当前环境对应的文件不存在,或文件位于仓库根目录,而presync是从 canister 目录运行的,envsubst就会读取不到内容并写入空清单。请在前端 canister 目录下为每个环境保留一个模板。 -
读取
ii-alternative-origins以查找派生来源。 它是反向关系(谁可以从该来源派生),而不是指向该来源的指针。它没有反向查找功能;反向使用会悄无声息地得到错误的主体——这是一个看似合理的错误答案,而不是错误。要获取正向事实,请发布并读取ii-derivation-origin。 -
以无法发现的方式命名行为方法。 方法名称必须出现在
candid:service中,因此请使用getApiDoc/get_api_doc。只能通过额外提示访问的方法会破坏零知识发现。 -
列出无法识别的 Canister。
id是唯一必填字段,因此仅包含 ID 的清单是有效的,而且仍然很有用:Agent 无需通过额外方式发现应用的 Canister。它失去的是路由信息——如果没有任何信息标明后端和前端,Agent 就只能为每个条目获取一次candid:service,并从方法名称推断角色。请为每个条目添加能识别它的信息(name通常就够了;再添加role,如果用途无法从名称中明显看出,则添加description)。 -
剥除
candid:service。 某些经过压缩或优化体积的构建会丢弃 wasm 元数据。请保留它——它能让接口可供获取。使用icp canister metadata <id> candid:service --network ic验证。 -
未经运营者同意就发布清单。 此处最可能出现的 Agent 错误,是把
/.well-known/ic-architecture当作普通构建配置,并在未获同意的情况下添加它。此举意味着接受 ICP MCP App Operator Terms,并允许 AI 助手向应用发起查询和更新调用。请先向用户确认,并确认其是运营者——第 1 节规定,不运营该应用的开发者不能代表运营者接受条款。 -
误把注册当作选择加入或退出。 注册是可选的,不会改变参与状态:已发布清单的未注册应用仍完全参与并受条款约束。注册只是告知 DFINITY 谁接受了条款、接受的是哪个版本,以及通知应发送到何处。同样,要停止参与,无需“注销”——移除清单才是关闭开关。移除清单会停用参与;终止协议则是另一项独立操作,需向
[email protected]发送通知(第 11 节)。两者都不会撤销已在 Internet Computer 上执行的调用;第 8、12、13 和 15 节——个人数据、保证、责任和适用法律——在协议终止后仍然有效。“删除文件就万事大吉”是错误的,而不仅仅是不完整。 -
以为移除清单就能彻底将应用从目录中下架。 删除清单后,一旦 ICP MCP 发现其已移除,就会停止新的元数据读取和查询/更新调用;但 ICP MCP 仍可能获取应用的公开网站,并介绍其中公开可用的信息。若要将应用从面向用户的展示中移除,运营者需联系 DFINITY:
[email protected]。 -
声明运营者无权控制的 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。