Skip to content

feat(proxy): embed admin MCP behind enterprise license and opt-in - #43881

Closed
tin-berri wants to merge 13 commits into
mainfrom
litellm_embedded_admin_mcp
Closed

tin-berri wants to merge 13 commits into
mainfrom
litellm_embedded_admin_mcp

Conversation

@tin-berri

@tin-berri tin-berri commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Admin MCP currently requires a separate local or hosted process

How it solves it:

  • Bundle a pinned connector in both LiteLLM Docker images
  • Require a base enterprise license and explicit opt-in
  • Authenticate every caller through the existing management API

Intentional product change: hosting requires a valid base enterprise license and LITELLM_ENABLE_ADMIN_MCP=true. An unlicensed opt-in prevents startup. The flag accepts true/false, 1/0, on/off and yes/no; any other value fails startup. Enabling it reserves /admin, including an MCP server alias named admin. Disabled deployments need no license

User Flow

Before: an operator must deploy the admin connector separately

  1. Deploy LiteLLM with database-backed admin authentication and a base enterprise license
  2. Set LITELLM_LICENSE and LITELLM_ENABLE_ADMIN_MCP=true, then restart
  3. Requests to /admin/mcp return 404

After: the licensed deployment serves the admin connector on its existing port

  1. Deploy LiteLLM with database-backed admin authentication and a base enterprise license
  2. Set LITELLM_LICENSE and LITELLM_ENABLE_ADMIN_MCP=true, then restart
  3. Connect to /admin/mcp with a personal proxy-admin bearer key
  4. Discover admin tools and manage keys, teams, models and budgets

Implementation and validation

A deep gauntlet run found that valid non-admin keys with no user row got a retryable 503 instead of 403. That covers team keys, bare keys and service-account keys. /user/info returns 404 for them, and the connector mapped every status other than 401 or 403 to 503. The re-pinned connector treats that 404 as a non-admin caller. A team-key case in the authorization test fails with 503 on the old pin and passes on the new one. Live QA against a real proxy and Postgres now returns 403 for team, bare and service-account keys, and admins still list tools. The batch endpoint test file now matches main, which repaired the same helper in #43958

The connector is pinned to upstream commit d35ec9c19c117d4c50cc1eccf6ce1296aac25a1a, the merge of liteadmin-mcp#3, and its archive checksum in the admin-mcp dependency group. Both Docker build paths install that group without changing existing dependency versions. The base SDK keeps its existing Python support; the connector requires Python 3.12+

The existing base enterprise check runs before loading the connector and on each incoming MCP request. No additional feature entitlement is required. Requests use the proxy's current validated enterprise status; this does not add an independent license refresh mechanism. The existing private helper remains an alias for compatibility

Management requests stay in process and retain the caller's credential, source address, scheme and policy headers. Browser cookies and shared connector credentials are excluded. The ASGI adapter uses the upstream app and standard httpx2 transport; docker/README.md explains why the existing outbound httpx handler cannot supply this transport. Full results are the embedded default; explicit compact results require worker affinity

The proxy owns shutdown ordering: pause scheduled jobs, drain requests, close the connector, then clean up shared resources. Cleanup still runs on connector startup, serving, cancellation or teardown failures. Only management calls explicitly linked to an active parent share its admission slot. Unrelated requests and calls using an expired or another worker's lease acquire their own slots. Invalid trusted origins fail startup

The licensing and dependency validation changes pass 54 focused tests for enterprise licensing and dependency-license validation. Three mutations that drop dependency groups, truncate URL hashes or allow parsing errors to pass are caught. The full dependency-license audit and native runtime/stub check pass. make check passes, changed workflow YAML parses, and no lint, type, test-quality or coverage budgets changed

The earlier regression coverage remains in place for one-slot operation, unrelated and expired admission leases, concurrent callers, URL prefixes, missing optional dependencies, cancellation, drain deadlines, scheduler ordering and startup cleanup. The trace tests, native stub and batch endpoint tests now match main, which repaired them in #43942 and #43958. make check passes on the current head with unchanged budgets

The dependency-license checker now scans all dependency groups, preserves pinned URL hashes and markers, and fails when requirements cannot be parsed. The connector's MIT license was verified at its pinned upstream source and recorded through the existing license-verification configuration

New advisories published during CI affected GitPython 3.1.61 and Tornado 6.5.8. Their constraints and lock entries now use fixed releases 3.1.62 and 6.5.9, both outside the existing release cooldown. Unrelated package versions are unchanged. Frozen resolution, real imports, 63 focused licensing, dependency-checker and MLflow tests, and the full dependency-license audit pass. The pinned OSV scanner in UTC confirms those four advisories are cleared; diskcache still has no fixed release, and its existing exception expired on October 1. No security exception was added or extended

