Skip to content

Stage 2: imcp2-local — the stdio binary with built-in II browser login - #150

Merged
aterga merged 11 commits into
mainfrom
claude/secondary-mcp-local-deploy-jpof9i-stage2
Aug 27, 2026
Merged

aterga merged 11 commits into
mainfrom
claude/secondary-mcp-local-deploy-jpof9i-stage2

Conversation

@aterga

@aterga aterga commented Aug 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Stage 2 of the local-deployment design (docs merged in #148, Stage 1 merged in #77): ship imcp2-local, the single-user binary an AI tool (Claude Desktop/Code, Codex, Cursor, …) spawns and talks to over stdio. It serves the same tool surface as the hosted server against the same mainnet and production Internet Identity, and carries none of the hosted server's OAuth machinery — signing in is a built-in browser handshake behind an in-band authenticate tool. No behavior change to the hosted server.

Related issues

Implements Implementation Stages → Stage 2 of the design docs merged in #148 (components 3–7).

Changes

  • crates/imcp2-local (new bin crate, publish = false — distributed as release binaries per the design's Stage 3):
    • Login driver (login.rs): authenticate mints a fresh session id + registration key, binds a transient loopback listener on 127.0.0.1:0, and returns the id.ai connect link in-band (plus best-effort open::that), never blocking on the callback. Exactly three routes — the pinned callback page (shared with hosted, same CSP + hardening headers), the slim redeem, and the #4091 allow-list (CORS-readable, no-store) — Host-validated against the listener's own authority (anti-DNS-rebinding, per the design's loopback hardening), torn down on redeem, on the 10-minute handshake timeout (hosted's CONNECT_TTL), or when a fresh flow replaces it. The redeem mirrors the hosted order (validate state → parse before claiming → single-flight claim → one mcp_register_v2 call) minus the OAuth tail: success answers {"done": true}; a registration mutex serializes register+commit across flows so the local slot always names the session II actually honors.
    • Wrapper handler (server.rs): the two local-only login tools live on LocalServer's own #[tool_router]; tools/list is the merged surface (26 core + 2), tools/call dispatches to the login router first and forwards everything else (calls, resources, get_tool) to the inner IcTools handler verbatim. Both tools declare object-rooted outputSchemas and attach matching structuredContent. get_info identifies as imcp2-local and appends sign-in guidance to core's instructions.
    • main.rs: mainnet agent (IMCP2_IC_URL override; IMCP2_FETCH_ROOT_KEY honoured only for loopback endpoints — the local-replica test configuration), production II (II_URL_PROD/II_CANISTER_ID_PROD), management identity derived at the hosted origin by default (IMCP2_MANAGEMENT_ORIGIN) so the same anchor keeps the same controller principal across deployments, IMCP2_NO_OPEN; all logging to stderr (stdout is the JSON-RPC channel).
  • imcp2-core (the seams Stage 1 left for the login driver, refined per review):
    • Authentication stays in the embedding binary: IcTools takes an injected SessionResolver that only reports the already-validated session for a call. The hosted crate's require_token middleware validates the bearer and its resolver reads the stashed AuthedSession back (both defined in imcp2's auth.rs); the local binary's login flow fills its SessionSlot (defined in imcp2-local's login.rs) and injects a slot-reading resolver. Core drops its http dependency along the way.
    • The pinned callback page's script gains the {"done": true} success arm ("Signed in — you can close this tab", .done terminal state in connect.css); hosted keeps answering redirect.
    • Regression guard: the core router must never carry the login tools (it is the hosted tools/list).
  • Workspace: imcp2-local added to members; axum/anyhow/uuid/tracing-subscriber pins lifted to [workspace.dependencies].

