Skip to content

MCP Inspector: OIDC sign-in spike #830

Description

@jasonmadigan

Follow-up to #671 (Tools) and #672 (Prompts). Spike: prove OIDC sign-in from the MCP Inspector against Keycloak on a local oinc cluster, then decide what ships.

What

Add "Sign in with OIDC" to the Inspector's authentication modal alongside the pasted bearer token. Authorization code with PKCE, done in the browser, with a pre-registered public client. No change to the plugin backend.

Why this shape

The standalone MCP Inspector needs mcp-gateway's Keycloak workarounds (Authorino hostAliases, an API server certificate rewrite, gateway CORS header injection, an EnvoyFilter faking the DCR preflight). Measured on Keycloak 26.4 with the mcp-gateway realm:

Dropping dynamic client registration removes the only step that needed a proxy or a workaround. A pre-registered public client with a redirect URI and web origin is the standard SPA pattern and is what real identity providers expect anyway. spec.oauthProtectedResource on the MCPGatewayExtension already gives the browser the protected resource metadata, so no discovery request to the gateway is needed either.

The backend-brokered alternative (metadata, registration and token exchange through cmd/plugin-server) was designed and rejected for this spike: it insulates from provider CORS and Console CSP at the cost of a second OAuth implementation in Go. Revisit if CSP enforcement or provider CORS bite in practice.

Flow

  1. initialize returns 401. Modal shows "Sign in with OIDC" when the extension has spec.oauthProtectedResource.authorizationServers.
  2. Browser takes issuer, resource and scopes from the extension, and the client id from the annotation console.kuadrant.io/oauth-client-id (default kuadrant-console-mcp-inspector).
  3. Authorization server metadata via window.fetch in the MCP specification order (/.well-known/oauth-authorization-server/<path>, /.well-known/openid-configuration/<path>, <path>/.well-known/openid-configuration). Validate issuer, require authorization_endpoint and token_endpoint, require S256 if code_challenge_methods_supported is present.
  4. PKCE verifier and challenge, random state. Pending entry in sessionStorage. Redirect to the authorization endpoint with resource (RFC 8707).
  5. Provider redirects to a new Console route /mcp-inspector/oauth/callback. Callback page checks state, exchanges the code with a form-encoded POST, keeps only access_token, token_type, expires_in, scope.
  6. Token goes into an in-memory handoff, never storage. Client-side navigate to /mcp-inspector, which restores the namespace and extension and connects through the existing X-Kuadrant-MCP-Authorization path.
  7. Later 401 means sign in again. No refresh tokens.

Never consoleFetch for provider requests; it would carry Console headers off-origin. credentials: 'omit', redirect: 'error'.

Frontend

  • src/utils/mcp/oauth.ts: settings from the extension, discovery, PKCE, authorize URL, code exchange, pending storage, token handoff.
  • MCPOAuthCallbackPage plus a console.page/route for /mcp-inspector/oauth/callback (its own page, so the one-route-per-page rule from the namespace switch crash still holds) and the exposedModules entry.
  • MCPInspectorPage: OIDC action in the modal naming the client id in use, authMode: 'oidc', issuer host and sign out on the connection card, handoff and router-state error handling on mount.
  • yarn i18n for new strings. Update docs/mcp-inspector.md, which currently says OIDC is unsupported, and document the provider requirement: register the Console origin as a web origin and the callback as a redirect URI.

Dev environment

make oinc-mcp-oidc (scripts/mcp-oidc-setup.sh, manifests in scripts/keycloak/), with a matching teardown. Idempotent:

  1. Node IP from the first node's InternalIP; KEYCLOAK_HOST=keycloak.<ip with dashes>.sslip.io. Port 80 on the node is the OpenShift router, reachable from the host on OrbStack and Linux and from every pod, so one plain HTTP issuer URL works for the browser and for Authorino. Stop with an explanation if the host cannot reach it (Docker Desktop, podman machine).
  2. Realm mcp: user mcp / mcp, accessTokenLifespan 1800, public client kuadrant-console-mcp-inspector with standard flow, PKCE S256 required, redirect URI http://localhost:9000/mcp-inspector/oauth/callback, web origin http://localhost:9000. CONSOLE_PORT override applied with kcadm.sh in the pod.
  3. Keycloak 26.4 start-dev --import-realm --proxy-headers=xforwarded, HTTP only, KC_HOSTNAME=http://$KEYCLOAK_HOST set after apply. Route in keycloak with that host, no TLS.
  4. Verify discovery from the host and from inside the cluster.
  5. Patch the MCPGatewayExtension: authorizationServers: [issuer], resource: http://mcp.127-0-0-1.sslip.io/mcp, scopesSupported: [openid, profile, email], and the client id annotation.
  6. Refuse if another AuthPolicy targets the gateway section (print the delete command; do not delete). Apply mcp-inspector-auth: JWT for Keycloak plus the existing API key selector so pasted tokens keep working, /.well-known exempt, 401 with WWW-Authenticate: Bearer resource_metadata=....

MCP_PROXY_ALLOW_INSECURE_AUTH=true stays on the backend because the dev listener is HTTP.

Tests

Jest only for this spike: oauth.test.ts (settings and annotation override, discovery order and rejections, RFC 7636 appendix B vector, authorize URL, token request and response filtering, pending round trip, handoff consumed once), MCPOAuthCallbackPage.test.tsx (state mismatch, provider error, success), MCPInspectorPage.test.tsx additions. No Playwright journey.

Out of scope

  • Dynamic client registration and client id metadata documents.
  • Refresh tokens, multiple authorization servers, provider-side sign-out.
  • Tool-level authorisation in the dev realm.
  • Kuadrant Operator support for ConsolePlugin.spec.contentSecurityPolicy ConnectSrc derived from authorizationServers. Console CSP is Report-Only in 4.22, so this works today, but it must land before Console enforces CSP for plugins.

PR description must include

  • How to review: oauth.ts and its tests first, then the callback page, then the Inspector page changes, then the dev scripts.
  • Manual check: make oinc, make oinc-mcp-oidc, open MCP Inspector, select the gateway, "Sign in with OIDC", log in as mcp / mcp, confirm reconnect and a toystore_greet run, confirm the pasted bearer path still works after sign out, confirm the only CSP entries in the browser console are the Report-Only ones for the Keycloak host.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestneeds designFlag ticket needs some UXD helptriage/needs-triageNew issue, awaiting maintainer review

Type

No type

Projects

  • Status
    Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions