8 releases
Uses new Rust 2024
| 0.3.0 | Oct 20, 2025 |
|---|---|
| 0.2.0 | Oct 7, 2025 |
| 0.1.11 | Sep 9, 2025 |
| 0.1.10 | Aug 15, 2025 |
| 0.1.5 | Jul 22, 2025 |
#220 in Asynchronous
369 downloads per month
Used in url-preview
300KB
5.5K
SLoC
Claude Code SDK for Rust
A Rust SDK for interacting with Claude Code CLI, providing both simple query interfaces and full interactive client capabilities.
Features
- 🚀 Simple Query Interface - One-shot queries with the
query()function - 💬 Interactive Client - Stateful conversations with context retention
- 🔄 Streaming Support - Real-time message streaming
- 🛑 Interrupt Capability - Cancel ongoing operations
- 🔧 Full Configuration - Comprehensive options for Claude Code
- 📦 Type Safety - Strongly typed with serde support
- ⚡ Async/Await - Built on Tokio for async operations
- 🔒 Control Protocol - Full support for permissions, hooks, and MCP servers (v0.1.11+)
- 💰 Token Optimization - Built-in tools to minimize costs and track usage (v0.1.12+)
Token Optimization (New in v0.1.12)
Minimize token consumption and control costs with built-in optimization tools:
use cc_sdk::{ClaudeCodeOptions, ClaudeSDKClient, PermissionMode};
use cc_sdk::token_tracker::BudgetLimit;
use cc_sdk::model_recommendation::ModelRecommendation;
// 1. Choose cost-effective model
let recommender = ModelRecommendation::default();
let model = recommender.suggest("simple").unwrap(); // → Haiku (cheapest)
// 2. Configure for minimal token usage
let options = ClaudeCodeOptions::builder()
.model(model)
.max_turns(Some(3)) // Limit conversation length
.max_output_tokens(2000) // Cap response size (NEW)
.allowed_tools(vec!["Read".to_string()]) // Restrict tools
.permission_mode(PermissionMode::BypassPermissions)
.build();
let mut client = ClaudeSDKClient::new(options);
// 3. Set budget with alerts
client.set_budget_limit(
BudgetLimit::with_cost(5.0), // $5 max
Some(|msg| eprintln!("⚠️ {}", msg)) // Alert at 80%
).await;
// ... run your queries ...
// 4. Monitor usage
let usage = client.get_usage_stats().await;
println!("Tokens: {}, Cost: ${:.2}", usage.total_tokens(), usage.total_cost_usd);
Key Features:
- ✅
max_output_tokens- Precise output control (1-32000, overrides env var) - ✅
TokenUsageTracker- Real-time token and cost monitoring - ✅
BudgetLimit- Set cost/token caps with 80% warning threshold - ✅
ModelRecommendation- Smart model selection (Haiku/Sonnet/Opus) - ✅ Automatic usage tracking from
ResultMessage
Model Cost Comparison:
- Haiku: 1x (baseline, cheapest)
- Sonnet: ~5x more expensive
- Opus: ~15x more expensive
See Token Optimization Guide for complete strategies and examples.
Complete Feature Set
This Rust SDK provides comprehensive functionality for Claude Code interactions:
- ✅ Client methods:
query(),send_message(),receive_response(),interrupt() - ✅ Interactive sessions: Full stateful conversation support
- ✅ Message streaming: Real-time async message handling
- ✅ Configuration options: System prompts, models, permissions, tools, etc.
- ✅ Message types: User, Assistant, System, Result messages
- ✅ Error handling: Comprehensive error types with detailed diagnostics
- ✅ Session management: Multi-session support with context isolation
- ✅ Type safety: Leveraging Rust's type system for reliable code
- ✅ Control Protocol: Permission callbacks, hook system, MCP servers (SDK type)
- ✅ CLI Compatibility: Configurable protocol format for maximum compatibility
Installation
Add this to your Cargo.toml:
[dependencies]
cc-sdk = "0.2.0"
tokio = { version = "1.0", features = ["full"] }
futures = "0.3"
Prerequisites
Install Claude Code CLI:
npm install -g @anthropic-ai/claude-code
Supported Models (2025)
The SDK supports the latest Claude models available in 2025:
Latest Models
-
Opus 4.1 - Most capable model
- Full name:
"claude-opus-4-1-20250805" - Alias:
"opus"(recommended - uses latest Opus)
- Full name:
-
Sonnet 4 - Balanced performance
- Full name:
"claude-sonnet-4-20250514" - Alias:
"sonnet"(recommended - uses latest Sonnet)
- Full name:
Previous Generation
- Claude 3.5 Sonnet -
"claude-3-5-sonnet-20241022" - Claude 3.5 Haiku -
"claude-3-5-haiku-20241022"(fastest)
Using Models in Code
use cc_sdk::{query, ClaudeCodeOptions, Result};
// Using Opus 4.1 (recommended: use alias)
let options = ClaudeCodeOptions::builder()
.model("opus") // or "claude-opus-4-1-20250805" for specific version
.build();
// Using Sonnet 4 (recommended: use alias)
let options = ClaudeCodeOptions::builder()
.model("sonnet") // or "claude-sonnet-4-20250514" for specific version
.build();
let mut messages = query("Your prompt", Some(options)).await?;
Quick Start
Simple Query (One-shot)
use cc_sdk::{query, Result};
use futures::StreamExt;
#[tokio::main]
async fn main() -> Result<()> {
let mut messages = query("What is 2 + 2?", None).await?;
while let Some(msg) = messages.next().await {
println!("{:?}", msg?);
}
Ok(())
}
Interactive Client
use cc_sdk::{InteractiveClient, ClaudeCodeOptions, Result};
#[tokio::main]
async fn main() -> Result<()> {
let mut client = InteractiveClient::new(ClaudeCodeOptions::default())?;
client.connect().await?;
// Send a message and receive response
let messages = client.send_and_receive(
"Help me write a Python web server".to_string()
).await?;
// Process responses
for msg in &messages {
match msg {
cc_sdk::Message::Assistant { message } => {
println!("Claude: {:?}", message);
}
_ => {}
}
}
// Send follow-up
let messages = client.send_and_receive(
"Make it use async/await".to_string()
).await?;
client.disconnect().await?;
Ok(())
}
Streaming Output (Since v0.1.8)
use cc_sdk::{InteractiveClient, ClaudeCodeOptions, Result};
use futures::StreamExt;
#[tokio::main]
async fn main() -> Result<()> {
let mut client = InteractiveClient::new(ClaudeCodeOptions::default())?;
client.connect().await?;
// Send a message
client.send_message("Explain quantum computing".to_string()).await?;
// Receive messages as a stream
let mut stream = client.receive_messages_stream().await;
while let Some(result) = stream.next().await {
match result {
Ok(message) => {
println!("Received: {:?}", message);
if matches!(message, cc_sdk::Message::Result { .. }) {
break;
}
}
Err(e) => eprintln!("Error: {}", e),
}
}
// Or use the convenience method that stops at Result message
client.send_message("What's 2 + 2?".to_string()).await?;
let mut stream = client.receive_response_stream().await;
while let Some(result) = stream.next().await {
match result {
Ok(message) => println!("Message: {:?}", message),
Err(e) => eprintln!("Error: {}", e),
}
}
client.disconnect().await?;
Ok(())
}
Advanced Usage
use cc_sdk::{InteractiveClient, ClaudeCodeOptions, Result};
#[tokio::main]
async fn main() -> Result<()> {
let mut client = InteractiveClient::new(ClaudeCodeOptions::default())?;
client.connect().await?;
// Send message without waiting for response
client.send_message("Calculate pi to 100 digits".to_string()).await?;
// Do other work...
// Receive response when ready (stops at Result message)
let messages = client.receive_response().await?;
// Cancel long-running operations
client.send_message("Write a 10,000 word essay".to_string()).await?;
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
client.interrupt().await?;
client.disconnect().await?;
Ok(())
}
Configuration Options
use cc_sdk::{ClaudeCodeOptions, PermissionMode, ControlProtocolFormat};
let options = ClaudeCodeOptions::builder()
.system_prompt("You are a helpful coding assistant")
.model("claude-3-5-sonnet-20241022")
.permission_mode(PermissionMode::AcceptEdits)
.max_turns(10)
.max_thinking_tokens(10000)
.allowed_tools(vec!["read_file".to_string(), "write_file".to_string()])
.cwd("/path/to/project")
// New in v0.1.6
.settings("claude-settings.json") // Use custom settings file
.add_dir("/path/to/related/project") // Add additional working directories
.add_dirs(vec![PathBuf::from("/dir1"), PathBuf::from("/dir2")]) // Add multiple dirs
// New in v0.1.11: Control protocol format configuration
.control_protocol_format(ControlProtocolFormat::Legacy) // Default: maximum compatibility
.build();
Control Protocol (v0.1.12+)
New request helpers and options aligned with the Python SDK:
Query::set_permission_mode("acceptEdits" | "default" | "plan" | "bypassPermissions")Query::set_model(Some("sonnet"))orset_model(None)to clearClaudeCodeOptions::builder().include_partial_messages(true)to include partial assistant chunksQuery::stream_input(stream)automatically calls end_input when finished
Example:
use cc_sdk::{Query, ClaudeCodeOptions};
use cc_sdk::transport::SubprocessTransport;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::Mutex;
# async fn demo() -> cc_sdk::Result<()> {
let options = ClaudeCodeOptions::builder()
.model("sonnet")
.include_partial_messages(true)
.build();
let transport: Box<dyn cc_sdk::transport::Transport + Send> =
Box::new(SubprocessTransport::new(options)?);
let transport = Arc::new(Mutex::new(transport));
let mut q = Query::new(transport, true, None, None, HashMap::new());
q.start().await?; // start routing
q.set_permission_mode("acceptEdits").await?;
q.set_model(Some("opus".into())).await?;
// Stream input; end_input is called automatically when the stream completes
let inputs = vec![serde_json::json!("Hello"), serde_json::json!({"content":"Ping"})];
q.stream_input(futures::stream::iter(inputs)).await?;
# Ok(()) }
Advanced flags mapped to CLI:
fork_session(true)→--fork-sessionsetting_sources(vec![User, Project, Local])→--setting-sources user,project,localagents(map)→--agents '<json>'
Agent Tools & MCP
- Tools whitelist/blacklist: set
allowed_tools/disallowed_toolsinClaudeCodeOptions. - Permission mode:
PermissionMode::{Default, AcceptEdits, Plan, BypassPermissions}. - Runtime approvals: implement
CanUseTooland returnPermissionResult::{Allow,Deny}. - MCP servers: configure via
options.mcp_servers(stdio/http/sse/sdk), SDK packs JSON for--mcp-config.
use cc_sdk::{ClaudeCodeOptions, PermissionMode, CanUseTool, ToolPermissionContext, PermissionResult,
PermissionResultAllow, transport::{Transport, SubprocessTransport}, Query};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::Mutex;
struct AllowRead;
#[async_trait::async_trait]
impl CanUseTool for AllowRead {
async fn can_use_tool(&self, tool:&str, _input:&serde_json::Value, _ctx:&ToolPermissionContext) -> PermissionResult {
if tool == "Read" { PermissionResult::Allow(PermissionResultAllow{updated_input: None, updated_permissions: None}) }
else { cc_sdk::PermissionResult::Deny(cc_sdk::PermissionResultDeny{ message: "Not allowed".into(), interrupt: false }) }
}
}
# async fn demo() -> cc_sdk::Result<()> {
let mut opts = ClaudeCodeOptions::builder()
.permission_mode(PermissionMode::AcceptEdits)
.include_partial_messages(true)
.build();
opts.allowed_tools = vec!["Read".into()];
let mut mcp = HashMap::new();
mcp.insert("filesystem".into(), cc_sdk::McpServerConfig::Stdio{ command: "npx".into(), args: Some(vec!["-y".into(), "@modelcontextprotocol/server-filesystem".into(), "/allowed".into()]), env: None });
opts.mcp_servers = mcp;
let transport: Box<dyn Transport + Send> = Box::new(SubprocessTransport::new(opts)?);
let transport = Arc::new(Mutex::new(transport));
let mut q = Query::new(transport, true, Some(Arc::new(AllowRead)), None, HashMap::new());
q.start().await?;
# Ok(()) }
Control Protocol Compatibility (v0.1.11+)
The SDK supports configurable control protocol formats for CLI compatibility:
- Legacy (default): Uses
sdk_control_request/responseformat - works with all CLI versions - Control: Uses new
type=controlformat - for newer CLI versions - Auto: Currently defaults to Legacy, will auto-negotiate in future
// Use environment variable to override (useful for testing)
// export CLAUDE_CODE_CONTROL_FORMAT=legacy # or "control"
// Or configure programmatically
let options = ClaudeCodeOptions::builder()
.control_protocol_format(ControlProtocolFormat::Legacy)
.build();
See CONTROL_PROTOCOL_COMPATIBILITY.md for detailed information.
API Reference
query()
Simple, stateless query function for one-shot interactions.
pub async fn query(
prompt: impl Into<String>,
options: Option<ClaudeCodeOptions>
) -> Result<impl Stream<Item = Result<Message>>>
InteractiveClient
Main client for stateful, interactive conversations.
Methods
new(options: ClaudeCodeOptions) -> Result<Self>- Create a new clientconnect() -> Result<()>- Connect to Claude CLIsend_and_receive(prompt: String) -> Result<Vec<Message>>- Send message and wait for complete responsesend_message(prompt: String) -> Result<()>- Send message without waitingreceive_response() -> Result<Vec<Message>>- Receive messages until Result messageinterrupt() -> Result<()>- Cancel ongoing operationdisconnect() -> Result<()>- Disconnect from Claude CLI
Message Types
UserMessage- User input messagesAssistantMessage- Claude's responsesSystemMessage- System notificationsResultMessage- Operation results with timing and cost info
Error Handling
The SDK provides comprehensive error types:
CLINotFoundError- Claude Code CLI not installedCLIConnectionError- Connection failuresProcessError- CLI process errorsInvalidState- Invalid operation state
Examples
Check the examples/ directory for more usage examples:
interactive_demo.rs- Interactive conversation demoquery_simple.rs- Simple query examplefile_operations.rs- File manipulation example
New Features (v0.1.6)
Test the latest features with these examples:
test_settings.rs- Using custom settings filestest_settings_safe.rs- Safe settings file handling with path detectiontest_add_dirs.rs- Adding multiple working directoriestest_combined_features.rs- Combining settings and add_dirstest_new_options.rs- Testing the new builder methods
Example settings files are provided:
examples/claude-settings.json- Basic settings configurationexamples/custom-claude-settings.json- Advanced settings with MCP servers
Note: When running examples from the project root, use:
cargo run --example test_settings
The settings files use relative paths from the project root (e.g., examples/claude-settings.json)
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Dependencies
~7–22MB
~296K SLoC