ACP Server
Source attribution: The protocol description in this page is drawn from the official ACP documentation at agentclientprotocol.com/get-started/introduction and the machine-readable spec at agentclientprotocol.com/llms.txt.
Feature Definition
Section titled “Feature Definition”AI coding agents today integrate with editors through ad-hoc custom protocols. Cursor has its own agent API. VS Code Copilot has another. Claude Desktop has yet another. Each agent that wants to reach users in multiple editors must implement multiple editor-specific integrations. Each editor that wants to support multiple agents must implement multiple agent-specific protocols. The combinatorial explosion of M×N integrations is the same problem the Language Server Protocol (LSP) solved for language tooling in 2016.
The Agent Client Protocol (ACP) is the proposed standard that breaks this M×N problem the same way LSP did. ACP standardizes how code editors (clients) communicate with coding agents (servers), so:
- Any ACP-compatible agent works in any ACP-compatible editor without custom integration work
- Editors can let users choose their agent the same way they choose their language server
- Agents can focus on capability rather than editor-specific API implementation
ACP is explicitly modeled on LSP’s success: “agents compatible with ACP [can] work across multiple editors, similar to how LSP standardized language server integration.”
The hard parts of defining such a protocol are:
- Transport flexibility: Agents can run locally (as editor subprocesses) or remotely (in the cloud). A single protocol must support both
stdin/stdoutpipes andHTTP/WebSockettransports. - Capability negotiation: Not all agents support all features (file writes, terminal execution, browser automation). The protocol must let agents advertise and clients query what’s available.
- Data type richness: Unlike a plain chat protocol, a coding agent needs structured types for diffs, file paths, terminal streams, and LSP diagnostics. Generic JSON message envelopes aren’t enough.
- Session lifecycle: Sessions can be created, resumed, forked, and deleted. Long-running agentic tasks need a way to cancel and report partial progress.
ACP Architecture Overview
Section titled “ACP Architecture Overview”ACP defines two roles:
- Server (the agent): Executes tasks, manages sessions, writes files, runs tools. In ACP terminology, “server” means the agent process.
- Client (the editor/IDE): Displays results, captures user input, manages the UI. In ACP terminology, “client” means the editor.
This naming is the opposite of web convention. The editor is the client that connects to the agent server.
Transport Layer
Section titled “Transport Layer”ACP supports two transport modes:
Local agents run as subprocesses spawned by the editor. Communication uses JSON-RPC 2.0 over standard input/output — the same channel used by LSP. The editor spawns the process, writes requests to stdin, and reads responses from stdout. No network stack, no port management, no authentication concerns.
Remote agents run on cloud infrastructure or a separate local process not managed by the editor. Communication uses HTTP (for request/response) or WebSocket (for streaming and bidirectional events). The documentation notes that “full support for remote agents is a work in progress,” with active collaboration between the ACP working group and cloud deployment platforms.
The choice of JSON-RPC as the base format is deliberate. It’s the same foundation as LSP, which means editors that already implement an LSP client have most of the plumbing needed for ACP. The message framing, error codes, and request/response correlation are identical.
MCP Reuse
Section titled “MCP Reuse”ACP deliberately reuses data types from MCP (Model Context Protocol) where applicable. MCP already defines JSON schemas for common AI data types — tool call inputs/outputs, message content blocks, etc. Rather than duplicate these, ACP imports them. ACP adds custom types for features that MCP doesn’t cover:
- Diff blocks: Structured representation of file changes (path + unified diff content)
- File system access controls: Scoping which paths an agent is permitted to read/write
- Terminal streams: Streaming PTY output with ANSI escape sequences preserved
- Slash commands: Agent-advertised shortcuts that appear in editor command palettes
The default text format throughout ACP is Markdown, which allows rich formatting (code blocks, headers, links) without requiring HTML rendering in editors.
Protocol Capabilities
Section titled “Protocol Capabilities”The full ACP specification covers:
Session Management
Section titled “Session Management”ACP sessions are the unit of conversation state. An editor can:
- Create a session, specifying the working directory scope and initial configuration
- Resume a session by ID — the agent restores its conversation history and file context
- Fork a session — creates a new session branching from a given point in an existing one (useful for exploring alternative approaches without losing the main thread)
- Delete a session — signals the agent to release all associated resources
Session IDs are opaque strings managed by the agent server. Editors persist them for resume across editor restarts.
Prompt Turns
Section titled “Prompt Turns”A “prompt turn” is one user→agent round trip. The client sends:
{ "type": "prompt", "session_id": "sess_abc123", "content": [ { "type": "text", "text": "Add error handling to the login function" } ]}The agent responds with a stream of events (for streaming responses) or a single response object (for non-streaming). Events can include:
- Text deltas: Incremental text output as the model generates
- Tool calls: The agent invoking file read/write/terminal tools
- Tool results: The results of those tool invocations
- Plan steps: Structured agentic planning output
- Diff blocks: File changes ready for user review
Tool Reporting
Section titled “Tool Reporting”ACP provides standard types for reporting tool usage so editors can display agent activity:
- File reads: Which files were read and when
- File writes: Which files were modified with what changes (diff blocks)
- Terminal commands: Which shell commands were run with their output
- Web searches: URLs accessed and content retrieved
This transparency is a core design goal. Users should always know what the agent did to their files, not just what it said.
Slash Command Advertisement
Section titled “Slash Command Advertisement”Agents can advertise custom slash commands that appear in the editor’s command palette. An agent might advertise:
{ "slash_commands": [ { "command": "/review", "description": "Review recent changes for bugs" }, { "command": "/explain", "description": "Explain the selected code" } ]}The editor adds these to its command UI. When the user triggers one, the editor sends it as a prompt turn with the command text. This lets agents expose capabilities that feel native to the editor without hardcoding them in the editor.
Request Cancellation
Section titled “Request Cancellation”Long-running tasks (large refactors, multi-file rewrites) support cancellation. The client sends a cancel message referencing the in-flight request ID. The agent is expected to stop as soon as possible and return a partial result or an explicit cancellation acknowledgment.
ACP Server Implementation: OpenOxide Blueprint
Section titled “ACP Server Implementation: OpenOxide Blueprint”OpenOxide will expose an ACP server as an optional mode alongside its default interactive TUI. When launched with openoxide --acp, it starts an ACP server instead of the TUI. Local editors connect via stdin/stdout; remote connections use an HTTP server.
Process Architecture
Section titled “Process Architecture”Editor (VS Code, Zed, Neovim ACP plugin) │ │ JSON-RPC over stdin/stdout (local) │ OR HTTP/WebSocket (remote) ▼openoxide --acp (ACP server layer) │ │ internal Rust channels ▼openoxide-core (agent loop, LLM calls, tool execution)The ACP server layer is a thin shim: it deserializes incoming JSON-RPC messages, dispatches them to the agent core via async channels, and serializes the streamed responses back to the transport.
Core Types
Section titled “Core Types”/// ACP Session — maps to one conversation thread in the agentpub struct Session { pub id: SessionId, pub working_dir: PathBuf, pub history: Vec<Turn>, pub config: SessionConfig,}
/// A single prompt/response round-trippub struct Turn { pub role: Role, // User or Agent pub content: Vec<ContentBlock>, pub tool_uses: Vec<ToolUse>, pub tool_results: Vec<ToolResult>,}
/// ACP content block — text, diff, terminal, or imagepub enum ContentBlock { Text(String), Diff(DiffBlock), Terminal(TerminalBlock), SlashCommand(String),}
pub struct DiffBlock { pub path: PathBuf, pub unified_diff: String, // standard unified diff format}Local Transport: JSON-RPC over Stdio
Section titled “Local Transport: JSON-RPC over Stdio”pub struct StdioTransport { reader: BufReader<Stdin>, writer: BufWriter<Stdout>,}
impl StdioTransport { pub async fn run(mut self, handler: Arc<dyn AcpHandler>) -> anyhow::Result<()> { let mut length_buf = String::new(); loop { // LSP-style Content-Length framing length_buf.clear(); self.reader.read_line(&mut length_buf).await?; let content_length: usize = length_buf .trim_start_matches("Content-Length: ") .trim() .parse()?; // skip blank line self.reader.read_line(&mut String::new()).await?;
let mut body = vec![0u8; content_length]; self.reader.read_exact(&mut body).await?;
let request: jsonrpc::Request = serde_json::from_slice(&body)?; let response = handler.handle(request).await; let json = serde_json::to_vec(&response)?;
write!(self.writer, "Content-Length: {}\r\n\r\n", json.len())?; self.writer.write_all(&json).await?; self.writer.flush().await?; } }}Content-Length framing is identical to LSP — editors that speak LSP already know how to frame these messages.
Remote Transport: HTTP + Server-Sent Events
Section titled “Remote Transport: HTTP + Server-Sent Events”For streaming responses over HTTP, ACP uses Server-Sent Events (SSE). A single HTTP POST initiates the prompt turn; the response body is an SSE stream of events:
// Axum route handlerasync fn handle_prompt( State(sessions): State<Arc<SessionStore>>, Json(req): Json<PromptRequest>,) -> impl IntoResponse { let session = sessions.get(&req.session_id)?; let event_stream = session.run_prompt(req.content).await;
Sse::new(event_stream.map(|event| { Ok::<_, Infallible>(Event::default() .event(event.event_type()) .data(serde_json::to_string(&event).unwrap())) }))}Session Store
Section titled “Session Store”Sessions are persisted to .openoxide/sessions/ as CBOR-encoded blobs, allowing resume across process restarts:
pub struct SessionStore { sessions: DashMap<SessionId, Arc<Mutex<Session>>>, storage_dir: PathBuf,}
impl SessionStore { pub async fn create(&self, config: SessionConfig) -> anyhow::Result<SessionId> { let id = SessionId::new(); let session = Session::new(id.clone(), config); self.persist(&session).await?; self.sessions.insert(id.clone(), Arc::new(Mutex::new(session))); Ok(id) }
pub async fn resume(&self, id: &SessionId) -> anyhow::Result<Arc<Mutex<Session>>> { if let Some(s) = self.sessions.get(id) { return Ok(s.clone()); } // Load from disk let session = self.load(id).await?; let arc = Arc::new(Mutex::new(session)); self.sessions.insert(id.clone(), arc.clone()); Ok(arc) }}Capability Advertisement
Section titled “Capability Advertisement”On connection, the server responds to the initialize handshake with its capability list:
pub struct ServerCapabilities { pub file_read: bool, pub file_write: bool, pub terminal_exec: bool, pub web_search: bool, pub slash_commands: Vec<SlashCommandDef>, pub streaming: bool, pub session_fork: bool,}Editors hide or grey out UI elements for capabilities the agent doesn’t support.
Pitfalls and Hard Lessons
Section titled “Pitfalls and Hard Lessons”ACP Is Not Yet Stable
Section titled “ACP Is Not Yet Stable”As of February 2026, ACP is a working draft. The specification is still evolving — “full support for remote agents is a work in progress” per the official docs. Implementing ACP now means building against a moving target. Pin to a specific spec version and document which version you implemented. Plan for breaking changes.
Session Fork Semantics Are Ambiguous
Section titled “Session Fork Semantics Are Ambiguous”The fork operation creates a new session “branching from a given point.” But what exactly does forking copy? The conversation history (messages), the file state (which files were modified), or both? If a fork copies message history but not file state, the forked session’s conversation references files that may have diverged. The spec doesn’t fully resolve this, so any implementation must make a choice and document it.
Slash Command Conflicts
Section titled “Slash Command Conflicts”If multiple agents are registered with the same editor, they can advertise conflicting slash commands. /explain from agent A and /explain from agent B are different behaviors. The ACP spec defers this to the editor to resolve — typically by prefixing commands with the agent name. OpenOxide commands should be namespaced: /openoxide:review rather than just /review when running alongside other agents.
Streaming Backpressure
Section titled “Streaming Backpressure”SSE doesn’t have a backpressure mechanism — if the editor can’t consume events fast enough, they queue in the server’s memory. For a long agentic task that generates thousands of events (many file reads, writes, terminal lines), this can exhaust memory. The implementation must either limit event queue depth or implement a pause/resume signal over a separate channel.