Claude Agent SDK for Python

repository·main·Indexed 27 days ago

https://github.com/anthropics/claude-agent-sdk-python

A Python SDK for Claude Code (version 0.2.128) used to build and test Claude-powered agents. It provides tools for asynchronous queries via the query() function and the ClaudeSDKClient for stateful, interactive conversations. The SDK supports custom in-process MCP tools using the @tool decorator, agent behavior interception via Hooks, and granular control over tool permissions and AI model selection.

Tokens
7.6K
Snippets
15
Records
39
Agent score
92%

What's inside claude-agent-sdk

  1. Create custom in-process tools using @tool

    main

    The ClaudeSDKClient allows you to define custom tools as in-process MCP servers. This is more performant than external MCP servers because it runs in the same process without IPC overhead.

    Use the @tool decorator to define a tool, then wrap it using create_sdk_mcp_server. To ensure the tool runs without a permission prompt, add its identifier (formatted as mcp__<server_name>__<tool_name>) to allowed_tools.

    from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions, ClaudeSDKClient
    
    # Define a tool using the @tool decorator
    @tool("greet", "Greet a user", {"name": str})
    async def greet_user(args):
        return {
            "content": [
                {"type": "text", "text": f"Hello, {args['name']}!"}
            ]
        }
    
    # Create an SDK MCP server
    server = create_sdk_mcp_server(
        name="my-tools",
        version="1.0.0",
        tools=[greet_user]
    )
    
    # Use it with Claude. allowed_tools pre-approves the tool
    options = ClaudeAgentOptions(
        mcp_servers={"tools": server},
        allowed_tools=["mcp__tools__greet"]
    )
    
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Greet Alice")
    
        # Extract and print response
        async for msg in client.receive_response():
            print(msg)
  2. Add new E2E tests to the suite

    main

    When contributing new end-to-end tests, follow these guidelines:

    1. Decorate tests with @pytest.mark.e2e.
    2. Use the api_key fixture to ensure the API key is available.
    3. Use simple prompts to minimize API costs.
    4. Verify actual tool execution (checking for ToolUseBlock in responses) rather than relying on mocked responses.
  3. Intercept agent behavior with Hooks

    main

    Hooks are Python functions invoked by the Claude Code application at specific points in the agent loop (e.g., PreToolUse). You can use HookMatcher to target specific tools. Hooks can return a dictionary containing a permissionDecision (e.g., 'deny') to programmatically control the agent.

    from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, HookMatcher
    
    async def check_bash_command(input_data, tool_use_id, context):
        tool_name = input_data["tool_name"]
        tool_input = input_data["tool_input"]
        if tool_name != "Bash":
            return {}
        command = tool_input.get("command", "")
        block_patterns = ["foo.sh"]
        for pattern in block_patterns:
            if pattern in command:
                return {
                    "hookSpecificOutput": {
                        "hookEventName": "PreToolUse",
                        "permissionDecision": "deny",
                        "permissionDecisionReason": f"Command contains invalid pattern: {pattern}",
                    }
                }
        return {}
    
    options = ClaudeAgentOptions(
        allowed_tools=["Bash"],
        hooks={
            "PreToolUse": [
                HookMatcher(matcher="Bash", hooks=[check_bash_command]),
            ],
        }
    )
    
    async with ClaudeSDKClient(options=options) as client:
        # This will be blocked by the hook
        await client.query("Run the bash command: ./foo.sh --help")
        async for msg in client.receive_response():
            print(msg)
  4. Install the Claude Agent SDK

    main

    Install the SDK using pip. The Claude Code CLI is automatically bundled with the package, so no separate installation is required for default usage.

    Prerequisites:

    • Python 3.10+

    If you want to use a system-wide installation of Claude Code or a specific version, you can install it separately via curl -fsSL https://claude.ai/install.sh | bash and then specify the path in ClaudeAgentOptions(cli_path="/path/to/claude").

    pip install claude-agent-sdk
  5. Quick Start with query()

    main

    Use the query() function for simple, asynchronous queries to Claude. It returns an AsyncIterator of response messages.

    import anyio
    from claude_agent_sdk import query
    
    async def main():
        async for message in query(prompt="What is 2 + 2?"):
            print(message)
    
    anyio.run(main)
  6. Run Claude Code SDK E2E tests

    main

    You can run the end-to-end tests using pytest. Note that these tests make actual API calls to Claude and will incur costs on your Anthropic account.

    # Run all e2e tests
    python -m pytest e2e-tests/ -v
    
    # Run only tests marked with the 'e2e' marker
    python -m pytest e2e-tests/ -v -m e2e
    
    # Run a specific test case
    python -m pytest e2e-tests/test_mcp_calculator.py::test_basic_addition -v
  7. Use ClaudeSDKClient for interactive conversations

    main

    The ClaudeSDKClient is designed for bidirectional, stateful, and interactive conversations with Claude Code. It supports streaming, interrupts, and dynamic message sending. Use this client when building chat interfaces, debugging sessions, or multi-turn conversations where you need to react to Claude's responses in real-time.

    Note on Async Contexts: As of v0.0.20, a ClaudeSDKClient instance cannot be used across different async runtime contexts (e.g., different trio nurseries or asyncio task groups). You must complete all operations within the same async context where it was connected.

    async with ClaudeSDKClient() as client:
        await client.query("Help me analyze this codebase")
        # Perform interactive tasks here
  8. Configure tool permissions and working directory

    main

    Claude has access to a default toolset (Read, Write, Edit, Bash, etc.). Use ClaudeAgentOptions to manage these:

    • allowed_tools: A list of tools that are auto-approved.
    • disallowed_tools: Tools to block.
    • permission_mode: Controls how unlisted tools are handled (e.g., 'acceptEdits' to auto-accept file edits).
    • cwd: Sets the working directory for the agent.

    Note: allowed_tools does not remove tools from the toolset; it only manages auto-approval.

    from pathlib import Path
    from claude_agent_sdk import ClaudeAgentOptions
    
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write", "Bash"],
        permission_mode='acceptEdits',
        cwd="/path/to/project"  # or Path("/path/to/project")
    )
  9. Troubleshoot E2E test failures

    main

    Common issues when running E2E tests include:

    • Missing API Key: If you see "ANTHROPIC_API_KEY environment variable is required", ensure you have exported the key using export ANTHROPIC_API_KEY=sk-ant-....
    • Timeouts: Verify your API key is valid, has available quota, and that you have network connectivity to api.anthropic.com.
    • Permission Denied: Ensure the allowed_tools parameter includes the necessary MCP tools and that tool names match the expected format (e.g., mcp__calc__add).
  10. Use query() with options and message parsing

    main

    You can pass ClaudeAgentOptions to query() to configure the system prompt and maximum turns. When iterating through messages, you can check for specific types like AssistantMessage and TextBlock to extract text content.

    from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock
    
    # Simple query with type checking
    async for message in query(prompt="Hello Claude"):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, TextBlock):
                    print(block.text)
    
    # Query with options
    options = ClaudeAgentOptions(
        system_prompt="You are a helpful assistant",
        max_turns=1
    )
    
    async for message in query(prompt="Tell me a joke", options=options):
        print(message)
  11. Configure Sandbox Settings

    main

    The SandboxSettings configuration controls how Claude Code sandboxes bash commands for filesystem and network isolation.

    Important: Filesystem and network restrictions are configured via permission rules, not via these sandbox settings:

    • Filesystem read restrictions: Use Read deny rules.
    • Filesystem write restrictions: Use Edit allow/deny rules.
    • Network restrictions: Use WebFetch allow/deny rules.

    Available attributes:

    • enabled: Enable bash sandboxing (macOS/Linux only). Default: False.
    • autoAllowBashIfSandboxed: Auto-approve bash commands when sandboxed. Default: True.
    • excludedCommands: Commands that should run outside the sandbox (e.g., ["git", "docker"]).
    • allowUnsandboxedCommands: If False, all commands must run sandboxed or be in excludedCommands. Default: True.
    • network: A SandboxNetworkConfig object.
    • ignoreViolations: A SandboxIgnoreViolations object.
    • enableWeakerNestedSandbox: Enable weaker sandbox for unprivileged Docker environments (Linux only). Default: False.
    sandbox_settings: SandboxSettings = {
        "enabled": True,
        "autoAllowBashIfSandboxed": True,
        "excludedCommands": ["docker"],
        "network": {
            "allowUnixSockets": ["/var/run/docker.sock"],
            "allowLocalBinding": True
        }
    }