Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
35fd196
Add CIMD module with document model, fetcher, and trust policy
jlowin Jan 13, 2026
8fd852b
Integrate CIMD support into OAuthProxy
jlowin Jan 13, 2026
f0a0f20
Enhance consent screen with CIMD domain verification badges
jlowin Jan 13, 2026
49a1216
Add CIMD CLI commands for document creation and validation
jlowin Jan 13, 2026
8ce6bae
Add unit tests for CIMD functionality
jlowin Jan 13, 2026
f1f493e
Fix CIMD implementation issues and test failures
jlowin Jan 13, 2026
f5f602d
Refactor OAuth client API to eliminate redundant URL specification
jlowin Jan 13, 2026
46b2dc7
Simplify CIMD implementation and add comprehensive tests
jlowin Jan 14, 2026
01b213d
Add comprehensive CIMD documentation
jlowin Jan 14, 2026
6718313
Harden CIMD SSRF protection and remove trust policy system
jlowin Jan 14, 2026
af22764
ruff
jlowin Jan 16, 2026
b979822
Fix code quality issues in CIMD implementation
jlowin Jan 16, 2026
192aa6d
Harden CIMD implementation with additional validation
jlowin Jan 16, 2026
af1a099
Fix CIMD security implementation bugs
jlowin Jan 17, 2026
2094c88
Mark CIMD support as beta feature
jlowin Jan 17, 2026
7f850a5
WIP: CIMD security hardening and redirect validation
jlowin Feb 5, 2026
ab38680
Fix rebase conflicts with oauth_proxy package restructuring
jlowin Feb 5, 2026
170c410
Revert OAuth client API refactor to match main
jlowin Feb 5, 2026
6a73423
Integrate CIMD support into OAuth proxy
jlowin Feb 5, 2026
c292694
Add CIMD client support, documentation, and comprehensive tests
jlowin Feb 6, 2026
8c0c57d
Add CIMD to v3 feature tracking
jlowin Feb 6, 2026
d807448
Address review feedback: JWKS caching, SSRF scope, redirect validation
jlowin Feb 6, 2026
52b716f
Collapse CIMDFetcher onto ssrf_safe_fetch, remove redundant SSRF tests
jlowin Feb 6, 2026
22f86b1
Revert loq cimd.py limit to 683
jlowin Feb 6, 2026
b9b9bf7
Restore loq.toml to main defaults
jlowin Feb 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions docs/clients/auth/cimd.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
title: CIMD Authentication
sidebarTitle: CIMD
description: Use Client ID Metadata Documents for verifiable, domain-based client identity.
icon: id-badge
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="3.0.0" />

<Tip>
CIMD authentication is only relevant for HTTP-based transports and requires a server that advertises CIMD support.
</Tip>

With standard OAuth, your client registers dynamically with every server it connects to, receiving a fresh `client_id` each time. This works, but the server has no way to verify *who* your client actually is — any client can claim any name during registration.

CIMD (Client ID Metadata Documents) flips this around. You host a small JSON document at an HTTPS URL you control, and that URL becomes your `client_id`. When your client connects to a server, the server fetches your metadata document and can verify your identity through your domain ownership. Users see a verified domain badge in the consent screen instead of an unverified client name.

## Client Usage

Pass your CIMD document URL to the `client_metadata_url` parameter of `OAuth`:

```python
from fastmcp import Client
from fastmcp.client.auth import OAuth

async with Client(
"https://mcp-server.example.com/mcp",
auth=OAuth(
client_metadata_url="https://myapp.example.com/oauth/client.json",
),
) as client:
await client.ping()
```

When the server supports CIMD, the client uses your metadata URL as its `client_id` instead of performing Dynamic Client Registration. The server fetches your document, validates it, and proceeds with the standard OAuth authorization flow.

<Note>
You don't need to pass `mcp_url` when using `OAuth` with `Client(auth=...)` — the transport provides the server URL automatically.
</Note>

## Creating a CIMD Document

A CIMD document is a JSON file that describes your client. The most important field is `client_id`, which must exactly match the URL where you host the document.

Use the FastMCP CLI to generate one:

```bash
fastmcp auth cimd create \
--name "My Application" \
--redirect-uri "http://localhost:*/callback" \
--client-id "https://myapp.example.com/oauth/client.json"
```

This produces:

```json
{
"client_id": "https://myapp.example.com/oauth/client.json",
"client_name": "My Application",
"redirect_uris": ["http://localhost:*/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code"],
"response_types": ["code"]
}
```

If you omit `--client-id`, the CLI generates a placeholder value and reminds you to update it before hosting.

### CLI Options

The `create` command accepts these flags:

| Flag | Description |
|------|-------------|
| `--name` | Human-readable client name (required) |
| `--redirect-uri`, `-r` | Allowed redirect URIs — can be specified multiple times (required) |
| `--client-id` | The URL where you'll host this document (sets `client_id` directly) |
| `--output`, `-o` | Write to a file instead of stdout |
| `--scope` | Space-separated list of scopes the client may request |
| `--client-uri` | URL of the client's home page |
| `--logo-uri` | URL of the client's logo image |
| `--no-pretty` | Output compact JSON |

### Redirect URIs

The `redirect_uris` field supports wildcard port matching for localhost. The pattern `http://localhost:*/callback` matches any port, which is useful for development clients that bind to random available ports (which is what FastMCP's `OAuth` helper does by default).

## Hosting Requirements

CIMD documents must be hosted at a publicly accessible HTTPS URL with a non-root path:

- **HTTPS required** — HTTP URLs are rejected for security
- **Non-root path** — The URL must have a path component (e.g., `/oauth/client.json`, not just `/`)
- **Public accessibility** — The server must be able to fetch the document over the internet
- **Matching `client_id`** — The `client_id` field in the document must exactly match the hosting URL

Common hosting options include static file hosting services like GitHub Pages, Cloudflare Pages, Vercel, or S3 — anywhere you can serve a JSON file over HTTPS.

## Validating Your Document

Before deploying, verify your hosted document passes validation:

```bash
fastmcp auth cimd validate https://myapp.example.com/oauth/client.json
```

The validator fetches the document and checks that:
- The URL is valid (HTTPS, non-root path)
- The document is well-formed JSON conforming to the CIMD schema
- The `client_id` in the document matches the URL it was fetched from

## How It Works

When your client connects to a CIMD-enabled server, the flow works like this:

<Steps>
<Step title="Client Presents Metadata URL">
Your client sends its `client_metadata_url` as the `client_id` in the OAuth authorization request.
</Step>
<Step title="Server Recognizes CIMD URL">
The server sees that the `client_id` is an HTTPS URL with a path — the signature of a CIMD client — and skips Dynamic Client Registration.
</Step>
<Step title="Server Fetches and Validates">
The server fetches your JSON document from the URL, validates that `client_id` matches the URL, and extracts your client metadata (name, redirect URIs, scopes).
</Step>
<Step title="Authorization Proceeds">
The standard OAuth flow continues: browser opens for user consent, authorization code exchange, token issuance. The consent screen shows your verified domain.
</Step>
</Steps>

The server caches your CIMD document according to HTTP cache headers, so subsequent requests don't require re-fetching.

## Server Configuration

CIMD is a server-side feature that your MCP server must support. FastMCP's OAuth proxy providers (GitHub, Google, Auth0, etc.) support CIMD by default. See the [OAuth Proxy CIMD documentation](/servers/auth/oauth-proxy#cimd-support) for server-side configuration, including private key JWT authentication and security details.
39 changes: 31 additions & 8 deletions docs/clients/auth/oauth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,25 @@ To fully configure the OAuth flow, use the `OAuth` helper and pass it to the `au
from fastmcp import Client
from fastmcp.client.auth import OAuth

oauth = OAuth(mcp_url="https://your-server.fastmcp.app/mcp")
oauth = OAuth(scopes=["user"])

async with Client("https://your-server.fastmcp.app/mcp", auth=oauth) as client:
await client.ping()
```

<Note>
You don't need to pass `mcp_url` when using `OAuth` with `Client(auth=...)` — the transport provides the server URL automatically.
</Note>

#### `OAuth` Parameters

- **`mcp_url`** (`str`): The full URL of the target MCP server endpoint. Used to discover OAuth server metadata
- **`scopes`** (`str | list[str]`, optional): OAuth scopes to request. Can be space-separated string or list of strings
- **`client_name`** (`str`, optional): Client name for dynamic registration. Defaults to `"FastMCP Client"`
- **`client_metadata_url`** (`str`, optional): URL-based client identity (CIMD). See [CIMD Authentication](/clients/auth/cimd) for details
- **`token_storage`** (`AsyncKeyValue`, optional): Storage backend for persisting OAuth tokens. Defaults to in-memory storage (tokens lost on restart). See [Token Storage](#token-storage) for encrypted storage options
- **`additional_client_metadata`** (`dict[str, Any]`, optional): Extra metadata for client registration
- **`callback_port`** (`int`, optional): Fixed port for OAuth callback server. If not specified, uses a random available port
- **`httpx_client_factory`** (`McpHttpClientFactory`, optional): Factory for creating httpx clients
Comment on lines 56 to +62
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for OAuth class definition and factory type
rg -n "class OAuth" -g '*.py' -A 20 | head -100

Repository: jlowin/fastmcp

Length of output: 7631


🏁 Script executed:

#!/bin/bash
# Find HttpClientFactory type definition
rg -n "HttpClientFactory|McpHttpClientFactory" -g '*.py' -B 2 -A 2

Repository: jlowin/fastmcp

Length of output: 2612


🏁 Script executed:

#!/bin/bash
# Check the actual oauth.mdx file to see the example and parameter list
cat -n docs/clients/auth/oauth.mdx | sed -n '40,70p'

Repository: jlowin/fastmcp

Length of output: 2011


🏁 Script executed:

#!/bin/bash
# Look for the complete OAuth.__init__ signature
ast-grep --pattern 'class OAuth { $$$__init__($$$) { $$$ }$$$}'

Repository: jlowin/fastmcp

Length of output: 40


🏁 Script executed:

#!/bin/bash
# More targeted search for OAuth __init__ method
rg -n "def __init__" -B 5 -A 15 -g '*.py' | grep -A 15 -B 5 "class OAuth\|OAuth.*__init__"

Repository: jlowin/fastmcp

Length of output: 40


Update OAuth parameter documentation to match the actual constructor signature.

The parameter list is missing mcp_url (a required first parameter shown in the example), and incorrectly includes client_metadata_url which does not exist in the actual OAuth.__init__ signature. The factory type McpHttpClientFactory is correctly cased. Update lines 52–58 to document mcp_url as the first required parameter and remove client_metadata_url from the list.

🧰 Tools
🪛 LanguageTool

[style] ~52-~52: To form a complete sentence, be sure to include a subject or ‘there’.
Context: ...]`, optional): OAuth scopes to request. Can be space-separated string or list of st...

(MISSING_IT_THERE)



## OAuth Flow
Expand All @@ -68,8 +73,8 @@ The client first checks the configured `token_storage` backend for existing, val
<Step title="OAuth Server Discovery">
If no valid tokens exist, the client attempts to discover the OAuth server's endpoints using a well-known URI (e.g., `/.well-known/oauth-authorization-server`) based on the `mcp_url`.
</Step>
<Step title="Dynamic Client Registration">
If the OAuth server supports it and the client isn't already registered (or credentials aren't cached), the client performs dynamic client registration according to RFC 7591.
<Step title="Client Registration">
If the OAuth server supports it and the client isn't already registered (or credentials aren't cached), the client performs dynamic client registration according to RFC 7591. Alternatively, if a `client_metadata_url` is configured and the server supports CIMD, the client uses its metadata URL as its identity instead of registering.
</Step>
<Step title="Local Callback Server">
A temporary local HTTP server is started on an available port (or the port specified via `callback_port`). This server's address (e.g., `http://127.0.0.1:<port>/callback`) acts as the `redirect_uri` for the OAuth flow.
Expand Down Expand Up @@ -115,10 +120,7 @@ encrypted_storage = FernetEncryptionWrapper(
fernet=Fernet(os.environ["OAUTH_STORAGE_ENCRYPTION_KEY"])
)

oauth = OAuth(
mcp_url="https://your-server.fastmcp.app/mcp",
token_storage=encrypted_storage
)
oauth = OAuth(token_storage=encrypted_storage)

async with Client("https://your-server.fastmcp.app/mcp", auth=oauth) as client:
await client.ping()
Expand All @@ -129,3 +131,24 @@ You can use any `AsyncKeyValue`-compatible backend from the [key-value library](
<Note>
When selecting a storage backend, review the [py-key-value documentation](https://github.com/strawgate/py-key-value) to understand the maturity level and limitations of your chosen backend. Some backends may be in preview or have constraints that affect production suitability.
</Note>

## CIMD Authentication

<VersionBadge version="3.0.0" />

Client ID Metadata Documents (CIMD) provide an alternative to Dynamic Client Registration. Instead of registering with each server, your client hosts a static JSON document at an HTTPS URL. That URL becomes your client's identity, and servers can verify who you are through your domain ownership.

```python
from fastmcp import Client
from fastmcp.client.auth import OAuth

async with Client(
"https://mcp-server.example.com/mcp",
auth=OAuth(
client_metadata_url="https://myapp.example.com/oauth/client.json",
),
) as client:
await client.ping()
```

See the [CIMD Authentication](/clients/auth/cimd) page for complete documentation on creating, hosting, and validating CIMD documents.
47 changes: 47 additions & 0 deletions docs/development/v3-notes/v3-features.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,53 @@ fastmcp install stdio server.py

The command automatically detects the project directory and generates the appropriate `uv run` invocation, making it easy to integrate FastMCP servers with MCP clients.

### CIMD (Client ID Metadata Documents)

CIMD provides an alternative to Dynamic Client Registration for OAuth-authenticated MCP servers. Instead of registering with each server dynamically, clients host a static JSON document at an HTTPS URL. That URL becomes the client's `client_id`, and servers verify identity through domain ownership.

**Client usage:**

```python
from fastmcp import Client
from fastmcp.client.auth import OAuth

async with Client(
"https://mcp-server.example.com/mcp",
auth=OAuth(
client_metadata_url="https://myapp.example.com/oauth/client.json",
),
) as client:
await client.ping()
```

The `OAuth` helper now supports deferred binding — `mcp_url` is optional when using `OAuth` with `Client(auth=...)`, since the transport provides the server URL automatically.

**CLI tools for document management:**

```bash
# Generate a CIMD document
fastmcp auth cimd create --name "My App" \
--redirect-uri "http://localhost:*/callback" \
--client-id "https://myapp.example.com/oauth/client.json" \
--output client.json

# Validate a hosted document
fastmcp auth cimd validate https://myapp.example.com/oauth/client.json
```

**Server-side support:**

CIMD is enabled by default on `OAuthProxy` and its provider subclasses (GitHub, Google, etc.). The server-side implementation includes SSRF-hardened document fetching with DNS pinning, dual redirect URI validation (both CIMD document patterns and proxy patterns must match), HTTP cache-aware revalidation, and `private_key_jwt` assertion validation for clients that need stronger authentication than public client auth.

Key details:
- CIMD URLs must be HTTPS with a non-root path
- `token_endpoint_auth_method` limited to `none` or `private_key_jwt` (no shared secrets)
- `redirect_uris` in CIMD documents support wildcard port patterns (`http://localhost:*/callback`)
- Servers fetch and cache documents with standard HTTP caching (ETag, Last-Modified, Cache-Control)
- CIMD is a protocol-level feature — any auth provider implementing the spec can support it

Documentation: [CIMD Authentication](/clients/auth/cimd), [OAuth Proxy CIMD config](/servers/auth/oauth-proxy#cimd-support)

### MCP Apps (SDK Compatibility)

Support for [MCP Apps](https://modelcontextprotocol.io/specification/2025-06-18/server/apps) — the spec extension that lets MCP servers deliver interactive UIs via sandboxed iframes. Extension negotiation, typed UI metadata on tools and resources, and the `ui://` resource scheme. No component DSL, renderer, or `FastMCPApp` class yet — those are future phases.
Expand Down
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
"icon": "key",
"pages": [
"clients/auth/oauth",
"clients/auth/cimd",
"clients/auth/bearer"
]
}
Expand Down
82 changes: 82 additions & 0 deletions docs/patterns/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ fastmcp --help
| `install` | Install a server in MCP client applications | **Supports:** Local files and fastmcp.json configs. **Deps:** Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable`. With fastmcp.json: Uses configured dependencies |
| `inspect` | Generate a JSON report about a FastMCP server | **Supports:** Local files and fastmcp.json configs. **Deps:** Uses your current environment; you are responsible for ensuring all dependencies are available |
| `project prepare` | Create a persistent uv project from fastmcp.json environment config | **Supports:** fastmcp.json configs only. **Deps:** Creates a uv project directory with all dependencies pre-installed for reuse with `--project` flag |
| `auth cimd` | Create and validate CIMD documents for OAuth authentication | N/A |
| `version` | Display version information | N/A |

## `fastmcp list`
Expand Down Expand Up @@ -750,6 +751,87 @@ The prepare command creates a uv project with:

This is useful when you want to separate environment setup from server execution, such as in deployment scenarios where dependencies are installed once and the server is run multiple times.

## `fastmcp auth`

<VersionBadge version="3.0.0" />

Authentication-related utilities and configuration commands.

### `fastmcp auth cimd create`

Generate a CIMD (Client ID Metadata Document) for hosting. This creates a JSON document that you can host at an HTTPS URL to use as your OAuth client identity.

```bash
fastmcp auth cimd create --name "My App" --redirect-uri "http://localhost:*/callback"
```

#### Options

| Option | Flag | Description |
| ------ | ---- | ----------- |
| Name | `--name` | **Required.** Human-readable name of the client application |
| Redirect URI | `--redirect-uri` | **Required.** Allowed redirect URIs (can specify multiple) |
| Client URI | `--client-uri` | URL of the client's home page |
| Logo URI | `--logo-uri` | URL of the client's logo image |
| Scope | `--scope` | Space-separated list of scopes the client may request |
| Output | `--output`, `-o` | Output file path (default: stdout) |
| Pretty | `--pretty` | Pretty-print JSON output (default: true) |

#### Example

```bash
# Generate document to stdout
fastmcp auth cimd create \
--name "My Production App" \
--redirect-uri "http://localhost:*/callback" \
--redirect-uri "https://myapp.example.com/callback" \
--client-uri "https://myapp.example.com" \
--scope "read write"

# Save to file
fastmcp auth cimd create \
--name "My App" \
--redirect-uri "http://localhost:*/callback" \
--output client.json
```

The generated document includes a placeholder `client_id` that you must update to match the URL where you'll host the document before deploying.

### `fastmcp auth cimd validate`

Validate a hosted CIMD document by fetching it from its URL and checking that it conforms to the CIMD specification.

```bash
fastmcp auth cimd validate https://myapp.example.com/oauth/client.json
```

#### Options

| Option | Flag | Description |
| ------ | ---- | ----------- |
| Timeout | `--timeout`, `-t` | HTTP request timeout in seconds (default: 10) |

The validator checks:

- The URL is a valid CIMD URL (HTTPS with non-root path)
- The document is valid JSON and conforms to the CIMD schema
- The `client_id` field in the document matches the URL
- No shared-secret authentication methods are used

On success, it displays the document details:

```
→ Fetching https://myapp.example.com/oauth/client.json...
✓ Valid CIMD document

Document details:
client_id: https://myapp.example.com/oauth/client.json
client_name: My App
token_endpoint_auth_method: none
redirect_uris:
• http://localhost:*/callback
```

## `fastmcp version`

Display version information about FastMCP and related components.
Expand Down
Loading