Repository navigation
Migrate II connect to session-key registration + device grant (mcp-server-guide / II #4086) - #27
Merged
Merged
Conversation
…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
There was a problem hiding this comment.
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/callbackto 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.
- 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/callbacknow serves II's two cross-origin JSON POSTs, distinguished by theexpirationfield:{state}→200 {"public_key": "<base64url, unpadded, DER>"}— a fresh Ed25519 session keypair minted per connection, on state match only (unknown/replayed state →403);{state, expiration}→ store the grant expiration (parsed from the decimal-ns string) and mark the grant live →204.mcp_register(II's frontend does, under the user's own auth).src/delegation.rs(self-chain verification) is deleted.ttlin seconds (3600), all in the URL fragment.Phase 2 — acting for the user
mcp_get_accounts/mcp_prepare_delegation/mcp_get_delegationdirectly with the session key (its principalself_authenticating(session_pubkey)is what the grant is bound to).mcp_prepare_account_delegation/mcp_get_account_delegation;mcp_get_accountsunchanged).session_key), acting as the user via aDelegatedIdentityover[user_key → per-app key].max_ttlisnull(II caps ≤ 1h, never past the grant).Unauthorizedfrom 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/mcphandshake); the client pollsPOST /oauth/token(authorization_pending/slow_down) until the grant is live. "Live" = the completion POST arrived or a signedmcp_get_accountssucceeds (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
PUBLIC_URL), derived on demand — replacing the removed standing identity while preserving the same principal.Testing
cargo buildclean (one pre-existing macro warning);cargo test— 29/29 pass.device_authorization→ verification page emits a spec-correct connect link (#callback=…&state=…&ttl=3600, nopublic_key) → key-request POST returns a base64url DER Ed25519 pubkey → unknown-state POST403→ completion POST204→ token poll issues the bearer token;/mcpstill gates on the token (401without it); CORS preflight returns 2xx withallow-origin: *.Notes for review
II_URL— the livemcp_*calls can't be exercised from CI. Method names/shapes were taken from #4086'sinternet_identity.did.mcp_register), intentionally avoided here.200(tower-http default) rather than the guide's example204; browsers accept any 2xx for preflight.🤖 Generated with Claude Code
https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym
Generated by Claude Code