Skip to content

Commit 66e0e0e

Browse files
committed
Support both "servers" and "mcpServers" keys
1 parent dd7a77d commit 66e0e0e

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

src/mcp/client/config/mcp_servers_config.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import yaml
1313
except ImportError:
1414
yaml = None # type: ignore
15-
from pydantic import BaseModel, Field, field_validator
15+
from pydantic import BaseModel, Field, field_validator, model_validator
1616

1717

1818
class MCPServerConfig(BaseModel):
@@ -82,7 +82,22 @@ class SSEServerConfig(MCPServerConfig):
8282
class MCPServersConfig(BaseModel):
8383
"""Configuration for multiple MCP servers."""
8484

85-
servers: dict[str, ServerConfigUnion] = Field(alias="mcpServers")
85+
servers: dict[str, ServerConfigUnion]
86+
87+
@model_validator(mode="before")
88+
@classmethod
89+
def handle_field_aliases(cls, data: dict[str, Any]) -> dict[str, Any]:
90+
"""Handle both 'servers' and 'mcpServers' field names."""
91+
92+
# If 'mcpServers' exists but 'servers' doesn't, use 'mcpServers'
93+
if "mcpServers" in data and "servers" not in data:
94+
data["servers"] = data["mcpServers"]
95+
del data["mcpServers"]
96+
# If both exist, prefer 'servers' and remove 'mcpServers'
97+
elif "mcpServers" in data and "servers" in data:
98+
del data["mcpServers"]
99+
100+
return data
86101

87102
@field_validator("servers", mode="before")
88103
@classmethod

tests/client/config/test_mcp_servers_config.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,44 @@ def test_stdio_server_with_quoted_arguments():
144144
assert mixed_quote_server.effective_command == "python"
145145
expected_args_mixed = ["-m", "my_server", "--name", "My Server", "--path", "/home/user/my path"]
146146
assert mixed_quote_server.effective_args == expected_args_mixed
147+
148+
149+
def test_both_field_names_supported():
150+
"""Test that both 'servers' and 'mcpServers' field names are supported."""
151+
# Test with 'mcpServers' field name (traditional format)
152+
config_with_mcp_servers = MCPServersConfig.model_validate(
153+
{"mcpServers": {"test_server": {"command": "python -m test_server", "type": "stdio"}}}
154+
)
155+
156+
# Test with 'servers' field name (new format)
157+
config_with_servers = MCPServersConfig.model_validate(
158+
{"servers": {"test_server": {"command": "python -m test_server", "type": "stdio"}}}
159+
)
160+
161+
# Both should produce identical results
162+
assert config_with_mcp_servers.servers == config_with_servers.servers
163+
assert "test_server" in config_with_mcp_servers.servers
164+
assert "test_server" in config_with_servers.servers
165+
166+
# Verify the server configurations are correct
167+
server1 = config_with_mcp_servers.servers["test_server"]
168+
server2 = config_with_servers.servers["test_server"]
169+
170+
assert isinstance(server1, StdioServerConfig)
171+
assert isinstance(server2, StdioServerConfig)
172+
assert server1.command == server2.command == "python -m test_server"
173+
174+
175+
def test_servers_field_takes_precedence():
176+
"""Test that 'servers' field takes precedence when both are present."""
177+
config_data = {
178+
"mcpServers": {"old_server": {"command": "python -m old_server", "type": "stdio"}},
179+
"servers": {"new_server": {"command": "python -m new_server", "type": "stdio"}},
180+
}
181+
182+
config = MCPServersConfig.model_validate(config_data)
183+
184+
# Should only have the 'servers' content, not 'mcpServers'
185+
assert "new_server" in config.servers
186+
assert "old_server" not in config.servers
187+
assert len(config.servers) == 1

0 commit comments

Comments
 (0)