Coverage uploads used a retired Codecov signing-key URL that now returns 404. All coverage actions now use the pinned v7.1.1 release and CLI v11.3.1. CI verified the replacement key signature, checksum and successful upload on 2c42ab9. Signature verification remains enabled; the PostgreSQL upload still fails CI on upload errors

On 8b575b8, the first actual standard Docker image passes all seven offline non-root tests, including all three license cases. MCP integration, all four isolated OAuth cases, native wheel tests and coverage uploads pass. Patch coverage is 86.71% against 81.70%. The second standard image build and its Grype scan are still running

Current-commit live QA passed against a disposable PostgreSQL database with one admission slot and no queue. Disabled and unlicensed returns 404; enabling without a license rejects startup. An RSA-signed base test license with an isolated test issuer passes real signature verification without extra feature claims. That deployment rejects anonymous and member callers, initializes MCP, discovers 67 tools, creates a team, verifies it through the management API and deletes it. Both running proxy cases finish shutdown cleanly

Greptile reviewed 8b575b8 at 00:48 UTC and returned 5/5 with no new actionable findings. Its Veria check reports success with "No security-relevant changes detected". Bugbot reviewed this exact commit at 00:49 UTC and found no new issues

Pre-Submission checklist

  • I have added meaningful tests
  • The focused test files pass locally
  • My PR passes all required CI/CD checks
  • My PR's scope is isolated to one feature
  • The current commit has a fresh Greptile review of at least 4/5

Screenshots / Proof of Fix

Local QA used a disposable PostgreSQL database and real LiteLLM admin/member keys. No LLM inference is involved. The licensed case used real RSA verification with a test-only issuer and signed base license, not a production-issued license. No premium result was mocked. To reproduce normally, supply a valid LITELLM_LICENSE and enable LITELLM_ENABLE_ADMIN_MCP=true on port 4097. Set ADMIN_KEY and MEMBER_KEY to personal keys created through /user/new; neither value belongs in logs

Before (6fd9334)

  1. Send POST http://127.0.0.1:4097/admin/mcp with {"jsonrpc":"2.0","id":1,"method":"tools/list"}
  2. Observe 404 and {"detail":"MCP server, toolset, or access group 'admin' not found"}

After (8b575b8)

  1. With the base license and enable flag, send the same anonymous request and observe 401; use the member key and observe 403

  2. Discover tools with an admin credential:

    curl -sS http://127.0.0.1:4097/admin/mcp \
      -H "Authorization: Bearer $ADMIN_KEY" \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
      | jq '{tools: (.result.tools | length)}'

    Observed: {"tools":67}

  3. Create a team through the MCP endpoint:

    curl -sS http://127.0.0.1:4097/admin/mcp \
      -H "Authorization: Bearer $ADMIN_KEY" \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_team","arguments":{"body":{"team_id":"embedded-admin-mcp-qa","team_alias":"embedded-admin-mcp-qa"}}}}' \
      | jq '.result.content[0].text | fromjson | {team_id,team_alias}'

    Observed: {"team_id":"embedded-admin-mcp-qa","team_alias":"embedded-admin-mcp-qa"}

  4. Verify the team through GET /team/info?team_id=embedded-admin-mcp-qa with the same admin key; observe the matching ID and alias. Delete it through POST /team/delete with {"team_ids":["embedded-admin-mcp-qa"]}; observe 200

  5. Restart with the flag off and no license; send the same POST /admin/mcp request and observe 404

  6. Restart with the flag on and no license; observe the enterprise-license error naming LITELLM_LICENSE and startup rejection

Type

New Feature

Caveats

Severe

  • Unlicensed opt-in prevents the whole proxy from starting

Medium

The documentation and code-quality checks consume the separate documentation repository; current CI still lacks documentation for CLICKHOUSE_FLUSH_INTERVAL_SECONDS, covered by the pending fix. The remaining known test failures are four Google Interactions schema/path cases. OSV remains blocked on the existing diskcache advisory. The second image build and security scan are pending; no tests, coverage targets, lint budgets or security suppressions were relaxed

Low

  • Explicit compact result reads require the same worker

Final Attestation

  • Tests cover licensing, enablement, authorization, caller isolation, lifecycle, read-only mode and result completeness

@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.71875% with 17 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/proxy_server.py 69.38% 15 Missing ⚠️
litellm/proxy/admin_mcp.py 96.87% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_embedded_admin_mcp (bce2c79) with main (0980f75)

