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
askrules 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
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
commandmust be an absolute path (which chatclito find it).envis 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 underagent_servers in Zed’s settings.json:
Session modes
Every ACP session runs in one of three modes, advertised to the client onsession/new and switchable at any time:
Switch modes from the IDE’s mode picker, or simply type the command in the prompt:
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.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
askrules raise the IDE’s native dialog with the full terminal vocabulary: Allow / Always allow / Reject / Always reject. The always choices persist a rule incoder_policy.json— exactly what pressingaorddoes 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:
execcommands 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.
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.jsonreconnects servers live; no IDE or agent restart needed./mcpfrom the prompt box shows connection status and tools. - Remote servers using OAuth must be authorized beforehand (run
@mcp-loginin 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@schedulerwaits emit a ⏳ heartbeat line every 30s. See Agent park & resume. - Permission dialogs are bounded and honest — each
session/request_permissionwaits up toCHATCLI_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(capabilityloadSession: true) brings a prior session id back: live state if the server is still running, or themcp-<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=debugin the agent’senvblock when investigating.
Troubleshooting
See also
- MCP Server — the sibling subcommand: ChatCLI’s full surface as MCP tools
- Coder Plugin — what the coder engine can do
- Coder Security — the dangerous-command classification behind permission dialogs
- Command Reference — the full slash-command surface
- Environment Variables — provider/model/security knobs