Skip to content

WIP feat(mcp): support pre-registered OAuth clients via catalog metadata#444

Draft
saucow wants to merge 5 commits intodocker:mainfrom
saucow:slim/manual-oauth-registration
Draft

WIP feat(mcp): support pre-registered OAuth clients via catalog metadata#444
saucow wants to merge 5 commits intodocker:mainfrom
saucow:slim/manual-oauth-registration

Conversation

@saucow
Copy link
Copy Markdown
Contributor

@saucow saucow commented Mar 16, 2026

Summary

Enables OAuth for MCP servers whose providers don't support Dynamic Client Registration (DCR) -- such as Google, Slack, and Microsoft. Catalog authors embed the OAuth client_id and server metadata (authorization/token endpoints, scopes) in a new RFC 8414-aligned schema. The client_secret is never distributed in catalogs; users provide it via Docker's secrets store.

Problem

The existing OAuth flow assumes providers support DCR: the gateway discovers OAuth endpoints via /.well-known/oauth-authorization-server and dynamically registers a client. Providers like Google and Slack don't support DCR, leaving teams with no way to set up OAuth through catalogs.

Solution

New catalog schema

Two new optional sections on OAuthProvider, following RFC 8414 field naming:

oauth:
  providers:
    - provider: google-workspace
      server_metadata:                                    # Public OAuth endpoints (omit if discoverable)
        authorization_endpoint: "https://accounts.google.com/o/oauth2/v2/auth"
        token_endpoint: "https://oauth2.googleapis.com/token"
        scopes_supported: ["https://www.googleapis.com/auth/drive"]
      registration:                                       # Out-of-band client credentials
        client_id: "700819..."                            # Only client_id -- no secrets in catalogs

The client_secret is declared as a required secret in the server's secrets section. Users set it via docker mcp secret set {server}.client_secret. The gateway reads it from the Secrets Engine and passes it to Pinata during DCR registration.

How it flows

sequenceDiagram
    participant Admin as Catalog Admin
    participant User as Team Member
    participant GW as Gateway
    participant P as Pinata
    participant SE as Secrets Engine
    participant Google as Google OAuth

    Note over Admin: One-time: register Google OAuth app, push catalog with client_id + endpoints
    User->>GW: Pull catalog, create profile with server
    GW->>SE: Read client_secret (user set via docker mcp secret set)
    GW->>P: RegisterDCRClientPending with client_id + client_secret + endpoints + scopes
    Note over P: Create REGISTERED client directly (skip DCR)
    User->>P: Click Authorize (or docker mcp oauth authorize)
    P->>Google: Authorization URL with scopes + PKCE + access_type=offline + prompt=consent
    Google-->>P: Callback with code
    P->>Google: Exchange code (+ client_secret + PKCE)
    Google-->>P: access_token + refresh_token
    P->>SE: Store token
    Note over P: Background refresh loop maintains token
Loading

CE mode support

For standalone environments without Docker Desktop:

  • docker mcp oauth register command for manual client registration
  • docker mcp oauth authorize handles the full flow (localhost callback, PKCE, token exchange)
  • readCEModeOAuthSecrets() resolves tokens from the credential helper for container secret injection (no se:// URI resolution in CE mode)

Key changes

File Change
pkg/catalog/types.go OAuthServerMetadata (RFC 8414), OAuthRegistration, HasPreRegisteredOAuth()
pkg/oauth/dcr_registration.go Read client_secret from Secrets Engine, pass to Pinata with scopes
pkg/workingset/oauth.go Read scopes from server_metadata.scopes_supported
cmd/docker-mcp/oauth/auth.go Re-register DcrClient with client_secret at authorize time (CLI path)
cmd/docker-mcp/oauth/register.go New docker mcp oauth register command for CE mode
cmd/docker-mcp/server/enable.go HasPreRegisteredOAuth() check alongside HasExplicitOAuthProviders()
pkg/gateway/clientpool.go Skip secrets without env field (OAuth infrastructure, not container env vars)
pkg/gateway/secrets_ce.go CE mode token injection from credential helper
pkg/gateway/configuration*.go CE mode secret supplementation
pkg/gateway/run.go Start OAuth provider loop for pre-registered servers
pkg/desktop/auth.go ClientSecret + Scopes on RegisterDCRRequest

Backward compatibility

  • All new fields are optional. Existing catalogs and DCR servers are unchanged.
  • HasExplicitOAuthProviders() (remote servers with DCR) continues to work as before.
  • The client_secret field exists on RegisterDCRRequest but is never populated from the catalog schema -- it comes from the Secrets Engine.

slimslenderslacks and others added 5 commits March 7, 2026 08:58
This commit adds support for manually registering OAuth client credentials
for MCP servers that don't support Dynamic Client Registration (DCR), along
with comprehensive documentation of OAuth flows in Docker CE mode.

Changes:
- Add 'docker mcp oauth register' command for manual client registration
  - Supports both confidential and public OAuth clients
  - Stores credentials securely in Docker credential helpers
  - Includes validation for URLs and required fields
- Add detailed OAuth CE mode documentation (572 lines)
  - Documents DCR flow, authorization, token storage, and refresh
  - Includes architecture diagrams, CLI examples, and troubleshooting
  - Provides file references with line numbers for code navigation
  - Covers security features (PKCE, token binding, credential helpers)
- Fix linting issues in OAuth command handlers
  - Add explicit error handling for MarkFlagRequired calls
  - Rename unused context parameter to underscore
  - Apply gofmt formatting to imports

The manual registration feature enables OAuth integration with providers
that don't support RFC 7591 DCR, expanding compatibility with a wider
range of OAuth providers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
In CE mode, Docker Desktop's secret mechanisms (jcat, Secrets Engine,
se:// URIs) are not available. OAuth tokens stored by `docker mcp oauth
authorize` in the credential helper were only accessible to remote
servers. This adds a CE mode path that reads OAuth tokens from the
credential helper and injects them as raw values into local container
environment variables.

Reuses the existing CredentialHelper.GetOAuthToken() -> getOAuthTokenCE()
code path that already works for remote servers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…njection

Catalog schema:
- OAuthServerMetadata (RFC 8414 field naming) + OAuthRegistration (client_id only)
- client_secret never in catalogs, user provides via secrets store
- HasPreRegisteredOAuth() enables OAuth for non-remote servers

OAuth registration:
- Read client_secret from Secrets Engine at registration time, pass to Pinata
- Read scopes from server_metadata.scopes_supported
- Re-register DcrClient with client_secret at authorize time (CLI path)

CE mode:
- readCEModeOAuthSecrets() for container secret injection without se:// URIs
- docker mcp oauth register command for manual client registration

Container args:
- Skip secrets without env field (OAuth infrastructure secrets)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants