feat: choose an upstream credential per server on the consent screen

Merged
DA
3d ago
+1830-58

AIM-596 (3 of 5)

Stacked on #8037.

Summary

Applies only in organizations with upstream_auth_enforced. Elsewhere consent keeps choosing through its service cards, unchanged.

  • Effective set (consentAuthChoices): each server's enabled, associated auth clients, each validated through the same resolver and mcp:auth permitter as serving (resolveAuthClientChoiceForConsent, a typed-client variant of the existing proposal check). Per option:

    • denied → left out
    • unusable → "not set up"
    • personal → "connect" or "reconnect" through its consent card
    • identity provider → ready once chained
    • shared / pass-through → ready

    Personal and identity-provider clients are skipped for callers representing nobody. Permission is judged as the consenting person, on the page and again on approval (gateway catalog publication included).

  • Connecting offered personal clients: the consent page lists the registration of every personal or identity-provider client a server offers, whether or not the server's sign-in links it or an older association names it, and reads its connection where the resolver serves it. Gateway discovery fetches a chosen client's tokens the same way.

  • Server states: "No authentication required" (explicit, or undecided with nothing offered), "Authentication setup incomplete" (required, nothing offered), "No access" (everything denied), otherwise a choice. Setup-incomplete and no-access block approval, with a request-access link for the latter. Where the page can also approve for an agent, the same message shows while "Myself" is selected.

  • Preselection: the person's pick if still usable; else the client they last approved for that server (ListRememberedAuthClients, any of their sessions) if still usable; else the fixed order: identity provider → shared credentials by name → own account → app's own credential. Nothing is preselected that can't be approved now.

  • Direct servers: a "Sign in to the service as" radio list in the approve form (a hidden input when there's one option). The POST recomputes the set and refuses a pick that is no longer usable ("Reload this page to choose again"). The grant carries AuthClientPicks, with the client's own registration in UpstreamClientPicks.

  • Gateways: each member row gets a select. Changing it reloads the page with auth_client.<slug> picks, so the tools captured for approval are those that credential reaches. Discovery uses the member's chosen client, and catalog publication re-validates that typed client rather than the card-derived pick.

  • Recording: RecordUpstreamClientSelections records a picked typed client with its own registration when the server still offers it enabled, even if the sign-in doesn't bind that registration. A picked headers-only client records none. Existing choices are still never replaced, so later approvals and refresh don't rewrite prior selections.

Not in this PR:

  • gateway members added after issuance (next PR)

Motivation

RFC section 7.6: when a server offers several upstream identities the person chooses one per server (gateway: per member), sees "No access" vs setup vs connect correctly, and the choice is recorded per credential. The picker only prefills; issuance validates.