fix(config): support defining MCP servers via environment variables and improve logging (#10069)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Xingyao Wang
2025-08-07 10:48:44 -04:00
committed by GitHub
parent 881729b49c
commit d525c5ad93
6 changed files with 257 additions and 18 deletions

View File

@@ -74,7 +74,7 @@ class MCPStdioServerConfig(BaseModel):
args: list[str] = Field(default_factory=list)
env: dict[str, str] = Field(default_factory=dict)
@field_validator('name')
@field_validator('name', mode='before')
@classmethod
def validate_server_name(cls, v: str) -> str:
"""Validate server name for stdio MCP servers."""
@@ -91,7 +91,7 @@ class MCPStdioServerConfig(BaseModel):
return v
@field_validator('command')
@field_validator('command', mode='before')
@classmethod
def validate_command(cls, v: str) -> str:
"""Validate command for stdio MCP servers."""
@@ -114,6 +114,7 @@ class MCPStdioServerConfig(BaseModel):
"""Parse arguments from string or return list as-is.
Supports shell-like argument parsing using shlex.split().
Examples:
- "-y mcp-remote https://example.com"
- '--config "path with spaces" --debug'
@@ -189,7 +190,7 @@ class MCPSHTTPServerConfig(BaseModel):
url: str
api_key: str | None = None
@field_validator('url')
@field_validator('url', mode='before')
@classmethod
def validate_url(cls, v: str) -> str:
"""Validate URL format for MCP servers."""
@@ -202,12 +203,12 @@ class MCPConfig(BaseModel):
Attributes:
sse_servers: List of MCP SSE server configs
stdio_servers: List of MCP stdio server configs. These servers will be added to the MCP Router running inside runtime container.
shttp_servers: List of MCP HTTP server configs.
"""
sse_servers: list[MCPSSEServerConfig] = Field(default_factory=list)
stdio_servers: list[MCPStdioServerConfig] = Field(default_factory=list)
shttp_servers: list[MCPSHTTPServerConfig] = Field(default_factory=list)
model_config = ConfigDict(extra='forbid')
@staticmethod
@@ -252,8 +253,7 @@ class MCPConfig(BaseModel):
@classmethod
def from_toml_section(cls, data: dict) -> dict[str, 'MCPConfig']:
"""
Create a mapping of MCPConfig instances from a toml dictionary representing the [mcp] section.
"""Create a mapping of MCPConfig instances from a toml dictionary representing the [mcp] section.
The configuration is built from all keys in data.
@@ -306,7 +306,7 @@ class MCPConfig(BaseModel):
class OpenHandsMCPConfig:
@staticmethod
def add_search_engine(app_config: 'OpenHandsConfig') -> MCPStdioServerConfig | None:
"""Add search engine to the MCP config"""
"""Add search engine to the MCP config."""
if (
app_config.search_api_key
and app_config.search_api_key.get_secret_value().startswith('tvly-')
@@ -327,21 +327,23 @@ class OpenHandsMCPConfig:
def create_default_mcp_server_config(
host: str, config: 'OpenHandsConfig', user_id: str | None = None
) -> tuple[MCPSHTTPServerConfig | None, list[MCPStdioServerConfig]]:
"""
Create a default MCP server configuration.
"""Create a default MCP server configuration.
Args:
host: Host string
config: OpenHandsConfig
user_id: Optional user ID for the MCP server
Returns:
tuple[MCPSHTTPServerConfig | None, list[MCPStdioServerConfig]]: A tuple containing the default SHTTP server configuration (or None) and a list of MCP stdio server configurations
"""
stdio_servers = []
search_engine_stdio_server = OpenHandsMCPConfig.add_search_engine(config)
if search_engine_stdio_server:
stdio_servers.append(search_engine_stdio_server)
shttp_servers = MCPSHTTPServerConfig(url=f'http://{host}/mcp/mcp', api_key=None)
return shttp_servers, stdio_servers