Testing

  • cargo build --workspace --all-targets / cargo test --workspace --all-targets — all suites green (hosted 52+10+9 unchanged; core 142; imcp2-local 12, including forged-Host rejection via raw sockets and the MCP round-trip with structured-content assertions)
  • Real MCP round-trip over an in-process duplex pipe: initialize → merged tools/list (28) → login lifecycle through tools/call (signed out → link → pending) → forwarded core tool
  • Listener driven like the browser (reqwest): pinned page + strict CSP + hardening headers; allow-list matches the link's callback verbatim with Access-Control-Allow-Origin: * and no-store; redeem refusal paths (unknown state, malformed delegation, garbage signatures) all offline and all leave the flow retryable
  • The built binary answers initialize/tools/list over real stdio (28 tools, identifies as imcp2-local)
  • The full local login passes end to end against a live II canister in PocketIC (the e2e ships in the Stage 3 PR and was run against this branch's code)
  • cargo check --features e2e --all-targets (hosted PocketIC harness intact); cargo fmt --check clean on the new crate; cargo clippy: no warnings in imcp2-local
  • cargo publish --dry-run -p imcp2-core still rehearses; cargo package -p imcp2 is blocked on imcp2-core's first (manual) crates.io release — pre-existing, reproduced identically on the Stage 1 base commit

Checklist


🤖 Generated with Claude Code

https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK

@aterga
aterga force-pushed the claude/secondary-mcp-local-deploy-jpof9i-stage2 branch from ac15eca to 2695c8a Compare August 25, 2026 14:19
@aterga
aterga requested a balanced review from Copilot August 25, 2026 14:31
Base automatically changed from claude/secondary-mcp-local-deploy-jpof9i to main August 25, 2026 14:33
The local half of the design merged in #148: a single-user binary an AI
tool spawns and talks to over stdio, serving the same tool surface as
the hosted server against the same mainnet and production Internet
Identity, with none of the hosted server's OAuth machinery.

imcp2-local (new crate, publish = false; distributed as release
binaries per the design's Stage 3):
- login driver: `authenticate` mints a FRESH session id + registration
  key, binds a transient loopback listener on 127.0.0.1:0, and returns
  the id.ai connect link in-band (plus a best-effort `open::that`
  browser launch), never blocking on the callback. The listener serves
  exactly three routes — the pinned fragment-reading callback page
  (shared with hosted, same CSP + hardening headers), the slim redeem,
  and the #4091 allow-list (CORS-readable, no-store) — and is torn down
  on redeem, on the 10-minute handshake timeout, or when a fresh flow
  replaces it. The redeem mirrors the hosted order (validate state,
  parse before claiming, single-flight claim, one mcp_register_v2
  call) minus the OAuth tail: success answers {"done": true} and the
  page renders "Signed in — you can close this tab" in place.
- wrapper handler: the two local-only login tools live on LocalServer's
  own #[tool_router]; tools/list is the merged surface (26 core + 2),
  tools/call dispatches to the login router first and forwards
  everything else (calls, resources, get_tool) to the inner IcTools
  handler verbatim. get_info identifies as imcp2-local and appends the
  sign-in guidance to core's instructions.
- main: mainnet agent (IMCP2_IC_URL override; IMCP2_FETCH_ROOT_KEY only
  honoured for loopback endpoints — the local-replica test
  configuration), production II (II_URL_PROD/II_CANISTER_ID_PROD
  overrides), management identity derived at the hosted server's origin
  by default (IMCP2_MANAGEMENT_ORIGIN) so the same anchor keeps the
  same controller principal across deployments, IMCP2_NO_OPEN, all
  logging to stderr (stdout is the JSON-RPC channel).

imcp2-core (the two seams Stage 1 left for the login driver):
- SessionSource::Singleton now holds a SessionSlot — a shared,
  REPLACEABLE holder, not a fixed id: the II contract requires a fresh
  session key after Unauthorized, so each successful login writes a
  fresh session id and reauthentication is a same-process step.
- the pinned callback page's script gains the {"done": true} success
  arm (hosted keeps answering `redirect`), with a `.done` terminal
  state in connect.css that drops the spinner without the error line.
- regression guard: the core router must never carry the login tools
  (it IS the hosted tools/list).

Tests: 218 pass workspace-wide (9 new in imcp2-local, 3 new in core),
including a real MCP round-trip over an in-process duplex pipe (merged
tools/list, login lifecycle through tools/call, forwarding into core
tools) and browser-played listener tests (pinned page + CSP, allow-list
verbatim-match + CORS, redeem refusal paths staying retryable). The
built binary answers initialize/tools/list over real stdio. No behavior
change to the hosted server.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
@aterga
aterga force-pushed the claude/secondary-mcp-local-deploy-jpof9i-stage2 branch from 2695c8a to 490983f Compare August 25, 2026 14:33
@aterga
aterga marked this pull request as ready for review August 25, 2026 14:34
@aterga
aterga requested a review from a team August 25, 2026 14:34

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 the imcp2-local stdio binary with browser-based Internet Identity authentication while preserving hosted-server behavior.

Changes:

  • Adds local login, session handling, and merged MCP tool routing.
  • Makes singleton sessions replaceable and supports local callback completion.
  • Registers the new crate and shared dependencies.

Reviewed changes

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

Show a summary per file
File Description
crates/imcp2-local/src/server.rs Wraps core tools with local authentication tools.
crates/imcp2-local/src/main.rs Configures and launches the stdio server.
crates/imcp2-local/src/login.rs Implements the loopback browser-login flow.
crates/imcp2-local/Cargo.toml Defines the local binary crate.
crates/imcp2-core/src/tools.rs Adds replaceable singleton sessions.
crates/imcp2-core/src/lib.rs Exports SessionSlot.
crates/imcp2-core/src/iiconnect.rs Handles local callback completion.
crates/imcp2-core/src/assets/connect.css Styles the completed-login state.
Cargo.toml Registers the crate and shared dependencies.
Cargo.lock Locks new dependencies.
Suppressed comments (2)

crates/imcp2-local/src/login.rs:493

  • The unconditional last-writer-wins commit allows an expired/replaced redeem to become current. For example, a redeem claimed just before the 10-minute deadline can remain in mcp_register_v2 while the watchdog removes it and a new flow starts; when the old call returns, it overwrites the new flow's session slot. Revalidate that this session is still the active, unexpired pending flow under the lock before committing it.
            // Fill the shared slot LAST-WRITER-WINS: II bound the grant to this
            // session's key, so even if a newer flow replaced the pending entry
            // mid-call, this successful login is real and becomes current.
            driver.inner.slot.set(body.state.clone());

crates/imcp2-local/src/server.rs:123

  • auth_status is also advertised without an object-rooted outputSchema or matching structuredContent, contrary to the all-tools contract enforced in crates/imcp2-core/src/tools.rs:2521-2537. Define a structured status output and return it alongside the human-readable text so the merged local surface preserves the same MCP contract as the core tools.
    #[tool(
        description = "Report the Internet Identity sign-in state of this local server: signed in (with the session principal, access level, and time to expiry), sign-in pending (with the link to finish it), expired, or signed out.",

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

Comment thread crates/imcp2-local/src/login.rs Resolved
Comment thread crates/imcp2-local/src/server.rs Resolved
Copilot AI review requested due to automatic review settings August 25, 2026 14:35

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 9 out of 10 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

crates/imcp2-local/src/login.rs:296

  • A refresh flow can coexist with the still-live previous grant, but this grant-first ordering hides that flow: after authenticate(refresh=true), auth_status immediately reports the old account as signed in instead of reporting the pending link. An agent can therefore treat an account switch/access upgrade as complete before the browser handshake finishes. Check a non-expired pending flow before the existing grant (while still falling back to the grant when the pending flow has expired).
        if let Some(g) = &state.grant {
            if !g.expired() {
                return LoginStatus::SignedIn(g.clone());
            }
        }

crates/imcp2-local/src/server.rs:85

  • authenticate omits output_schema and returns only text, so the merged tool surface breaks the repository's established MCP contract that every tool exposes an object-rooted output schema and matching structured content (crates/imcp2-core/src/tools.rs:2521-2538; all core tool attributes follow this pattern). Add a small serializable output type, declare it with output_schema, and populate structuredContent alongside the human-readable message.
    #[tool(
        description = "Sign in with Internet Identity. Returns an id.ai sign-in link for the USER to open in their browser (the server also tries to open it for them), and returns immediately — it never waits for the browser. After the user finishes, auth_status (or simply retrying the tool that needed a session) confirms the session. Call this when a tool answers that it needs an authenticated session. Repeat calls while a sign-in is pending return the same link.",
        annotations(
            title = "Sign in with Internet Identity",
            read_only_hint = false,
            destructive_hint = false,
            open_world_hint = true
        )

crates/imcp2-local/src/server.rs:129

  • auth_status also lacks the object-rooted output_schema and structured result required by the established tool convention (crates/imcp2-core/src/tools.rs:2521-2538). Returning a structured state (for example, state plus optional URL/grant details) is especially important here so clients do not have to parse changing prose to determine whether login is pending or complete.
    #[tool(
        description = "Report the Internet Identity sign-in state of this local server: signed in (with the session principal, access level, and time to expiry), sign-in pending (with the link to finish it), expired, or signed out.",
        annotations(
            title = "Check sign-in status",
            read_only_hint = true,
            destructive_hint = false,
            open_world_hint = false
        )

Comment thread crates/imcp2-local/src/login.rs Resolved
…le-redeem guard

- authenticate/auth_status now declare the object-rooted outputSchema
  and attach matching structuredContent (one AuthOutput shape for the
  whole sign-in lifecycle), preserving the all-tools MCP contract the
  core surface enforces.
- auth_status reports a live pending handshake BEFORE a live grant,
  matching begin's precedence: after authenticate(refresh=true) the
  truthful answer is "a replacement sign-in is waiting", not
  "signed in".
- The redeem's commit is no longer last-writer-wins: each flow carries
  an epoch, and a stale redeem (claimed, then outlived by the watchdog
  while its mcp_register_v2 call ran) is refused when a NEWER pending
  or completed sign-in exists — while a late success with nothing newer
  around still lands.

Tests: 220 pass workspace-wide (2 new: pending-outranks-grant status,
stale-redeem supersession) with structured-content assertions added to
the MCP round-trip and the outputSchema check extended to the merged
surface.

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

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 9 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

crates/imcp2-local/src/login.rs:207

  • A grant is considered active solely from its recorded expiration. If II has already returned Unauthorized because the grant was revoked or replaced, identities.rs only returns RECONNECT_MSG and does not invalidate this Grant. Consequently, a normal authenticate call reports AlreadySignedIn (and auth_status reports signed_in) until the original expiry, instead of starting the fresh-session flow required after Unauthorized. Propagate that definitive invalidation to the local login state/slot, or otherwise make begin distinguish a revoked session so reauthentication works without requiring callers to discover refresh=true through an extra failed call.
        if !refresh {
            if let Some(g) = &state.grant {
                if !g.expired() {
                    return Ok(BeginOutcome::AlreadySignedIn(g.clone()));

…local-deploy-jpof9i-stage2

# Conflicts:
#	crates/imcp2-core/src/tools.rs
Copilot AI review requested due to automatic review settings August 27, 2026 08:15

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 9 out of 10 changed files in this pull request and generated 1 comment.

Comment thread crates/imcp2-local/src/login.rs Resolved
The epoch guard fixed only the local half of the stale-redeem race: II's
mcp_register_v2 REPLACES the anchor's previous grant, so a timed-out
flow's slow registration landing after a newer flow's left II honoring
the old session while the guarded slot kept naming the new one — every
subsequent tool call would come back Unauthorized. A local commit-time
check cannot undo the remote side effect.

Replace the guard with what the race actually needs: a registration
mutex that serializes each redeem's mcp_register_v2 AND its local
commit, across flows. The last registration to settle at II is the live
grant, and under the lock the commit order equals the settle order, so
the slot always names the session II actually honors — in both
interleavings of an old in-flight registration and its replacement. The
per-flow single-flight claim stays (it stops page double-submits); the
epoch machinery goes.

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

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 9 out of 10 changed files in this pull request and generated 1 comment.

Comment thread crates/imcp2-local/src/login.rs Resolved
The design's loopback hardening requires it (bind 127.0.0.1 explicitly
AND validate the Host header, the local analogue of the hosted server's
allowed-hosts list), and the listener accepted any Host: a page on an
attacker's hostname that rebinds to 127.0.0.1 could reach the three
routes with `Host: attacker.example`. Every URL this flow hands out
carries exactly one authority — this listener's 127.0.0.1:<port> — so
a middleware now rejects anything else (exact string match, like II's
own allow-list check) before routing. Even without it the routes are
largely inert to a rebinder (/redeem requires the connect state and a
chain targeting the in-process X); this closes the door outright.

Tested with raw-socket requests: a rebound hostname and even
localhost:<port> bounce with 403; the one advertised authority passes.

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

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 9 out of 10 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-local/src/server.rs:176

  • This advertised tool description always claims that the server tries to open the browser, even when IMCP2_NO_OPEN disables that behavior. Since clients use this description to explain the flow, qualify the claim so it remains accurate in both configurations.

This issue also appears on line 203 of the same file.

        description = "Sign in with Internet Identity. Returns an id.ai sign-in link for the USER to open in their browser (the server also tries to open it for them), and returns immediately — it never waits for the browser. After the user finishes, auth_status (or simply retrying the tool that needed a session) confirms the session. Call this when a tool answers that it needs an authenticated session. Repeat calls while a sign-in is pending return the same link.",

crates/imcp2-local/src/server.rs:207

  • On a repeated authenticate call, fresh is false and LoginDriver::begin does not invoke open::that, but this message still says the system was asked to open the browser whenever auto-open is enabled. Gate this text on fresh as well so the tool result accurately describes what happened.
                let opener = if self.auto_open {
                    "I also asked the system to open it in your browser. "
                } else {
                    ""
                };

Comment thread crates/imcp2-core/src/iiconnect.rs Resolved
Comment thread crates/imcp2-core/src/tools.rs Resolved
The pinned page's inline script used single-character names (t, cls, c,
r, d); source stays readable — minification, if ever wanted, is a build
transformation, not a source style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Copilot AI review requested due to automatic review settings August 27, 2026 09:40
Comment thread crates/imcp2-local/src/login.rs Resolved
Comment thread crates/imcp2-local/src/login.rs Resolved

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 9 out of 10 changed files in this pull request and generated 1 comment.

Comment thread crates/imcp2-local/src/login.rs Resolved
…lver

The SessionSource enum put deployment knowledge (bearer vs singleton)
inside core's tool layer. Replace it with an injected resolver: core
only asks "which already-validated session does this call act as?", and
each binary owns its authentication end to end, in its own package —
the hosted crate's require_token middleware validates the bearer and
its resolver reads the stashed AuthedSession back (both now defined in
imcp2's auth.rs again); the local binary's login flow fills its
SessionSlot (now defined in imcp2-local's login.rs) and hands core the
slot-reading resolver. Core drops its `http` dependency along the way —
the request-extension read was the only use.

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

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 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

crates/imcp2-local/src/login.rs:420

  • The Stage 2 acceptance criteria in docs/scoping-local-deployment.md:621-624 require an automated Chromium/Firefox run plus Safari/enterprise-policy validation before this loopback callback flow is declared working. This PR explicitly leaves that gate unchecked and the reqwest test cannot exercise browser mixed-content/PNA policy, fragment handling, or the page script. Complete the required browser compatibility gate (or align the stated stage/scope) before shipping Stage 2.
/// GET /callback — the pinned fragment-reading page (shared with the hosted
/// server), pointed at this listener's `/redeem`, with the same non-CSP
/// hardening headers the hosted server adds.
async fn callback_page() -> Response {

claude added 2 commits August 27, 2026 10:03
…h the opener

- login.rs (~1000 lines) becomes the login/ module: mod.rs (the driver
  and its state machine), slot.rs (the SessionSlot), routes.rs (the
  loopback listener's three routes + Host validation), tests.rs.
- CONTACT -> CONTACT_EMAIL, saying what kind of string it is.
- The browser opener uses open::that_detached: open 5.x already nulls
  the launcher's stdio either way (so nothing could interleave with the
  JSON-RPC stream), but detached additionally double-forks the launcher
  away off macOS, so it can never hold a thread or a handle on us.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
…blind to it

Component 7 now says what the reviewed implementation does: IcTools
asks one injected SessionResolver "which already-validated session is
this call?" and knows nothing else — not the transport, not how callers
are validated, not whether the deployment is single- or multi-user.
Each binary owns its authentication end to end in its own package (the
hosted bearer middleware + AuthedSession in imcp2's auth.rs; the local
login flow's SessionSlot in imcp2-local), and the doc spells out why
the seam is a value-returning callback rather than a middleware slot in
core: real authentication middleware needs its transport, so the
middlewares live with their transports and core receives only their
outcome. Core's dependency profile loses `http` accordingly, and the
concise doc's component 7 matches.

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

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 17 out of 18 changed files in this pull request and generated 2 comments.

Comment thread crates/imcp2-local/src/main.rs Resolved
Comment thread crates/imcp2-local/src/login/routes.rs Resolved
…is PR

- The local binary now runs the same 60-second session reaper the
  hosted server does: expired grants, abandoned sign-in keys, and their
  cached delegations leave memory promptly instead of accumulating over
  a long-lived process (below MAX_PENDING_CONNECTS nothing else scans
  the map).
- The register -> slot/grant commit -> listener-shutdown success path
  now has an in-repository test ON THIS PR: the PocketIC end-to-end
  login (a live II canister issues a genuine canister-signed
  registration delegation; the test plays the browser over real HTTP
  against the real listener) moves here from the follow-up branch,
  gated exactly like the hosted crate's harness (cargo feature `e2e` +
  II_WASM/POCKET_IC_BIN at runtime). Run here and passing.

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

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 18 out of 19 changed files in this pull request and generated 1 comment.

Comment thread crates/imcp2-local/src/main.rs Resolved
The reaper removes an expired session's server-side state, but the slot
kept resolving its id — session-requiring tools would then bypass their
"needs an authenticated session" guidance and surface internal errors
(or re-create a keyless session via ensure_session). The slot now
stores the grant expiration II returned and reports NO session the
moment the grant lapses, so unauthenticated is immediate and accurate —
the reaper's 60-second cadence frees memory, it does not gate
authentication. auth_status keeps its Expired reporting via the
driver's retained Grant.

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

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 18 out of 19 changed files in this pull request and generated no new comments.

@aterga
aterga merged commit d50fd08 into main Aug 27, 2026
8 checks passed
@aterga aterga mentioned this pull request Sep 1, 2026
8 of 10 tasks
sea-snake pushed a commit that referenced this pull request Sep 1, 2026
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>
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