[Feat] IP-Based Access Control for MCP Servers#20620
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile OverviewGreptile Summary
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| CLAUDE.md | Adds a style guideline discouraging inline imports; no functional impact. |
| litellm-proxy-extras/litellm_proxy_extras/schema.prisma | Adds available_on_public_internet boolean to LiteLLM_MCPServerTable schema; requires DB migration consistency with other prisma schemas. |
| litellm/proxy/_experimental/mcp_server/auth/litellm_auth_handler.py | Extends MCPAuthenticatedUser to store client_ip in auth context; safe if all callers pass/handle new field. |
| litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py | Threads client IP into MCP server lookup to hide non-public servers from external callers. |
| litellm/proxy/_experimental/mcp_server/mcp_server_manager.py | Implements IP-based filtering (public vs internal) and adds available_on_public_internet; introduces expensive per-request filtering path and repeats config-loading logic. |
| litellm/proxy/_experimental/mcp_server/rest_endpoints.py | Adds IP filtering to REST list_tools/call_tool flows and refactors header extraction; behavior change depends on correct client IP extraction. |
| litellm/proxy/_experimental/mcp_server/server.py | Adds client IP extraction for MCP session requests, plumbs it through auth context, and applies IP filtering to allowed-server computation; includes new exception handling. |
| litellm/proxy/_types.py | Extends MCP server request/response types with available_on_public_internet and adds general settings fields for MCP IP ranges. |
| litellm/proxy/auth/ip_address_utils.py | Introduces IP parsing/classification and trusted-proxy-aware client IP extraction; has edge-case behavior if request client info is missing. |
| litellm/proxy/management_endpoints/mcp_management_endpoints.py | Filters MCP registry response by client IP; adds available_on_public_internet when creating servers. |
| litellm/proxy/proxy_server.py | Exposes new general settings keys in config list and applies IP filtering in dynamic MCP route. |
| litellm/proxy/schema.prisma | Adds available_on_public_internet boolean column to MCP server table schema; must remain consistent with root schema. |
| litellm/types/mcp_server/mcp_server_manager.py | Adds available_on_public_internet field to MCPServer pydantic model. |
| schema.prisma | Adds available_on_public_internet boolean column to MCP server table schema in root prisma schema. |
| tests/mcp_tests/test_mcp_server.py | Updates MCP server REST tests to account for IP filtering by mocking manager methods and request client IP. |
| tests/test_litellm/proxy/auth/test_mcp_ip_filtering.py | Adds unit tests for internal/external IP classification and server filtering behavior. |
| ui/litellm-dashboard/src/components/mcp_tools/MCPNetworkSettings.tsx | Adds UI for configuring MCP internal IP ranges and calls external ipify to detect public IP (introduces third-party network call from UI). |
| ui/litellm-dashboard/src/components/mcp_tools/MCPPermissionManagement.tsx | Adds form toggle for available_on_public_internet in MCP permission management UI. |
| ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx | Includes available_on_public_internet in create MCP server payload. |
| ui/litellm-dashboard/src/components/mcp_tools/mcp_server_columns.tsx | Adds 'Network Access' column showing Public/Internal based on available_on_public_internet. |
| ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx | Adds available_on_public_internet to MCP server edit payload and defaults. |
| ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx | Displays available_on_public_internet status in server detail view. |
| ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx | Adds a 'Network Settings' tab to MCP servers UI to configure internal IP ranges. |
| ui/litellm-dashboard/src/components/mcp_tools/types.tsx | Extends MCPServer TS type with available_on_public_internet optional boolean. |
Sequence Diagram
sequenceDiagram
autonumber
participant Client as MCP Client (external)
participant Proxy as LiteLLM Proxy
participant IPU as IPAddressUtils
participant MSM as MCPServerManager
participant MCP as Upstream MCP Server
Client->>Proxy: "HTTP /mcp/{server_name}/... (or REST /mcp/tools)"
Proxy->>IPU: "get_mcp_client_ip(request)"
IPU-->>Proxy: "client_ip (direct or XFF)"
Proxy->>MSM: "get_mcp_server_by_name(name, client_ip)"
MSM->>MSM: "_is_server_accessible_from_ip(server, client_ip)"
MSM->>MSM: "_get_general_settings()/parse_internal_networks()"
MSM-->>Proxy: "server or None"
alt "server visible & allowed"
Proxy->>MSM: "get_allowed_mcp_servers(user_api_key_auth)"
MSM-->>Proxy: "allowed_server_ids"
Proxy->>MSM: "filter_server_ids_by_ip(allowed_server_ids, client_ip)"
MSM-->>Proxy: "filtered_allowed_ids"
Proxy->>MSM: "get_mcp_server_by_id(server_id)"
MSM-->>Proxy: "MCPServer config"
Proxy->>MCP: "list_tools / call_tool (merged auth headers)"
MCP-->>Proxy: "tools/result"
Proxy-->>Client: "response"
else "server hidden/forbidden"
Proxy-->>Client: "404/403"
end
ui/litellm-dashboard/src/components/mcp_tools/MCPNetworkSettings.tsx
Outdated
Show resolved
Hide resolved
| def filter_server_ids_by_ip( | ||
| self, server_ids: List[str], client_ip: Optional[str] | ||
| ) -> List[str]: | ||
| """ | ||
| Filter server IDs by client IP — external callers only see public servers. | ||
|
|
||
| Returns server_ids unchanged when client_ip is None (no filtering). | ||
| """ | ||
| if client_ip is None: | ||
| return server_ids | ||
| return [ | ||
| sid | ||
| for sid in server_ids | ||
| if (s := self.get_mcp_server_by_id(sid)) is not None | ||
| and self._is_server_accessible_from_ip(s, client_ip) | ||
| ] |
There was a problem hiding this comment.
N+1 lookup per request
filter_server_ids_by_ip() calls get_mcp_server_by_id() inside a list comprehension, and get_mcp_server_by_id() itself loops over registry.values(). This makes filtering O(N*M) on hot paths (REST list_tools / call_tool), and will degrade quickly as server count grows. Precompute an id→server map once per registry refresh or change get_mcp_server_by_id to be O(1) for this usage.
…ng format for Claude Code (#20631) * Add http support to custom code guardrails + Unified guardrails for MCP + Agent guardrail support (#20619) * fix: fix styling * fix(custom_code_guardrail.py): add http support for custom code guardrails allows users to call external guardrails on litellm with minimal code changes (no custom handlers) Test guardrail integrations more easily * feat(a2a/): add guardrails for agent interactions allows the same guardrails for llm's to be applied to agents as well * fix(a2a/): support passing guardrails to a2a from the UI * style(code-editor): allow editing custom code guardrails on ui + add examples of pre/post calls for custom code guardrails * feat(mcp/): support custom code guardrails for mcp calls allows custom code guardrails to work on mcp input * feat(chatui.tsx): support guardrails on mcp tool calls on playground * fix(mypy): resolve missing return statements and type casting issues (#20618) * fix(mypy): resolve missing return statements and type casting issues * fix(pangea): use elif to prevent UnboundLocalError and handle None messages Address Greptile review feedback: - Make branches mutually exclusive using elif to prevent input_messages from being overwritten - Handle case where data.get('messages') returns None to avoid passing invalid payload to Pangea API --------- Co-authored-by: Shin <shin@openclaw.ai> * [Feat] MCP Gateway - Allow setting MCP Servers as Private/Public available on Internet (#20607) * update MCPAuthenticatedUser * add available_on_public_internet for MCPs * update claude.md * init IPAddressUtils * init available_on_public_internet * add on REST endpoints * filter with IP * TestIsInternalIp * _extract_mcp_headers_from_request * init get_mcp_client_ip * _get_general_settings * allowed_server_ids * address PR comments * get_mcp_server_by_name fix * fix server * fix review comments * get_public_mcp_servers * address _get_allowed_mcp_servers * fixing user_id * [Feat] IP-Based Access Control for MCP Servers (#20620) * update MCPAuthenticatedUser * add available_on_public_internet for MCPs * update claude.md * init IPAddressUtils * init available_on_public_internet * add on REST endpoints * filter with IP * TestIsInternalIp * _extract_mcp_headers_from_request * init get_mcp_client_ip * _get_general_settings * allowed_server_ids * address PR comments * get_mcp_server_by_name fix * fix server * fix review comments * get_public_mcp_servers * address _get_allowed_mcp_servers * test fix * fix linting * inint ui types * add ui for managing MCP private/public * add ui * fixes * add to schema * add types * fix endpoint * add endpoint * update manager * test mcp * dont use external party for ip address * Add OpenAI/Azure release test suite with HTTP client lifecycle regression detection (#20622) * docs (#20626) * docs * fix(mypy): resolve type checking errors in 5 files (#20627) - a2a_protocol/exception_mapping_utils.py: Fix type ignore comment for None assignment - caching/redis_cache.py: Add type ignore for async ping return type - caching/redis_cluster_cache.py: Add type ignore for async ping return type - llms/deprecated_providers/palm.py: Add type ignore for palm.generate_text - proxy/auth/handle_jwt.py: Add type ignore for jwt.decode options argument All changes add appropriate type: ignore comments to handle library typing inconsistencies. * fix(test): update deprecated gemini embedding model (#20621) Replace text-embedding-004 with gemini-embedding-001. The old model was deprecated and returns 404: 'models/text-embedding-004 is not found for API version v1beta' Co-authored-by: Shin <shin@openclaw.ai> * ui new buil * fix(websearch_interception): convert agentic loop response to streaming format when original request was streaming Fixes #20187 - When using websearch_interception in Bedrock with Claude Code: 1. Output tokens were showing as 0 because the agentic loop response wasn't being converted back to streaming format 2. The response from the agentic loop (follow-up request) was returned as a non-streaming dict, but Claude Code expects a streaming response This fix adds streaming format conversion for the agentic loop response when the original request was streaming (detected via the websearch_interception_converted_stream flag in logging_obj). The fix ensures: - Output tokens are correctly included in the message_delta event - stop_reason is properly preserved - The response format matches what Claude Code expects --------- Co-authored-by: Krish Dholakia <krrishdholakia@gmail.com> Co-authored-by: Shin <shin@openclaw.ai> Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com> Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com> Co-authored-by: Alexsander Hamir <alexsanderhamirgomesbaptista@gmail.com>
…iohttp tracing (#20630) * Add http support to custom code guardrails + Unified guardrails for MCP + Agent guardrail support (#20619) * fix: fix styling * fix(custom_code_guardrail.py): add http support for custom code guardrails allows users to call external guardrails on litellm with minimal code changes (no custom handlers) Test guardrail integrations more easily * feat(a2a/): add guardrails for agent interactions allows the same guardrails for llm's to be applied to agents as well * fix(a2a/): support passing guardrails to a2a from the UI * style(code-editor): allow editing custom code guardrails on ui + add examples of pre/post calls for custom code guardrails * feat(mcp/): support custom code guardrails for mcp calls allows custom code guardrails to work on mcp input * feat(chatui.tsx): support guardrails on mcp tool calls on playground * fix(mypy): resolve missing return statements and type casting issues (#20618) * fix(mypy): resolve missing return statements and type casting issues * fix(pangea): use elif to prevent UnboundLocalError and handle None messages Address Greptile review feedback: - Make branches mutually exclusive using elif to prevent input_messages from being overwritten - Handle case where data.get('messages') returns None to avoid passing invalid payload to Pangea API --------- Co-authored-by: Shin <shin@openclaw.ai> * [Feat] MCP Gateway - Allow setting MCP Servers as Private/Public available on Internet (#20607) * update MCPAuthenticatedUser * add available_on_public_internet for MCPs * update claude.md * init IPAddressUtils * init available_on_public_internet * add on REST endpoints * filter with IP * TestIsInternalIp * _extract_mcp_headers_from_request * init get_mcp_client_ip * _get_general_settings * allowed_server_ids * address PR comments * get_mcp_server_by_name fix * fix server * fix review comments * get_public_mcp_servers * address _get_allowed_mcp_servers * fixing user_id * [Feat] IP-Based Access Control for MCP Servers (#20620) * update MCPAuthenticatedUser * add available_on_public_internet for MCPs * update claude.md * init IPAddressUtils * init available_on_public_internet * add on REST endpoints * filter with IP * TestIsInternalIp * _extract_mcp_headers_from_request * init get_mcp_client_ip * _get_general_settings * allowed_server_ids * address PR comments * get_mcp_server_by_name fix * fix server * fix review comments * get_public_mcp_servers * address _get_allowed_mcp_servers * test fix * fix linting * inint ui types * add ui for managing MCP private/public * add ui * fixes * add to schema * add types * fix endpoint * add endpoint * update manager * test mcp * dont use external party for ip address * Add OpenAI/Azure release test suite with HTTP client lifecycle regression detection (#20622) * docs (#20626) * docs * fix(mypy): resolve type checking errors in 5 files (#20627) - a2a_protocol/exception_mapping_utils.py: Fix type ignore comment for None assignment - caching/redis_cache.py: Add type ignore for async ping return type - caching/redis_cluster_cache.py: Add type ignore for async ping return type - llms/deprecated_providers/palm.py: Add type ignore for palm.generate_text - proxy/auth/handle_jwt.py: Add type ignore for jwt.decode options argument All changes add appropriate type: ignore comments to handle library typing inconsistencies. * fix(test): update deprecated gemini embedding model (#20621) Replace text-embedding-004 with gemini-embedding-001. The old model was deprecated and returns 404: 'models/text-embedding-004 is not found for API version v1beta' Co-authored-by: Shin <shin@openclaw.ai> * ui new buil * fix(http_handler): bypass cache when shared_session is provided for aiohttp tracing When users pass a shared_session with trace_configs to acompletion(), the get_async_httpx_client() function was ignoring it and returning a cached client without the user's tracing configuration. This fix bypasses the cache when shared_session is provided, ensuring the user's ClientSession (with its trace_configs, connector settings, etc.) is actually used for the request. Fixes #20174 --------- Co-authored-by: Krish Dholakia <krrishdholakia@gmail.com> Co-authored-by: Shin <shin@openclaw.ai> Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com> Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com> Co-authored-by: Alexsander Hamir <alexsanderhamirgomesbaptista@gmail.com> Co-authored-by: shin-bot-litellm <shin-bot-litellm@users.noreply.github.com>
…iohttp tracing (#20630) * Add http support to custom code guardrails + Unified guardrails for MCP + Agent guardrail support (#20619) * fix: fix styling * fix(custom_code_guardrail.py): add http support for custom code guardrails allows users to call external guardrails on litellm with minimal code changes (no custom handlers) Test guardrail integrations more easily * feat(a2a/): add guardrails for agent interactions allows the same guardrails for llm's to be applied to agents as well * fix(a2a/): support passing guardrails to a2a from the UI * style(code-editor): allow editing custom code guardrails on ui + add examples of pre/post calls for custom code guardrails * feat(mcp/): support custom code guardrails for mcp calls allows custom code guardrails to work on mcp input * feat(chatui.tsx): support guardrails on mcp tool calls on playground * fix(mypy): resolve missing return statements and type casting issues (#20618) * fix(mypy): resolve missing return statements and type casting issues * fix(pangea): use elif to prevent UnboundLocalError and handle None messages Address Greptile review feedback: - Make branches mutually exclusive using elif to prevent input_messages from being overwritten - Handle case where data.get('messages') returns None to avoid passing invalid payload to Pangea API --------- Co-authored-by: Shin <shin@openclaw.ai> * [Feat] MCP Gateway - Allow setting MCP Servers as Private/Public available on Internet (#20607) * update MCPAuthenticatedUser * add available_on_public_internet for MCPs * update claude.md * init IPAddressUtils * init available_on_public_internet * add on REST endpoints * filter with IP * TestIsInternalIp * _extract_mcp_headers_from_request * init get_mcp_client_ip * _get_general_settings * allowed_server_ids * address PR comments * get_mcp_server_by_name fix * fix server * fix review comments * get_public_mcp_servers * address _get_allowed_mcp_servers * fixing user_id * [Feat] IP-Based Access Control for MCP Servers (#20620) * update MCPAuthenticatedUser * add available_on_public_internet for MCPs * update claude.md * init IPAddressUtils * init available_on_public_internet * add on REST endpoints * filter with IP * TestIsInternalIp * _extract_mcp_headers_from_request * init get_mcp_client_ip * _get_general_settings * allowed_server_ids * address PR comments * get_mcp_server_by_name fix * fix server * fix review comments * get_public_mcp_servers * address _get_allowed_mcp_servers * test fix * fix linting * inint ui types * add ui for managing MCP private/public * add ui * fixes * add to schema * add types * fix endpoint * add endpoint * update manager * test mcp * dont use external party for ip address * Add OpenAI/Azure release test suite with HTTP client lifecycle regression detection (#20622) * docs (#20626) * docs * fix(mypy): resolve type checking errors in 5 files (#20627) - a2a_protocol/exception_mapping_utils.py: Fix type ignore comment for None assignment - caching/redis_cache.py: Add type ignore for async ping return type - caching/redis_cluster_cache.py: Add type ignore for async ping return type - llms/deprecated_providers/palm.py: Add type ignore for palm.generate_text - proxy/auth/handle_jwt.py: Add type ignore for jwt.decode options argument All changes add appropriate type: ignore comments to handle library typing inconsistencies. * fix(test): update deprecated gemini embedding model (#20621) Replace text-embedding-004 with gemini-embedding-001. The old model was deprecated and returns 404: 'models/text-embedding-004 is not found for API version v1beta' Co-authored-by: Shin <shin@openclaw.ai> * ui new buil * fix(http_handler): bypass cache when shared_session is provided for aiohttp tracing When users pass a shared_session with trace_configs to acompletion(), the get_async_httpx_client() function was ignoring it and returning a cached client without the user's tracing configuration. This fix bypasses the cache when shared_session is provided, ensuring the user's ClientSession (with its trace_configs, connector settings, etc.) is actually used for the request. Fixes #20174 --------- Co-authored-by: Krish Dholakia <krrishdholakia@gmail.com> Co-authored-by: Shin <shin@openclaw.ai> Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com> Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com> Co-authored-by: Alexsander Hamir <alexsanderhamirgomesbaptista@gmail.com> Co-authored-by: shin-bot-litellm <shin-bot-litellm@users.noreply.github.com>
|
Just a heads up; this is a breaking change. |
|
@andresC98 re: #20620 (comment) — fixed in #22331. Default is back to public. Patching onto stable as 1.81.12.stable.2. Migration script in the PR for flipping any existing servers that got set to private. |
IP-Based Access Control for MCP Servers
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unitCI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🆕 New Feature
✅ Test
Changes