Expand description
§sacp – the Symposium Agent Client Protocol (ACP) SDK
sacp is a Rust SDK for building agents and editors using the Agent-Client Protocol (ACP). It makes it easy to build ACP editors and clients – or, indeed, any JSON-RPC-based application.
§Quick Start
Building an ACP agent is straightforward with sacp’s type-safe API:
use sacp::role::UntypedRole;
use sacp::{MessageCx, UntypedMessage};
use sacp::schema::{InitializeRequest, InitializeResponse, AgentCapabilities};
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
// Start by creating an agent connection
UntypedRole::builder()
.name("my-agent") // Give it a name for logging purposes
.on_receive_request(async move |initialize: InitializeRequest, request_cx, _cx| {
// Create one or more request handlers -- these are attempted in order.
// You can do anything you want in here, but you should eventually
// respond to the request with `request_cx.respond(...)`:
request_cx.respond(InitializeResponse {
protocol_version: initialize.protocol_version,
agent_capabilities: AgentCapabilities::default(),
auth_methods: Default::default(),
agent_info: Default::default(),
meta: Default::default(),
})
})
.on_receive_message(async move |message: MessageCx, cx| {
// You can also handle any kind of message:
message.respond_with_error(sacp::util::internal_error("TODO"), cx)
})
.serve(sacp::ByteStreams::new(
tokio::io::stdout().compat_write(),
tokio::io::stdin().compat(),
))
.await§Common Patterns
§Pattern 1: Defining Reusable Components
When building agents or proxies, define a struct that implements Component. Internally, use the role’s builder() method to set up handlers:
use sacp::Component;
struct MyAgent {
config: AgentConfig,
}
impl Component for MyAgent {
async fn serve(self, client: impl Component) -> Result<(), sacp::Error> {
UntypedRole::builder()
.name("my-agent")
.on_receive_request(async move |req: PromptRequest, cx| {
// Don't block the message loop! Use await_when_* for async work
cx.respond(self.process_prompt(req))
.await_when_result_received(async move |response| {
// This runs after the response is received
log_response(&response);
cx.respond(response)
})
})
.serve(client)
.await
}
}Important: Message handlers run on the event loop. Blocking in a handler will prevent the connection from processing new messages.
Use JrConnectionCx::spawn to offload expensive work, or use the await_when_* methods to avoid blocking.
§Pattern 2: Custom Message Handlers
For reusable message handling logic, implement JrMessageHandler and use MatchMessage for dispatching:
use sacp::{JrMessageHandler, MessageAndCx, Handled};
use sacp::util::MatchMessage;
struct MyHandler {
state: Arc<Mutex<State>>,
}
impl JrMessageHandler for MyHandler {
async fn handle_message(&mut self, message: MessageAndCx)
-> Result<Handled<MessageAndCx>, sacp::Error>
{
MatchMessage::new(message)
.if_request(async |req: MyRequest, cx| {
// Handle using self.state
cx.respond(MyResponse { /* ... */ })
})
.await
.done()
}
fn describe_chain(&self) -> impl std::fmt::Debug { "MyHandler" }
}§Pattern 3: Connecting as a Client
To connect to a JSON-RPC server and send requests, use with_client. Note the use of async (not async move)
to share access to local variables:
UntypedRole::builder()
.on_receive_notification(async |notif: SessionUpdate, cx| {
// Handle notifications from the server
Ok(())
})
.with_client(sacp::ByteStreams::new(stdout, stdin), async |cx| {
// Send requests using the connection context
let response = cx.send_request(MyRequest { /* ... */ })
.block_task()
.await?;
// Can access local variables here
process_response(response);
Ok(())
})
.await§Using the Request Context
The request context (JrRequestCx) provided to handlers is not just for responding—it offers several capabilities:
- Respond to the request:
cx.respond(response)sends a response back to the caller - Send requests to the other side:
cx.send_request(request)initiates a new request - Send notifications:
cx.send_notification(notification)sends a fire-and-forget message - Spawn background tasks:
cx.spawn(future)runs work concurrently without blocking the message loop
If you need a connection context that’s independent of a particular request (for example, to store in a struct for later use),
use cx.connection_cx(). This gives you a JrConnectionCx that can spawn tasks and send messages but isn’t tied to
responding to a specific request.
§Learning more
You can learn more in the docs for JrConnection or on our
GitHub Pages site.
You may also enjoy looking at some of these examples:
simple_agent.rs- Minimal agent implementationyolo_one_shot_client.rs- Complete client that spawns an agent and sends a promptelizacp- Full working agent with session management (also useful for testing)sacp-conductor- The “conductor” is an ACP agent that composes proxy components with a final agent.
§Related Crates
- sacp-proxy - Framework for building ACP proxies that extend agent behavior
- sacp-tokio - Tokio-specific utilities (process spawning, connection management)
- sacp-conductor - Binary for orchestrating proxy chains
Re-exports§
pub use role::Agent;pub use role::AgentToClient;pub use role::Client;pub use role::ClientToAgent;pub use role::Conductor;pub use role::HasDefaultEndpoint;pub use role::HasEndpoint;pub use role::JrEndpoint;pub use role::JrRole;pub use role::ProxyToConductor;pub use component::Component;pub use component::DynComponent;
Modules§
- component
- Component abstraction for agents and proxies Component abstraction for agents and proxies.
- handler
- JSON-RPC handler types for building custom message handlers Handler types for building custom JSON-RPC message handlers.
- jsonrpcmsg
- JSON-RPC message types.
- mcp
- MCP declarations (minimal)
- mcp_
server - MCP server support for providing MCP tools over ACP MCP server support for providing MCP tools over ACP.
- role
- Proxy support for building ACP proxy components Role types for JSON-RPC connections Role traits and Role types for ACP and related protocols.
- schema
- ACP protocol schema types - all message types, requests, responses, and supporting types ACP protocol schema types and message implementations.
- util
- Utility functions and types
Structs§
- Byte
Streams - A component that communicates over byte streams (stdin/stdout, sockets, pipes, etc.).
- Channel
- A channel endpoint representing one side of a bidirectional message channel.
- Error
- JSON-RPC error object.
- Error
Code - Predefined error codes for common JSON-RPC and ACP-specific errors.
- JrConnection
- A JSON-RPC connection with an active transport.
- JrConnection
Builder - A JSON-RPC connection that can act as either a server, client, or both.
- JrConnection
Cx - Trait for types that can provide transport for JSON-RPC messages.
- JrRequest
Cx - The context to respond to an incoming request.
- JrResponse
- Represents a pending response of type
Rfrom an outgoing request. - Lines
- A component that communicates over line streams.
- McpAcp
Transport - The mcp_acp_transport capability - indicates support for MCP-over-ACP bridging.
- Null
Handler - Null handler that accepts no messages.
- Untyped
Message - An incoming JSON message without any typing. Can be a request or a notification.
Enums§
- Agent
Notification - All possible notifications that an agent can send to a client.
- Agent
Request - All possible requests that an agent can send to a client.
- Agent
Response - All possible responses that an agent can send to a client.
- Client
Notification - All possible notifications that a client can send to an agent.
- Client
Request - All possible requests that a client can send to an agent.
- Client
Response - All possible responses that a client can send to an agent.
- Handled
- Return type from JrHandler; indicates whether the request was handled or not.
- Message
Cx - An enum capturing an in-flight request or notification. In the case of a request, also includes the context used to respond to the request.
Traits§
- Into
Handled - Trait for converting handler return values into
Handled. - JrMessage
- Common bounds for any JSON-RPC message.
- JrMessage
Handler - Handlers are invoked when new messages arrive at the
JrConnection. They have a chance to inspect the method and parameters and decide whether to “claim” the request (i.e., handle it). If they do not claim it, the request will be passed to the next handler. - JrMessage
Handler Send - A version of
JrMessageHandlerwhere thehandle_messagecode is required to beSend; any type implementing this trait must also implementJrMessageHandler. - JrNotification
- A struct that represents a notification (JSON-RPC message that does not expect a response).
- JrRequest
- A struct that represents a request (JSON-RPC message expecting a response).
- JrResponse
Payload - Defines the “payload” of a successful response to a JSON-RPC request.
- Meta
Capability - Trait for capabilities stored in the
_meta.symposiumobject. - Meta
Capability Ext - Extension trait for checking and modifying capabilities in
InitializeRequest.
Type Aliases§
- BoxFuture
- An owned dynamically typed
Futurefor use in cases where you can’t statically type your result or need to add some indirection.
Derive Macros§
- JrNotification
- Derive macro for implementing
JrNotificationandJrMessagetraits. - JrRequest
- Derive macro for implementing
JrRequestandJrMessagetraits. - JrResponse
Payload - Derive macro for implementing
JrResponsePayloadtrait.