Skip to content

feat(be): backend /mcp account-delegation methods — #4034 rebased on latest main#4052

Draft
aterga wants to merge 9 commits into
mainfrom
claude/zen-keller-70jkva
Draft

feat(be): backend /mcp account-delegation methods — #4034 rebased on latest main#4052
aterga wants to merge 9 commits into
mainfrom
claude/zen-keller-70jkva

Conversation

@aterga

@aterga aterga commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

What this is

This branch is #4034 (sea-snake:feat/mcp-backend-delegation) brought up to date with the latest main, with the merge conflict resolved. #4034's head lives on an external fork that this environment can't push to, so the merged result is published here on a dfinity/internet-identity branch instead. It carries the full #4034 diff plus a merge of current main.

What was done

Verification

  • cargo check -p internet_identity — clean.
  • cargo test -p internet_identity --test integration --no-run — clean (includes feat(be): backend /mcp account-delegation methods (MCP server acts as anchor) #4034's new mcp.rs / config/mcp_server_origin.rs).
  • Generated bindings verified against the pinned didc.
  • Frontend tsc/npm ci could not run in this environment (requires node ≥24/npm ≥11; node 22/npm 10 present) — this is unrelated to the merge: package.json/package-lock.json are byte-identical to main.

See #4034 for the feature description.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CGvgPmEVcgyUP2mAnwnDcj


Generated by Claude Code

sea-snake and others added 9 commits June 18, 2026 14:32
Adds the canister side of the backend-direct MCP delegation model: a single,
deploy-configured MCP server can fetch per-app account delegations for an
anchor without a per-app browser flow.

- `mcp_server_origin` deploy arg (InternetIdentityInit + PersistentState,
  persisted via StorablePersistentState like `backend_origin`). When unset, the
  path is disabled.
- New reverse index `lookup_anchor_with_mcp_principal`
  (StableBTreeMap<Principal, AnchorNumber>, MemoryId 25): maps the principal II
  derives for an anchor's default account at `mcp_server_origin` to the anchor.
- `mcp_set_access(anchor_number, enabled)` / `mcp_access_enabled(anchor_number)`:
  opt-in toggle (authorized as the anchor) that binds/unbinds that principal in
  the index. This is the opt-in the connect flow sets.
- `mcp_prepare_account_delegation(target_origin, session_key, max_ttl)` and
  `mcp_get_account_delegation(target_origin, session_key, expiration)`: called by
  the MCP server as the anchor's mcp-origin principal. The anchor is recovered
  from `caller()` via the index (no `anchor_number` param — being the right
  caller is the authorization); the delegation is for the anchor's default
  account at `target_origin`, capped at 5 minutes, reusing the existing
  account-delegation machinery.

Candid updated for all of the above.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /mcp route now grants the whole-session MCP connection rather than a
per-app authorization:

- On "Allow access", call `mcp_set_access(anchor, true)` before issuing the
  standing delegation, so II records the anchor's principal at
  `mcp_server_origin` and authorizes the MCP server's later on-demand per-app
  delegation calls (it recovers the anchor from the caller). Idempotent.
- Reframe the authorize view copy: "Connect <mcp server>" / "act as you across
  your apps for this session (~1h, then reconnect)", instead of the per-app
  "act as your <app> account".
- Regenerate II candid bindings (didc) for the new `mcp_*` methods and the
  `mcp_server_origin` init field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Handle `mcp_set_access`'s `{ Ok; Err: text }` result directly (its string Err
  isn't a record, so `throwCanisterError` can't type it).
- Add the new `mcp_server_origin` init field to the two `InternetIdentityInit`
  test/mock fixtures (iiConnection.test.ts, vc-flow +page) so `tsc` passes.
- Rework McpHero into a single MCP-server node + hostname badge, mirroring the
  generic (non-app) /cli screen — the connection is whole-session, not per app,
  so there's no app tile. Drops the unused `app`/dapp lookup; views/+page pass
  only `mcpServer`.

`tsc --noEmit` + `svelte-check`: 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The connection is to the MCP server itself, whose origin II already holds in
its `mcp_server_origin` config (FE: callback/CSP check; BE: caller-principal
derivation). So `app` was redundant — a leftover from the per-app authorize
model. `/mcp` now derives the delegation's account origin from the configured
MCP server origin instead of requiring an `app` fragment param (which also
removed an easy way to land on the invalid screen).

- `mcpAuthorize` takes `mcpServerOrigin` (the configured origin) instead of `app`.
- `+page.ts` no longer parses/requires `app` (parseApp removed).
- `+page.svelte` passes `mcpServer.origin`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new lookup_anchor_with_mcp_principal_memory_wrapper field was never read,
which clippy (-D warnings) rejects. Report its size alongside the other
managed-memory wrappers, matching every other lookup index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cipal test

The connect-flow pivot broke the #4026 per-app mcp e2e:
- `mcpAuthorize` now calls `mcp_set_access` on the backend, which reads the BE
  `mcp_server_origin` — but the e2e only set it on the frontend canister. Add it
  to the backend install args in canister-tests.yml so the connect (and its
  delegation post) succeeds.
- Remove "MCP acts as the same principal /authorize gives for that app": the
  browser flow no longer derives per-app — it issues the standing credential for
  the configured MCP-server origin, and per-app delegations are minted
  server-side by mcp_prepare/get_account_delegation (parity test belongs there).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ config

Holistic split: the per-app principal check (removed from the e2e suite) is
canister logic, so it lives here as an integration test, alongside the rest of
the backend behaviour. The browser e2e keeps only the connect-flow UX.

- tests/integration/mcp.rs: caller-authorized minting (anchor recovered from the
  index, no anchor_number arg), 5-min TTL cap, principal-derivation parity with
  the regular account-delegation API, opt-in/revoke, and path-disabled-without-
  mcp_server_origin.
- tests/integration/config/mcp_server_origin.rs: init/update/retain round-trip,
  mirroring the other config fields.
- api_v2 bindings for mcp_set_access / mcp_access_enabled /
  mcp_prepare_account_delegation / mcp_get_account_delegation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the MCP backend-delegation branch up to date with the latest main
(data-only OpenID providers + on-demand SSO discovery/JWKS caches, the
form_post OpenID callback, session delegations, refreshed sign-in dialogs,
etc.).

Conflict resolved in the generated frontend candid binding
src/frontend/src/lib/generated/internet_identity_types.d.ts: regenerated
with the pinned didc from the merged .did so the file is main's binding
plus the mcp_* methods and the mcp_server_origin config field.
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.

3 participants