ACP-compatible agent for Claude Code (Python version)
Project description
Claude Code ACP (Python)
Python implementation of ACP (Agent Client Protocol) for Claude Code.
This package bridges the Claude Agent SDK with the Agent Client Protocol (ACP), providing two ways to use Claude:
- ACP Server - Connect Claude to any ACP-compatible editor (Zed, Neovim, JetBrains, etc.)
- Python Client - Event-driven API for building Python applications with Claude
Features
- Uses Claude CLI subscription - No API key needed, uses your existing Claude subscription
- Full ACP protocol support - Compatible with Zed, Neovim, and other ACP clients
- Bidirectional communication - Permission requests, tool calls, streaming responses
- Event-driven Python API - Decorator-based handlers for easy integration
- Session management - Create, fork, resume, list sessions
- Multiple permission modes - default, acceptEdits, plan, bypassPermissions
Installation
pip install claude-code-acp
Or with uv:
uv tool install claude-code-acp
Requirements
- Python 3.10+
- Claude CLI installed and authenticated (
claude /login)
Components
| Class | Type | Description |
|---|---|---|
ClaudeAcpAgent |
ACP Server | For editors (Zed, Neovim) to connect |
ClaudeClient |
Python API | Event-driven wrapper (uses agent internally) |
AcpClient |
ACP Client | Connect to any ACP agent via subprocess |
Usage 1: ACP Server for Editors
Run as an ACP server to connect Claude to your editor:
claude-code-acp
Zed Editor
Add to your Zed settings.json:
{
"agent_servers": {
"Claude Code Python": {
"type": "custom",
"command": "claude-code-acp",
"args": [],
"env": {}
}
}
}
Then open the Agent Panel (Ctrl+? / Cmd+?) and select "Claude Code Python" from the + menu.
Other Editors
Any ACP-compatible client can connect by spawning claude-code-acp as a subprocess and communicating via stdio.
Usage 2: Python Event-Driven API
Use ClaudeClient for building Python applications with Claude:
import asyncio
from claude_code_acp import ClaudeClient
async def main():
client = ClaudeClient(cwd=".")
@client.on_text
async def handle_text(text: str):
"""Called for each text chunk from Claude."""
print(text, end="", flush=True)
@client.on_tool_start
async def handle_tool_start(tool_id: str, name: str, input: dict):
"""Called when Claude starts using a tool."""
print(f"\n๐ง {name}")
@client.on_tool_end
async def handle_tool_end(tool_id: str, status: str, output):
"""Called when a tool completes."""
icon = "โ
" if status == "completed" else "โ"
print(f" {icon}")
@client.on_permission
async def handle_permission(name: str, input: dict) -> bool:
"""Called when Claude needs permission. Return True to allow."""
print(f"๐ Permission requested: {name}")
return True # or prompt user
@client.on_complete
async def handle_complete():
"""Called when the query completes."""
print("\n--- Done ---")
# Send a query
response = await client.query("Create a hello.py file that prints Hello World")
print(f"\nFull response: {response}")
asyncio.run(main())
Event Handlers
| Decorator | Arguments | Description |
|---|---|---|
@client.on_text |
(text: str) |
Streaming text chunks from Claude |
@client.on_thinking |
(text: str) |
Thinking/reasoning blocks |
@client.on_tool_start |
(tool_id, name, input) |
Tool execution started |
@client.on_tool_end |
(tool_id, status, output) |
Tool execution completed |
@client.on_permission |
(name, input) -> bool |
Permission request (return True/False) |
@client.on_error |
(exception) |
Error occurred |
@client.on_complete |
() |
Query completed |
Client Methods
# Start a new session
session_id = await client.start_session()
# Send a query (returns full response text)
response = await client.query("Your prompt here")
# Set permission mode
await client.set_mode("acceptEdits") # or "default", "plan", "bypassPermissions"
Usage 3: ACP Client (Connect to Any Agent)
Use AcpClient to connect to any ACP-compatible agent:
import asyncio
from claude_code_acp import AcpClient
async def main():
# Connect to claude-code-acp (Python version)
client = AcpClient(command="claude-code-acp")
# Or connect to the TypeScript version
# client = AcpClient(command="npx", args=["@zed-industries/claude-code-acp"])
# Or any other ACP agent
# client = AcpClient(command="my-custom-agent")
@client.on_text
async def handle_text(text: str):
print(text, end="", flush=True)
@client.on_tool_start
async def handle_tool(tool_id: str, name: str, input: dict):
print(f"\n๐ง {name}")
@client.on_permission
async def handle_permission(name: str, input: dict, options: list) -> str:
"""Return option_id: 'allow', 'reject', or 'allow_always'"""
print(f"๐ Permission: {name}")
return "allow"
@client.on_complete
async def handle_complete():
print("\n--- Done ---")
async with client:
response = await client.prompt("What files are here?")
asyncio.run(main())
AcpClient vs ClaudeClient
| Feature | ClaudeClient |
AcpClient |
|---|---|---|
| Uses | Claude Agent SDK directly | Any ACP agent via subprocess |
| Connection | In-process | Subprocess + stdio |
| Agents | Claude only | Any ACP-compatible agent |
| Use case | Simple Python apps | Multi-agent, testing, flexibility |
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your Application โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Zed/Neovim โ โ Python Application โ โ
โ โ (ACP Client) โ โ โ โ
โ โโโโโโโโโโฌโโโโโโโโโ โ client = ClaudeClient() โ โ
โ โ โ โ โ
โ โ ACP Protocol โ @client.on_text โ โ
โ โ (stdio/JSON-RPC) โ async def handle(text): โ โ
โ โ โ print(text) โ โ
โ โผ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โ claude-code-acp (This Package) โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ ClaudeAcpAgent โ โ ClaudeClient โ โ โ
โ โ โ (ACP Server) โ โ (Event-driven wrapper) โ โ โ
โ โ โโโโโโโโโโโโฌโโโโโโโโโโโ โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ โ โ
โ โ โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Claude Agent SDK โ โ
โ โ (claude-agent-sdk) โ โ
โ โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Claude CLI โ โ
โ โ (Your Claude Subscription) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
What We Built
This project combines two official SDKs to create a complete Python solution:
Integrated Components
| Component | Source | Purpose |
|---|---|---|
| Agent Client Protocol SDK | Anthropic | ACP server/client protocol implementation |
| Claude Agent SDK | Anthropic | Claude CLI wrapper with streaming support |
Our Contributions
-
ClaudeAcpAgent (
agent.py)- Bridges Claude Agent SDK with ACP protocol
- Converts Claude messages to ACP session updates
- Handles bidirectional permission requests
- Session management (create, fork, resume, list)
-
ClaudeClient (
client.py)- Event-driven Python API with decorators
- Smart text deduplication for streaming
- Simple permission handling
- Clean async/await interface
-
ACP Server Entry Point
- Standalone
claude-code-acpcommand - Direct integration with Zed and other ACP clients
- No configuration needed
- Standalone
Why This Package?
| Approach | API Key | Subscription | ACP Support | Event-Driven |
|---|---|---|---|---|
| Anthropic API directly | โ Required | โ | โ | โ |
| Claude Agent SDK | โ | โ Uses CLI | โ | Partial |
| claude-code-acp | โ | โ Uses CLI | โ Full | โ Full |
Examples
Simple Chat
import asyncio
from claude_code_acp import ClaudeClient
async def main():
client = ClaudeClient()
@client.on_text
async def on_text(text):
print(text, end="")
while True:
user_input = input("\nYou: ")
if user_input.lower() == "quit":
break
await client.query(user_input)
asyncio.run(main())
File Operations with Permission Control
import asyncio
from claude_code_acp import ClaudeClient
async def main():
client = ClaudeClient(cwd="/path/to/project")
@client.on_text
async def on_text(text):
print(text, end="")
@client.on_permission
async def on_permission(name, input):
response = input(f"Allow '{name}'? [y/N]: ")
return response.lower() == "y"
await client.query("Refactor the main.py file to use async/await")
asyncio.run(main())
Auto-approve Mode
import asyncio
from claude_code_acp import ClaudeClient
async def main():
client = ClaudeClient(cwd=".")
# Bypass all permission checks
await client.set_mode("bypassPermissions")
@client.on_text
async def on_text(text):
print(text, end="")
await client.query("Create a complete Flask app with tests")
asyncio.run(main())
Development
# Clone
git clone https://github.com/yazelin/claude-code-acp-py
cd claude-code-acp-py
# Install dependencies
uv sync
# Run locally
uv run claude-code-acp
# Run tests
uv run python -c "from claude_code_acp import ClaudeClient; print('OK')"
Related Projects
- claude-code-acp - TypeScript version by Zed Industries
- agent-client-protocol - ACP specification and SDKs
- claude-agent-sdk-python - Official Claude Agent SDK
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_code_acp-0.3.0.tar.gz.
File metadata
- Download URL: claude_code_acp-0.3.0.tar.gz
- Upload date:
- Size: 58.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b144697581a5c3db37d80685f30163544260266df95b72f0d613b0c48963c0fc
|
|
| MD5 |
86bd60312a335de7e5091f15f0b1b3f2
|
|
| BLAKE2b-256 |
fc4610aae55fe25138ad403f128e5688fdd1fd6706401ba695769369b1afbacc
|
Provenance
The following attestation bundles were made for claude_code_acp-0.3.0.tar.gz:
Publisher:
publish.yml on yazelin/claude-code-acp-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_code_acp-0.3.0.tar.gz -
Subject digest:
b144697581a5c3db37d80685f30163544260266df95b72f0d613b0c48963c0fc - Sigstore transparency entry: 909900410
- Sigstore integration time:
-
Permalink:
yazelin/claude-code-acp-py@28c28376c9fdf93b6f82bf3fb8a3bacb84ecab68 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/yazelin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@28c28376c9fdf93b6f82bf3fb8a3bacb84ecab68 -
Trigger Event:
release
-
Statement type:
File details
Details for the file claude_code_acp-0.3.0-py3-none-any.whl.
File metadata
- Download URL: claude_code_acp-0.3.0-py3-none-any.whl
- Upload date:
- Size: 17.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72ad6c908eeed7f4c803c330a2bb93b18e144e908c7a88de67a7685cb21fec85
|
|
| MD5 |
94d379de0ea4cbb45ae909f02a8ff00f
|
|
| BLAKE2b-256 |
2ffb6573545bbd5a4635eb42c057e666791a7fcacebea2ad049e6525bcafda69
|
Provenance
The following attestation bundles were made for claude_code_acp-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on yazelin/claude-code-acp-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_code_acp-0.3.0-py3-none-any.whl -
Subject digest:
72ad6c908eeed7f4c803c330a2bb93b18e144e908c7a88de67a7685cb21fec85 - Sigstore transparency entry: 909900413
- Sigstore integration time:
-
Permalink:
yazelin/claude-code-acp-py@28c28376c9fdf93b6f82bf3fb8a3bacb84ecab68 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/yazelin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@28c28376c9fdf93b6f82bf3fb8a3bacb84ecab68 -
Trigger Event:
release
-
Statement type: