7.5 KiB
claude-agent-sdk (Rust)
Async Rust SDK for the Claude Agent CLI — a faithful port of the official
Python claude-agent-sdk.
This crate wraps the claude CLI as a subprocess and exposes its
newline-delimited JSON stream as ergonomic, strongly-typed Rust values. The
Python SDK's two entry points map directly:
query()for fire-and-forget one-shot prompts.ClaudeSDKClient→claude_agent_sdk::Clientfor bidirectional, multi-turn sessions.
It is an independent, community-maintained port and is not affiliated with or endorsed by Anthropic.
Status
v0.0.1 — initial port. Core feature parity for the subprocess wire protocol; advanced features (control protocol, hooks, in-process MCP servers, session store, sandbox, plugins, agents) are deferred. See scope and known limitations below.
Requirements
- Rust 1.85 or newer (the crate uses the 2024 edition).
- The
claudeCLI available onPATH(or supplied explicitly viaClaudeAgentOptions::with_cli_path()), and an authenticated Claude session. The SDK does not bundle the CLI binary — it shells out to whateverclaudeit finds.
Install
The crate is distributed as a git dependency:
[dependencies]
claude-agent-sdk = { git = "https://git.sulkta.com/Sulkta-OSS/claude-agent-sdk-rust" }
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"
Pin to a tag or commit (rev = "..." / tag = "...") for reproducible
builds.
Quick start
use claude_agent_sdk::{query, ClaudeAgentOptions, Message, ContentBlock};
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> claude_agent_sdk::Result<()> {
let opts = ClaudeAgentOptions::new()
.with_system_prompt("You are a helpful assistant.")
.with_max_turns(1);
let mut stream = query("What is 2 + 2?", opts).await?;
while let Some(msg) = stream.next().await {
match msg? {
Message::Assistant(a) => {
for block in a.message.content {
if let ContentBlock::Text(t) = block {
println!("Claude: {}", t.text);
}
}
}
Message::Result(r) => {
if let Some(usd) = r.total_cost_usd {
println!("Cost: ${:.4}", usd);
}
break;
}
_ => {}
}
}
Ok(())
}
query() returns a stream that ends when the CLI emits its terminal result
message, automatically tearing down the subprocess.
Multi-turn sessions
use claude_agent_sdk::{Client, ClaudeAgentOptions, Message, ContentBlock};
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> claude_agent_sdk::Result<()> {
let mut client = Client::new(ClaudeAgentOptions::new()).await?;
client.connect().await?;
let mut messages = client.messages();
client.send("What's the capital of France?").await?;
// ...consume messages until a Result frame, then ask a follow-up...
client.send("And of Germany?").await?;
// Drain until the next Result, then disconnect.
while let Some(msg) = messages.next().await {
if let Message::Result(_) = msg? {
break;
}
}
client.disconnect().await?;
Ok(())
}
Client keeps a single CLI subprocess alive across turns, so follow-up
prompts don't pay the cold-start cost again.
Configuration
Both entry points are configured with ClaudeAgentOptions, a builder you
chain .with_*() methods on:
use claude_agent_sdk::{ClaudeAgentOptions, PermissionMode};
let opts = ClaudeAgentOptions::new()
.with_model("claude-sonnet-4-5")
.with_permission_mode(PermissionMode::AcceptEdits)
.with_allowed_tool("Read")
.with_allowed_tool("Bash")
.with_cwd("/tmp/my-project")
.with_max_turns(5);
See the ClaudeAgentOptions API docs for the full list of supported fields.
Examples
Runnable examples live in examples/. With the claude CLI on
PATH and authenticated:
cargo run --example basic # minimal one-shot query()
cargo run --example with_options # query() with a configured options builder
cargo run --example interactive # multi-turn Client session
Building and testing
cargo build
cargo test # unit + end-to-end tests. A fake CLI fixture stands in
# for the real `claude` binary, so the suite needs no
# network access or authenticated session to run.
cargo clippy --all-targets
cargo fmt --check
Mapping the Python SDK
| Python | Rust |
|---|---|
query(prompt=...) |
query(prompt, opts).await? |
ClaudeSDKClient |
Client |
ClaudeAgentOptions |
ClaudeAgentOptions |
AssistantMessage |
Message::Assistant(_) |
UserMessage |
Message::User(_) |
SystemMessage |
Message::System(_) |
ResultMessage |
Message::Result(_) |
TextBlock / ToolUseBlock |
ContentBlock::Text/ToolUse(_) |
CLINotFoundError |
Error::CliNotFound |
CLIConnectionError |
Error::CliConnection |
ProcessError |
Error::Process { .. } |
CLIJSONDecodeError |
Error::JsonDecode { .. } |
MessageParseError |
Error::MessageParse { .. } |
Scope and known limitations
This release covers the core path of the Python SDK:
- Subprocess transport with newline-delimited JSON framing.
- All message and content-block types parsed faithfully.
query()+Clientfor one-shot and bidirectional use.- The
ClaudeAgentOptionsflag surface relevant to subprocess arguments.
The following are deferred — see the upstream Python README for the current shape of these features:
- Control protocol over JSON-RPC —
interrupt(),set_permission_mode(),set_model(),get_mcp_status(), etc. The RustClientcurrently speaks only the bare user / assistant / result frames. Adding the control protocol unlocks the rest of the API. can_use_toolpermission callback — requires the control protocol.- In-process MCP servers (
@tool/create_sdk_mcp_server) — needs a Rust trait/derive-macro shape that isn't drafted yet. HookMatcher— the wire format is supported on the CLI side, but the Rust callback surface is not designed.SessionStoremirroring adapter.Sandboxsettings, plugins, agents dataclass.
Each of these degrades gracefully — a caller using a newer CLI sees fewer features, not breakage.
Contributing
Contributions are welcome. A good change:
- keeps the wire protocol faithful to the upstream Python SDK (cite the behavior you're matching where it isn't obvious);
- comes with a test — the end-to-end suite drives a fake CLI over the real
framing, so most behavior can be covered without a live
claudebinary; - passes
cargo fmt --check,cargo clippy --all-targets, andcargo test.
Open an issue to discuss larger features (especially anything from the deferred list above) before sending a big patch.
License
MIT. See LICENSE.
This is an independent port; the upstream Python SDK is © Anthropic, PBC, and
the bundled LICENSE preserves that notice alongside this port's copyright.