Files
wild-cloud/ai_context/claude_code/CLAUDE_CODE_SDK.md

54 KiB
Raw Blame History

Claude Code SDK

Build custom AI agents with the Claude Code SDK

Why use the Claude Code SDK?

The Claude Code SDK provides all the building blocks you need to build production-ready agents:

  • Optimized Claude integration: Automatic prompt caching and performance optimizations
  • Rich tool ecosystem: File operations, code execution, web search, and MCP extensibility
  • Advanced permissions: Fine-grained control over agent capabilities
  • Production essentials: Built-in error handling, session management, and monitoring

What can you build with the SDK?

Here are some example agent types you can create:

Coding agents:

  • SRE agents that diagnose and fix production issues
  • Security review bots that audit code for vulnerabilities
  • Oncall engineering assistants that triage incidents
  • Code review agents that enforce style and best practices

Business agents:

  • Legal assistants that review contracts and compliance
  • Finance advisors that analyze reports and forecasts
  • Customer support agents that resolve technical issues
  • Content creation assistants for marketing teams

The SDK is currently available in TypeScript and Python, with a command line interface (CLI) for quick prototyping.

Quick start

Get your first agent running in under 5 minutes:

Install `@anthropic-ai/claude-code` from NPM:
    ```bash
    npm install -g @anthropic-ai/claude-code
    ```
  </Tab>

  <Tab title="TypeScript">
    Install `@anthropic-ai/claude-code` from NPM:

    ```bash
    npm install -g @anthropic-ai/claude-code
    ```
  </Tab>

  <Tab title="Python">
    Install `claude-code-sdk` from PyPI and `@anthropic-ai/claude-code` from NPM:

    ```bash
    pip install claude-code-sdk
    npm install -g @anthropic-ai/claude-code  # Required dependency
    ```

    (Optional) Install IPython for interactive development:

    ```bash
    pip install ipython
    ```
  </Tab>
