From 37eb63388aaf6f8482e815af1f99c48ed474b20e Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Mon, 9 Mar 2026 14:43:16 -0700 Subject: [PATCH] fix(mcp): require explicit opt-in for OAuth2 M2M client_credentials flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-detecting M2M from client_id+secret+token_url presence broke existing interactive OAuth setups (e.g. GitHub Enterprise). Add oauth2_flow field and default has_client_credentials to False — M2M must be explicitly opted into with oauth2_flow: client_credentials. --- .../mcp_server/mcp_server_manager.py | 2 ++ litellm/types/mcp_server/mcp_server_manager.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 0b58009fcf6..b569be416b3 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -318,6 +318,7 @@ class MCPServerManager: # oauth specific fields client_id=server_config.get("client_id", None), client_secret=server_config.get("client_secret", None), + oauth2_flow=server_config.get("oauth2_flow", None), scopes=resolved_scopes, authorization_url=resolved_authorization_url, token_url=resolved_token_url, @@ -632,6 +633,7 @@ class MCPServerManager: client_id=client_id_value or getattr(mcp_server, "client_id", None), client_secret=client_secret_value or getattr(mcp_server, "client_secret", None), + oauth2_flow=getattr(mcp_server, "oauth2_flow", None), scopes=resolved_scopes, authorization_url=mcp_server.authorization_url or getattr(mcp_oauth_metadata, "authorization_url", None), diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py index d94795fda2e..2af2dcb88b5 100644 --- a/litellm/types/mcp_server/mcp_server_manager.py +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Literal, Optional from pydantic import BaseModel, ConfigDict @@ -60,12 +60,22 @@ class MCPServer(BaseModel): byok_api_key_help_url: Optional[str] = None created_at: Optional[datetime] = None updated_at: Optional[datetime] = None + # OAuth2 flow type. Defaults to None (interactive / authorization_code). + # Set to "client_credentials" to enable M2M token fetching. + oauth2_flow: Optional[Literal["client_credentials", "authorization_code"]] = None model_config = ConfigDict(arbitrary_types_allowed=True) @property def has_client_credentials(self) -> bool: - """True if this server has OAuth2 client_credentials config (client_id, client_secret, token_url).""" - return bool(self.client_id and self.client_secret and self.token_url) + """True if this server should use the OAuth2 client_credentials (M2M) flow. + + M2M flow must be opted into explicitly via ``oauth2_flow: client_credentials``. + Having client_id / client_secret / token_url present is NOT sufficient — + those fields are also used for interactive (authorization_code) OAuth, + e.g. GitHub Enterprise. Auto-detecting M2M from field presence was a + breaking regression introduced with the M2M feature. + """ + return self.oauth2_flow == "client_credentials" @property def needs_user_oauth_token(self) -> bool: