From 572f00a39ef519dc4d6a85d2bee845b9687e7c3f Mon Sep 17 00:00:00 2001 From: Yucheng He Date: Wed, 2 Sep 2026 01:00:30 -0700 Subject: [PATCH 1/5] docs(mcp): document pinning server_id on config.yaml MCP servers A config-defined MCP server's id is derived from its connection fields, so editing the url, name, transport, auth type or alias mints a new id and every key or team grant holding the old one silently stops matching. Document the new optional server_id field, the recipe for adopting it on a server that already has grants, and the rules: non-empty string, no duplicate pins, no pin that is another entry's name or alias, what happens when a pin collides with a database-backed server, and the effect on the short tool prefix. --- docs/mcp.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/mcp.md b/docs/mcp.md index 9aa16321f..f523c0145 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -224,6 +224,7 @@ mcp_servers: **Configuration Options:** - **Server Name**: Use any descriptive name for your MCP server (e.g., `zapier_mcp`, `deepwiki_mcp`, `circleci_mcp`) +- **server_id**: Optional. Pins the server's id instead of deriving it from `server_name`, `url`, `transport`, `auth_type` and `alias`. See [Pinning `server_id`](#pinning-server_id) - **Alias**: This name will be prefilled with the server name with "_" replacing spaces, else edit it to be the prefix in tool names - **URL**: The endpoint URL for your MCP server (required for HTTP/SSE transports) - **Transport**: Optional transport type (defaults to `sse`) @@ -318,6 +319,35 @@ mcp_servers: X-Custom-Header: "some-value" ``` +### Pinning `server_id` + +Key and team MCP permissions are stored as server ids. For a server defined in `config.yaml`, LiteLLM derives that id by hashing `server_name`, `url`, `transport`, `auth_type` and `alias`, so editing any of those fields (moving a server from a staging url to a production url, renaming it, adding an alias) produces a different id. Every key and team that was granted the old id keeps pointing at an id that no longer exists, and the server stops appearing in `tools/list` for them. + +Set `server_id` to pin it. The pinned value is used verbatim and does not change when the connection fields do, so existing grants keep working. + +```yaml title="Pinned server_id (config.yaml)" showLineNumbers +mcp_servers: + internal_docs: + server_id: "internal-docs" # 👈 grants survive url/name/alias edits + url: "https://docs.internal.example/mcp" + transport: "http" +``` + +To adopt this on a server that already has grants, read its current id from `GET /v1/mcp/server` (or the Admin UI MCP Servers page) and pin that exact value, so the ids already stored in `object_permission.mcp_servers` stay valid: + +```bash +curl -s http://localhost:4000/v1/mcp/server -H "Authorization: Bearer sk-1234" +``` + +Rules: + +- The value must be a non-empty string. A blank or non-string `server_id` fails config load rather than silently falling back to the derived hash. +- Use letters, digits and underscores. The id is used verbatim, the same way a `server_id` supplied to `POST /v1/mcp/server` is, so an id containing `/` makes the server unreachable on `/v1/mcp/server/{server_id}` routes. +- Two `mcp_servers` entries cannot share an id, and a pinned id cannot be another entry's `server_name` or `alias`. Both fail config load: the first would overwrite the other's registry entry, and the second would capture permission entries written for that other server, since a grant is matched against server ids before it is matched against names and aliases. +- A pinned id that is already held by a server added through the Admin UI or `/v1/mcp/server` is a misconfiguration, but it does not fail config load on a restart. LiteLLM reads `config.yaml` before it reads the database, so nothing is there to compare against yet; the database row then wins and the config server becomes unreachable. You get `config.yaml MCP server_id(s) ... are also database-backed MCP servers` at WARNING level, so watch the startup logs after pinning. +- With `LITELLM_USE_SHORT_MCP_TOOL_PREFIX` enabled the short tool prefix is derived from the server id, so pinning an id other than the one currently in use renames every tool that server exposes. Pin the current derived id (the adoption recipe above) if anything holds prefixed tool names. +- Servers added through the Admin UI or `/v1/mcp/server` are unaffected: they already have a persistent id that survives edits. + ### MCP Walkthroughs - **Strands (STDIO)** – [watch tutorial](https://screen.studio/share/ruv4D73F) From 5bc4a707974ae19120485efceab30149a847f255 Mon Sep 17 00:00:00 2001 From: Yucheng He Date: Wed, 2 Sep 2026 01:20:42 -0700 Subject: [PATCH 2/5] docs(mcp): cover mapped aliases and both database collision warnings --- docs/mcp.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/mcp.md b/docs/mcp.md index f523c0145..5f89dee43 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -343,8 +343,8 @@ Rules: - The value must be a non-empty string. A blank or non-string `server_id` fails config load rather than silently falling back to the derived hash. - Use letters, digits and underscores. The id is used verbatim, the same way a `server_id` supplied to `POST /v1/mcp/server` is, so an id containing `/` makes the server unreachable on `/v1/mcp/server/{server_id}` routes. -- Two `mcp_servers` entries cannot share an id, and a pinned id cannot be another entry's `server_name` or `alias`. Both fail config load: the first would overwrite the other's registry entry, and the second would capture permission entries written for that other server, since a grant is matched against server ids before it is matched against names and aliases. -- A pinned id that is already held by a server added through the Admin UI or `/v1/mcp/server` is a misconfiguration, but it does not fail config load on a restart. LiteLLM reads `config.yaml` before it reads the database, so nothing is there to compare against yet; the database row then wins and the config server becomes unreachable. You get `config.yaml MCP server_id(s) ... are also database-backed MCP servers` at WARNING level, so watch the startup logs after pinning. +- Two `mcp_servers` entries cannot share an id, and a pinned id cannot be another entry's `server_name` or `alias`, counting aliases mapped through `litellm_settings.mcp_aliases`. Both fail config load: the first would overwrite the other's registry entry, and the second would capture permission entries written for that other server, since a grant is matched against server ids before it is matched against names and aliases. +- The same collision against a server added through the Admin UI or `/v1/mcp/server` is a misconfiguration but does not fail config load on a restart. LiteLLM reads `config.yaml` before it reads the database, so nothing is there to compare against yet. Watch the startup logs for either of these at WARNING level: `config.yaml MCP server_id(s) ... are also database-backed MCP servers` means the database row wins and your config server is unreachable, and `config.yaml MCP server_id(s) ... are the name or alias of a database-backed MCP server` means the reverse, that grants written for the database server now resolve to your config server. - With `LITELLM_USE_SHORT_MCP_TOOL_PREFIX` enabled the short tool prefix is derived from the server id, so pinning an id other than the one currently in use renames every tool that server exposes. Pin the current derived id (the adoption recipe above) if anything holds prefixed tool names. - Servers added through the Admin UI or `/v1/mcp/server` are unaffected: they already have a persistent id that survives edits. From a78f1948f8dd0545dfdf2e87ca71fea7bce3a284 Mon Sep 17 00:00:00 2001 From: Yucheng He Date: Fri, 4 Sep 2026 20:03:22 -0700 Subject: [PATCH 3/5] docs(mcp): a self-pin shared with another entry's alias fails config load --- docs/mcp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mcp.md b/docs/mcp.md index 5f89dee43..0cb2d3e47 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -343,7 +343,7 @@ Rules: - The value must be a non-empty string. A blank or non-string `server_id` fails config load rather than silently falling back to the derived hash. - Use letters, digits and underscores. The id is used verbatim, the same way a `server_id` supplied to `POST /v1/mcp/server` is, so an id containing `/` makes the server unreachable on `/v1/mcp/server/{server_id}` routes. -- Two `mcp_servers` entries cannot share an id, and a pinned id cannot be another entry's `server_name` or `alias`, counting aliases mapped through `litellm_settings.mcp_aliases`. Both fail config load: the first would overwrite the other's registry entry, and the second would capture permission entries written for that other server, since a grant is matched against server ids before it is matched against names and aliases. +- Two `mcp_servers` entries cannot share an id, and a pinned id cannot be another entry's `server_name` or `alias`, counting aliases mapped through `litellm_settings.mcp_aliases`. Both fail config load: the first would overwrite the other's registry entry, and the second would capture permission entries written for that other server, since a grant is matched against server ids before it is matched against names and aliases. Pinning a server's own `server_name` or `alias` is fine, unless another entry also uses that string as its alias: a grant naming it reaches both servers today, and the pin would narrow it to the pinning server, so that case fails config load too. - The same collision against a server added through the Admin UI or `/v1/mcp/server` is a misconfiguration but does not fail config load on a restart. LiteLLM reads `config.yaml` before it reads the database, so nothing is there to compare against yet. Watch the startup logs for either of these at WARNING level: `config.yaml MCP server_id(s) ... are also database-backed MCP servers` means the database row wins and your config server is unreachable, and `config.yaml MCP server_id(s) ... are the name or alias of a database-backed MCP server` means the reverse, that grants written for the database server now resolve to your config server. - With `LITELLM_USE_SHORT_MCP_TOOL_PREFIX` enabled the short tool prefix is derived from the server id, so pinning an id other than the one currently in use renames every tool that server exposes. Pin the current derived id (the adoption recipe above) if anything holds prefixed tool names. - Servers added through the Admin UI or `/v1/mcp/server` are unaffected: they already have a persistent id that survives edits. From 04892511f6757d842b79c7f8ef3efd54003610cf Mon Sep 17 00:00:00 2001 From: Yucheng He Date: Fri, 4 Sep 2026 23:59:23 -0700 Subject: [PATCH 4/5] docs(mcp): simplify server_id guidance --- docs/mcp.md | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/mcp.md b/docs/mcp.md index 0cb2d3e47..354b244ab 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -224,7 +224,7 @@ mcp_servers: **Configuration Options:** - **Server Name**: Use any descriptive name for your MCP server (e.g., `zapier_mcp`, `deepwiki_mcp`, `circleci_mcp`) -- **server_id**: Optional. Pins the server's id instead of deriving it from `server_name`, `url`, `transport`, `auth_type` and `alias`. See [Pinning `server_id`](#pinning-server_id) +- **server_id**: Optional stable id for the server. See [Pinning `server_id`](#pinning-server_id) - **Alias**: This name will be prefilled with the server name with "_" replacing spaces, else edit it to be the prefix in tool names - **URL**: The endpoint URL for your MCP server (required for HTTP/SSE transports) - **Transport**: Optional transport type (defaults to `sse`) @@ -321,32 +321,28 @@ mcp_servers: ### Pinning `server_id` -Key and team MCP permissions are stored as server ids. For a server defined in `config.yaml`, LiteLLM derives that id by hashing `server_name`, `url`, `transport`, `auth_type` and `alias`, so editing any of those fields (moving a server from a staging url to a production url, renaming it, adding an alias) produces a different id. Every key and team that was granted the old id keeps pointing at an id that no longer exists, and the server stops appearing in `tools/list` for them. +LiteLLM derives a config server's id from its name and connection settings. If you change those settings, the id changes and existing key or team permissions stop matching. -Set `server_id` to pin it. The pinned value is used verbatim and does not change when the connection fields do, so existing grants keep working. +Set `server_id` to keep the same id when the server changes: -```yaml title="Pinned server_id (config.yaml)" showLineNumbers +```yaml mcp_servers: internal_docs: - server_id: "internal-docs" # 👈 grants survive url/name/alias edits + server_id: "internal-docs" url: "https://docs.internal.example/mcp" transport: "http" ``` -To adopt this on a server that already has grants, read its current id from `GET /v1/mcp/server` (or the Admin UI MCP Servers page) and pin that exact value, so the ids already stored in `object_permission.mcp_servers` stay valid: +If the server already has permissions, use its current id from `GET /v1/mcp/server` or the Admin UI, then set that exact value as `server_id`: ```bash -curl -s http://localhost:4000/v1/mcp/server -H "Authorization: Bearer sk-1234" +curl -s http://localhost:4000/v1/mcp/server \ + -H "Authorization: Bearer sk-1234" ``` -Rules: +`server_id` must be a non-empty string. Config entries cannot reuse an id or another entry's name or alias. If it conflicts with a database-backed server, LiteLLM logs a warning and the database server takes precedence. -- The value must be a non-empty string. A blank or non-string `server_id` fails config load rather than silently falling back to the derived hash. -- Use letters, digits and underscores. The id is used verbatim, the same way a `server_id` supplied to `POST /v1/mcp/server` is, so an id containing `/` makes the server unreachable on `/v1/mcp/server/{server_id}` routes. -- Two `mcp_servers` entries cannot share an id, and a pinned id cannot be another entry's `server_name` or `alias`, counting aliases mapped through `litellm_settings.mcp_aliases`. Both fail config load: the first would overwrite the other's registry entry, and the second would capture permission entries written for that other server, since a grant is matched against server ids before it is matched against names and aliases. Pinning a server's own `server_name` or `alias` is fine, unless another entry also uses that string as its alias: a grant naming it reaches both servers today, and the pin would narrow it to the pinning server, so that case fails config load too. -- The same collision against a server added through the Admin UI or `/v1/mcp/server` is a misconfiguration but does not fail config load on a restart. LiteLLM reads `config.yaml` before it reads the database, so nothing is there to compare against yet. Watch the startup logs for either of these at WARNING level: `config.yaml MCP server_id(s) ... are also database-backed MCP servers` means the database row wins and your config server is unreachable, and `config.yaml MCP server_id(s) ... are the name or alias of a database-backed MCP server` means the reverse, that grants written for the database server now resolve to your config server. -- With `LITELLM_USE_SHORT_MCP_TOOL_PREFIX` enabled the short tool prefix is derived from the server id, so pinning an id other than the one currently in use renames every tool that server exposes. Pin the current derived id (the adoption recipe above) if anything holds prefixed tool names. -- Servers added through the Admin UI or `/v1/mcp/server` are unaffected: they already have a persistent id that survives edits. +If `LITELLM_USE_SHORT_MCP_TOOL_PREFIX` is enabled, changing the id also changes the server's tool prefix. Servers added through the UI or `/v1/mcp/server` already have stable ids. ### MCP Walkthroughs From fb558646e8fba297befe0c17fbe364b016d983fd Mon Sep 17 00:00:00 2001 From: Yucheng He Date: Sat, 5 Sep 2026 00:06:07 -0700 Subject: [PATCH 5/5] docs(mcp): split the two database collision outcomes --- docs/mcp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mcp.md b/docs/mcp.md index 354b244ab..04f9dcbea 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -340,7 +340,7 @@ curl -s http://localhost:4000/v1/mcp/server \ -H "Authorization: Bearer sk-1234" ``` -`server_id` must be a non-empty string. Config entries cannot reuse an id or another entry's name or alias. If it conflicts with a database-backed server, LiteLLM logs a warning and the database server takes precedence. +`server_id` must be a non-empty string. Config entries cannot reuse an id or another entry's name or alias. Clashes with a database-backed server only log a warning: if the id matches, the database server wins and the config one is unreachable, and if it matches that server's name or alias, permissions naming it reach the config server instead. If `LITELLM_USE_SHORT_MCP_TOOL_PREFIX` is enabled, changing the id also changes the server's tool prefix. Servers added through the UI or `/v1/mcp/server` already have stable ids.