claude-code-rs 0.1.1

Rust rewrite of the official Anthropic Claude Agent SDK (Python). Wraps Claude Code CLI via subprocess with bidirectional JSON streaming.
Documentation

claude-code-rs

Rust rewrite of the official Anthropic Claude Agent SDK (Python).

Wraps the Claude Code CLI via subprocess with a bidirectional JSON streaming protocol. Full async, typed messages, hooks, permissions, and in-process MCP server support.

Requirements

Quick Start

[dependencies]
claude-code-rs = "0.1"
tokio = { version = "1", features = ["full"] }
use claude_code_rs::{query_text, ClaudeAgentOptions, PermissionMode};

#[tokio::main]
async fn main() -> claude_code_rs::Result<()> {
    let options = ClaudeAgentOptions {
        permission_mode: PermissionMode::AcceptAll,
        max_turns: Some(1),
        ..Default::default()
    };

    let response = query_text("What is 2+2? Reply with just the number.", options).await?;
    println!("{response}");
    Ok(())
}

APIs

One-shot query

// Stream messages
let mut stream = claude_code_rs::query("prompt", options).await?;

// Collect all messages
let messages = claude_code_rs::query_collect("prompt", options).await?;

// Get text only
let text = claude_code_rs::query_text("prompt", options).await?;

Stateful client (multi-turn)

use claude_code_rs::{ClaudeSDKClient, ClaudeAgentOptions, Message};
use tokio_stream::StreamExt;

let mut client = ClaudeSDKClient::new(ClaudeAgentOptions::default());
client.connect(None).await?;

client.query("What is Rust?", None).await?;
let messages = client.receive_response().await?;

client.query("How does ownership work?", None).await?;
let messages = client.receive_response().await?;

client.disconnect().await?;

Hooks

use claude_code_rs::*;
use claude_code_rs::types::hooks::HookInput;

let hook = HookDefinition {
    event: HookEvent::PreToolUse,
    matcher: HookMatcher { tool_name: Some("Bash".into()) },
    callback: hook_callback(|input| async move {
        if let HookInput::PreToolUse(pre) = &input {
            let cmd = pre.tool_input.get("command")
                .and_then(|v| v.as_str()).unwrap_or("");
            if cmd.contains("rm ") {
                return HookOutput::block("rm not allowed");
            }
        }
        HookOutput::approve()
    }),
};

let options = ClaudeAgentOptions {
    hooks: vec![hook],
    ..Default::default()
};

In-process MCP tools

use claude_code_rs::*;

let tool = new_tool(
    "add", "Add two numbers",
    serde_json::json!({"type": "object", "properties": {"a": {"type": "number"}, "b": {"type": "number"}}}),
    |input| async move {
        let a = input.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0);
        let b = input.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0);
        McpToolResult::text(format!("{}", a + b))
    },
);

let server = create_sdk_mcp_server(vec![tool]);
let mut client = ClaudeSDKClient::new(ClaudeAgentOptions::default());
client.add_mcp_server(server);

Architecture

Mirrors the Python SDK's architecture:

Layer Python Rust
Transport SubprocessCLITransport SubprocessTransport + TransportWriter
Protocol Query Query (spawn_router task)
Messages dataclasses enums + serde
One-shot API query() query() / query_text() / query_collect()
Stateful API ClaudeSDKClient ClaudeSDKClient
MCP SdkMcpServer SdkMcpServer + JSONRPC router

All communication is newline-delimited JSON over stdin/stdout with a bidirectional control protocol for hooks, permissions, MCP routing, and interrupts.

Examples

cargo run --example quick_start
cargo run --example streaming
cargo run --example hooks
cargo run --example mcp_calculator

License

MIT - same as the original Python SDK.