From f233df1852d302df374c6e04f1da988c46feb892 Mon Sep 17 00:00:00 2001 From: Sam Fu Date: Wed, 6 Aug 2025 14:13:59 -0700 Subject: [PATCH] teach ClaudeCodeOptions.mcp_servers to accept a filepath (#114) `claude`'s `--mcp-config` takes either a JSON file or string, so support this in `ClaudeCodeOptions` ``` --mcp-config Load MCP servers from a JSON file or string ``` --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Sam Fu --- .../_internal/transport/subprocess_cli.py | 14 +++- src/claude_code_sdk/types.py | 2 +- tests/test_transport.py | 72 +++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/claude_code_sdk/_internal/transport/subprocess_cli.py b/src/claude_code_sdk/_internal/transport/subprocess_cli.py index 83dac8e..66fb6eb 100644 --- a/src/claude_code_sdk/_internal/transport/subprocess_cli.py +++ b/src/claude_code_sdk/_internal/transport/subprocess_cli.py @@ -130,9 +130,17 @@ class SubprocessCLITransport(Transport): cmd.extend(["--add-dir", str(directory)]) if self._options.mcp_servers: - cmd.extend( - ["--mcp-config", json.dumps({"mcpServers": self._options.mcp_servers})] - ) + if isinstance(self._options.mcp_servers, dict): + # Dict format: serialize to JSON + cmd.extend( + [ + "--mcp-config", + json.dumps({"mcpServers": self._options.mcp_servers}), + ] + ) + else: + # String or Path format: pass directly as file path or JSON string + cmd.extend(["--mcp-config", str(self._options.mcp_servers)]) # Add extra args for future CLI flags for flag, value in self._options.extra_args.items(): diff --git a/src/claude_code_sdk/types.py b/src/claude_code_sdk/types.py index 1dfce38..fe905d5 100644 --- a/src/claude_code_sdk/types.py +++ b/src/claude_code_sdk/types.py @@ -117,7 +117,7 @@ class ClaudeCodeOptions: max_thinking_tokens: int = 8000 system_prompt: str | None = None append_system_prompt: str | None = None - mcp_servers: dict[str, McpServerConfig] = field(default_factory=dict) + mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict) permission_mode: PermissionMode | None = None continue_conversation: bool = False resume: str | None = None diff --git a/tests/test_transport.py b/tests/test_transport.py index ea5cca2..6ab1363 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -227,3 +227,75 @@ class TestSubprocessCLITransport: boolean_idx = cmd.index("--boolean-flag") # Either it's the last element or the next element is another flag assert boolean_idx == len(cmd) - 1 or cmd[boolean_idx + 1].startswith("--") + + def test_build_command_with_mcp_servers(self): + """Test building CLI command with mcp_servers option.""" + import json + + mcp_servers = { + "test-server": { + "type": "stdio", + "command": "/path/to/server", + "args": ["--option", "value"], + } + } + + transport = SubprocessCLITransport( + prompt="test", + options=ClaudeCodeOptions(mcp_servers=mcp_servers), + cli_path="/usr/bin/claude", + ) + + cmd = transport._build_command() + + # Find the --mcp-config flag and its value + assert "--mcp-config" in cmd + mcp_idx = cmd.index("--mcp-config") + mcp_config_value = cmd[mcp_idx + 1] + + # Parse the JSON and verify structure + config = json.loads(mcp_config_value) + assert "mcpServers" in config + assert config["mcpServers"] == mcp_servers + + def test_build_command_with_mcp_servers_as_file_path(self): + """Test building CLI command with mcp_servers as file path.""" + from pathlib import Path + + # Test with string path + transport = SubprocessCLITransport( + prompt="test", + options=ClaudeCodeOptions(mcp_servers="/path/to/mcp-config.json"), + cli_path="/usr/bin/claude", + ) + + cmd = transport._build_command() + assert "--mcp-config" in cmd + mcp_idx = cmd.index("--mcp-config") + assert cmd[mcp_idx + 1] == "/path/to/mcp-config.json" + + # Test with Path object + transport = SubprocessCLITransport( + prompt="test", + options=ClaudeCodeOptions(mcp_servers=Path("/path/to/mcp-config.json")), + cli_path="/usr/bin/claude", + ) + + cmd = transport._build_command() + assert "--mcp-config" in cmd + mcp_idx = cmd.index("--mcp-config") + assert cmd[mcp_idx + 1] == "/path/to/mcp-config.json" + + def test_build_command_with_mcp_servers_as_json_string(self): + """Test building CLI command with mcp_servers as JSON string.""" + json_config = '{"mcpServers": {"server": {"type": "stdio", "command": "test"}}}' + transport = SubprocessCLITransport( + prompt="test", + options=ClaudeCodeOptions(mcp_servers=json_config), + cli_path="/usr/bin/claude", + ) + + cmd = transport._build_command() + assert "--mcp-config" in cmd + mcp_idx = cmd.index("--mcp-config") + assert cmd[mcp_idx + 1] == json_config