π¦ crimson-crab
The production-grade Rust SDK for Anthropic's Claude API.
Everything the Claude API can do, in idiomatic Rust β streaming, tool use, extended thinking, prompt caching, and batches β with wire-faithful, zero-surprise types and bulletproof retries. The definitive Rust client for building on Claude: depth beats breadth.
191 tests Β· zero clippy warnings Β· a panic-free library (unwrap/expect/panic denied at compile time) Β· MSRV 1.75 Β· MIT OR Apache-2.0
- Docs: docs.rs/crimson-crab
- Site: singhpratech.github.io/crimson-crab
- Source: github.com/singhpratech/crimson-crab
Install
Streaming and batch results are plain futures_core::Streams. To drive them with .next(), add the StreamExt extension trait:
30-second quickstart
use CLAUDE_OPUS_4_8;
use *;
async
Client is Clone + Send + Sync and shares one connection pool, so build it once and store it in your axum state, your MCP server struct, or a plain field β no Arc, no Mutex, no manual bounds.
Why crimson-crab
- Wire-faithful types β your code never breaks on new models. Types mirror the API field-for-field; there are no renamed concepts or leaky abstractions to relearn, and no adapter layer to lag behind a release.
- Forward-compatible enums β future models work day one. Every wire enum (content blocks, stream events, deltas, stop reasons, tool definitions, cache TTLs, thinking configs) carries an
Unknowncatch-all that preserves the raw JSON and re-serializes it unchanged instead of erroring. A response from a model the SDK has never heard of β including future Fable- and Mythos-class models β deserializes cleanly and round-trips verbatim. - A tokio-free public API β runs anywhere. The public surface exposes
futures_core::Stream, not runtime-specific types;tokiois a dev-dependency only. The same builder code compiles for native andwasm32-unknown-unknownon default features. - Official-SDK-parity retries β production-grade out of the box. Connection errors, timeouts,
408/409/429, and5xxare retried with full-jitter exponential backoff (0.5s base, 8s cap) and honorretry-afterβ capped at 60s so a hostile or broken server can't park your retry loop for hours. Streaming requests retry only before the first byte. - Streaming that never truncates mid-generation. The client uses an idle read timeout rather than a total-request deadline, so a long-but-actively-flowing SSE response is never cut off just because total elapsed time crossed a limit.
- Tested against real API fixtures. Every content block and stream event from the wire reference has a serde round-trip test, and every endpoint has
wiremockcoverage β 191 tests, zero clippy warnings, and a library that deniesunwrap/expect/panicso it cannot panic on you in production.
Feature coverage
| Capability | crimson-crab | Generic multi-provider clients |
|---|---|---|
| Messages (create / count tokens) | β | β |
Fine-grained SSE streaming + accumulated final Message |
β | usually text-only |
| Tool use (custom tools + server-tool passthrough) | β | partial |
| Extended thinking (adaptive / budgeted / display) | β | rare |
Prompt caching (cache_control, 5m/1h TTLs) |
β | rare |
Structured output (output_config JSON Schema) |
β | rare |
| Message Batches (create / poll / cancel / stream results) | β | rare |
| Models endpoint (get / list with pagination) | β | varies |
| Forward-compatible unknown-variant handling | β | varies |
New beta flags without an SDK release (betas + extra_body) |
β | rare |
Claude-specific features are first-class here because Claude is the only API this crate targets. Building against several model vendors? A multi-provider framework will serve you better β rig and genai are genuinely good. crimson-crab is for teams who have chosen Claude and want the whole surface, exactly as Anthropic ships it.
Streaming
Iterate typed events as they arrive; the stream accumulates a complete Message for you in the background.
use *;
use StreamExt;
# async
Relaying text deltas (SSE bodies, channels, web handlers)
MessageStream is Send + Unpin and crimson_crab::Error is Send + Sync + std::error::Error, so a streaming response drops straight into an axum Sse body (or any channel) with no Box::pin and no wrapper error type. Map the event stream down to plain String deltas with filter_map:
use *;
use StreamExt;
# async
# async # where
# S: Stream,
#
Tool use (manual agentic loop)
message.into_param() converts a response Message straight into a request MessageParam β the two tool_use blocks echoed verbatim β so the "append the assistant turn, then a user message of tool results" contract is two lines with no lossy serde round-trip. Parallel tool calls need no special handling: just iterate message.content.
use *;
use ToolResultBlockParam;
# async
// Your tool dispatch. Note `std::result::Result<_, _>`: see "Imports & the prelude".
#
Prompt caching & token budgeting
The simplest caching path: a plain-string system prompt plus a top-level cache_control, which auto-places one breakpoint on the last cacheable block β no per-block wiring.
use *;
# async
Token accounting for cost reports. The three input buckets are disjoint: input_tokens counts only the uncached input, while cache_creation_input_tokens and cache_read_input_tokens are separate. Total input tokens = input_tokens + cache_creation_input_tokens + cache_read_input_tokens. Never add the cache buckets into input_tokens β that double-bills the cached prefix.
Need the number before you spend on generation? Derive a count request from the same messages request β no rebuilding the prompt twice:
# use *;
# async
For fine-grained control you can attach a breakpoint to an individual block instead: build a TextBlockParam (in crimson_crab::types), set cache_control, and pass it as a system block or message content β see examples/prompt_caching.rs.
Message Batches
The whole submit β poll β stream β tally pipeline. BatchRequestItem::from_request turns a MessagesRequest into a batch entry with no hand-rolled JSON; BatchStatus and BatchRequestCounts are typed for progress display; and results() decodes the JSONL stream line-by-line, tolerating blank lines and a missing trailing newline.
use ;
use *;
use StreamExt;
# async
Imports & the prelude
use crimson_crab::prelude::*; is the fastest way to get the common types β Client, MessagesRequest, MessageParam, StreamEvent, ContentDelta, ContentBlock, ContentBlockParam, Tool, ToolChoice, StopReason, CacheControl, and more.
One thing worth knowing: the prelude also re-exports the crate's Result<T> and Error type aliases, which shadow std::result::Result / std::error::Error inside a glob import. If you write a two-type-argument Result<T, E> in the same scope β common with axum handlers, thiserror, or macro-heavy crates like rmcp β it resolves to the one-argument alias and fails with a confusing E0107 ("type alias takes 1 generic argument but 2 were supplied"). Two easy fixes:
- Fully-qualify it:
std::result::Result<String, String>(as in the tool-loop example above); or - Skip the glob and import exactly what you need. Curated paths:
crimson_crab::Client, request/response types undercrimson_crab::api::*(e.g.MessagesRequest), and the wire types undercrimson_crab::types::*(e.g.MessageParam,TextBlockParam,ToolResultBlockParam).
Platform support
The crate compiles for native targets and wasm32-unknown-unknown on default features β no feature juggling. On wasm, reqwest resolves to the browser fetch backend and TLS features are ignored, so you do not need to disable rustls-tls; cargo tree -i tokio --target wasm32-unknown-unknown prints nothing.
Two honest caveats for edge/browser deployments: the retry loop cannot sleep on wasm (there are no threads), so on that target retries fire without backoff and do not observe retry-after β the browser applies its own backpressure. Streaming type-checks on wasm but is best-effort and not exercised in a headless browser in CI. For a 429-sensitive edge worker, cap max_retries accordingly.
More examples
Runnable programs live in examples/: basic, streaming, tool_use, thinking, prompt_caching, and structured_output. Run one with:
ANTHROPIC_API_KEY=sk-ant-...
Build a Claude-powered MCP server
Want a ready-to-run Model Context Protocol server backed by Claude? Start from the template β a Rust MCP server (rmcp + crimson-crab) you can spin up in one click:
β crimson-crab-mcp-template β click Use this template, or:
Roadmap
Shipping fast and iterating; spec fidelity and release cadence are the whole point. On deck for v0.2+: the Files API (beta), schemars-derived tool schemas with a #[tool] macro, typed tool-input deserialization, a tool-runner loop helper, a batches().poll_until_ended() convenience, a parse::<T>() structured-output helper, the Admin/Usage API, and Vertex/Bedrock transports. The model field is an open string everywhere, so any model Anthropic ships works today with zero changes.
Minimum supported Rust version
MSRV is 1.75. Raising it is a minor-version change.
Semver policy
The wire enums are #[non_exhaustive] and carry Unknown catch-all variants, so a minor release may add a new known variant (for a feature Anthropic ships) without breaking your build. Match with a wildcard _ arm on SDK enums to stay forward-compatible across minor versions.
License
Licensed under either of MIT or Apache-2.0 at your option.
π¦ Crimson Crab Β· crimson-crab is an independent open-source project and is not affiliated with Anthropic.