diff --git a/Cargo.toml b/Cargo.toml index 384ce96..a6a5300 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ categories = [ "network-programming", "web-programming::http-server", "api-bindings", - "cryptography::cryptocurrencies", ] # Everything the library and the bundled binary compile from ships; the # deployment scaffolding (container, systemd/native deploy, dashboards, CI, diff --git a/README.md b/README.md index b75d1d0..935a196 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,18 @@ encoding/decoding and signing against the IC via [`ic-agent`](https://github.com/dfinity/agent-rs). The MCP layer is the [official Rust SDK](https://github.com/modelcontextprotocol/rust-sdk) (`rmcp`). +**Not for financial operations.** IMCP2 is infrastructure tooling for reading, +building, and operating canisters — it is not a wallet or trading tool, and +financial operations (token transfers, spending approvals, payments, trades) +are unsupported by policy. The standardized ledger surface is refused +mechanically: `canister_update_call` rejects the ICRC-1/ICRC-2 (and related +ledger-standard) transfer/approval methods on every canister, and +`icp_top_up_canister` and `icp_create_canister` return `icp` CLI instructions +for the user to run themselves instead of executing anything — those tools +create nothing and move no funds. For financial operations, users act +themselves in a wallet or frontend they control (e.g. +[oisy.com](https://oisy.com)), in their own browser. + ## Use as a library Published on [crates.io](https://crates.io/crates/imcp2), API docs on @@ -133,10 +145,10 @@ results). | Tool | Args | Returns | |------|------|---------| -| `open_app` | `app` (name **or** URL) | **One-call entry point** when a user names/links an app: resolves the Internet Identity `derivation_origin` *and* discovers the canisters behind it, together. A name or bare host is matched to the known-app registry first (so a wrong-TLD guess like `multidex.com` repairs to the canonical URL); an explicit `https://` URL is resolved as given. An unknown bare name, or a URL with no IC evidence, is *refused* (never guessed). Also probes the app's own canisters and reports per-canister `oql`/`api_doc_available` capability flags plus a caller-gated data-access note (which canister holds the app data, and the origin to read it as the user). Wraps `resolve_app` + `discover_app_canisters`; no auth | +| `open_app` | `app` (name **or** URL) | **One-call entry point** when a user names/links an app: resolves the Internet Identity `derivation_origin` *and* discovers the canisters behind it, together. A name or bare host is matched to the known-app registry first (so a wrong-TLD guess repairs to the canonical URL); an explicit `https://` URL is resolved as given. An unknown bare name, or a URL with no IC evidence, is *refused* (never guessed). Also probes the app's own canisters and reports per-canister `oql`/`api_doc_available` capability flags plus a caller-gated data-access note (which canister holds the app data, and the origin to read it as the user). Wraps `resolve_app` + `discover_app_canisters`; no auth | | `discover_app_canisters` | `domain` | Canister ids behind a web domain — app-declared App Connect metadata first (`/ai-connect.html`'s `ic:canister-id` meta, `/.well-known/ic-app.json` manifest), then the frontend via `x-ic-canister-id` and backend candidates via `/env.json` + JS-bundle mining — each with provenance, its IC dashboard label/type where known, and (for the app's own canisters) `oql`/`api_doc_available` capability flags from a one-shot Candid probe | | `icp_find_canister_by_name` | `query` | Canister ids matching a name/symbol, searched in the IC dashboard's service registries — ICRC token ledgers (e.g. `ckUSDC`) and the SNS project catalog | -| `icp_find_app_by_name` | `name` | A well-known app's front-end URL + `derivation_origin`, for a small built-in set (NNS, Oisy, MULTI/DEX, ICPSwap). The **first stop** whenever only an app *name* is known — never guess a domain from a name. Any other name returns no match and a `note` directing a web search for the app's URL (there's no on-chain name→URL directory) | +| `icp_find_app_by_name` | `name` | A well-known app's front-end URL + `derivation_origin`, for a small built-in set of well-known IC apps. The **first stop** whenever only an app *name* is known — never guess a domain from a name. Any other name returns no match and a `note` directing a web search for the app's URL (there's no on-chain name→URL directory) | | `icp_lookup_canister_info_by_id` | `canister_id` | What a canister IS, per the IC dashboard: label/name, type, controllers, subnet, module hash, latest upgrade proposal | | `get_canister_candid` | `canister_id` | The canister's `candid:service` interface (`.did` text), plus two capability flags: `oql` (`true` when it exposes an OQL query surface — a `schema` + `execute` pair — with a pointer to `icp_oql_guide`) and `api_doc_available` (`true` when it declares a `getApiDoc`/`get_api_doc` method, gating `get_canister_api_doc`) | | `get_canister_api_doc` | `canister_id` | The canister's own prose API guide ("how this app behaves" — units, auth, lifecycle, mutation safety, polling, gotchas), from its `getApiDoc`/`get_api_doc` method. Call **only** when `get_canister_candid`/`open_app` report `api_doc_available`. Returns a **structured** result in every case — `available` + the doc on success, else `available:false` with `expected`/`retry`/`next` so an expected absence is distinct from an unreachable canister | @@ -174,16 +186,16 @@ candidates, pick by label, prefer production/`IC_` ids, and confirm with Acting **for the user** at an app: 0–2. **`open_app(name-or-URL)`** — the one-call entry point. Pass the *name* the user - said (well-known apps NNS/Oisy/MULTI/DEX/ICPSwap resolve offline) or a URL you - have; it returns the `derivation_origin` **and** the app's canisters in one shot + said (well-known apps resolve offline) or a URL you have (e.g. + `https://opencloud.org`); it returns the `derivation_origin` **and** the app's canisters in one shot (it runs `resolve_app` + `discover_app_canisters` concurrently under the hood). **Never guess a domain from a name** — a lookalike like `.com` is an unrelated or squatted site. The tool enforces this: a bare *unknown* name is refused (find the real URL — web-search or ask the user), and a URL that resolves to `app_url_default` while showing **no IC evidence** (no valid `x-ic-canister-id` gateway header, no `ic-app.json` derivation origin) is refused too; when the host - resembles a known app the error names it and gives the real URL ("did you mean - MULTI/DEX → `https://multidex.ai`"). For a single step, the narrower tools remain: + resembles a known app the error names it and gives the real URL (a + "did you mean" repair). For a single step, the narrower tools remain: **`icp_find_app_by_name`** (name→URL only), **`resolve_app(url)`** (origin only), **`discover_app_canisters(url)`** (canisters only). 3. **`list_app_accounts`** — if there's more than one account, ask which to use (and @@ -297,10 +309,8 @@ token (see Auth). > and reuse it: `open_app` (or `resolve_app`) turns an app name/URL into it and > reports how — `derivation_origin_source`: **declared** > (`/.well-known/ic-app.json` → `derivation_origin`), else a built-in **known-app** -> value for a few apps that pin a custom origin without declaring it (NNS → -> `https://nns.ic0.app`, Oisy → `https://oisy.com`, MULTI/DEX → -> `https://hcv4s-…icp0.io`, ICPSwap → `https://app.icpswap.com`; an app's own -> declaration always overrides this), else the app origin *assumed* +> value for a few apps that pin a custom origin without declaring it (an app's +> own declaration always overrides this), else the app origin *assumed* > (**app_url_default**). Feeding that resolved origin to an identity tool records > `derivation_origin_source` = **explicit**. Every identity result echoes > `derived_for_origin` (the origin actually used) alongside `requested` (what you @@ -328,7 +338,7 @@ token (see Auth). `icp_list_skills` / `icp_get_skill` expose the official Internet Computer [skills](https://skills.internetcomputer.org) — authoritative, current how-to guides for authoring and shipping IC apps (the Motoko language, the `mops` and -`icp` CLIs, cycles management, stable memory & upgrades, canister security, DeFi, +`icp` CLIs, cycles management, stable memory & upgrades, canister security, auth, …). The catalogue is fetched live from the registry's manifest (`/api/skills.json`, cached ~15 min) and each skill's `SKILL.md` on demand; nothing is bundled, so the agent always sees the current skills. They are also diff --git a/crates/imcp2-core/src/discover.rs b/crates/imcp2-core/src/discover.rs index 8ee8fc0..73072d8 100644 --- a/crates/imcp2-core/src/discover.rs +++ b/crates/imcp2-core/src/discover.rs @@ -1778,7 +1778,8 @@ impl From<(String, Vec)> for FindCanisterOutput { /// Arguments for `icp_find_app_by_name`. #[derive(Debug, Deserialize, schemars::JsonSchema)] pub struct FindAppArgs { - /// The app name to look up, e.g. "Oisy", "NNS", "MULTI/DEX", "ICPSwap". + /// The app name to look up, as the user said it. For a site you already know + /// (e.g. "opencloud.org"), use open_app / resolve_app directly. pub name: String, } @@ -1810,9 +1811,9 @@ pub struct FindAppOutput { /// Arguments for `open_app`. #[derive(Debug, Deserialize, schemars::JsonSchema)] pub struct OpenAppArgs { - /// An app NAME (e.g. "MULTI/DEX", "Oisy", "NNS") OR its URL (e.g. - /// "https://oisy.com"). A name — or a bare host — is matched against the - /// built-in known-app registry first, so a wrong-TLD guess like "multidex.com" + /// An app NAME as the user said it OR its URL (e.g. + /// "https://opencloud.org"). A name — or a bare host — is matched against the + /// built-in known-app registry first, so a wrong-TLD guess /// repairs to the canonical URL; an explicit `https://…` URL is resolved as /// given. NEVER pass a domain you fabricated from a name: an unknown bare name /// is refused with instructions to find the real URL, and a URL with no diff --git a/crates/imcp2-core/src/tools.rs b/crates/imcp2-core/src/tools.rs index fd262a0..fbfcb76 100644 --- a/crates/imcp2-core/src/tools.rs +++ b/crates/imcp2-core/src/tools.rs @@ -515,7 +515,7 @@ impl IcTools { } #[tool( - description = "If the request is about a specific app or the user's own data in it (a balance, holdings, a profile, \"what can it do\"), start with open_app first to resolve the app and discover its canisters, then compose the answer from what it returns, since there is rarely a dedicated per-feature tool. READ from an Internet Computer canister — provide EITHER a Candid `query` method OR an OQL query (exactly one). `method` is a query function from the canister's Candid interface, invoked with textual-Candid `args`; `oql` is an OQL query as a JSON object string, run against the canister's `execute` method (no Candid escaping — write plain JSON). Use `oql` when get_canister_candid reports `oql: true` (a Candid `method` query is then REJECTED — read via OQL); use `method` for a plain query canister such as a ledger. The `oql` path REQUIRES `derivation_origin` (per-app data is caller-gated; an anonymous OQL read is rejected for now) and returns `columns` + `rows` (a markdown table) with `has_more` for paging; on an empty result it validates the query's `start` against the schema and returns valid_entities + a did_you_mean repair. The `method` path may be anonymous, or pass `derivation_origin` + `account` to read AS your account; it returns the decoded reply in textual Candid. Get `derivation_origin` from open_app / resolve_app (not a raw URL), and the OQL entity/field names from get_canister_oql_schema. For state changes use canister_update_call.", + description = "If the request is about a specific app or the user's own data in it (a booking, an order, a profile, \"what can it do\"), start with open_app first to resolve the app and discover its canisters, then compose the answer from what it returns, since there is rarely a dedicated per-feature tool. READ from an Internet Computer canister — provide EITHER a Candid `query` method OR an OQL query (exactly one). `method` is a query function from the canister's Candid interface, invoked with textual-Candid `args`; `oql` is an OQL query as a JSON object string, run against the canister's `execute` method (no Candid escaping — write plain JSON). Use `oql` when get_canister_candid reports `oql: true` (a Candid `method` query is then REJECTED — read via OQL); use `method` for a plain query canister such as a ledger. The `oql` path REQUIRES `derivation_origin` (per-app data is caller-gated; an anonymous OQL read is rejected for now) and returns `columns` + `rows` (a markdown table) with `has_more` for paging; on an empty result it validates the query's `start` against the schema and returns valid_entities + a did_you_mean repair. The `method` path may be anonymous, or pass `derivation_origin` + `account` to read AS your account; it returns the decoded reply in textual Candid. Get `derivation_origin` from open_app / resolve_app (not a raw URL), and the OQL entity/field names from get_canister_oql_schema. For state changes use canister_update_call.", annotations(title = "Query a canister (Candid method or OQL)", read_only_hint = true, destructive_hint = false, open_world_hint = true), output_schema = schema_for_output::(), )] @@ -926,7 +926,7 @@ impl IcTools { } #[tool( - description = "ROUTING. Run this chain before answering anything that names, or implies, a specific app or the user's own data in one app (a balance, holdings, bookings, positions, profile, or \"what can it do\"): (1) Is the request about a specific APP's functionality or the user's data in it, rather than the IC protocol itself? (2) Is that app on the Internet Computer? If unsure, THIS tool (or icp_find_app_by_name) is how you find out: a known app or an IC-verifiable URL resolves, while an unrecognized app name, or a URL with no Internet Computer evidence, is refused rather than guessed. (3) If so, START HERE: one open_app call resolves the app AND discovers its service. An app feature is almost never a single named tool (there is no \"get_balance\"); you COMPOSE it from what open_app returns, reading the discovered canisters and their `oql`/`api_doc_available` flags to see how to READ (canister_query, including OQL) or ACT (canister_update_call). So never conclude \"there's no tool for that\" from the flat tool list without opening the app first. Open an Internet Computer app in ONE call, given its NAME or its URL — the recommended entry point when a user names an app. It resolves the app's Internet Identity derivation origin (like resolve_app) AND discovers the canisters behind it (like discover_app_canisters) together, so you don't chain those yourself. Pass a NAME (e.g. \"MULTI/DEX\", \"Oisy\", \"NNS\") or a URL (e.g. \"https://oisy.com\"): a name or bare host is matched to the built-in known-app registry FIRST (so even a wrong-TLD guess like \"multidex.com\" repairs to the canonical URL), and an explicit https:// URL is resolved as given. NEVER fabricate a domain from a name — an unknown bare name is refused with instructions to find the real URL (web search / ask the user), and a URL with no Internet Computer evidence is refused, both instead of guessing a wrong identity. Returns `app_url` (the one used), `derivation_origin` (+ its source) to act with, `alternative_origins`, and the discovered `canisters` (with provenance/labels AND per-canister `oql`/`api_doc_available` capability flags, from a one-shot Candid probe of the app's own canisters). A canister flagged `oql` holds the app's data, GATED BY THE CALLER's principal: to read the USER's own data (\"my …\", \"our …\") pass the returned `derivation_origin` to get_canister_oql_schema (for the entity/field names) and to canister_query (with the `oql` argument, to run the query). Those OQL reads REQUIRE `derivation_origin` — not `app_url` — and reject an anonymous read for now. No authenticated session required for open_app itself (no principal is derived here). Narrower tools remain for single steps: icp_find_app_by_name (name→URL only), resolve_app (origin only), discover_app_canisters (canisters only).", + description = "ROUTING. Run this chain before answering anything that names, or implies, a specific app or the user's own data in one app (bookings, appointments, orders, a profile, or \"what can it do\"): (1) Is the request about a specific APP's functionality or the user's data in it, rather than the IC protocol itself? (2) Is that app on the Internet Computer? If unsure, THIS tool (or icp_find_app_by_name) is how you find out: a known app or an IC-verifiable URL resolves, while an unrecognized app name, or a URL with no Internet Computer evidence, is refused rather than guessed. (3) If so, START HERE: one open_app call resolves the app AND discovers its service. An app feature is almost never a single named tool (there is no \"get_bookings\"); you COMPOSE it from what open_app returns, reading the discovered canisters and their `oql`/`api_doc_available` flags to see how to READ (canister_query, including OQL) or ACT (canister_update_call). So never conclude \"there's no tool for that\" from the flat tool list without opening the app first. Open an Internet Computer app in ONE call, given its NAME or its URL — the recommended entry point when a user names an app. It resolves the app's Internet Identity derivation origin (like resolve_app) AND discovers the canisters behind it (like discover_app_canisters) together, so you don't chain those yourself. Pass a NAME as the user said it, or a URL (e.g. \"https://opencloud.org\"): a name or bare host is matched to the built-in known-app registry FIRST (so even a wrong-TLD guess repairs to the canonical URL), and an explicit https:// URL is resolved as given. NEVER fabricate a domain from a name — an unknown bare name is refused with instructions to find the real URL (web search / ask the user), and a URL with no Internet Computer evidence is refused, both instead of guessing a wrong identity. Returns `app_url` (the one used), `derivation_origin` (+ its source) to act with, `alternative_origins`, and the discovered `canisters` (with provenance/labels AND per-canister `oql`/`api_doc_available` capability flags, from a one-shot Candid probe of the app's own canisters). A canister flagged `oql` holds the app's data, GATED BY THE CALLER's principal: to read the USER's own data (\"my …\", \"our …\") pass the returned `derivation_origin` to get_canister_oql_schema (for the entity/field names) and to canister_query (with the `oql` argument, to run the query). Those OQL reads REQUIRE `derivation_origin` — not `app_url` — and reject an anonymous read for now. No authenticated session required for open_app itself (no principal is derived here). Narrower tools remain for single steps: icp_find_app_by_name (name→URL only), resolve_app (origin only), discover_app_canisters (canisters only).", annotations(title = "Open an app (resolve origin + discover canisters)", read_only_hint = true, destructive_hint = false, open_world_hint = true), output_schema = schema_for_output::(), )] @@ -1069,7 +1069,7 @@ impl IcTools { } #[tool( - description = "Resolve an application URL to its Internet Identity derivation context, so you don't have to figure out the derivation origin yourself. `app_url` must be a URL you actually HAVE — from the user, from icp_find_app_by_name, or from a web search of the app's official site. NEVER guess or fabricate a domain from an app's name (when you only know a NAME, call icp_find_app_by_name first): a lookalike domain is an unrelated or squatted site, and this tool REFUSES to resolve an origin that shows no evidence of being an Internet Computer app rather than hand back a wrong identity. Returns the `application_origin`, the `derivation_origin` to pass to the identity tools, how it was determined (`derivation_origin_source`: \"declared\" — the app published it in /.well-known/ic-app.json, authoritative; \"known\" — from the connector's built-in registry of well-known custom-derivation-origin apps (e.g. NNS, Oisy, MULTI/DEX), used only when the app declares none; or \"app_url_default\" — the origin IS IC-served but declares nothing, so it was assumed to be its own derivation origin, correct only if the app has no custom one), and the app's `alternative_origins` (informational — the INVERSE relation, never use it to infer the derivation origin). This does NOT return a principal — it resolves the origin only, since you haven't picked an account; to get the principal you act as, pass the returned `derivation_origin` to get_app_principal (choosing an `account`) or list_app_accounts. Use this first when you only know an app's URL; no authenticated session is required.", + description = "Resolve an application URL to its Internet Identity derivation context, so you don't have to figure out the derivation origin yourself. `app_url` must be a URL you actually HAVE — from the user, from icp_find_app_by_name, or from a web search of the app's official site. NEVER guess or fabricate a domain from an app's name (when you only know a NAME, call icp_find_app_by_name first): a lookalike domain is an unrelated or squatted site, and this tool REFUSES to resolve an origin that shows no evidence of being an Internet Computer app rather than hand back a wrong identity. Returns the `application_origin`, the `derivation_origin` to pass to the identity tools, how it was determined (`derivation_origin_source`: \"declared\" — the app published it in /.well-known/ic-app.json, authoritative; \"known\" — from the connector's built-in registry of well-known custom-derivation-origin apps, used only when the app declares none; or \"app_url_default\" — the origin IS IC-served but declares nothing, so it was assumed to be its own derivation origin, correct only if the app has no custom one), and the app's `alternative_origins` (informational — the INVERSE relation, never use it to infer the derivation origin). This does NOT return a principal — it resolves the origin only, since you haven't picked an account; to get the principal you act as, pass the returned `derivation_origin` to get_app_principal (choosing an `account`) or list_app_accounts. Use this first when you only know an app's URL; no authenticated session is required.", annotations(title = "Resolve an app's derivation origin", read_only_hint = true, destructive_hint = false, open_world_hint = true), output_schema = schema_for_output::(), )] @@ -1238,7 +1238,7 @@ impl IcTools { } #[tool( - description = "Find a well-known Internet Computer APP by NAME and get its front-end URL and Internet Identity derivation origin — the missing name→app step (discovery otherwise needs a URL). ALWAYS call this FIRST when the user names an app and you don't have its URL — NEVER guess a domain from the name (lookalike domains like .com/.app are unrelated or squatted sites, and the URL-taking tools refuse origins with no IC evidence). Offline and instant. Covers only a small built-in set (NNS, Oisy, MULTI/DEX, ICPSwap). For ANY other app there is NO on-chain name→URL directory, so the result's `note` directs you to WEB SEARCH the app's official URL (or ask the user) and then use resolve_app / discover_app_canisters. Returns `matches` (each with `app_url` + `derivation_origin`) — usually one, or none. This is name→app; for a token/service→canister-id use icp_find_canister_by_name, and for a URL→canisters use discover_app_canisters.", + description = "Find a well-known Internet Computer APP by NAME and get its front-end URL and Internet Identity derivation origin — the missing name→app step (discovery otherwise needs a URL). ALWAYS call this FIRST when the user names an app and you don't have its URL — NEVER guess a domain from the name (lookalike domains like .com/.app are unrelated or squatted sites, and the URL-taking tools refuse origins with no IC evidence). Offline and instant. Covers only a small built-in set of well-known IC apps. For ANY other app there is NO on-chain name→URL directory, so the result's `note` directs you to WEB SEARCH the app's official URL (or ask the user) and then use resolve_app / discover_app_canisters. Returns `matches` (each with `app_url` + `derivation_origin`) — usually one, or none. This is name→app; for a token/service→canister-id use icp_find_canister_by_name, and for a URL→canisters use discover_app_canisters.", annotations(title = "Find a well-known app by name", read_only_hint = true, destructive_hint = false, open_world_hint = false), output_schema = schema_for_output::(), )] @@ -1284,7 +1284,7 @@ impl IcTools { // ---- ICP skills awareness ---------------------------------------------- #[tool( - description = "List the official Internet Computer skills — authoritative how-to guides for authoring and shipping IC apps (Motoko language, mops/icp CLIs, cycles management, stable memory & upgrades, security, DeFi, auth, …). Returns each skill's name and a one-line description. Load a skill's full instructions with icp_get_skill(name). Consult these BEFORE writing Motoko/Rust canister code, building, or deploying.", + description = "List the official Internet Computer skills — authoritative how-to guides for authoring and shipping IC apps (Motoko language, mops/icp CLIs, cycles management, stable memory & upgrades, security, auth, …). Returns each skill's name and a one-line description. Load a skill's full instructions with icp_get_skill(name). Consult these BEFORE writing Motoko/Rust canister code, building, or deploying.", annotations(title = "List Internet Computer skills", read_only_hint = true, destructive_hint = false, open_world_hint = false), output_schema = schema_for_output::(), )] @@ -1931,8 +1931,8 @@ const SERVER_INSTRUCTIONS: &str = "Internet Computer tools. Every tool speaks TE wallet at https://oisy.com; a refused cycles-ledger canister-creation spend points \ at the user-run icp CLI instead.\n\n\ START WITH open_app WHEN THE USER NAMES OR LINKS AN APP. `open_app(name-or-URL)` is the \ - one-call entry point: it takes an app NAME (e.g. \"MULTI/DEX\", \"Oisy\", \"NNS\") or a \ - URL, resolves the Internet Identity derivation origin AND discovers the canisters \ + one-call entry point: it takes an app NAME as the user said it, or a URL (e.g. \ + \"https://opencloud.org\"), resolves the Internet Identity derivation origin AND discovers the canisters \ together, probes the app's own canisters for their `oql`/`api_doc_available` \ capabilities, and repairs a wrong-TLD guess to the canonical known-app URL — so you do \ NOT chain resolve_app + discover_app_canisters yourself, and you do NOT start from \ @@ -1947,7 +1947,7 @@ const SERVER_INSTRUCTIONS: &str = "Internet Computer tools. Every tool speaks TE id; `icp_lookup_canister_info_by_id(id)` tells you what a bare canister id IS (dashboard \ label, type, controllers, subnet).\n\n\ \"MY / OUR …\" IS AN AUTHENTICATED READ. A question about the USER's OWN data in an app \ - (\"who am I meeting with…\", \"my bookings\", \"our balance\") reads data the app gates \ + (\"who am I meeting with…\", \"my bookings\", \"our open orders\") reads data the app gates \ by the CALLER's principal. An OQL read (get_canister_oql_schema, and canister_query \ with the `oql` argument) REQUIRES the app's \ `derivation_origin` (from open_app / resolve_app) — anonymous per-app reads are \ @@ -2010,7 +2010,7 @@ const SERVER_INSTRUCTIONS: &str = "Internet Computer tools. Every tool speaks TE that way, ask the user to reconnect and choose \"Actions & questions\".\n\n\ Typical flow (acting FOR THE USER at an app): (0-2) `open_app(name-or-URL)` in ONE \ call gives the `derivation_origin` AND the app's canisters (with `oql`/`api_doc_available` \ - flags) — pass the NAME the user said (well-known apps NNS/Oisy/MULTI/DEX/ICPSwap resolve \ + flags) — pass the NAME the user said (well-known apps resolve \ offline) or a URL you have, NEVER a domain guessed from the name (there is no on-chain \ name→URL directory; `icp_find_canister_by_name` finds token/SNS canister ids, not \ front-ends). If you want just one part, `icp_find_app_by_name` does name→URL, \ diff --git a/docs/anthropic-directory-submission.md b/docs/anthropic-directory-submission.md index 28c02b4..1d3adc3 100644 --- a/docs/anthropic-directory-submission.md +++ b/docs/anthropic-directory-submission.md @@ -48,7 +48,7 @@ Verified against the live production deployment (2026-07-31): | Claude Code loopback redirects (RFC 8252) | ✅ loopback redirects are exempt from the hosted allow-list | | Discovery documents (RFC 8414 + RFC 9728, path-scoped + root fallback) | ✅ all four live, `WWW-Authenticate` on the 401 points at the resource metadata | | Every tool: `title` + `readOnlyHint`/`destructiveHint` (+ `idempotentHint`, `openWorldHint`) | ✅ on all 26 tools, enforced by a unit test ([`src/tools.rs`](../src/tools.rs)) | -| No catch-all read/write tool; reads and writes are separate tools | ✅ 17 read-only tools; writes split per operation | +| No catch-all read/write tool; reads and writes are separate tools | ✅ 19 read-only tools (incl. the instructions-only top-up and creation, [#153](https://github.com/dfinity/imcp2/pull/153)/[#154](https://github.com/dfinity/imcp2/pull/154)); writes split per operation | | Tool names ≤ 64 chars | ✅ longest is 30 | | `outputSchema` + structured content on every tool | ✅ enforced by a unit test | | Certificates from a recognized authority | ✅ Let's Encrypt via Caddy | @@ -133,52 +133,54 @@ by the MCP server itself. The page and its route shipped in effective date agree. Then enter that URL in the portal. The reviewed source text is [`icp-mcp-privacy-policy-draft.md`](icp-mcp-privacy-policy-draft.md). -### 2. Financial-transactions policy (decision needed) +### 2. Financial-transactions policy (resolved in code) The Directory Policy **prohibits connectors that transfer money, cryptocurrency, or other financial assets, or execute financial transactions**, and the portal's compliance step requires acknowledging this. -Three IMCP2 capabilities are exposed to that reading: - -- `canister_update_call` can invoke arbitrary update methods as the user — - including ICRC ledger `transfer`/`approve` (i.e. token transfers). -- `icp_create_canister` / `icp_top_up_canister` with the `icp` argument - convert ICP from the user's ledger account via the CMC. - -**Status: asked.** The email to went out on -2026-07-31 and put **two** questions: whether cycles funding (including the -ICP-conversion path) is acceptable, and whether the general-purpose -`canister_update_call` passes review. Awaiting a reply; do not check the -compliance boxes or submit until it lands, since a truthful acknowledgment -isn't possible without it. - -Two things that email leaves open: - -- **The first-party-API acknowledgment was not asked about.** It needs its - own follow-up (see the data-handling draft below). -- **The mitigation it offers for `canister_update_call` — blocking the - well-known ledger `transfer`/`approve` methods — does not actually satisfy - the prohibition,** for the reason in option 2 below: the tool can reach a - custom ledger, or an intermediary canister that forwards a transfer. If the - reply takes that offer up, correct the record before shipping it; committing - to a bypassable control is worse than the current position. - -Options, in increasing order of product impact, once the answer arrives: - -1. **Proceed as-is** if cycles funding (compute-resource payment) and generic - update calls are cleared. -2. **Directory-safe profile.** Serve a restricted instance for the - directory, keeping the full server available as a custom connector at - another path. Note that a blocklist of known ledger canisters or transfer - methods is NOT sufficient: `canister_update_call` can reach any custom - ledger, or an intermediary canister that forwards a transfer. A profile - that actually satisfies the prohibition has to drop the generic update - tool and the `icp` conversion paths entirely, or restrict actions to a - reviewed allow-list of non-financial operations. -3. **Submit anyway**, arguing in the description that funds movement is gated - by the explicit access-level choice on the II consent screen ("Questions - only" vs "Actions & questions"). Only if the reply is inconclusive — the - risk is rejection at the automated scan, burning review-queue time. + +**Mitigations shipped across +[#153](https://github.com/dfinity/imcp2/pull/153) / +[#154](https://github.com/dfinity/imcp2/pull/154) — the stated and enforced +posture is that the server is not a financial tool (this section assumes +both are merged):** + +- `icp_top_up_canister` is **instructions-only**: it executes nothing and + moves no funds — it returns the CLI steps for the user to run a top-up + themselves, and the server-side execution path (cycles-ledger `withdraw`, + CMC `notify_top_up`) is removed from the binary + ([#153](https://github.com/dfinity/imcp2/pull/153)). +- `icp_create_canister` is likewise **instructions-only**: creating and + funding a canister spends the user's ICP or cycles, so the tool returns the + icp CLI steps — ending with the controller-handover step that lets the + connector's lifecycle tools manage the new canister — and executes nothing; + the ICP-ledger transfer, CMC notify, and cycles-ledger creation paths are + removed from the binary ([#154](https://github.com/dfinity/imcp2/pull/154)). +- `canister_update_call` **refuses the standardized ledger methods** that + move value or grant spending rights — the ICRC-standard names + (ICRC-1/ICRC-2 plus ICRC-4/-7/-37) on every canister, and the ICP and + cycles ledgers' own `transfer`/`send_dfx`/`withdraw`/`create_canister` + methods on those ledgers; the refusal recommends the user act themselves + in a wallet they control (oisy.com; a refused canister-creation spend + points at the user-run icp CLI), and the policy is stated in the + server-level instructions — deliberately not in the tool description, + which stays free of financial language per maintainer review + ([#154](https://github.com/dfinity/imcp2/pull/154)). +- The README, the landing page, and the server instructions all state + explicitly that financial transactions are not supported. + +**Posture, stated plainly — the black-and-white answer the compliance step +needs:** no tool initiates or executes a transfer of the user's funds. +Financial ledger methods are refused, and the funding operations — top-up and +canister creation — are instructions-only. The financial-transactions +acknowledgment is made on that basis, without qualifications. + +**Status: resolved in code.** An email to +(2026-07-31) had asked whether cycles funding and the general-purpose +`canister_update_call` pass review; the changes above made both questions +moot — funding no longer executes at all, and the update tool refuses +financial ledger methods. No open compliance question remains on this topic; +if a reply arrives, answer with the shipped posture. Related honesty point for the same step: there is **no per-call confirmation** for sensitive methods server-side today (an open roadmap item in the README) — @@ -253,7 +255,10 @@ Paste-and-adapt; portal limits in parentheses. > or OQL, and discover the canisters behind any IC app from its name or URL. > With your consent it can also act as your Internet Identity accounts at a > specific app, and manage canisters you control: check status, create, - > install code, start/stop, and top up with cycles. + > install code, and start/stop. Financial transactions are not supported: + > token-ledger transfer and approval methods are refused to protect you, + > and cycle top-ups and canister creation return CLI instructions for you + > to run yourself instead of executing anything. > > On the Internet Identity consent screen you explicitly choose the session > duration (10 minutes to 30 days) and the access level: "Questions only" @@ -294,12 +299,12 @@ Paste-and-adapt; portal limits in parentheses. - **Allowed link URIs:** none needed (no `ui/open-link` usage). - **Example prompts** (≥3 required; all work with a fresh Questions-only session): - 1. *"What is canister ryjl3-tyaaa-aaaaa-aaaba-cai? Who controls it and + 1. *"What is canister gftcp-myaaa-aaaar-qcaaa-cai? Who controls it and what's its interface?"* - 2. *"Open the NNS app and list my accounts there."* - 3. *"Find the ckUSDC ledger and show me its Candid interface."* - 4. *"What canisters are behind https://oisy.com, and which one holds the - app's data?"* + 2. *"Open opencloud.org and list my accounts there."* + 3. *"How do I add cycles to canister gftcp-myaaa-aaaar-qcaaa-cai?"* + 4. *"What canisters are behind https://opencloud.org, and which one holds + the app's data?"* ### Reviewer test instructions (draft for the test-credentials field) @@ -310,27 +315,34 @@ Paste-and-adapt; portal limits in parentheses. > Every read-only tool works with any identity, because it reads public > network state. > 2. On the consent screen pick a session duration and an access level: -> "Questions only" exercises the 17 read-only-annotated tools; "Actions & +> "Questions only" exercises the 19 read-only-annotated tools; "Actions & > questions" additionally allows state-changing calls. > 3. Try the example prompts above. Questions-only sessions cause management > tools to return an actionable reconnect message rather than an opaque > error — that behavior is intended. Access is revocable at any time at > https://id.ai/manage/settings. -> 4. The canister-management tools act on canisters you control and spend -> cycles, so a brand-new identity has nothing for them to operate on. Ask -> us at mcp@dfinity.org if you would like an identity provisioned with a -> canister and a cycles balance to exercise those. +> 4. The canister-management tools act on canisters you control, so a +> brand-new identity has nothing for them to operate on. Ask us at +> mcp@dfinity.org if you would like an identity provisioned with a +> canister to exercise those. Canister creation and top-ups return CLI +> instructions for the user to run themselves — nothing is executed. +> 5. Financial ledger operations are refused by design: asking the assistant +> to move tokens returns a policy message directing the user to a wallet +> they control — that behavior is intended (financial transactions are +> not supported). ### The seven compliance acknowledgments Topics: directory guidelines, first-party API usage, financial transactions, AI media generation, prompt injection, conversation-data collection, public -documentation. Two need resolution before the boxes can be checked -truthfully: **financial transactions** (blocker 2) and **first-party API -usage** — DFINITY operates the connector, but it reaches the network through -DAO-governed infrastructure (`icp-api.io`, `id.ai`) and forwards -user-directed calls to third-party application canisters, so "first-party" -is not a clean yes; fold both questions into the mcp-review email. The **prompt-injection** acknowledgment needs open disclosure rather +documentation. **Financial transactions** is a clean acknowledgment: no +tool initiates or executes a transfer of the user's funds — financial ledger +methods are refused, and top-up and canister creation are instructions-only. +**First-party API usage** is answered by describing the architecture as it +is: DFINITY operates the connector itself; it reaches the network through +public Internet Computer infrastructure (`icp-api.io`, `id.ai`) and forwards +user-directed calls to the application canisters the user names — state +exactly that in the acknowledgment. The **prompt-injection** acknowledgment needs open disclosure rather than a bare yes: tool descriptions are static and contain no hidden instructions, but `icp_list_skills`/`icp_get_skill` (and the `skill://` resources) intentionally return DFINITY-published how-to documents fetched diff --git a/docs/openai-directory-submission.md b/docs/openai-directory-submission.md index 31f5b92..71a3a5a 100644 --- a/docs/openai-directory-submission.md +++ b/docs/openai-directory-submission.md @@ -108,13 +108,22 @@ financial-transfers prohibition: canisters. - Commerce rules ("only for physical goods", no digital-goods selling, no embedded checkout) govern *selling through the app*; IMCP2 sells nothing. -- `canister_update_call` can still, in principle, reach token ledgers at the - user's direction. The description should say what the connector is - (infrastructure tooling) and what gates actions (explicit "Actions & - questions" opt-in, network-enforced), the same honesty posture as the - Anthropic submission. There is no published pre-submission contact at - OpenAI equivalent to mcp-review@anthropic.com; the attestations step is - where this is declared. +- The attestation "my plugin does not initiate or execute money transfers, + crypto transfers, or investment trades on behalf of users" is satisfied by + the shipped behavior — check it on that basis: no tool initiates or + executes a transfer of the user's funds. `canister_update_call` refuses + the financial ledger methods (ICRC-1/ICRC-2 and the ICRC-4/-7/-37 + equivalents on every canister, plus the ICP and cycles ledgers' own + value-moving methods on those ledgers), with the policy stated in the + server-level instructions rather than the tool description (which stays + free of financial language, per maintainer review) + ([#154](https://github.com/dfinity/imcp2/pull/154)), and + `icp_top_up_canister` and `icp_create_canister` are instructions-only — + each returns the icp CLI steps for the user to run themselves and executes + nothing ([#153](https://github.com/dfinity/imcp2/pull/153), + [#154](https://github.com/dfinity/imcp2/pull/154)). README, landing page, + and server instructions state that financial transactions are not + supported. ### 4. Test cases (authoring work) @@ -122,24 +131,23 @@ At least 5 positive + 3 negative cases with expected outcomes, passing on web and mobile. Draft set: Positive: -1. "What is canister ryjl3-tyaaa-aaaaa-aaaba-cai?" → identifies the ICP - ledger, its controllers and interface. -2. "Find the ckUSDC ledger and show its interface" → resolves via the - dashboard registry, returns the Candid interface. -3. "What canisters are behind https://oisy.com?" → App Connect discovery - returns the app's canisters with provenance. -4. "Open the NNS app and list my accounts there" (signed in) → resolves the - derivation origin, lists II accounts. +1. "What is canister gftcp-myaaa-aaaar-qcaaa-cai?" → identifies the + canister behind opencloud.org, its controllers and interface. +2. "How do I add cycles to canister gftcp-myaaa-aaaar-qcaaa-cai?" → returns step-by-step icp CLI + instructions; nothing is executed. +3. "What canisters are behind https://opencloud.org?" → App Connect + discovery returns the app's canisters with provenance. +4. "Open opencloud.org and list my accounts there" (signed in) → resolves + the derivation origin, lists II accounts. 5. "Check the status of canister " (signed in, Actions session, identity controls it) → run state, cycles, module hash. 6. "Get the Motoko skill" → returns the skill document. Negative: -1. "Open https://multidex.com" → refused by the IC-evidence gate, with the - did-you-mean error naming the real app (`https://multidex.ai`). The - explicit scheme matters: a bare `multidex.com` is deliberately treated as - a wrong-TLD guess and *repaired* to the canonical URL rather than refused, - so the bare form is not a negative case. +1. "Open https://no-ic.example.com" (a reserved domain with no Internet + Computer presence) → refused by the IC-evidence gate with guidance + (web-search or ask the user for the real URL) rather than resolved to a + wrong identity. 2. A management call on a "Questions only" session → actionable reconnect-with-actions message, not an opaque error. 3. Any authenticated tool with no sign-in → clean 401 → OAuth flow starts diff --git a/src/assets/index.html b/src/assets/index.html index 3718c48..4e487c4 100644 --- a/src/assets/index.html +++ b/src/assets/index.html @@ -405,7 +405,7 @@ \ No newline at end of file