Commit graph

46529 commits

Author SHA1 Message Date
yuneng-jiang
b00877c0a6
Merge pull request #32530 from BerriAI/litellm_/competent-williams-410458
ci: run unit test workflows on ui-only changes (revert #32422)
2026-07-08 12:09:12 -07:00
Yuneng Jiang
b76073e4ee
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/competent-williams-410458 2026-07-08 11:56:32 -07:00
Yuneng Jiang
82fd456b94
Revert "ci: skip unit test workflows when only ui or markdown files change (#32422)"
This reverts commit 6df5e1b263.
2026-07-08 11:55:59 -07:00
tin-berri
86a9871ae9
Merge pull request #32507 from BerriAI/litellm_fix_mcp_token_exchange_secret_pairing
fix(mcp): pair token-endpoint client_secret with the same source as client_id
2026-07-08 11:47:08 -07:00
yuneng-jiang
4da00b88ac
Merge branch 'litellm_internal_staging' into litellm_yj_july8 2026-07-08 11:26:17 -07:00
devin-ai-integration[bot]
93c047d52e
feat(proxy): make Microsoft Graph endpoint configurable for GCC High (LIT-4282) (#32517)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-08 11:07:58 -07:00
ishaan-berri
ad69d6f3f9
test: emit e2e coverage lines for loki (#32513) 2026-07-08 11:06:56 -07:00
Ishaan Jaff
33aaea363c
ci: add OSS daily branch workflow 2026-07-08 10:54:13 -07:00
David Katz
c0327cded4 fix(mcp): pair token-endpoint client_secret with the same source as client_id
On re-auth against a server with a persisted DCR client, register_client_with_server
short-circuits and returns a placeholder client_secret ("dummy") that the browser
echoes back to /token. exchange_token_with_server overrode the caller's client_id
with the persisted one but still fell back to the caller's secret when the server
had none stored, so a persisted public PKCE client (which has no secret) was paired
with the literal string "dummy" and the IdP rejected the exchange with 401 on every
re-authorization; the proxy surfaced that as a 500. First connects and brand-new
servers worked because a real DCR registration ran and no placeholder existed.
Resolve the secret from the server whenever the server's client_id wins, so a
secretless public client sends no client_secret at all
2026-07-08 10:43:19 -07:00
yucheng-berri
f982b67d78
fix(proxy): harden secret name validation for external secret manager integrations (LIT-4201) (#32092)
key_alias can become the secret name used by external secret manager
integrations (HashiCorp Vault, CyberArk Conjur) when store_virtual_keys is
enabled. Add raise_if_unsafe_secret_name, a shared validation check applied
unconditionally before a secret name reaches either integration or the
/key/generate, /key/update, and /key/regenerate API boundary, independent
of the existing enable_key_alias_format_validation opt-in flag.

Also hardens the Vault URL builder to percent-encode reserved characters
in secret_name (preserving "/" and "@"), and switches the Conjur policy
body to a real YAML serializer instead of raw string interpolation.
2026-07-08 10:36:00 -07:00
Mateo Wang
c2d8a17692
test(responses): replace perma-skip azure shell e2e with offline coverage (#32444) 2026-07-08 10:01:41 -07:00
yuneng-jiang
cbc6a79972
Merge pull request #32432 from thibault-linktree/litellm_ui_session_sidebar_sort_toggle
feat(ui): sort session sidebar calls by duration or start time
2026-07-08 09:22:41 -07:00
Yassin Kortam
bfff5e8d86
fix(mcp): log MCP tool calls returning isError=true as failures (#32238)
An MCP tool call that completes with CallToolResult.isError=true correctly
returns HTTP 200 per the MCP spec, but the shared post-call logging helper
always fired async_success_handler, so the standard logging payload carried
status=success and OTel (whose _parse_error only marks ERROR on
status=failure) showed green spans for failed tools.

The helper now checks the result after async_post_mcp_tool_call_hook runs
(guardrails may flip isError there) and routes error results to the failure
path: success gates are consumed so the @client wrapper cannot enqueue a
success log, failure_handler and async_failure_handler fire with a new
MCPToolResultError carrying the tool's first text content, and
post_call_failure_hook records the failure the same way raised exceptions
already do. Raised exceptions never reach the helper, so no double failure
logging. HTTP wire behavior is unchanged

Resolves LIT-4081
2026-07-08 09:02:48 -07:00
KnyazSh
e30c1722ed Merge branch 'litellm_internal_staging' into feature/improve-gigachat-provider 2026-07-08 09:10:42 +00:00
KnyazSh
9e5a710e8e fix(tests): revert casting of litellm_logging_obj to correctly inject mocks 2026-07-08 08:40:44 +00:00
Chenlu Ji
c62e1238d6 feat(tinyfish): surface response headers + top-level response extras
Follow-up to #31411 (superseded and merged as #31997). Two related fixes
so LiteLLM callers see what TinyFish actually returns, plus small
correctness cleanups.

## Response headers surfaced on _hidden_params

TinyFish sets useful response headers (x-request-id on every response,
retry-after and x-ratelimit-limit on 429s). Previously these were only
accessible via BaseLLMException.headers on error paths; on the success
path they were dropped entirely.

Fix: stash headers on both LiteLLM-conventional channels, matching the
pattern used by Gemini / Volcengine / Manus / ChatGPT / OpenAI-responses
providers.

- `_hidden_params["headers"]` -- raw dict from httpx, all keys lowercased.
- `_hidden_params["additional_headers"]` -- passed through
  process_response_headers, which prefixes any x-litellm-* provider
  header with `llm_provider-` so downstream LiteLLM code that trusts
  bare x-litellm-* markers can't be spoofed (values still survive
  under the prefixed key for observability).

## Top-level response extras (query, total_results, page, future fields)

transform_search_response was building a fresh SearchResponse from just
`results`, silently dropping every top-level field TinyFish's response
carries beyond `results` / `object`.

Fix: mutate parsed.results to its truncated slice and return the same
SearchResponse instance rather than reconstructing. Every field pydantic
populated during model_validate -- declared attributes AND extras
(query, total_results, page, parameter_warnings, and any future TinyFish
additions) -- survives regardless of which storage bucket holds it.
Robust against upstream schema evolution: if LiteLLM later promotes a
field from extras to declared, this code needs no change.

## Code cleanup

- List-valued custom params JSON-encoded on the wire (matching the
  existing dict handling), so callers can pass a natural Python list
  for JSON-array wire params.
- URL-encodable-params adapter accepts float in addition to
  str / int / bool; server-side rejection of a wrong-typed float now
  surfaces cleanly with `TinyFish Search:` attribution + docs link.
- Assorted comment / docstring / test-fixture hygiene (no logic changes).

## Tests

70 unit + integration tests pass locally. Live-tested against
production TinyFish with 6 diverse queries (basic / max_results /
country=US / language=ja / domain filter / fetch={"format":"html"}) --
all 6 pass every expected-behavior check.
2026-07-08 01:06:17 -07:00
KnyazSh
8b0164d5b2 fix(lint): fix LIT003, add explanatory comments to exception handlers 2026-07-08 07:57:51 +00:00
Mateo Wang
cd6e8cdf23
test(realtime): record and replay websocket traffic in redis vcr cassettes (#32390)
* test(realtime): record and replay websocket traffic in redis vcr cassettes

* style(realtime): ruff-format ws-vcr harness

* fix(realtime): warn instead of silently disabling ws-vcr when the redis client cannot be built
2026-07-08 00:19:06 -07:00
Mateo Wang
684e3e1c2e
test(vertex_ai): bump local_testing vertex tests from gemini-2.5-flash to gemini-3.5-flash (#32439) 2026-07-08 00:17:55 -07:00
Thibault Serot
6d2090a21b fix(ui): reset session sort mode when drawer closes 2026-07-08 17:17:44 +10:00
Yuriy
7a73138877
Merge branch 'BerriAI:litellm_internal_staging' into feature/improve-gigachat-provider 2026-07-08 10:13:29 +03:00
KnyazSh
12c291c39d test(gigachat): add authenticator tests and update passthrough test 2026-07-08 07:11:50 +00:00
Thibault Serot
5d89be551b fix(ui): fit session sort toggle inside sidebar column 2026-07-08 17:06:38 +10:00
Yassin Kortam
6f6bd45681
perf(auth): negative-cache missing user/key lookups on the request hot path (#32368) 2026-07-08 09:59:57 +03:00
tin-berri
1fb2b4aef4
fix(mcp): drop the cached per-user OAuth token when the credential row changes (#32302)
* fix(mcp): drop the cached per-user OAuth token when the credential row changes

The v2 authorization_code chain Cached(Refreshing(V2PerUserTokenStore)) caches a positive token
until its expires_at (or 300s without one), and CachedOAuthTokenStore.invalidate had no callers,
so a re-authorization or revocation wrote the DB while egress kept serving the replaced token
from the in-process cache until its TTL. LazyPerUserOAuthTokenStore now exposes invalidate,
MCPServerManager threads it to the write side, and the three credential write sites (the OAuth
callback, the Tools-tab persist endpoint, and the revoke endpoint) drop the cache entry after
the row changes. The v2 refresher's own persist stays untouched; RefreshingTokenStore already
feeds the rotated token back into the cache in the same fetch

* test(mcp): pin cache invalidation on the revoke already-gone branch

Greptile's review flagged that only the happy-path delete asserted the invalidate; a refactor
moving the call inside the try block would silently skip the cache drop when the row was
already deleted by a concurrent request while the cache still held the revoked token. The new
test fails on exactly that mutation

* test(mcp): cover invalidate on the redis-backed lazy store path

Codecov flagged the redis fast path of LazyPerUserOAuthTokenStore.invalidate as unexercised;
the existing invalidate tests only ran the no-redis chain. The new test builds the redis chain
via a fetch and asserts a subsequent invalidate reaches the same store instance without a
rebuild
2026-07-07 23:59:35 -07:00
Thibault Serot
df2d44bab1 feat(ui): sort session sidebar by duration or start time 2026-07-08 16:54:39 +10:00
Yassin Kortam
bcd52754de
feat(rate_limit): support per-tag rpm limiting on a single key (#31502)
Add a tag_rpm_limit field to virtual keys so each request tag gets its own independent RPM counter on the v3 rate limiter. A key configured with per-tag limits tracks each tag/group separately, and requests whose tag has no configured limit fall back to the key-level limit. Includes the dashboard UI to manage per-tag limits on key create and edit.

Resolves LIT-3147
2026-07-08 09:43:47 +03:00
KnyazSh
30ed0c14b5 fix(lint) : resolve LIT006 violations 2026-07-08 06:36:55 +00:00
Thibault Serot
34db5f4813 feat(ui): add start time sort toggle to session logs sidebar 2026-07-08 16:26:47 +10:00
tin-berri
d6cbf6e7e3
feat(ui): expose MCP max_concurrent_requests in server create and edit forms (#32397)
* feat(ui): expose MCP max_concurrent_requests in server create and edit forms

The proxy has enforced a per-server outbound tool-call concurrency cap
(max_concurrent_requests) across every MCP egress path since #31641, and the
management API has accepted the field on create and update all along, but the
dashboard offered no way to set it. Add an optional Max Concurrent Requests
input to the MCP server create and edit forms; it applies to every auth type
and transport, so it renders unconditionally rather than gated on auth mode.
Clearing the field on edit sends null so the stored limit is unset.

Also rebuild the per-server semaphore when the configured limit changes.
Previously the semaphore was created once per server_id and never resized, so
an edited limit only took effect after a proxy restart even though the new
value was persisted and reloaded into the registry.

* feat(ui): mark MCP max concurrent requests field label as optional

* test(ui): stop OBO create-form tests from timing out on CI

The token-exchange payload test and the Entra scope-required test filled five
text fields with user.type, which dispatches a full keystroke sequence per
character; every input event runs the antd form onValuesChange handler and
re-renders the whole CreateMCPServer tree, roughly 120 renders per test. As
the form grew the two tests reached 8s and 18s locally, which crosses the 30s
vitest timeout on slower CI containers; ui_unit_tests failed twice this way.
Switch the plain text fields to fireEvent.change (one input event per field),
matching the existing stdio test pattern. Both tests assert form output, not
keystroke behavior, and now run in about 3s each.
2026-07-07 22:47:03 -07:00
Mateo Wang
6df5e1b263
ci: skip unit test workflows when only ui or markdown files change (#32422)
* ci: skip unit test workflows when only docs or ui files change

Mirror the CircleCI backend path filter (.circleci/scripts/classify_changes.sh)
in the GitHub Actions unit test workflows by adding paths-ignore for ui/**,
docs/**, *.md and *.mdx to every test-unit-*.yml pull_request trigger

* ci: drop docs/** from unit test paths-ignore since the folder no longer exists
2026-07-07 22:10:58 -07:00
Mateo Wang
06a43d11c4
test(responses): bound azure shell tool live call at 90s and skip on provider timeout (#32424)
* ci(responses): bound azure shell tool e2e call and enforce per-test timeout

The azure variant of test_responses_api_shell_tool always makes a live
Azure call (its skip outcome means no VCR cassette is ever persisted).
When Azure held the connection instead of answering, the call sat on
litellm's 6000s responses deadline until CircleCI killed the whole job
via no_output_timeout after 15m of silence (job 2013288).

Bound the e2e call at 90s and skip on litellm.Timeout, matching the
existing InternalServerError and BadRequestError skips, and give the
llm_responses_api_testing job the same pytest-timeout guard the
llm_translation_testing job already uses so no single hung test can
consume the 15m no-output window again.

* test(responses): drop job-level pytest timeout, keep shell tool 90s bound
2026-07-07 21:58:14 -07:00
Mateo Wang
404ec7fc2e
ci(llm_responses_api_testing): bound live re-record calls and rerun timeout-only failures to stop 15m no-output kills (#32420) 2026-07-07 21:57:46 -07:00
Mateo Wang
c212c16852
ci: ratchet LIT003 budget down to current count to remove suppression slack (#32423) 2026-07-07 21:56:05 -07:00
yuneng-jiang
ec4f324482
Merge pull request #32405 from BerriAI/litellm_kraken-remove-envref-gates
fix(proxy): resolve os.environ/ refs universally in DB-sourced models
2026-07-07 21:53:56 -07:00
tin-berri
f922be32f0
fix(mcp): accept integer progressToken in host progress capture (#32402) 2026-07-07 21:46:36 -07:00
tin-berri
7cc660866a
fix(ui/mcp): do not reset in-flight OAuth resume when create modal mounts closed (#32416) 2026-07-07 21:42:08 -07:00
Mateo Wang
b2e2a38bc0
fix(passthrough): stream non-sse passthrough responses instead of buffering in memory (#32386)
* fix(passthrough): stream non-sse passthrough responses instead of buffering in memory

Non-SSE passthrough responses were fully read into proxy memory (content = await response.aread()) before the first byte reached the client. For large non-JSON bodies such as Anthropic batch results jsonl files this ballooned proxy RSS to a multiple of the file size and produced near-total TTFB dead air, letting intermediaries kill the silent connection and truncate the download.

The upstream request is now sent with httpx stream semantics and the buffering decision is made from the response headers: application/json (and +json) bodies plus upstream errors keep the buffered behavior since spend logging, guardrails and managed-id rewriting inspect them, while every other 2xx body is relayed as a StreamingResponse that iterates upstream bytes without accumulating them, preserving status code and headers (including x-litellm-*) and firing the success-handler logging with response_body=None once the stream completes.

* fix(passthrough): log client disconnects mid-stream and derive test client cache key from production code

* test(passthrough): intercept AsyncClient.send in legacy passthrough tests and assert final wire params

* test(passthrough): fail with a clear assert when the passthrough client cache scan misses
2026-07-07 20:51:15 -07:00
Mateo Wang
734fd29e00
fix(utils): resolve bedrock regional inference profiles to regional pricing in get_model_info (LIT-4056) (#32389)
* fix(utils): resolve bedrock regional inference profiles to regional pricing in get_model_info (LIT-4056)

* test(register_model): use a triple provider prefix as the unresolvable-key fixture

get_model_info now resolves bedrock/bedrock/... like a routing prefix, so the
double-prefix fixture stopped exercising the register_model fallback path.
Lock the new double-prefix resolution in as a model-info regression test
2026-07-07 20:49:03 -07:00
thibault-linktree
4b0ac8b352
fix(responses): make response-id encoding idempotent to prevent MCP gateway double-encoding previous_response_id (#32034) 2026-07-07 19:47:11 -07:00
Tin
732832d342 fix(mcp): bind client-forwarded Authorization to a single upstream
Some checks failed
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
In a listing fan-out over a scope containing more than one server that
consumes the caller's Authorization (true_passthrough, oauth_delegate,
or the legacy delegate/passthrough shapes), the request-wide bearer is
now withheld from the new modes instead of being replayed against every
upstream (RFC 9700 cross-resource replay). Explicitly-addressed
operations (tool call, get_prompt, read_resource, single-server routes)
keep forwarding it.

Multi-server aggregates use the per-server x-mcp-{alias}-authorization
header instead: its value now feeds the passthrough resolver arm as the
inbound token and wins over the request-wide header, binding one token
to one server.
2026-07-07 19:46:23 -07:00
ishaan-berri
3ea27bd64c
test: add e2e coverage module metrics (#32403)
* Split LLM e2e coverage modules

* Add e2e coverage dashboard metrics

* Remove dashboard brief from e2e coverage PR
2026-07-07 19:38:56 -07:00
Mateo Wang
07aeaa17a0
fix(passthrough): stop request params from clobbering merged target query params (#32404)
* fix(passthrough): stop request params from clobbering merged target query params

* fix(passthrough): rewrite managed ids in query params before folding them into the URL
2026-07-07 18:56:56 -07:00
Mateo Wang
ae0d84116a
ci(server-root-path): retry npm/playwright installs and disable matrix fail-fast (#32406) 2026-07-07 18:56:12 -07:00
Mateo Wang
bd6cabee83
fix(model_prices): add gpt-realtime-2.1 models with regional processing uplift (#32387)
* fix(model_prices): add gpt-realtime-2.1 models with regional processing uplift

* fix(model_prices): add cache_read_input_audio_token_cost to gpt-realtime-2.1
2026-07-07 18:56:00 -07:00
mubashir1osmani
a05a1eef94
fix(ui): scope key models dropdown options to the key's team (#32382)
* fix(ui): scope key models dropdown options to the key's team

A teamless key no longer offers the all-team-models option in the create and
edit forms; the backend expands that sentinel to the full proxy model list when
no team is attached, which is rarely what the user intended. A team key no
longer surfaces the all-proxy-models sentinel that leaks in verbatim when the
team's own model list carries it; the dropdown keeps All Team Models plus the
team's individual models.

Adds browser coverage to the management e2e suite: playwright (an optional
dependency behind importorskip) drives the proxy-served dashboard at /ui,
asserts the dropdown options a real user sees for teamless and team keys on
both create and edit, and walks the create modal end to end, reading the
persisted key back through /key/info.

* fix(ui): offer all-proxy-models on teamless keys in the models dropdown

A teamless key has no team allowlist to inherit, so the dropdown now offers All
Proxy Models in place of All Team Models on both the create and edit forms, with
the same exclusive-selection handling. Component and browser e2e tests updated to
pin the swapped option pair; the teamless create case now also walks the modal end
to end and reads the persisted key back through /key/info.

* test(ui): update no-team key creation spec to pick All Proxy Models

The create modal no longer offers All Team Models without a team; the teamless
path now offers All Proxy Models, which is what this spec exercises

* fix(ui): gate All Team Models on the team object being loaded

When a key has a team_id but the teams prop does not yet include the matching team, availableModels stays empty and the models dropdown rendered All Team Models on its own with nothing to compare against. Gate the option on the team object being present so it only appears once team models are known, and add a regression test for the loading state

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): filter all-proxy-models from teamless model fetch in key edit form

The teamless fetch path stored modelAvailableCall results without excludeProxyWideSentinel, so an all-proxy-models entry in the response rendered a second option colliding with the hardcoded All Proxy Models sentinel. Apply the same filter used on the team path and add a regression test asserting the sentinel option is not duplicated

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Mubashir Osmani <mubashir@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-07 18:54:19 -07:00
yucheng-berri
5862be3e79 fix(proxy): resolve os.environ/ refs universally in DB-sourced models
Root cause: PR #30867 removed request-time os.environ/ expansion in
BaseAWSLLM.get_credentials. That is only safe if config-load pre-resolves
os.environ/ refs so the value reaching get_credentials is already the real
secret. The YAML config path has always done this. The DB-load path
(ProxyConfig._resolve_db_litellm_param) only re-expanded keys in a hardcoded
whitelist (_DB_LITELLM_PARAM_ENV_REF_KEYS) plus short-circuited env-ref
resolution entirely for team-scoped rows. PR #32256 extended that whitelist
to 18 keys to unblock a customer whose Bedrock model with aws_role_name:
os.environ/BEDROCK_ASSUME_ROLE_ARN broke on v1.90+, but the whitelist is
structurally fragile: every future auth field breaks the same way until
someone remembers to add it

Fix: remove the whitelist and the team-scope short-circuit. The DB-load
resolver now expands os.environ/ on every string field, matching the YAML
path. Trust boundary stays on the write side: only PROXY_ADMIN can create
team_id=None rows, only team admins of a team can create rows scoped to
that team, and the request-body vector is still blocked by
_BANNED_REQUEST_BODY_PARAMS. Team-scoped rows now resolve env refs — this
is a deliberate LIT-3831 threat-model expansion trusting team admins for
env-var reads

Regression tests in tests/test_litellm/proxy/proxy_server/test_proxy_config.py:
- test_ProxyConfig__add_deployment_resolves_env_refs_after_db_decrypt pins
  admin-scoped rows resolve every field (previously api_base stayed literal)
- test_ProxyConfig__add_deployment_resolves_team_env_refs pins team rows
  resolve env refs (previously stayed literal)
- test_ProxyConfig__add_deployment_resolves_env_refs_on_arbitrary_field pins
  the no-whitelist invariant against a made-up field name
- test_ProxyConfig__add_deployment_resolves_env_refs_for_aws_bedrock_auth_params
  (from #32256) still passes
- Path B counterparts (decrypt_model_list_from_db) mirror the above

Left as followups (not fixed here):
- /model/info and /v2/model/info still echo resolved values for fields not
  in the current pop-list (aws_role_name, aws_sts_endpoint, api_base, etc.).
  Fix is to extend remove_sensitive_info_from_deployment; separate PR
- Master-key rotation reads DB rows via decrypt_model_list_from_db which
  now resolves universally, so rotation collapses env-refs into hardcoded
  values. Pre-existing bug for the 6 previously-whitelisted fields; wider
  surface after this PR. Separate PR
2026-07-07 18:31:44 -07:00
Tin Chi Lo
9bf3907de5 fix(mcp): bind oauth_delegate discovery resource to the upstream
oauth_delegate forwards the caller's token to the upstream, which validates its
audience, so the protected-resource metadata must keep resource pointing at the
upstream (returned verbatim, like true_passthrough) rather than rewriting it to
the gateway. Rewriting to the gateway asks the client to mint a token bound to
the gateway audience, which a strict IdP (Entra) refuses to issue for an
unregistered resource and a spec-compliant upstream rejects on receipt. The
legacy is_oauth_passthrough opt-in keeps the gateway rewrite unchanged.
2026-07-07 17:32:13 -07:00
Tin Chi Lo
446a6e8cdd fix(mcp): route true_passthrough and oauth_delegate through upstream OAuth discovery
Both modes advertised LiteLLM as the authorization server and answered initialize locally, so a client with no token connected empty and was never driven into the upstream OAuth flow. The protected-resource discovery now proxies the upstream metadata for both modes (verbatim for true_passthrough, resource rewritten to the gateway for oauth_delegate), and the preemptive 401 emits the matching challenge: oauth_delegate uses the gateway-proxied resource_metadata once admission passes, true_passthrough probes the upstream anonymously and surfaces its WWW-Authenticate verbatim so the client authorizes directly against the upstream
2026-07-07 17:32:13 -07:00
Tin Chi Lo
50c90281f4 feat(mcp): add true_passthrough and oauth_delegate auth modes
Introduce two first-class MCP server auth_type values that make LiteLLM's
role in upstream authentication explicit, added alongside the existing
delegate_auth_to_upstream / oauth_passthrough flags without changing their
behavior.

true_passthrough is a transparent proxy: LiteLLM performs no admission auth,
requires no x-litellm-api-key, mints/stores/refreshes nothing, and forwards the
client's Authorization to the upstream exactly as received. oauth_delegate keeps
normal LiteLLM admission (x-litellm-api-key / SSO / JWT) and then forwards the
client's separate upstream Authorization unchanged; the admission credential is
never forwarded upstream.

Both modes forward the caller's token via the existing extra_headers path and
defer egress credential resolution to v1 (the v2 to_server_spec returns None for
them). Upstream 401/403 responses are surfaced rather than swallowed so upstream
OAuth challenges are preserved. Servers in either mode require per-user auth, so
userless health checks are skipped.
2026-07-07 17:32:13 -07:00