Skip to content

[FEATURE]: Dynamic Client Registration (RFC 7591) for OAuth-protected MCP servers #5720

Description

@a-effort

Status update (2026-08-28)

DCR is implemented and enabled by default.

  • mcpgateway/services/dcr_service.py implements RFC 7591 registration, AS metadata discovery, client persistence, update and delete.
  • DCR_ENABLED and DCR_AUTO_REGISTER_ON_MISSING_CREDENTIALS both default to true (config.py:1008, :1011).
  • /oauth/authorize/{gateway_id} auto-registers when a gateway has an issuer and no client_id (oauth_router.py:670), persists the credentials and sets auth_type=oauth.
  • Registration sends redirect_uri programmatically, so for DCR-capable providers the operator never copies it.
  • /oauth/registered-clients covers listing, per-gateway lookup and deletion.

What is missing is UI surfacing: nothing tells the user that Client ID and Client Secret can be left blank for a DCR-capable issuer. That hint is tracked in #6460 and depends on the v1 discovery endpoint in #5717.

The deprecation label is correct and traces to #5692 ([Deprecated-3] DCR to Client ID Metadata Documents). The MCP 2026-07-28 spec deprecates RFC 7591 in favour of CIMD, retaining DCR only as a fallback for authorization servers without CIMD support. CIMD is not implemented here yet, so DCR is currently the only registration path.

Given that, do not close this as delivered. Two of its own requirements are unmet by what shipped: Security Requirement 2 (confirmation before registration, not skippable via API) and Scenario 1.5 (de-registration on server deletion). The shipped path auto-registers silently inside /oauth/authorize. Any further investment here should be framed as fallback-path work, and user-facing copy should avoid naming DCR so it survives the move to CIMD.

Note for anyone planning from this issue: because DCR sends the redirect URI without displaying it, the origin mismatch in #6458 becomes invisible on this path.


🧭 Type of Feature

  • Enhancement to existing functionality

🧭 Epic

Dynamic Client Registration (RFC 7591) for OAuth-protected MCP servers

