Skip to content

Commit a707aeb

Browse files
committed
Use shlex.split to parse command
instead of str.split, which breaks when there are quoted arguments with spaces in them.
1 parent 68b1a2e commit a707aeb

File tree

2 files changed

+46
-2
lines changed

2 files changed

+46
-2
lines changed

src/mcp/client/config/mcp_servers_config.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# stdlib imports
44
import json
5+
import shlex
56
from pathlib import Path
67
from typing import Annotated, Any, Literal
78

@@ -27,15 +28,19 @@ class StdioServerConfig(MCPServerConfig):
2728
args: list[str] | None = None
2829
env: dict[str, str] | None = None
2930

31+
def _parse_command(self) -> list[str]:
32+
"""Parse the command string into parts, handling quotes properly."""
33+
return shlex.split(self.command)
34+
3035
@property
3136
def effective_command(self) -> str:
3237
"""Get the effective command (first part of the command string)."""
33-
return self.command.split()[0]
38+
return self._parse_command()[0]
3439

3540
@property
3641
def effective_args(self) -> list[str]:
3742
"""Get the effective arguments (parsed from command plus explicit args)."""
38-
command_parts = self.command.split()
43+
command_parts = self._parse_command()
3944
parsed_args = command_parts[1:] if len(command_parts) > 1 else []
4045
explicit_args = self.args or []
4146
return parsed_args + explicit_args

tests/client/config/test_mcp_servers_config.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,42 @@ def test_streamable_http_server_with_headers(mcp_config_file: Path):
106106
assert http_server.url == "https://api.example.com/mcp"
107107
assert http_server.headers == {"Authorization": "Bearer token123"}
108108
assert http_server.type == "streamable_http" # Should be automatically inferred
109+
110+
111+
def test_stdio_server_with_quoted_arguments():
112+
"""Test that stdio servers handle quoted arguments with spaces correctly."""
113+
# Test with double quotes
114+
config_data = {
115+
"mcpServers": {
116+
"server_with_double_quotes": {"command": 'python -m my_server --config "path with spaces/config.json"'},
117+
"server_with_single_quotes": {
118+
"command": "python -m my_server --config 'another path with spaces/config.json'"
119+
},
120+
"server_with_mixed_quotes": {
121+
"command": "python -m my_server --name \"My Server\" --path '/home/user/my path'"
122+
},
123+
}
124+
}
125+
126+
config = MCPServersConfig.model_validate(config_data)
127+
128+
# Test double quotes
129+
double_quote_server = config.servers["server_with_double_quotes"]
130+
assert isinstance(double_quote_server, StdioServerConfig)
131+
assert double_quote_server.effective_command == "python"
132+
expected_args_double = ["-m", "my_server", "--config", "path with spaces/config.json"]
133+
assert double_quote_server.effective_args == expected_args_double
134+
135+
# Test single quotes
136+
single_quote_server = config.servers["server_with_single_quotes"]
137+
assert isinstance(single_quote_server, StdioServerConfig)
138+
assert single_quote_server.effective_command == "python"
139+
expected_args_single = ["-m", "my_server", "--config", "another path with spaces/config.json"]
140+
assert single_quote_server.effective_args == expected_args_single
141+
142+
# Test mixed quotes
143+
mixed_quote_server = config.servers["server_with_mixed_quotes"]
144+
assert isinstance(mixed_quote_server, StdioServerConfig)
145+
assert mixed_quote_server.effective_command == "python"
146+
expected_args_mixed = ["-m", "my_server", "--name", "My Server", "--path", "/home/user/my path"]
147+
assert mixed_quote_server.effective_args == expected_args_mixed

0 commit comments

Comments
 (0)