SCP - Symposium Component Protocol
A Rust library providing foundational building blocks for implementing the Symposium Component Protocol. Currently provides a generic JSON-RPC 2.0 implementation and ACP (Agent Client Protocol) support for both agents and editors.
Architecture Overview
The library is structured in three layers:
┌─────────────────────────────────────┐
│ ACP Protocol Layer │ ← Agent Client Protocol support
│ (acp/agent.rs, acp/editor.rs) │
├─────────────────────────────────────┤
│ JSON-RPC 2.0 Layer │ ← Generic JSON-RPC implementation
│ (jsonrpc.rs, jsonrpc/actors.rs) │
├─────────────────────────────────────┤
│ Async I/O (tokio, futures) │ ← Transport layer
└─────────────────────────────────────┘
Design Principles
- Layer independence: The JSON-RPC layer has no knowledge of ACP. You can use it for any JSON-RPC application.
- Type safety: Request/response pairs are statically typed using traits, catching mismatches at compile time.
- Handler composition: Multiple handlers can be chained together, each claiming specific message types.
- Actor-based concurrency: Message processing is split across three cooperating actors to prevent deadlocks.
JSON-RPC Layer
The JSON-RPC layer provides a generic implementation of the JSON-RPC 2.0 protocol over stdio or any async read/write streams.
Core Types
JsonRpcConnection<H>: Main entry point. Manages bidirectional JSON-RPC communication.JsonRpcHandler: Trait for implementing message handlers.JsonRpcCx: Context for sending requests and notifications.JsonRpcRequestCx<T>: Context for responding to incoming requests.
Actor Architecture
The connection spawns three actors that cooperate via channels:
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Outgoing Actor │ │ Incoming Actor │ │ Reply Actor │
│ │ │ │ │ │
│ Serializes and │────▶│ Deserializes and │────▶│ Correlates │
│ writes messages │ │ routes to │ │ request IDs with │
│ to transport │ │ handlers │ │ response futures │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Why this design?
- The outgoing actor handles all writes, preventing interleaved JSON.
- The incoming actor routes messages to handlers without blocking on I/O.
- The reply actor manages the map of pending requests, keeping correlation logic centralized.
Handler Chain Pattern
Handlers use a chain-of-responsibility pattern. When a message arrives, each handler gets a chance to claim it:
Handlers are added via add_handler() and tried in order until one returns Handled::Yes.
Example: JSON-RPC Echo Server
use ;
use ;
// Define request/response types
// Implement handler
;
// Run server
async
Example: JSON-RPC Client
use ;
async
The connection serves messages in the background while your client function runs, then cleanly shuts down when the client function returns.
ACP Protocol Layer
The ACP layer builds on the JSON-RPC foundation to implement the Agent Client Protocol, which defines bidirectional communication between agents and editors.
Core Types
For implementing agents:
AcpAgent<CB>: Handler for agent-side messages (requests from editors).AcpAgentCallbacks: Trait you implement to handle requests agents receive.
For implementing editors:
AcpEditor<CB>: Handler for editor-side messages (requests from agents).AcpEditorCallbacks: Trait you implement to handle requests editors receive.
For proxies: Implement both AcpAgentCallbacks and AcpEditorCallbacks to sit in the middle of the communication chain.
ACP Protocol Methods
Agent-side methods (editors → agents):
initialize- Protocol negotiation and capability exchangeauthenticate- Authentication flowsession/new- Create a new agent sessionsession/load- Load an existing sessionsession/prompt- Send a user prompt to the agentsession/set_mode- Change session modesession/cancel- Cancel an in-progress request (notification)
Editor-side methods (agents → editors):
session/request_permission- Ask user for permission to execute toolsfs/read_text_file- Read file contentsfs/write_text_file- Write to filesterminal/create- Start a terminal commandterminal/output- Get terminal outputterminal/wait_for_exit- Wait for command completionterminal/kill- Terminate running commandterminal/release- Release terminal resourcessession/update- Stream progress updates (notification)
Example: Minimal ACP Agent
use ;
use ;
use agent_client_protocol as acp;
// Implement the callbacks
;
async
Combining Multiple Handlers
You can chain multiple handlers to extend ACP with custom methods:
;
// Chain handlers: try CustomHandler first, then AcpAgent
new
.add_handler
.add_handler
.serve
.await
This pattern enables the proxy architecture: each proxy can add its own handler to the chain while forwarding unhandled messages downstream.
Using the Library
As a JSON-RPC Server
- Define your request/response types implementing
serde::Serializeandserde::Deserialize - Implement
JsonRpcRequesttrait for your request types - Create a handler struct implementing
JsonRpcHandler - Build a
JsonRpcConnectionwith your handler and call.serve()
As an ACP Agent
- Create a struct to hold your agent state
- Implement
AcpAgentCallbackstrait with your agent logic - Wrap it in
AcpAgent::new(your_agent) - Add to a
JsonRpcConnectionand serve - Use
AcpAgentExttrait methods to make requests to the editor:
use AcpAgentExt; // Import the extension trait
async
As an ACP Editor
- Create a struct to hold your editor state
- Implement
AcpEditorCallbackstrait to handle agent requests - Wrap it in
AcpEditor::new(your_editor) - Add to a
JsonRpcConnectionand serve - Use
AcpEditorExttrait methods to make requests to the agent:
use AcpEditorExt; // Import the extension trait
async
As an ACP Proxy
Proxies implement both callback traits:
- Implement
AcpAgentCallbacksto receive messages from upstream (editor) - Implement
AcpEditorCallbacksto receive messages from downstream (agent) - Use
cx.send_request()to forward and transform messages in both directions - Add custom handlers for proxy-specific extensions
Type Safety Patterns
Request/Response Correlation
The JsonRpcRequest trait ensures responses match requests at compile time:
// This works:
let response: MyResponse = cx.send_request.recv.await?;
// This would be a compile error:
let wrong: OtherResponse = cx.send_request.recv.await?;
Response Context Casting
When handling generic jsonrpcmsg::Response, use .cast() to get typed context:
async
Error Handling
The library uses two error types:
acp::Error: JSON-RPC protocol errors (method not found, invalid params, etc.)acp::Error: ACP protocol errors, automatically converted to JSON-RPC errors
Convert ACP errors using the utility function:
use acp_to_jsonrpc_error;
let acp_err = Error ;
let jsonrpc_err = acp_to_jsonrpc_error;
Implementation Status
Complete
- ✅ JSON-RPC 2.0 server and client implementation
- ✅ Actor-based message routing
- ✅ Handler chain pattern
- ✅ ACP agent-side support (handling requests from editors)
- ✅ ACP editor-side support (handling requests from agents)
- ✅ Type-safe request/response correlation
In Progress
- 🚧 SCP-specific protocol extensions (
_scp/successor/*messages)
Planned
- ⏳ Orchestrator binary for managing proxy chains
- ⏳ Reference proxy implementations
- ⏳ Process lifecycle management
Design Rationale
Why actors instead of async/await everywhere?
The actor pattern separates concerns and prevents deadlocks that can occur when multiple async tasks try to write to the same transport. The outgoing actor ensures messages are serialized atomically, while the incoming actor can dispatch to handlers without blocking on I/O.
Why trait-based handlers instead of closures?
Traits enable:
- Stateful handlers that can maintain connections or track sessions
- Handler composition via the chain pattern
- Clear separation between protocol layers
- Testable, modular components
Why separate the JSON-RPC and ACP layers?
This design allows:
- Reusing the JSON-RPC implementation for non-ACP protocols
- Testing each layer independently
- Adding SCP extensions without modifying ACP support
- Building proxies that layer additional behavior
The JSON-RPC layer is protocol-agnostic and could be extracted as a standalone crate if needed.
Examples Directory
See the examples/ directory for complete working examples:
echo_server.rs- Minimal JSON-RPC serverecho_client.rs- JSON-RPC client making requestsacp_agent.rs- Basic ACP agent implementation
Dependencies
agent-client-protocol- ACP protocol types and definitionsjsonrpcmsg- JSON-RPC message typestokio- Async runtimefutures- Async utilities and traitsserde/serde_json- Serialization
Contributing
When extending the library:
- Keep layers independent: Changes to JSON-RPC shouldn't require ACP changes
- Maintain type safety: Use traits to enforce compile-time guarantees
- Document actor interactions: Changes to the actor system should document message flows
- Add tests: Unit tests for protocol logic, integration tests for end-to-end flows