Skip to main content

ACP-compatible agent for Claude Code (Python version)

Project description

Claude Code ACP (Python)

PyPI Python License

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:

  1. ACP Server - Connect Claude to any ACP-compatible editor (Zed, Neovim, JetBrains, etc.)
  2. 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

  1. 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)
  2. ClaudeClient (client.py)

    • Event-driven Python API with decorators
    • Smart text deduplication for streaming
    • Simple permission handling
    • Clean async/await interface
  3. ACP Server Entry Point

    • Standalone claude-code-acp command
    • Direct integration with Zed and other ACP clients
    • No configuration needed

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


License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

claude_code_acp-0.3.0.tar.gz (58.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

claude_code_acp-0.3.0-py3-none-any.whl (17.3 kB view details)

Uploaded Python 3

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

Hashes for claude_code_acp-0.3.0.tar.gz
Algorithm Hash digest
SHA256 b144697581a5c3db37d80685f30163544260266df95b72f0d613b0c48963c0fc
MD5 86bd60312a335de7e5091f15f0b1b3f2
BLAKE2b-256 fc4610aae55fe25138ad403f128e5688fdd1fd6706401ba695769369b1afbacc

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file claude_code_acp-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for claude_code_acp-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 72ad6c908eeed7f4c803c330a2bb93b18e144e908c7a88de67a7685cb21fec85
MD5 94d379de0ea4cbb45ae909f02a8ff00f
BLAKE2b-256 2ffb6573545bbd5a4635eb42c057e666791a7fcacebea2ad049e6525bcafda69

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page