feat(fe): add /mcp delegation flow for MCP server sign-in#4026
Conversation
Adds a /mcp page that lets a deploy-configured MCP server obtain an Internet Identity delegation acting as the user's default account at a given application, plus an MCP access device-gate in Settings. - New mcp_server_origin frontend deploy arg, appended to the form-action CSP and exposed to the frontend; /mcp delivers the delegation to that origin only (rejects any other callback origin). - /mcp route: GET-in/redirect-back flow mirroring /cli — McpHero, the combined 'Allow MCP access' authorize screen, success/invalid/error and the MCP-access-not-enabled gate; default account resolved via get_default_account so MCP acts as the same principal /authorize gives. - Settings: new MCP access card + confirm dialog (CLI card untouched). - mcpAuthorizeFunnel analytics; e2e fixture + spec; CSP unit test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
✅ No security or compliance issues detected. Reviewed everything up to 8e6c0fa. Security Overview
Detected Code Changes| Change Type | Relevant files ... (code changes summary truncated to fit VCS comment limits.) |
There was a problem hiding this comment.
Pull request overview
This PR adds an operator-configured /mcp delegation flow to Internet Identity, allowing a single trusted MCP server to obtain an app-scoped Internet Identity delegation on the user’s behalf, gated by a per-device opt-in and a consent screen. It extends the frontend deploy args and CSP to safely allow a single remote form-action destination for the MCP server callback, and adds e2e coverage analogous to the existing /cli flow.
Changes:
- Add
mcp_server_origindeploy arg (Candid + Rust types) and thread it into CSP generation to append exactly one allowedform-actionorigin. - Implement the
/mcproute UI + logic (fragment-parsed request, consent screen, device gate, success/error/invalid terminal screens) and identity-switcher behavior. - Add MCP device opt-in toggle in Settings + analytics funnel + Playwright e2e fixture/spec for the flow.
Reviewed changes
Copilot reviewed 28 out of 30 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/internet_identity_interface/src/internet_identity/types.rs | Adds mcp_server_origin to shared frontend args. |
| src/internet_identity_frontend/tests/integration/http.rs | Updates default args helper to include mcp_server_origin. |
| src/internet_identity_frontend/src/main.rs | Threads mcp_server_origin into CSP generation and adds CSP unit test. |
| src/internet_identity_frontend/local_test_arg.did.template | Enables MCP origin in local test template. |
| src/internet_identity_frontend/internet_identity_frontend.did | Adds mcp_server_origin to the init interface. |
| src/frontend/tests/e2e-playwright/routes/mcp.spec.ts | New e2e spec covering invalid/off-origin/gate/success/TTL/principal parity. |
| src/frontend/tests/e2e-playwright/fixtures/mcp.ts | New fixture to intercept MCP form POST navigation + redirect-back. |
| src/frontend/tests/e2e-playwright/fixtures/index.ts | Registers MCP fixture into merged test fixture. |
| src/frontend/src/routes/(new-styling)/mcp/views/McpInvalidView.svelte | Invalid-request terminal screen. |
| src/frontend/src/routes/(new-styling)/mcp/views/McpErrorView.svelte | Error terminal screen for MCP-reported failure. |
| src/frontend/src/routes/(new-styling)/mcp/views/McpDisabledView.svelte | Device-gated terminal screen when MCP access is disabled. |
| src/frontend/src/routes/(new-styling)/mcp/views/McpCloseWindowView.svelte | Success terminal screen instructing user to close the tab. |
| src/frontend/src/routes/(new-styling)/mcp/views/McpAuthorizeView.svelte | Consent screen (“Allow MCP access”) and submit UI. |
| src/frontend/src/routes/(new-styling)/mcp/utils.ts | Builds and delivers a two-hop delegation chain via top-level form POST. |
| src/frontend/src/routes/(new-styling)/mcp/mcp-switcher.store.ts | Store controlling identity switcher visibility for /mcp layout. |
| src/frontend/src/routes/(new-styling)/mcp/components/McpHero.svelte | Header hero illustrating app ↔ MCP server relationship. |
| src/frontend/src/routes/(new-styling)/mcp/+page.ts | Fragment parsing/validation for MCP request params + status. |
| src/frontend/src/routes/(new-styling)/mcp/+page.svelte | Main /mcp flow state machine, validation, analytics, and navigation. |
| src/frontend/src/routes/(new-styling)/mcp/+layout.svelte | /mcp-scoped layout with identity switcher + dialogs. |
| src/frontend/src/routes/(new-styling)/manage/(authenticated)/settings/components/McpConfirmDialog.svelte | Confirmation dialog with risk acknowledgement checkbox. |
| src/frontend/src/routes/(new-styling)/manage/(authenticated)/settings/components/McpAccessSection.svelte | Settings card + toggle wiring for device-local MCP access. |
| src/frontend/src/routes/(new-styling)/manage/(authenticated)/settings/+page.svelte | Adds MCP access card to Settings page. |
| src/frontend/src/lib/utils/analytics/mcpAuthorizeFunnel.ts | Adds analytics funnel for /mcp flow. |
| src/frontend/src/lib/stores/mcp-access.store.ts | LocalStorage-backed per-identity MCP access enablement store. |
| src/frontend/src/lib/globals.ts | Exposes getMcpServerOrigin() from canister config. |
| src/frontend/src/lib/generated/internet_identity_frontend_types.d.ts | Regenerated types including mcp_server_origin. |
| src/frontend/src/lib/generated/internet_identity_frontend_idl.js | Regenerated IDL including mcp_server_origin. |
| src/frontend/src/lib/constants/store.constants.ts | Adds LocalStorage key for MCP access. |
| src/frontend/src/lib/components/ui/McpLogo.svelte | Adds MCP logo UI component. |
| scripts/frontend-arg-helpers.bash | Adds mcp_server_origin to interactive deploy-arg helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Move the MCP logo from lib/components/ui/McpLogo.svelte to lib/components/icons/McpIcon.svelte, alongside PasskeyIcon/SsoIcon, and match their SVGAttributes/size-5 convention. - main.rs: sanitize mcp_server_origin to a strict scheme://host[:port] origin before splicing into the form-action CSP; drop (fail closed) any value carrying whitespace/';'/','/path/query/fragment/userinfo or a disallowed scheme, so a misconfigured deploy arg can't break or inject the policy. Extend the CSP test with malformed cases. (Copilot) - /mcp +page.svelte: parse the configured origin defensively (try/catch); a malformed value is treated as unset so the route shows the invalid screen instead of throwing during render. (Copilot) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
build_fe_install_arg omitted mcp_server_origin, so staging deploys via deploy-pr-to-beta always sent null and never asked for it — leaving /mcp disabled. Add the prompt (default parsed from the live /.config) and emit the field in the FE install arg. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ity + sign_in + list_identities) New auth model (PR dfinity/internet-identity#4026): the MCP server holds a per-OpenID-session Ed25519 key; the user signs into a domain via II's /mcp flow, which delegates to that key as their default account for the app. The server then signs calls as that identity with ic-agent's DelegatedIdentity. - identities.rs: per-session key + per-domain delegation store; sign-in state machine (link -> GET /signin -> II redirect -> form-POST callback); parses DelegationChain JSON into ic-agent SignedDelegations; builds DelegatedIdentity. - call_canister gains an arg (anonymous | <domain>); list_identities; sign_in(domain) returns the short URL. - Session binding: OpenID approve mints a session_id + sets an httpOnly mcp_session cookie carried to the token; GET /signin requires that cookie to match the link's session; a SameSite=None flow cookie is required on the cross-site callback POST; state is single-use and routes to the session. - II staging target uhh2r-oyaaa-aaaad-agbva-cai.icp0.io (II_MCP_URL overridable). Builds clean; 7 existing tests pass. propose_call/check_proposal/the /app flow still present — trimmed in a follow-up. Real round-trip needs the staging II deployed with mcp_server_origin = our origin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The e2e job installs the frontend canister with inline --args that omitted mcp_server_origin, so getMcpServerOrigin() was undefined and the /mcp flow always rendered the invalid screen, timing out all interactive mcp.spec.ts tests. Add mcp_server_origin = opt "https://mcp.id.ai" to match the origin the mcp fixture posts to.
Apply reviewer suggestion from aterga.
Adds an MCP sign-in flow analogous to the recently shipped CLI flow: a single, deploy-configured (operator-trusted) MCP server can obtain an Internet Identity delegation that acts as the user's default account at a given application. This lets AI assistants connecting through the ICP MCP server act on the user's behalf in apps, gated by an explicit per-device opt-in and a per-request consent screen. UI follows the Claude Design handoff (authorize-mcp screens + MCP settings card).
Changes
mcp_server_originonInternetIdentityFrontendArgs/internet_identity_frontend.did, exposed to the frontend viaglobals.ts, and appended to theform-actionCSP for that single origin (never broadened tohttps:). The shared deploy-arg helper (frontend-arg-helpers.bash) and the local test arg template support it./cli. Request params live in the URL fragment; the callback must match the configured MCP origin (belt-and-suspenders with the CSP). Screens:McpHero(app · lock · MCP server), the combined "Allow MCP access" authorize screen, success, invalid, error, and the "MCP access not enabled" device gate. The delegation is built viaget_default_account→prepare/get_account_delegation, so MCP acts as the same principal/authorizegives for that app.mcpAuthorizeFunnel.Tests
form-actionand that it is never broadened tohttps:.fixtures/mcp.ts,routes/mcp.spec.ts): invalid params, off-origin callback rejected, device-gate behavior, successful two-hop delegation, TTL honoured, identity-switcher visibility, and principal-parity with/authorize.npm run check, eslint, prettier,cargo clippy, the candid-compat test, and the frontend bin tests all pass locally; the e2e suite runs in CI.🤖 Generated with Claude Code