Introduction
CIMD (Client ID Metadata Documents) enables MCP clients to use HTTPS URLs as client identifiers. Instead of pre-registering clients with the authorization server, clients can host their metadata at a URL, and that URL becomes theirclient_id. This implements the draft-ietf-oauth-client-id-metadata-document-00 specification.
This approach enables self-service client registration where clients can publish their own metadata without coordinating with the authorization server, verifiable client identity where the server can fetch and validate metadata from the URL, and decentralized trust where metadata is hosted by the client rather than stored centrally.
How It Works
Flow:- Client sends OAuth request with
client_idset to an HTTPS URL (e.g.,https://example.com/oauth/client-metadata.json) - Authorization server detects this is a CIMD URL and fetches the document
- Client’s server returns a JSON metadata document
- Authorization server validates the document (schema, client_id match, redirect_uri)
- OAuth flow continues with the verified client metadata
Key Insight: CIMD identifies the client application, not the user. The user’s identity still comes from the OAuth token. The access token is bound to both: issued for a specific user via a specific client.
Real-World Example: AI Agent Authorization
This example shows the complete flow when an AI Agent wants to use protected MCP tools.The Scenario
- AI Agent: Claude wants to create a GitHub issue
- MCP Client: “Claude MCP Client” publishes metadata at
https://mcpclient.ai/oauth/metadata.json - User: John (
john@example.com) must approve the access - Protected Tool:
create_issuein thegithub-app
Complete Authorization Flow
Three-Layer Identity Model
FrontMCP uses a three-layer identity model to separate concerns:| Layer | What It Identifies | How It’s Represented | Purpose |
|---|---|---|---|
| CLIENT | The MCP client application | CIMD URL (e.g., https://mcpclient.ai/oauth/metadata.json) | Verifies which app is requesting access |
| USER | The person authorizing | OAuth token claims (sub, name, email) | Identifies who is granting permissions |
| PERMISSIONS | What can be accessed | OAuth scopes + authorized apps/tools | Controls what actions are allowed |
CIMD vs Standard OAuth
CIMD changes how clients are registered, not the OAuth flow itself. You still get an access token with user identity.| Aspect | Standard OAuth | CIMD |
|---|---|---|
| Client Registration | Pre-register with server, get client_id | Self-host metadata at a URL |
| Client ID Format | Opaque string (client_abc123) | HTTPS URL (https://mcpclient.ai/oauth/metadata.json) |
| Metadata Storage | On authorization server’s database | Hosted by the client |
| Adding New Clients | Requires server admin action | Client just publishes JSON file |
| Token Contains | User identity + scopes | Same! User identity + scopes |
| Who Approves | End user via consent screen | Same! End user via consent screen |
What Stays the Same
The core OAuth flow is identical:- User authenticates
- User consents to permissions
- Token is issued with user identity and scopes
- Token is used to call protected tools
What Changes
Only the client identification step:- Standard: Server looks up
client_idin its database - CIMD: Server fetches metadata from the
client_idURL
Configuration
CIMD is configured through theauth.cimd option when creating a FrontMCP server. The CIMD service is automatically created and registered by the framework when orchestrated mode is enabled.
Basic Configuration
- Decorator Pattern
- Functional Pattern
Full Configuration Options
- Complete Example
- Minimal Example
CIMD is enabled by default when using orchestrated auth mode. The framework automatically creates and registers the
CimdService internally, so you don’t need to instantiate it yourself.Security Settings
| Option | Default | Description |
|---|---|---|
blockPrivateIPs | true | Block private/internal IP addresses (SSRF protection) |
allowedDomains | undefined | If set, only these domains can host CIMD documents |
blockedDomains | undefined | These domains cannot host CIMD documents |
warnOnLocalhostRedirects | true | Log warning for localhost-only redirect URIs |
Cache Settings
| Option | Default | Description |
|---|---|---|
defaultTtlMs | 3600000 (1 hour) | Default cache TTL when no headers present |
maxTtlMs | 86400000 (24 hours) | Maximum TTL even if server suggests longer |
minTtlMs | 60000 (1 minute) | Minimum TTL even if server suggests shorter |
Network Settings
| Option | Default | Description |
|---|---|---|
timeoutMs | 5000 (5s) | Request timeout |
maxResponseSizeBytes | 65536 (64KB) | Maximum response body size |
Client Metadata Document Format
The metadata document is a JSON file hosted at the client’s URL:Required Fields
| Field | Type | Description |
|---|---|---|
client_id | string | MUST match the URL from which this document was fetched |
client_name | string | Human-readable name of the client |
redirect_uris | string[] | Array of allowed redirect URIs (at least one required) |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
token_endpoint_auth_method | string | "none" | Auth method: none, client_secret_basic, client_secret_post, private_key_jwt |
grant_types | string[] | ["authorization_code"] | OAuth grant types |
response_types | string[] | ["code"] | OAuth response types |
client_uri | string | - | URL of the client’s home page |
logo_uri | string | - | URL of the client’s logo image |
jwks_uri | string | - | URL of the client’s JWKS (for private_key_jwt) |
jwks | object | - | Inline JWKS { keys: [...] } |
tos_uri | string | - | URL of the terms of service |
policy_uri | string | - | URL of the privacy policy |
scope | string | - | Requested OAuth scopes |
contacts | string[] | - | Array of contact email addresses |
software_id | string | - | Unique identifier for the client software |
software_version | string | - | Version of the client software |
Security Features
SSRF Protection
Whensecurity.blockPrivateIPs is enabled (default), the following addresses are blocked:
IPv4 Blocked Ranges:
| Range | Description |
|---|---|
127.0.0.0/8 | Loopback addresses |
10.0.0.0/8 | Private Class A |
172.16.0.0/12 | Private Class B (172.16.x.x - 172.31.x.x) |
192.168.0.0/16 | Private Class C |
169.254.0.0/16 | Link-local addresses |
0.0.0.0/8 | Current network |
224.0.0.0/4 | Multicast (224.x.x.x - 239.x.x.x) |
255.255.255.255 | Broadcast |
| Range | Description |
|---|---|
::1 | Loopback |
:: | Unspecified |
fe80::/10 | Link-local |
fc00::/7 | Unique local (ULA) |
::ffff:x.x.x.x | IPv4-mapped (checked against IPv4 rules) |
localhostlocalhost.localdomain*.localhost
Domain Allow/Block Lists
- Exact match:
example.commatches onlyexample.com - Subdomain match:
example.comalso matchessub.example.com - Wildcard:
*.example.commatchessub.example.comandexample.com
HTTP Caching
The CIMD service respects HTTP caching headers to minimize network requests.Cache-Control Support
Age header is subtracted from max-age if present.
Conditional Requests
When a cached entry has anETag or Last-Modified header, the service sends conditional requests:
304 Not Modified response refreshes the cache TTL without re-downloading the document.
TTL Bounds
The computed TTL is always clamped to the configured bounds:Error Handling
Error Classes
| Error | HTTP Status | Description |
|---|---|---|
InvalidClientIdUrlError | 400 | Invalid CIMD URL format (HTTP, missing path, etc.) |
CimdFetchError | 502 | Failed to fetch document (network error, HTTP error) |
CimdValidationError | 400 | Document failed schema validation |
CimdClientIdMismatchError | 400 | client_id in document doesn’t match URL |
CimdSecurityError | 403 | URL blocked by security policy |
RedirectUriMismatchError | 400 | redirect_uri not registered for client |
CimdResponseTooLargeError | 502 | Response exceeds size limit |
CimdDisabledError | 400 | CIMD disabled but CIMD client_id used |
Understanding Error Responses
When CIMD validation fails during the OAuth flow, the authorization endpoint returns an error response. Here’s how to interpret and handle these errors on the client side:Troubleshooting
CIMD client_id rejected
CIMD client_id rejected
Symptoms: Authorization fails with “Invalid client_id URL” error.Common causes:
- Using HTTP instead of HTTPS (CIMD requires HTTPS)
- URL missing a path component
- URL blocked by security policy (private IP, blocked domain)
- Ensure your client_id URL uses HTTPS
- Verify the URL has a path (e.g.,
/oauth/client.json) - Check if your domain is blocked or not in the allow list
- Verify the URL is publicly accessible
Document fetch failed
Document fetch failed
Symptoms: Server cannot retrieve the client metadata document.Common causes:
- Network timeout (document host too slow)
- HTTP error (404, 500, etc.)
- Response too large (exceeds 64KB default)
- SSL/TLS certificate issues
- Verify the metadata URL is accessible via
curl - Check that the document is valid JSON
- Ensure the document is under 64KB
- Verify SSL certificate is valid and trusted
Redirect URI mismatch
Redirect URI mismatch
Symptoms: Authorization fails with “redirect_uri not registered” error.Common causes:
- The
redirect_uriparameter doesn’t exactly match any URI in the metadata document - Trailing slash differences
- Port number differences
- Ensure the exact
redirect_uriis listed in theredirect_urisarray - Check for trailing slash consistency
- Include both development (localhost) and production URIs in the document
Next Steps
Local OAuth
Learn about local OAuth authentication for standalone MCP servers
Remote OAuth
Configure remote OAuth providers for enterprise authentication
Progressive Authorization
Enable incremental authorization for tool-by-tool access control
Production Checklist
Security best practices for deploying CIMD in production