diff --git a/docs/toolhive/concepts/cimd.mdx b/docs/toolhive/concepts/cimd.mdx
new file mode 100644
index 00000000..de3fcf17
--- /dev/null
+++ b/docs/toolhive/concepts/cimd.mdx
@@ -0,0 +1,183 @@
+---
+title: Client ID Metadata Documents (CIMD)
+description:
+ How ToolHive uses Client ID Metadata Documents to eliminate OAuth app
+ pre-registration when connecting to remote MCP servers and accepting
+ connections from MCP clients.
+---
+
+Client ID Metadata Documents (CIMD) are defined in
+[draft-ietf-oauth-client-id-metadata-document](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-06.txt).
+Instead of registering an OAuth application in advance to obtain an opaque
+`client_id` string, a client presents an HTTPS URL as its `client_id`. The
+authorization server fetches the document at that URL and uses it to resolve the
+client's metadata on the fly. The
+[MCP authorization specification (2025-11-25)](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)
+designates CIMD as the preferred client registration mechanism for clients and
+servers that do not have a prior relationship.
+
+CIMD eliminates the manual app-registration step on both sides of an OAuth
+interaction. A client that hosts a stable metadata document at a public HTTPS
+URL can interact with any authorization server that supports CIMD without
+pre-registration, and an authorization server that supports CIMD can accept new
+clients without requiring them to call a registration endpoint first.
+
+## How ToolHive uses CIMD
+
+ToolHive supports CIMD in two independent directions: as a client connecting to
+remote MCP servers, and as a server accepting connections from MCP clients such
+as VS Code or Claude Code.
+
+### ToolHive as a CIMD client
+
+When you run `thv run` against a remote MCP server that requires OAuth
+authentication, ToolHive discovers the server's authorization server and checks
+whether it advertises CIMD support via the
+`client_id_metadata_document_supported` field in its OAuth discovery metadata
+(RFC 8414). If it does, ToolHive presents
+`https://toolhive.dev/oauth/client-metadata.json` as its `client_id` instead of
+calling the Dynamic Client Registration (DCR) endpoint.
+
+ToolHive follows this priority order when authenticating to a remote server:
+
+1. **Stored credentials** — if a previous session left a cached refresh token or
+ DCR `client_id`, those are used directly.
+2. **CIMD** — if the remote authorization server advertises
+ `client_id_metadata_document_supported: true` and no credentials are stored,
+ ToolHive presents its CIMD URL.
+3. **DCR** — if the remote authorization server does not support CIMD, ToolHive
+ falls back to RFC 7591 Dynamic Client Registration.
+
+If the remote authorization server advertises CIMD support but rejects the CIMD
+`client_id` (for example, because the server is still rolling out support),
+ToolHive automatically retries using DCR.
+
+:::tip
+
+Linear's MCP server (`https://mcp.linear.app/mcp`) advertises
+`client_id_metadata_document_supported: true`. When you connect to it with
+`thv run`, ToolHive uses its CIMD URL automatically and no OAuth app
+pre-registration on the Linear side is required.
+
+:::
+
+### ToolHive as a CIMD server (embedded AS)
+
+The ToolHive embedded authorization server can accept HTTPS URLs as `client_id`
+values from MCP clients. When CIMD is enabled on the embedded AS, a client such
+as VS Code presents `https://vscode.dev/oauth/client-metadata.json` as its
+`client_id`. The embedded AS fetches that document, validates it, and uses the
+declared metadata (redirect URIs, grant types, scopes) to authorize the client
+without any prior DCR call.
+
+When CIMD is disabled (the default), the embedded AS only accepts `client_id`
+values that were issued through DCR. Enabling CIMD adds a second lookup path
+alongside DCR; existing DCR-registered clients continue to work.
+
+:::note
+
+The embedded authorization server and its CIMD support are available only for
+Kubernetes deployments using the ToolHive Operator. See
+[Enable CIMD on the embedded authorization server](../guides-k8s/cimd-embedded-as.mdx)
+for setup instructions and version requirements.
+
+:::
+
+## Two-layer architecture
+
+When the embedded AS is deployed and CIMD is enabled, the OAuth flow involves
+two independent legs. The client's CIMD identity is used only within the
+embedded AS. The embedded AS uses its own pre-configured identity when talking
+to the upstream identity provider.
+
+```mermaid
+sequenceDiagram
+ participant Client as VS Code
(CIMD client_id)
+ participant EmbeddedAS as ToolHive Embedded AS
+ participant IDP as Upstream IDP
(e.g. GitHub)
+
+ Note over Client,EmbeddedAS: Leg 1 — client to embedded AS (CIMD)
+ Client->>EmbeddedAS: Authorization request
client_id = https://vscode.dev/oauth/client-metadata.json
+ EmbeddedAS->>EmbeddedAS: Fetch and validate CIMD document
+ EmbeddedAS-->>Client: Redirect to upstream IDP
+
+ Note over EmbeddedAS,IDP: Leg 2 — embedded AS to upstream IDP (pre-configured identity)
+ Client->>IDP: Authenticate (via redirect)
+ IDP-->>EmbeddedAS: Authorization code
+ EmbeddedAS->>IDP: Exchange code for token
(AS's own client_id + secret)
+ IDP-->>EmbeddedAS: Upstream access token
+ EmbeddedAS-->>Client: Issue ToolHive JWT
+```
+
+The upstream IDP never sees the client's CIMD URL. The embedded AS uses its own
+pre-configured `client_id` and `client_secret` (or DCR-obtained credentials)
+when talking to the upstream IDP. The two OAuth legs are completely independent.
+
+## Security model
+
+### Document validation
+
+When the embedded AS receives a CIMD `client_id`, it fetches the document and
+enforces the following rules:
+
+- The URL must use `https`. Loopback `http://localhost` is accepted only in
+ development and test environments.
+- The `client_id` field inside the fetched document must exactly match the URL
+ used to fetch it. No normalization is applied — allowing normalization would
+ permit subtle spoofing attacks where a document at URL A claims the identity
+ of URL B.
+- The document must declare at least one `redirect_uris` entry, and all redirect
+ URIs must pass strict validation (RFC 8252).
+- Symmetric shared-secret `token_endpoint_auth_method` values
+ (`client_secret_post`, `client_secret_basic`, `client_secret_jwt`) are
+ forbidden. CIMD clients have no pre-shared secret by definition.
+- `grant_types` must be a subset of `[authorization_code, refresh_token]` and
+ must include `authorization_code`.
+- `response_types` must be a subset of `[code]`.
+- If the embedded AS has a restricted `scopes_supported` list, the document's
+ declared scopes must be a subset of it.
+
+### SSRF protection
+
+The HTTP client used to fetch CIMD documents includes SSRF protection:
+
+- Keep-alive connections are disabled so the IP check runs on every request.
+- DNS resolution is performed first and the resulting IP is checked against
+ private and special-use ranges (RFC 1918). Loopback addresses are allowed for
+ development use only.
+- Redirects are not followed. Per the CIMD specification, the authorization
+ server must not automatically follow redirects when retrieving a client
+ metadata document.
+- The fetch timeout is five seconds and the response body is capped at 10 KB.
+
+### Caching
+
+To avoid fetching the CIMD document on every request, the embedded AS caches
+documents in an LRU cache. Two parameters control the cache:
+
+| Parameter | Default | Description |
+| ------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
+| `cacheMaxSize` | `256` | Maximum number of documents held in the cache. When full, the least-recently-used entry is evicted. |
+| `cacheFallbackTtl` | `5m` | Fixed TTL applied to every cached entry. Cache-Control header parsing is not yet implemented; all entries use this value regardless. |
+
+If a fetch fails (network error, invalid document, policy violation), the
+request is rejected and the failure is not cached.
+
+## Limitations
+
+- **Cache-Control header parsing is not yet implemented.** All cached CIMD
+ documents use the `cacheFallbackTtl` value regardless of the `Cache-Control`
+ header in the response. This means a document that declares a short max-age is
+ still cached for `cacheFallbackTtl`.
+- **Kubernetes only for the embedded AS.** CIMD support on the embedded AS is
+ configured via the `MCPExternalAuthConfig` CRD and is not available for
+ standalone `thv run` deployments.
+
+## Related information
+
+- [Enable CIMD on the embedded authorization server](../guides-k8s/cimd-embedded-as.mdx)
+ — step-by-step Kubernetes setup
+- [Embedded authorization server](./embedded-auth-server.mdx) — conceptual
+ overview of the embedded AS, including DCR and token forwarding
+- [Authentication and authorization](./auth-framework.mdx) — the overall
+ authentication framework
diff --git a/docs/toolhive/concepts/embedded-auth-server.mdx b/docs/toolhive/concepts/embedded-auth-server.mdx
index a7da9740..6861e32c 100644
--- a/docs/toolhive/concepts/embedded-auth-server.mdx
+++ b/docs/toolhive/concepts/embedded-auth-server.mdx
@@ -166,6 +166,28 @@ DCR-registered client gains the ability to request these scopes, including
public clients like Claude Code, Cursor, and VS Code, so privileged scopes do
not belong in the baseline.
+## CIMD support
+
+The embedded authorization server can accept HTTPS URLs as `client_id` values
+from MCP clients using the Client ID Metadata Document (CIMD) protocol
+([draft-ietf-oauth-client-id-metadata-document](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-06.txt)).
+When CIMD is enabled, a client such as VS Code presents
+`https://vscode.dev/oauth/client-metadata.json` as its `client_id`. The embedded
+AS fetches and validates that document instead of requiring the client to call
+`/oauth/register` first.
+
+CIMD is disabled by default. When enabled, it adds a second client-lookup path
+alongside DCR; DCR-registered clients continue to work without changes. You
+configure CIMD through the `cimd` block on the `MCPExternalAuthConfig` resource.
+
+For the complete explanation of how CIMD works, the two-layer architecture that
+separates client identity from upstream IDP identity, and the security model for
+document fetching and caching, see
+[Client ID Metadata Documents (CIMD)](./cimd.mdx).
+
+For setup instructions, see
+[Enable CIMD on the embedded authorization server](../guides-k8s/cimd-embedded-as.mdx).
+
## Session storage
By default, session storage is in-memory. Upstream tokens are lost when pods
diff --git a/docs/toolhive/guides-k8s/cimd-embedded-as.mdx b/docs/toolhive/guides-k8s/cimd-embedded-as.mdx
new file mode 100644
index 00000000..53c2f217
--- /dev/null
+++ b/docs/toolhive/guides-k8s/cimd-embedded-as.mdx
@@ -0,0 +1,227 @@
+---
+title: Enable CIMD on the embedded authorization server
+description:
+ How to configure Client ID Metadata Document (CIMD) support on the ToolHive
+ embedded authorization server so MCP clients such as VS Code can authenticate
+ without prior Dynamic Client Registration.
+---
+
+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)](../concepts/cimd.mdx).
+
+:::
+
+:::info[Availability]
+
+The `cimd` field on `MCPExternalAuthConfig` is available from the version of the
+ToolHive Operator that includes this feature. Check the
+[ToolHive release notes](https://github.com/stacklok/toolhive/releases) 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](./auth-k8s.mdx#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`.
+
+```yaml
+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:
+
+```bash
+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:
+
+| Field | Default | Description |
+| ------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------- |
+| `cacheMaxSize` | `256` | Maximum number of CIMD documents held in the LRU cache. When the cache is full, the least-recently-used entry is evicted. |
+| `cacheFallbackTtl` | `5m` | Fixed 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:
+
+```yaml
+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:
+
+```bash
+# Replace with the name of your MCPServer resource
+kubectl port-forward -n toolhive-system svc/mcp--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:
+
+```json
+"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:
+
+```yaml
+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.
+
+:::
+
+## Next steps
+
+- [Client ID Metadata Documents (CIMD)](../concepts/cimd.mdx) — understand the
+ two-layer architecture and full security model
+- [Configure Redis session storage](./redis-session-storage.mdx) — for
+ production deployments where token persistence across pod restarts is required
+- [Set up rate limiting](./rate-limiting.mdx) — protect the embedded AS
+ authorization endpoint from abuse
+
+## Related information
+
+- [Client ID Metadata Documents (CIMD)](../concepts/cimd.mdx) — conceptual
+ overview, two-layer architecture, and security model
+- [Embedded authorization server](../concepts/embedded-auth-server.mdx) — full
+ description of the embedded AS, DCR, token forwarding, and session storage
+- [Set up embedded authorization server authentication](./auth-k8s.mdx#set-up-embedded-authorization-server-authentication)
+ — initial embedded AS setup before adding CIMD
+
+## 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:
+ ```bash
+ 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 `response_types` that include a value 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.
diff --git a/sidebars.ts b/sidebars.ts
index bd77117e..895ea4fb 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -180,6 +180,7 @@ const sidebars: SidebarsConfig = {
'toolhive/guides-k8s/connect-clients',
'toolhive/guides-k8s/customize-tools',
'toolhive/guides-k8s/auth-k8s',
+ 'toolhive/guides-k8s/cimd-embedded-as',
'toolhive/guides-k8s/redis-session-storage',
'toolhive/guides-k8s/rate-limiting',
'toolhive/guides-k8s/token-exchange-k8s',
@@ -288,6 +289,7 @@ const sidebars: SidebarsConfig = {
'toolhive/concepts/cedar-policies',
'toolhive/concepts/backend-auth',
'toolhive/concepts/embedded-auth-server',
+ 'toolhive/concepts/cimd',
'toolhive/concepts/vmcp',
'toolhive/concepts/skills',
],