Skip to content

Add CIMD support for hosted MCP OAuth #1102

Description

@dcramer

Summary

Add and validate support for OAuth Client ID Metadata Documents (CIMD) for the hosted MCP Cloudflare OAuth proxy.

CIMD lets OAuth clients use an HTTPS URL as their client_id, where that URL serves the client metadata document. In MCP 2025-11-25, clients and authorization servers SHOULD support CIMD; Dynamic Client Registration is now a MAY fallback for compatibility.

Current State

Confirmed from source inspection:

  • @cloudflare/workers-oauth-provider supports CIMD in the version currently installed by this repo (0.3.0) and in the latest published version (0.8.0).
  • The provider requires both:
    • clientIdMetadataDocumentEnabled: true
    • the Workers global_fetch_strictly_public compatibility flag
  • Our wrangler.jsonc, wrangler.canary.jsonc, and wrangler.test.jsonc already include global_fetch_strictly_public.
  • Our Cloudflare OAuth proxy does not currently pass clientIdMetadataDocumentEnabled: true to new OAuthProvider(...).
  • Our path-scoped authorization-server metadata compatibility shim does not advertise client_id_metadata_document_supported.
  • Our protected-resource metadata implementation is separate and already path-scoped for /mcp....
  • The MCP TypeScript SDK has client-side CIMD support in @modelcontextprotocol/sdk@1.26.0: it checks client_id_metadata_document_supported, uses provider.clientMetadataUrl as the client_id when available, and falls back to Dynamic Client Registration otherwise.
  • I did not find equivalent direct CIMD support in @ai-sdk/mcp@1.0.52.
  • Our repo-local mcp-test-client always uses Dynamic Client Registration today.

Relevant files:

  • packages/mcp-cloudflare/src/server/index.ts
  • packages/mcp-cloudflare/src/server/authorization-server-metadata.ts
  • packages/mcp-cloudflare/src/server/protected-resource-metadata.ts
  • packages/mcp-cloudflare/wrangler*.jsonc
  • packages/mcp-test-client/src/auth/oauth.ts

Goals

  • Enable CIMD support in the hosted Cloudflare OAuth proxy.
  • Advertise CIMD support consistently through authorization-server metadata.
  • Preserve Dynamic Client Registration fallback for clients that do not support CIMD.
  • Keep path-scoped MCP resource behavior intact for /mcp, /mcp/{org}, and /mcp/{org}/{project}.
  • Add focused tests that prove the behavior and guard the security-sensitive edge cases.

Non-Goals

  • Do not remove Dynamic Client Registration.
  • Do not change stdio auth; stdio uses Sentry device-code auth and is unrelated to CIMD.
  • Do not implement a custom CIMD fetcher unless the Cloudflare provider behavior proves insufficient.
  • Do not add enterprise-managed authorization or ID-JAG support as part of this work.

Proposed Plan

1. Enable Cloudflare provider support

  • Add clientIdMetadataDocumentEnabled: true to new OAuthProvider(...) in packages/mcp-cloudflare/src/server/index.ts.
  • Keep the existing global_fetch_strictly_public flag in all Wrangler configs.
  • Confirm local/test runtime exposes the flag sufficiently for provider metadata to report support.

2. Advertise support in metadata

  • Verify root authorization metadata from the Cloudflare provider reports:

    {
      "client_id_metadata_document_supported": true
    }
  • Update createScopedAuthorizationServerMetadataResponse(...) so path-scoped compatibility metadata also includes client_id_metadata_document_supported: true.

  • Ensure scoped metadata still includes the resource-bound authorization endpoint for /mcp... URLs.

3. Add server behavior tests

Add Cloudflare package tests covering:

  • /.well-known/oauth-authorization-server includes client_id_metadata_document_supported: true.
  • /.well-known/oauth-authorization-server/mcp includes client_id_metadata_document_supported: true.
  • /.well-known/oauth-authorization-server/mcp/{org}/{project} includes client_id_metadata_document_supported: true.
  • Existing protected-resource metadata still returns the exact path/query resource identifier.
  • Existing WWW-Authenticate patching still points to path-specific protected-resource metadata.

4. Add CIMD flow tests

Use mocked fetch or worker test harness support to simulate a client metadata URL.

Cover successful flow:

  • Authorization request uses client_id=https://client.example/oauth/client.json.
  • Provider fetches the metadata document.
  • Metadata document includes matching client_id, client_name, redirect_uris, grant_types, response_types, and token_endpoint_auth_method: "none".
  • Authorization request redirect URI is accepted only when listed in the fetched metadata document.

Cover failure cases:

  • Metadata URL fetch returns non-200.
  • Metadata document client_id does not exactly match the metadata URL.
  • Metadata document has no redirect_uris.
  • Authorization request uses a redirect URI not present in the metadata document.
  • Metadata document uses disallowed token_endpoint_auth_method, such as client_secret_post.
  • Oversized metadata response is rejected by provider behavior if feasible to exercise.

5. Review consent and phishing UX

CIMD changes what the user sees during OAuth consent because the client identity is now fetched from a URL.

Review the authorization approval UI and tests to ensure it displays enough information for user trust decisions:

  • client name
  • client metadata host or full client ID URL
  • redirect URI hostname
  • warnings or clear treatment for localhost redirect URIs

If existing UI only shows the friendly client name, add the hostname/redirect host before enabling broadly.

6. Decide whether to update repo-local test client

The current mcp-test-client is DCR-only. Options:

  • Leave it as DCR-only and rely on dedicated Cloudflare tests for CIMD.
  • Add optional CIMD support with a clientMetadataUrl config/flag so it can exercise the new path end-to-end.

If we update it, also fix the adjacent MCP 2025-11-25 drift where token requests should include the RFC 8707 resource parameter; it currently sends resource on authorization but not token exchange.

7. Validate against real clients

After unit/integration tests:

  • Test with an MCP SDK OAuth client configured with clientMetadataUrl.
  • Verify the SDK chooses CIMD when metadata advertises support.
  • Verify the same SDK falls back to DCR when the flag is disabled or metadata omits support.
  • Test at least one non-CIMD client to confirm DCR remains working.

8. Rollout

  • Prefer canary first.
  • Watch OAuth telemetry for:
    • invalid client errors
    • metadata fetch failures
    • redirect URI validation failures
    • registration volume changes
  • If canary is clean, enable in production.

Acceptance Criteria

  • Cloudflare OAuth metadata advertises client_id_metadata_document_supported: true when CIMD is enabled.
  • Path-scoped authorization metadata advertises the same support.
  • URL-based client_id authorization requests work when the metadata document is valid.
  • Invalid metadata documents fail safely.
  • Redirect URIs are validated against fetched metadata.
  • Dynamic Client Registration still works.
  • Existing scoped MCP OAuth flows continue to bind the correct /mcp... resource.
  • Tests cover success and key failure cases.
  • pnpm run tsc && pnpm run lint && pnpm run test passes before merge.

Open Questions

  • Should CIMD be always-on in production once tests pass, or controlled by an env flag for staged rollout?
  • Do we need additional allowlist/trust policy controls for client metadata domains, or is open CIMD acceptable for our hosted MCP server?
  • Should the repo-local mcp-test-client gain first-class CIMD support, or is that better left to SDK/client compatibility QA?
  • Should we upgrade @cloudflare/workers-oauth-provider from 0.3.0 to 0.8.0 before enabling this, given newer versions include related metadata/resource handling improvements?

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions