Skip to content
36 changes: 36 additions & 0 deletions src/elizacp/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
//! # elizacp
//!
//! A classic Eliza chatbot implemented as an ACP (Agent-Client Protocol) agent.
//!
//! ## Overview
//!
//! Elizacp provides a simple, predictable agent implementation that's useful for:
//!
//! - **Testing ACP clients** - Lightweight agent with deterministic pattern-based responses
//! - **Protocol development** - Verify ACP implementations without heavy AI infrastructure
//! - **Learning ACP** - Clean example of implementing the Agent-Client Protocol
//!
//! ## Features
//!
//! - **Classic Eliza patterns** - Pattern matching and reflection-based responses
//! - **Full ACP support** - Session management, initialization, and prompt handling
//! - **Per-session state** - Each session maintains its own Eliza instance
//! - **Extensible patterns** - Easy to add new response patterns
//!
//! ## Usage
//!
//! ```bash
//! # Build and run
//! cargo run -p elizacp
//!
//! # With debug logging
//! cargo run -p elizacp -- --debug
//! ```
//!
//! The agent communicates over stdin/stdout using JSON-RPC, following the ACP specification.
//!
//! ## Implementation
//!
//! The agent maintains a `HashMap<SessionId, Eliza>` to track per-session state.
//! Each session gets its own Eliza instance with independent conversation state.

mod eliza;

