ACP (Agent Client Protocol) is the stdio-based protocol used by IDEs such as Zed to drive
full agent sessions in qcr. The IDE spawns qcr --acp as a child process and
communicates over stdin/stdout using the official
agent-client-protocol SDK (v0.9.3).
| ACP | MCP | |
|---|---|---|
| Purpose | Drive full agent sessions (prompt, stream, cancel) | Expose tools and resources to a host |
| Direction | IDE → qcr | qcr → external MCP server |
| Transport | ndjson JSON-RPC 2.0 over stdio | JSON-RPC 2.0 over stdio or HTTP/SSE |
| Sessions | Stateful — per-session history, mode, model | Stateless calls |
| Spawn | IDE spawns qcr --acp |
qcr spawns or connects to MCP server |
qcr --acpThe process reads from stdin and writes to stdout using ndjson (newline-delimited JSON).
All other flags (--config) are honoured — they supply defaults that the IDE can override
per session.
Every message is a single JSON object followed by a newline (\n). The protocol follows
JSON-RPC 2.0 as implemented by the agent-client-protocol SDK.
Requests from the IDE:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-07-09","clientInfo":{"name":"zed","version":"1.0.0"},"capabilities":{}}}Responses from qcr:
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"qcr","version":"0.1.16"},"agentCapabilities":{}}}Session-update notifications (no id, sent by the agent during prompt execution):
{"jsonrpc":"2.0","method":"notifications/session","params":{"sessionId":"acp-...","update":{"agentMessageChunk":{"content":{"type":"text","text":"Hello"}}}}}IDE qcr --acp
| |
|-- initialize -----------> |
|<- result (agentInfo,caps) - |
| |
|-- notifications/initialized -> |
| |
|-- session/new -----------> |
|<- result (sessionId,modes) - |
| |
|-- session/prompt --------> |
|<- notification (agentMessageChunk) * N
|<- notification (toolCall) * M
|<- notification (toolCallUpdate) * M
|<- notification (plan) * K
|<- result (stopReason) -------- |
| |
|-- session/cancel --------> | (optional, mid-prompt)
| |
|-- [close stdin] --------> | (graceful shutdown)
Must be the first message sent. Returns protocol version, agent info, and capabilities.
Request params:
| Field | Type | Description |
|---|---|---|
protocolVersion |
string | Protocol version (e.g. "2025-07-09") |
clientInfo |
object | { "name": "...", "version": "..." } |
capabilities |
object | Client capabilities (can be empty) |
Response result:
| Field | Type | Description |
|---|---|---|
protocolVersion |
integer | 1 (ProtocolVersion::V1) |
agentInfo.name |
string | "qcr" |
agentInfo.version |
string | Semver string from Cargo.toml |
agentCapabilities |
object | Agent capability flags |
Create a new session. The IDE supplies the working directory.
Request params:
| Field | Type | Required | Description |
|---|---|---|---|
cwd |
string | Yes | Working directory (absolute path) |
mcpServers |
array | Yes | MCP servers to connect (can be []) |
Response result:
| Field | Type | Description |
|---|---|---|
sessionId |
string | Opaque session identifier (acp-{hex}-{hex}) |
modes |
object | Available modes and current mode |
Modes object:
| Field | Type | Description |
|---|---|---|
currentModeId |
string | Initially "default" |
availableModes |
array | [{id, name}, ...] — Default, Plan, Auto-edit |
Send a user prompt to a session and stream the agent response as session notifications. The response is sent after all notifications have been flushed.
Request params:
| Field | Type | Required | Description |
|---|---|---|---|
sessionId |
string | Yes | Target session |
prompt |
array | Yes | Array of ContentBlock objects |
Each content block:
{"type": "text", "text": "your prompt here"}Response result:
| Field | Type | Description |
|---|---|---|
stopReason |
string | "endTurn" or "cancelled" |
Cancel the currently running agent turn for a session.
Request params:
| Field | Type | Required | Description |
|---|---|---|---|
sessionId |
string | Yes | Target session |
No response — this is a notification.
While a session/prompt is in flight, qcr emits a stream of notifications via the
SDK's session_notification method. Each notification carries the session ID and an
update variant.
Echo of the user's prompt.
A streamed chunk of the agent's reply text.
A chunk of the agent's chain-of-thought (thinking) text. Not all models emit this.
A tool has started executing.
| Field | Type | Description |
|---|---|---|
toolCallId |
string | Opaque call identifier |
title |
string | Human-readable tool invocation title |
kind |
string | Tool category (read, edit, execute, etc.) |
status |
string | "pending" |
content |
array | Empty at start |
locations |
array | File path + optional line |
rawInput |
object | Full JSON arguments |
A tool has completed or failed.
| Field | Type | Description |
|---|---|---|
toolCallId |
string | Same ID as the corresponding toolCall |
status |
string | "completed" or "failed" |
content |
array | Output blocks (text or diff) |
rawOutput |
object or null | Raw output |
The agent's todo/plan list has been updated.
| Field | Type | Description |
|---|---|---|
entries |
array | Ordered list of plan entries |
Each plan entry: { "content": "...", "status": "pending|in_progress|completed", "priority": "low|medium|high" }
| kind | Tools |
|---|---|
read |
ReadFile, Glob, LS |
edit |
EditFile, WriteFile |
delete |
DeleteFile |
move |
MoveFile |
search |
Grep, WebSearch |
execute |
Shell |
think |
Think |
fetch |
WebFetch |
other |
all other tools |
Add to ~/.config/zed/settings.json:
{
"agent_servers": {
"qcr": {
"type": "custom",
"command": "/Users/you/.cargo/bin/qcr",
"args": ["--acp"]
}
}
}The ACP server is implemented in crates/qcr-cli/src/acp/:
| File | Purpose |
|---|---|
mod.rs |
run_acp_server() entry point — creates AgentSideConnection over stdio |
agent.rs |
Qwen Code RustAgent implementing the SDK Agent trait |
bootstrap.rs |
build_registry, build_loop_config, build_system_prompt_for_acp — mirrors app.rs setup |
convert.rs |
Internal SessionUpdate → SDK SessionUpdate conversion |
session.rs |
Per-session state and registry |
transport.rs |
ndjson frame reader/writer |
The SDK handles all JSON-RPC framing, method routing, and serialization. Qwen Code RustAgent
implements the Agent trait methods (initialize, new_session, prompt, cancel)
which delegate to qcr-core's agent loop, tool registry, and session persistence.
After every prompt completes, qcr saves the conversation history to
~/.qcr/sessions/<sessionId>.json.
AskUserQuestionreturns a canned fallback in ACP mode; full IDE round-trip is a future feature.- Cron jobs (
CronCreate,CronList,CronDelete) are session-scoped and are lost when the session ends.
- Fixed empty responses in Zed: the
PromptResponsewas returned before all session notifications had been flushed. Now the prompt handler awaits the bridge and forwarder tasks to drain before returning. - LLM errors forwarded to IDE:
AgentEvent::Erroris now emitted asagentMessageChunkwith[error]prefix instead of being silently swallowed. - Temperature fix: o-series (o1/o3/o4-mini) and codex (gpt-5.2-codex) models no longer receive unsupported
temperature/top_pparameters.
ACP sessions use the cwd provided by session/new as the project root. qcr then:
- Loads
QCR.md,AGENTS.md, andCLAUDE.mdfrom the project root when present. - Reads steering files from
<cwd>/.qcr/steering/. - Discovers plugins from
~/.qcr/plugins(global) and<cwd>/.qcr/plugins(project).
Close the process's stdin to trigger a clean shutdown. qcr will finish any in-flight notifications, flush stdout, and exit with code 0.