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())

Connect to Different Agents

from claude_code_acp import AcpClient

# Connect to our Claude ACP server
claude = AcpClient(command="claude-code-acp")

# Connect to Gemini CLI
gemini = AcpClient(command="gemini", args=["--experimental-acp"])

# Connect to TypeScript version
ts_claude = AcpClient(command="npx", args=["@zed-industries/claude-code-acp"])

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

Tested Agents

Agent Command Status
claude-code-acp (this package) claude-code-acp โœ… Works
Gemini CLI gemini --experimental-acp โœ… Works
TypeScript version npx @zed-industries/claude-code-acp โœ… Compatible

Architecture

This package provides three ways to use Claude:

Method A: Editor via ACP (ClaudeAcpAgent)

For Zed, Neovim, and other ACP-compatible editors:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      ACP Protocol      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      SDK      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Zed    โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€ stdio โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ  โ”‚ ClaudeAcpAgent  โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ   โ”‚ Claude CLI โ”‚
โ”‚  Editor  โ”‚                        โ”‚  (ACP Server)   โ”‚               โ”‚            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Method B: Python Direct (ClaudeClient)

For Python apps that want simple, direct access to Claude (no ACP protocol):

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     direct call      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      SDK      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Python  โ”‚ โ”€โ”€โ”€โ”€ in-process โ”€โ”€โ”€โ–บ โ”‚  ClaudeClient   โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ   โ”‚ Claude CLI โ”‚
โ”‚   App    โ”‚                      โ”‚                 โ”‚               โ”‚            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Method C: Python via ACP (AcpClient)

For Python apps that want to connect to any ACP-compatible agent:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      ACP Protocol      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Python  โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€ stdio โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ  โ”‚  Any ACP Agent  โ”‚
โ”‚   App    โ”‚                        โ”‚                 โ”‚
โ”‚          โ”‚                        โ”‚ โ€ข claude-code   โ”‚
โ”‚ AcpClientโ”‚                        โ”‚ โ€ข gemini        โ”‚
โ”‚          โ”‚                        โ”‚ โ€ข custom agents โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

Component Uses ACP? Purpose
ClaudeAcpAgent Yes (Server) Let editors connect to Claude
ClaudeClient No Simplest way for Python apps
AcpClient Yes (Client) Connect to any ACP agent

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.2.tar.gz (59.0 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.2-py3-none-any.whl (17.4 kB view details)

Uploaded Python 3

File details

Details for the file claude_code_acp-0.3.2.tar.gz.

File metadata

  • Download URL: claude_code_acp-0.3.2.tar.gz
  • Upload date:
  • Size: 59.0 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.2.tar.gz
Algorithm Hash digest
SHA256 3e57ddce47730b9ff5a8fca8e3220a7a5b442bc75c17f3d1b985d2ae13be9e7f
MD5 10800a3b1f70c65d003a19c210724c78
BLAKE2b-256 496625c7917a5ec4ad898b7260c67631d8c75ded2f8cebd5dc1fdaff0fe5bb82

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_code_acp-0.3.2.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.2-py3-none-any.whl.

File metadata

File hashes

Hashes for claude_code_acp-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 05817d489bafdca3d1687887eed859b7128c7c776c3da85dfed83bea575b09ba
MD5 04bfca421eb52927162a1f395cb6df5e
BLAKE2b-256 b67b4620337e4d44e7559ea877944e45ea75d496a8b8ec3fe8340f188d1d1901

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_code_acp-0.3.2-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