Skip to content

Refuse ledger transfer/approval methods in canister_update_call - #154

Merged
aterga merged 12 commits into
mainfrom
claude/icp-marketplace-compliance-vpawel-2-ledger-guard
Aug 27, 2026
Merged

Refuse ledger transfer/approval methods in canister_update_call#154
aterga merged 12 commits into
mainfrom
claude/icp-marketplace-compliance-vpawel-2-ledger-guard

Conversation

@aterga

@aterga aterga commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Second of three marketplace-compliance PRs preparing ICP MCP for the Anthropic and OpenAI directories (companion to the top-up instructions-only PR — merged as #153 — and the financial-framing cleanup PR). Directory policy prohibits a connector from initiating or executing money/crypto transfers or trades on the user's behalf.

Two changes, both per review:

  1. canister_update_call could previously reach token ledgers at the user's direction (e.g. icrc1_transfer on any ICRC ledger). This PR adds a guard of disallowed financial ledger methods, states the policy in the server-level instructions in plain protective terms (financial transactions are not supported — asset-moving requests are denied, to protect the user; deliberately NOT in any tool's own description, which would read as a hint at financial usability), and turns each refusal into a recommendation: the user performs the operation themselves — in a wallet or frontend they control (e.g. https://oisy.com), or, for canister creation, with the icp CLI in their own terminal.
  2. icp_create_canister gets the same treatment as canister top-ups (Make icp_top_up_canister instructions-only: never execute funding #153): it is now instructions-only — it prints the icp CLI steps (create, fund, and add the connector's management principal as a controller so the lifecycle tools can operate the new canister) and executes nothing; the executing ICP-ledger-transfer/CMC/cycles-ledger paths are removed from the binary. With that, no tool initiates or executes a transfer of the user's funds, and the creation refusal may name icp_create_canister — it is the thing that prints those steps, not an executing route.

Related issues

None — part of the marketplace-compliance track.

Changes

  • New crates/imcp2-core/src/compliance.rs — the guard, in two groups (per review):
    • ICRC-standard methods, matched literally on every canister — the standards fix the exact names, and Candid method names are case-sensitive, so literal matching is both sufficient and precise: icrc1_transfer; icrc2_approve, icrc2_transfer_from; icrc4_transfer_batch; icrc7_transfer; icrc37_transfer_from, icrc37_approve_tokens, icrc37_approve_collection, icrc37_revoke_token_approvals, icrc37_revoke_collection_approvals.
    • Abstract names, scoped by canister id to the system ledger where they are financial — so an app whose own transfer/withdraw is non-financial keeps working: on the ICP ledger (ryjl3-…): transfer, send_dfx, notify_dfx; on the cycles ledger (um5iw-…): withdraw, withdraw_from, create_canister, create_canister_from.
    • Legacy DIP20/EXT names on arbitrary canisters (transfer/transferFrom/approve) are deliberately not matched — too abstract to block everywhere without breaking non-financial apps (per review); the stated policy covers them.
  • canister_update_call runs the gate right after parsing the canister id, before any network work. Queries need no gate (a query cannot commit state). CMC notify_create_canister/notify_top_up are deliberately not listed — they move no funds out of any account, and they are the recovery path for a user's own interrupted icp-CLI mint.
  • The refusal states the policy in plain terms an agent can relay — financial transactions are not supported, asset-moving requests are denied, to protect the user (no marketplace/compliance jargon, no bypass-hinting sentence; per review) — and redirects to a user-controlled wallet, or, for creation, to the user-run icp CLI (icp_create_canister is named only as the instructions-printer).
  • The policy is a server-level instruction (a new SERVER_INSTRUCTIONS const backing get_info); no tool description carries financial-policy language (per review) — canister_update_call's description is purely functional, and the instructions-only tools' descriptions state just the functional fact (prints steps, never executes).
  • icp_create_canister is instructions-only: management::create_canister_instructions is a pure function (validated cycles/icp amounts echoed into copy-pastable commands, same as top-up), and the executing machinery — cmc_icp_deposit, notify_create_canister, the cycles-ledger create_canister call, their wire types and helpers (~370 lines) — is removed from the binary. Annotations flip to read-only/non-destructive (a pure read).
  • Tests pin literal matching, the per-ledger scoping in both directions, the allowed set, the refusal wording, the policy placement (descriptions free of financial language; the server instructions carry the denial), and the instructions-only contract for both funding tools (echoed amounts, controller-handover step, executed: false). README's tool table and management section document the behavior.

Testing

  • cargo build --locked --all-targets
  • cargo test --locked --all-targets (all pass; 147 tests in imcp2-core)
  • cargo clippy --all-targets — no new warnings (10 pre-existing, unchanged); formatting matched to surrounding style (a repo-wide cargo fmt on current stable reformats unrelated files)
  • npm test --prefix monitoring/mcp-status (dashboard unchanged)

Checklist

  • I have read the Contributing guidelines.
  • Docs (README / comments) updated for any user-visible change.
  • No secrets, credentials, or internal-only information are included.

Marketplace directories (Anthropic, OpenAI) prohibit connectors that
initiate or execute money or crypto transfers on the user's behalf, and
this server's purpose is reading, building, and operating canisters —
not moving funds.

canister_update_call now refuses the update methods that move value or
grant spending rights on ICP ledgers, on every canister, before any
network work. The disallow list leans on the ICRC standards that ledgers
on ICP follow — ICRC-1/ICRC-2 (fungible transfers and approvals), ICRC-4
(batch transfers), ICRC-7/ICRC-37 (NFT transfers and approvals) — plus
the ICP ledger's pre-ICRC surface (transfer, send_dfx, notify_dfx), the
cycles ledger's withdraw/withdraw_from, and the ERC-20-style names of
the older DIP20/EXT token standards (transferFrom, approve). Matching is
exact on a normalized name (lowercased, separators stripped), never a
substring test, so spelling variants are caught without false positives
on names that merely contain a blocked one.

The refusal is a recommendation, not a dead end: it tells the agent to
send the user to a wallet or frontend they control in their own browser
(e.g. https://oisy.com; for governance, the NNS dapp). The tool
description states the policy up front — financial transactions are not
supported, for marketplace compliance and user safety — and a test pins
both the description and the refusal wording.

The CMC's notify_create_canister / notify_top_up stay callable: they
move no funds out of any account and blocking them would strand the
documented recovery path for an interrupted ICP funding flow. The module
docs are explicit that a name-based list is a guardrail for the
standardized ledger surface, not a hermetic seal over bespoke canisters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
@aterga
aterga requested a balanced review from Copilot August 26, 2026 13:57
@aterga
aterga marked this pull request as ready for review August 26, 2026 13:57
@aterga
aterga requested a review from a team August 26, 2026 13:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a compliance guard preventing standardized ledger transactions through generic canister update calls.

Changes:

  • Adds normalized disallowed-method matching and tests.
  • Enforces the guard before network activity.
  • Documents the financial-operation restriction.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
README.md Documents the restriction.
crates/imcp2-core/src/tools.rs Enforces and describes the guard.
crates/imcp2-core/src/lib.rs Registers the compliance module.
crates/imcp2-core/src/compliance.rs Defines blocked methods and tests.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/imcp2-core/src/compliance.rs Outdated
Comment thread crates/imcp2-core/src/tools.rs Outdated
…view)

Address the Copilot review on the ledger-method guard:

- The cycles ledger's create_canister / create_canister_from draw cycles
  from the caller's balance through the exact generic route this guard
  closes (withdraw was already blocked for that reason), so they join
  the disallow list. Their refusal redirects to the purpose-built
  icp_create_canister tool (or the icp CLI) rather than to a wallet —
  creation stays supported, only the uncontrolled generic route closes.
- The absolute 'this server does not move financial assets' wording in
  the tool description and the refusal message is scoped to 'this tool':
  precise on this branch standalone (icp_create_canister still funds at
  creation) and after the companion top-up change merges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 26, 2026 14:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread crates/imcp2-core/src/tools.rs Outdated
aterga pushed a commit that referenced this pull request Aug 26, 2026
…iew)

Address the reviews on the framing cleanup:

- Per author feedback, Oisy (also a DeFi app) stops serving as the
  neutral usage example: name examples use NNS (the registry app that
  fits), and URL / bare-host examples use https://opencloud.org — live
  and IC-served (x-ic-canister-id present), so it passes the IC-evidence
  gate as documented. Oisy remains only where a wallet is genuinely
  meant: the refusal recommendation for financial operations.
- The absolute 'never moves money or tokens' claims flagged by the
  review bot become enforcement-scoped statements: README, the landing
  trust card, and the server instructions now say token transfers,
  approvals, payments, and trades ARE REFUSED (via the update-call guard
  and the instructions-only top-up) and disclose that creating and
  funding the user's own canisters remains available through
  icp_create_canister.
- The OpenAI submission doc presents the shipped changes as mitigations
  toward a still-pending attestation decision (the icp_create_canister
  funding path and the bespoke-method limitation are named as the open
  counterexamples), not as grounds to check the box. The Anthropic doc's
  'shipped' section is phrased against the PR set (#153/#154) it
  describes rather than any single branch's tree.

This PR is documentation/framing for the enforcement #153 and #154
ship; merge order remains #153, #154, then this.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
…iew)

The review bot's follow-up is right that a method deny-list cannot make
the generic update tool UNABLE to move assets — a bespoke method name is
out of any list's reach, which the module docs and submission docs
already record. The description and refusal therefore stop claiming
impossibility: they state the binding policy (moving financial assets
through this tool is not supported and must not be attempted under any
method name) alongside what is mechanically enforced (the standardized
ledger surface is refused on every canister). Whether to go further —
dropping the generic update surface or moving to a reviewed allow-list —
is the recorded 'directory-safe profile' option and stays a product
decision, not something this PR takes on its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 26, 2026 14:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

…e-compliance-vpawel-2-ledger-guard

# Conflicts:
#	crates/imcp2-core/src/tools.rs
Copilot AI review requested due to automatic review settings August 26, 2026 14:20
aterga pushed a commit that referenced this pull request Aug 26, 2026
The framing PR now contains the enforcement it describes: main (with
the merged instructions-only top-up, #153) and the ledger-method guard
branch (#154) are merged in, and the PR is retargeted onto #154's
branch so it cannot merge ahead of it — the ordering the review kept
flagging is now mechanical rather than a note in the description. The
Anthropic readiness row flips to verified since #153 is merged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread crates/imcp2-core/src/compliance.rs Outdated
Comment thread README.md Outdated
Per author direction on the PR: no web system can tell from the outside
whether a given request moves funds, so the honest claim is intention,
not capability. The description now reads 'not intended for moving
money, tokens, or other financial assets on the user's behalf' with the
ledger-method refusals presented as the reasonable measures taken to
refuse asset-moving requests; the refusal message matches, and the
description test pins the new header phrase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 26, 2026 14:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

README.md:144

  • The blanket statement “Financial transactions are refused” overstates this name-based guard: as the new compliance module explains, bespoke value-moving methods remain callable. This also conflicts with the intentionally narrower “not intended” wording in the tool description. Limit the claim to standardized ledger methods so the public documentation accurately describes the enforcement.
| `canister_update_call` | `canister_id`, `method`, `args` (textual Candid), `derivation_origin?`, `account?`, `candid?` | Make an UPDATE (state-changing) call; reply as textual Candid; anonymous, or as your account at an app (identified by its canonical II `derivation_origin`, obtained once from `open_app`/`resolve_app`). **Financial transactions are refused**: token-ledger transfer/approval methods (ICRC-1/ICRC-2 and related ICRC standards, the ICP ledger's legacy `transfer`, the cycles ledger's `withdraw` and `create_canister` spends) are disallowed on every canister, for marketplace compliance and user safety — the refusal directs the user to act themselves in a wallet they control (e.g. [oisy.com](https://oisy.com)), or to the dedicated `icp_create_canister` tool for canister creation. `candid` is the same `.did` fallback as on `canister_query`, used when the interface isn't published on-chain. Echoes `derived_for_origin` / `requested` / `acted_as_principal` |

crates/imcp2-core/src/compliance.rs:32

  • This quote claims the description says the server “does not support” financial transactions, but the description was deliberately softened to “not intended for” them because bespoke value-moving methods remain callable. Keep the module documentation aligned with that actual contract rather than preserving the impossibility-style claim.
//!     no name-based list can enumerate. The policy itself ("this server
//!     does not support financial transactions") is stated in the tool
//!     description; this guard enforces it for the standardized ledger

sea-snake
sea-snake previously approved these changes Aug 26, 2026
Comment thread crates/imcp2-core/src/compliance.rs Outdated
Per author feedback: tokens are only valuable if they can be exchanged
or used, and the ICP ecosystem's financial platforms (wallets like Oisy,
exchanges like ICP Swap) integrate ledgers through the ICRC standards —
a token on a bespoke, non-standard ledger can exist, but the ecosystem's
platforms cannot hold or trade it, so it carries little exchangeable
value. Also syncs the quoted policy wording in the same note with the
intent framing the description now uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 27, 2026 08:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread crates/imcp2-core/src/compliance.rs
Comment thread crates/imcp2-core/src/compliance.rs Outdated
Comment thread crates/imcp2-core/src/compliance.rs Outdated
Comment thread crates/imcp2-core/src/compliance.rs Outdated
Per review: recommending icp_create_canister inside the refusal reads as
this server supporting canister creation on the user's behalf, which is
not what the refusal should communicate. The cycles-ledger create_canister
/ create_canister_from refusal now points the user at running the
operation themselves with the icp CLI (with the install pointer), like
every other refusal points at a user-controlled venue. README row updated
to match; the test now pins that the refusal names the CLI and never the
connector tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Comment thread crates/imcp2-core/src/tools.rs Outdated
Copilot AI review requested due to automatic review settings August 27, 2026 13:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread crates/imcp2-core/src/tools.rs Outdated
Comment thread crates/imcp2-core/src/compliance.rs Outdated
Per review, three changes to how the policy is stated:

- The policy paragraph moves out of canister_update_call's description
  into the server-level get_info instructions (now a SERVER_INSTRUCTIONS
  const so the test can pin it): a policy block inside the tool's own
  description reads as a hint that the tool is usable for financial
  transactions. The description is purely functional again.
- The wording drops the marketplace/compliance rationale and the
  'reasonable measures' hedge: it now simply states that financial
  transactions are not supported and asset-moving requests are denied,
  to protect the user.
- The refusal message loses the 'do not route under another method name'
  sentence, which read as a hint that the guards could be bypassed.

README row aligned; tests updated to pin the description staying free of
financial language and the instructions carrying the policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 27, 2026 13:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

crates/imcp2-core/src/tools.rs:1934

  • This server-wide claim is factually false while icp_create_canister remains exposed: that tool spends cycles via the cycles ledger or transfers ICP to the CMC (management.rs:304-356), and these same instructions later recommend icp_create_canister(icp=Y) at line 2057. Either scope this paragraph specifically to canister_update_call, or make canister creation instructions-only before claiming all asset-moving requests are denied.
             FINANCIAL TRANSACTIONS ARE NOT SUPPORTED — asset-moving requests are denied, to \
             protect the user: canister_update_call refuses the ICRC-standard transfer/approval \
             methods (icrc1_transfer, icrc2_approve, icrc2_transfer_from, and the ICRC-4/-7/-37 \
             equivalents) on every canister, and the ICP and cycles ledgers' own \
             transfer/withdrawal/creation methods on those ledgers. For financial operations \

Comment thread crates/imcp2-core/src/tools.rs
@aterga
aterga requested a review from sea-snake August 27, 2026 13:35
Per maintainer review across the compliance PRs: creating and funding a
canister spends the user's ICP or cycles, so the connector must not
execute it. icp_create_canister now works exactly like the top-up tool
(#153): it returns the icp CLI steps for the user to run themselves —
install the CLI, check/mint cycles, icp canister create, and finally add
the connector's management principal (printed by icp_cycles_balance) as a
controller so icp_install_code and the lifecycle tools can operate the
new canister. The executing paths are removed from the binary: the
ICP-ledger transfer to the CMC, notify_create_canister, and the
cycles-ledger create_canister call, with their wire types and helpers.

With this, no tool initiates or executes a transfer of the user's funds:
the server-wide 'asset-moving requests are denied' instruction now has no
executing counterexample. The compliance refusal for cycles-ledger
creation spends may name icp_create_canister again — it prints steps and
executes nothing — and the CMC notify_* methods stay callable as the
recovery path for a user's own interrupted CLI mint. README and server
instructions updated; tests moved to pin the instructions-only contract
(annotations, description, echoed amounts, controller handover step).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 27, 2026 13:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread crates/imcp2-core/src/compliance.rs
Per review: policy language belongs in the server-level instructions, not
in per-tool descriptions. The top-up and creation descriptions now state
the one functional fact a calling model needs — the tool only prints the
icp CLI steps and never executes the operation — without the
provided-for-completeness / never-moves-funds disclaimer prose. Tests pin
the functional wording and that the descriptions stay free of financial
language.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk
Copilot AI review requested due to automatic review settings August 27, 2026 14:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

crates/imcp2-core/src/compliance.rs:131

  • The creation refusal again points callers to another connector tool, even though the PR description and the resolved review thread explicitly require this path to recommend only a user-run CLI. Keep the refusal self-contained and direct the user to the CLI/install guide without naming icp_create_canister.
        "Recommend that the user creates and funds canisters themselves with \
         the icp CLI in their own terminal — the icp_create_canister tool \
         prints the exact steps (it executes nothing and moves no funds), and \
         the icp-cli and cycles-management skills (icp_get_skill) carry the \
         full guide."

@aterga
aterga merged commit 5822c74 into main Aug 27, 2026
8 checks passed
aterga added a commit that referenced this pull request Aug 27, 2026
…tions (#155)

* Refuse ledger transfer/approval methods in canister_update_call

Marketplace directories (Anthropic, OpenAI) prohibit connectors that
initiate or execute money or crypto transfers on the user's behalf, and
this server's purpose is reading, building, and operating canisters —
not moving funds.

canister_update_call now refuses the update methods that move value or
grant spending rights on ICP ledgers, on every canister, before any
network work. The disallow list leans on the ICRC standards that ledgers
on ICP follow — ICRC-1/ICRC-2 (fungible transfers and approvals), ICRC-4
(batch transfers), ICRC-7/ICRC-37 (NFT transfers and approvals) — plus
the ICP ledger's pre-ICRC surface (transfer, send_dfx, notify_dfx), the
cycles ledger's withdraw/withdraw_from, and the ERC-20-style names of
the older DIP20/EXT token standards (transferFrom, approve). Matching is
exact on a normalized name (lowercased, separators stripped), never a
substring test, so spelling variants are caught without false positives
on names that merely contain a blocked one.

The refusal is a recommendation, not a dead end: it tells the agent to
send the user to a wallet or frontend they control in their own browser
(e.g. https://oisy.com; for governance, the NNS dapp). The tool
description states the policy up front — financial transactions are not
supported, for marketplace compliance and user safety — and a test pins
both the description and the refusal wording.

The CMC's notify_create_canister / notify_top_up stay callable: they
move no funds out of any account and blocking them would strand the
documented recovery path for an interrupted ICP funding flow. The module
docs are explicit that a name-based list is a guardrail for the
standardized ledger surface, not a hermetic seal over bespoke canisters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Remove financial framing; state the server is not for financial operations

The marketplace-compliance posture (top-up instructions-only, ledger
transfer/approval methods refused) needs the surrounding material to say
the same thing: this MCP server is infrastructure tooling and is not
intended for financial operations.

- README: a 'Not for financial operations' statement up front; the
  skills enumeration drops DeFi; DEX-app names (MULTI/DEX, ICPSwap) in
  examples give way to neutral ones (NNS, Oisy), with the known-app
  registry and its wrong-TLD repair behavior unchanged in code.
- Tool descriptions and server instructions: same example cleanup, plus
  a NOT FOR FINANCIAL OPERATIONS paragraph in the server instructions
  that tells agents to send users to a wallet they control (oisy.com)
  for such operations.
- Landing page (src/assets/index.html bundle): the wallet-balance chat
  mock becomes an app-data read, the portfolio/DEX example prompts
  become neutral app examples, the tool-group copy stops claiming
  server-side top-ups, and the trust section gains a 'Not for financial
  operations' card. Only the embedded template changed; decoded-value
  equality is asserted around the re-encode.
- Cargo.toml: drop the cryptography::cryptocurrencies crates.io
  category — network/api tooling categories remain.
- Directory-submission docs: the financial-transactions blockers now
  record the shipped posture (instructions-only top-up, ledger-method
  refusals, stated policy) and keep the honest caveats: the
  icp_create_canister icp path still executes an ICP-to-cycles
  conversion and needs an explicit decision, and a name-based guard
  covers the standardized ledger surface, not bespoke canisters. The
  listing description draft and reviewer instructions match the new
  behavior.

Factual technical references to ledgers (reading a ledger is a public
Candid query) and the ckUSDC lookup examples stay: identifying what a
canister is remains core, non-financial functionality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Block cycles-ledger creation spends; scope the no-financial claim (review)

Address the Copilot review on the ledger-method guard:

- The cycles ledger's create_canister / create_canister_from draw cycles
  from the caller's balance through the exact generic route this guard
  closes (withdraw was already blocked for that reason), so they join
  the disallow list. Their refusal redirects to the purpose-built
  icp_create_canister tool (or the icp CLI) rather than to a wallet —
  creation stays supported, only the uncontrolled generic route closes.
- The absolute 'this server does not move financial assets' wording in
  the tool description and the refusal message is scoped to 'this tool':
  precise on this branch standalone (icp_create_canister still funds at
  creation) and after the companion top-up change merges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Use opencloud.org example; scope financial claims to enforcement (review)

Address the reviews on the framing cleanup:

- Per author feedback, Oisy (also a DeFi app) stops serving as the
  neutral usage example: name examples use NNS (the registry app that
  fits), and URL / bare-host examples use https://opencloud.org — live
  and IC-served (x-ic-canister-id present), so it passes the IC-evidence
  gate as documented. Oisy remains only where a wallet is genuinely
  meant: the refusal recommendation for financial operations.
- The absolute 'never moves money or tokens' claims flagged by the
  review bot become enforcement-scoped statements: README, the landing
  trust card, and the server instructions now say token transfers,
  approvals, payments, and trades ARE REFUSED (via the update-call guard
  and the instructions-only top-up) and disclose that creating and
  funding the user's own canisters remains available through
  icp_create_canister.
- The OpenAI submission doc presents the shipped changes as mitigations
  toward a still-pending attestation decision (the icp_create_canister
  funding path and the bespoke-method limitation are named as the open
  counterexamples), not as grounds to check the box. The Anthropic doc's
  'shipped' section is phrased against the PR set (#153/#154) it
  describes rather than any single branch's tree.

This PR is documentation/framing for the enforcement #153 and #154
ship; merge order remains #153, #154, then this.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* State the no-financial rule as policy, not as a capability claim (review)

The review bot's follow-up is right that a method deny-list cannot make
the generic update tool UNABLE to move assets — a bespoke method name is
out of any list's reach, which the module docs and submission docs
already record. The description and refusal therefore stop claiming
impossibility: they state the binding policy (moving financial assets
through this tool is not supported and must not be attempted under any
method name) alongside what is mechanically enforced (the standardized
ledger surface is refused on every canister). Whether to go further —
dropping the generic update surface or moving to a reviewed allow-list —
is the recorded 'directory-safe profile' option and stays a product
decision, not something this PR takes on its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Present the posture as policy plus standardized-method guardrail (review)

Second review pass on the framing: every remaining absolute 'X is
refused' claim is reworded to the stable formula the guard actually
supports — financial operations are unsupported BY POLICY, and the
STANDARDIZED ledger surface is refused mechanically. Applied to the
README statement, the server instructions (which now also tell the
model never to attempt a financial operation through any other method
or route), and the landing trust card ('standard ledger transfers and
token approvals are refused by design'). The Anthropic readiness row
for the read-only tool count is marked pending on the top-up PR rather
than verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Stack on the ledger-guard branch; mark the top-up milestone merged

The framing PR now contains the enforcement it describes: main (with
the merged instructions-only top-up, #153) and the ledger-method guard
branch (#154) are merged in, and the PR is retargeted onto #154's
branch so it cannot merge ahead of it — the ordering the review kept
flagging is now mechanical rather than a note in the description. The
Anthropic readiness row flips to verified since #153 is merged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Frame the financial restriction as intent plus reasonable measures

Per author direction on the PR: no web system can tell from the outside
whether a given request moves funds, so the honest claim is intention,
not capability. The description now reads 'not intended for moving
money, tokens, or other financial assets on the user's behalf' with the
ledger-method refusals presented as the reasonable measures taken to
refuse asset-moving requests; the refusal message matches, and the
description test pins the new header phrase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Align the listing draft and reviewer test with the intent framing

The pasted marketplace copy and the reviewer test instruction now match
the settled framing: the connector is not INTENDED for financial
transactions, and the STANDARDIZED token-ledger transfer/approval
methods are refused as the safety measure — neither line claims the
guard catches more than it does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Explain why real funds live on the standardized ledger surface (review)

Per author feedback: tokens are only valuable if they can be exchanged
or used, and the ICP ecosystem's financial platforms (wallets like Oisy,
exchanges like ICP Swap) integrate ledgers through the ICRC standards —
a token on a bespoke, non-standard ledger can exist, but the ecosystem's
platforms cannot hold or trade it, so it carries little exchangeable
value. Also syncs the quoted policy wording in the same note with the
intent framing the description now uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Split the guard: literal ICRC matching plus ledger-scoped abstract names

Per review: normalization was both too loose and unnecessary. The guard
now has two groups. ICRC-standard methods (icrc1_transfer, icrc2_*, the
ICRC-4/-7/-37 equivalents) are matched literally on every canister — the
standards fix the exact names and Candid method names are case-sensitive,
so a differently-spelled name is not the standard method and could never
reach it on chain. Abstract names (transfer, send_dfx, notify_dfx,
withdraw, withdraw_from, create_canister, create_canister_from) are
refused only on the specific system canister where they are financial —
the ICP ledger and the cycles ledger by canister id — so an app whose own
transfer or withdraw is non-financial keeps working.

The legacy DIP20/EXT names on arbitrary canisters (transfer,
transferFrom, approve) are deliberately no longer matched: too abstract
to block everywhere without breaking non-financial apps, per review, and
those standards sit outside the ecosystem's ICRC-integrated platforms;
the stated policy covers them. Tests pin literal matching, the
per-ledger scoping in both directions, and the unchanged refusal
contract; the tool description and README describe the scoped behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Describe the per-ledger scoping of the guard in the OpenAI doc

The submission record now matches the restructured guard: ICRC-standard
methods refused on every canister, and the ICP/cycles ledgers' own
value-moving methods refused on those ledgers, rather than a blanket
every-canister claim for the whole set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Point the creation refusal at the user-run icp CLI, not a connector tool

Per review: recommending icp_create_canister inside the refusal reads as
this server supporting canister creation on the user's behalf, which is
not what the refusal should communicate. The cycles-ledger create_canister
/ create_canister_from refusal now points the user at running the
operation themselves with the icp CLI (with the install pointer), like
every other refusal points at a user-controlled venue. README row updated
to match; the test now pins that the refusal names the CLI and never the
connector tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Make the financial policy a server instruction in plain protective terms

Per review, three changes to how the policy is stated:

- The policy paragraph moves out of canister_update_call's description
  into the server-level get_info instructions (now a SERVER_INSTRUCTIONS
  const so the test can pin it): a policy block inside the tool's own
  description reads as a hint that the tool is usable for financial
  transactions. The description is purely functional again.
- The wording drops the marketplace/compliance rationale and the
  'reasonable measures' hedge: it now simply states that financial
  transactions are not supported and asset-moving requests are denied,
  to protect the user.
- The refusal message loses the 'do not route under another method name'
  sentence, which read as a hint that the guards could be bypassed.

README row aligned; tests updated to pin the description staying free of
financial language and the instructions carrying the policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Align the directory-submission docs with the server-instruction policy

The policy now lives in the server-level instructions rather than the
canister_update_call description (which stays free of financial language),
the refusal points a canister-creation spend at the user-run icp CLI, and
the public surfaces say financial transactions are not supported. Update
the mitigation descriptions in both submission docs to match, and describe
the per-ledger scoping in the Anthropic doc's bullet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Make icp_create_canister instructions-only: never execute funding

Per maintainer review across the compliance PRs: creating and funding a
canister spends the user's ICP or cycles, so the connector must not
execute it. icp_create_canister now works exactly like the top-up tool
(#153): it returns the icp CLI steps for the user to run themselves —
install the CLI, check/mint cycles, icp canister create, and finally add
the connector's management principal (printed by icp_cycles_balance) as a
controller so icp_install_code and the lifecycle tools can operate the
new canister. The executing paths are removed from the binary: the
ICP-ledger transfer to the CMC, notify_create_canister, and the
cycles-ledger create_canister call, with their wire types and helpers.

With this, no tool initiates or executes a transfer of the user's funds:
the server-wide 'asset-moving requests are denied' instruction now has no
executing counterexample. The compliance refusal for cycles-ledger
creation spends may name icp_create_canister again — it prints steps and
executes nothing — and the CMC notify_* methods stay callable as the
recovery path for a user's own interrupted CLI mint. README and server
instructions updated; tests moved to pin the instructions-only contract
(annotations, description, echoed amounts, controller handover step).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Merge the instructions-only creation change; sweep NNS and de-risk the docs

Merge branch 'claude/icp-marketplace-compliance-vpawel-2-ledger-guard'
(icp_create_canister is now instructions-only) and apply the review sweep
on this branch's surfaces:

- NNS is no longer used as an example app or venue anywhere agent-facing
  or in the README/landing page (per review: it can hold funds). The
  functional pieces stay — the known-app registry, derivation-origin map,
  security guardrails, and dashboard status lines.
- The README intro now says creation, like top-up, returns icp CLI
  instructions for the user to run themselves.
- Both directory-submission docs state the decided posture plainly: no
  tool initiates or executes a transfer of the user's funds; the
  counterexample/options/open-question passages are gone (per review:
  submissions should be black or white, no grey areas or follow-ups).
  Example prompts and test cases are financially neutral (opencloud.org,
  cycles-instructions, plain canister ids); the OpenAI financial negative
  test case is removed.
- OpenAppArgs/FindAppArgs docs lose the MULTI/DEX/Oisy/NNS name examples
  (also flagged by review-bot); landing page prompt and group-04 copy
  updated to match the instructions-only behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Keep the instructions-only tool descriptions functional, not disclaiming

Per review: policy language belongs in the server-level instructions, not
in per-tool descriptions. The top-up and creation descriptions now state
the one functional fact a calling model needs — the tool only prints the
icp CLI steps and never executes the operation — without the
provided-for-completeness / never-moves-funds disclaimer prose. Tests pin
the functional wording and that the descriptions stay free of financial
language.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Fix the read-only tool count and pin the top-up example prompts

Per review-bot findings on the submission docs: with creation now
instructions-only there are 19 read-only-annotated tools, not 18 (both
places); the cycles-instructions example prompt names a concrete canister
id since icp_top_up_canister requires one; and the README intro clause is
scoped to the two instructions-only tools.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Neutralize the remaining financial data examples; fix a heading and a placeholder

Per review-bot findings: the open_app / canister_query descriptions and
the server instructions still used balance/holdings/positions and a
get_balance example as the model-visible illustrations of per-app data —
replaced with bookings/appointments/orders/profile. The anthropic doc's
section heading still said 'answer pending' above a body that says
resolved in code; and the openai doc's negative test case used an angle-
bracket placeholder that Markdown parses as a raw HTML tag — it now uses
a reserved example.com domain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

---------

Co-authored-by: Claude <noreply@anthropic.com>
aterga added a commit that referenced this pull request Aug 27, 2026
* Refuse ledger transfer/approval methods in canister_update_call

Marketplace directories (Anthropic, OpenAI) prohibit connectors that
initiate or execute money or crypto transfers on the user's behalf, and
this server's purpose is reading, building, and operating canisters —
not moving funds.

canister_update_call now refuses the update methods that move value or
grant spending rights on ICP ledgers, on every canister, before any
network work. The disallow list leans on the ICRC standards that ledgers
on ICP follow — ICRC-1/ICRC-2 (fungible transfers and approvals), ICRC-4
(batch transfers), ICRC-7/ICRC-37 (NFT transfers and approvals) — plus
the ICP ledger's pre-ICRC surface (transfer, send_dfx, notify_dfx), the
cycles ledger's withdraw/withdraw_from, and the ERC-20-style names of
the older DIP20/EXT token standards (transferFrom, approve). Matching is
exact on a normalized name (lowercased, separators stripped), never a
substring test, so spelling variants are caught without false positives
on names that merely contain a blocked one.

The refusal is a recommendation, not a dead end: it tells the agent to
send the user to a wallet or frontend they control in their own browser
(e.g. https://oisy.com; for governance, the NNS dapp). The tool
description states the policy up front — financial transactions are not
supported, for marketplace compliance and user safety — and a test pins
both the description and the refusal wording.

The CMC's notify_create_canister / notify_top_up stay callable: they
move no funds out of any account and blocking them would strand the
documented recovery path for an interrupted ICP funding flow. The module
docs are explicit that a name-based list is a guardrail for the
standardized ledger surface, not a hermetic seal over bespoke canisters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Remove financial framing; state the server is not for financial operations

The marketplace-compliance posture (top-up instructions-only, ledger
transfer/approval methods refused) needs the surrounding material to say
the same thing: this MCP server is infrastructure tooling and is not
intended for financial operations.

- README: a 'Not for financial operations' statement up front; the
  skills enumeration drops DeFi; DEX-app names (MULTI/DEX, ICPSwap) in
  examples give way to neutral ones (NNS, Oisy), with the known-app
  registry and its wrong-TLD repair behavior unchanged in code.
- Tool descriptions and server instructions: same example cleanup, plus
  a NOT FOR FINANCIAL OPERATIONS paragraph in the server instructions
  that tells agents to send users to a wallet they control (oisy.com)
  for such operations.
- Landing page (src/assets/index.html bundle): the wallet-balance chat
  mock becomes an app-data read, the portfolio/DEX example prompts
  become neutral app examples, the tool-group copy stops claiming
  server-side top-ups, and the trust section gains a 'Not for financial
  operations' card. Only the embedded template changed; decoded-value
  equality is asserted around the re-encode.
- Cargo.toml: drop the cryptography::cryptocurrencies crates.io
  category — network/api tooling categories remain.
- Directory-submission docs: the financial-transactions blockers now
  record the shipped posture (instructions-only top-up, ledger-method
  refusals, stated policy) and keep the honest caveats: the
  icp_create_canister icp path still executes an ICP-to-cycles
  conversion and needs an explicit decision, and a name-based guard
  covers the standardized ledger surface, not bespoke canisters. The
  listing description draft and reviewer instructions match the new
  behavior.

Factual technical references to ledgers (reading a ledger is a public
Candid query) and the ckUSDC lookup examples stay: identifying what a
canister is remains core, non-financial functionality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Block cycles-ledger creation spends; scope the no-financial claim (review)

Address the Copilot review on the ledger-method guard:

- The cycles ledger's create_canister / create_canister_from draw cycles
  from the caller's balance through the exact generic route this guard
  closes (withdraw was already blocked for that reason), so they join
  the disallow list. Their refusal redirects to the purpose-built
  icp_create_canister tool (or the icp CLI) rather than to a wallet —
  creation stays supported, only the uncontrolled generic route closes.
- The absolute 'this server does not move financial assets' wording in
  the tool description and the refusal message is scoped to 'this tool':
  precise on this branch standalone (icp_create_canister still funds at
  creation) and after the companion top-up change merges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Use opencloud.org example; scope financial claims to enforcement (review)

Address the reviews on the framing cleanup:

- Per author feedback, Oisy (also a DeFi app) stops serving as the
  neutral usage example: name examples use NNS (the registry app that
  fits), and URL / bare-host examples use https://opencloud.org — live
  and IC-served (x-ic-canister-id present), so it passes the IC-evidence
  gate as documented. Oisy remains only where a wallet is genuinely
  meant: the refusal recommendation for financial operations.
- The absolute 'never moves money or tokens' claims flagged by the
  review bot become enforcement-scoped statements: README, the landing
  trust card, and the server instructions now say token transfers,
  approvals, payments, and trades ARE REFUSED (via the update-call guard
  and the instructions-only top-up) and disclose that creating and
  funding the user's own canisters remains available through
  icp_create_canister.
- The OpenAI submission doc presents the shipped changes as mitigations
  toward a still-pending attestation decision (the icp_create_canister
  funding path and the bespoke-method limitation are named as the open
  counterexamples), not as grounds to check the box. The Anthropic doc's
  'shipped' section is phrased against the PR set (#153/#154) it
  describes rather than any single branch's tree.

This PR is documentation/framing for the enforcement #153 and #154
ship; merge order remains #153, #154, then this.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* State the no-financial rule as policy, not as a capability claim (review)

The review bot's follow-up is right that a method deny-list cannot make
the generic update tool UNABLE to move assets — a bespoke method name is
out of any list's reach, which the module docs and submission docs
already record. The description and refusal therefore stop claiming
impossibility: they state the binding policy (moving financial assets
through this tool is not supported and must not be attempted under any
method name) alongside what is mechanically enforced (the standardized
ledger surface is refused on every canister). Whether to go further —
dropping the generic update surface or moving to a reviewed allow-list —
is the recorded 'directory-safe profile' option and stays a product
decision, not something this PR takes on its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Present the posture as policy plus standardized-method guardrail (review)

Second review pass on the framing: every remaining absolute 'X is
refused' claim is reworded to the stable formula the guard actually
supports — financial operations are unsupported BY POLICY, and the
STANDARDIZED ledger surface is refused mechanically. Applied to the
README statement, the server instructions (which now also tell the
model never to attempt a financial operation through any other method
or route), and the landing trust card ('standard ledger transfers and
token approvals are refused by design'). The Anthropic readiness row
for the read-only tool count is marked pending on the top-up PR rather
than verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Stack on the ledger-guard branch; mark the top-up milestone merged

The framing PR now contains the enforcement it describes: main (with
the merged instructions-only top-up, #153) and the ledger-method guard
branch (#154) are merged in, and the PR is retargeted onto #154's
branch so it cannot merge ahead of it — the ordering the review kept
flagging is now mechanical rather than a note in the description. The
Anthropic readiness row flips to verified since #153 is merged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Frame the financial restriction as intent plus reasonable measures

Per author direction on the PR: no web system can tell from the outside
whether a given request moves funds, so the honest claim is intention,
not capability. The description now reads 'not intended for moving
money, tokens, or other financial assets on the user's behalf' with the
ledger-method refusals presented as the reasonable measures taken to
refuse asset-moving requests; the refusal message matches, and the
description test pins the new header phrase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Align the listing draft and reviewer test with the intent framing

The pasted marketplace copy and the reviewer test instruction now match
the settled framing: the connector is not INTENDED for financial
transactions, and the STANDARDIZED token-ledger transfer/approval
methods are refused as the safety measure — neither line claims the
guard catches more than it does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Explain why real funds live on the standardized ledger surface (review)

Per author feedback: tokens are only valuable if they can be exchanged
or used, and the ICP ecosystem's financial platforms (wallets like Oisy,
exchanges like ICP Swap) integrate ledgers through the ICRC standards —
a token on a bespoke, non-standard ledger can exist, but the ecosystem's
platforms cannot hold or trade it, so it carries little exchangeable
value. Also syncs the quoted policy wording in the same note with the
intent framing the description now uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Split the guard: literal ICRC matching plus ledger-scoped abstract names

Per review: normalization was both too loose and unnecessary. The guard
now has two groups. ICRC-standard methods (icrc1_transfer, icrc2_*, the
ICRC-4/-7/-37 equivalents) are matched literally on every canister — the
standards fix the exact names and Candid method names are case-sensitive,
so a differently-spelled name is not the standard method and could never
reach it on chain. Abstract names (transfer, send_dfx, notify_dfx,
withdraw, withdraw_from, create_canister, create_canister_from) are
refused only on the specific system canister where they are financial —
the ICP ledger and the cycles ledger by canister id — so an app whose own
transfer or withdraw is non-financial keeps working.

The legacy DIP20/EXT names on arbitrary canisters (transfer,
transferFrom, approve) are deliberately no longer matched: too abstract
to block everywhere without breaking non-financial apps, per review, and
those standards sit outside the ecosystem's ICRC-integrated platforms;
the stated policy covers them. Tests pin literal matching, the
per-ledger scoping in both directions, and the unchanged refusal
contract; the tool description and README describe the scoped behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Describe the per-ledger scoping of the guard in the OpenAI doc

The submission record now matches the restructured guard: ICRC-standard
methods refused on every canister, and the ICP/cycles ledgers' own
value-moving methods refused on those ledgers, rather than a blanket
every-canister claim for the whole set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Point the creation refusal at the user-run icp CLI, not a connector tool

Per review: recommending icp_create_canister inside the refusal reads as
this server supporting canister creation on the user's behalf, which is
not what the refusal should communicate. The cycles-ledger create_canister
/ create_canister_from refusal now points the user at running the
operation themselves with the icp CLI (with the install pointer), like
every other refusal points at a user-controlled venue. README row updated
to match; the test now pins that the refusal names the CLI and never the
connector tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Make the financial policy a server instruction in plain protective terms

Per review, three changes to how the policy is stated:

- The policy paragraph moves out of canister_update_call's description
  into the server-level get_info instructions (now a SERVER_INSTRUCTIONS
  const so the test can pin it): a policy block inside the tool's own
  description reads as a hint that the tool is usable for financial
  transactions. The description is purely functional again.
- The wording drops the marketplace/compliance rationale and the
  'reasonable measures' hedge: it now simply states that financial
  transactions are not supported and asset-moving requests are denied,
  to protect the user.
- The refusal message loses the 'do not route under another method name'
  sentence, which read as a hint that the guards could be bypassed.

README row aligned; tests updated to pin the description staying free of
financial language and the instructions carrying the policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Align the directory-submission docs with the server-instruction policy

The policy now lives in the server-level instructions rather than the
canister_update_call description (which stays free of financial language),
the refusal points a canister-creation spend at the user-run icp CLI, and
the public surfaces say financial transactions are not supported. Update
the mitigation descriptions in both submission docs to match, and describe
the per-ledger scoping in the Anthropic doc's bullet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Make icp_create_canister instructions-only: never execute funding

Per maintainer review across the compliance PRs: creating and funding a
canister spends the user's ICP or cycles, so the connector must not
execute it. icp_create_canister now works exactly like the top-up tool
(#153): it returns the icp CLI steps for the user to run themselves —
install the CLI, check/mint cycles, icp canister create, and finally add
the connector's management principal (printed by icp_cycles_balance) as a
controller so icp_install_code and the lifecycle tools can operate the
new canister. The executing paths are removed from the binary: the
ICP-ledger transfer to the CMC, notify_create_canister, and the
cycles-ledger create_canister call, with their wire types and helpers.

With this, no tool initiates or executes a transfer of the user's funds:
the server-wide 'asset-moving requests are denied' instruction now has no
executing counterexample. The compliance refusal for cycles-ledger
creation spends may name icp_create_canister again — it prints steps and
executes nothing — and the CMC notify_* methods stay callable as the
recovery path for a user's own interrupted CLI mint. README and server
instructions updated; tests moved to pin the instructions-only contract
(annotations, description, echoed amounts, controller handover step).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Merge the instructions-only creation change; sweep NNS and de-risk the docs

Merge branch 'claude/icp-marketplace-compliance-vpawel-2-ledger-guard'
(icp_create_canister is now instructions-only) and apply the review sweep
on this branch's surfaces:

- NNS is no longer used as an example app or venue anywhere agent-facing
  or in the README/landing page (per review: it can hold funds). The
  functional pieces stay — the known-app registry, derivation-origin map,
  security guardrails, and dashboard status lines.
- The README intro now says creation, like top-up, returns icp CLI
  instructions for the user to run themselves.
- Both directory-submission docs state the decided posture plainly: no
  tool initiates or executes a transfer of the user's funds; the
  counterexample/options/open-question passages are gone (per review:
  submissions should be black or white, no grey areas or follow-ups).
  Example prompts and test cases are financially neutral (opencloud.org,
  cycles-instructions, plain canister ids); the OpenAI financial negative
  test case is removed.
- OpenAppArgs/FindAppArgs docs lose the MULTI/DEX/Oisy/NNS name examples
  (also flagged by review-bot); landing page prompt and group-04 copy
  updated to match the instructions-only behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Keep the instructions-only tool descriptions functional, not disclaiming

Per review: policy language belongs in the server-level instructions, not
in per-tool descriptions. The top-up and creation descriptions now state
the one functional fact a calling model needs — the tool only prints the
icp CLI steps and never executes the operation — without the
provided-for-completeness / never-moves-funds disclaimer prose. Tests pin
the functional wording and that the descriptions stay free of financial
language.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Fix the read-only tool count and pin the top-up example prompts

Per review-bot findings on the submission docs: with creation now
instructions-only there are 19 read-only-annotated tools, not 18 (both
places); the cycles-instructions example prompt names a concrete canister
id since icp_top_up_canister requires one; and the README intro clause is
scoped to the two instructions-only tools.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Split IcTools into IcCanisterTools and IcProtocolTools

The one mega-struct carried all 26 tools. Split it along the scope
taxonomy the server instructions already teach: IcCanisterTools holds the
app- and canister-scoped tools (the …canister…/…_app… names — Candid/OQL
reads, update calls, app resolution/discovery, per-app identity), and
IcProtocolTools holds the icp_-prefixed protocol/meta tools (dashboard
lookups, skills, the OQL guide, and canister creation/management as the
management principal). Each half owns only the state it uses (the agent
stays on the canister side; the skills catalog on the protocol side).

IcTools remains as the composed ServerHandler — same constructor, same
get_info/instructions/resources, and a served surface byte-identical to
before — dispatching tools/call by router membership and answering
tools/list with the concatenated routers (the same pattern imcp2-local's
LocalServer wrapper already uses to add its login tools). Tool order in
tools/list changes only in that icp_oql_guide now lists with the other
icp_ tools.

Tests: a new taxonomy test pins the split (icp_ prefix <=> IcProtocolTools,
no overlap, nothing dropped); the existing surface tests move to
IcTools::all_tools(), and imcp2-local's real-MCP round-trip exercises the
composed dispatch end to end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Neutralize the remaining financial data examples; fix a heading and a placeholder

Per review-bot findings: the open_app / canister_query descriptions and
the server instructions still used balance/holdings/positions and a
get_balance example as the model-visible illustrations of per-app data —
replaced with bookings/appointments/orders/profile. The anthropic doc's
section heading still said 'answer pending' above a body that says
resolved in code; and the openai doc's negative test case used an angle-
bracket placeholder that Markdown parses as a raw HTML tag — it now uses
a reserved example.com domain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

* Trigger the org-required External PR Ruleset workflow

Empty commit at the author's request: the org ruleset began applying to
this repository after this branch's last push, so its required workflow
never ran on the previous head and the check sat at Expected forever.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvwAVXfx5kLNc4SqKXkavk

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants