From d121a72f306ff513999f6786c0c33b0365760da0 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Wed, 17 Jun 2026 14:01:15 -0700 Subject: [PATCH] feat(mcp/v2): cover the full v1 MCPAuth surface in AuthSpecKind The first cut declared only the five OAuth-grant-shaped modes, which silently dropped two live v1 auth_types: none (no upstream credential, the default for public upstreams) and aws_sigv4 (per-request SigV4 signing for Bedrock AgentCore-style upstreams). Neither had a home, so a server configured with them would have fallen through the resolver. Adds both as AuthSpecKind members with their resolve() arms (Phase 0 stubs), and records that the static-header family v1 splits across bearer_token/api_key/basic/token/authorization collapses into the single api_key mode with the scheme carried as a parameter rather than its own mode. The match-exhaustiveness gate forced the spike's label_enum to grow the two arms too, which is the gate doing its job. --- .../_experimental/mcp_server/v2/README.md | 7 +++-- .../mcp_server/v2/_spike_exhaustiveness.py | 4 +++ .../mcp_server/v2/oauth/types.py | 27 ++++++++++++++++--- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/v2/README.md b/litellm/proxy/_experimental/mcp_server/v2/README.md index 1353f594a75..0874c02541c 100644 --- a/litellm/proxy/_experimental/mcp_server/v2/README.md +++ b/litellm/proxy/_experimental/mcp_server/v2/README.md @@ -55,8 +55,11 @@ _spike_exhaustiveness.py:44:18 - error: Argument of type "Literal[AuthSpecKind.a assigned to parameter "arg" of type "Never" in function "assert_never" (reportArgumentType) ``` -Restore the arm and the errors disappear. This is why adding a sixth `AuthSpecKind` member without a -`resolve()` arm fails the type gate rather than failing at runtime. +Restore the arm and the errors disappear. This is why adding a new `AuthSpecKind` member without a +`resolve()` arm fails the type gate rather than failing at runtime. `AuthSpecKind` covers v1's full +`MCPAuth` surface: the three OAuth grants (`authorization_code`, `client_credentials`, +`token_exchange`), the collapsed static-header family (`api_key`), client `passthrough`, `none` +(no upstream auth), and `aws_sigv4` (per-request signing). ## Toolchain notes diff --git a/litellm/proxy/_experimental/mcp_server/v2/_spike_exhaustiveness.py b/litellm/proxy/_experimental/mcp_server/v2/_spike_exhaustiveness.py index a2cc96d2a4d..54bd44e9dbe 100644 --- a/litellm/proxy/_experimental/mcp_server/v2/_spike_exhaustiveness.py +++ b/litellm/proxy/_experimental/mcp_server/v2/_spike_exhaustiveness.py @@ -41,6 +41,10 @@ def label_enum(kind: AuthSpecKind) -> str: return "static header" case AuthSpecKind.passthrough: return "client-forwarded" + case AuthSpecKind.none: + return "no upstream auth" + case AuthSpecKind.aws_sigv4: + return "aws sigv4 signing" # Reached only if an enum member has no arm above. basedpyright then narrows `kind` to that # uncovered member (not `Never`), so this `assert_never` is a type error => the gate bit. assert_never(kind) diff --git a/litellm/proxy/_experimental/mcp_server/v2/oauth/types.py b/litellm/proxy/_experimental/mcp_server/v2/oauth/types.py index 3781ddef35c..0ab6494663f 100644 --- a/litellm/proxy/_experimental/mcp_server/v2/oauth/types.py +++ b/litellm/proxy/_experimental/mcp_server/v2/oauth/types.py @@ -37,9 +37,12 @@ from ..result import Error, Ok, Result class AuthSpecKind(str, Enum): """The server's statically-declared upstream-auth mode — the single source of truth. - Canonical names follow the OAuth-grant vocabulary (see the Notion "resolve() Per-Mode - Behavior" page). BYOK is *not* a member: it is the `api_key` mode seeded per-user, a - source selector inside that arm. + Covers v1's full `MCPAuth` surface, not only OAuth grants: the three grant modes, the + collapsed static-header family, client passthrough, no-auth, and AWS request signing. + BYOK is *not* a member: it is the `api_key` mode seeded per-user, a source selector + inside that arm. The static-header schemes v1 splits into separate `MCPAuth` values + (`bearer_token`/`api_key`/`basic`/`token`/`authorization`) collapse into `api_key`; the + scheme is a parameter the arm carries, not its own mode. """ authorization_code = ( @@ -47,8 +50,10 @@ class AuthSpecKind(str, Enum): ) client_credentials = "client_credentials" # gateway service account (M2M) token_exchange = "token_exchange" # RFC 8693 on-behalf-of - api_key = "api_key" # fixed/static header (BYOK = per-user-seeded source) + api_key = "api_key" # static header, any scheme (BYOK = per-user-seeded source) passthrough = "passthrough" # client forwards an upstream-audience token + none = "none" # no upstream credential; resolve yields a no-op auth, never an error + aws_sigv4 = "aws_sigv4" # AWS SigV4 per-request signing (e.g. Bedrock AgentCore) @tagged_union(frozen=True) @@ -163,6 +168,10 @@ class UpstreamCredentialProvider: return self._api_key(subject, server) case AuthSpecKind.passthrough: return self._passthrough(subject, server) + case AuthSpecKind.none: + return self._none(subject, server) + case AuthSpecKind.aws_sigv4: + return self._aws_sigv4(subject, server) # --- arms: Phase 0 stubs (errors-as-values, no raise). Filled in Phase 1. ------------- def _authorization_code( @@ -190,6 +199,16 @@ class UpstreamCredentialProvider: ) -> Result[httpx.Auth, CredError]: return _todo(AuthSpecKind.passthrough) + def _none( + self, subject: Subject, server: ServerSpec + ) -> Result[httpx.Auth, CredError]: + return _todo(AuthSpecKind.none) + + def _aws_sigv4( + self, subject: Subject, server: ServerSpec + ) -> Result[httpx.Auth, CredError]: + return _todo(AuthSpecKind.aws_sigv4) + def _todo(kind: AuthSpecKind) -> Result[httpx.Auth, CredError]: return Error(