litellm/litellm
yuneng-jiang 36bd7f1138
fix(mcp): honor an explicit null on toolset update, cover MCP lifecycle e2e (#40022)
* fix(mcp): honor an explicit null on toolset update, cover MCP lifecycle e2e

PUT /v1/mcp/toolset dumped its payload with exclude_none, so a field sent as
null looked exactly like one the caller left out and the stored value
survived. An admin could not clear a toolset's description: the save reported
success and the old text came straight back. It now dumps with exclude_unset,
so absent keeps and null clears, which is what PUT /v1/mcp/server already did.
A null tools list clears the selection to empty, and a null toolset_name is
ignored because a toolset always has a name.

Adds create, read, partial-update, clear and delete e2e coverage for MCP
servers and toolsets, with every read-back polled on every replica so an edit
that lands on one replica and not another fails the test, plus an enforcement
test proving a key granted a toolset lists exactly that toolset's tools
against the real Datadog upstream.

* fix(e2e): refuse a read-back that no replica serves

A read-back over an empty replica mapping satisfied every predicate and
returned as if it had converged, so it would have asserted nothing and
passed. No wiring can produce that today, since the replica list always
falls back to at least one URL, but a helper whose whole job is proving a
write reached every replica should not have a shape that passes vacuously.

* fix(mcp): keep a null tools list a no-op on toolset update

Treating a null tools list as a clear meant an existing client that sends
tools=null during a partial update, meaning "leave the selection alone",
silently lost every tool the toolset grants. That is a permission surface,
so the quiet version of it is the worst version.

A toolset always has a tool list, the same way it always has a name, so a
null on either is now a no-op. Emptying the selection is an explicit [],
which cannot be confused with a field the caller left out, and which is
what the dashboard already sends.

* fix(e2e): keep MCP admin routes on the data plane

/v1/mcp/* is a lazily mounted feature, so a gateway registers it on the first
matching request, which happens after the startup route trim that drops
management endpoints. Routing it to the control plane therefore sent every MCP
call to the one backend process: the new lifecycle read-backs proved a single
process rather than every replica, and mcp_client's await_registered barrier
waited on a registry that does not serve the tools/list call it guards, so the
existing MCP suites polled a gateway that had not synced yet until poll_timeout

Verified against a two-gateway split stack (backend on 4001, gateways on 4010
and 4011, one postgres): both gateways answer /v1/mcp/server and /v1/mcp/toolset,
and each served 6 server reads and 7 toolset reads over the run

* fix(e2e): grant the toolset by the tool's own name, not the wire name

tools/list serves a tool as <prefix><tool_name>, but a toolset grants by the
tool's own name: resolve_toolset_permissions reads toolset.tools[].tool_name
straight through, and the prefix is added on the way out. The test built the
toolset from the names tools/list reported, so the grant matched nothing, the
scoped key listed no tools, and await_tools ran out its whole poll_timeout
before failing

Measure the prefix off search_datadog_logs, whose own name is known, rather than
guessing it from the alias, since the proxy can be configured to prefix with a
short server id instead. The expectation compared against tools/list stays in
wire names; only what the toolset stores crosses back

* test(mcp): build immutable lifecycle updates and replica results

* test: validate opaque stream IDs and hide log-reader credentials

* test: isolate auto-router scenarios and clean partial setup

* test: honor Datadog search rate-limit reset headers

* test: share the Datadog read-back deadline across retries

* test: preserve captured MCP toolset update fields
2026-09-08 22:50:13 -07:00
..
a2a_protocol Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_decrease_anys_opus5_r4 2026-09-02 23:32:10 +00:00
anthropic_interface fix(anthropic_endpoints): return Anthropic type:error envelope for /v1/messages errors 2026-08-31 16:23:36 -07:00
assistants refactor(typing): replace Any with proven types in 89 more backend files 2026-09-02 23:08:42 +00:00
batch_completion
batches Merge pull request #39626 from BerriAI/litellm_batch_ui_logs 2026-09-08 15:19:25 -07:00
caching fix(least-busy): count for every router, and keep the expiry through a clamp 2026-09-06 01:59:15 -07:00
completion_extras fix(responses bridge): keep mid-conversation system messages in input 2026-09-08 11:44:49 -07:00
compression refactor(typing): replace Any with proven types in 89 more backend files 2026-09-02 23:08:42 +00:00
containers Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_decrease_anys_opus5_r4 2026-09-02 23:32:10 +00:00
endpoints/speech/speech_to_completion_bridge refactor(speech): freeze httpx response header dicts (LIT002) 2026-08-31 21:12:53 -07:00
evals
experimental_mcp_client chore: merge origin/litellm_internal_staging into litellm_decrease_anys_opus5_r4 2026-09-04 18:59:35 -07:00
files refactor(typing): replace Any with proven types in 42 more backend files 2026-09-02 15:35:01 +00:00
fine_tuning refactor(typing): replace Any with proven types in 89 more backend files 2026-09-02 23:08:42 +00:00
google_genai refactor(types): replace Any with precise types across 73 modules 2026-09-01 11:05:02 +00:00
images refactor(typing): replace Any with proven types in 89 more backend files 2026-09-02 23:08:42 +00:00
integrations feat(otel v2): send a key's or team's whole trace to its own destination (#39654) 2026-09-08 16:23:21 -07:00
interactions refactor(typing): replace Any with proven types in 42 more backend files 2026-09-02 15:35:01 +00:00
litellm_core_utils Merge pull request #37781 from ZXT-zjbiliy/fix/build-base-response-empty-choices 2026-09-08 19:14:03 -07:00
llms Merge pull request #40189 from BerriAI/litellm_lit_3157_azure_ai_catalog_models 2026-09-08 20:08:40 -07:00
models Merge pull request #39626 from BerriAI/litellm_batch_ui_logs 2026-09-08 15:19:25 -07:00
ocr test(ocr): add SDK callback E2E parity (#40061) 2026-09-07 11:23:12 -07:00
passthrough Merge origin/litellm_internal_staging into litellm_techdebt_20260901 2026-09-01 19:38:19 +00:00
proxy fix(mcp): honor an explicit null on toolset update, cover MCP lifecycle e2e (#40022) 2026-09-08 22:50:13 -07:00
proxy_auth
rag chore: merge origin/litellm_internal_staging into litellm_decrease_anys_opus5_r4 2026-09-04 18:59:35 -07:00
realtime_api Merge pull request #39851 from BerriAI/litellm_fix_realtime_backend_close_hang 2026-09-05 09:51:15 -07:00
repositories fix(router): serialize heuristic tuning quota filters for Prisma 2026-09-07 10:08:19 -07:00
rerank_api fix(rerank): adopt declared authenticating providers in arerank instead of resolving them 2026-09-01 14:47:59 -07:00
responses Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_lit_4116_drop_params_string_coerce 2026-09-08 15:08:38 -07:00
router_strategy Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_non_reasoning_tier 2026-09-08 15:49:53 -07:00
router_utils fix(router): count allowed_fails in the shared router cache so multi-worker proxies bench a deployment fleet-wide (#40224) 2026-09-08 13:54:48 -07:00
rust_bridge refactor(rust): remove per-request enablement arguments (#39928) 2026-09-07 10:43:45 -07:00
sandbox
search fix(search): forward search-tool params through the router, complete Parallel AI v1 param mapping (#37883) 2026-09-01 21:46:46 -07:00
secret_managers chore: merge origin/litellm_internal_staging into litellm_decrease_anys_opus5_r4 2026-09-04 18:59:35 -07:00
skills feat(skills): semantic search over the LiteLLM-hosted skill registry (#39401) 2026-09-07 12:28:38 -07:00
types Merge pull request #40180 from BerriAI/litellm_lit_4352_marengo_embed_3 2026-09-08 16:38:07 -07:00
vector_store_files refactor(typing): replace Any with proven types in 42 more backend files 2026-09-02 15:35:01 +00:00
vector_stores Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_python_version_ci 2026-09-03 00:16:19 -07:00
videos refactor(videos): make video upload param keyword-only on public edit fns 2026-08-24 15:55:07 -07:00
__init__.py feat(otel v2): send a key's or team's whole trace to its own destination (#39654) 2026-09-08 16:23:21 -07:00
_internal_context.py
_lazy_imports.py fix(lazy_imports): type import_map as Mapping to stay under the LIT001 budget 2026-09-05 16:12:45 -07:00
_lazy_imports_registry.py Revert "perf: lazy-load SDK symbols so import litellm stays under 60 MB RSS (…" 2026-09-05 16:07:09 -07:00
_logging.py fix(proxy): stop leaking internal exception details to clients (#39380) 2026-09-02 17:32:00 -07:00
_redis.py fix(redis): coerce env var string types and fix param discovery through decorator wrappers (#30644) 2026-08-31 20:51:31 -07:00
_redis_credential_provider.py refactor(typing): replace Any with proven types in 89 more backend files 2026-09-02 23:08:42 +00:00
_service_logger.py chore: merge origin/litellm_internal_staging into litellm_decrease_anys_opus5_r4 2026-09-04 18:59:35 -07:00
_uuid.py
_version.py
anthropic_beta_headers_config.json
anthropic_beta_headers_manager.py
blog_posts.json
budget_manager.py
constants.py Merge pull request #40275 from BerriAI/litellm_lit6852_spend_attribution 2026-09-08 19:05:43 -07:00
cost.json
cost_calculator.py Merge pull request #40189 from BerriAI/litellm_lit_3157_azure_ai_catalog_models 2026-09-08 20:08:40 -07:00
exceptions.py fix(bedrock): keep x-amzn-RequestId on chat error responses (#40089) 2026-09-07 17:16:47 -07:00
main.py Merge pull request #40189 from BerriAI/litellm_lit_3157_azure_ai_catalog_models 2026-09-08 20:08:40 -07:00
model_prices_and_context_window_backup.json Merge pull request #40189 from BerriAI/litellm_lit_3157_azure_ai_catalog_models 2026-09-08 20:08:40 -07:00
policy_templates_backup.json
provider_endpoints_support_backup.json fix(ocr): send each provider a health-check document it accepts 2026-09-04 22:52:12 -07:00
py.typed
router.py fix(router): move retry-policy retries off the refusing deployment on every router entrypoint (#40306) 2026-09-08 15:54:11 -07:00
scheduler.py
setup_wizard.py feat(models): add Claude Fable 5.1 across Anthropic, Bedrock, Vertex AI, and Azure AI 2026-09-01 18:07:06 +00:00
timeout.py
utils.py Merge pull request #40180 from BerriAI/litellm_lit_4352_marengo_embed_3 2026-09-08 16:38:07 -07:00