diff --git a/cookbook/litellm_proxy_server/mcp/README.md b/cookbook/litellm_proxy_server/mcp/README.md deleted file mode 100644 index aeee0719019..00000000000 --- a/cookbook/litellm_proxy_server/mcp/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Publish MCP servers in the AI Hub - -Set `litellm_settings.public_mcp_servers` to the concrete IDs of the servers you want listed in the public AI Hub. Pin `server_id` in each configuration entry so the publication list stays stable across deployments - -```yaml -mcp_servers: - documentation: - server_id: documentation-mcp - url: https://mcp.example.com/mcp - transport: http - available_on_public_internet: true - -litellm_settings: - public_mcp_hub_strict_whitelist: true - public_mcp_servers: - - documentation-mcp -``` - -Use `documentation-mcp`, the `server_id`, in the publication list. The configuration key `documentation`, display names, and aliases are not publication IDs. Database-created servers use the ID returned by `/v1/mcp/server` - -The dashboard's **AI Hub > MCP Hub > Manage MCP Hub Visibility** dialog edits this same list. Its YAML example includes the selected server IDs. With database-backed configuration (`store_model_in_db: true`), a value declared in YAML is owned by that file: edit the file and reload, or remove that key from YAML to let the dashboard manage it in the database. File-backed deployments can save the list directly to their configuration file - -To remove all explicit entries, save an empty selection in the dialog or configure: - -```yaml -litellm_settings: - public_mcp_hub_strict_whitelist: true - public_mcp_servers: [] -``` - -## Hub listing and network access - -The **Hub listing** column in AI Hub identifies servers that appear in `/public/mcp_hub`. The dashboard derives this status from the current registry and publication settings. Setting `mcp_info.is_public` on a server does not publish it; that response field is derived metadata. `mcp_info.is_public_explicit` identifies registered servers included in the explicit publication list - -Gateway cards and server details show **All Networks** when `available_on_public_internet` is enabled or the server is explicitly published in `public_mcp_servers`. They show **Internal Only** when both are false. The per-server flag defaults to `true`; explicit publication overrides a disabled flag for compatibility. Older proxies that omit the metadata needed to determine access show **Unknown**. These labels describe allowed client IPs; authentication and tool permissions still apply - -The default `public_mcp_hub_strict_whitelist: true` lists only registered servers in `public_mcp_servers`. Legacy mode (`false`) additionally lists registered servers with `available_on_public_internet: true`. In legacy mode, clearing the explicit publication list leaves these automatically listed servers visible. Enable strict mode when the publication list should fully determine hub visibility diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 31896d9ddc5..ea685c431bd 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -6799,15 +6799,13 @@ class MCPServerManager: return server return None - @staticmethod - def _is_public_mcp_server(server: MCPServer, public_ids: Container[str]) -> bool: - return server.server_id in public_ids or ( - not litellm.public_mcp_hub_strict_whitelist and server.available_on_public_internet - ) - - def is_mcp_server_public(self, server_id: str) -> bool: + def is_mcp_server_public(self, server_id: str, *, public_ids: Container[str] | None = None) -> bool: server: Final = self.registry.get(server_id) or self.config_mcp_servers.get(server_id) - return server is not None and self._is_public_mcp_server(server, litellm.public_mcp_servers or ()) + published_ids: Final = (litellm.public_mcp_servers or ()) if public_ids is None else public_ids + return server is not None and ( + server_id in published_ids + or (not litellm.public_mcp_hub_strict_whitelist and server.available_on_public_internet) + ) def get_public_mcp_servers(self) -> list[MCPServer]: """ @@ -6827,7 +6825,11 @@ class MCPServerManager: removed in a future release. """ public_ids: Final = frozenset(litellm.public_mcp_servers or ()) - return [server for server in self.get_registry().values() if self._is_public_mcp_server(server, public_ids)] + return [ + server + for server in self.get_registry().values() + if self.is_mcp_server_public(server.server_id, public_ids=public_ids) + ] def expand_permission_list(self, identifiers: list[str]) -> list[str]: """ diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index 70ef4312f4c..bb4e9e0e0f0 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -9616,6 +9616,9 @@ class TestGetPublicMCPServers: ) assert manager.is_mcp_server_public("server-alias") is False assert manager.is_mcp_server_public("missing-server") is False + assert manager.is_mcp_server_public(server.server_id, public_ids=frozenset()) is ( + registered_in != "neither" and implicitly_public + ) assert server.model_dump() == original_server assert config_server.model_dump() == original_config_server