Neutral Go ACP adapter for Claude Code.
This repository provides a Go ACP adapter for Claude Code. It can run as a small, auditable ACP stdio binary or as an in-process Go library. Both modes run Claude Code prompts through the same native command bridge; deeper Claude-native parity remains tracked as future work.
- Speak standard Agent Client Protocol over stdio.
- Keep the adapter independent from Hecate internals.
- Avoid package-manager launchers, shell wrappers, and broad environment inheritance.
- Preserve the important behavior exposed by the previous Claude Agent ACP adapter: sessions, settings, auth, model/config options, permission requests, MCP servers, elicitation, tool updates, terminal output, cancellation, and resume/load behavior.
- Ship deterministic, checksum-verified, provenance-attested Go release binaries.
Implemented:
- stdlib-only JSON-RPC/NDJSON ACP transport scaffold
initializeresponse with adapter metadata- structured errors for unimplemented methods
- source-review notes for the previous adapter behavior
- unit tests for the protocol scaffold
doctorcommand for probing the local Claude Code binary boundary- process-backed runtime launcher seam
- subprocess JSON-RPC client for ACP-style stdio runtime bridges
- ACP initialize client for subprocess runtime negotiation
- typed ACP session lifecycle calls for subprocess runtimes
- ACP server-to-runtime bridge for session methods and streamed updates
- runtime host seam that launches, initializes, and exposes the bridged child
- protocol forwarding for session load, resume, fork, list, delete, and MCP-over-ACP message payloads
- command-backed native Claude Code path using
claude --print - ACP model, effort, and permission-mode config options for the command-backed path
- command-backed Claude UUID session ids created with
claude --session-idand continued withclaude --resume, includingsession/load/session/resumeadoption of host-known ids after adapter process restart - in-memory command-backed session fork plus bounded transcript replay for multi-turn continuity while the adapter process is alive
- command-backed
session/listmetadata,config_option_updatenotifications for config changes, andsession_info_updatenotifications when transcript metadata changes - live provider-owned command discovery through a short-lived Claude
stream-JSON control exchange, published as replacement ACP
available_commands_updatesnapshots (including provider aliases) - command-backed ACP stdio/HTTP MCP server config propagation into Claude
--mcp-configwith--strict-mcp-config - Claude
--output-format stream-jsontranslation into ACP assistant text, thinking, tool-call, usage, and terminal stop-reason updates, plus generic commandtool_callactivity for the native Claude process - ACP
authenticatemapped to the nativeclaude /logincommand - ACP
logoutmapped to the nativeclaude auth logoutcommand - CI and tag-driven release packaging for checksum-verified, provenance-attested binaries
Not implemented yet:
- deeper Claude Code / Claude Agent SDK integration beyond
claude --print - deeper vendor-specific durable/native persistent session semantics beyond
Claude
--session-idcreation and--resumecontinuation - deeper provider-native permission/MCP lifecycle and elicitation edge cases beyond parsed request mapping and the selected Claude Code permission mode
- an explicit trusted mode for an unrestricted project/plugin command catalog; automatic discovery intentionally uses Claude CLI's documented bare/minimal startup boundary
- runtime config/auth/model discovery and orphan-result handling
- draft ACP RFD surfaces documented as future work in
docs/STABLE_READINESS.md
Shared ACP transport, runtime JSON-RPC, bridge, host, process, doctor runner, and fake-runtime test code lives in acp-adapter-kit. Keep this repo focused on the Claude Code-specific CLI boundary, doctor defaults, docs, release workflow, and vendor behavior.
Hosts can import
github.com/hecatehq/claude-code-acp-adapter/claudecodeadapter to build the
same ACP server, info/options, CLI spec, config options, environment allowlists,
and Claude Code prompt command without shelling out to
claude-code-acp-adapter. The embedded path still launches the underlying
claude CLI for prompts; it only removes the extra adapter process boundary.
NewServerWithRunner lets the host bind that child process to an exact
executable path and host-owned sanitized environment. To receive live command
catalog updates, that runner must also implement the shared
commandbridge.CommandStarter interface; otherwise prompts remain available
and command discovery is intentionally skipped.
make release-check
make snapshot
go test ./...
go test -race ./...
go vet ./...
go run ./cmd/claude-code-acp-adapter --version
go run ./cmd/claude-code-acp-adapter doctorSee docs/TESTING.md for what is covered today and what still needs production-grade/deeper Claude-native parity coverage. See docs/RELEASE.md for the tag-driven release flow. See docs/STABLE_READINESS.md for the gate before a stable adapter tag.
The binary uses Cobra for human commands, but the root command with no arguments is reserved for ACP stdio. Do not add default logging, banners, usage output, or prompts to the no-argument path; stdout is the protocol stream.
Use doctor before wiring this adapter into an ACP host. It resolves the Claude
Code binary, runs a fixed-argv version probe through the hardened process
runner, and reports selected environment variable presence without printing
secret values. Use --binary to point at a non-default Claude executable and
--json for machine-readable output.
By default, the root ACP server owns lightweight ACP sessions and runs each
prompt through claude --print in the session workspace. The command-backed
path exposes ACP config options for model, effort, and Claude Code permission
mode, including Claude Code's bypassPermissions full-access mode, passes only
provider-specific environment variables through the shared process runner, and
runs Claude with --output-format stream-json. Known
Claude JSONL events are translated into ACP assistant text, thinking,
tool-call, permission-request, and usage updates. Tool updates preserve Claude
Code categories such as shell execution, file reads/edits, web fetch/search,
task tools, memory recall, todos, and plan/thinking tools; unknown JSONL events
are ignored rather than shown as raw chat text. A generic tool_call still
wraps the native Claude process execution so hosts can show the outer command
boundary. The session state is lightweight in the adapter, but session ids are
Claude-native UUIDs. New sessions are created with claude --session-id; later
prompts and host-known sessions adopted through session/load or
session/resume continue with claude --resume. If a host-known session was
persisted before an earlier Claude process launched, Claude has no matching
native conversation after adapter restart. In that narrow case, the adapter
classifies the prompt failure as native_session_missing, but only when Claude
reports the exact UUID-bound error before emitting any prompt output. The ACP
host decides from its persisted transcript whether replacing that native
conversation is safe; the adapter never retries a prompt. session/fork and
transcript replay remain in-memory conveniences while the adapter process is
alive.
session/list returns the adapter's currently loaded session metadata, and
later prompts receive a bounded transcript prelude so command-backed turns keep
conversational context while still using Claude's native session id.
session/close cancels active work and frees the adapter's in-memory session
state; session/delete is the destructive cleanup path hosts should use when
deleting the owning chat/session. Config changes return the current config
option list and publish config_option_update notifications. Completed
command-backed prompts publish session_info_update notifications with the
in-memory title and updated timestamp when transcript metadata changes.
After a session lifecycle change, the adapter asks Claude Code for its live
command inventory through a bounded, no-prompt --print --bare stream-JSON
control exchange. The resulting available_commands_update is a replacement
snapshot: the adapter does not maintain, filter, or extend a static slash-command
allowlist, and aliases are included when Claude advertises them. The probe uses
Claude CLI's documented bare/minimal startup boundary plus a strict MCP
boundary: it omits prompts, native session ids, MCP configuration, and
extra directories; it intentionally does not trigger automatic project/plugin
initialization. Command discovery is best-effort—an unavailable or malformed
catalog never prevents a usable ACP session—and provider output other than the
bounded command fields is discarded.
The adapter advertises one ACP auth method (agent-login): ACP
authenticate maps to claude /login, and ACP logout maps to
claude auth logout.
The command-backed path does not advertise ACP inline-image or embedded-context
capabilities. Hosts should provide files and images as ACP resource links; the
adapter turns each link into an explicit name, MIME type, and URI in the prompt
passed to claude. The shared command bridge validates and privately restages
local file links, then the adapter grants Claude access to that prompt-scoped
staging directory via --add-dir. Remote links remain unfetched prompt
metadata. Invalid local links fail before the provider process starts.
The root ACP server can also launch an explicit subprocess-backed ACP runtime
with --runtime-binary, --runtime-workdir, and repeated --runtime-arg
flags. That runtime process receives only the Claude Code adapter's explicit
environment allowlist (PATH, HOME, XDG_CONFIG_HOME, TMPDIR,
ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, and CLAUDE_CONFIG_DIR); the parent
environment is not inherited wholesale. Runtime flags override the native
command-backed path and are mostly useful for protocol parity testing.
Before implementing the real bridge, read docs/SOURCE_REVIEW.md. It records historical package/source behavior that this project needs to preserve or deliberately replace.