Skip to content

feat(mcp): track MCP tool versions, changelogs and deprecations - #44245

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
litellm_mcp_tool_versions
Open

devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
litellm_mcp_tool_versions

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Re-pinning an MCP server overwrote the previous tool list with no history
  • Admins could not see versions, changelogs, deprecations or breaking changes

How it solves it:

  • Every pin records a numbered version per changed tool, with a changelog note
  • Each version is classified as initial, non-breaking, breaking or removed, with per-change summaries
  • Admins can mark any version deprecated with a sunset date and note
  • New "Tool versions" panel on the server's MCP Tools tab shows all of it

User Flow

Before: an admin re-pins a server whose upstream tools changed, and the old tool definitions are gone

  1. Admin opens http://localhost:4000/ui/mcp-servers, picks the server, opens the MCP Tools tab
  2. They POST /v1/mcp/server/{id}/pin after the upstream changed; it returns 200 with only the new tool list
  3. GET /v1/mcp/server/{id}/tool-versions returns 404, and nothing on the page shows versions or what changed
  4. There is no way to mark the old definition deprecated or give callers a sunset date

Before: MCP Tools tab has no version information

After: the same re-pin records a new version and the page shows what changed and what is deprecated

  1. Admin opens the same MCP Tools tab and sees a "Tool versions" panel under the tool list
  2. They type a changelog and click "Pin and record versions"; each tool shows its current version and a Breaking, Non-breaking or Removed badge
  3. Expanding a tool lists every version newest first, each change marked Breaking or not, plus the changelog
  4. Clicking "Deprecate" on a version and saving a sunset date shows "Deprecated" and "Sunset" on that version; "Edit deprecation" changes the sunset or note and keeps the original deprecated date

After: Tool versions panel after a UI pin

After: get_forecast history with breaking changes, changelogs and a deprecated v1

After: v3 deprecated from the UI with sunset 2027-01-15

Linear ticket

Resolves LIT-9138

Root cause

The pin path stored only the latest tool list in one JSON column on the server row and replaced it on every pin, so earlier definitions, what changed between them, and any deprecation metadata had nowhere to live.

flowchart LR
  A[POST /pin, optional changelog] --> B[fetch upstream tools/list]
  B --> C[open one writer transaction, take per-server advisory lock]
  C --> D[read version history]
  D --> E[classify each tool vs its latest version]
  E --> F[insert new version rows]
  F --> G[update pinned_tools snapshot]
  G --> H[commit, then publish the server config change]
Loading

Classification rules (tool_versioning.py): description-only edits, new optional params, params becoming optional and param description edits are non-breaking; removed params, new required params, type or schema changes, newly required params (including names listed only in required), top-level schema changes and tool removal are breaking; top-level schema description and title edits are non-breaking. A tool that comes back after removal gets a new version marked "Tool restored", breaking if its schema also changed. Unchanged tools record nothing, and unpinning records nothing.

Files changed

File Change
schema.prisma, litellm/proxy/schema.prisma, litellm-proxy-extras/litellm_proxy_extras/schema.prisma New LiteLLM_MCPToolVersion model, unique on (server_id, tool_name, version)
litellm-proxy-extras/.../20261002000000_mcp_tool_versions/migration.sql Additive, idempotent CREATE TABLE/INDEX IF NOT EXISTS
litellm/types/mcp_server/mcp_server_manager.py MCPToolVersion, MCPToolChange, MCPToolChangeKind, pin and deprecation request models
litellm/proxy/_experimental/mcp_server/tool_versioning.py Pure classifier and version planner, no proxy imports; description/title are ignored at every nested schema position (never as property names), so documentation-only edits are non-breaking
litellm/proxy/_experimental/mcp_server/db.py Pin takes a per-server advisory lock and records versions plus the new snapshot in one writer transaction, publishing the config change after commit; list helper; deprecation reads and updates the row in one writer transaction so replica lag cannot 404 or reset deprecated_at; best-effort cleanup on server delete
litellm/integrations/otel/model/spans.py Registers LiteLLM_MCPToolVersion as a renderable Prisma span table
litellm/repositories/table_repositories.py MCPToolVersionRepository
litellm/proxy/management_endpoints/mcp_management_endpoints.py Pin accepts optional {"changelog"} body; GET .../tool-versions filtered by the caller's key/team tool permissions; admin-only PUT/DELETE .../tools/{tool}/versions/{n}/deprecation; 409 on a version-number conflict
litellm/proxy/_lazy_openapi_snapshot.json Regenerated
ui/litellm-dashboard/src/components/networking.tsx Typed calls for list, pin with changelog and the viewer's upstream headers, set and clear deprecation
ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPToolVersionsPanel.tsx New panel: current-version badges, history, change summaries, changelog, admin deprecate, edit and remove deprecation controls
ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_tools.tsx Renders the panel under the tools viewer when canManageVersions is set, passing the same x-mcp-* headers the viewer uses for tools/list
ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_server_view.tsx Passes canManageVersions to the tools viewer
ui/litellm-dashboard/src/components/mcp_tools/types.tsx canManageVersions prop on the viewer
ui/litellm-dashboard/src/lib/http/schema.d.ts Regenerated with npm run gen:api

