> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentcat.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust: behavior and limits

> The versions and features of the Rust SDK, what agents see, how hooks, errors and delivery behave, what it sends, and its known limits.

This page lists the versions and features that the Rust SDK supports, what agents see, how its hooks, errors and delivery behave, what it sends, and the limits that you can hit. [Rust: rmcp](https://docs.agentcat.com/sdk/rust/rmcp.md) shows how to track a server, [Rust runtimes](https://docs.agentcat.com/sdk/rust/runtimes.md) covers how it runs, and the [crate guide](https://docs.rs/agentcat-sdk) on docs.rs is the full reference.

## Supported versions

* **Rust.** Rust 1.88 or later. The minimum supported Rust version (MSRV) is the MSRV of `rmcp` 3, and it is never higher than the MSRV of the `rmcp` versions that the crate supports. The MSRV is always a Rust release that is at least 6 months old. An MSRV increase is a minor release and has its own CHANGELOG section.
* **`rmcp`.** `rmcp` 3.0.0 or a later 3.x release. CI tests the latest patch of each 3.x minor release.
* **tokio.** A tokio runtime with the time driver (see [The tokio runtime](https://docs.agentcat.com/sdk/rust/runtimes.md#the-tokio-runtime)). The `local` feature of `rmcp` is not supported.

## Features

| Feature | Default | Effect |
| - | - | - |
| `rustls` | on | TLS with rustls for the connection to the AgentCat API. |
| `native-tls` | off | TLS with the TLS library of the platform. |
| `test-util` | off | The `testing` module with `testing::Fake`, for your tests. |

Turn on one TLS feature. To use `native-tls`, turn off the default features (`default-features = false`) and turn on `native-tls`. Without a TLS feature, the SDK can send events only to an `http:` URL. It logs a warning when the URL is an `https:` URL.

## What agents see

The SDK changes the tool list that `tools/list` returns:

* Each tool gets a `context` parameter, in which the agent says why it calls the tool (see [Tool Call Context](https://docs.agentcat.com/sdk/tool-call-context.md)).
* Each tool gets a `session_id` parameter, which ties the calls of one task together (see [Stateless Servers](https://docs.agentcat.com/sdk/stateless-servers.md)).
* With `enable_agent_tracking(true)`, each tool gets an `agent_id` parameter, in which the agent names itself.
* The list gets one more tool, `get_more_tools`, through which agents report a tool that they miss (see [Agent Feedback](https://docs.agentcat.com/sdk/report-missing-tools.md)).

A tool that declares its own parameter with one of these names keeps it, and the SDK does not add its own. Before your tool runs, the SDK removes the parameters that it added from the arguments, so your tool never sees them.

The first call of a task gets a text block that begins `[session_id issued` before the content of the tool's own result. An error result gets the block too. A JSON-RPC error (`Err(ErrorData)`) has no content, so it gets no block. A `session_id` that is neither `start` nor a valid `ses_` ID gets a text block that begins `[session_id unrecognized`, and the call then has no session. When a tool declares an output schema, the SDK adds an `mcp_session` property to the schema. A structured result of that tool then gets `mcp_session` as its first key, with the session ID, the agent ID when the agent sent one, and a status.

## Hooks, errors and delivery

### Hooks get a `ToolCall`

The hooks get a `ToolCall`, not the raw request and the handler context (see [Rust: the ToolCall](https://docs.agentcat.com/sdk/identifying-users.md#rust-the-toolcall)). They are async closures that return a `Result`: an `Err` gives no value, and the SDK logs it.

### Hooks that do not complete

The SDK stops a call hook (`identify`, `event_tags`, `event_properties` or `resolve_session_id`) that has not completed within 30 seconds, and sends the event without its value.

### Errors

* **JSON-RPC errors.** A tool that returns `Err(ErrorData)` gives the client a JSON-RPC error, as `rmcp` sends it. The event gets the name of the JSON-RPC error code as `error.type`.
* **No stack frames.** The `error` of an event has the platform `rust` and no stack frames.
* **Cancelled calls.** A tool call that is cancelled (its future dropped) gives an event with the error type `cancelled`.
* **Panics.** When a tool panics, the SDK records the panic as the error of the call, then lets the panic go on unchanged.

`rmcp` turns an error that a tool returns into the text of an error result, so the event gets only that text. Call `capture_error` before the tool returns the error. The event then also gets the type of the error and the texts of its `source()` chain:

```rust theme={null}
/// Always fails. `capture_error` records the type and the cause chain of the error, which the
/// text of an `is_error` result would lose.
#[tool(description = "Always fails")]
async fn fail(&self) -> Result<String, String> {
    let error = std::io::Error::new(std::io::ErrorKind::NotFound, "the record does not exist");
    capture_error(&error);
    Err(error.to_string())
}
```

When a tool calls `capture_error` more than once, the last error counts. It works in the tool's own future only: in a task that the tool spawns, or in a thread that it starts, it does nothing. The result and the error of the tool reach the client unchanged.

### Delivery and shutdown

* **One queue for each handle.** Each `AgentCat` handle has its own queue and API URL.
* **No signal handlers.** `AgentCat::flush`, `AgentCat::shutdown` and the drain of the last drop deliver the queued events. The SDK installs no signal or exit handler (see [Flush and shutdown](https://docs.agentcat.com/sdk/rust/runtimes.md#flush-and-shutdown)).

### Switches, tests and logging

* **`AGENTCAT_ENABLED`** turns the SDK off with no code (see [Environment variables](https://docs.agentcat.com/sdk/rust/runtimes.md#environment-variables)).
* **`testing::Fake`**, behind the `test-util` feature, builds handles whose events stay in the process. It records each event as a real handle sends it, after the hooks and the redaction, and `assert_tracked`, `assert_tracked_count`, `assert_custom_event_published`, `assert_nothing_tracked` and `events` check what it recorded. The Testing section of the [crate guide](https://docs.rs/agentcat-sdk) has a complete test.
* **`LogDestination::Tracing`** sends the log lines of the SDK to `tracing` (see [Logging](https://docs.agentcat.com/sdk/rust/runtimes.md#logging)). Without it, the SDK writes `~/agentcat.log`.
* **`flush_blocking` and `shutdown_blocking`** wait for the delivery without a tokio runtime.

### Not in this release

The Rust SDK has no exporters (OpenTelemetry, Datadog, Sentry, PostHog) and no SDK diagnostics yet, and `Options` has no setter for either.

## What the Rust SDK sends

* **Tool calls and custom events.** The SDK publishes one event for each `tools/call` request, and the custom events that your code publishes. Tool listings, resources and prompts give no events.
* **Request headers and URL.** On a streamable HTTP server, the event of a tool call carries every request header of that call, `authorization` and `cookie` included, under `parameters.extra.requestInfo.headers`, and the URL of the request under `parameters.extra.requestInfo.url`. Header names are in lower case. The values of a repeated header are joined with `, ` (`; ` for `cookie`), and a repeated `set-cookie` keeps its last value. A stdio server sends no headers. Remove what you do not want in a `redact_event` hook (see [Client-Side Redaction](https://docs.agentcat.com/privacy-security/client-side-redaction.md)).
* **No diagnostics.** The SDK sends none of its own log lines to AgentCat.

## Known limits

* **The tokio time driver.** A tracked server needs a tokio runtime with the time driver: the SDK times a rebuild of its tool registry with `tokio::time::timeout`, and tokio panics at such a timer on a runtime without the driver. `rmcp` uses timers too. `#[tokio::main]`, `#[tokio::test]` and `tokio::runtime::Runtime::new` turn the time driver on. A runtime from `tokio::runtime::Builder` needs `enable_time` or `enable_all`.
* **The `local` feature of `rmcp`.** The SDK does not support it. It spawns the hooks as `Send` tasks on the tokio runtime of the call.
* **`preserve_order` of `serde_json`.** The SDK turns on the `preserve_order` feature of `serde_json`, so that `mcp_session` stays the first key of a result. Cargo turns a feature on for the whole build, so every `serde_json::Map` in your program keeps the order of insertion and does not sort its keys.
* **`arbitrary_precision` and `float_roundtrip`.** When another crate in your build turns on the `arbitrary_precision` or the `float_roundtrip` feature of `serde_json`, Cargo turns it on for the SDK too. These features change how `serde_json` reads and writes numbers: the text of some numbers in the events, the token estimates and the size checks can change. The SDK logs a warning that names the feature each time its delivery thread starts.
* **URLs behind `nest_service`.** `parameters.extra.requestInfo.url` of an event is the URL that the `rmcp` service sees. `axum::Router::nest_service` removes the path prefix of the route before that, so the event records the URL without the prefix. `route_service` keeps the full path.
* **The tool registry.** The SDK keeps a registry of what the tool listings tell it, for each handle and each type of server handler, by tool name. It never removes an entry, so a server whose tool names differ per tenant grows the registry with each new name. A hand-written `ServerHandler` without `get_tool` depends on that registry (`#[tool_handler]` writes `get_tool` for you). On such a server, the listing of one session decides which arguments the SDK removes from the calls of another session.
* **A hook that holds its handle.** A hook that holds a clone of its `AgentCat` handle makes a reference cycle. The handle then never drops, so the drain of the last drop never runs. Let the hook capture only what it needs, or call `AgentCat::shutdown` before the process ends. A tool reaches its handle through `CallContext::agentcat` and needs no clone.
* **Redaction hooks have no time limit.** The SDK gives the redaction hooks no time limit. A redaction hook that never completes holds one of the 5 delivery slots of its handle for good. Five such hooks stop the delivery of the handle: later events wait in the queue until a shutdown abandons them.
* **The end of the process.** The drain of the last drop does not run after `std::process::exit`, or after a panic with `panic = "abort"`. On a current-thread runtime, call `shutdown` before the runtime ends (see [Current-thread runtimes](https://docs.agentcat.com/sdk/rust/runtimes.md#current-thread-runtimes)).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.