Skip to content

chore(release): backport #44066 to rc/1.104.0 - #44216

Merged
yuneng-berri merged 7 commits into
rc/1.104.0from
litellm_chore_e3afa7
Oct 2, 2026
Merged

yuneng-berri merged 7 commits into
rc/1.104.0from
litellm_chore_e3afa7

Conversation

@yuneng-berri

@yuneng-berri yuneng-berri commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Backport of #44066 (merged on main) to rc/1.104.0 with git cherry-pick -x, plus lock-only dependency bumps and the rebuilt Admin UI bundle. No litellm version bump, since 1.104.0 has not been released

Problem this solves:

How it solves it:

  • Picks feat(mcp)!: disable stdio MCP servers by default #44066: stdio MCP is off unless LITELLM_ENABLE_MCP_STDIO=true is set in the process env
  • Rebuilds the committed UI bundle so the MCP Servers page shows the stdio state
  • Lock-only bumps: pyjwt 2.15.0, pypdf 6.19.0, tornado 6.5.9, urllib3 2.8.0, gitpython 3.1.62

Intentional product change: proxies on 1.104.0 that run stdio MCP servers stop starting them until the admin sets LITELLM_ENABLE_MCP_STDIO=true and restarts. The proxy logs one warning per affected server naming the env var

User Flow

Before: an admin with a stdio MCP server in config.yaml upgrades to 1.104.0 and stdio keeps running, with no way to turn it off

  1. GET /mcp-rest/tools/list?server_id= returns ["echo"]
  2. POST /mcp-rest/tools/call for echo_stdio-echo returns echo: hi
  3. POST /v1/mcp/server with "transport": "stdio" creates a new stdio server

After: the same admin upgrades without setting the env var, and stdio stays off until they opt in

  1. GET /mcp-rest/tools/list?server_id= returns [], and POST /mcp-rest/tools/call returns 403 naming LITELLM_ENABLE_MCP_STDIO=true
  2. POST /v1/mcp/server with "transport": "stdio" returns 422 with the same message
  3. They set LITELLM_ENABLE_MCP_STDIO=true, restart, and steps 1 to 3 of Before work again

What is included

  • feat(mcp)!: disable stdio MCP servers by default #44066 feat(mcp)!: disable stdio MCP servers by default
  • chore(deps): bump pyjwt to 2.15.0
  • chore(deps): bump pypdf to 6.19.0
  • chore(deps): bump tornado to 6.5.9
  • chore(deps): bump urllib3 to 2.8.0
  • chore(deps): bump gitpython to 3.1.62
  • chore: update Next.js build artifacts (built on this line with node v24.19.0)

Each dependency commit moves only its own package version, checked by parsing the lock before and after, and uv lock --check with the line's uv 0.11.7 passes on the tip. main already resolves every one of them at or above these versions. The pyjwt commit reuses the minimal lock edit from #44156. The pypdf commit also carries about 190 lines of resolver marker reshuffling that any upgrade relock of this lock produces; no other package version changes

Adaptation notes

#44066 changes the same set of lines as upstream; only context and paths differ. The conflicts were all neighbours this line does not have: imports (tool_catalog_guard, getMCPNetworkAccess, AgentResponse), two ProxyConfig attributes, a LITELLM_LICENSE line in run_integration.sh, and upstream-only neighbour tests in three test files. For those test files only the PR's own test functions were applied, checked by name against the source. Unit tests live under tests/test_litellm/ on this line instead of tests/unit/. One addition: MCPServerCard.test.tsx gets import userEvent from "@testing-library/user-event", which upstream already had from an earlier PR and the pick's new tests use

Known noise on this line

test_rest_endpoints.py::TestListToolsRestAPI::test_single_server_response_includes_paginated_upstream_tools failed once under -n 8 and passed in 3 isolated runs, 2 file runs and a full rerun. It uses an HTTP server and does not touch stdio. The review also saw the pick's prompts-listing skip test fail 1 time in 3, when an unclosed aiohttp session warning leaked into its no-warning assertion. That assertion is the same on main

Screenshots / Proof of Fix

Setup: the proxy runs from this line's own venv under env -i against a fresh Postgres 16 container, with STORE_MODEL_IN_DB=True. echo_stdio is a stdio server in config.yaml running a one-tool MCP server that echoes its input. K is the master key and P is the proxy address

Before (5663d07)

stdio server

  1. curl -s "$P/mcp-rest/tools/list?server_id=$SID" -H "Authorization: Bearer $K" | jq -c '[.tools[].name]'
    ["echo"]
    
  2. curl -s -w ' HTTP %{http_code}' $P/mcp-rest/tools/call -H "Authorization: Bearer $K" -H 'Content-Type: application/json' -d '{"server_id":"<SID>","name":"echo_stdio-echo","arguments":{"text":"hi"}}'
    {"_meta":null,"content":[{"type":"text","text":"echo: hi","annotations":null,"_meta":null}],"structuredContent":{"result":"echo: hi"},"isError":false,"resultType":"complete"} HTTP 200
    