Pre-Submission checklist

  • I have added meaningful tests
  • The handful of test files covering my change pass locally (1710 passed across the four mapped MCP test files; 91 dashboard tests across the four touched dashboard test files)
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review

Screenshots / Proof of Fix

Shared setup: real Postgres (lit9138), proxy on :4000 with PYTHONPATH pointed at each checkout, a local HTTP MCP upstream on :8765 serving catalog.json. catalog_v1.json has get_forecast(city, days:int), get_alerts(region), get_radar(city). catalog_v2.json changes days to string and adds required units on get_forecast, adds optional severity and a new description on get_alerts, and drops get_radar. The same files are used on both sides. Build marker: tool_versioning.plan_tool_versions is ABSENT on the base checkout and present on the head checkout.

Before (merge base)

  1. Upstream serves v1, POST /v1/mcp/server/$S/pin returns 200 with three tools
  2. Upstream serves v2, POST /v1/mcp/server/$S/pin -d '{"changelog": "Weather API 2.0"}' returns 200 with two tools; v1 definitions are gone from pinned_tools
  3. GET /v1/mcp/server/$S/tool-versions returns 404 {"detail":"Not Found"}
  4. PUT /v1/mcp/server/$S/tools/get_forecast/versions/1/deprecation -d '{"sunset_date": "2026-12-31T00:00:00Z"}' returns 404
  5. UI: see the Before screenshot under User Flow

After (first round, 6742c15)

  1. Upstream serves v1, pin returns 200; upstream serves v2, pin with {"changelog": "Weather API 2.0"} returns 200
  2. GET /v1/mcp/server/$S/tool-versions returns 200:
get_alerts   v2 non_breaking 'Weather API 2.0' ['Description changed', 'Added optional parameter "severity"']
get_forecast v2 breaking     'Weather API 2.0' ['BREAKING Parameter "days" type changed from "integer" to "string"', 'BREAKING Added required parameter "units"']
get_radar    v2 removed      'Weather API 2.0' ['BREAKING Tool removed']
(plus v1 initial rows for all three)
  1. PUT .../tools/get_forecast/versions/1/deprecation -d '{"sunset_date": "2026-12-31T00:00:00Z", "deprecation_note": "Use v2 with units"}' returns 200 with deprecated_at set, sunset_date 2026-12-31, the note
  2. Edge legs, all on the same server:
    • Re-pin unchanged v2: 200, row count stays 6
    • Second PUT with a new sunset: deprecated_at unchanged, sunset updated, omitted note cleared
    • PUT .../versions/9/deprecation: 404 MCP tool 'get_forecast' version 9 not found for server '...'
    • Non-admin key on PUT: 403 Admin access required to update MCP tool version deprecation.
    • Key without access to the server on GET tool-versions: 403, same message as the server GET
    • Restore v1: v3 rows, get_radar v3 non_breaking "Tool restored", get_forecast v3 breaking (type back to integer, units removed)
    • DELETE .../deprecation: all three deprecation fields null
    • Unpin: 200, row count unchanged at 9
    • Two simultaneous pins of the same changed catalog: both 200, exactly one new version per tool (v5, changelog of the first), no 409, no traceback in the proxy log
  3. UI, driven in Chrome against this proxy: typed a changelog and clicked "Pin and record versions", expanded get_forecast history, deprecated v3 with sunset 2027-01-15 (see the After screenshots under User Flow)

