| Crates.io | claude-agents-sdk |
| lib.rs | claude-agents-sdk |
| version | 0.1.3 |
| created_at | 2026-01-02 11:51:03.144175+00 |
| updated_at | 2026-01-03 20:58:28.94837+00 |
| description | Rust SDK for building agents with Claude Code CLI |
| homepage | |
| repository | https://github.com/jimmystridh/claude-agents-sdk |
| max_upload_size | |
| id | 2018432 |
| size | 642,317 |
A Rust SDK for building agents that interact with the Claude Code CLI.
Inspired by the Python Claude Agent SDK, providing similar functionality with idiomatic Rust APIs.
query() functionClaudeClient for complex interactionsAdd to your Cargo.toml:
[dependencies]
claude-agents-sdk = "0.1"
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"
For MCP tool support:
[dependencies]
claude-agents-sdk = { version = "0.1", features = ["mcp"] }
use claude_agents_sdk::{query, ClaudeAgentOptions, Message};
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ClaudeAgentOptions::new()
.with_max_turns(3);
let mut stream = query("What is 2 + 2?", Some(options), None).await?;
while let Some(message) = stream.next().await {
match message? {
Message::Assistant(msg) => print!("{}", msg.text()),
Message::Result(result) => {
println!("\nCost: ${:.4}", result.total_cost_usd.unwrap_or(0.0));
}
_ => {}
}
}
Ok(())
}
use claude_agents_sdk::{ClaudeClient, ClaudeAgentOptions};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = ClaudeClient::new(None, None);
client.connect().await?;
// First query
client.query("What is the capital of France?").await?;
let (response, _) = client.receive_response().await?;
println!("Response: {}", response);
// Follow-up query
client.query("What's its population?").await?;
let (response, _) = client.receive_response().await?;
println!("Response: {}", response);
client.disconnect().await?;
Ok(())
}
use claude_agents_sdk::{ClaudeClientBuilder, PermissionResult, PermissionMode};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = ClaudeClientBuilder::new()
.permission_mode(PermissionMode::Default)
.can_use_tool(|tool_name, input, _ctx| async move {
println!("Tool requested: {} with {:?}", tool_name, input);
// Allow Read, deny dangerous Bash commands
if tool_name == "Bash" {
if let Some(cmd) = input.get("command").and_then(|v| v.as_str()) {
if cmd.contains("rm -rf") {
return PermissionResult::deny_with_message("Dangerous command");
}
}
}
PermissionResult::allow()
})
.build();
client.connect().await?;
// ... use client
Ok(())
}
query(prompt, options, transport) - One-shot query returning a message streamquery_all(prompt, options) - Collect all messages from a queryquery_result(prompt, options) - Get final response and result metadatalet mut client = ClaudeClient::new(options, transport);
client.connect().await?;
client.query("Hello").await?;
let (response, result) = client.receive_response().await?;
client.disconnect().await?;
Methods:
connect() - Connect to CLIquery(prompt) - Send a queryreceive_messages() - Stream of messagesreceive_response() - Collect response and resultinterrupt() - Interrupt current operationset_permission_mode(mode) - Change permission modeset_model(model) - Change modelrewind_files(message_id) - Rewind to checkpointdisconnect() - Disconnect from CLIlet options = ClaudeAgentOptions::new()
.with_model("claude-3-sonnet")
.with_system_prompt("You are helpful.")
.with_max_turns(10)
.with_permission_mode(PermissionMode::AcceptEdits)
.with_allowed_tools(vec!["Bash".into(), "Read".into()])
.with_partial_messages();
enum Message {
User(UserMessage),
Assistant(AssistantMessage),
System(SystemMessage),
Result(ResultMessage),
StreamEvent(StreamEvent),
}
// AssistantMessage has helpful methods
let text = assistant_msg.text();
let tool_uses = assistant_msg.tool_uses();
enum ContentBlock {
Text(TextBlock),
Thinking(ThinkingBlock),
ToolUse(ToolUseBlock),
ToolResult(ToolResultBlock),
}
Run the examples:
# Simple one-shot query
cargo run --example simple_query
# Streaming client with multiple queries
cargo run --example streaming_client
# Tool permission callbacks
cargo run --example tool_permission_callback
# Lifecycle hooks (PreToolUse, PostToolUse)
cargo run --example hooks
# Error handling patterns
cargo run --example error_handling
# Streaming with progress indicators
cargo run --example streaming_progress
# System prompt configuration
cargo run --example system_prompt
# Budget limits
cargo run --example max_budget_usd
# MCP tools (requires --features mcp)
cargo run --example mcp_calculator --features mcp
See examples/ directory for the full list.
Unit tests run without authentication:
cargo test
# or
make test
Integration tests run against the real Claude API in Docker. They require authentication.
Setup (one-time):
# Generate an OAuth token (requires Claude Pro/Max subscription)
claude setup-token
# Create .env file with your token
cp .env.example .env
# Edit .env and paste your token
Run tests:
# Run all integration tests
make integration-test
# Run with verbose output
make integration-test-verbose
# Run specific test
./scripts/run-integration-tests.sh test_oneshot
# Interactive shell for debugging
make integration-shell
What's tested:
See TESTING.md for the complete testing guide.
The SDK provides ClaudeSDKError, a comprehensive error type for all failure modes.
use claude_agents_sdk::ClaudeSDKError;
match result {
// CLI not found or not installed
Err(ClaudeSDKError::CLINotFound { message }) => {
eprintln!("Claude CLI not installed: {}", message);
eprintln!("Install from: https://docs.anthropic.com/en/docs/claude-code");
}
// CLI process exited with error
Err(ClaudeSDKError::Process { exit_code, stderr, .. }) => {
eprintln!("CLI failed with exit code {:?}", exit_code);
if let Some(err) = stderr {
eprintln!("stderr: {}", err);
}
}
// Operation timed out
Err(ClaudeSDKError::Timeout { duration_ms }) => {
eprintln!("Operation timed out after {}ms", duration_ms);
}
// Connection to CLI failed
Err(ClaudeSDKError::CLIConnection { message }) => {
eprintln!("Failed to connect to CLI: {}", message);
}
// Invalid configuration
Err(ClaudeSDKError::Configuration { message }) => {
eprintln!("Configuration error: {}", message);
}
// Message parsing failed (malformed JSON from CLI)
Err(ClaudeSDKError::MessageParse { message, .. }) => {
eprintln!("Failed to parse CLI message: {}", message);
}
// Control protocol error
Err(ClaudeSDKError::ControlProtocol { message, .. }) => {
eprintln!("Control protocol error: {}", message);
}
// All other errors
Err(e) => eprintln!("Error: {}", e),
Ok(_) => {}
}
Some errors can be retried:
if error.is_recoverable() {
// Can retry: CLIConnection, Timeout, Channel errors
tokio::time::sleep(Duration::from_secs(1)).await;
// ... retry operation
}
Configure timeouts to prevent indefinite hangs:
let options = ClaudeAgentOptions::new()
.with_timeout_secs(60) // 60 second timeout
.with_max_turns(5);
// Or disable timeout (not recommended)
let options = ClaudeAgentOptions::new()
.with_timeout_secs(0); // No timeout
Default timeout is 300 seconds (5 minutes).
When consuming message streams, handle errors per-message:
while let Some(result) = stream.next().await {
match result {
Ok(Message::Assistant(msg)) => {
println!("{}", msg.text());
}
Ok(Message::Result(result)) => {
if result.is_error {
eprintln!("Query failed: {:?}", result.result);
}
}
Err(e) => {
eprintln!("Stream error: {}", e);
break;
}
_ => {}
}
}
All SDK functions return Result<T, ClaudeSDKError>:
use claude_agents_sdk::Result;
async fn my_function() -> Result<String> {
let mut client = ClaudeClient::new(None, None);
client.connect().await?; // Propagates errors with ?
// ...
Ok("success".to_string())
}
The SDK communicates with the Claude Code CLI via a subprocess:
┌─────────────────┐ stdin/stdout ┌──────────────┐
│ Rust SDK │ ◄──── JSON-RPC ────► │ Claude CLI │
│ (your code) │ │ (subprocess)│
└─────────────────┘ └──────────────┘
Key internal components:
Transport - Abstract communication layerSubprocessTransport - Subprocess implementationQuery - Control protocol handlerInternalClient - Core query processing| Feature | Python | Rust |
|---|---|---|
| One-shot query | query() |
query() |
| Streaming client | ClaudeSDKClient |
ClaudeClient |
| Tool callback | can_use_tool |
can_use_tool |
| Hooks | hooks dict |
hooks HashMap |
| Async | asyncio | tokio |
| Type safety | TypedDict | Enums + Structs |
MIT License - see LICENSE for details.