Skip to main content

Enable CIMD on the embedded authorization server

This guide shows you how to enable Client ID Metadata Document (CIMD) support on the ToolHive embedded authorization server. With CIMD enabled, MCP clients that host a public metadata document — such as VS Code (https://vscode.dev/oauth/client-metadata.json) — can authenticate without registering through the /oauth/register endpoint first.

note

CIMD support on the embedded AS requires Kubernetes and the ToolHive Operator. It is not available for standalone thv run deployments.

For the conceptual background on CIMD, including the two-layer architecture and security model, see Client ID Metadata Documents (CIMD).

Availability

The cimd field on MCPExternalAuthConfig is available from the version of the ToolHive Operator that includes this feature. Check the ToolHive release notes to confirm the minimum required version before applying this configuration.

Prerequisites

  • Kubernetes cluster with the ToolHive Operator installed
  • An existing MCPExternalAuthConfig resource with type: embeddedAuthServer and at least one upstream provider configured (see Set up embedded authorization server authentication)
  • kubectl access to your cluster

Enable CIMD

Add a cimd block to the embeddedAuthServer section of your MCPExternalAuthConfig resource and set enabled: true.

apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPExternalAuthConfig
metadata:
name: my-embedded-auth-server
namespace: toolhive-system
spec:
type: embeddedAuthServer
embeddedAuthServer:
issuer: https://auth.example.com
upstreamProviders:
- name: github
type: oidc
oidcConfig:
issuerUrl: https://github.com
clientId: your-github-client-id
clientSecretRef:
name: github-client-secret
key: client_secret
cimd:
enabled: true

Apply the updated resource:

kubectl apply -f my-embedded-auth-server.yaml

The embedded AS begins advertising client_id_metadata_document_supported: true in its OAuth discovery documents (/.well-known/oauth-authorization-server and /.well-known/openid-configuration). MCP clients that read discovery metadata will use CIMD automatically.

Configure cache settings

The embedded AS caches fetched CIMD documents to avoid an outbound HTTP request on every authorization. Two fields control the cache:

FieldDefaultDescription
cacheMaxSize256Maximum number of CIMD documents held in the LRU cache. When the cache is full, the least-recently-used entry is evicted.
cacheFallbackTtl5mFixed TTL for each cached document. Cache-Control header parsing is not yet implemented; all entries use this value.

Adjust these values based on the number of distinct MCP clients you expect and how quickly you need metadata changes to take effect:

cimd:
enabled: true
cacheMaxSize: 512
cacheFallbackTtl: '10m'
info

Setting cacheFallbackTtl to a shorter value causes more frequent outbound fetches to the client's metadata URL. Setting it to a longer value means a client's metadata changes (for example, adding a new redirect URI) take longer to propagate. Until Cache-Control header parsing is implemented, cacheFallbackTtl is the only way to control cache lifetime.

Verify the configuration

Confirm that the embedded AS is advertising CIMD support in its discovery document. Port-forward to the proxy service and query the well-known endpoint:

# Replace <mcp-server-name> with the name of your MCPServer resource
kubectl port-forward -n toolhive-system svc/mcp-<mcp-server-name>-proxy 8080:8080 &
curl -s http://localhost:8080/.well-known/oauth-authorization-server | \
python3 -m json.tool | grep client_id_metadata_document

You should see:

"client_id_metadata_document_supported": true

Complete example

The following example shows a full MCPExternalAuthConfig with CIMD enabled, custom cache settings, and an OIDC upstream provider:

apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPExternalAuthConfig
metadata:
name: my-embedded-auth-server
namespace: toolhive-system
spec:
type: embeddedAuthServer
embeddedAuthServer:
issuer: https://auth.example.com
upstreamProviders:
- name: default
type: oidc
oidcConfig:
issuerUrl: https://accounts.google.com
clientId: your-google-client-id
clientSecretRef:
name: google-oauth-secret
key: client_secret
baselineClientScopes:
- openid
- offline_access
cimd:
enabled: true
cacheMaxSize: 256
cacheFallbackTtl: '5m'
tip

baselineClientScopes and CIMD work independently. Baseline scopes are unioned into every client's registered scope set — this applies equally to DCR-registered clients and to CIMD-resolved clients. Keep your baseline scopes narrow to avoid granting unexpected permissions to dynamically resolved third-party clients.

Troubleshooting

VS Code is not using CIMD

VS Code reads the OAuth discovery document when it first connects to an MCP server. If client_id_metadata_document_supported is absent or false, VS Code falls back to DCR. Check that:

  1. cimd.enabled is set to true in the MCPExternalAuthConfig.
  2. The resource has been applied and the operator has reconciled it:
    kubectl describe mcpexternalauthconfig my-embedded-auth-server -n toolhive-system
  3. The proxy runner pod has restarted after the configuration change if you updated an existing resource.

CIMD authentication is failing with invalid_client

The embedded AS rejects CIMD documents that fail validation. Common causes:

  • The client_id field inside the fetched document does not exactly match the URL used to fetch it. The client's metadata document must be self-consistent.
  • The document declares a token_endpoint_auth_method that uses a symmetric shared secret (client_secret_post, client_secret_basic, client_secret_jwt). CIMD clients cannot use shared-secret authentication methods.
  • The document declares grant_types that do not include authorization_code, or declares a response_type other than code.
  • The redirect_uris field is empty or contains a URI that fails strict validation.

The embedded AS cannot reach the client's metadata URL

The embedded AS makes an outbound HTTPS request to fetch the CIMD document. If your cluster has egress restrictions, ensure that the proxy runner pod has network access to https://vscode.dev (or whichever host the client uses). The fetch uses a five-second timeout; if the fetch times out, the authorization request fails with an error.