| Crates.io | claude-code-agent-sdk |
| lib.rs | claude-code-agent-sdk |
| version | 0.1.36 |
| created_at | 2026-01-07 05:00:28.656241+00 |
| updated_at | 2026-01-21 09:59:17.066469+00 |
| description | Rust SDK for Claude Code CLI with bidirectional streaming, hooks, custom tools, and plugin support |
| homepage | https://github.com/soddygo/claude-code-agent-sdk |
| repository | https://github.com/soddygo/claude-code-agent-sdk |
| max_upload_size | |
| id | 2027549 |
| size | 366,372 |
Rust SDK for interacting with Claude Code CLI, enabling programmatic access to Claude's capabilities with full bidirectional streaming support.
Status: β 100% feature parity with Python SDK - Production Ready
ClaudeClienttool! macroAdd this to your Cargo.toml:
[dependencies]
claude-agent-sdk-rs = "0.5"
tokio = { version = "1", features = ["full"] }
Or use cargo-add:
cargo add claude-agent-sdk-rs
cargo add tokio --features full
use claude_agent_sdk_rs::{query, ClaudeAgentOptions, Message, ContentBlock};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Simple query with default options
let messages = query("What is 2 + 2?", None).await?;
for message in messages {
if let Message::Assistant(msg) = message {
for block in msg.message.content {
if let ContentBlock::Text(text) = block {
println!("Claude: {}", text.text);
}
}
}
}
Ok(())
}
With custom options:
let options = ClaudeAgentOptions {
model: Some("claude-sonnet-4-5".to_string()),
max_turns: Some(5),
allowed_tools: vec!["Read".to_string(), "Write".to_string()],
..Default::default()
};
let messages = query("Create a hello.txt file", Some(options)).await?;
For large conversations or real-time processing, use query_stream():
use claude_agent_sdk_rs::{query_stream, Message, ContentBlock};
use futures::stream::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Get a stream of messages instead of collecting them all
let mut stream = query_stream("What is 2 + 2?", None).await?;
// Process messages as they arrive (O(1) memory)
while let Some(result) = stream.next().await {
let message = result?;
if let Message::Assistant(msg) = message {
for block in msg.message.content {
if let ContentBlock::Text(text) = block {
println!("Claude: {}", text.text);
}
}
}
}
Ok(())
}
When to use:
query(): Small to medium conversations, need all messages for post-processingquery_stream(): Large conversations, real-time processing, memory constraintsuse claude_agent_sdk_rs::{ClaudeClient, ClaudeAgentOptions, Message, ContentBlock};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut client = ClaudeClient::new(ClaudeAgentOptions::default());
// Connect to Claude
client.connect().await?;
// First question
client.query("What is the capital of France?").await?;
// Receive response
loop {
match client.receive_message().await? {
Some(Message::Assistant(msg)) => {
for block in msg.message.content {
if let ContentBlock::Text(text) = block {
println!("Claude: {}", text.text);
}
}
}
Some(Message::Result(_)) => break,
Some(_) => continue,
None => break,
}
}
// Follow-up question - Claude remembers context!
client.query("What's the population of that city?").await?;
loop {
match client.receive_message().await? {
Some(Message::Assistant(msg)) => {
for block in msg.message.content {
if let ContentBlock::Text(text) = block {
println!("Claude: {}", text.text);
}
}
}
Some(Message::Result(_)) => break,
Some(_) => continue,
None => break,
}
}
client.disconnect().await?;
Ok(())
}
Create custom in-process tools that Claude can use:
use claude_agent_sdk_rs::{tool, create_sdk_mcp_server, ToolResult, McpToolResultContent};
use serde_json::json;
async fn greet_handler(args: serde_json::Value) -> anyhow::Result<ToolResult> {
let name = args["name"].as_str().unwrap_or("World");
Ok(ToolResult {
content: vec![McpToolResultContent::Text {
text: format!("Hello, {}!", name),
}],
is_error: false,
})
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let greet_tool = tool!(
"greet",
"Greet a user",
json!({
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}),
greet_handler
);
let server = create_sdk_mcp_server("my-tools", "1.0.0", vec![greet_tool]);
// Configure ClaudeClient with the MCP server and allowed tools
let mut mcp_servers = HashMap::new();
mcp_servers.insert("my-tools".to_string(), McpServerConfig::Sdk(server));
let options = ClaudeAgentOptions {
mcp_servers: McpServers::Dict(mcp_servers),
allowed_tools: vec!["mcp__my-tools__greet".to_string()],
permission_mode: Some(PermissionMode::AcceptEdits),
..Default::default()
};
let mut client = ClaudeClient::new(options);
client.connect().await?;
// Claude can now use your custom tools!
client.query("Greet Alice").await?;
// ... handle responses
client.disconnect().await?;
Ok(())
}
Note: Tools must be explicitly allowed using the format mcp__{server_name}__{tool_name}.
For a comprehensive guide, see examples/MCP_INTEGRATION.md.
The SDK is structured in layers:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Public API Layer β
β (query(), ClaudeClient, tool!(), create_sdk_server()) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Control Protocol Layer β
β (Query: handles bidirectional control) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Transport Layer β
β (SubprocessTransport, custom implementations) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Claude Code CLI β
β (external process via stdio/subprocess) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The SDK provides multiple ways to manage conversation context and clear memory:
Different session IDs maintain completely separate conversation contexts:
let mut client = ClaudeClient::new(ClaudeAgentOptions::default());
client.connect().await?;
// Session 1: Math conversation
client.query_with_session("What is 2 + 2?", "math-session").await?;
// Session 2: Programming conversation (different context)
client.query_with_session("What is Rust?", "programming-session").await?;
// Back to Session 1 - Claude remembers math context
client.query_with_session("What about 3 + 3?", "math-session").await?;
Use fork_session to start completely fresh without any history:
let options = ClaudeAgentOptions::builder()
.fork_session(true) // Each resumed session starts fresh
.build();
let mut client = ClaudeClient::new(options);
client.connect().await?;
Use new_session() for quick session switching:
client.new_session("session-2", "Tell me about Rust").await?;
See examples/16_session_management.rs for complete examples.
The SDK provides strongly-typed Rust interfaces for all Claude interactions:
Message, ContentBlock, TextBlock, ToolUseBlock, etc.ClaudeAgentOptions, SystemPrompt, PermissionModeHookEvent, HookCallback, HookInput, HookJsonOutputPermissionResult, PermissionUpdate, CanUseToolCallbackMcpServers, SdkMcpServer, ToolHandler, ToolResultThe SDK includes 23 comprehensive examples demonstrating all features with 100% parity to Python SDK. See examples/README.md for details.
# Basic usage
cargo run --example 01_hello_world # Simple query with tool usage
cargo run --example 02_limit_tool_use # Restrict allowed tools
cargo run --example 03_monitor_tools # Monitor tool execution
# Streaming & Conversations
cargo run --example 06_bidirectional_client # Multi-turn conversations
cargo run --example 14_streaming_mode -- all # Comprehensive streaming patterns
# Hooks & Control
cargo run --example 05_hooks_pretooluse # PreToolUse hooks
cargo run --example 15_hooks_comprehensive -- all # All hook types
cargo run --example 07_dynamic_control # Runtime control
# Custom Tools & MCP
cargo run --example 08_mcp_server_integration # In-process MCP servers
# Configuration
cargo run --example 09_agents # Custom agents
cargo run --example 11_setting_sources -- all # Settings control
cargo run --example 13_system_prompt # System prompt configs
# Production Features
cargo run --example 17_fallback_model # Fallback model for reliability
cargo run --example 18_max_budget_usd # Budget control
cargo run --example 19_max_thinking_tokens # Extended thinking limits
cargo run --example 20_query_stream # Streaming query API
# Plugin System
cargo run --example 21_custom_plugins # Load custom plugins
cargo run --example 22_plugin_integration # Real-world plugin usage
# Multimodal
cargo run --example 23_image_input # Image input with text queries
# Session Management
cargo run --example 16_session_management # Session clearing and management
| Category | Examples | Description |
|---|---|---|
| Basics | 01-03 | Simple queries, tool control, monitoring |
| Advanced | 04-07 | Permissions, hooks, streaming, dynamic control |
| MCP | 08 | Custom tools and MCP server integration |
| Config | 09-13 | Agents, settings, prompts, debugging |
| Patterns | 14-16 | Comprehensive streaming, hooks, and sessions |
| Production | 17-20 | Fallback models, budgets, thinking limits, streaming |
| Plugins | 21-22 | Custom plugin loading and integration |
| Multimodal | 23 | Image input alongside text |
// Main client for bidirectional streaming
ClaudeClient
// Simple query functions for one-shot interactions
query(prompt: &str, options: Option<ClaudeAgentOptions>) -> Vec<Message>
query_stream(prompt: &str, options: Option<ClaudeAgentOptions>) -> Stream<Item = Result<Message>>
query_with_content(content: Vec<UserContentBlock>, options: Option<ClaudeAgentOptions>) -> Vec<Message>
query_stream_with_content(content: Vec<UserContentBlock>, options: Option<ClaudeAgentOptions>) -> Stream<Item = Result<Message>>
// Configuration
ClaudeAgentOptions {
model: Option<String>,
fallback_model: Option<String>, // NEW: Backup model for reliability
max_budget_usd: Option<f64>, // NEW: Cost control
max_thinking_tokens: Option<u32>, // NEW: Extended thinking limit
plugins: Vec<SdkPluginConfig>, // NEW: Custom plugin loading
max_turns: Option<u32>,
allowed_tools: Vec<String>,
system_prompt: Option<SystemPromptConfig>,
hooks: Option<HashMap<String, Vec<HookMatcher>>>,
mcp_servers: Option<HashMap<String, McpServer>>,
// ... and more
}
// Messages
Message::Assistant(AssistantMessage)
Message::User(UserMessage)
Message::System(SystemMessage)
Message::Result(ResultMessage)
// Create and connect
let mut client = ClaudeClient::new(options);
client.connect().await?;
// Send queries
client.query("Hello").await?;
// Receive messages
loop {
match client.receive_message().await? {
Some(Message::Assistant(msg)) => { /* Handle */ }
Some(Message::Result(_)) => break,
None => break,
_ => continue,
}
}
// Session management - separate conversation contexts
client.query_with_session("First question", "session-1").await?;
client.query_with_session("Different context", "session-2").await?;
client.new_session("session-3", "Fresh start").await?;
// Dynamic control (mid-execution)
client.interrupt().await?; // Stop current operation
// Client will handle the interrupt automatically
// Disconnect
client.disconnect().await?;
use claude_agent_sdk_rs::{Hook, HookMatcher, HookInput, HookContext, HookJSONOutput};
async fn my_hook(
input: HookInput,
tool_use_id: Option<String>,
context: HookContext,
) -> anyhow::Result<HookJSONOutput> {
// Block dangerous commands
if let Some(command) = input.get("tool_input")
.and_then(|v| v.get("command"))
.and_then(|v| v.as_str())
{
if command.contains("rm -rf") {
return Ok(serde_json::json!({
"hookSpecificOutput": {
"permissionDecision": "deny",
"permissionDecisionReason": "Dangerous command blocked"
}
}));
}
}
Ok(serde_json::json!({}))
}
let mut hooks = HashMap::new();
hooks.insert("PreToolUse".to_string(), vec![
HookMatcher {
matcher: Some("Bash".to_string()),
hooks: vec![Hook::new(my_hook)],
}
]);
let options = ClaudeAgentOptions {
hooks: Some(hooks),
..Default::default()
};
# Run all tests
cargo test
# Run tests with output
cargo test -- --nocapture
# Run specific test
cargo test test_name
# Check code with clippy
cargo clippy --all-targets --all-features
# Format code
cargo fmt
# Check formatting
cargo fmt -- --check
# Build library
cargo build
# Build with release optimizations
cargo build --release
# Build all examples
cargo build --examples
# Build documentation
cargo doc --open
"Claude Code CLI not found"
claude is in your PATH"API key not configured"
ANTHROPIC_API_KEY environment variable"Permission denied" errors
permission_mode: PermissionMode::AcceptEdits for automated workflowsEnable debug output to see what's happening:
let options = ClaudeAgentOptions {
stderr_callback: Some(Arc::new(|msg| eprintln!("DEBUG: {}", msg))),
extra_args: Some({
let mut args = HashMap::new();
args.insert("debug-to-stderr".to_string(), None);
args
}),
..Default::default()
};
The Rust SDK closely mirrors the Python SDK API:
| Python | Rust |
|---|---|
async with ClaudeClient() as client: |
client.connect().await? |
await client.query("...") |
client.query("...").await? |
async for msg in client.receive_response(): |
while let Some(msg) = stream.next().await |
await client.interrupt() |
client.interrupt().await? |
await client.disconnect() |
client.disconnect().await? |
Contributions are welcome! Please feel free to submit a Pull Request.
# Clone the repository
git clone https://github.com/tyrchen/claude-agent-sdk-rs
cd claude-agent-sdk-rs
# Install dependencies
cargo build
# Run tests
cargo test
# Run examples
cargo run --example 01_hello_world
cargo fmt and cargo clippy before submittingThis SDK is based on the claude-agent-sdk-python specification.
This project is distributed under the terms of MIT.
See LICENSE.md for details.
If you find this project useful, please consider giving it a star on GitHub!
See CHANGELOG.md for version history and changes.