Repository navigation
Conversation
ebdac6f to
b397fd4
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Concurrent deployments or retries after an interrupted response can create duplicate funded canisters; balance checks may use the wrong network, and revoked grants may continue signing.
6 open issues
Disable signing after terminal authorization failures · New Persist canister creation progress across interrupted requests · New Prevent concurrent deploys from creating duplicate canisters · New Check the deployment balance on mainnet · New Keep the resolved II account stable during deployment · New Clarify which identity the deploy tool uses · New
What changed in this PR
Completes the three-part deployment integration by exposing bundle deployment and cycles-balance tools through the local MCP server, while keeping deployment dependencies out of the published core crates.
Changes:
- Adds authenticated deployment tools with validated inputs, structured results, and refreshing signing identities.
- Composes the tools and deployment skill into the local server, with expanded MCP round-trip tests.
- Documents the bundle–deploy–save-mapping workflow and exposes reusable core balance helpers.
| File | Description |
|---|---|
crates/imcp2-local/src/server.rs |
Routes deployment tools, serves the skill, and extends integration tests. |
crates/imcp2-local/src/main.rs |
Constructs and wires the deployment tool group. |
crates/imcp2-local/README.md |
Documents deployment inputs, identities, and mapping persistence. |
crates/imcp2-local/Cargo.toml |
Adds the deployment crate dependency. |
crates/imcp2-deploy/static/skills/deploy-with-mcp.md |
Adds agent guidance for bundling, deploying, and saving IDs. |
crates/imcp2-deploy/src/lib.rs |
Implements tool schemas, validation, authorization, and results. |
crates/imcp2-deploy/src/identity.rs |
Adds delegation refresh during long-running deployments. |
crates/imcp2-core/src/management.rs |
Extracts a balance helper for an explicit principal and agent. |
crates/imcp2-core/src/lib.rs |
Exports balance helpers and their result type. |
crates/imcp2-core/src/identities.rs |
Makes identity-specific agent construction public. |
Cargo.lock |
Records the local server’s deployment dependency. |
Review effort: Balanced
b397fd4 to
70e073e
Compare
There was a problem hiding this comment.
Note
Copilot was unable to run its full agentic suite in this review.
Copilot review overview
10 open issues
Check the deployment balance on mainnet Prevent concurrent deploys from creating duplicate canisters Persist canister creation progress across interrupted requests Disable signing after terminal authorization failuresbundle_pathis documented (and taught in the skill) as requiring an absolute path, but… · Newlist_resourcesappends an extra resource after delegating toself.tools.list_resources(...),… · New Clarify which identity the deploy tool uses Keep the resolved II account stable during deployment The rejection message saysis not a canister id, but the parser used (Principal::from_text)… · New This comment says \"The deploy tool\" but the PR adds a deploy tool group with two tools… · New
Review effort: Lite
70e073e to
2725472
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Recovery records can be lost or incomplete, risking duplicate funded canisters, while untrusted plugins can exhaust server memory.
4 open issues
10 resolved since last review
Check the deployment balance on mainnet Prevent concurrent deploys from creating duplicate canisters Persist canister creation progress across interrupted requests Disable signing after terminal authorization failureslist_resourcesappends an extra resource after delegating toself.tools.list_resources(...),…bundle_pathis documented (and taught in the skill) as requiring an absolute path, but… Clarify which identity the deploy tool uses Keep the resolved II account stable during deployment This comment says \"The deploy tool\" but the PR adds a deploy tool group with two tools… The rejection message saysis not a canister id, but the parser used (Principal::from_text)…
Review effort: Balanced
| Ok(id) => id, | ||
| Err(e) => return Ok(err(e)), | ||
| }; | ||
| match deploy_bundle(identity, request).await { |
There was a problem hiding this comment.
Still open: the runner builds its own engine and store (icp-sync-plugin), so the limiter belongs upstream, and host_for's doc records the gap. Refusing sync steps until then would refuse the asset canister a bundle is deployed for. Left open until the runner has one.
2725472 to
b338b60
Compare
b338b60 to
bfbaff6
Compare
bfbaff6 to
2cda921
Compare
2cda921 to
a02bfb5
Compare
a02bfb5 to
c658fc7
Compare
| /// Where the deploy's working directory is made (the system temporary | ||
| /// directory when `None`). A run that returns removes it; a run that is | ||
| /// cut off — the server stopped mid-deploy, the call cancelled — leaves | ||
| /// it, and with it `.icp/data/mappings/<environment>.ids.json`, which | ||
| /// records every canister the deploy created, so a caller who never got | ||
| /// the answer can recover the ids instead of creating a second set. | ||
| pub workdir: Option<Utf8PathBuf>, |
There was a problem hiding this comment.
Any risk here of one user writing a malicious value to .icp/data/mappings/<environment>.ids.json that then is served to another user?
There was a problem hiding this comment.
For a given agent call, the server should create a temporary directory to run the deployment from and this file is a byproduct of the deployment. The directory is not supposed to be reused. even if it is reused the agent would still need a controller identity.
There was a problem hiding this comment.
No run reads another's file. Each call makes a fresh, randomly named directory (deploy_directory in deploy.rs, called where the scratch directory is made) under the user's local data directory (main.rs, deploys_in), extracts the bundle there, and that run alone reads it. The ids file in it is seeded from the call's own canister_ids argument and written by the library during the run; the bundle's own .icp/ entries are refused at extraction (host.rs), so a bundle cannot plant one. imcp2-local is one user per process, and the deploy tools are not on the gateway. What was missing: the directory's mode followed the umask, so another OS user could read a left-behind one where the home allows. 08516e7 makes it 0700 on Unix, with a test.
| // records, and the build artifacts. Until the deploy starts it is a | ||
| // temporary directory, gone with any early return; from then on it is | ||
| // kept on purpose and removed only once the outcome — the ids above all — | ||
| // has been assembled, so a run that is cut off leaves its record behind. |
There was a problem hiding this comment.
Same question as above, how do we make sure runs are isolated between users?
There was a problem hiding this comment.
Same answer as above: this is where the per-run directory is made, fresh and randomly named for every call, and a second deploy while one runs is refused before anything else (claim in lib.rs), so two runs never share one. As of 08516e7 it is created with mode 0700 on Unix.
| /// The skill that teaches an agent the deploy loop around this tool — bundle | ||
| /// the project, call `icp_deploy_bundle` with the environment's canister-id | ||
| /// mapping, write the returned mapping back — served as the MCP resource | ||
| /// `skill://deploy-with-mcp` by the server that mounts the tool, next to |
There was a problem hiding this comment.
Serving as MCP resource isn't reliable in all agent, see e.g. the candid resource as an example where we also exposed it through a tool for agents that don't work with skill resources.
There was a problem hiding this comment.
Done in 09ed508: icp_deploy_guide returns the same document as a tool result (text plus {content} structured output), the way core serves its Candid guide as both candid://reference and candid_syntax_guide. The resource stays; the server instructions and README name both.
The last of three changes bringing `icp deploy` to the local MCP server: the tools over the bundle host and the deploy pipeline, and their wiring. - `icp_deploy_bundle` takes a bundle (by absolute path, or inline as base64 bounded on its length before decoding), the environment name, and optionally the canister-id mapping an earlier deploy returned; it answers with the status, the mapping to pass back next time, the canisters created and a per-phase log, as structured content under a declared output schema. It needs a session granted "Actions & questions", checked before the bundle is decoded. - Calls are signed as the user's CLI identity: their default Internet Identity account at `https://cli.id.ai`, the origin `icp identity link web` signs in at, so canisters created here are the ones `icp` lists and controls, funded from the same cycles balance. It is not the management identity the canister lifecycle tools act as; the docs say so. A per-app delegation lives five minutes and a deploy can run longer, so the agent signs through `RefreshingIdentity`, which swaps in a fresh delegation from the session store every ten seconds, for the same principal, until the deploy ends. - `icp_cycles_balance` reports that principal and its cycles-ledger balance, the check a deploy that will create canisters starts with. Core's tool of that name sits in the protocol half the local server does not serve; this is the same logic on the served surface. `cycles_balance_of` is split out of core's `cycles_balance` and `Identities::agent_as` made public for it. - The local server composes the deploy router next to the login tools, serves the `skill://deploy-with-mcp` skill (bundle, deploy, write the mapping back into `.icp/data/mappings/<environment>.ids.json`) ahead of core's bundled skills, and adds a DEPLOYING paragraph to its instructions. README and the crate docs describe the tool, the identity it acts as and the `ic`-only rule. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t past a cut-off What the review found around the tools rather than in the pipeline, each taken as it was raised. A deploy signs as the account the first derivation resolved the session's default to, and asks for that account by number from then on (core's delegated_identity_resolved reports it): the default is the user's to change, and a refresh that came back as another principal would have been refused until the delegation in hand expired. When the store reports the grant over — II's one terminal answer, RECONNECT_MSG, now public — the identity stops signing at once instead of riding out that delegation. Two deploys at once for one project, each with the mapping as it was before either, would create two sets of canisters: a server runs one at a time, and refuses rather than queues a second, since its mapping would be stale by the time it ran. And a deploy's working directory is kept under the OS's local data directory until the outcome is assembled, so a call cut off mid-deploy leaves `.icp/data/mappings/<env>.ids.json` with every canister it created where the README and the skill say to look. The balance check reads the Internet Computer through the same mainnet agent a deploy uses, whatever endpoint the rest of the server is pointed at. `bundle_path` is trimmed and must be absolute before any I/O, and an unparsable id is reported as what it is, not a valid principal. The deploy skill joins the first page of resources alone. The identity the tools act as is described as what it is: the default II account at cli.id.ai, which `icp identity link web` links the CLI to — the CLI's own principal only when its selected identity is that link. The README, the skill, the server instructions and the module docs say so, and point at `icp identity principal` for the comparison. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A deploy cut off mid-run leaves the ids it created in its working directory, under the local data directory the README points at. With none reported by the OS the server fell back to the system temporary directory in silence, where a cleanup may remove the record before the user looks; it now fails at startup, naming what to set. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
c658fc7 to
5c60be8
Compare
…esources The skill around `icp_deploy_bundle` was served as the MCP resource `skill://deploy-with-mcp` alone, and not every client reads resources (review on #232). `icp_deploy_guide` returns the same document as a tool result, text and structured content, as core serves its Candid guide both ways. The server's instructions and README say so, and the round-trip test calls it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every deploy runs in a fresh, randomly named directory under the user's local data directory (or the system temporary directory), and nothing reads it but that run. Its mode followed the umask, though, so another user of the machine could read what the bundle holds and the ids the deploy records wherever the parent allowed it. On Unix the directory is now made with mode 0700. A test creates two and checks that they differ, sit under the parent, and are the owner's alone (review on #232). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The deployment guide overstates compatibility protection because the upstream check skips comparison when deployed Candid metadata is unreadable; qualify the guarantee and advise separate verification in that case.
2 open issues
3 resolved since last review
Review effort: Balanced
| Canisters named in `canister_ids` are upgraded with the Candid compatibility | ||
| check; an incompatible interface change fails the deploy before any code is | ||
| replaced. Pass `skip_candid_check: true` only when the user has accepted the | ||
| break. `mode` (default `auto`; `install`, `reinstall`, `upgrade`), |



Summary
Last of three stacked PRs bringing
icp deployto the local MCP server: the tools over the host (#230) and the pipeline (#231), and their wiring intoimcp2-local.icp_deploy_bundletakes a bundle (by absolute path, or inline as base64, bounded on its length before decoding), the environment name, and optionally the canister-id mapping an earlier deploy returned. It answers with the status, the mapping to pass back next time, the canisters created and a per-phase log, as structured content under a declared output schema. A failed run still reports every canister it created, so the mapping can be passed back to resume rather than repeat. It needs a session granted "Actions & questions", checked before the bundle is decoded.Who deploys. Calls are signed as the user's CLI identity: their default Internet Identity account at
https://cli.id.ai, the originicp identity link websigns in at. That is the principal the icp CLI already deploys as on the user's machine, so canisters created here are the onesicplists and controls, funded from the same cycles balance. It is deliberately not the management identity the canister lifecycle tools act as; the docs say a deployed canister is reached through those tools only after adding their principal as a controller. A per-app delegation lives five minutes and a deploy can run longer, so the agent signs throughRefreshingIdentity, which swaps in a fresh delegation from the session store every ten seconds, for the same principal, until the deploy ends.icp_cycles_balancereports that principal and its cycles-ledger balance, the check a deploy that will create canisters starts with. Core has a tool of that name in the protocol half the local server does not serve; this is the same logic on the served surface, and should be dropped if that half is ever served.A skill for the agent. The server also serves
skill://deploy-with-mcpnext to core's bundled IC skills, only where the tool is. It walks through the loop from the agent's side: bundle the project withicp project bundle, callicp_deploy_bundlewith the environment's.icp/data/mappings/<environment>.ids.jsonascanister_ids, write the returned mapping back into that file so the next deploy upgrades rather than creates. The server instructions point at it. Not every client reads resources, soicp_deploy_guidereturns the same document as a tool result, as core serves its Candid guide both ways.Policy note for reviewers. Core's server instructions say no tool creates, funds or deploys canisters. This tool creates canisters from the user's cycles-ledger balance (2T each by default). The local server's instructions call it out as the one exception; the wording in core is untouched and may want revisiting.
Related issues
Changes
crates/imcp2-deploy/src/lib.rs:DeployBundleArgs/DeployBundleOutput, argument validation (exactly one bundle source, bounded base64, mode, principals), theIcDeployToolsrouter with the two tools,CLI_ORIGIN, the skill document, structured ok/error results.crates/imcp2-deploy/src/identity.rs:RefreshingIdentity.crates/imcp2-deploy/static/skills/deploy-with-mcp.md: the skill, in the registry's SKILL.md shape.crates/imcp2-core:cycles_balance_of(agent, principal)split out ofcycles_balance, andIdentities::agent_asmade public, so the deploy tools can read a balance as an identity other than the management one.crates/imcp2-local:server.rscomposes the deploy router (list/call/get) next to the login tools, serves the deploy skill throughlist_resources/read_resourceahead of core's, and adds a DEPLOYING paragraph to the instructions;main.rswires it and documents the identity; README gains a "Deploying a project bundle" section;Cargo.tomltakes the dependency.Testing
cargo build --locked --workspace --all-targetscargo test --locked --workspace --all-targets(75 + 8 + 10 + 214 + 35 + 21 + 1; 8 new in the crate, local server round-trips extended)cargo fmt --all -- --check/cargo clippy --locked --workspace --all-targets(clean)npm test --prefix monitoring/mcp-status(dashboard unchanged)New here: an oversized inline bundle refused on its base64 length, the refreshing identity's swap and same-principal check, argument validation and the structured error shape, the tool schemas and annotations, and the skill's frontmatter and steps. The local server's round-trip test expects 14 tools and checks a signed-out
icp_deploy_bundlecall is refused before any file is read; its resource round-trip expects the deploy skill in the listing and reads it whole through a real client.Not covered: the Internet Identity round trip that mints a delegation at
cli.id.ai, and an on-chain deploy (see #231).Checklist
🤖 Generated with Claude Code