use anyhow::Result;
Expand Down
2 changes: 1 addition & 1 deletion src/sacp-conductor/src/conductor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ impl Conductor {
let (component_read, component_write) = tokio::io::split(component_stream);
let (conductor_read, conductor_write) = tokio::io::split(conductor_stream);

let cx = serve_args.connection.json_rpc_cx();
let cx = serve_args.connection.connection_cx();

// Create the component streams based on the provider type
let cleanup = next_provider.create(
Expand Down
79 changes: 79 additions & 0 deletions src/sacp-conductor/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,86 @@
//! # sacp-conductor
//!
//! Binary for orchestrating ACP proxy chains.
//!
//! ## What is the conductor?
//!
//! The conductor is a tool that manages proxy chains - it spawns proxy components and the base agent,
//! then routes messages between them. From the editor's perspective, the conductor appears as a single ACP agent.
//!
//! ```text
//! Editor ← stdio → Conductor → Proxy 1 → Proxy 2 → Agent
//! ```
//!
//! ## Usage
//!
//! ### Agent Mode
//!
//! Orchestrate a chain of proxies in front of an agent:
//!
//! ```bash
//! # Chain format: proxy1 proxy2 ... agent
//! sacp-conductor agent "python proxy1.py" "python proxy2.py" "python base-agent.py"
//! ```
//!
//! The conductor:
//! 1. Spawns each component as a subprocess
//! 2. Connects them in a chain
//! 3. Presents as a single agent on stdin/stdout
//! 4. Manages the lifecycle of all processes
//!
//! ### MCP Bridge Mode
//!
//! Connect stdio to a TCP-based MCP server:
//!
//! ```bash
//! # Bridge stdio to MCP server on localhost:8080
//! sacp-conductor mcp 8080
//! ```
//!
//! This allows stdio-based tools to communicate with TCP MCP servers.
//!
//! ## How It Works
//!
//! **Component Communication:**
//! - Editor talks to conductor via stdio
//! - Conductor uses `_proxy/successor/*` protocol extensions to route messages
//! - Each proxy can intercept, transform, or forward messages
//! - Final agent receives standard ACP messages
//!
//! **Process Management:**
//! - All components are spawned as child processes
//! - When conductor exits, all children are terminated
//! - Errors in any component bring down the entire chain
//!
//! ## Example Use Case
//!
//! Add Sparkle embodiment + custom tools to any agent:
//!
//! ```bash
//! sacp-conductor agent \
//! "sparkle-acp-proxy" \
//! "my-custom-tools-proxy" \
//! "claude-agent"
//! ```
//!
//! This creates a stack where:
//! 1. Sparkle proxy injects MCP servers and prepends embodiment
//! 2. Custom tools proxy adds domain-specific functionality
//! 3. Base agent handles the actual AI responses
//!
//! ## Related Crates
//!
//! - **[sacp-proxy](https://crates.io/crates/sacp-proxy)** - Framework for building proxy components
//! - **[sacp](https://crates.io/crates/sacp)** - Core ACP SDK
//! - **[sacp-tokio](https://crates.io/crates/sacp-tokio)** - Tokio utilities for process spawning

use crate::{component::CommandComponentProvider, conductor::Conductor};

/// Component abstraction for spawning and managing proxies/agents
pub mod component;
/// Core conductor logic for orchestrating proxy chains
pub mod conductor;
/// MCP bridge functionality for TCP-based MCP servers
mod mcp_bridge;

use clap::{Parser, Subcommand};
Expand Down
108 changes: 108 additions & 0 deletions src/sacp-proxy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,111 @@
#![deny(missing_docs)]

//! # sacp-proxy
//!
//! Framework for building ACP proxy components that extend agent functionality.
//!
//! ## What are proxies?
//!
//! Proxies are modular components that sit between an editor and an agent, intercepting and transforming messages.
//! They enable **composable agent architectures** where functionality can be added without modifying the base agent.
//!
//! ```text
//! Editor → Proxy 1 → Proxy 2 → Agent
//! ```
//!
//! Use cases:
//! - Add MCP tools/resources/prompts to any agent
//! - Inject context or modify prompts before they reach the agent
//! - Filter or transform agent responses
//! - Add logging, metrics, or policy enforcement
//!
//! ## Quick Start
//!
//! The simplest proxy just forwards messages unchanged:
//!
//! ```rust,no_run
//! use sacp::JrConnection;
//! use sacp_proxy::{AcpProxyExt, McpServiceRegistry};
//! use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
//!
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! JrConnection::new(
//! tokio::io::stdout().compat_write(),
//! tokio::io::stdin().compat()
//! )
//! .name("my-proxy")
//! .provide_mcp(McpServiceRegistry::default()) // Provide MCP services
//! .proxy() // Enable proxy mode
//! .serve()
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! To add MCP tools to the proxy, provide an MCP server:
//!
//! ```rust,no_run
//! use sacp::JrConnection;
//! use sacp_proxy::{AcpProxyExt, McpServiceRegistry};
//! use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
//!
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! JrConnection::new(
//! tokio::io::stdout().compat_write(),
//! tokio::io::stdin().compat()
//! )
//! .name("my-proxy")
//! // Add MCP servers to provide tools/resources/prompts
//! .provide_mcp(
//! McpServiceRegistry::default()
//! // .with_rmcp_server("my-server", || MyMcpServer::new())?
//! )
//! .proxy()
//! .serve()
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! See the `with_mcp_server.rs` example for a complete implementation.
//!
//! ## Key Concepts
//!
//! **Predecessor vs Successor:**
//! - **Predecessor** - The component closer to the editor (could be editor or another proxy)
//! - **Successor** - The component farther from the editor (could be agent or another proxy)
//!
//! Messages flow: `Predecessor → This Proxy → Successor`
//!
//! **Extension Traits:**
//! - [`AcpProxyExt`] - Adds proxy-specific methods to [`JrConnection`](sacp::JrConnection)
//! - [`JrCxExt`] - Adds forwarding methods (`send_to_predecessor`, `send_to_successor`)
//!
//! ## Examples
//!
//! See the [examples directory](https://github.com/symposium-org/symposium-acp/tree/main/src/sacp-proxy/examples):
//!
//! - **[`minimal.rs`](https://github.com/symposium-org/symposium-acp/blob/main/src/sacp-proxy/examples/minimal.rs)** - Simplest possible proxy that forwards everything unchanged
//! - **[`with_mcp_server.rs`](https://github.com/symposium-org/symposium-acp/blob/main/src/sacp-proxy/examples/with_mcp_server.rs)** - Proxy that adds MCP tools to any agent
//!
//! ## How Proxies Work
//!
//! When you call `.proxy()`, the framework:
//! 1. Handles capability negotiation with predecessor and successor
//! 2. Sets up message routing between components
//! 3. Wraps your handlers to intercept specific message types
//! 4. Forwards everything else automatically
//!
//! You only need to handle the messages you want to intercept or transform.
//!
//! ## Related Crates
//!
//! - **[sacp](https://crates.io/crates/sacp)** - Core ACP SDK
//! - **[sacp-conductor](https://crates.io/crates/sacp-conductor)** - Binary for orchestrating proxy chains
//! - **[sacp-tokio](https://crates.io/crates/sacp-tokio)** - Tokio utilities for spawning agents

/// Proxying MCP messages over ACP.
mod mcp_over_acp;
pub use mcp_over_acp::*;
Expand Down
7 changes: 7 additions & 0 deletions src/sacp-proxy/src/mcp_over_acp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ use sacp::{
};
use serde::{Deserialize, Serialize};

/// JSON-RPC method name for MCP connect requests
pub const METHOD_MCP_CONNECT_REQUEST: &str = "_mcp/connect";

/// Creates a new MCP connection. This is equivalent to "running the command".
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpConnectRequest {
/// The ACP URL to connect to (e.g., "acp:uuid")
pub acp_url: String,
}

Expand Down Expand Up @@ -41,8 +43,10 @@ impl JrRequest for McpConnectRequest {
type Response = McpConnectResponse;
}

/// Response to an MCP connect request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpConnectResponse {
/// Unique identifier for the established MCP connection
pub connection_id: String,
}

Expand All @@ -56,6 +60,7 @@ impl JrResponsePayload for McpConnectResponse {
}
}

/// JSON-RPC method name for MCP disconnect notifications
pub const METHOD_MCP_DISCONNECT_NOTIFICATION: &str = "_mcp/disconnect";

/// Disconnects the MCP connection.
Expand Down Expand Up @@ -92,6 +97,7 @@ impl JrMessage for McpDisconnectNotification {

impl JrNotification for McpDisconnectNotification {}

/// JSON-RPC method name for MCP requests over ACP
pub const METHOD_MCP_REQUEST: &str = "_mcp/request";

/// An MCP request sent via ACP. This could be an MCP-server-to-MCP-client request
Expand Down Expand Up @@ -155,6 +161,7 @@ impl<R: JrRequest> JrRequest for McpOverAcpRequest<R> {
type Response = R::Response;
}

/// JSON-RPC method name for MCP notifications over ACP
pub const METHOD_MCP_NOTIFICATION: &str = "_mcp/notification";

/// An MCP notification sent via ACP, either from the MCP client (the ACP agent)
Expand Down
1 change: 1 addition & 0 deletions src/sacp-proxy/src/mcp_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct McpServiceRegistryData {
}

impl McpServiceRegistry {
/// Creates a new empty MCP service registry
pub fn new() -> Self {
Self::default()
}
Expand Down
3 changes: 3 additions & 0 deletions src/sacp-proxy/src/to_from_successor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ impl<Req: JrNotification> JrNotification for SuccessorNotification<Req> {}
// Proxy methods
// ============================================================

/// Extension trait for JrConnection that adds proxy-specific functionality
pub trait AcpProxyExt<OB: AsyncWrite, IB: AsyncRead, H: JrHandler> {
/// Adds a handler for requests received from the successor component.
///
Expand Down Expand Up @@ -252,6 +253,7 @@ where
R: JrRequest,
F: AsyncFnMut(R, JrRequestCx<R::Response>) -> Result<(), sacp::Error>,
{
/// Creates a new handler for requests from the successor
pub fn new(handler: F) -> Self {
Self {
handler,
Expand Down Expand Up @@ -315,6 +317,7 @@ where
N: JrNotification,
F: AsyncFnMut(N, JrConnectionCx) -> Result<(), sacp::Error>,
{
/// Creates a new handler for notifications from the successor
pub fn new(handler: F) -> Self {
Self {
handler,
Expand Down
3 changes: 3 additions & 0 deletions src/sacp/src/acp.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/// ACP messages sent from agent to client
pub mod agent_to_client;
/// ACP messages sent from client to agent
pub mod client_to_agent;
/// Enum type implementations for ACP message types
mod enum_impls;

pub use agent_to_client::*;
Expand Down
Loading