agentcat-sdk crate, version 1.x, for rmcp 3.x. For the first setup, see Rust setup. The crate guide on docs.rs is the full API reference.
Options
Options::default() has the defaults below. Each setter returns the changed options, which you pass to AgentCat::new(project_id, options), AgentCat::from_env(options), or agentcat_sdk::track(server, project_id, options).
We recommend the defaults of
enable_tool_call_context, enable_report_missing, and enable_tracing, because each feeds a part of the dashboard: intent data, missing-tool reports, and sessions.UserIdentity::new(user_id) takes the user ID, and the setters user_name and user_data (a serde_json::Map) add the optional fields.
An empty project ID turns on telemetry-only mode: the handle builds each event and runs your hooks, but sends nothing. AgentCat::from_env does the same when AGENTCAT_PROJECT_ID is unset or empty.
The SDK reads these environment variables:
The SDK has no exporters or diagnostics reports yet. When
AGENTCAT_ENABLED turns the SDK off, a handle starts no thread and its Tracked servers pass every request to your handler untouched. Custom events publish nothing, and flush and shutdown return true.
Framework and transport details
rmcp 3.x
- Stateless HTTP.
StreamableHttpService::newcalls your closure for each request, and the setup snippet wraps the new handler there withAgentCat::track. It also setswith_legacy_session_mode(false)andwith_json_response(true)and mounts the service inaxumwithroute_service. On a stateless server, an event names the client only when the request_metadoes. The defaultallowed_hostsofrmcpaccepts only loopback hosts, so a server behind a public hostname needswith_allowed_hosts. - Streamable HTTP with sessions. The default
StreamableHttpServerConfigkeeps one MCP session per client and answers with server-sent events, for clients on protocol versions before 2026-07-28. Requests that negotiate 2026-07-28 always get stateless service.rmcpcalls your closure once per new session. Thesession_idparameter groups calls on every transport, and the SDK ignores themcp-session-idheader. - stdio.
AgentCat::from_env(options).track(server)returns aTrackedserver that you serve withrmcp::transport::stdio(). - The handle.
AgentCatstarts no thread until the first event or log line, and a clone is cheap and shares everything with the original. We recommend one handle for the process. - Crate features.
rustls(default) ornative-tls(withdefault-features = false) connects to the AgentCat API, andtest-utiladds thetestingmodule. Without a TLS feature, the SDK can send events only to anhttp:URL.
What track() changes in your server
- Each tool gets
session_idandcontextparameters (andagent_idwithenable_agent_tracking(true)), and the tool list getsget_more_tools. AgentCat removes the injected values before your tool runs. - The first call of a task gets a text block that begins
[session_id issuedbefore the result content, error results included. A JSON-RPC error (Err(ErrorData)) has no content, so it gets no block. - A
session_idthat is neitherstartnor a validses_ID gets a text block that begins[session_id unrecognized, and the call has no session. - When a tool declares an output schema, AgentCat adds an
mcp_sessionproperty to it, and a structured result getsmcp_sessionas its first key. - A tool that declares its own
session_id,agent_id, orcontextkeeps it, and AgentCat adds none. - A hand-written
ServerHandlerwithoutget_tooldepends on the registry of tool listings that the SDK keeps. The#[tool_handler]macro writesget_toolfor you.
The call context in a tool
A tool reads itsCallContext as an Extension<CallContext> parameter, next to Parameters<..>:
session_id()returns the session of the call, orNone.agent_id()returns theagent_idthat the agent sent, when agent tracking is on.agentcat()returns the handle that tracks the call.publish_custom_event(project_id, event)queues a custom event with the client and the server of this call, and returns at once.
call.session_id() to CustomEvent::session_id. Code outside a tool call has no CallContext, so it uses AgentCat::publish_custom_event(session_id, project_id, event) or Tracked::publish_custom_event(project_id, event). Both only queue the event, which goes to the project of the handle, so the project ID argument only has to be non-empty.
Errors
- A tool that returns
Err(ErrorData)gives the client a JSON-RPC error, and the event gets the name of the error code aserror.type. Event errors have the platformrustand no stack frames. - A cancelled tool call gives the error type
cancelled. A panic in a tool is recorded, then goes on unchanged. rmcpturns a returned error into the text of an error result. Callingcapture_error(&error)before the tool returns it adds the error type and thesource()chain texts to the event. It works in the tool’s own future only, and the last call counts.
Runtimes, delivery, and shutdown
The SDK needs a tokio runtime with the time driver.#[tokio::main] and Runtime::new turn it on, and a runtime from tokio::runtime::Builder needs enable_time or enable_all.
The identify, event_tags, event_properties, and resolve_session_id hooks run as Send tasks on the tokio runtime of the tool call, so they never delay it. AgentCat waits up to 30 seconds per hook, then sends the event without that value. Redaction hooks have no time limit.
Each AgentCat handle delivers its events from a thread of its own, agentcat-delivery, so a tool call never waits for the network. The queue holds up to 10,000 events and drops the oldest when full. Up to 5 events are in progress at a time, and AgentCat tries a failed send 3 more times (after 1, 2, and 4 seconds, with a timeout of 10 seconds each).
AgentCat::flush(timeout)waits up totimeoutuntil the handle delivers every event.AgentCat::shutdown(timeout)does the same, then stops the handle for good. Both returntruewhen the events went out in time.flush_blockingandshutdown_blockingblock the calling thread and need no tokio runtime.- The drain of the last drop does not run after
std::process::exit, or after a panic withpanic = "abort". Ashutdowncall before them sends the queued events. - On a current-thread runtime, the drop of the last handle blocks the only thread, so no hook can run, and the SDK drops an event that still needs a hook after 5 seconds. Calling
shutdownbefore the runtime ends avoids this. The server thatagentcat_sdk::trackreturns holds the only clone of its handle, so you can clone the handle withTracked::agentcatbefore you serve and callshutdownon that clone. - A hook that holds a clone of its own handle makes a reference cycle, so the handle never drops. Hooks that capture only what they need avoid this, and a tool reaches its handle through
CallContext::agentcat.
Logging and debug mode
The SDK has no debug option. By default it writes its log to~/agentcat.log, never to stdout or stderr, because stdout carries JSON-RPC on a stdio server. With no home directory, the SDK writes no log. Each line holds the time, the SDK, Rust, and rmcp versions, and the message:
log_destination(LogDestination::Tracing) sends the lines to tracing instead, with the target agentcat. They need a global subscriber, such as one from tracing::subscriber::set_global_default, because the delivery thread emits them. The first handle built in the process sets the log destination for the whole process.
On a stdio server, the fmt subscriber of tracing-subscriber writes to stdout unless you give it another writer, for example .with_writer(std::io::stderr).
Testing
The SDK does not detect test runs, so AgentCat stays on in tests unlessAGENTCAT_ENABLED is false. The test-util feature adds testing::Fake, which builds handles whose events stay in the process, recorded after hooks and redaction:
Fake::events returns the recorded events. Fake also has assert_tracked_count, assert_nothing_tracked, and assert_custom_event_published. The first call of a task starts with the [session_id issued block, which matters for tests that read the first content block.
Differences and known limits
- Only
tools/callrequests give events, one each, plus the custom events that your code publishes. Tool listings, resources, and prompts give none. Events go to AgentCat only, as the SDK has no exporters. - On streamable HTTP, a tool call event carries every request header,
authorizationandcookieincluded, underparameters.extra.requestInfo.headers, and the request URL underparameters.extra.requestInfo.url. A stdio server sends no headers. Aredact_eventhook can remove what you prefer not to send. axum::Router::nest_serviceremoves the path prefix before the request reaches the service, so the event records the URL without it.route_servicekeeps the full path.- The SDK turns on the
preserve_orderfeature ofserde_json, which Cargo applies to the whole build, so everyserde_json::Mapin your program keeps insertion order. - The
localfeature ofrmcpis not supported, as hooks run asSendtasks. - A redaction hook that never completes holds one of the 5 delivery slots for good, and five such hooks stop delivery.
- The registry of tool listings never removes an entry, so tool names that differ per tenant grow it.
- When another crate turns on the
arbitrary_precisionorfloat_roundtripfeature ofserde_json, the text of some numbers in events can change. The SDK logs a warning that names the feature. - Rust 1.88 or later is supported, following
rmcp3. An increase of the minimum is a minor release.