Skip to main content
The Agent Client Protocol (ACP) is an open protocol that lets editors and IDEs host external coding agents as first-class citizens: the IDE renders the agent’s reasoning, tool calls, plans and permission prompts natively, while the agent runs as a separate process speaking JSON-RPC over stdio. chatcli acp is ChatCLI’s ACP server. Everything you have configured in ChatCLI — providers, models, memory, contexts, skills, plugins, MCP servers — drives your IDE’s AI chat.
What the IDE renders for agent/coder runs is fully structured — not a streamed terminal transcript:
  • Thoughts — the agent’s reasoning arrives as native thought chunks (collapsible in most clients).
  • Tool calls — each action is a first-class tool call with a human title (“Reading: main.go”), a semantic kind (read, edit, execute, search…), live status, and the files it touches. Successful file reads show which file was read — never a dump of its contents into the chat.
  • Plan — the coder’s task list streams as a live plan panel, checked off as the agent progresses.
  • Final answer — clean assistant prose, rendered as markdown by the IDE.
  • Permission dialogs — dangerous commands and policy ask rules pause the run and raise the IDE’s native dialog with the same four choices as the terminal prompt: allow or reject, once or always.

Quick start

The process speaks newline-delimited JSON-RPC on stdin/stdout. All logging goes to the file logger — the protocol channel stays clean.

JetBrains IDEs (IntelliJ IDEA, GoLand, WebStorm, …)

JetBrains AI Assistant hosts custom ACP agents natively (macOS and Linux; no AI subscription required for ACP agents).
1

Open the custom-agent configuration

In the AI Chat tool window, open the agent dropdown and choose Add Custom Agent. The IDE creates and opens ~/.jetbrains/acp.json.
2

Register ChatCLI

~/.jetbrains/acp.json
  • command must be an absolute path (which chatcli to find it).
  • env is optional — with it you pin provider/model for the IDE independently of your terminal sessions; without it, ChatCLI boots with your regular .env/environment resolution.
3

Select ChatCLI and chat

Back in AI Chat, pick ChatCLI from the agent dropdown and send a prompt. Custom agents show a distinct icon.
use_custom_mcp / use_idea_mcp control which MCP servers the IDE offers to agents. ChatCLI brings its own MCP client configuration (~/.chatcli/mcp_servers.json) either way — tools you configured in ChatCLI keep working inside the IDE.

Zed

Add ChatCLI under agent_servers in Zed’s settings.json:
Then open the Agent Panel and select ChatCLI.

Session modes

Every ACP session runs in one of three modes, advertised to the client on session/new and switchable at any time: Switch modes from the IDE’s mode picker, or simply type the command in the prompt:
Mode changes are confirmed to the client via current_mode_update, so the IDE picker always reflects reality.

Slash commands in the IDE

Type / in the prompt box: the IDE autocompletes ChatCLI’s command surface (advertised via available_commands_update). Each command’s input hint shows its real subcommands — derived live from the same completer that powers the terminal, so it never drifts (the ACP protocol has no structured completion past the command name; the hint line is the whole surface, and ChatCLI makes it count). Three groups: Mode switches/chat, /agent, /coder (and /run as an alias for agent). Slash-command templates — every command from the project’s slash-command catalog (.chatcli/commands, ~/.chatcli/commands and every interop dir — Claude Code, Devin, Windsurf, Cursor, opencode, Codex, Gemini, Qwen, Copilot) is advertised with its argument-hint as the input hint. Invoking one expands the template and runs it through the session’s current mode, exactly like typed text. Headless-capable commands — run for real, output returned to the chat: Everything else — commands that need a live terminal (/menu, /gateway, /schedule, /auth, …) answer with a clear “not available over the IDE connection” message instead of failing silently. /session operates on the caller’s session, not on process-global state: each IDE session saves, loads and binds independently. /session attach <name> (or save/load) binds the IDE conversation to a named session in the shared store — every turn is written through and writes from other surfaces (terminal REPL, gateway, MCP clients) are adopted before each turn. See Cross-surface continuity.
A prompt that merely starts with a slash — a filesystem path like /usr/local/bin explain this, a typo’d word — is not hijacked: unknown tokens flow to the model as normal user text.
Commands that would ask for confirmation on a terminal (e.g. /context rm <name>) run with stdin quarantined on the server: any confirmation is answered as deny, fail-safe. Destructive maintenance is best done in the terminal REPL.

Permission dialogs