Handler branches exercised: pin with no body, pin with changelog body, pin with unchanged catalog (no rows), pin with removed tool, pin with restored tool, concurrent pins (lock path), unpin; tool-versions GET as admin and as a key without server access; deprecation PUT as admin (new and existing), non-admin, missing version; deprecation DELETE. Not exercised live: a config-only server (GET returns [], covered by unit test) and the 409 version-conflict branch, which the lock now makes unreachable between pins (covered by unit test).

Tests: classifier and planner rules, exact create_many payload with JSON-serialized input_schema/changes, lock acquired before the history read and held until after the snapshot update, endpoint auth and 404/403/409 shapes, whitespace-only changelog becomes null, deprecation set/preserve/clear, cleanup failure does not fail server delete, dashboard badges, history, admin-only controls, loading/empty/error states, sunset payload ${date}T00:00:00Z. Core tests were mutation-checked. The two removed assert lines in test_mcp_management_endpoints.py were replaced with the same assertion plus changelog=None.

After (review round, 06f07ab)

Proxy restarted on the head checkout (MARKER_plan_tool_versions present), same fixtures and server.

  1. Upstream adds "required": ["token"] to get_radar without a token property; pin returns 200 and get_radar v6 is recorded breaking with Parameter "token" is now required
  2. Re-pin unchanged: 200, no new rows
  3. Two simultaneous pins of a changed catalog: both 200, one coherent new get_alerts version
  4. PUT .../tools/get_radar/versions/5/deprecation set, then edited from the UI: deprecated_at stays 2026-10-02T21:14:46.307Z, note updated
  5. GET .../tool-versions with a virtual key whose MCP permissions allow only get_forecast: only get_forecast rows; master key: all three tools

After: get_radar v6 recorded breaking for a required-only change, with Edit deprecation on v5

After: Edit deprecation form prefilled with the current sunset date and note

After: saved edit keeps "Deprecated 2026-10-02" and shows the new note

Tests added this round: pin runs lock, lookup, history read, insert and snapshot update on the transaction client in that order and never on the regular client, publish happens once after commit and not when the update fails; required-only parameter changes both ways; schema description and title changes; history filtered for allowed ["tool_a"], [] and None; dashboard edit prefill, payload and non-admin hiding. Mutants killed: insert routed back through the regular client, publish moved inside the transaction, required-only names dropped from the comparison, history filter removed.

After (review round 2, 7dc6351)

Same proxy and fixture, database lit9138 confirmed in the proxy log and /health/readiness before any write.

  • Nested documentation only: changing filter.properties.term.description inside get_alerts and pinning recorded v8 as non_breaking with the single change Parameter "filter" description changed
  • Deprecation edit on already-deprecated get_forecast v3: two PUTs returned 200 and deprecated_at stayed 2026-10-02T20:12:46.665000Z
  • History auth: a key restricted to get_forecast got 5 rows, all get_forecast; the master key got 17 rows across the three tools
  • Dashboard pin from the panel, now rendered inside the tools viewer, recorded get_alerts v9 after the fixture was restored

After: Tool versions panel under the tools viewer after a dashboard pin

Tests added this round: deprecation lookup and update both go through the transaction client; nested child description, items.description and $defs description edits are non-breaking while nested type and $defs type edits stay breaking, and removing a property literally named title is breaking; pin forwards the viewer's x-mcp-* headers. Mutants killed: deprecation lookup on the regular client, non-recursive documentation stripping, stripping keys inside a properties map.

After (review round 3, bdfbae3)

  • $schema-only change to get_radar pinned as v8 breaking with Input schema changed; restoring it pinned v9 breaking
  • Two concurrent first-deprecation PUTs on get_radar v9 with different notes both returned 200 with deprecated_at 2026-10-02T22:29:51.931000Z, and history shows that one timestamp

Tests added this round: a draft-only $schema change is breaking and planned as a new version; deprecation takes the per-server lock on the transaction before lookup and update. Mutant killed: deprecation without the lock.

After (rebase onto main, c1508c1 and 5f05c78)