Goal: When the discovery flow (#5717 or #5719) surfaces a registration_endpoint, the user can register a new OAuth client at the provider with one click. ContextForge handles the RFC 7591 protocol exchange, securely stores the returned registration_access_token, and populates client_id/client_secret in the form.

Why now: Companion issues #5717 and #5718 surface DCR availability as a hint ("you may leave Client ID/Secret blank"). The next step — actually performing the registration — eliminates the two largest remaining manual fields on the OAuth form for the providers people most commonly use (Keycloak, Okta, Auth0).

Parent Epic: #5716

🧑🏻‍💻 User Story 1

As a platform administrator registering a Keycloak / Okta / Auth0 MCP server, I want ContextForge to create the OAuth client at my provider automatically, so I do not have to switch to the provider's admin console, create a client manually, and copy IDs back into the form.

Acceptance Criteria

Scenario 1.1: DCR available, no initial access token required
Given discovery returned a registration_endpoint and the provider does not require an initial access token (e.g., Keycloak with anonymous registration enabled), when I click "Register OAuth client automatically", a confirmation dialog summarises what will be registered (client name, redirect URI, grant type, scopes) and warns that the action creates a real OAuth client at the provider. On confirmation, the backend POSTs RFC 7591 client metadata and:

  • client_id is populated in the form
  • client_secret, if returned, is stored server-side encrypted and shown only as •••••• in the UI (never round-tripped in plaintext after the response)
  • registration_access_token is stored encrypted alongside the MCP server record for future updates/deletes
  • A success notice surfaces

Scenario 1.2: Provider requires an initial access token
If a previous DCR attempt returned 401 invalid_token or the provider's metadata signals required initial-access tokens, the UI prompts for an initial access token. The token is sent in the DCR Authorization: Bearer header and is not persisted after the call completes.

Scenario 1.3: DCR not available
If discovery did not surface a registration_endpoint, the "Register OAuth client automatically" button does not appear. The form behaves as today (manual Client ID / Secret entry).

Scenario 1.4: DCR fails
The form surfaces a structured error code (invalid_redirect_uri, invalid_client_metadata, unauthorized, unreachable, timeout) and a human-readable message. Nothing is persisted server-side. The user can retry or fall back to manual entry.

Scenario 1.5: User-initiated client de-registration
A registered client retains its registration_access_token. Deleting the MCP server record offers an option ("Also delete the client at the OAuth provider"). If selected, the backend issues DELETE to the client's registration URI using the stored access token. If de-registration fails, MCP server deletion still proceeds; the failure is logged and the user is informed.

🛡 Security Requirements (must-have)

  1. RBAC: default-deny. Same permission as MCP server creation.
  2. Explicit confirmation before registration. DCR creates a real, persistent side effect at the provider. Confirm dialog is mandatory; cannot be skipped via API.
  3. Per-user rate limit on DCR endpoint (~5/min). Prevents using ContextForge to spam an IdP's registration endpoint.
  4. Encrypt registration_access_token and client_secret at rest using the existing AUTH_ENCRYPTION_SECRET mechanism. Same key handling as other secrets in oauth_config.
  5. Never log client secrets or registration access tokens. Apply redaction in audit log and error-log paths.
  6. HTTPS-only target with SharedHttpClient SSRF posture.
  7. Validate DCR response before persisting: client_id must be a non-empty string; client_secret (if present) must be non-empty; registration_access_token (if present) must be non-empty.
  8. Audit log every DCR call (user, provider URL, outcome, not the secrets themselves).
  9. Software statements (signed JWT registration tokens) are out of scope for v1 — document the gap. Providers requiring software statements (some enterprise OIDC providers) cannot use this flow yet.
  10. Deny-path regression tests: unauthenticated caller, insufficient-permission caller, no registration_endpoint in cached discovery, non-HTTPS registration endpoint, oversized response, provider returns 4xx/5xx, secret persisted only encrypted, secret never returned in plaintext after initial registration response.

📐 Endpoint Contract

POST /gateways/dcr-register
Authorization: Bearer <token>     # gateway-create permission required
Content-Type: application/json

{
  "issuer": "https://auth.example.com",      // must already have been discovered
  "redirect_uri": "https://gw.example.com/oauth/gateway/{id}/callback",
  "grant_types": ["authorization_code"],
  "scopes": ["openid", "profile"],
  "client_name": "ContextForge — MyGateway",
  "initial_access_token": null               // optional
}

200 OK

{
  "registered": true,
  "client_id": "abc123",
  "client_secret_set": true,            // boolean only; secret itself is server-side
  "registration_access_token_set": true,
  "error": null,
  "error_code": null
}

200 OK — failed (form falls back to manual)

{
  "registered": false,
  "client_id": null,
  "client_secret_set": false,
  "registration_access_token_set": false,
  "error": "Provider rejected the client metadata.",
  "error_code": "invalid_client_metadata"
}

A separate DELETE /gateways/{id}/dcr-client removes the client at the provider using the stored registration_access_token.

✅ MCP Standards Check

  • Implements RFC 7591 Dynamic Client Registration.
  • Implements RFC 7592 client registration management (for delete).
  • No breaking changes.

❎ Alternatives Considered

  • Auto-register on form submit (no confirmation). Rejected. DCR is a real side effect at a third-party provider; explicit consent is mandatory.
  • Store secrets client-side after registration. Rejected. Secrets are encrypted server-side via the existing AUTH_ENCRYPTION_SECRET pattern, mirroring how other MCP server secrets are handled.
  • Reuse client_credentials token endpoint for client metadata. Rejected. RFC 7591 is the standard; reusing token endpoints is not.

📎 Additional Context

Builds on:

New code required:

  • DcrService.register_client() and DcrService.delete_client() in mcpgateway/services/dcr_service.py.
  • New router endpoints in mcpgateway/routers/gateways.py.
  • Storage column(s) for registration_access_token and registration management URI on the MCP server record (Alembic migration; idempotent per project convention).

React client:

  • "Register OAuth client automatically" button, visible only when dcr_available: true.
  • Confirmation dialog with summary of what will be registered.

🔗 Related Issues

Activity

  1. added
    enhancementNew feature or request
    triageIssues / Features awaiting triage
    securityImproves security
    uiUser Interface
    apiREST API Related item
    on Jul 20, 2026
  2. removed
    triageIssues / Features awaiting triage
    on Aug 25, 2026
  3. added
    deprecationIssue targets removal of a product feature that is no longer to be supported.
    oauth-oidcOAuth/OIDC related issues and PRs
    on Aug 25, 2026
  4. self-assigned this
    on Sep 9, 2026
  5. added
    1.1CF 1.0 + update MCP support + new UI
    triageIssues / Features awaiting triage
    on Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

1.1CF 1.0 + update MCP support + new UIapiREST API Related itemdeprecationIssue targets removal of a product feature that is no longer to be supported.enhancementNew feature or requestoauth-oidcOAuth/OIDC related issues and PRssecurityImproves securitytriageIssues / Features awaiting triageuiUser Interface

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions