On 2026-10-26 we wired Claude Code into Xcode 27.1 (build 25183.120.5) using the built-in ACP support. Xcode's Add an ACP Agent dialog asks for five fields: Name, Executable, Interpreter, Arguments, and Environment Variables. No plugin, no project file: point Xcode at one executable and get the handshake right.
These notes cover what we ran, what came back, and four failures. Everything below was verified in a terminal. We did not verify the GUI session end to end.
What ACP is doing inside Xcode
ACP is the Agent Client Protocol. An editor acts as the client, driving an agent over JSON-RPC on stdio. Xcode 27.1 ships the client side as IDEIntelligenceACP.framework under Xcode.app/Contents/PlugIns/. It sends protocol requests and does not care what is on the other end.
The Claude Code CLI does not speak ACP. A small adapter translates ACP requests into something Claude Code understands and returns the result as ACP. The package you install is the adapter, not Claude Code itself.
Step 1: install the adapter under its current name
The old package is deprecated. Check it first:
npm view @zed-industries/claude-code-acp version deprecated
# 0.16.2
# This package has been renamed to @agentclientprotocol/claude-agent-acp.
# Please migrate to continue receiving updates.
Install the new one:
npm i -g @agentclientprotocol/claude-agent-acp
which claude-agent-acp
# /Users/<your-username>/.npm-global/bin/claude-agent-acp
If your npm prefix differs, run npm prefix -g and look under <prefix>/bin/. Our machine had 0.95.0 installed; the latest on npm at the time was 0.97.0.
Do not put npx in the Executable field. Xcode starts the agent in something closer to a clean shell; npx may download the package on the spot, stop at a prompt, or resolve a different version. A global install gives you one fixed entry point.
Confirm what npm produced:
ls -lL ~/.npm-global/bin/claude-agent-acp
file ~/.npm-global/bin/claude-agent-acp
# a /usr/bin/env node script text executable
It is a #!/usr/bin/env node script, not a native binary. That decides how you fill in Interpreter.
Step 2: fill the five fields
| Field | Value | Why |
|---|---|---|
| Name | Claude Agent |
Display name; avoid an existing agent's name |
| Executable | /Users/<your-username>/.npm-global/bin/claude-agent-acp |
Absolute path; ~ is not guaranteed to expand |
| Interpreter | leave empty | The script has a shebang; the kernel finds node |
| Arguments | leave empty | The adapter reads ACP from stdio and needs no flags |
| Environment Variables | usually empty | Only needed if node is missing from Xcode's inherited PATH |
Interpreter is the field people overfill. Xcode's help says to specify one only when the executable is a script that needs it; Xcode then runs the executable as an argument to that interpreter. This adapter already starts with #!/usr/bin/env node.
Point Executable at the bin/ entry, not lib/node_modules/.../dist/index.js; the bin/ symlink is npm-managed, while the dist path can move.
Arguments only appends flags. This adapter needs none, and extra values can be read as unknown options.
Step 3: probe the handshake before opening Xcode
The first ACP request is initialize. It does not create a session or read project files; it exchanges protocol version and capabilities. A small Python script proves the adapter launches:
import json
import subprocess
req = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": {
"fs": {"readTextFile": True, "writeTextFile": True},
"terminal": True,
},
"clientInfo": {"name": "acp-probe", "version": "0.1.0"},
},
}
p = subprocess.Popen(
["/Users/<your-username>/.npm-global/bin/claude-agent-acp"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True,
bufsize=1,
)
p.stdin.write(json.dumps(req) + "\n")
p.stdin.flush()
print(p.stdout.readline())
p.terminate()
ACP uses line-delimited JSON-RPC: one request per line, one response per line. You should see one JSON line immediately. This is the real response from the new package, version 0.95.0, on our machine:
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"_meta":{"claudeCode":{"promptQueueing":true},"authStatus":{}},"promptCapabilities":{"image":true,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true},"auth":{"logout":{}},"providers":{},"loadSession":true,"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"0.95.0"},"authMethods":[],"_meta":{"steering":{"supported":true}}}}
Run the same script against the old path, ~/.npm-global/bin/claude-code-acp, and the capability block differs:
- The old package's
sessionCapabilitieshas onlyfork,list, andresume; the new one addsclose,delete,additionalDirectories, andsystemPrompt.mode.override. - The new package adds
claudeCode.promptQueueing,authStatus,auth.logout, andprovidersunderagentCapabilities. - The old package returns one
authMethodsentry,claude-login; the new package returns[]and expresses login state throughauthStatus.
These fields are version-specific; do not treat this JSON as a contract.
Reading the response
-
promptCapabilities.imageandembeddedContext: the client may send images and embedded context; whether Xcode does is separate. -
mcpCapabilities.httpandsse: the adapter accepts those MCP transports, not that it connects on its own. -
loadSession: trueandsessionCapabilities: reload and session operations.authMethods: []means no ACP login method; checkauthStatus.
Together these identify the installed binary without trusting the version string.
Troubleshooting the four failures
1. Quarantine or permissions. Check extended attributes first:
xattr -l ~/.npm-global/bin/claude-agent-acp
If com.apple.quarantine is present, remove it with xattr -d com.apple.quarantine <path> and retry. On our machine the only attribute was com.apple.provenance — the provenance "ghost" attribute, not quarantine — and Gatekeeper did not block it. If Xcode raises a Files and Folders or Full Disk Access prompt, the prompt is usually attributed to Xcode, the parent process.
2. Node is not on PATH. The shebang uses the PATH inherited by the process, not the PATH after you source ~/.zshrc. which node working in your terminal does not prove Xcode can see it. Add PATH under Environment Variables and leave Interpreter empty, or set Interpreter to node's absolute path while Executable stays the script. Check first:
which node # for example /usr/local/bin/node
node --version # our machine: v24.12.0
3. The script is not executable. Inspect the symlink and its target:
ls -l ~/.npm-global/bin/claude-agent-acp
ls -lL ~/.npm-global/bin/claude-agent-acp
test -x ~/.npm-global/bin/claude-agent-acp && echo executable
If file says "script text executable" but test -x fails, the permission bits were lost. Run chmod +x on the target file. A normal npm global install is 755; upgrades and manual copies break this.
4. The handshake times out. The Python probe blocks on readline(). If nothing comes back: run the executable directly and see whether it exits with an error; confirm node --version works; confirm you are pointing at the bin/ entry and not a stale path from the old package. initialize does not require login. If a later session reports an unauthenticated state, run claude once in a terminal. "Xcode shows nothing" is not a diagnosis; the terminal probe is the minimal reproduction.
How we judged success
The terminal bar is simple: result.agentInfo.name matches the package you installed, protocolVersion is 1, and agentCapabilities is not empty. That proves the command Xcode will launch can start, read a request, and answer over ACP.
We then saved the configuration and created a session, but did not verify every GUI behavior, so we are not vouching for it. Once the handshake passes, the remaining problems tend to be PATH, permissions, or login.
Scope and caveats
Based on our own setup on 2026-10-26; Apple has not documented ACP in Xcode beyond the dialog. The commands and JSON response come from that machine's output, not a vendor walkthrough. ACP is changing quickly. Run the initialize probe against your installed version before debugging the dialog.
Top comments (0)