Refuse update calls to known finance-related canisters - #163
Conversation
Per maintainer review on #163: the service no longer exists (its site and repository are gone), so its backend, treasury extension, frontend, and SNS governance come off the list — a dead service does not meet the list's prominence bar. The list stands at 45 entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
There was a problem hiding this comment.
Pull request overview
Adds a finance-canister denylist so all update calls to curated financial services are refused, while retaining method-specific precedence.
Changes:
- Adds and tests 45 labeled finance-canister restrictions.
- Removes NNS from name-based app discovery while preserving explicit URL identity derivation.
- Updates policy documentation and landing-page messaging.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
crates/imcp2-core/src/compliance.rs |
Implements and tests the denylist. |
crates/imcp2-core/src/tools.rs |
Updates server policy instructions. |
crates/imcp2-core/src/discover.rs |
Removes NNS name resolution. |
README.md |
Documents update-call restrictions. |
docs/openai-directory-submission.md |
Updates OpenAI policy explanation. |
docs/anthropic-directory-submission.md |
Updates Anthropic policy explanation. |
src/assets/index.html |
Updates landing-page policy text. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Per maintainer review on #163: the submission docs read as if a gap had been closed at some point, rather than describing a product that simply does not support financial transactions. The financial sections of both directory docs now state the posture present-tense only — the "(resolved in code)" heading, the shipped-mitigations lead with its PR citations, the "execution paths that once moved funds are removed from the binary" sentences, the "per maintainer review" attributions, and the "Status: resolved in code" gap-closure paragraph are gone. The mcp-review email logistics stay (no reply needed; answer with the stated posture if one arrives), and the production-gap blocker keeps its operational fact (deploy a release from current main before submitting) without dwelling on what the old build serves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Answering review on #163 ("why not list all SNS governance canisters?"): enumerating them can't stay current — 54 SNS DAOs exist today (a third of them aborted swaps, some renamed or folded) and new ones launch by NNS proposal. Their value-moving surface is one standardized method, so the guard now refuses `manage_neuron` on EVERY canister, the way it refuses the ICRC transfer/approval names: that covers each existing SNS DAO's governance, every future one, and the NNS's, with no list to go stale. The four per-DAO governance entries come off the canister list (the NNS Governance entry stays — the network's own governance keeps its whole update surface refused), and the standardized-methods group is renamed and re-documented to say it carries both the ICRC token surface and the governance interface. Also per review: the Chain Fusion Signer label had an accidental run of spaces (interpolated verbatim into refusals) — collapsed; and the gate function's doc now describes all three refusal scopes instead of the stale "both groups" wording. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Per review on #163. WaterNeuron (liquid staking): the water_neuron protocol canister (ICP deposits, nICP minting, unstaking withdrawals), the nICP ledger, and the frontend — ids pinned by the project's own canister_ids.json and the dashboard registry; the nICP index/archive (read-only) and the icrc_validator (proposal validator) stay off, as do the two SNS-registered dapp canisters with no documented role. MULTI/DEX (exchange): its backend, bridge, and frontend, self-declared by the app's own /.well-known/ic-app.json manifest (the frontend id also matches the live x-ic-canister-id header and the known-app registry). The list stands at 47 entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
crates/imcp2-core/src/compliance.rs:138
- The PR description calls this a 41-entry canister list, but the static contains 46 entries (44 inline IDs plus
ICP_LEDGERandCYCLES_LEDGER). Update the stated count, or remove any unintended entries, so the documented review scope matches the implemented guard.
const DISALLOWED_FINANCE_CANISTERS: &[(&str, &str, &str)] = &[
Review on #163: the guard refuses manage_neuron everywhere, but the server instructions, README tool row, both directory docs, and the landing page still enumerated only the ICRC methods, the ledger-scoped methods, and the canister list — understating the guard and leaving SNS neuron coverage unexplained. All four surfaces now name the governance method alongside the ICRC names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
crates/imcp2-core/src/compliance.rs:138
- The PR description identifies this as a 47-entry list, but the constant contains 52 entries (6 infrastructure + 3 minters + 14 ledgers + 5 wallets + 3 WaterNeuron + 3 MULTI/DEX + 5 ICPSwap + 5 Sonic + 5 ICDex/ICLighthouse + 3 ICPEx). Reconcile the five-entry discrepancy or update the PR description so the audited inventory count is accurate.
const DISALLOWED_FINANCE_CANISTERS: &[(&str, &str, &str)] = &[
docs/openai-directory-submission.md:120
- The directory submission's exhaustive guard description omits the new global
manage_neuronrule. Because per-SNS governance canisters are intentionally not in the curated list, the current wording understates the protection added by this PR. Includemanage_neuronamong the standardized methods refused on every canister.
`manage_neuron` — neuron staking and disbursement — on every canister,
plus the ICP and cycles ledgers' own
value-moving methods on those ledgers, plus every update call on a curated
crates/imcp2-core/src/tools.rs:2009
- The documented refusal scopes omit the new global
manage_neuronrule. Per-SNS governance canisters are deliberately absent from the curated list, so these server instructions currently do not disclose the restriction that provides coverage for every SNS. Include the NNS/SNSmanage_neuronmethod alongside the globally refused ICRC methods.
equivalents) AND the governance method manage_neuron (neuron staking/disbursement, \
on the NNS and every SNS) on every canister, the ICP and cycles ledgers' own \
transfer/withdrawal/creation methods on those ledgers, and EVERY update call on a \
curated list of known financial-service canisters (token ledgers and minters, \
|
All three requested changes are in 1. OpenAI policy analysis — corrected. You're right, and the old section was wrong in a way that mattered: it read the restriction as narrower. Section 3 of 2. CMC — blocked, and I extended it to four methods. You named Recovery is preserved, just not through the connector: these refusals point at the icp CLI, with the install pointer and the 3. Description now matches behavior.
The test that prohibited the word "financial" there is replaced by 4. Venue names removed (the risk-reduction change). Adopted. Every refusal, the server instructions, the README, the landing page, and the submission docs now say the operation belongs outside the connector, in a trusted interface the user controls. Nothing names a wallet. Two related cleanups: the Canister creation and funding keep the icp CLI pointer, on the reasoning that it's the user's own tooling rather than a transactional venue — and your point 2 asks for exactly that pointer for the recovery path. Say the word if you'd rather those went neutral too. Tests: 153 core (up from 151) + 52/10/9/20/1, all green; clippy at the repo's 10-warning baseline. Generated by Claude Code |
|
@aterga — correct, and Fixed in @sea-snake's removal of the scope enumeration stands. I have not put the method families back, and I agree with the reason: a copy of
The policy now carries the weight as a directive rather than a coverage claim, and a non-refusal is explicitly not permission — the inference the old sentence invited, and the one that actually matters for safety. Three adjacent places asserted the same thing about this text, so they went with it:
A new assertion pins Rebased onto One consequence to flag: this push resets your approval, @sea-snake. Generated by Claude Code |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
crates/imcp2-core/src/tools.rs:2458
- Updating the router count here leaves
docs/scoping-local-deployment.md:109-111stale: it still documents 26 tool definitions and 15 deferredIcProtocolTools, while this change makes those counts 24 and 13. Update that architecture inventory so it continues to describe the codebase accurately.
assert_eq!(tools.len(), 24, "expected 24 tools across both halves, got {}", tools.len());
Four review findings, each verified against the code first. All four were real. 1. The tool inventory was left stale by this PR's own deletions. `docs/scoping-local-deployment.md` said 26 `#[tool]` definitions with 15 deferred; the routers now hold 11 served and 13 deferred, for 24 — the deferred count is pinned by a test that already asserts 13. Fixed. Two more sites described a group that no longer exists: the Rustdoc on `IcProtocolTools` and the section heading both said "canister creation/management", and creation is gone from that group. They now say management, and the Rustdoc says outright that creating and funding are not among these tools. 2. A reviewer step contradicted an enforced test. The refusal deliberately names no venue — a test asserts the message contains no "wallet" — while the Anthropic document told a reviewer to expect "a policy message directing the user to a wallet they control". A reviewer following that step would have found the documented expectation wrong. The step now states the actual wording and says explicitly not to expect a wallet. 3. "Managing canisters is done by the user with the icp CLI" claimed more than the surface does, in both submission documents and in the server instructions. No creation, funding, or dedicated management tool is served, which is the true half. But `canister_update_call` takes an arbitrary canister id and consults only the financial guard: the management canister is not restricted, so a caller whose principal controls a canister can reach its lifecycle methods through the generic tool. All three sites now say the connector serves no such tools rather than that the operations cannot happen, and both documents name the generic path that remains. This does not weaken the financial acknowledgment: management operations move no funds. It removes a claim the surface cannot support, which is what both directories ask a description to do. Workspace green (52 + 10 + 10 + 146 + 21 + 1), clippy at the 10-warning baseline. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Review: the shared CLI redirect is also used for `notify_mint_cycles`, whose operation mints cycles into a cycles-ledger account rather than creating or funding a canister, so "Recommend that the user creates and funds canisters themselves with the icp CLI" gave that caller the wrong next step. Verified: `notify_mint_cycles` is in `CLI_REDIRECT_METHODS`, and that constant's own doc comment already said an interrupted mint is recovered in the CLI too — the intent was there, the redirect text just did not say it. It now covers all three operations, and the doc comment records why the wording is deliberately broader than canisters. The refusal still points only at the user's own terminal, names no connector tool, and keeps the install pointer the test asserts. Workspace green (52 + 10 + 10 + 146 + 21 + 1), clippy at the 10-warning baseline. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
crates/imcp2-core/src/compliance.rs:277
- The non-Bitcoin signing calls only produce signatures; unlike
btc_caller_send, they do not broadcast or themselves move funds. Because this label is interpolated verbatim into the refusal, it should explain that those signatures can authorize asset-moving transactions rather than claiming the signing call executes them.
"its btc_caller_send call signs and broadcasts Bitcoin transactions, and its eth/ecdsa/schnorr signing calls move funds on other chains",
crates/imcp2-core/src/discover.rs:547
- This absolute claim contradicts the static-list limitation documented just above: the guard evaluates every later update, but it only blanket-refuses principals present in
DISALLOWED_FINANCE_CANISTERS; it does not refuse every update to every canister associated with the NNS. Describe the guard invocation without claiming complete destination coverage.
// name yields a URL and a derivation origin for reads, and every update call
// to its canisters is refused by that guard regardless of how the URL was
main gained #165, which drops the landing site in favour of internetcomputer.org/icp-mcp and redirects the old paths. Two conflicts: - `src/assets/index.html` (modify/delete): accepted the deletion. This branch had edited that page's two "refused by design" strings, replacing "use a wallet you control, like oisy.com" with "use a trusted interface you control, outside the connector" — the no-named-venue rule review asked for. That file is gone, so the edit has no home here; the page is maintained in dfinity/internetcomputer-org and the change belongs there. - The Anthropic checklist: took main's privacy-policy line, which names the page's new single home, and kept this branch's financial-acknowledgment line. main's phrasing ("question resolved in code") is the past-gap framing review asked to remove — the posture is stated present-tense, not as a gap we closed. The move also falsified a claim in both submission documents: they said the README, the landing page, and the server instructions all state that financial transactions are not supported. The README does (its opening "Not for financial operations" section) and the instructions do, but the landing page is no longer in this repository, and the page committed in the other one carries no policy text at all. Both documents now claim only the two surfaces this repository controls, and say where the third lives and that adding the posture there is a separate change. Workspace green (52 + 7 + 10 + 146 + 21 + 1), clippy at the 10-warning baseline. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Review caught an inaccuracy I introduced two commits ago. Fixing the CLI-only claim, I wrote that the generic `canister_update_call` can still reach the management canister's lifecycle methods for a controller principal. That is wrong, and the repository says so in its own words. `raw_call` issues `agent.update(&canister, method)` with no `with_effective_canister_id`, so an ingress call to `aaaaa-aa` carries `aaaaa-aa` as its effective canister id. `management.rs`'s `mgmt_call` sets `.with_effective_canister_id(target)` explicitly, and its doc comment states why: "the boundary node requires this for lifecycle methods". So those calls are rejected before they reach a replica; the update-call path is not a route to canister management. All three sites that carried my claim now say that, with the mechanism, so a reviewer can check it rather than take it on trust: the Anthropic posture bullet, its reviewer step 4, and the OpenAI posture bullet. Worth recording what this cost: the original text said canister management is done by the user with the icp CLI, review called that an overclaim, and I widened it on a premise I had not verified against the agent call path. The claim is now no wider than the mechanism behind it. Workspace green (52 + 7 + 10 + 146 + 21 + 1), clippy at the 10-warning baseline; documentation-only change. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
docs/anthropic-directory-submission.md:349
- The reviewer instructions promise a path that the generic tool cannot execute.
canister_update_callusescalls::raw_callwithout setting the target effective canister ID (tools.rs:555,calls.rs:1508), while management lifecycle requests require that routing value (management.rs:316-327). Update this expected behavior so reviewers are not told to test an unavailable route.
> 4. No canister creation, funding, or dedicated management tool is served,
> so there is nothing to provision: that work happens outside the
…nisters #163 was squash-merged as 2279f91, so its commits are not ancestors of main and this stacked branch went un-mergeable against its new base. Merged main in rather than rewriting history. Five conflicts, all this branch's own rewrites against the pre-merge text of the same lines, resolved to this branch: `discover_app_canisters`'s description, SERVER_INSTRUCTIONS and its doc comment, the model-readable-metadata policy test (absent on the other side), the metadata paragraph in the Anthropic document, and its reviewer step 3. Review then found the same defect at seven sites, and it is one this branch created: the corrected `DiscoveredCanister` docs say method names establish neither what a canister stores nor how it authorizes callers, while other surfaces still asserted "per-app data is gated by the caller's principal" as fact. What is actually enforced is narrower and entirely ours: this server rejects an OQL read with no derivation origin. A canister may serve public data, and a Candid `method` read may be anonymous. Reattributed to the connector, with that distinction stated: the server instructions, `icp_oql_guide`'s served text, `canister_query`'s and `get_canister_oql_schema`'s descriptions, `OqlSchemaArgs.derivation_origin`, `CanisterQueryOutput.is_anonymous`, and both README passages. Three implementation comments keep the old shorthand deliberately — they are neither served nor public, so they are notes, not metadata. Two further findings, both real: - The declared-origin path is NOT uniformly fail-soft, and the exception is security-relevant. `decide_declared_origin` returns Err — refusing resolution outright — for a CROSS-origin declaration the declared origin does not authorize in its /.well-known/ii-alternative-origins, with "Refusing to derive an identity here rather than use a wrong one". My earlier wording lumped that in with the fetch failures that fall back to the assumed origin, so a caller could have expected an IC-evidence fallback where resolution actually fails. `open_app`'s and `resolve_app`'s descriptions and the OpenAI readiness row now name it as a hard refusal, so a declaration cannot read as a way around the identity checks. - The README's `get_canister_api_doc` row still promised a structured result "in every case" after the description and output schema had been corrected: an unusable canister id is a plain error, and `expected: true` is not proof of absence. This push resets the approval on this PR. That is the cost of the seven metadata corrections plus a base branch that had to be merged anyway — the merge alone would have reset it. Workspace green (52 + 7 + 10 + 149 + 21 + 1), clippy at the 10-warning baseline. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Both #163 (financial-canister refusals) and #165 (landing site dropped) landed under this branch and touched the same files. Conflicts and how they were taken: - `SERVER_INSTRUCTIONS`: keep this branch's "WRITES GO ONLY TO APPS THAT OPTED IN" paragraph, take main's rewritten FINANCIAL paragraph (which names no venue and states the guard is a safeguard, not a complete filter). The two paragraphs are about different things and both stand. - `canister_update_call` README row: main's fuller financial-policy text, plus this branch's `app_url` argument and declaration gate. - `discover_app_canisters` description: this branch's provenance list, with main's example domain — its new test refuses any ".com" in a tool description, which the old "oisy.com" example would now trip. - Test comments: this branch's two gate tests, then main's rewritten note on `financial_policy_is_a_server_instruction_not_a_description`. The gate ordering survives the merge: `compliance::disallowed_update_method` (now four scopes) still runs first, then the discoverability gate, then any network work. Build and the full suite pass on the merge: 254 tests, 0 failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JarhapgjytMcVSCW7d8sqB
…el (#167) ## Summary The server instructions and the served tool descriptions told a model how to work: `START WITH open_app`, `ALWAYS call this FIRST`, `NEVER guess a domain`, "web search the official URL", a numbered seven-step flow across tools, and a routing chain to run before answering anything. Both directories read these fields, and both prohibit a description that steers the model rather than describing the tool — so every surface now describes what it is, what it returns, what it rejects, and what it requires. What a caller needs is kept, and after review the emphasis moved: both directories *expect* a description to say when a tool applies and what is unsafe to pass, so the rule this PR enforces is not "no guidance" but "none of the five manipulations the directories actually prohibit" — unrelated behavioral instructions, overly broad triggering, preference over or interference with other tools, calls to unrelated external software, and hidden or obfuscated instructions. The second half of the PR became an accuracy pass. Review turned up a recurring defect — metadata claiming more than the code does — and every instance found is fixed and listed below. **Stacked on [#163](#163 (base is that branch, so the diff shows this change alone; GitHub retargets to `main` when #163 merges). It rewrites the same regions #163 touched — the update-call description and the instructions' policy paragraph — so branching off `main` would have lost that work. ## Related issues None — follow-up in the directory-submission series. ## Changes - **Server instructions: 10005 → ~3600 characters.** What remains is a description: where textual Candid applies (and where it does not), what the two tool families act on, what a derivation origin is and which reads it gates, the two Internet Identity access levels, how canister values are stored, the financial-transactions policy (stated as policy — the base branch removed the copy of `compliance.rs`'s method families and canister scopes, and this branch takes that rewrite verbatim), and where building and deploying happen. Gone: the `START WITH` entry-point rule, the routing chain, the numbered "Typical flow", the locale-conversion directives, "web search or ask the user", and the caps imperatives. - **All 11 served descriptions rewritten** as capability statements. `canister_query` loses its "start with open_app first" preamble; `open_app` loses its `ROUTING` block; `resolve_app` and `discover_app_canisters` lose the "NEVER guess / web search" rules. `open_app` carries, per review, the one constraint the runtime cannot enforce: pass the user's app name unchanged, pass only a URL the user supplied or one from a verified official source, and do not construct a domain from the name — because the IC-evidence check shows a domain is served from the Internet Computer, not that it is the app the user meant. - **Argument and reply schemas follow the same rule** (`calls.rs`, `discover.rs`, `identities.rs`): imperatives become plain statements, and "or ask the user for it" becomes what actually provides the value. - **Accuracy fixes, all found in review and each verified against the handler first.** The IC-evidence refusal was asserted unconditionally in six places when it fires only where the derivation origin must be assumed — a declared origin is never probed. `discover_app_canisters` claimed a refusal it does not perform (it returns an empty result). `get_canister_api_doc` promised a structured result "in every case", in its description and again in its root schema. `retry: true` was presented as a diagnosis, though an unreadable interface and a rejecting or trapping method both set it. `api_doc_available` was presented as "a guide is returned" when it reports a declaration — corrected on `get_canister_candid`, `open_app`, the `DiscoveredCanister` schema, and the runtime notes. `ResolveAppOutput.alternative_origins` was attributed to the application origin, though it is read from the derivation origin. `canister_query` promised a did-you-mean repair on any empty OQL result, when it needs an unknown `start` and a schema read that returns entities. `get_canister_oql_schema`'s whole schema documented an anonymous path the handler rejects. Three tools did not disclose their session prerequisite. The identity-mismatch note claimed a wrong derivation origin when a different account or Internet Identity produces the same difference. And the access levels govern only calls signed with the session's delegation, not every update call — which was also wrong in both submission documents' reviewer procedures, where a reviewer following the step would not have seen the promised behavior. - **New test `model_readable_metadata_respects_marketplace_policy`** scans the server instructions, all 11 descriptions, and every argument and reply schema for those five prohibited categories. It checks each schema's *decoded* strings rather than its JSON serialization — escaping turns an invisible character into printable ASCII, which would split a banned phrase and pass the character check — matches phrases on collapsed whitespace, and allows only printable ASCII plus the three punctuation marks the metadata actually uses, so no invisible character can ride along. A second test, `open_app_metadata_forbids_a_constructed_domain`, pins the safeguard above on both surfaces a model reads before calling. - **Submission docs** state the real rule (descriptions explain when a tool applies; the five prohibited manipulations) rather than "must not manipulate how the model works", record the guessed-identifier posture, and drop a stale line claiming the instructions "prime the assistant". Out of scope: the 13 deferred `IcProtocolTools` descriptions are not served and are unchanged. Two behavior changes review raised are deliberately not here, since they are not metadata: classifying retryable agent errors, and extending the IC-evidence probe to declared derivation origins. ## Testing - [x] `cargo build --locked --all-targets` - [x] `cargo test --workspace` — 146 core + 52 / 10 / 9 / 20 / 1, all green - [x] `cargo clippy --workspace --all-targets` — unchanged 10-warning baseline (this repo is not rustfmt-clean; no repo-wide `cargo fmt`, matching prior PRs) - [ ] `npm test --prefix monitoring/mcp-status` (dashboard unchanged) ## Checklist - [x] I have read the [Contributing guidelines](../CONTRIBUTING.md). - [x] Docs (README / comments) updated for any user-visible change. - [x] No secrets, credentials, or internal-only information are included. --- _Generated by [Claude Code](https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk)_ --- _Generated by [Claude Code](https://claude.ai/code)_
Resolves the conflicts #163's follow-ups (#167) and the landing-site move (#165) created against the write gate: - compliance.rs: takes main's four-group guard wholesale (the CMC's funding-completion methods are listed there now, so the branch's "deliberately NOT listed" note went away) and re-applies only the Layer-2 framing plus the note that Layer 1, not this list, bounds the un-enumerable remainder. - tools.rs: the gate is now described in #167's factual register — no ordering rules, no imperatives — on canister_update_call, resolve_app, discover_app_canisters and in SERVER_INSTRUCTIONS, which keeps the surface inside the marketplace-policy scan. resolve_app's "declared" source and discovery's app-declared sources are corrected to ii-derivation-origin and ic-architecture. - The Developer Terms page follows every other human-facing page to dfinity/internetcomputer-org: its source text lives in docs/icp-mcp-developer-terms-draft.md (the privacy policy's convention), DEVELOPER_TERMS_URL points at internetcomputer.org/icp-mcp/developer-terms/, and this origin redirects /developer-terms there. The anti-drift test moves with it: the source text must name the revision the gate enforces, and the URL refusals carry must be the page this origin redirects to. - Refusal-text assertions follow main's guard, which no longer names a venue. cargo test --locked --workspace --all-targets: 263 tests green; clippy unchanged at the 6-warning pre-existing baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SzGAjX3k9utczAg2Jw8yQX
A minor bump rather than a patch, because both the library API and the served tool surface changed incompatibly since 0.2.0. `imcp2` no longer contains the tool surface and the II session engine: they moved to a new sibling crate, `imcp2-core` (#150, #157), which `imcp2` now depends on by version and which publishes alongside it. The re-exports in `src/lib.rs` keep the familiar names resolving (`IiInstance`, `SessionGauges`, `Agent`, `IC_URL`), but the crate an embedder links has changed shape, and `IcTools` is gone — split into `IcCanisterTools` and `IcProtocolTools` (#157). The surface an MCP client sees changed too: only the app/canister tools are served (#158), state-changing calls now reach only applications that publish a service declaration (#166), the ledger transfer/approval methods are refused (#154, #163), `icp_top_up_canister` is instructions-only (#153), and routine replies no longer echo the per-app principal (#177). Under cargo's 0.x rules `0.2` is the compatibility range, so shipping this as 0.2.1 would hand the break to everyone on `imcp2 = "0.2"` — the requirement our own README recommends — the next time they ran `cargo update`. 0.3.0 opens a new range instead; existing users stay on 0.2.x until they choose to move. The README's dependency line follows, as `"0.2"` would no longer resolve to a current release. `imcp2-core` moves in lockstep: publish-crate.yml checks the tag against both manifests, and the workspace pin that binds them (`imcp2-core = { path = …, version = … }`) has to name the same number or the path dependency stops resolving. `imcp2-local` moves with them — it releases on its own `imcp2-local-v*` tags and has never been cut, so there is nothing to keep it behind, and its `--version` output would otherwise report 0.2.0 from a 0.3.0 tree. Claude-Session: https://claude.ai/code/session_01GqhkbpCwxs67F5E2EzyDwh Co-authored-by: Claude <noreply@anthropic.com>
Extends the financial-transactions guard in two ways. First,
manage_neuron— the one standardized method through which NNS and SNS governance neurons are disbursed, split, and spawned — joins the always-refused method names alongside the ICRC transfer/approval surface, covering every SNS DAO's governance (54 exist today, more launch by NNS proposal) and every future one with no list to go stale. Second, a curated, labeled disallow list of finance-related canisters on whichcanister_update_callrefuses every update method — closing the gap where value could still move through non-standardized methods (a DEX swap, a minter withdrawal) that no method-name list can enumerate. Each canister entry carries two labels — what service it provides, and how it is finance-related — interpolated verbatim into the refusal, e.g.:Each of the three refusal scopes gets the register that is true of it: a value-moving method says the call itself carries out that operation;
manage_neuronsays the entry point is disabled as a whole, so the refusal does not mean this particular command moves staked assets; a listed destination says what the quote above says. None of them classifies every blocked call as asset-moving.The canister list (52 entries — count verified programmatically, per review)
Every id was verified against the IC dashboard's canister registry (
ic-api.internetcomputer.org, exact registered names) and the services' own published sources (theircanister_ids.json/ manifests / docs / on-chain SNS records) on 2026-08-28; the dashboard-unnamed entries are each pinned by a project-official source (DFINITY's own repos, apps' own/.well-known/ic-app.jsonmanifests, livex-ic-canister-idheaders, SNS proposal payloads).manage_neuron), both NNS dapp frontends (classicqoctq…and currentmc7vh…origins), the Genesis Token canister (claim_neurons), the ICP ledger, the cycles ledger.retrieve_btc), ckETH/ckERC20 (withdraw_eth/withdraw_erc20), ckDOGE (retrieve_doge).canister_ids.json./.well-known/ic-app.json); ICPSwap (SwapFactory, PasscodeManager, farm + staking-pool factories, frontend); Sonic (swap canister, LBP registry, vesting, plus the XTC and WICP DIP-20 ledgers — their legacytransfer/mintnames are exactly what the standardized-methods group deliberately does not match, so the canister entries close that documented gap for the two most prominent legacy ledgers); ICDex/ICLighthouse (router, DexAggregator, DAO trader, trading-mining, frontend); ICPEx (router, token-creation service, frontend).Deliberately not listed
manage_neuronmethod rule instead (per review): a static list of 54 DAOs (a third of them aborted swaps, some renamed or folded) would go stale the day the next SNS launches, while the method rule covers them all, present and future.Mechanics
notify_top_up,notify_create_canister,notify_mint_cycles, andcreate_canisteron the CMC each complete a funding operation against ICP the user already sent. They were first left out as the recovery path for an interrupted user-run CLI flow; that recovery still exists, in the CLI, which is where the refusal points. The redirect is worded for all three operations, since a cycles mint targets a cycles-ledger account rather than a canister.manage_neuronrefused on any canister; every canister entry refuses arbitrary update names with both labels + the protective policy wording and no marketplace/compliance jargon; the list is well-formed (valid, unique principals; non-empty labels); precedence pinned. The literal-matching test demonstrates on an unlisted canister (on a listed one, everything is refused regardless of spelling).manage_neuronrule alongside the ICRC names (per review). This branch also removed the named venue from the landing page's two "refused by design" strings ("use a wallet you control, like oisy.com" → "use a trusted interface you control, outside the connector"), but Drop the landing site in favour of internetcomputer.org/icp-mcp; the old page paths redirect permanently #165 has since droppedsrc/assets/index.htmlfrom this repository — the page's one home is now internetcomputer.org/icp-mcp, maintained in dfinity/internetcomputer-org — so that edit is gone from this PR and needs making in that repository instead. The page committed there states no financial policy at all, and both submission documents now say so rather than counting it as a third surface that states the posture.SERVER_INSTRUCTIONSdeliberately does NOT: the method families, canister scopes and curated-list hedge were removed from it, because they were a copy ofcompliance.rsto keep in sync forever and an attempted call already receives a refusal accurate for its own scope. What it states is the policy, plus the limit of the guard behind it — the guard is a safeguard, not a complete filter, so the policy rather than the absence of a refusal is the boundary. Per review, the directory docs state the posture present-tense only — no history of past functionality.Verification
cargo testworkspace-green: 146 core + 21 local tests (52 + 10 + 10 + 146 + 21 + 1 across the workspace).cargo clippyat the existing 10-warning baseline.ic-api.internetcomputer.orgbefore curation (exact name matches, or unnamed-but-source-pinned; rejected/undeployed ids excluded).🤖 Generated with Claude Code
https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Generated by Claude Code