creating a stdio server

  1. curl -s $P/v1/mcp/server -H "Authorization: Bearer $K" -H 'Content-Type: application/json' -d '{"server_name":"stdio_base","transport":"stdio","command":"python","args":["echo_server.py"]}' | jq -c '{server_name,transport}'
    {"server_name":"stdio_base","transport":"stdio"}
    

UI config

  1. curl -s $P/.well-known/litellm-ui-config | jq -c '{mcp_stdio_enabled}'
    {"mcp_stdio_enabled":null}
    

chat completion

  1. curl -s $P/v1/chat/completions -H "Authorization: Bearer $K" -H 'Content-Type: application/json' -d '{"model":"gpt-5.4-mini","messages":[{"role":"user","content":"Say OK"}]}' | jq -c '{model,content:.choices[0].message.content}'
    {"model":"gpt-5.4-mini","content":"OK"}
    

After (123be8b)

stdio server

Flag unset:

  1. Proxy boot log
    LiteLLM:WARNING: stdio_gate.py:28 - MCP server 'echo_stdio' will not start: stdio MCP servers are disabled on this proxy. Set LITELLM_ENABLE_MCP_STDIO=true on the proxy and restart to enable them
    
  2. Same tools/list call
    []
    
  3. Same tools/call
    {"detail":"stdio MCP servers are disabled on this proxy. Set LITELLM_ENABLE_MCP_STDIO=true on the proxy and restart to enable them"} HTTP 403
    

With LITELLM_ENABLE_MCP_STDIO=true, and no "will not start" lines in the boot log:

  1. Same tools/list call
    ["echo"]
    
  2. Same tools/call
    {"_meta":null,"content":[{"type":"text","text":"echo: hi","annotations":null,"_meta":null}],"structuredContent":{"result":"echo: hi"},"isError":false,"resultType":"complete"} HTTP 200
    

creating a stdio server

  1. Flag unset, same create call with "server_name":"stdio_off"
    {"detail":[{"type":"value_error","loc":["body"],"msg":"Value error, stdio MCP servers are disabled on this proxy. Set LITELLM_ENABLE_MCP_STDIO=true on the proxy and restart to enable them",...}]} HTTP 422
    
  2. Flag on, same call with "server_name":"stdio_on"
    {"server_name":"stdio_on","transport":"stdio"}
    

UI config

  1. Flag unset
    {"mcp_stdio_enabled":false}
    
  2. Flag on
    {"mcp_stdio_enabled":true}
    

flag set only in config.yaml

  1. Flag unset in the process env, with environment_variables: {LITELLM_ENABLE_MCP_STDIO: "true"} in config.yaml. Boot log:
    LiteLLM Proxy:WARNING: proxy_server.py:5951 - Ignoring LITELLM_ENABLE_MCP_STDIO set in the config file. Set it in the proxy's environment instead
    
  2. ui-config returns {"mcp_stdio_enabled":false}, and the same tools/call returns the message with HTTP 403

chat completion

  1. Same call, with the flag unset and again with it on
    {"model":"gpt-5.4-mini","content":"OK"}
    

supporting checks

  • Targeted tests: the 7 mapped Python files and 3 JWT auth files pass (2055 passed). The PR's 6 vitest files pass (237 tests)
  • Wider run of the proxy _experimental, management_endpoints, auth, discovery_endpoints and proxy_server test dirs: 12584 passed on this branch against 12558 on the base, with 0 new failures. One failure is present on both (test_team_default_params.py::test_nonexistent_default_organization_returns)
  • Adversarial review (deep gauntlet, 20 agents): all four claims survived with no verified findings. The checked claims: every symbol resolves, the pick behaves as described, no rc-line caller breaks, and only the five planned lock versions move
  • CircleCI does not run on this rc line, which is why the wider unit run above was done locally

Type

🆕 New Feature

Caveats (if any)

Severe

  • Breaking on 1.104.0: existing stdio MCP servers stop starting after upgrade
    • Fix: set LITELLM_ENABLE_MCP_STDIO=true on the proxy and restart

Low

  • With the flag off, a PUT on an existing stdio server that omits transport is accepted and saves command/args edits. Nothing spawns while the flag is off. Upstream feat(mcp)!: disable stdio MCP servers by default #44066 behaves the same way
  • The pypdf lock commit carries resolver marker churn; versions are unchanged
  • .circleci/scripts/run_integration.sh sets the flag so this line's stdio integration tests keep running

* feat(mcp)!: disable stdio MCP servers by default

