-
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
[Feat] MCP Oauth2 Fixes - Add support for MCP M2M Oauth2 support #20788
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+603
−45
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
c1636c6
add has_client_credentials
ishaan-jaff 9b3fd6a
MCPOAuth2TokenCache
ishaan-jaff b7ea713
init MCP Oauth2 constants
ishaan-jaff 3c25cda
MCPOAuth2TokenCache
ishaan-jaff 2579169
resolve_mcp_auth
ishaan-jaff 2ac13de
test fixes
ishaan-jaff 1d8de53
docs fix
ishaan-jaff 07447fc
address greptile review: min TTL, env-configurable constants, tests, …
ishaan-jaff 08a6197
fix mypy lint
ishaan-jaff 4f12a0f
fix oauth2
ishaan-jaff 3d8207f
remove old files
ishaan-jaff d12ec23
docs fix
ishaan-jaff 3a0b725
address greptile comments
ishaan-jaff e1cd2ea
fix: atomic lock creation + validate JSON response shape
ishaan-jaff 9b8cdc3
fix: replace asserts with proper guards, wrap HTTP errors with context
ishaan-jaff File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| import Tabs from '@theme/Tabs'; | ||
| import TabItem from '@theme/TabItem'; | ||
|
|
||
| # MCP OAuth | ||
|
|
||
| LiteLLM supports two OAuth 2.0 flows for MCP servers: | ||
|
|
||
| | Flow | Use Case | How It Works | | ||
| |------|----------|--------------| | ||
| | **Interactive (PKCE)** | User-facing apps (Claude Code, Cursor) | Browser-based consent, per-user tokens | | ||
| | **Machine-to-Machine (M2M)** | Backend services, CI/CD, automated agents | `client_credentials` grant, proxy-managed tokens | | ||
|
|
||
| ## Interactive OAuth (PKCE) | ||
|
|
||
| For user-facing MCP clients (Claude Code, Cursor), LiteLLM supports the full OAuth 2.0 authorization code flow with PKCE. | ||
|
|
||
| ### Setup | ||
|
|
||
| ```yaml title="config.yaml" showLineNumbers | ||
| mcp_servers: | ||
| github_mcp: | ||
| url: "https://api.githubcopilot.com/mcp" | ||
| auth_type: oauth2 | ||
| client_id: os.environ/GITHUB_OAUTH_CLIENT_ID | ||
| client_secret: os.environ/GITHUB_OAUTH_CLIENT_SECRET | ||
| ``` | ||
|
|
||
| [**See Claude Code Tutorial**](./tutorials/claude_responses_api#connecting-mcp-servers) | ||
|
|
||
| ### How It Works | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant Browser as User-Agent (Browser) | ||
| participant Client as Client | ||
| participant LiteLLM as LiteLLM Proxy | ||
| participant MCP as MCP Server (Resource Server) | ||
| participant Auth as Authorization Server | ||
|
|
||
| Note over Client,LiteLLM: Step 1 – Resource discovery | ||
| Client->>LiteLLM: GET /.well-known/oauth-protected-resource/{mcp_server_name}/mcp | ||
| LiteLLM->>Client: Return resource metadata | ||
|
|
||
| Note over Client,LiteLLM: Step 2 – Authorization server discovery | ||
| Client->>LiteLLM: GET /.well-known/oauth-authorization-server/{mcp_server_name} | ||
| LiteLLM->>Client: Return authorization server metadata | ||
|
|
||
| Note over Client,Auth: Step 3 – Dynamic client registration | ||
| Client->>LiteLLM: POST /{mcp_server_name}/register | ||
| LiteLLM->>Auth: Forward registration request | ||
| Auth->>LiteLLM: Issue client credentials | ||
| LiteLLM->>Client: Return client credentials | ||
|
|
||
| Note over Client,Browser: Step 4 – User authorization (PKCE) | ||
| Client->>Browser: Open authorization URL + code_challenge + resource | ||
| Browser->>Auth: Authorization request | ||
| Note over Auth: User authorizes | ||
| Auth->>Browser: Redirect with authorization code | ||
| Browser->>LiteLLM: Callback to LiteLLM with code | ||
| LiteLLM->>Browser: Redirect back with authorization code | ||
| Browser->>Client: Callback with authorization code | ||
|
|
||
| Note over Client,Auth: Step 5 – Token exchange | ||
| Client->>LiteLLM: Token request + code_verifier + resource | ||
| LiteLLM->>Auth: Forward token request | ||
| Auth->>LiteLLM: Access (and refresh) token | ||
| LiteLLM->>Client: Return tokens | ||
|
|
||
| Note over Client,MCP: Step 6 – Authenticated MCP call | ||
| Client->>LiteLLM: MCP request with access token + LiteLLM API key | ||
| LiteLLM->>MCP: MCP request with Bearer token | ||
| MCP-->>LiteLLM: MCP response | ||
| LiteLLM-->>Client: Return MCP response | ||
| ``` | ||
|
|
||
| **Participants** | ||
|
|
||
| - **Client** -- The MCP-capable AI agent (e.g., Claude Code, Cursor, or another IDE/agent) that initiates OAuth discovery, authorization, and tool invocations on behalf of the user. | ||
| - **LiteLLM Proxy** -- Mediates all OAuth discovery, registration, token exchange, and MCP traffic while protecting stored credentials. | ||
| - **Authorization Server** -- Issues OAuth 2.0 tokens via dynamic client registration, PKCE authorization, and token endpoints. | ||
| - **MCP Server (Resource Server)** -- The protected MCP endpoint that receives LiteLLM's authenticated JSON-RPC requests. | ||
| - **User-Agent (Browser)** -- Temporarily involved so the end user can grant consent during the authorization step. | ||
|
|
||
| **Flow Steps** | ||
|
|
||
| 1. **Resource Discovery**: The client fetches MCP resource metadata from LiteLLM's `.well-known/oauth-protected-resource` endpoint to understand scopes and capabilities. | ||
| 2. **Authorization Server Discovery**: The client retrieves the OAuth server metadata (token endpoint, authorization endpoint, supported PKCE methods) through LiteLLM's `.well-known/oauth-authorization-server` endpoint. | ||
| 3. **Dynamic Client Registration**: The client registers through LiteLLM, which forwards the request to the authorization server (RFC 7591). If the provider doesn't support dynamic registration, you can pre-store `client_id`/`client_secret` in LiteLLM (e.g., GitHub MCP) and the flow proceeds the same way. | ||
| 4. **User Authorization**: The client launches a browser session (with code challenge and resource hints). The user approves access, the authorization server sends the code through LiteLLM back to the client. | ||
| 5. **Token Exchange**: The client calls LiteLLM with the authorization code, code verifier, and resource. LiteLLM exchanges them with the authorization server and returns the issued access/refresh tokens. | ||
| 6. **MCP Invocation**: With a valid token, the client sends the MCP JSON-RPC request (plus LiteLLM API key) to LiteLLM, which forwards it to the MCP server and relays the tool response. | ||
|
|
||
| See the official [MCP Authorization Flow](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#authorization-flow-steps) for additional reference. | ||
|
|
||
| ## Machine-to-Machine (M2M) Auth | ||
|
|
||
| LiteLLM automatically fetches, caches, and refreshes OAuth2 tokens using the `client_credentials` grant. No manual token management required. | ||
|
|
||
| ### Setup | ||
|
|
||
| <Tabs> | ||
| <TabItem value="config" label="config.yaml"> | ||
|
|
||
| ```yaml title="config.yaml" showLineNumbers | ||
| mcp_servers: | ||
| my_mcp_server: | ||
| url: "https://my-mcp-server.com/mcp" | ||
| auth_type: oauth2 | ||
| client_id: os.environ/MCP_CLIENT_ID | ||
| client_secret: os.environ/MCP_CLIENT_SECRET | ||
| token_url: "https://auth.example.com/oauth/token" | ||
| scopes: ["mcp:read", "mcp:write"] # optional | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem value="ui" label="LiteLLM UI"> | ||
|
|
||
| Navigate to **MCP Servers → Add Server → Authentication → OAuth**, then fill in `client_id`, `client_secret`, and `token_url`. | ||
|
|
||
| </TabItem> | ||
| </Tabs> | ||
|
|
||
| ### How It Works | ||
|
|
||
| 1. On first MCP request, LiteLLM POSTs to `token_url` with `grant_type=client_credentials` | ||
| 2. The access token is cached in-memory with TTL = `expires_in - 60s` | ||
| 3. Subsequent requests reuse the cached token | ||
| 4. When the token expires, LiteLLM fetches a new one automatically | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant Client as Client | ||
| participant LiteLLM as LiteLLM Proxy | ||
| participant Auth as Authorization Server | ||
| participant MCP as MCP Server | ||
|
|
||
| Client->>LiteLLM: MCP request + LiteLLM API key | ||
| LiteLLM->>Auth: POST /oauth/token (client_credentials) | ||
| Auth->>LiteLLM: access_token (expires_in: 3600) | ||
| LiteLLM->>MCP: MCP request + Bearer token | ||
| MCP-->>LiteLLM: MCP response | ||
| LiteLLM-->>Client: MCP response | ||
|
|
||
| Note over LiteLLM: Token cached for subsequent requests | ||
| Client->>LiteLLM: Next MCP request | ||
| LiteLLM->>MCP: MCP request + cached Bearer token | ||
| MCP-->>LiteLLM: MCP response | ||
| LiteLLM-->>Client: MCP response | ||
| ``` | ||
|
|
||
| ### Test with Mock Server | ||
|
|
||
| Use [BerriAI/mock-oauth2-mcp-server](https://github.com/BerriAI/mock-oauth2-mcp-server) to test locally: | ||
|
|
||
| ```bash title="Terminal 1 - Start mock server" showLineNumbers | ||
| pip install fastapi uvicorn | ||
| python mock_oauth2_mcp_server.py # starts on :8765 | ||
| ``` | ||
|
|
||
| ```yaml title="config.yaml" showLineNumbers | ||
| mcp_servers: | ||
| test_oauth2: | ||
| url: "http://localhost:8765/mcp" | ||
| auth_type: oauth2 | ||
| client_id: "test-client" | ||
| client_secret: "test-secret" | ||
| token_url: "http://localhost:8765/oauth/token" | ||
| ``` | ||
|
|
||
| ```bash title="Terminal 2 - Start proxy and test" showLineNumbers | ||
| litellm --config config.yaml --port 4000 | ||
|
|
||
| # List tools | ||
| curl http://localhost:4000/mcp-rest/tools/list \ | ||
| -H "Authorization: Bearer sk-1234" | ||
|
|
||
| # Call a tool | ||
| curl http://localhost:4000/mcp-rest/tools/call \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Authorization: Bearer sk-1234" \ | ||
| -d '{"name": "echo", "arguments": {"message": "hello"}}' | ||
| ``` | ||
|
|
||
| ### Config Reference | ||
|
|
||
| | Field | Required | Description | | ||
| |-------|----------|-------------| | ||
| | `auth_type` | Yes | Must be `oauth2` | | ||
| | `client_id` | Yes | OAuth2 client ID. Supports `os.environ/VAR_NAME` | | ||
| | `client_secret` | Yes | OAuth2 client secret. Supports `os.environ/VAR_NAME` | | ||
| | `token_url` | Yes | Token endpoint URL | | ||
| | `scopes` | No | List of scopes to request | | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing trailing newline
This file is missing a final newline (
\ No newline at end of filein the diff). This commonly trips formatting/lint checks; add a newline at EOF.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File already has a trailing newline — verified with
xxd.