Skip to content

Migrate II connect to session-key registration + device grant (mcp-server-guide / II #4086) - #27

Merged
aterga merged 2 commits into
mainfrom
claude/mcp-spec-compliance-9202ka
Jul 3, 2026
Merged

aterga merged 2 commits into
mainfrom
claude/mcp-spec-compliance-9202ka

Conversation

@aterga

@aterga aterga commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

What & why

Brings the Internet Identity MCP connect implementation into line with the current MCP server guide / the session-key registration model in dfinity/internet-identity#4086. The previous code implemented the older standing-delegation-chain flow; this PR reworks it to the new passive/registration model.

Phase 1 — connect handshake

  • /oauth/connect/callback now serves II's two cross-origin JSON POSTs, distinguished by the expiration field:
    • key request {state} → 200 {"public_key": "<base64url, unpadded, DER>"} — a fresh Ed25519 session keypair minted per connection, on state match only (unknown/replayed state → 403);
    • completion notification {state, expiration} → store the grant expiration (parsed from the decimal-ns string) and mark the grant live → 204.
  • The server never receives or verifies a delegation chain representing itself, and never calls mcp_register (II's frontend does, under the user's own auth). src/delegation.rs (self-chain verification) is deleted.
  • The connect link carries no key material and uses ttl in seconds (3600), all in the URL fragment.

Phase 2 — acting for the user

  • Signs mcp_get_accounts / mcp_prepare_delegation / mcp_get_delegation directly with the session key (its principal self_authenticating(session_pubkey) is what the grant is bound to).
  • Method names updated per #4086 (were mcp_prepare_account_delegation / mcp_get_account_delegation; mcp_get_accounts unchanged).
  • Mints per-app delegations to a separate per-app key (passed as session_key), acting as the user via a DelegatedIdentity over [user_key → per-app key]. max_ttl is null (II caps ≤ 1h, never past the grant).
  • Any Unauthorized from II is surfaced as "session over → reconnect" (no retry-loop).

OAuth completion (RFC 8628 device grant)

II's handshake has no redirect back to the server, so the MCP client login is modelled as the OAuth 2.0 Device Authorization Grant: POST /oauth/device_authorization → device_code/verification_uri; the user opens the verification page (which launches II's /mcp handshake); the client polls POST /oauth/token (authorization_pending / slow_down) until the grant is live. "Live" = the completion POST arrived or a signed mcp_get_accounts succeeds (belt-and-suspenders, since the completion POST is best-effort). PKCE (S256) is bound to the poll. Discovery metadata advertises the device grant + device_authorization_endpoint.

Other

  • Management tools act as a stable per-user management identity — the user's default account at this server's own origin (PUBLIC_URL), derived on demand — replacing the removed standing identity while preserving the same principal.
  • README (Auth, Domain identities, Connect, Roadmap) rewritten for the new model.

Testing

  • cargo build clean (one pre-existing macro warning); cargo test — 29/29 pass.
  • Drove the full flow locally against a running server (no live II needed, using the completion POST to mark the grant live): metadata → device_authorization → verification page emits a spec-correct connect link (#callback=…&state=…&ttl=3600, no public_key) → key-request POST returns a base64url DER Ed25519 pubkey → unknown-state POST 403 → completion POST 204 → token poll issues the bearer token; /mcp still gates on the token (401 without it); CORS preflight returns 2xx with allow-origin: *.

Notes for review

  • Runtime round-trip needs the #4086 II build deployed at the configured II_URL — the live mcp_* calls can't be exercised from CI. Method names/shapes were taken from #4086's internet_identity.did.
  • The device-grant framing assumes the MCP client supports RFC 8628 polling. The redirect-based alternative would need an II-side change (a return navigation after mcp_register), intentionally avoided here.
  • CORS preflight returns 200 (tower-http default) rather than the guide's example 204; browsers accept any 2xx for preflight.

🤖 Generated with Claude Code

https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym


Generated by Claude Code

…rant

Rework the Internet Identity MCP connect flow to the session-key
registration model (dfinity/internet-identity#4086), replacing the old
standing-delegation-chain flow.

Connect handshake (Phase 1):
- /oauth/connect/callback now serves II's two cross-origin JSON POSTs: a
  key request {state} -> {public_key} (a fresh Ed25519 session keypair
  minted per connection) and a completion notification {state, expiration}
  -> mark the grant live. No delegation chain is received or verified, and
  the server never calls mcp_register (II's frontend does).
- The connect link carries no key material; ttl is in seconds (3600).

Acting for the user (Phase 2):
- Sign mcp_get_accounts / mcp_prepare_delegation / mcp_get_delegation
  directly with the session key; mint per-app delegations to a separate
  per-app key. Method names per #4086 (were *_account_delegation).
- Treat any Unauthorized as "session over -> reconnect" (no retry).

OAuth completion:
- II's handshake has no redirect back, so model the client login as the
  OAuth 2.0 Device Authorization Grant (RFC 8628): a device_authorization
  endpoint + token polling (authorization_pending/slow_down), PKCE bound
  to the poll.

Also: delete src/delegation.rs (self-chain verification no longer needed);
management tools act as a stable per-user "management identity" (the user's
default account at this server's origin); update README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym
@aterga
aterga requested a review from Copilot July 3, 2026 17:22
@aterga
aterga marked this pull request as ready for review July 3, 2026 17:22

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

This pull request migrates the Internet Identity (II) “connect” implementation to the session-key registration model described in the MCP server guide / dfinity/internet-identity#4086, replacing the older standing-delegation-chain approach. It also reframes the MCP client login as an OAuth 2.0 Device Authorization Grant flow (RFC 8628) to match II’s new “no redirect back” handshake.

Changes:

  • Replaces the auth-code redirect flow with an RFC 8628 device-code flow (/oauth/device_authorization, /oauth/device, polling /oauth/token) and updates /oauth/connect/callback to serve II’s two JSON POSTs.
  • Reworks identity/delegation plumbing to sign II mcp_* calls with the registered session key and mint per-app delegations to a distinct per-app key.
  • Removes server-side delegation-chain verification (src/delegation.rs) and updates docs to match the new model.

Reviewed changes

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

Show a summary per file
File Description
src/auth.rs Implements device authorization flow and new JSON-based connect callback behavior.
src/identities.rs Moves to session-key registration model; updates II method calls and delegation derivation.
src/management.rs Switches management tooling to use the new “management identity” derived on demand.
src/main.rs Updates routing (adds device endpoints, removes old authorize route) and simplifies request logging.
src/delegation.rs Deletes now-obsolete server-side delegation-chain verification.
README.md Rewrites documentation for device flow + session-key registration handshake and updated II method names.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/auth.rs Resolved
Comment thread src/identities.rs Resolved
Comment thread src/identities.rs Resolved
Comment thread src/management.rs Resolved
Comment thread src/auth.rs Resolved
- device_authorization: reject code_challenge_method other than S256 (and
  require code_challenge when a method is named), instead of silently
  ignoring it and failing with a confusing S256 mismatch at token time.
- connect_callback: make the completion notification best-effort — only the
  key-request POST strictly validates state; a completion POST for an unknown
  or already-consumed state (e.g. the grant went live via the signed-call
  fallback before the completion POST arrived) returns 204 instead of 403,
  and never creates a session for an unknown state.
- management.rs: rename standing_agent -> management_agent (the standing
  delegation concept is gone; it builds from management_identity).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym
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