stdio MCP servers now only run when the proxy is started with
LITELLM_ENABLE_MCP_STDIO=true. While it is off, existing stdio servers stay
registered but never start: tool listings skip them quietly, direct tool
calls and health checks return a 403 naming the env var, and creating or
updating a stdio server is rejected. The flag is read from the process
environment only, so DB-stored environment_variables cannot turn it on.

The UI reads mcp_stdio_enabled from /.well-known/litellm-ui-config to grey
out the stdio transport, show a banner on stdio forms, and badge stdio
server cards.

BREAKING CHANGE: stdio MCP servers are off by default. Set
LITELLM_ENABLE_MCP_STDIO=true in the proxy environment and restart to keep
using them.

* fix(mcp): ignore stdio flag from config file and read UI flag from the selected worker

LITELLM_ENABLE_MCP_STDIO set under environment_variables in config.yaml is now skipped like the DB-stored value, so only the process environment can enable stdio. The dashboard reads mcp_stdio_enabled from the proxy it is managing, so a control plane shows each worker's own setting.

* test(mcp): cover non-mapping payloads in the shared transport validator

* fix(mcp): skip blocked stdio servers quietly in every listing and keep the UI unchanged until the flag loads

Prompt, resource and resource-template listings now skip a blocked stdio server at debug level like tool listing does, instead of logging a warning per server on every call. The dashboard only treats stdio as disabled once the proxy explicitly reports mcp_stdio_enabled false, so a proxy with the flag on, or an older one without the field, renders exactly as before with no flicker while loading.

* fix(mcp): route blocked stdio tool calls to the flag error and warn once per server

A gateway tools/call naming a blocked stdio server's tool now returns the
LITELLM_ENABLE_MCP_STDIO message instead of "Tool not found".

The "will not start" warning moves out of build_mcp_server_from_table, which
DB reload re-runs on every cycle for rows with a NULL updated_at and which
drafts and test-connection also call. It now fires when a row first enters
the registry or changes transport.

* fix(ui): explain on the server detail page why a stdio server is inert

The Overview and MCP Tools tabs showed "No tools available" with no reason
while stdio is disabled. The detail page now shows the same warning banner
as the edit form, and hands off to the form's banner once editing starts.

* refactor(ui): name the stdio banner conditions on the server detail page

Keeps local/no-long-condition-chain within its budget

* fix(proxy): log the ignored DB-stored LITELLM_ENABLE_MCP_STDIO warning once

The DB config sync re-reads environment_variables on every cycle, so a stored
flag logged the warning on each sync per worker

(cherry picked from commit 2b85808)
@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Backport adds MCP stdio transport gating and rebuilds dashboard assets.

The PR appears safe to merge, with non-blocking dashboard availability-state issues worth correcting.

Findings

  1. P2 Missing status enables stdio ▶
  2. P2 Stdio status survives worker switches ▶

Summary

The PR makes proxy-hosted stdio MCP servers opt-in through a process environment variable, exposes that state to the dashboard, rebuilds the dashboard bundle, and updates five locked dependencies.

  • The proxy gates stdio client creation, listings, calls, and management validation.
  • The dashboard indicates when stdio is unavailable, but its availability query can present missing or stale worker data as the current state.

Reviews (1) · Last reviewed commit: "chore: update Next.js build artifacts (2..."

Comment on lines +12 to +13
export const useMcpStdioEnabled = (): boolean =>
$api.useQuery("get", "/.well-known/litellm-ui-config").data?.mcp_stdio_enabled !== false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing status enables stdio
If the UI-config request fails or omits mcp_stdio_enabled, this expression treats stdio as enabled. On a proxy where stdio is off, the dashboard offers stdio without a warning and lets an admin submit a configuration the proxy will reject. Treat an unavailable setting as unknown or disabled.

Knowledge Base Used: Dashboard and enterprise UI

"stdio MCP servers are disabled on this proxy. Set LITELLM_ENABLE_MCP_STDIO=true on the proxy and restart to enable them";

export const useMcpStdioEnabled = (): boolean =>
$api.useQuery("get", "/.well-known/litellm-ui-config").data?.mcp_stdio_enabled !== false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stdio status survives worker switches
The request target changes when an operator selects another worker, but this query keeps the same cache key and worker selection does not refresh it. An already-open MCP page can therefore show the previous worker’s stdio setting, hiding an available option or offering one the current worker will reject. Include the worker in the query key or refresh the setting when workers change.

Knowledge Base Used: Dashboard and enterprise UI

@yuneng-berri
yuneng-berri merged commit fb90b66 into rc/1.104.0 Oct 2, 2026
11 of 12 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_chore_e3afa7 branch October 2, 2026 19:04

This branch is waiting to be deployed

1 waiting deployment
e2e-changed — 123be8b1 Waiting Oct 2, 2026 by yuneng-berri via oauth #2621
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant