fix: preserve llm_base_url when saving MCP server config (#13225)

This commit is contained in:
Chris Bagwell
2026-03-05 19:39:58 -06:00
committed by GitHub
parent 6c394cc415
commit 41d8bd28e9
2 changed files with 70 additions and 3 deletions

View File

@@ -141,9 +141,12 @@ async def store_llm_settings(
settings.llm_api_key = existing_settings.llm_api_key
if settings.llm_model is None:
settings.llm_model = existing_settings.llm_model
# if llm_base_url is missing or empty, try to determine appropriate URL
# if llm_base_url is missing or empty, try to preserve existing or determine appropriate URL
if not settings.llm_base_url:
if is_openhands_model(settings.llm_model):
if settings.llm_base_url is None and existing_settings.llm_base_url:
# Not provided at all (e.g. MCP config save) - preserve existing
settings.llm_base_url = existing_settings.llm_base_url
elif is_openhands_model(settings.llm_model):
# OpenHands models use the LiteLLM proxy
settings.llm_base_url = LITE_LLM_API_URL
elif settings.llm_model:

View File

@@ -6,6 +6,7 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import SecretStr
from openhands.core.config.mcp_config import MCPConfig, MCPStdioServerConfig
from openhands.integrations.provider import ProviderToken
from openhands.integrations.service_types import ProviderType
from openhands.server.routes.secrets import (
@@ -193,7 +194,8 @@ async def test_store_llm_settings_partial_update():
For OpenAI models, this returns https://api.openai.com.
"""
settings = Settings(
llm_model='gpt-4' # Only updating model (not an openhands model)
llm_model='gpt-4', # Only updating model (not an openhands model)
llm_base_url='', # Explicitly cleared (e.g. basic mode save)
)
# Create existing settings
@@ -209,10 +211,72 @@ async def test_store_llm_settings_partial_update():
assert result.llm_model == 'gpt-4'
# For SecretStr objects, we need to compare the secret value
assert result.llm_api_key.get_secret_value() == 'existing-api-key'
# llm_base_url was explicitly cleared (""), so auto-detection runs
# OpenAI models: litellm.get_api_base() returns https://api.openai.com
assert result.llm_base_url == 'https://api.openai.com'
@pytest.mark.asyncio
async def test_store_llm_settings_mcp_update_preserves_base_url():
"""Test that saving MCP config (without LLM fields) preserves existing base URL.
Regression test: When adding an MCP server, the frontend sends only mcp_config
and v1_enabled. This should not wipe out the existing llm_base_url.
"""
# Simulate what the MCP add/update/delete mutations send: mcp_config but no LLM fields
settings = Settings(
mcp_config=MCPConfig(
stdio_servers=[
MCPStdioServerConfig(
name='my-server',
command='npx',
args=['-y', '@my/mcp-server'],
env={'API_TOKEN': 'secret123', 'ENDPOINT': 'https://example.com'},
)
],
),
)
# Create existing settings with a custom base URL
existing_settings = Settings(
llm_model='anthropic/claude-sonnet-4-5-20250929',
llm_api_key=SecretStr('existing-api-key'),
llm_base_url='https://my-custom-proxy.example.com',
)
result = await store_llm_settings(settings, existing_settings)
# All existing LLM settings should be preserved
assert result.llm_model == 'anthropic/claude-sonnet-4-5-20250929'
assert result.llm_api_key.get_secret_value() == 'existing-api-key'
assert result.llm_base_url == 'https://my-custom-proxy.example.com'
@pytest.mark.asyncio
async def test_store_llm_settings_no_existing_base_url_uses_auto_detection():
"""Test auto-detection kicks in only when there is no existing base URL.
When neither the incoming settings nor existing settings have a base URL,
auto-detection from litellm should be used.
"""
settings = Settings(
llm_model='gpt-4' # Not an openhands model
)
# Existing settings without a base URL
existing_settings = Settings(
llm_model='gpt-3.5',
llm_api_key=SecretStr('existing-api-key'),
)
result = await store_llm_settings(settings, existing_settings)
assert result.llm_model == 'gpt-4'
assert result.llm_api_key.get_secret_value() == 'existing-api-key'
# No existing base URL, so auto-detection should set it
assert result.llm_base_url == 'https://api.openai.com'
@pytest.mark.asyncio
async def test_store_llm_settings_anthropic_model_gets_api_base():
"""Test store_llm_settings with an Anthropic model.