Fresh Postgres database lit9138_head (all 200 migrations applied, confirmed in the proxy log), proxy on :4000 with PYTHONPATH at this checkout, same v1 and v2 catalogs served by a local HTTP MCP upstream on :8765

  1. Pin v1 with no body: 200 with three tools, so the old route contract still holds
  2. Pin v2 with {"changelog": "Weather API 2.0"}: 200, then GET .../tool-versions returns 6 rows: get_forecast v2 breaking (days integer to string, new required units), get_alerts v2 non_breaking (description, optional severity), get_radar v2 removed, plus three v1 initial rows
  3. Re-pin unchanged v2: 200 and still 6 rows
  4. Deprecate get_forecast v1 with sunset 2026-12-31: 200; editing the sunset to 2027-01-15 keeps deprecated_at at 2026-10-05T09:08:13.596000Z
  5. Version 9: 404 with the not-found message; a non-admin key: 403 Admin access required to update MCP tool version deprecation.
  6. DELETE deprecation clears all three fields; unpin returns 200 and leaves the 6 rows

The 5f05c78 delta only registers LiteLLM_MCPToolVersion in the OTel Prisma span table list, which a test added on main requires for every Prisma model. The dashboard part of the diff is byte-identical to bdfbae3, so the UI screenshots above still apply

After (review round 4, 31470b0)

