Claude Agent SDK Python Tutorial: Automate Coding Tasks Programmatically
If you've used Claude Code from the terminal, you already know it's one of the best AI coding assistants out there. But what if you want to go beyond the CLI? What if you need Claude Code running inside your Python scripts — automating refactors, generating tests, or orchestrating multi-step coding workflows without you babysitting a terminal?
That's exactly what the Claude Agent SDK is for. It's Anthropic's official Python SDK that wraps Claude Code into a programmable interface. You get the same powerful coding agent, but controlled from Python with full access to custom tools, hooks, and interactive conversations.
This tutorial walks you through everything: installation, basic queries, custom tools, and real-world automation patterns. By the end, you'll have a working setup that can automate coding tasks you'd normally do by hand.
What Is the Claude Agent SDK?
The Claude Agent SDK (formerly claude-code-sdk, now claude-agent-sdk) is a Python package that lets you invoke Claude Code programmatically. Under the hood, it spawns the Claude Code CLI as a subprocess and communicates with it via structured messages.
This matters because Claude Code isn't just a chat model — it's an agent that can read files, write code, run shell commands, and iterate on its own output. The SDK gives you programmatic control over all of that.
Key capabilities:
- Send prompts and receive structured responses (text, tool calls, tool results)
- Define custom tools as Python functions that Claude can invoke
- Control which built-in tools Claude can use (Read, Write, Bash, etc.)
- Set working directories, system prompts, and conversation limits
- Run interactive, multi-turn conversations
Prerequisites and Installation
You'll need three things:
- Python 3.10+
- Node.js (Claude Code CLI is a Node.js application)
- Claude Code CLI installed globally
First, install the Claude Code CLI:
npm install -g @anthropic-ai/claude-code
Then install the Python SDK:
pip install claude-agent-sdk
You'll also need an API key. Set it as an environment variable:
export ANTHROPIC_API_KEY=your-api-key-here
If you don't have direct Anthropic API access (or want to use a different provider), you can point Claude Code at any OpenAI-compatible endpoint by setting ANTHROPIC_BASE_URL. Services like KissAPI provide Claude API access through a single gateway — handy if you're in a region where Anthropic's direct API isn't available.
Basic Usage: Your First Query
The simplest way to use the SDK is the query() function. It's async and returns an iterator of messages:
import anyio
from claude_code_sdk import query, AssistantMessage, TextBlock
async def main():
async for message in query(prompt="Write a Python function that checks if a number is prime"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
anyio.run(main)
This sends a prompt to Claude Code and prints the text response. Simple enough. But the real power comes when you let Claude use tools.
Letting Claude Read and Write Files
Claude Code's killer feature is that it can interact with your filesystem. With the SDK, you control exactly which tools it can use:
from claude_code_sdk import query, ClaudeCodeOptions
options = ClaudeCodeOptions(
cwd="/path/to/your/project",
allowed_tools=["Read", "Write", "Bash"],
permission_mode="acceptEdits" # auto-accept file changes
)
async for message in query(
prompt="Read main.py and add type hints to all functions",
options=options
):
print(message)
The permission_mode="acceptEdits" flag is important for automation — without it, Claude Code would pause and ask for confirmation before writing files, which defeats the purpose of running it programmatically.
Available permission modes:
| Mode | Behavior | Use Case |
|---|---|---|
default | Asks for confirmation on writes | Interactive use |
acceptEdits | Auto-accepts file edits, asks for Bash | Automated refactoring |
bypassPermissions | Auto-accepts everything | Fully automated pipelines (use with caution) |
Building Custom Tools
This is where things get interesting. You can define Python functions as tools that Claude can call during a conversation. The SDK runs them as in-process MCP servers — no separate process needed.
Here's a practical example: a tool that queries your database so Claude can write code that matches your actual schema:
from claude_code_sdk import (
tool, create_sdk_mcp_server,
ClaudeCodeOptions, ClaudeSDKClient
)
@tool("get_table_schema", "Get the schema of a database table", {"table_name": str})
async def get_table_schema(args):
# In practice, query your actual database here
schemas = {
"users": "id INT PK, email VARCHAR(255), name VARCHAR(100), created_at TIMESTAMP",
"orders": "id INT PK, user_id INT FK, total DECIMAL(10,2), status VARCHAR(20)",
}
schema = schemas.get(args["table_name"], "Table not found")
return {"content": [{"type": "text", "text": schema}]}
@tool("list_tables", "List all database tables", {})
async def list_tables(args):
return {"content": [{"type": "text", "text": "users, orders, products, sessions"}]}
server = create_sdk_mcp_server(
name="db-tools",
version="1.0.0",
tools=[get_table_schema, list_tables]
)
options = ClaudeCodeOptions(
mcp_servers={"db": server},
allowed_tools=["Read", "Write", "mcp__db__get_table_schema", "mcp__db__list_tables"],
cwd="/path/to/project"
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Write a SQLAlchemy model for the users table")
async for msg in client.receive_response():
print(msg)
Claude will call list_tables and get_table_schema on its own to understand your database, then write accurate model code. No hallucinated column names.
Real-World Automation Patterns
Pattern 1: Automated Test Generation
Point Claude at a source file and have it generate tests:
async def generate_tests(source_file: str, test_dir: str):
options = ClaudeCodeOptions(
cwd="/path/to/project",
allowed_tools=["Read", "Write"],
permission_mode="acceptEdits",
max_turns=10
)
prompt = f"""Read {source_file} and generate comprehensive unit tests.
Write the tests to {test_dir}/test_{source_file.split('/')[-1]}.
Use pytest. Cover edge cases and error paths."""
async for message in query(prompt=prompt, options=options):
pass # Claude writes the test file directly
# Usage
await generate_tests("src/auth.py", "tests/")
Pattern 2: Batch Code Review
Run Claude over a list of files and collect review comments:
async def review_files(files: list[str]) -> dict[str, str]:
reviews = {}
for file in files:
options = ClaudeCodeOptions(
cwd="/path/to/project",
allowed_tools=["Read"],
max_turns=3
)
result_text = []
async for message in query(
prompt=f"Review {file} for bugs, security issues, and code smells. Be specific and concise.",
options=options
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
result_text.append(block.text)
reviews[file] = "\n".join(result_text)
return reviews
Pattern 3: CI/CD Integration
Run the SDK in your CI pipeline to auto-fix linting issues:
import subprocess
import anyio
from claude_code_sdk import query, ClaudeCodeOptions
async def auto_fix_lint():
# Run linter first
result = subprocess.run(
["ruff", "check", "src/", "--output-format=json"],
capture_output=True, text=True
)
if result.returncode == 0:
print("No lint issues found")
return
options = ClaudeCodeOptions(
cwd="/path/to/project",
allowed_tools=["Read", "Write"],
permission_mode="acceptEdits",
max_turns=15
)
prompt = f"""Fix these lint issues. Edit the files directly.
Don't change any logic, only fix the style/lint problems.
Lint output:
{result.stdout}"""
async for message in query(prompt=prompt, options=options):
pass
anyio.run(auto_fix_lint)
Using a Custom API Endpoint
The Claude Agent SDK uses Claude Code under the hood, which means it respects the same environment variables. If you're using an API gateway like KissAPI instead of the direct Anthropic API, just set:
export ANTHROPIC_BASE_URL=https://api.kissapi.ai
export ANTHROPIC_API_KEY=your-kissapi-key
Everything else works the same. This is particularly useful if you need to route through a gateway for cost tracking, rate limit management, or regional access.
Tips and Gotchas
- Set
max_turnsfor automation. Without it, Claude might loop indefinitely trying to fix an issue. For most tasks, 5-15 turns is plenty. - Use
system_promptto constrain behavior. Tell Claude exactly what it should and shouldn't do. "Only modify files in the src/ directory" prevents accidental changes elsewhere. - The SDK requires the CLI. It doesn't call the Anthropic API directly — it spawns
claude-codeas a subprocess. Make sure the CLI is installed and accessible in your PATH. - Token costs add up. Each tool use (reading a file, running a command) consumes tokens. For large codebases, be specific about which files Claude should look at.
- Error handling matters. Wrap your queries in try/except blocks. The subprocess can fail if the CLI isn't installed, the API key is invalid, or you hit rate limits.
query() vs ClaudeSDKClient
The SDK offers two interfaces:
| Feature | query() | ClaudeSDKClient |
|---|---|---|
| Use case | One-shot tasks | Multi-turn conversations |
| Custom tools | No | Yes (MCP servers) |
| Hooks | No | Yes |
| Interactive | No | Yes |
| Complexity | Low | Medium |
Use query() for simple, fire-and-forget tasks. Use ClaudeSDKClient when you need custom tools, multi-turn conversations, or hooks that run before/after tool execution.
Need Claude API Access?
KissAPI provides Claude API access through an OpenAI-compatible endpoint. Pay-as-you-go, no subscription. Works with Claude Code, Cursor, and any OpenAI SDK.
Start Free →What's Next
The Claude Agent SDK opens up a lot of possibilities. Some ideas to explore:
- Build a GitHub Action that auto-reviews PRs using Claude with your codebase context
- Create a Slack bot that can refactor code on demand
- Set up nightly automated test generation for new code
- Build a migration tool that reads your old framework code and rewrites it for a new one
The SDK is still evolving — Anthropic recently renamed it from claude-code-sdk to claude-agent-sdk and added features like hooks and the ClaudeSDKClient class. Keep an eye on the GitHub repo for updates.