Skip to content

[Feat] IP-Based Access Control for MCP Servers#20620

Merged
ishaan-jaff merged 32 commits intomainfrom
litellm_mcp_control_ui
Feb 7, 2026
Merged

[Feat] IP-Based Access Control for MCP Servers#20620
ishaan-jaff merged 32 commits intomainfrom
litellm_mcp_control_ui

Conversation

@ishaan-jaff
Copy link
Member

@ishaan-jaff ishaan-jaff commented Feb 7, 2026

IP-Based Access Control for MCP Servers

  • Add available_on_public_internet flag to MCP servers so admins can control which servers are visible to external callers vs internal-only
    • External callers (public IPs) only see servers marked as public; internal callers (private IPs) see all servers
    • Add UI for toggling the flag on create/edit, displaying Public/Internal badges, and configuring private IP ranges

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

🆕 New Feature
✅ Test

Changes

@vercel
Copy link

vercel bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Feb 7, 2026 1:59am

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 7, 2026

Greptile Overview

Greptile Summary

  • Adds IP-based access control for MCP server discovery/registry and tool endpoints: external callers only see servers marked available_on_public_internet, while internal callers can see all.
  • Introduces IPAddressUtils to derive client IP (optionally trusting X-Forwarded-For only from configured trusted proxy ranges) and new general settings for internal/trusted proxy CIDRs.
  • Extends MCP server schema/types/UI to store and manage available_on_public_internet, plus adds a dashboard tab to configure internal IP ranges.
  • Updates tests to account for IP filtering by mocking request client IP and server manager filtering behavior.

Confidence Score: 3/5

  • This PR is mergeable after addressing a couple of concrete security/performance issues in the MCP network settings UI and server filtering path.
  • Core changes are coherent (new schema field + IP-based filtering threaded through endpoints) and tests were added, but there is (1) a third-party IP-discovery call from the admin UI that leaks metadata and can break in restricted deployments, and (2) an inefficient filtering implementation that will be on hot request paths and scales poorly with server count.
  • ui/litellm-dashboard/src/components/mcp_tools/MCPNetworkSettings.tsx; litellm/proxy/_experimental/mcp_server/mcp_server_manager.py

Important Files Changed

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

Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

24 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +708 to +723
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)
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ishaan-jaff ishaan-jaff changed the title Litellm mcp control UI [Feat] IP-Based Access Control for MCP Servers Feb 7, 2026
@ishaan-jaff ishaan-jaff merged commit 9b1ccc0 into main Feb 7, 2026
10 of 13 checks passed
krrishdholakia added a commit that referenced this pull request Feb 7, 2026
…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>
krrishdholakia added a commit that referenced this pull request Feb 7, 2026
…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>
Sameerlite pushed a commit that referenced this pull request Feb 12, 2026
…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>
@andresC98
Copy link
Contributor

Just a heads up; this is a breaking change.
We tested upgrading to 1.81.12-stable.1 tag in our DEV deployment and we noticed MCPs now are not able to authenticate as a result of LiteLLM being deployed on a k8s cluster, and MCP endpoints be deployed on a different cluster, thus treating them as "external/public". As by default now with this change available_on_public_internet is false and filter_server_ids_by_ip strips the server out, unless we manually set available_on_public_internet: true MCPs stop working.

@ishaan-jaff
Copy link
Member Author

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

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.

2 participants