Unattended agent servers historically face a hard choice: auto-approve everything or block everything. ACP has a better answer — session/request_permission — and ChatCLI uses it:
  • Your security policy ask rules raise the IDE’s native dialog with the full terminal vocabulary: Allow / Always allow / Reject / Always reject. The always choices persist a rule in coder_policy.json — exactly what pressing a or d does in the terminal prompt — so the next match never needs a dialog.
  • When the coder wants to run a command classified dangerous (recursive deletes, force pushes, privilege escalation…), the dialog offers Allow once / Reject once only: exec commands never get a blanket always, matching the interactive prompt that hides those options for them on purpose.
  • Anything except an explicit Allow — reject, dialog dismissed, client crash, timeout — denies the action. The agent sees the refusal in-band and replans; an Always reject also records a permanent deny rule.
  • The same gate covers shell blocks the agent proposes in agent mode.
A dialog that never gets an answer cannot freeze the run: the wait is bounded by CHATCLI_MCP_PERMISSION_TIMEOUT (shared with the MCP server; a Go duration like 90s/10m or plain seconds, default 600s; 0/off lifts the bound to a 24h ceiling). On expiry the action denies fail-safe, the model is told the request went unanswered — not that you denied it — so it can continue and explain, and later dialogs in the same run fail fast instead of stalling again. Prefer no dialogs at all for a session? /policy mode auto switches the session into policy automode: ask rules auto-approve while deny rules and safety-immune operations keep gating. /policy mode interactive restores the dialogs. Clients that don’t implement the permission request (answering method not found) keep working without it: dangerous commands are refused fail-safe, while policy ask rules fall back to the historical unattended contract (auto-approve) — so minimal or wrapped frontends are never bricked. Explicit deny rules block on every surface.

Your MCP servers work here too

The ACP process is a full ChatCLI boot — including the MCP client. If ~/.chatcli/mcp_servers.json exists, every configured MCP server is connected at startup and its tools join the agent’s toolbox, exactly like in the terminal:
  • Ask the agent something that needs an MCP tool (“check the open Jira issues”, “query the staging database”) and it calls the connected server’s tool autonomously. In the IDE, the call renders as a native structured tool call like any built-in.
  • Hot reload — editing mcp_servers.json reconnects servers live; no IDE or agent restart needed. /mcp from the prompt box shows connection status and tools.
  • Remote servers using OAuth must be authorized beforehand (run @mcp-login in the terminal once; tokens persist in the keychain).
Connections are established asynchronously at boot: a prompt sent in the very first seconds may not see a slow server’s tools yet — they join the catalog as soon as the connection completes.One conceptual difference from chatcli mcp-server: ACP has no client-facing tool list, so the IDE never invokes MCP tools directly — the agent uses them on your behalf. Direct tool invocation (hub passthrough) is an MCP-server feature.

Under the hood (for integrators)

ChatCLI implements ACP protocol version 1. Methods: session/update kinds emitted: agent_thought_chunk, agent_message_chunk, tool_call, tool_call_update, plan, current_mode_update, available_commands_update. Tool calls carry kind (read/edit/delete/move/search/execute/think/fetch/other), status, rawInput, locations (file paths for follow-along), and content capped for display — the model always receives the full output; the chat view gets a bounded summary. File mentions from the IDE work both ways: resource_link blocks arrive as file references the agent’s tools open themselves, and resource blocks (embedded contents) are injected as attached context.

Operational notes

  • One run at a time — agent/coder runs serialize process-wide (they share the engine state). A second session’s prompt waits for the current run to finish; a second prompt on the same session is rejected while one is in flight — and a queued session that you cancel gives up its place immediately instead of staying wedged behind the active run.
  • Parks and monitors stay in-turn — when the model uses @park (e.g. “check the PR gate every 30s”), the turn stays open and every monitoring cycle streams into the IDE on the same request; pressing Stop cancels the monitor completely (scheduler job + snapshot). Synchronous @scheduler waits emit a ⏳ heartbeat line every 30s. See Agent park & resume.
  • Permission dialogs are bounded and honest — each session/request_permission waits up to CHATCLI_MCP_PERMISSION_TIMEOUT (default 600s; off = 24h ceiling). A timeout or a failed dialog round-trip blocks the action fail-safe but is reported to the model as unanswered/failed, never as a user denial.
  • Environment — the ACP process is a normal ChatCLI boot: .env, keychain-backed OAuth logins, provider catalogs, MCP client config and skills all resolve exactly as in your terminal.
  • Sessions are per-session and restorable — each IDE session keeps its own conversation history. session/load (capability loadSession: true) brings a prior session id back: live state if the server is still running, or the mcp-<id> autosave mirror after a restart, with the conversation replayed into the IDE. For durable continuity across surfaces, bind to a named session with /session attach <name> — see Cross-surface continuity.
  • Logs — stdout is the protocol; diagnostics go to the standard ChatCLI log file. Set LOG_LEVEL=debug in the agent’s env block when investigating.

Troubleshooting

See also