</Tabs>
Get your API key from the [Anthropic Console](https://console.anthropic.com/) and set the `ANTHROPIC_API_KEY` environment variable:
```bash
export ANTHROPIC_API_KEY="your-api-key-here"
```
Create one of these example agents:
<Tabs>
  <Tab title="Command line">
    ```bash
    # Create a simple legal assistant
    claude -p "Review this contract clause for potential issues: 'The party agrees to unlimited liability...'" \
      --append-system-prompt "You are a legal assistant. Identify risks and suggest improvements."
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts
    // legal-agent.ts
    import { query } from "@anthropic-ai/claude-code";

    // Create a simple legal assistant
    for await (const message of query({
      prompt: "Review this contract clause for potential issues: 'The party agrees to unlimited liability...'",
      options: {
        systemPrompt: "You are a legal assistant. Identify risks and suggest improvements.",
        maxTurns: 2
      }
    })) {
      if (message.type === "result") {
        console.log(message.result);
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python
    # legal-agent.py
    import asyncio
    from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions

    async def main():
        async with ClaudeSDKClient(
            options=ClaudeCodeOptions(
                system_prompt="You are a legal assistant. Identify risks and suggest improvements.",
                max_turns=2
            )
        ) as client:
            # Send the query
            await client.query(
                "Review this contract clause for potential issues: 'The party agrees to unlimited liability...'"
            )

            # Stream the response
            async for message in client.receive_response():
                if hasattr(message, 'content'):
                    # Print streaming content as it arrives
                    for block in message.content:
                        if hasattr(block, 'text'):
                            print(block.text, end='', flush=True)

    if __name__ == "__main__":
        asyncio.run(main())
    ```
  </Tab>
</Tabs>
Copy and paste the command above directly into your terminal.
  <Tab title="TypeScript">
    1. Set up project:

    ```bash
    npm init -y
    npm install @anthropic-ai/claude-code tsx
    ```

    2. Add `"type": "module"` to your package.json

    3. Save the code above as `legal-agent.ts`, then run:

    ```bash
    npx tsx legal-agent.ts
    ```
  </Tab>

  <Tab title="Python">
    Save the code above as `legal-agent.py`, then run:

    ```bash
    python legal-agent.py
    ```

    For [IPython](https://ipython.org/)/Jupyter notebooks, you can run the code directly in a cell:

    ```python
    await main()
    ```
  </Tab>
</Tabs>

Each example above creates a working agent that will:

  • Analyze the prompt using Claude's reasoning capabilities
  • Plan a multi-step approach to solve the problem
  • Execute actions using tools like file operations, bash commands, and web search
  • Provide actionable recommendations based on the analysis

Core usage

Overview

The Claude Code SDK allows you to interface with Claude Code in non-interactive mode from your applications.

**Prerequisites**
* Node.js 18+
* `@anthropic-ai/claude-code` from NPM

**Basic usage**

The primary command-line interface to Claude Code is the `claude` command. Use the `--print` (or `-p`) flag to run in non-interactive mode and print the final result:

```bash
claude -p "Analyze system performance" \
  --append-system-prompt "You are a performance engineer" \
  --allowedTools "Bash,Read,WebSearch" \
  --permission-mode acceptEdits \
  --cwd /path/to/project
```

**Configuration**

The SDK leverages all the CLI options available in Claude Code. Here are the key ones for SDK usage:

| Flag                       | Description                                                                                            | Example                                                                                                                   |
| :------------------------- | :----------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
| `--print`, `-p`            | Run in non-interactive mode                                                                            | `claude -p "query"`                                                                                                       |
| `--output-format`          | Specify output format (`text`, `json`, `stream-json`)                                                  | `claude -p --output-format json`                                                                                          |
| `--resume`, `-r`           | Resume a conversation by session ID                                                                    | `claude --resume abc123`                                                                                                  |
| `--continue`, `-c`         | Continue the most recent conversation                                                                  | `claude --continue`                                                                                                       |
| `--verbose`                | Enable verbose logging                                                                                 | `claude --verbose`                                                                                                        |
| `--append-system-prompt`   | Append to system prompt (only with `--print`)                                                          | `claude --append-system-prompt "Custom instruction"`                                                                      |
| `--allowedTools`           | Space-separated list of allowed tools, or <br /><br /> string of comma-separated list of allowed tools | `claude --allowedTools mcp__slack mcp__filesystem`<br /><br />`claude --allowedTools "Bash(npm install),mcp__filesystem"` |
| `--disallowedTools`        | Space-separated list of denied tools, or <br /><br /> string of comma-separated list of denied tools   | `claude --disallowedTools mcp__splunk mcp__github`<br /><br />`claude --disallowedTools "Bash(git commit),mcp__github"`   |
| `--mcp-config`             | Load MCP servers from a JSON file                                                                      | `claude --mcp-config servers.json`                                                                                        |
| `--permission-prompt-tool` | MCP tool for handling permission prompts (only with `--print`)                                         | `claude --permission-prompt-tool mcp__auth__prompt`                                                                       |

For a complete list of CLI options and features, see the [CLI reference](/en/docs/claude-code/cli-reference) documentation.
**Prerequisites**
* Node.js 18+
* `@anthropic-ai/claude-code` from NPM

<Note>
  To view the TypeScript SDK source code, visit the [`@anthropic-ai/claude-code` page on NPM](https://www.npmjs.com/package/@anthropic-ai/claude-code?activeTab=code).
</Note>

**Basic usage**

The primary interface via the TypeScript SDK is the `query` function, which returns an async iterator that streams messages as they arrive:

```ts
import { query } from "@anthropic-ai/claude-code";

for await (const message of query({
  prompt: "Analyze system performance",
  abortController: new AbortController(),
  options: {
    maxTurns: 5,
    systemPrompt: "You are a performance engineer",
    allowedTools: ["Bash", "Read", "WebSearch"]
  }
})) {
  if (message.type === "result") {
    console.log(message.result);
  }
}
```

**Configuration**

The TypeScript SDK accepts all arguments supported by the [command line](/en/docs/claude-code/cli-reference), as well as the following additional options:

| Argument                     | Description                         | Default                                                                              |
| :--------------------------- | :---------------------------------- | :----------------------------------------------------------------------------------- |
| `abortController`            | Abort controller                    | `new AbortController()`                                                              |
| `cwd`                        | Current working directory           | `process.cwd()`                                                                      |
| `executable`                 | Which JavaScript runtime to use     | `node` when running with Node.js, `bun` when running with Bun                        |
| `executableArgs`             | Arguments to pass to the executable | `[]`                                                                                 |
| `pathToClaudeCodeExecutable` | Path to the Claude Code executable  | Executable that ships with `@anthropic-ai/claude-code`                               |
| `permissionMode`             | Permission mode for the session     | `"default"` (options: `"default"`, `"acceptEdits"`, `"plan"`, `"bypassPermissions"`) |
**Prerequisites**
* Python 3.10+
* `claude-code-sdk` from PyPI
* Node.js 18+
* `@anthropic-ai/claude-code` from NPM

<Note>
  To view the Python SDK source code, see the [`claude-code-sdk`](https://github.com/anthropics/claude-code-sdk-python) repo.
</Note>

<Tip>
  For interactive development, use [IPython](https://ipython.org/): `pip install ipython`
</Tip>

**Basic usage**

The Python SDK provides two primary interfaces:

1. The `ClaudeSDKClient` class (Recommended)

Best for streaming responses, multi-turn conversations, and interactive applications:

```python
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions

async def main():
    async with ClaudeSDKClient(
        options=ClaudeCodeOptions(
            system_prompt="You are a performance engineer",
            allowed_tools=["Bash", "Read", "WebSearch"],
            max_turns=5
        )
    ) as client:
        await client.query("Analyze system performance")

        # Stream responses
        async for message in client.receive_response():
            if hasattr(message, 'content'):
                for block in message.content:
                    if hasattr(block, 'text'):
                        print(block.text, end='', flush=True)

# Run as script
asyncio.run(main())

# Or in IPython/Jupyter: await main()
```

The SDK also supports passing structured messages and image inputs:

```python
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions

async with ClaudeSDKClient() as client:
    # Text message
    await client.query("Analyze this code for security issues")

    # Message with image reference (image will be read by Claude's Read tool)
    await client.query("Explain what's shown in screenshot.png")

    # Multiple messages in sequence
    messages = [
        "First, analyze the architecture diagram in diagram.png",
        "Now suggest improvements based on the diagram",
        "Finally, generate implementation code"
    ]

    for msg in messages:
        await client.query(msg)
        async for response in client.receive_response():
            # Process each response
            pass

# The SDK handles image files through Claude's built-in Read tool
# Supported formats: PNG, JPG, PDF, and other common formats
```

<Note>
  The Python examples on this page use `asyncio`, but you can also use `anyio`.
</Note>

2. The `query` function

For simple, one-shot queries:

```python
from claude_code_sdk import query, ClaudeCodeOptions

async for message in query(
    prompt="Analyze system performance",
    options=ClaudeCodeOptions(system_prompt="You are a performance engineer")
):
    if type(message).__name__ == "ResultMessage":
        print(message.result)
```

**Configuration**

As the Python SDK accepts all arguments supported by the [command line](/en/docs/claude-code/cli-reference) through the `ClaudeCodeOptions` class.

Authentication

Anthropic API key

For basic authentication, retrieve an Anthropic API key from the Anthropic Console and set the ANTHROPIC_API_KEY environment variable, as demonstrated in the Quick start.

Third-party API credentials

The SDK also supports authentication via third-party API providers:

  • Amazon Bedrock: Set CLAUDE_CODE_USE_BEDROCK=1 environment variable and configure AWS credentials
  • Google Vertex AI: Set CLAUDE_CODE_USE_VERTEX=1 environment variable and configure Google Cloud credentials

For detailed configuration instructions for third-party providers, see the Amazon Bedrock and Google Vertex AI documentation.

Multi-turn conversations

For multi-turn conversations, you can resume conversations or continue from the most recent session:

```bash # Continue the most recent conversation claude --continue "Now refactor this for better performance"
# Resume a specific conversation by session ID
claude --resume 550e8400-e29b-41d4-a716-446655440000 "Update the tests"

# Resume in non-interactive mode
claude --resume 550e8400-e29b-41d4-a716-446655440000 "Fix all linting issues" --no-interactive
```
```ts import { query } from "@anthropic-ai/claude-code";
// Continue most recent conversation
for await (const message of query({
  prompt: "Now refactor this for better performance",
  options: { continueSession: true }
})) {
  if (message.type === "result") console.log(message.result);
}

// Resume specific session
for await (const message of query({
  prompt: "Update the tests",
  options: {
    resumeSessionId: "550e8400-e29b-41d4-a716-446655440000",
    maxTurns: 3
  }
})) {
  if (message.type === "result") console.log(message.result);
}
```
```python import asyncio from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions, query
# Method 1: Using ClaudeSDKClient for persistent conversations
async def multi_turn_conversation():
    async with ClaudeSDKClient() as client:
        # First query
        await client.query("Let's refactor the payment module")
        async for msg in client.receive_response():
            # Process first response
            pass

        # Continue in same session
        await client.query("Now add comprehensive error handling")
        async for msg in client.receive_response():
            # Process continuation
            pass

        # The conversation context is maintained throughout

# Method 2: Using query function with session management
async def resume_session():
    # Continue most recent conversation
    async for message in query(
        prompt="Now refactor this for better performance",
        options=ClaudeCodeOptions(continue_conversation=True)
    ):
        if type(message).__name__ == "ResultMessage":
            print(message.result)

    # Resume specific session
    async for message in query(
        prompt="Update the tests",
        options=ClaudeCodeOptions(
            resume="550e8400-e29b-41d4-a716-446655440000",
            max_turns=3
        )
    ):
        if type(message).__name__ == "ResultMessage":
            print(message.result)

# Run the examples
asyncio.run(multi_turn_conversation())
```

Using Plan Mode

Plan Mode allows Claude to analyze code without making modifications, useful for code reviews and planning changes.

```bash claude -p "Review this code" --permission-mode plan ``` ```ts import { query } from "@anthropic-ai/claude-code";
for await (const message of query({
  prompt: "Your prompt here",
  options: {
    permissionMode: 'plan'
  }
})) {
  if (message.type === "result") {
    console.log(message.result);
  }
}
```
```python from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async with ClaudeSDKClient(
    options=ClaudeCodeOptions(permission_mode='plan')
) as client:
    await client.query("Your prompt here")
```
Plan Mode restricts editing, file creation, and command execution. See [permission modes](/en/docs/claude-code/iam#permission-modes) for details.

Custom system prompts

System prompts define your agent's role, expertise, and behavior. This is where you specify what kind of agent you're building:

```bash # SRE incident response agent claude -p "API is down, investigate" \ --append-system-prompt "You are an SRE expert. Diagnose issues systematically and provide actionable solutions."
# Legal document review agent
claude -p "Review this contract" \
  --append-system-prompt "You are a corporate lawyer. Identify risks, suggest improvements, and ensure compliance."

# Append to default system prompt
claude -p "Refactor this function" \
  --append-system-prompt "Always include comprehensive error handling and unit tests."
```
```ts import { query } from "@anthropic-ai/claude-code";
// SRE incident response agent
for await (const message of query({
  prompt: "API is down, investigate",
  options: {
    systemPrompt: "You are an SRE expert. Diagnose issues systematically and provide actionable solutions.",
    maxTurns: 3
  }
})) {
  if (message.type === "result") console.log(message.result);
}

// Append to default system prompt
for await (const message of query({
  prompt: "Refactor this function",
  options: {
    appendSystemPrompt: "Always include comprehensive error handling and unit tests.",
    maxTurns: 2
  }
})) {
  if (message.type === "result") console.log(message.result);
}
```
```python import asyncio from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def specialized_agents():
    # SRE incident response agent with streaming
    async with ClaudeSDKClient(
        options=ClaudeCodeOptions(
            system_prompt="You are an SRE expert. Diagnose issues systematically and provide actionable solutions.",
            max_turns=3
        )
    ) as sre_agent:
        await sre_agent.query("API is down, investigate")

        # Stream the diagnostic process
        async for message in sre_agent.receive_response():
            if hasattr(message, 'content'):
                for block in message.content:
                    if hasattr(block, 'text'):
                        print(block.text, end='', flush=True)

    # Legal review agent with custom prompt
    async with ClaudeSDKClient(
        options=ClaudeCodeOptions(
            append_system_prompt="Always include comprehensive error handling and unit tests.",
            max_turns=2
        )
    ) as dev_agent:
        await dev_agent.query("Refactor this function")

        # Collect full response
        full_response = []
        async for message in dev_agent.receive_response():
            if type(message).__name__ == "ResultMessage":
                print(message.result)

asyncio.run(specialized_agents())
```

Advanced Usage

Custom tools via MCP

The Model Context Protocol (MCP) lets you give your agents custom tools and capabilities. This is crucial for building specialized agents that need domain-specific integrations.

Example agent tool configurations:

{
  "mcpServers": {
    "slack": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-slack"],
      "env": { "SLACK_TOKEN": "your-slack-token" }
    },
    "jira": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-jira"],
      "env": { "JIRA_TOKEN": "your-jira-token" }
    },
    "database": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres"],
      "env": { "DB_CONNECTION_STRING": "your-db-url" }
    }
  }
}

Usage examples:

```bash # SRE agent with monitoring tools claude -p "Investigate the payment service outage" \ --mcp-config sre-tools.json \ --allowedTools "mcp__datadog,mcp__pagerduty,mcp__kubernetes" \ --append-system-prompt "You are an SRE. Use monitoring data to diagnose issues."
# Customer support agent with CRM access
claude -p "Help resolve customer ticket #12345" \
  --mcp-config support-tools.json \
  --allowedTools "mcp__zendesk,mcp__stripe,mcp__user_db" \
  --append-system-prompt "You are a technical support specialist."
```
```ts import { query } from "@anthropic-ai/claude-code";
// SRE agent with monitoring tools
for await (const message of query({
  prompt: "Investigate the payment service outage",
  options: {
    mcpConfig: "sre-tools.json",
    allowedTools: ["mcp__datadog", "mcp__pagerduty", "mcp__kubernetes"],
    systemPrompt: "You are an SRE. Use monitoring data to diagnose issues.",
    maxTurns: 4
  }
})) {
  if (message.type === "result") console.log(message.result);
}
```
```python import asyncio from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def mcp_enabled_agent():
    # Legal agent with document access and streaming
    # Note: Configure your MCP servers as needed
    mcp_servers = {
        # Example configuration - uncomment and configure as needed:
        # "docusign": {
        #     "command": "npx",
        #     "args": ["-y", "@modelcontextprotocol/server-docusign"],
        #     "env": {"API_KEY": "your-key"}
        # }
    }

    async with ClaudeSDKClient(
        options=ClaudeCodeOptions(
            mcp_servers=mcp_servers,
            allowed_tools=["mcp__docusign", "mcp__compliance_db"],
            system_prompt="You are a corporate lawyer specializing in contract review.",
            max_turns=4
        )
    ) as client:
        await client.query("Review this contract for compliance risks")

        # Monitor tool usage and responses
        async for message in client.receive_response():
            if hasattr(message, 'content'):
                for block in message.content:
                    if hasattr(block, 'type'):
                        if block.type == 'tool_use':
                            print(f"\n[Using tool: {block.name}]\n")
                        elif hasattr(block, 'text'):
                            print(block.text, end='', flush=True)
                    elif hasattr(block, 'text'):
                        print(block.text, end='', flush=True)

            if type(message).__name__ == "ResultMessage":
                print(f"\n\nReview complete. Total cost: ${message.total_cost_usd:.4f}")

asyncio.run(mcp_enabled_agent())
```
When using MCP tools, you must explicitly allow them using the `--allowedTools` flag. MCP tool names follow the pattern `mcp____` where:
  • serverName is the key from your MCP configuration file
  • toolName is the specific tool provided by that server

This security measure ensures that MCP tools are only used when explicitly permitted.

If you specify just the server name (i.e., mcp__<serverName>), all tools from that server will be allowed.

Glob patterns (e.g., mcp__go*) are not supported.

Custom permission prompt tool

Optionally, use --permission-prompt-tool to pass in an MCP tool that we will use to check whether or not the user grants the model permissions to invoke a given tool. When the model invokes a tool the following happens:

  1. We first check permission settings: all settings.json files, as well as --allowedTools and --disallowedTools passed into the SDK; if one of these allows or denies the tool call, we proceed with the tool call
  2. Otherwise, we invoke the MCP tool you provided in --permission-prompt-tool

The --permission-prompt-tool MCP tool is passed the tool name and input, and must return a JSON-stringified payload with the result. The payload must be one of:

// tool call is allowed
{
  "behavior": "allow",
  "updatedInput": {...}, // updated input, or just return back the original input
}

// tool call is denied
{
  "behavior": "deny",
  "message": "..." // human-readable string explaining why the permission was denied
}

Implementation examples:

```bash # Use with your MCP server configuration claude -p "Analyze and fix the security issues" \ --permission-prompt-tool mcp__security__approval_prompt \ --mcp-config security-tools.json \ --allowedTools "Read,Grep" \ --disallowedTools "Bash(rm*),Write"
# With custom permission rules
claude -p "Refactor the codebase" \
  --permission-prompt-tool mcp__custom__permission_check \
  --mcp-config custom-config.json \
  --output-format json
```
```ts const server = new McpServer({ name: "Test permission prompt MCP Server", version: "0.0.1", });
server.tool(
  "approval_prompt",
  'Simulate a permission check - approve if the input contains "allow", otherwise deny',
  {
    tool_name: z.string().describe("The name of the tool requesting permission"),
    input: z.object({}).passthrough().describe("The input for the tool"),
    tool_use_id: z.string().optional().describe("The unique tool use request ID"),
  },
  async ({ tool_name, input }) => {
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(
            JSON.stringify(input).includes("allow")
              ? {
                  behavior: "allow",
                  updatedInput: input,
                }
              : {
                  behavior: "deny",
                  message: "Permission denied by test approval_prompt tool",
                }
          ),
        },
      ],
    };
  }
);

// Use in SDK
import { query } from "@anthropic-ai/claude-code";

for await (const message of query({
  prompt: "Analyze the codebase",
  options: {
    permissionPromptTool: "mcp__test-server__approval_prompt",
    mcpConfig: "my-config.json",
    allowedTools: ["Read", "Grep"]
  }
})) {
  if (message.type === "result") console.log(message.result);
}
```
```python import asyncio from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def use_permission_prompt():
    """Example using custom permission prompt tool"""

    # MCP server configuration
    mcp_servers = {
        # Example configuration - uncomment and configure as needed:
        # "security": {
        #     "command": "npx",
        #     "args": ["-y", "@modelcontextprotocol/server-security"],
        #     "env": {"API_KEY": "your-key"}
        # }
    }

    async with ClaudeSDKClient(
        options=ClaudeCodeOptions(
            permission_prompt_tool_name="mcp__security__approval_prompt",  # Changed from permission_prompt_tool
            mcp_servers=mcp_servers,
            allowed_tools=["Read", "Grep"],
            disallowed_tools=["Bash(rm*)", "Write"],
            system_prompt="You are a security auditor"
        )
    ) as client:
        await client.query("Analyze and fix the security issues")

        # Monitor tool usage and permissions
        async for message in client.receive_response():
            if hasattr(message, 'content'):
                for block in message.content:
                    if hasattr(block, 'type'):  # Added check for 'type' attribute
                        if block.type == 'tool_use':
                            print(f"[Tool: {block.name}] ", end='')
                    if hasattr(block, 'text'):
                        print(block.text, end='', flush=True)

            # Check for permission denials in error messages
            if type(message).__name__ == "ErrorMessage":
                if hasattr(message, 'error') and "Permission denied" in str(message.error):
                    print(f"\n⚠ Permission denied: {message.error}")

# Example MCP server implementation (Python)
# This would be in your MCP server code
async def approval_prompt(tool_name: str, input: dict, tool_use_id: str = None):
    """Custom permission prompt handler"""
    # Your custom logic here
    if "allow" in str(input):
        return json.dumps({
            "behavior": "allow",
            "updatedInput": input
        })
    else:
        return json.dumps({
            "behavior": "deny",
            "message": f"Permission denied for {tool_name}"
        })

asyncio.run(use_permission_prompt())
```

Usage notes:

  • Use updatedInput to tell the model that the permission prompt mutated its input; otherwise, set updatedInput to the original input, as in the example above. For example, if the tool shows a file edit diff to the user and lets them edit the diff manually, the permission prompt tool should return that updated edit.
  • The payload must be JSON-stringified

Output formats

The SDK supports multiple output formats:

Text output (default)

```bash claude -p "Explain file src/components/Header.tsx" # Output: This is a React component showing... ``` ```ts // Default text output for await (const message of query({ prompt: "Explain file src/components/Header.tsx" })) { if (message.type === "result") { console.log(message.result); // Output: This is a React component showing... } } ``` ```python # Default text output with streaming async with ClaudeSDKClient() as client: await client.query("Explain file src/components/Header.tsx")
    # Stream text as it arrives
    async for message in client.receive_response():
        if hasattr(message, 'content'):
            for block in message.content:
                if hasattr(block, 'text'):
                    print(block.text, end='', flush=True)
                    # Output streams in real-time: This is a React component showing...
```

JSON output

Returns structured data including metadata:

```bash claude -p "How does the data layer work?" --output-format json ``` ```ts // Collect all messages for JSON-like access const messages = []; for await (const message of query({ prompt: "How does the data layer work?" })) { messages.push(message); }
// Access result message with metadata
const result = messages.find(m => m.type === "result");
console.log({
  result: result.result,
  cost: result.total_cost_usd,
  duration: result.duration_ms
});
```
```python # Collect all messages with metadata async with ClaudeSDKClient() as client: await client.query("How does the data layer work?")
    messages = []
    result_data = None

    async for message in client.receive_messages():
        messages.append(message)

        # Capture result message with metadata
        if type(message).__name__ == "ResultMessage":
            result_data = {
                "result": message.result,
                "cost": message.total_cost_usd,
                "duration": message.duration_ms,
                "num_turns": message.num_turns,
                "session_id": message.session_id
            }
            break

    print(result_data)
```

Response format:

{
  "type": "result",
  "subtype": "success",
  "total_cost_usd": 0.003,
  "is_error": false,
  "duration_ms": 1234,
  "duration_api_ms": 800,
  "num_turns": 6,
  "result": "The response text here...",
  "session_id": "abc123"
}

Streaming JSON output

Streams each message as it is received:

$ claude -p "Build an application" --output-format stream-json

Each conversation begins with an initial init system message, followed by a list of user and assistant messages, followed by a final result system message with stats. Each message is emitted as a separate JSON object.

Message schema

Messages returned from the JSON API are strictly typed according to the following schema:

type SDKMessage =
  // An assistant message
  | {
      type: "assistant";
      message: Message; // from Anthropic SDK
      session_id: string;
    }

  // A user message
  | {
      type: "user";
      message: MessageParam; // from Anthropic SDK
      session_id: string;
    }

  // Emitted as the last message
  | {
      type: "result";
      subtype: "success";
      duration_ms: float;
      duration_api_ms: float;
      is_error: boolean;
      num_turns: int;
      result: string;
      session_id: string;
      total_cost_usd: float;
    }

  // Emitted as the last message, when we've reached the maximum number of turns
  | {
      type: "result";
      subtype: "error_max_turns" | "error_during_execution";
      duration_ms: float;
      duration_api_ms: float;
      is_error: boolean;
      num_turns: int;
      session_id: string;
      total_cost_usd: float;
    }

  // Emitted as the first message at the start of a conversation
  | {
      type: "system";
      subtype: "init";
      apiKeySource: string;
      cwd: string;
      session_id: string;
      tools: string[];
      mcp_servers: {
        name: string;
        status: string;
      }[];
      model: string;
      permissionMode: "default" | "acceptEdits" | "bypassPermissions" | "plan";
    };

We will soon publish these types in a JSONSchema-compatible format. We use semantic versioning for the main Claude Code package to communicate breaking changes to this format.

Message and MessageParam types are available in Anthropic SDKs. For example, see the Anthropic TypeScript and Python SDKs.

Input formats

The SDK supports multiple input formats:

Text input (default)

```bash # Direct argument claude -p "Explain this code"
# From stdin
echo "Explain this code" | claude -p
```
```ts // Direct prompt for await (const message of query({ prompt: "Explain this code" })) { if (message.type === "result") console.log(message.result); }
// From variable
const userInput = "Explain this code";
for await (const message of query({ prompt: userInput })) {
  if (message.type === "result") console.log(message.result);
}
```
```python import asyncio from claude_code_sdk import ClaudeSDKClient
async def process_inputs():
    async with ClaudeSDKClient() as client:
        # Text input
        await client.query("Explain this code")
        async for message in client.receive_response():
            # Process streaming response
            pass

        # Image input (Claude will use Read tool automatically)
        await client.query("What's in this diagram? screenshot.png")
        async for message in client.receive_response():
            # Process image analysis
            pass

        # Multiple inputs with mixed content
        inputs = [
            "Analyze the architecture in diagram.png",
            "Compare it with best practices",
            "Generate improved version"
        ]

        for prompt in inputs:
            await client.query(prompt)
            async for message in client.receive_response():
                # Process each response
                pass

asyncio.run(process_inputs())
```

Streaming JSON input

A stream of messages provided via stdin where each message represents a user turn. This allows multiple turns of a conversation without re-launching the claude binary and allows providing guidance to the model while it is processing a request.

Each message is a JSON 'User message' object, following the same format as the output message schema. Messages are formatted using the jsonl format where each line of input is a complete JSON object. Streaming JSON input requires -p and --output-format stream-json.

Currently this is limited to text-only user messages.

$ echo '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Explain this code"}]}}' | claude -p --output-format=stream-json --input-format=stream-json --verbose

Agent integration examples

SRE incident response bot

```bash #!/bin/bash
# Automated incident response agent
investigate_incident() {
    local incident_description="$1"
    local severity="${2:-medium}"

    claude -p "Incident: $incident_description (Severity: $severity)" \
      --append-system-prompt "You are an SRE expert. Diagnose the issue, assess impact, and provide immediate action items." \
      --output-format json \
      --allowedTools "Bash,Read,WebSearch,mcp__datadog" \
      --mcp-config monitoring-tools.json
}

# Usage
investigate_incident "Payment API returning 500 errors" "high"
```
```ts import { query } from "@anthropic-ai/claude-code";
// Automated incident response agent
async function investigateIncident(
  incidentDescription: string,
  severity = "medium"
) {
  const messages = [];

  for await (const message of query({
    prompt: `Incident: ${incidentDescription} (Severity: ${severity})`,
    options: {
      systemPrompt: "You are an SRE expert. Diagnose the issue, assess impact, and provide immediate action items.",
      maxTurns: 6,
      allowedTools: ["Bash", "Read", "WebSearch", "mcp__datadog"],
      mcpConfig: "monitoring-tools.json"
    }
  })) {
    messages.push(message);
  }

  return messages.find(m => m.type === "result");
}

// Usage
const result = await investigateIncident("Payment API returning 500 errors", "high");
console.log(result.result);
```
```python import asyncio from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def investigate_incident(incident_description: str, severity: str = "medium"):
    """Automated incident response agent with real-time streaming"""

    # MCP server configuration for monitoring tools
    mcp_servers = {
        # Example configuration - uncomment and configure as needed:
        # "datadog": {
        #     "command": "npx",
        #     "args": ["-y", "@modelcontextprotocol/server-datadog"],
        #     "env": {"API_KEY": "your-datadog-key", "APP_KEY": "your-app-key"}
        # }
    }

    async with ClaudeSDKClient(
        options=ClaudeCodeOptions(
            system_prompt="You are an SRE expert. Diagnose the issue, assess impact, and provide immediate action items.",
            max_turns=6,
            allowed_tools=["Bash", "Read", "WebSearch", "mcp__datadog"],
            mcp_servers=mcp_servers
        )
    ) as client:
        # Send the incident details
        prompt = f"Incident: {incident_description} (Severity: {severity})"
        print(f"🚨 Investigating: {prompt}\n")
        await client.query(prompt)

        # Stream the investigation process
        investigation_log = []
        async for message in client.receive_response():
            if hasattr(message, 'content'):
                for block in message.content:
                    if hasattr(block, 'type'):
                        if block.type == 'tool_use':
                            print(f"[{block.name}] ", end='')
                    if hasattr(block, 'text'):
                        text = block.text
                        print(text, end='', flush=True)
                        investigation_log.append(text)

            # Capture final result
            if type(message).__name__ == "ResultMessage":
                return {
                    'analysis': ''.join(investigation_log),
                    'cost': message.total_cost_usd,
                    'duration_ms': message.duration_ms
                }

# Usage
result = await investigate_incident("Payment API returning 500 errors", "high")
print(f"\n\nInvestigation complete. Cost: ${result['cost']:.4f}")
```

Automated security review

```bash # Security audit agent for pull requests audit_pr() { local pr_number="$1"
    gh pr diff "$pr_number" | claude -p \
      --append-system-prompt "You are a security engineer. Review this PR for vulnerabilities, insecure patterns, and compliance issues." \
      --output-format json \
      --allowedTools "Read,Grep,WebSearch"
}

# Usage and save to file
audit_pr 123 > security-report.json
```
```ts import { query } from "@anthropic-ai/claude-code"; import { execSync } from "child_process";
async function auditPR(prNumber: number) {
  // Get PR diff
  const prDiff = execSync(`gh pr diff ${prNumber}`, { encoding: 'utf8' });

  const messages = [];
  for await (const message of query({
    prompt: prDiff,
    options: {
      systemPrompt: "You are a security engineer. Review this PR for vulnerabilities, insecure patterns, and compliance issues.",
      maxTurns: 3,
      allowedTools: ["Read", "Grep", "WebSearch"]
    }
  })) {
    messages.push(message);
  }

  return messages.find(m => m.type === "result");
}

// Usage
const report = await auditPR(123);
console.log(JSON.stringify(report, null, 2));
```
```python import subprocess import asyncio import json from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def audit_pr(pr_number: int):
    """Security audit agent for pull requests with streaming feedback"""
    # Get PR diff
    pr_diff = subprocess.check_output(
        ["gh", "pr", "diff", str(pr_number)],
        text=True
    )

    async with ClaudeSDKClient(
        options=ClaudeCodeOptions(
            system_prompt="You are a security engineer. Review this PR for vulnerabilities, insecure patterns, and compliance issues.",
            max_turns=3,
            allowed_tools=["Read", "Grep", "WebSearch"]
        )
    ) as client:
        print(f"🔍 Auditing PR #{pr_number}\n")
        await client.query(pr_diff)

        findings = []
        async for message in client.receive_response():
            if hasattr(message, 'content'):
                for block in message.content:
                    if hasattr(block, 'text'):
                        # Stream findings as they're discovered
                        print(block.text, end='', flush=True)
                        findings.append(block.text)

            if type(message).__name__ == "ResultMessage":
                return {
                    'pr_number': pr_number,
                    'findings': ''.join(findings),
                    'metadata': {
                        'cost': message.total_cost_usd,
                        'duration': message.duration_ms,
                        'severity': 'high' if 'vulnerability' in ''.join(findings).lower() else 'medium'
                    }
                }

# Usage
report = await audit_pr(123)
print(f"\n\nAudit complete. Severity: {report['metadata']['severity']}")
print(json.dumps(report, indent=2))
```
```bash # Legal document review with session persistence session_id=$(claude -p "Start legal review session" --output-format json | jq -r '.session_id')
# Review contract in multiple steps
claude -p --resume "$session_id" "Review contract.pdf for liability clauses"
claude -p --resume "$session_id" "Check compliance with GDPR requirements"
claude -p --resume "$session_id" "Generate executive summary of risks"
```
```ts import { query } from "@anthropic-ai/claude-code";
async function legalReview() {
  // Start legal review session
  let sessionId: string;

  for await (const message of query({
    prompt: "Start legal review session",
    options: { maxTurns: 1 }
  })) {
    if (message.type === "system" && message.subtype === "init") {
      sessionId = message.session_id;
    }
  }

  // Multi-step review using same session
  const steps = [
    "Review contract.pdf for liability clauses",
    "Check compliance with GDPR requirements",
    "Generate executive summary of risks"
  ];

  for (const step of steps) {
    for await (const message of query({
      prompt: step,
      options: { resumeSessionId: sessionId, maxTurns: 2 }
    })) {
      if (message.type === "result") {
        console.log(`Step: ${step}`);
        console.log(message.result);
      }
    }
  }
}
```
```python import asyncio from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def legal_review():
    """Legal document review with persistent session and streaming"""

    async with ClaudeSDKClient(
        options=ClaudeCodeOptions(
            system_prompt="You are a corporate lawyer. Provide detailed legal analysis.",
            max_turns=2
        )
    ) as client:
        # Multi-step review in same session
        steps = [
            "Review contract.pdf for liability clauses",
            "Check compliance with GDPR requirements",
            "Generate executive summary of risks"
        ]

        review_results = []

        for step in steps:
            print(f"\n📋 {step}\n")
            await client.query(step)

            step_result = []
            async for message in client.receive_response():
                if hasattr(message, 'content'):
                    for block in message.content:
                        if hasattr(block, 'text'):
                            text = block.text
                            print(text, end='', flush=True)
                            step_result.append(text)

                if type(message).__name__ == "ResultMessage":
                    review_results.append({
                        'step': step,
                        'analysis': ''.join(step_result),
                        'cost': message.total_cost_usd
                    })

        # Summary
        total_cost = sum(r['cost'] for r in review_results)
        print(f"\n\n✅ Legal review complete. Total cost: ${total_cost:.4f}")
        return review_results

# Usage
results = await legal_review()
```

Python-Specific Best Practices

Key Patterns

import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions

# Always use context managers
async with ClaudeSDKClient() as client:
    await client.query("Analyze this code")
    async for msg in client.receive_response():
        # Process streaming messages
        pass

# Run multiple agents concurrently
async with ClaudeSDKClient() as reviewer, ClaudeSDKClient() as tester:
    await asyncio.gather(
        reviewer.query("Review main.py"),
        tester.query("Write tests for main.py")
    )

# Error handling
from claude_code_sdk import CLINotFoundError, ProcessError

try:
    async with ClaudeSDKClient() as client:
        # Your code here
        pass
except CLINotFoundError:
    print("Install CLI: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
    print(f"Process error: {e}")

# Collect full response with metadata
async def get_response(client, prompt):
    await client.query(prompt)
    text = []
    async for msg in client.receive_response():
        if hasattr(msg, 'content'):
            for block in msg.content:
                if hasattr(block, 'text'):
                    text.append(block.text)
        if type(msg).__name__ == "ResultMessage":
            return {'text': ''.join(text), 'cost': msg.total_cost_usd}

IPython/Jupyter Tips

# In Jupyter, use await directly in cells
client = ClaudeSDKClient()
await client.connect()
await client.query("Analyze data.csv")
async for msg in client.receive_response():
    print(msg)
await client.disconnect()

# Create reusable helper functions
async def stream_print(client, prompt):
    await client.query(prompt)
    async for msg in client.receive_response():
        if hasattr(msg, 'content'):
            for block in msg.content:
                if hasattr(block, 'text'):
                    print(block.text, end='', flush=True)

Best practices

  • Use JSON output format for programmatic parsing of responses:

    # Parse JSON response with jq
    result=$(claude -p "Generate code" --output-format json)
    code=$(echo "$result" | jq -r '.result')
    cost=$(echo "$result" | jq -r '.cost_usd')
    
  • Handle errors gracefully - check exit codes and stderr:

    if ! claude -p "$prompt" 2>error.log; then
        echo "Error occurred:" >&2
        cat error.log >&2
        exit 1
    fi
    
  • Use session management for maintaining context in multi-turn conversations

  • Consider timeouts for long-running operations:

    timeout 300 claude -p "$complex_prompt" || echo "Timed out after 5 minutes"
    
  • Respect rate limits when making multiple requests by adding delays between calls