Open in CodSpeed

@tin-berri
tin-berri marked this pull request as ready for review September 30, 2026 19:30
@tin-berri
tin-berri requested a review from a team September 30, 2026 19:30
@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[High risk] Adds enterprise-gated admin MCP feature with new dependency.

The PR appears safe to merge based on the reviewed changes and the resolved previous findings.

Summary

The PR embeds an opt-in, enterprise-gated admin MCP connector in the proxy. The latest changes update the GitPython and Tornado dependency constraints and locked versions; no new actionable issue was established.

Reviews (9) · Last reviewed commit: "fix(deps): patch GitPython and Tornado a..."

Comment thread litellm/proxy/proxy_server.py Outdated
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review the latest commit: proxy cleanup now survives connector failures, and admin MCP tests run automatically in CI

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review the latest commit, including connector failure cleanup, authentication, and the newly selected CI coverage

Comment thread litellm/proxy/admin_mcp.py
Comment thread tests/unit/proxy/test_proxy_server.py

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

Comment thread litellm/proxy/proxy_server.py Outdated
Comment thread litellm/proxy/admin_mcp.py
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review c2efee7: documented transport requirements, isolated shutdown state, preserved request draining, and fixed nested admission with regression coverage

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review c2efee7: requests drain before connector teardown, and nested management calls share an admission slot with bounded lifetime

@cursor

cursor Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 014c3e0: centralized shutdown, explicit admission leases, and expanded lifecycle, cancellation, configuration, and offline image regression coverage

@cursor

cursor Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review 014c3e0: centralized shutdown, explicit admission leases, caller isolation, cancellation, drain deadlines, and offline image enablement

@cursor

cursor Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@tin-berri tin-berri changed the title feat(proxy): embed admin MCP behind an opt-in flag feat(proxy): embed admin MCP behind enterprise license and opt-in Sep 30, 2026
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 82489ac, including base enterprise licensing, startup cleanup, request enforcement, and Docker probes; target confidence is 5/5

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review 82489ac for licensing bypasses, startup failures, request authorization, and lifecycle regressions in the embedded admin MCP

@cursor

cursor Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 6334cae after syncing main and replacing the batch request fixture; verify enterprise licensing and lifecycle safety

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review 6334cae for enterprise license bypasses, lifecycle regressions, and integration issues after the main update

@cursor

cursor Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review this updated commit, especially dependency license validation, native signature parity, and coverage upload integrity

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review this updated commit for dependency license validation, native signatures, and coverage upload integrity regressions

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

@tin-berri
tin-berri deployed to e2e-changed October 1, 2026 00:32 — with GitHub Actions Active
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 390ea37, including named trace queries, receipt timestamp validation, enterprise gating, and the complete updated diff

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review 390ea37 for regressions in named trace queries, receipt timestamps, enterprise licensing, and the updated diff

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

@tin-berri
tin-berri deployed to e2e-changed October 1, 2026 00:45 — with GitHub Actions Active
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 8b575b8, including GitPython and Tornado security updates, unchanged scanner exceptions, enterprise gating, and the complete diff

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review 8b575b8 for regressions in the GitPython and Tornado updates, enterprise licensing, and the complete diff

@cursor cursor Bot left a comment

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 8b575b8. Configure here.

tin-berri and others added 2 commits September 30, 2026 19:33
A deep gauntlet run found that valid non-admin keys with no user row
(team, bare and service-account keys) got a retryable 503 from
/admin/mcp instead of 403. /user/info returns 404 for them and the
connector mapped that to 503. Re-pin the connector to
BerriAI/liteadmin-mcp a083adc, which treats that 404 as a non-admin
caller, and add a team-key case to the authorization test.

Merge origin/main and resolve the uv.lock conflict by taking main's
tornado 6.5.10, which satisfies this branch's >=6.5.9 floor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n_mcp

# Conflicts:
#	tests/test_litellm/proxy/batches_endpoints/test_endpoints.py
…erge main

Re-pin litellm-admin-mcp to BerriAI/liteadmin-mcp d35ec9c, the merge of
#3 that returns 403 for userless keys. LITELLM_ENABLE_ADMIN_MCP=off or no
previously aborted proxy startup; on/off and yes/no now parse, and typos
still fail startup.

Merge origin/main. tests/test_litellm_rust/test_traces.py takes main's
version, which #43942 rewrote alongside the trace runtime it covers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch is waiting to be deployed

1 waiting deployment
e2e-changed — bce2c79c Waiting Oct 1, 2026 by tin-berri via oauth #2280
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