chore(release): backport #31029 to stable/1.89.x and cut 1.89.4 - #31168
Conversation
…el (#31029) * feat(mcp): scope a key to zero MCP servers with no-mcp-servers sentinel A key under a team that has MCP servers had no way to opt out of them; an empty list has always meant "inherit the team". This adds a no-mcp-servers sentinel (mirroring no-default-models for models) so a key can declare an explicit zero that overrides team inheritance, additive grants, and allow_all_keys servers, surfaced as an exclusive "No MCP Servers" option in the key create/edit UI. * refactor(ui): centralize no-mcp-servers sentinel in a shared constant The sentinel string was defined under two different local names and inlined in two more files; a single exported constant removes the drift risk flagged in review. * fix(mcp): enforce no-mcp-servers sentinel on toolset-scoped routes Toolset scoping replaced a key's mcp_servers with the toolset's servers, dropping the no-mcp-servers sentinel, so a key opted out of all MCP could still execute a granted toolset's tools via /toolset/{name}/mcp. Deny toolset access when the key carries the sentinel, checked before the admin branch to match get_allowed_mcp_servers. (cherry picked from commit 19a29e0)
Greptile SummaryThis PR backports the
Confidence Score: 4/5Safe to merge; the sentinel correctly returns zero servers at every reachable code path and all nine new tests pass cleanly. The three-layer enforcement (auth handler, server manager, toolset scope) is correctly ordered and independently tested. The
|
| Filename | Overview |
|---|---|
| litellm/proxy/_types.py | Adds SpecialMCPServerNames enum with no_mcp_servers = "no-mcp-servers", mirroring the existing SpecialModelNames pattern. |
| litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py | Adds sentinel short-circuit at two points: _get_allowed_mcp_servers_for_key surfaces the sentinel unexpanded; get_allowed_mcp_servers returns [] when sentinel detected, before consulting the team. |
| litellm/proxy/_experimental/mcp_server/mcp_server_manager.py | Adds sentinel guard at the top of MCPServerManager.get_allowed_mcp_servers, returning [] before allow_all_keys layering — prevents allow-all servers from overriding an explicit opt-out. |
| litellm/proxy/_experimental/mcp_server/server.py | Adds sentinel check at the top of _apply_toolset_scope, raising 403 before the admin branch — prevents toolset-scoped access from bypassing the sentinel by replacing mcp_servers. |
| litellm/proxy/management_helpers/object_permission_utils.py | Passes sentinel through _rewrite_object_permission_mcp_servers unchanged; discards it from _extract_requested_mcp_server_ids so team-validation logic skips it — both changes are correct. |
| ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx | Adds allowNoMcpServers prop; handler enforces exclusivity (sentinel clears all else); disabled overlay applied to real-server options when sentinel is active. |
| ui/litellm-dashboard/src/components/permissions/MCPServerPermissions.tsx | Filters sentinel from display list; renders a "Blocked" badge and red callout when sentinel is present — correct and informative. |
| ui/litellm-dashboard/src/components/organisms/create_key_button.tsx | Passes allowNoMcpServers to selector; filters sentinel before handing servers to MCPToolPermissions to avoid querying tool-level permissions for a blocked key. |
| ui/litellm-dashboard/src/components/templates/key_edit_view.tsx | Same sentinel-filter applied to MCPToolPermissions in the edit view; consistent with create_key_button.tsx. |
| ui/litellm-dashboard/src/components/mcp_tools/constants.ts | New file; exports NO_MCP_SERVERS_SENTINEL = "no-mcp-servers" — correctly placed in constants.ts and matches the backend enum value. |
| tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py | Adds two new mock-only tests: sentinel returns empty across varied team configurations, and _get_allowed_mcp_servers_for_key surfaces the sentinel unexpanded. |
| tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py | Adds test_no_mcp_servers_sentinel_blocks_allow_all_keys — verifies sentinel short-circuits before MCPRequestHandler.get_allowed_mcp_servers is called and with allow_all_keys servers present. |
| tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_toolset_scope.py | Adds test_no_mcp_servers_sentinel_denies_toolset_access parameterized over regular and admin roles, confirming 403 before the toolset resolver is consulted. |
| tests/test_litellm/proxy/management_helpers/test_object_permission_utils.py | Adds three tests: sentinel excluded from validation set, sentinel preserved through rewrite, and sentinel passes team validation without rejection. |
| ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.test.tsx | New test file covering: sentinel hidden by default, exclusive sentinel emission on selection, and real-server options disabled while sentinel is active. |
Comments Outside Diff (1)
-
litellm/proxy/management_helpers/object_permission_utils.py, line 483-526 (link)Untested mixed-sentinel case in validation path
When a key's
mcp_serverscontains both the sentinel and real server IDs — e.g.["no-mcp-servers", "server-1"]—_extract_requested_mcp_server_idsdiscards the sentinel and validates only"server-1". If that server is allowed, the permission write-back in_rewrite_object_permission_mcp_serverswill store both the sentinel and the resolved server ID. At runtime the sentinel dominates and the key sees zero servers, but during key-creation the real server silently passes validation, making the stored state harder to reason about. A test covering this combination (sentinel + valid server → sentinel still dominates at auth time) would make the invariant explicit and guard against future changes to the validation order.
Reviews (1): Last reviewed commit: "chore: refresh uv.lock for 1.89.4" | Re-trigger Greptile
| # drop the sentinel. Checked before the admin branch, mirroring | ||
| # get_allowed_mcp_servers. | ||
| original_op = user_api_key_auth.object_permission | ||
| if original_op is not None and SpecialMCPServerNames.no_mcp_servers.value in ( |
There was a problem hiding this comment.
Medium: MCP opt-out bypass through Responses toolsets
This guard covers _apply_toolset_scope, but the Responses API uses LiteLLM_Proxy_MCP_Handler._apply_toolset_permissions() in litellm/responses/mcp/litellm_proxy_mcp_handler.py, which replaces object_permission.mcp_servers with the resolved toolset server IDs and drops the new sentinel. A user with a key containing mcp_servers: ["no-mcp-servers"] and an mcp_toolsets grant can request litellm_proxy/mcp/<toolset> through Responses and still get those MCP tools; add the same sentinel check before resolving or applying toolset permissions there, or centralize the check so all toolset-scoping paths share it.
PR overviewThis PR backports changes from #31029 to the stable/1.89.x branch and prepares the LiteLLM 1.89.4 release. The touched code includes LiteLLM proxy MCP server and toolset permission handling. There is one open security issue remaining. The MCP opt-out sentinel is not consistently enforced on the Responses API toolset path, so a key configured to opt out of MCP servers but also granted an MCP toolset can still reach those tools through that route. This is an authenticated permission-bypass scenario with a constrained scope, but it should be fixed before release. Open issues (1)
Fixed/addressed: 0 · PR risk: 6/10 |
….4) (#196) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.89.3` → `v1.89.4` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.89.4`](https://github.com/BerriAI/litellm/releases/tag/v1.89.4) [Compare Source](BerriAI/litellm@v1.89.4...v1.89.4) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.4/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(release): backport [#​31029](BerriAI/litellm#31029) to stable/1.89.x and cut 1.89.4 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31168](BerriAI/litellm#31168) - chore(ui): rebuild dashboard artifacts for stable/1.89.x by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31170](BerriAI/litellm#31170) - chore(release): backport [#​30787](BerriAI/litellm#30787), [#​30788](BerriAI/litellm#30788), [#​31035](BerriAI/litellm#31035), [#​31036](BerriAI/litellm#31036), [#​31122](BerriAI/litellm#31122), [#​31133](BerriAI/litellm#31133) to stable/1.89.x (litellm-enterprise 0.1.42.post2) by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31259](BerriAI/litellm#31259) **Full Changelog**: <BerriAI/litellm@v1.89.3...v1.89.4> ### [`v1.89.4`](https://github.com/BerriAI/litellm/releases/tag/v1.89.4) [Compare Source](BerriAI/litellm@v1.89.3...v1.89.4) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.4/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(release): backport [#​31029](BerriAI/litellm#31029) to stable/1.89.x and cut 1.89.4 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31168](BerriAI/litellm#31168) - chore(ui): rebuild dashboard artifacts for stable/1.89.x by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31170](BerriAI/litellm#31170) - chore(release): backport [#​30787](BerriAI/litellm#30787), [#​30788](BerriAI/litellm#30788), [#​31035](BerriAI/litellm#31035), [#​31036](BerriAI/litellm#31036), [#​31122](BerriAI/litellm#31122), [#​31133](BerriAI/litellm#31133) to stable/1.89.x (litellm-enterprise 0.1.42.post2) by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31259](BerriAI/litellm#31259) **Full Changelog**: <BerriAI/litellm@v1.89.3...v1.89.4> </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/London) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMzQuMiIsInVwZGF0ZWRJblZlciI6IjQzLjIzNC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/196
….4) (#386) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.89.3` → `v1.89.4` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.89.4`](https://github.com/BerriAI/litellm/releases/tag/v1.89.4) [Compare Source](BerriAI/litellm@v1.89.4...v1.89.4) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.4/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(release): backport [#​31029](BerriAI/litellm#31029) to stable/1.89.x and cut 1.89.4 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31168](BerriAI/litellm#31168) - chore(ui): rebuild dashboard artifacts for stable/1.89.x by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31170](BerriAI/litellm#31170) - chore(release): backport [#​30787](BerriAI/litellm#30787), [#​30788](BerriAI/litellm#30788), [#​31035](BerriAI/litellm#31035), [#​31036](BerriAI/litellm#31036), [#​31122](BerriAI/litellm#31122), [#​31133](BerriAI/litellm#31133) to stable/1.89.x (litellm-enterprise 0.1.42.post2) by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31259](BerriAI/litellm#31259) **Full Changelog**: <BerriAI/litellm@v1.89.3...v1.89.4> ### [`v1.89.4`](https://github.com/BerriAI/litellm/releases/tag/v1.89.4) [Compare Source](BerriAI/litellm@v1.89.3...v1.89.4) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.4/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(release): backport [#​31029](BerriAI/litellm#31029) to stable/1.89.x and cut 1.89.4 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31168](BerriAI/litellm#31168) - chore(ui): rebuild dashboard artifacts for stable/1.89.x by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31170](BerriAI/litellm#31170) - chore(release): backport [#​30787](BerriAI/litellm#30787), [#​30788](BerriAI/litellm#30788), [#​31035](BerriAI/litellm#31035), [#​31036](BerriAI/litellm#31036), [#​31122](BerriAI/litellm#31122), [#​31133](BerriAI/litellm#31133) to stable/1.89.x (litellm-enterprise 0.1.42.post2) by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31259](BerriAI/litellm#31259) **Full Changelog**: <BerriAI/litellm@v1.89.3...v1.89.4> </details> --- ### Configuration 📅 **Schedule**: (in timezone America/New_York) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMzQuMSIsInVwZGF0ZWRJblZlciI6IjQzLjIzNC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/386
…to v1.89.4 (#228) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [https://github.com/BerriAI/litellm.git](https://github.com/BerriAI/litellm) | patch | `v1.89.3` → `v1.89.4` | --- ### Release Notes <details> <summary>BerriAI/litellm (https://github.com/BerriAI/litellm.git)</summary> ### [`v1.89.4`](https://github.com/BerriAI/litellm/releases/tag/v1.89.4) [Compare Source](BerriAI/litellm@v1.89.3...v1.89.4) #### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.4/cosign.pub \ ghcr.io/berriai/litellm:v1.89.4 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** #### What's Changed - chore(release): backport [#​31029](BerriAI/litellm#31029) to stable/1.89.x and cut 1.89.4 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31168](BerriAI/litellm#31168) - chore(ui): rebuild dashboard artifacts for stable/1.89.x by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31170](BerriAI/litellm#31170) - chore(release): backport [#​30787](BerriAI/litellm#30787), [#​30788](BerriAI/litellm#30788), [#​31035](BerriAI/litellm#31035), [#​31036](BerriAI/litellm#31036), [#​31122](BerriAI/litellm#31122), [#​31133](BerriAI/litellm#31133) to stable/1.89.x (litellm-enterprise 0.1.42.post2) by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31259](BerriAI/litellm#31259) **Full Changelog**: <BerriAI/litellm@v1.89.3...v1.89.4> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMjAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjIyMC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=--> Co-authored-by: Renovate Bot <renovate@bhamm-lab.com> Reviewed-on: https://codeberg.org/blake-hamm/bhamm-lab/pulls/228
Relevant issues
Backports #31029 onto
stable/1.89.xand cuts1.89.4. The change adds ano-mcp-serverssentinel (mirroringno-default-modelsfor models) so a virtual key can be scoped to zero MCP servers. Before this, an emptymcp_serverslist always meant "inherit the team", so there was no value a user could set to say "this key gets none"; the sentinel is the explicit zero that overrides team inheritance, additive access-group grants, andallow_all_keysservers, and also denies toolset-scoped access.This is a feature backport. The pick applied cleanly with no conflicts; no production logic line was altered from the staging commit.
What is included
19a29e0UI rebuild deferred
This pick changes production UI source (
MCPServerSelector.tsx,create_key_button.tsx,MCPServerPermissions.tsx,key_edit_view.tsx, andmcp_tools/constants.ts). The proxy serves a prebuilt Next.js bundle fromlitellm/proxy/_experimental/out/, and that bundle is NOT rebuilt in this PR. The stable UI will be rebuilt separately on this branch after merge. Until that rebuild lands, the backend opt-out is fully functional over the API (a key whoseobject_permission.mcp_serversholdsno-mcp-serversreturns zero tools), but the "No MCP Servers" option will not yet appear in the key create/edit forms on the stable build.Adaptation notes
The pick is patch-id ADAPTED relative to staging, but only in surrounding context; none of the PR's own added lines were modified, and there was no merge conflict. The divergences are import-block neighbors that differ between 1.89.x and staging in three source files (for example
user_api_key_authvsip_address_utils, and aTYPE_CHECKINGblock vs direct repository imports inobject_permission_utils.py), plus hunk line-number offsets. The PR's addedSpecialMCPServerNamesimports and logic are byte-identical; only the unrelated neighbor lines differ, confirmed withgit range-diff.Known noise on this line
None in the targeted set. The four test files this PR touches were all green on the line tip before picking (359 passed / 0 failed); after the pick the same four files are 368 passed / 0 failed, a clean +9 that matches the sentinel and toolset tests the PR adds.
Screenshots / Proof of Fix
Verified against a live proxy running the 1.89.x worktree code with the pick applied, with two config MCP servers (
wikipediastdio anddeepwikihttp) markedallow_all_keys: true, against a clean isolated Postgres stood up for the replay. The shared dev DB could not be used because its Prisma client under the shared.venvis newer than the database it points at (a client/schema mismatch unrelated to this pick), so an isolated database was used instead.Every key that is not scoped with the sentinel sees the allow-all servers union (4 tools); only the sentinel key sees zero. Keys are created with
POST /key/generatesettingobject_permission.mcp_servers, then tools are listed per key overGET /mcp-rest/tools/list:Only the two sentinel keys return zero, while empty-inherit, named-server, and no-permission keys all return the full set; the worktree was verified clean (unmutated) before and after the replay. The authoritative behavioral proof is the unit-test delta above: the sentinel and toolset tests the PR adds all pass on the line, including
test_no_mcp_servers_sentinel_blocks_allow_all_keys(allow-all override) andtest_no_mcp_servers_sentinel_denies_toolset_access(toolset 403), which the live curl cannot easily isolate.Gauntlet (behavioral, universal, deep): SURVIVED across 2 rounds and 7 lenses, zero verified findings, seal holds. All three sub-claims survived: every symbol the pick references resolves, the pick delivers its opt-out on every reachable path, and an empty
mcp_serverslist still inherits the team exactly as before. The only state divergence the seal examined was the benign bump and lock commits on top of the pick (the relevant source files are byte-identical to the verified pick). Symbol closure resolves (SpecialMCPServerNamesdefined in_types.py, ruff clean on all touched modules).Type
🆕 New Feature
Changes
Backport of the no-mcp-servers sentinel onto stable/1.89.x with a 1.89.4 version bump. Backend, tests, and UI source are carried; the UI bundle is rebuilt separately after merge.