mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-17 23:51:30 +00:00
refactor(mcp): make AuthorizationCodeConfig fields optional (discovery + DCR)
The four fields (client_id, client_secret, authorization_url, token_url) were required, which only models the legacy manual-registration case. Real MCP OAuth (Slack, Notion, Atlassian) discovers endpoints (RFC 9728 -> RFC 8414) and registers the client via DCR (RFC 7591), so an admin provides none of them - the discovered endpoints and DCR-registered client are obtained at runtime and persisted by the AS surface, and resolve() reads only the per-user token from the TokenStore. Make the four optional manual overrides; scopes is the one genuine config field. The rejects-missing-required test moves to client_credentials (whose creds are provisioned, not DCR'd), and a new test pins that an empty authorization_code config is valid.
This commit is contained in:
parent
108e75c3cd
commit
02e752886d
2 changed files with 21 additions and 7 deletions
|
|
@ -138,15 +138,22 @@ ApiKeyScheme = Literal["bearer", "apikey", "basic", "token", "raw"]
|
|||
|
||||
|
||||
class AuthorizationCodeConfig(BaseModel):
|
||||
"""Per-user 3LO; the gateway is the OAuth client and stores the user's token."""
|
||||
"""Per-user 3LO; the gateway is the OAuth client and stores the user's token.
|
||||
|
||||
Endpoints are discovered (RFC 9728 -> RFC 8414) and the client is registered via DCR
|
||||
(RFC 7591), so the common case carries none of the fields below; they are optional manual
|
||||
overrides for IdPs without discovery / DCR. The discovered endpoints and the DCR-registered
|
||||
client are persisted by the AS surface, not here; `resolve()` reads the per-user token from
|
||||
the `TokenStore`.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
kind: Literal[AuthSpecKind.authorization_code] = AuthSpecKind.authorization_code
|
||||
client_id: str
|
||||
client_secret: SecretStr
|
||||
authorization_url: str
|
||||
token_url: str
|
||||
scopes: tuple[str, ...] = ()
|
||||
client_id: str | None = None
|
||||
client_secret: SecretStr | None = None
|
||||
authorization_url: str | None = None
|
||||
token_url: str | None = None
|
||||
|
||||
|
||||
class ClientCredentialsConfig(BaseModel):
|
||||
|
|
|
|||
|
|
@ -186,9 +186,16 @@ def test_auth_spec_kind_is_derived_from_config():
|
|||
|
||||
|
||||
def test_discriminated_union_rejects_config_missing_required_fields():
|
||||
# authorization_code requires client_id/secret/urls; an empty body must fail at construction.
|
||||
# client_credentials requires client_id/secret/token_url; an empty body must fail.
|
||||
with pytest.raises(ValidationError):
|
||||
_spec({"kind": "authorization_code"})
|
||||
_spec({"kind": "client_credentials"})
|
||||
|
||||
|
||||
def test_authorization_code_config_allows_discovery_defaults():
|
||||
# Discovery + DCR: endpoints and client creds are obtained at runtime, so an empty
|
||||
# authorization_code config is valid - the fields are optional manual overrides.
|
||||
spec = _spec({"kind": "authorization_code"})
|
||||
assert isinstance(spec.config, AuthorizationCodeConfig)
|
||||
|
||||
|
||||
def test_discriminated_union_picks_the_variant_by_kind():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue