diff --git a/README.md b/README.md index fd919247..9141fbc5 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,32 @@ options = ClaudeCodeOptions( ) ``` +### MCP Servers + +```python +# Configure MCP servers programmatically +options = ClaudeCodeOptions( + mcp_servers={ + "memory-server": { + "command": "npx", + "args": ["@modelcontextprotocol/server-memory"] + } + } +) + +# Use strict MCP config to ignore all file-based configurations +# This ensures ONLY your programmatically specified servers are used +options = ClaudeCodeOptions( + mcp_servers={ + "my-server": { + "command": "node", + "args": ["my-mcp-server.js"] + } + }, + strict_mcp_config=True # Ignore global/project MCP settings +) +``` + ## API Reference ### `query(prompt, options=None)` diff --git a/examples/mcp_servers.py b/examples/mcp_servers.py new file mode 100644 index 00000000..dbf31215 --- /dev/null +++ b/examples/mcp_servers.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Example demonstrating MCP (Model Context Protocol) server configuration.""" + +import anyio + +from claude_code_sdk import ( + AssistantMessage, + ClaudeCodeOptions, + ResultMessage, + TextBlock, + query, +) + + +async def with_strict_mcp_config_example(): + """Example using strict MCP configuration.""" + print("=== Strict MCP Config Example ===") + + # This ensures ONLY the MCP servers specified here will be used, + # ignoring any global or project-level MCP configurations + options = ClaudeCodeOptions( + mcp_servers={ + "memory-server": { + "command": "npx", + "args": ["@modelcontextprotocol/server-memory"], + } + }, + strict_mcp_config=True, # Ignore all file-based MCP configurations + ) + + async for message in query( + prompt="List the available MCP tools from the memory server", + options=options, + ): + if isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + print(f"Claude: {block.text}") + elif isinstance(message, ResultMessage): + print(f"\nResult: {message.subtype}") + print() + + +async def main(): + """Run the example.""" + await with_strict_mcp_config_example() + + +if __name__ == "__main__": + anyio.run(main) \ No newline at end of file diff --git a/examples/quick_start.py b/examples/quick_start.py index 37d93b04..cc1f2ccd 100644 --- a/examples/quick_start.py +++ b/examples/quick_start.py @@ -43,33 +43,10 @@ async def with_options_example(): print() -async def with_tools_example(): - """Example using tools.""" - print("=== With Tools Example ===") - - options = ClaudeCodeOptions( - allowed_tools=["Read", "Write"], - system_prompt="You are a helpful file assistant.", - ) - - async for message in query( - prompt="Create a file called hello.txt with 'Hello, World!' in it", - options=options, - ): - if isinstance(message, AssistantMessage): - for block in message.content: - if isinstance(block, TextBlock): - print(f"Claude: {block.text}") - elif isinstance(message, ResultMessage) and message.total_cost_usd > 0: - print(f"\nCost: ${message.total_cost_usd:.4f}") - print() - - async def main(): """Run all examples.""" await basic_example() await with_options_example() - await with_tools_example() if __name__ == "__main__": diff --git a/examples/using_tools.py b/examples/using_tools.py new file mode 100644 index 00000000..eef0c0aa --- /dev/null +++ b/examples/using_tools.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Example demonstrating how to use tools with Claude Code SDK.""" + +import anyio + +from claude_code_sdk import ( + AssistantMessage, + ClaudeCodeOptions, + ResultMessage, + TextBlock, + query, +) + + +async def with_tools_example(): + """Example using tools.""" + print("=== With Tools Example ===") + + options = ClaudeCodeOptions( + allowed_tools=["Read", "Write"], + system_prompt="You are a helpful file assistant.", + ) + + async for message in query( + prompt="Create a file called hello.txt with 'Hello, World!' in it", + options=options, + ): + if isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + print(f"Claude: {block.text}") + elif isinstance(message, ResultMessage) and message.total_cost_usd > 0: + print(f"\nCost: ${message.total_cost_usd:.4f}") + print() + + +async def main(): + """Run the example.""" + await with_tools_example() + + +if __name__ == "__main__": + anyio.run(main) \ No newline at end of file diff --git a/src/claude_code_sdk/_internal/transport/subprocess_cli.py b/src/claude_code_sdk/_internal/transport/subprocess_cli.py index f4fbc58a..2915ac89 100644 --- a/src/claude_code_sdk/_internal/transport/subprocess_cli.py +++ b/src/claude_code_sdk/_internal/transport/subprocess_cli.py @@ -116,6 +116,9 @@ def _build_command(self) -> list[str]: ["--mcp-config", json.dumps({"mcpServers": self._options.mcp_servers})] ) + if self._options.strict_mcp_config: + cmd.append("--strict-mcp-config") + cmd.extend(["--print", self._prompt]) return cmd diff --git a/src/claude_code_sdk/types.py b/src/claude_code_sdk/types.py index bd3c7267..5d2ce957 100644 --- a/src/claude_code_sdk/types.py +++ b/src/claude_code_sdk/types.py @@ -127,3 +127,4 @@ class ClaudeCodeOptions: model: str | None = None permission_prompt_tool_name: str | None = None cwd: str | Path | None = None + strict_mcp_config: bool = False diff --git a/tests/test_transport.py b/tests/test_transport.py index 65702bc7..e8898e4b 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -132,3 +132,24 @@ def test_receive_messages(self): # So we just verify the transport can be created and basic structure is correct assert transport._prompt == "test" assert transport._cli_path == "/usr/bin/claude" + + def test_build_command_with_strict_mcp_config(self): + """Test building CLI command with strict MCP config.""" + transport = SubprocessCLITransport( + prompt="test", + options=ClaudeCodeOptions(strict_mcp_config=True), + cli_path="/usr/bin/claude", + ) + + cmd = transport._build_command() + assert "--strict-mcp-config" in cmd + + # Test that flag is not present when False + transport_no_strict = SubprocessCLITransport( + prompt="test", + options=ClaudeCodeOptions(strict_mcp_config=False), + cli_path="/usr/bin/claude", + ) + + cmd_no_strict = transport_no_strict._build_command() + assert "--strict-mcp-config" not in cmd_no_strict diff --git a/tests/test_types.py b/tests/test_types.py index 60462923..2d6b268f 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -69,6 +69,7 @@ def test_default_options(self): assert options.permission_mode is None assert options.continue_conversation is False assert options.disallowed_tools == [] + assert options.strict_mcp_config is False def test_claude_code_options_with_tools(self): """Test Options with built-in tools."""