Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/_experimental/mcp_server/faults/types.py: 100%
36 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""Fault taxonomy for upstream OAuth token and DCR registration failures.
3Each fault is a frozen model on a ``tag`` literal. The tag alone decides the HTTP status, the wire
4error code, and whose prose the caller sees, so those three facts can never disagree the way they can
5when an upstream's status and error code are relayed independently.
6"""
8from __future__ import annotations
10from typing import Final, Literal, TypeAlias
12from pydantic import BaseModel, ConfigDict
14MAX_WIRE_FIELD_CHARS: Final = 500
15"""Bound on every upstream-derived string that crosses to a caller or into a log line."""
17CredentialSource: TypeAlias = Literal["gateway_stored", "caller_supplied"]
18"""Whose client credentials the gateway presented upstream: the MCP server's stored configuration or
19credentials the caller supplied on the request. Decides whether a credential rejection is the
20caller's problem to fix or the gateway operator's."""
22GATEWAY_CREDENTIAL_CODES: Final[frozenset[str]] = frozenset({"invalid_client", "unauthorized_client"})
23"""RFC 6749 error codes that indict the OAuth client's credentials or grant authorization. When the
24gateway presented its own stored credentials, these are gateway-side faults the caller cannot act on;
25when the caller supplied the credentials, they are the caller's to fix."""
27GATEWAY_CAPABILITY_CODES: Final[frozenset[str]] = frozenset({"invalid_target"})
28"""Codes that indict gateway configuration regardless of whose credentials were presented:
29``invalid_target`` means the upstream did not accept the RFC 8707 resource indicator the server
30sent, or requires one it was not configured to send (``upstream_resource``). Never the caller's
31fault."""
33UPSTREAM_FAULT_CODES: Final[frozenset[str]] = frozenset({"server_error", "temporarily_unavailable"})
34"""Codes by which the upstream blames itself. Relaying them as caller faults would invert blame, so
35they classify as upstream-reported faults and render on the 5xx their meaning implies."""
38class CallerRejected(BaseModel):
39 """The upstream spoke the OAuth error contract and the failure is actionable by our caller
40 (e.g. ``invalid_grant``: re-run authorization). The code and its bounded prose relay on the
41 4xx status the code itself implies."""
43 model_config = ConfigDict(frozen=True)
44 tag: Literal["caller_rejected"] = "caller_rejected"
45 code: str
46 description: str | None = None
47 error_uri: str | None = None
50class GatewayRejected(BaseModel):
51 """The upstream rejected the request for a cause only the gateway operator can address: the
52 server's stored client credentials or a gateway capability gap. Not actionable by the caller:
53 rendered as 502 with gateway-authored prose naming the code; the upstream's prose goes to
54 server logs only."""
56 model_config = ConfigDict(frozen=True)
57 tag: Literal["gateway_rejected"] = "gateway_rejected"
58 code: str
61class UpstreamReportedFault(BaseModel):
62 """The upstream blamed itself in the OAuth vocabulary. Rendered on the 5xx the code implies
63 (``server_error`` 502, ``temporarily_unavailable`` 503) so blame and status agree."""
65 model_config = ConfigDict(frozen=True)
66 tag: Literal["upstream_reported_fault"] = "upstream_reported_fault"
67 code: Literal["server_error", "temporarily_unavailable"]
70class UpstreamProtocolFault(BaseModel):
71 """The upstream broke the error contract: no JSON ``error`` field, an undecodable body, or a
72 success response without a usable token. Rendered as 502 with a gateway-authored note; the
73 upstream body never crosses to the caller."""
75 model_config = ConfigDict(frozen=True)
76 tag: Literal["upstream_protocol_fault"] = "upstream_protocol_fault"
77 note: str
80class UpstreamRegistrationRefused(BaseModel):
81 model_config = ConfigDict(frozen=True)
82 tag: Literal["upstream_registration_refused"] = "upstream_registration_refused"
83 status_code: Literal[401, 403]
86UpstreamOAuthFault: TypeAlias = (
87 CallerRejected | GatewayRejected | UpstreamReportedFault | UpstreamProtocolFault | UpstreamRegistrationRefused
88)