Same proxy and database as the rebase run above, restarted on the fixed build

  1. GET /v1/mcp/server/weather_head/tool-versions (the server's name) on 5f05c78: 200 with 0 rows, while the same server by id returned 6
  2. The same name request on the fixed build: 200 with the same 6 rows as the id request
  3. Dashboard: a sunset sent with no UTC offset (2026-12-31T00:00:00) now shows as 2026-12-31 in Asia/Tokyo, both on the badge and in the edit prefill. Covered by a vitest that sets TZ=Asia/Tokyo and fails on the old formatter
  4. DELETE /v1/mcp/server/{id} on the fixed build: 202, the server's 6 history rows are gone, and its tool-versions route now returns 404. If removing the history fails, the server is kept rather than left with orphaned history a reused id could pick up

After (review round 5, 2b37e3997b)

Same proxy and database, restarted on the fixed build, new server weather_r5

  1. Pin v1 then v2: 200, 200, and the history read right after returns all 6 rows (history reads now go to the writer)
  2. PUT /v1/mcp/server/weather_r5/tools/get_forecast/versions/1/deprecation (the server's name, not its id): 200, and the row stored under the server's id carries the note. DELETE on the same name path: 200 and the row has no deprecation left
  3. A pin and a server delete fired at the same time: delete 202, pin 404, 0 history rows and 0 server rows left. History and the server row are now removed in one transaction holding the same per-server lock pins take, so a pin can no longer write history between the two deletes, and a failed delete keeps both
  4. Dashboard: when the history request fails, the error shows above the pin form and the admin can still pin, covered by a vitest that fails on the old early return

After (review round 6, 83bcc0d)

New server weather_r6 on the same proxy and database

  1. After two pins, history by id and by name both return 6 rows
  2. To stand in for a deleted server whose id now belongs to a newer server row, the row's created_at was moved forward on the database while the proxy still held the old row: history by name returned 0 rows instead of the newer server's history. Restoring the timestamp brought back the 6 rows
  3. DELETE /v1/mcp/server/{id}: 202, and its tool-versions route then returns 404

History is read on the writer under the server's pin lock and only if the server row there is still the one the caller was authorized against

After (review round 7, 19d0965)

New server weather_r7 on the same proxy and database, tool get_alerts version 1, deprecation called by server name

  1. PUT .../deprecation with a sunset date and note: 200, row holds use v2 with a sunset date. DELETE: 200, row cleared
  2. With the server row's created_at moved forward on the database while the proxy still held the old row, PUT returned 404 and the row stayed cleared. DELETE also returned 404 and left a note written on the newer row in place
  3. After restoring the timestamp, PUT returned 200 again. No tracebacks in the proxy log

Deprecation writes now run the same writer side identity check as history reads, under the same pin lock

After (rebase onto main, 0091cc4)

Rebased onto main 2c67ae9. The PR's own delta from 19d0965 is two lines of kind: _resolve_and_authorize_mcp_server now runs under @catalog_operation like fetch_mcp_server on main, and the four new models extend LiteLLMBaseModel like the rest of the file on main. The dashboard diff is byte-identical to 19d0965, so the UI screenshots above still apply

Same local HTTP MCP upstream and catalogs in both arms, fresh Postgres per arm. Marker: tool_versioning.py ABSENT on the base worktree, present on head

Base (main 2c67ae9):

  1. Pin v1: 200 with get_alerts, get_forecast, get_radar. Pin v2 with {"changelog": "Weather API 2.0"}: 200 with two tools
  2. GET .../tool-versions: 404 {"detail":"Not Found"}. PUT .../tools/get_forecast/versions/1/deprecation: 404

Head (0091cc4):

  1. Pin v1 then pin v2 with the changelog: both 200, snapshot holds get_alerts, get_forecast
  2. GET .../tool-versions: 200, 6 rows
    • get_forecast v2 breaking, changelog Weather API 2.0: Parameter "days" type changed from "integer" to "string" (breaking), Added required parameter "units" (breaking)
    • get_alerts v2 non_breaking: Description changed, Added optional parameter "severity"
    • get_radar v2 removed: Tool removed (breaking)
    • each tool's v1 is initial with no changelog
  3. Re-pin with the catalog unchanged: 200, still 6 rows
  4. PUT .../get_forecast/versions/1/deprecation sunset 2026-12-31 and note: 200 with deprecated_at set. A second PUT with sunset 2027-01-15: same deprecated_at, new sunset and note
  5. Version 9: 404 MCP tool 'get_forecast' version 9 not found .... Non-admin key: 403 Admin access required to update MCP tool version deprecation.
  6. DELETE .../deprecation: 200, deprecated_at, sunset_date, deprecation_note all null
  7. Stale authorization, as a real race: a psql session holds the server's mcp_pin:{id} advisory lock, the request authorizes against the current server row and waits on the lock, psql moves the row's created_at forward and commits
    • PUT with note stale write: 404 after 3.0s, version row unchanged (Use v2 with units, sunset 2026-12-31)
    • DELETE: 404 after 3.0s, the deprecation on the row is left in place
    • Control, same race with no row change: PUT 200 after 3.1s and the row holds current write, sunset 2027-06-30
  8. No tracebacks in the proxy log

Authorization on this head reads the server row from the database at request start, so moving created_at before sending the request (the round 6 and 7 harness) is no longer stale: on head that request authorizes against the moved row and returns 200. The lock-held race above is what opens the window between authorization and the write

After (review round 8, 5b95c5d)

Greptile P1 on 0091cc4: the history route authorized inside a catalog snapshot but looked up the key's tool grants against the live catalog, so a rename between the two could drop a name-keyed restriction. fetch_mcp_server_tool_versions now runs under @catalog_operation itself, like tool listing. The new test records catalog.current() at authorization and at the grant lookup and asserts the same snapshot; with the decorator removed the grant lookup sees None and the test fails

Proxy restarted on 5b95c5d, new server weather_r8 on the same database

  1. Pin v1, pin v2 with changelog, unchanged re-pin: 200 each, 6 history rows, same kinds and change summaries as the 0091cc4 run
  2. Deprecation set, edit (same deprecated_at), version 9 404, non-admin 403, clear: same results as the 0091cc4 run
  3. A key granted only get_forecast on this server: history 200 with 2 rows, all get_forecast. Master key: 6 rows across the three tools
  4. Lock-held stale race: PUT 404 after 3.1s and DELETE 404 after 3.0s with the version row unchanged, control PUT 200 after 3.1s writes current write
  5. No tracebacks in the proxy log

Review round 9, c16b055

Greptile P2 on 5b95c5d asked for Final bindings and typed callbacks in the new test. The test now records both snapshots through Final MagicMock recorders and typed callbacks with the signatures of authorize_mcp_server and _get_allowed_tool_names_for_server. Test-only change, git diff 5b95c5d102 c16b055209 -- . ':!tests' is empty, so the round 8 live run covers the production code on this head. Mutation check repeated: without the decorator the grant lookup records None and the test fails

Taxonomy audit (A-BB)

  • A1 fixed: pin invalidates the versions, MCP tools and server-list queries
  • B1 fixed: concurrent pins serialize on a per-server advisory lock, and history rows plus the snapshot commit in one writer transaction, so history and snapshot cannot disagree and reads never hit a replica
  • Rebase delta audited for M, N, D, P, Z: @catalog_operation on _resolve_and_authorize_mcp_server only makes server lookup and the allow_catalog_view registry check read the same consistent catalog snapshot as fetch_mcp_server; a failed refresh surfaces as main's existing 503, never as wider access. History GET still filters with tool_is_granted (None unrestricted, [] nothing) and deprecation writes stay admin-only with the writer-side identity check. No new credential or header handling
  • D, Z (round 8): history route authorization and the key and team tool grant lookup read one catalog snapshot, so a concurrent rename cannot drop a name-keyed tool restriction
  • C1-C7: pin route still accepts no body and returns the same shape; new routes only
  • D, P, Z: GET reuses the server-read auth and then the same key/team tool-permission filter as tool listing (None unrestricted, [] nothing); writes are admin-only with the existing 403 shape
  • A2: the pin's server write bypasses the config-sync wrapper inside the transaction, so it publishes the server change once after commit
  • H1-H6: no docs, no new comments, no em dashes, no Optional, no Any, no bare dict in the diff
  • H7: prebuilt _experimental/out bundle is left to the scheduled build-artifact job, as for other dashboard PRs
  • H8: every MCPToolVersion field has a column in all three schema files; prisma migrate diff against the migrated DB is empty
  • M, N: version rows hold only tool names, descriptions and schemas; the dashboard pin forwards only the browser-held x-mcp-* headers the tools viewer already sends for tools/list, and the server reads them through the same extractor as tools/list
  • B, read replicas: deprecation takes the per-server lock, then reads and updates on the writer in one transaction, so concurrent first deprecations keep one deprecated_at
  • R: migration is additive and idempotent, ran twice cleanly
  • T1-T5, T17: tests fail on unfixed code, no weakened assertions, no global state left behind
  • X1: whitespace-only changelog maps to null via an explicit strip, not an or fallback on the stored value

Type

New Feature

Caveats (if any)

Medium

  • The pin holds one pooled DB connection for its transaction while it writes
    • Pins are rare admin actions; the critical section is a few queries
  • Version history is recorded only from the first pin after upgrade
    • Servers pinned before this change start at v1 on their next pin

Low

  • Breaking classification compares JSON schemas structurally; a semantically equivalent rewrite of a schema counts as breaking

Link to Devin session: https://app.devin.ai/sessions/b6d7b76b141849dab34e4f89f5f937f9
Open in Devin Desktop: https://app.devin.ai/desktop/session/b6d7b76b141849dab34e4f89f5f937f9?variant=devin
Requested by: @yassin-berriai


Note

Medium Risk
Changes the pin and delete code paths and adds new DB writes under advisory locks; impact is limited to MCP admin flows but mistakes could affect pinned catalogs and history consistency.

Overview
Adds version history for admin-pinned MCP tool catalogs: each pin can record per-tool versions with breaking/non-breaking change summaries, an optional changelog, and later deprecation metadata (sunset date and note).

Persistence and concurrency: New LiteLLM_MCPToolVersion table (migration + Prisma). Pinning, history reads, deprecation updates, and server delete all use a per-server Postgres advisory lock and writer transactions so version rows stay aligned with pinned_tools and are removed with the server row.

API: POST .../pin accepts optional changelog; new GET .../tool-versions (filtered by key/team tool grants); admin PUT/DELETE .../versions/{n}/deprecation. Concurrent version inserts surface as 409 on pin.

Logic: New tool_versioning.py diffs pinned tool descriptions and JSON input schemas to plan initial / non_breaking / breaking / removed versions.

Dashboard: Tool versions panel on the MCP Tools tab (history, pin with changelog, deprecate/edit/clear), reusing the viewer’s x-mcp-* headers for pin.

Reviewed by Cursor Bugbot for commit c16b055. Bugbot is set up for automated code reviews on this repo. Configure here.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[High risk] Adds database schema and tool versioning for MCP servers.

The PR appears safe to merge, with the previous test-style finding fully addressed

Summary

This PR adds MCP tool history, change summaries, changelogs, and deprecation controls. Since the previous review, only the snapshot regression test changed

  • The latest rewrite addresses the previous coding-rule finding without weakening its checks
  • No production code changed, and no new actionable issues were found

Reviews (18) · Last reviewed commit: "feat(mcp): track MCP tool versions, chan..." · Reviewed by Greptile

Comment thread litellm/proxy/_experimental/mcp_server/db.py
Comment thread litellm/proxy/_experimental/mcp_server/tool_versioning.py Outdated
Comment thread litellm/proxy/management_endpoints/mcp_management_endpoints.py Outdated
@codspeed

codspeed Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_mcp_tool_versions (c16b055) with main (7b439d9)

Open in CodSpeed

@codecov

codecov Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 56.70996% with 100 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
.../proxy/_experimental/mcp_server/tool_versioning.py 33.33% 62 Missing ⚠️
litellm/proxy/_experimental/mcp_server/db.py 36.36% 35 Missing ⚠️
...y/management_endpoints/mcp_management_endpoints.py 94.44% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review at 9ef19ee (single commit, all four findings addressed)

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review at 06f07ab (same change as 9ef19ee plus prettier formatting of MCPToolVersionsPanel.tsx)

Comment thread litellm/proxy/_experimental/mcp_server/db.py Outdated
Comment thread litellm/proxy/_experimental/mcp_server/tool_versioning.py Outdated
Comment thread ui/litellm-dashboard/src/components/networking.tsx Outdated
Comment thread litellm/proxy/_experimental/mcp_server/db.py Outdated
Comment thread tests/unit/proxy/management_endpoints/test_mcp_management_endpoints.py Outdated
Comment thread tests/unit/proxy/_experimental/mcp_server/test_tool_versioning.py Outdated
Comment thread tests/unit/proxy/_experimental/mcp_server/test_mcp_partial_update.py Outdated
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review at 7dc6351 (single commit). All seven findings from the 06f07ab review are addressed: pin from the panel forwards the tools viewer's x-mcp-* headers; deprecation read and update run in one writer transaction; the history test seam is a module-level helper instead of a class patch; new tests are typed with Final bindings and use mock call history for ordering; documentation stripping is recursive at schema positions only, so nested description/title edits are non-breaking while property names are never stripped

Comment thread litellm/proxy/_experimental/mcp_server/tool_versioning.py Outdated
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review at b9f5bd9 (single commit). Addresses "Schema comparisons fail type checks": a typed _mapping_without_documentation(schema: Mapping[str, object]) -> dict[str, object] now backs both top-level comparisons, so basedpyright reports nothing on those lines; it also fixes the LIT006 lint budget from 7dc6351

Comment thread litellm/proxy/_experimental/mcp_server/tool_versioning.py Outdated
Comment thread litellm/proxy/_experimental/mcp_server/db.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.

@devin-ai-integration
devin-ai-integration Bot force-pushed the litellm_mcp_tool_versions branch from 83bcc0d to 8012935 Compare October 5, 2026 17:34
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@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/_experimental/mcp_server/db.py
@devin-ai-integration
devin-ai-integration Bot force-pushed the litellm_mcp_tool_versions branch from 8012935 to 19d0965 Compare October 5, 2026 17:49
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@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.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai please review the new head 0091cc4, rebased onto current main (the previous 5/5 on 19d0965 is no longer an ancestor). The rebase resolved conflicts with the catalog consistency change by keeping @catalog_operation on fetch_mcp_server and adding it to _resolve_and_authorize_mcp_server, and moved the four new MCP models to LiteLLMBaseModel

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@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/management_endpoints/mcp_management_endpoints.py
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai please review the new head 5b95c5d. Since 0091cc4 the only change is @catalog_operation on fetch_mcp_server_tool_versions, addressing the P1 on the tool grant lookup running outside the authorization snapshot, plus a regression test

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@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 tests/unit/proxy/management_endpoints/test_mcp_management_endpoints.py Outdated
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai please review the new head c16b055. Since 5b95c5d the only change is the test rewrite for the P2 on test style: Final mock recorders instead of a mutated dict and typed callbacks. No production code changed

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@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 c16b055. Configure here.

This branch is waiting to be deployed

1 waiting deployment
e2e-changed — c16b0552 Waiting Oct 7, 2026 by devin-ai-integration[bot] via oauth #3522
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.

3 participants