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 combines the Claude Agent SDK with the Agent Client Protocol (ACP) to provide a complete Python solution for working with AI agents.

What It Does

Component Type Description
ClaudeAcpAgent ACP Server Lets editors (Zed, Neovim, JetBrains) use Claude
ClaudeClient Python API Event-driven wrapper for building Python apps with Claude
AcpClient ACP Client Connect to any ACP-compatible agent (Claude, Gemini, custom)
AcpProxyServer Copilot Proxy Bridge Copilot SDK to any ACP backend

Two CLI commands:

  • claude-code-acp - ACP server for editors
  • copilot-acp-proxy - Copilot SDK proxy to ACP backends

Features

  • Uses Claude CLI subscription - No API key needed
  • Full ACP protocol - Compatible with Zed, Neovim, and other ACP clients
  • Universal ACP client - Connect to any ACP agent (Claude, Gemini, or custom)
  • Copilot SDK bridge - Use Copilot SDK with any ACP backend
  • Bidirectional communication - Permission requests, tool calls, streaming
  • Event-driven Python API - Decorator-based handlers
  • Session management - Create, fork, resume, list sessions
  • MCP server support - Dynamic loading via stdio, HTTP, SSE
  • Model & command enumeration - Discover available models and commands at runtime

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)

Usage 1: ACP Server for Editors

Run as an ACP server so editors can use Claude:

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=".", system_prompt="You are a helpful assistant.")

    @client.on_text
    async def handle_text(text: str):
        print(text, end="", flush=True)

    @client.on_tool_start
    async def handle_tool_start(tool_id: str, name: str, input: dict):
        print(f"\n[Tool] {name}")

    @client.on_tool_end
    async def handle_tool_end(tool_id: str, status: str, output):
        icon = "ok" if status == "completed" else "fail"
        print(f" [{icon}]")

    @client.on_permission
    async def handle_permission(name: str, input: dict) -> bool:
        print(f"Permission requested: {name}")
        return True  # or prompt user

    @client.on_complete
    async def handle_complete():
        print("\n--- Done ---")

    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
@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

# Init with optional MCP servers and system prompt
client = ClaudeClient(cwd=".", mcp_servers=[...], system_prompt="...")

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

Since this package implements a full ACP client, you can use AcpClient to connect to any ACP-compatible agent - not just Claude.

import asyncio
from claude_code_acp import AcpClient

async def main():
    # Connect to any ACP agent
    client = AcpClient(command="claude-code-acp")

    @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[Tool] {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

# Claude (this package)
claude = AcpClient(command="claude-code-acp")

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

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

# Any custom ACP agent
custom = AcpClient(command="my-custom-agent")

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

File Operation Handlers

Intercept file read/write operations for security or custom handling:

@client.on_file_read
async def handle_read(path: str) -> str | None:
    """Return content to override, or None to proceed normally."""
    print(f"Reading: {path}")
    return None

@client.on_file_write
async def handle_write(path: str, content: str) -> bool:
    """Return True to allow, False to block."""
    print(f"Writing: {path}")
    return input("Allow? [y/N]: ").lower() == "y"

Terminal Operation Handlers

Intercept shell execution for security:

@client.on_terminal_create
async def handle_terminal(command: str, cwd: str) -> bool:
    """Return True to allow, False to block."""
    print(f"Command: {command} in {cwd}")
    return input("Allow? [y/N]: ").lower() == "y"

@client.on_terminal_output
async def handle_output(terminal_id: str, output: str) -> None:
    print(output, end="")

Model Selection

# Set model before connecting (pending)
client = AcpClient(command="claude-code-acp")
client.set_model("opus")  # Will be applied when session starts

# Or set model on active session
async with client:
    await client.set_model("sonnet")
    response = await client.prompt("Hello!")

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
File/Terminal hooks No Yes
Use case Simple Python apps Multi-agent, testing, flexibility

Usage 4: Copilot SDK Proxy

The copilot-acp-proxy command bridges the Copilot SDK to any ACP backend, allowing Copilot SDK applications to use Claude, Gemini, or other ACP agents.

# Connect Copilot SDK to Gemini
copilot-acp-proxy --headless --stdio --backend gemini

# Connect Copilot SDK to Claude
copilot-acp-proxy --headless --stdio --backend claude-code-acp

# Connect Copilot SDK to Copilot CLI
copilot-acp-proxy --headless --stdio --backend copilot

Proxy with Copilot SDK (Python)

import asyncio
from copilot import CopilotClient

async def main():
    client = CopilotClient({"cli_path": "copilot-acp-proxy"})
    await client.start()

    session = await client.create_session({"model": "sonnet"})

    def on_event(event):
        event_type = event.type.value if hasattr(event.type, 'value') else str(event.type)
        if event_type == "assistant.message_delta":
            delta = getattr(event.data, 'deltaContent', None)
            if delta:
                print(delta, end="", flush=True)

    session.on(on_event)
    await session.send({"prompt": "Hello!"})

asyncio.run(main())

Supported Backends

Backend Command Model Examples
Gemini gemini gemini-2.0-flash, gemini-2.5-flash
Claude claude-code-acp opus, sonnet
Copilot copilot gpt-4, gpt-4o

Environment variables:

  • ACP_PROXY_BACKEND - Default backend (default: gemini)
  • ACP_PROXY_LOG_LEVEL - Log level (none/error/warning/info/debug/all)
  • ACP_PROXY_LOG_FILE - Log file path

Gemini ACP Usage

Note: Gemini takes ~12 seconds to initialize on first connection.

import asyncio
from claude_code_acp import AcpClient

async def main():
    client = AcpClient(
        command="gemini",
        args=["--experimental-acp"],
        cwd="/tmp",
    )

    @client.on_text
    async def on_text(text):
        print(text, end="", flush=True)

    @client.on_thinking
    async def on_thinking(text):
        print(f"[Thinking] {text[:50]}...")

    async with client:
        response = await client.prompt("Hello!")
        print(f"\nResponse: {response}")

asyncio.run(main())

Gemini with MCP Servers

Gemini requires MCP servers to be pre-configured via CLI:

# Add MCP server to Gemini config
gemini mcp add nanobanana "uvx nanobanana"

# Verify
gemini mcp list

Then enable via --allowed-mcp-server-names:

client = AcpClient(
    command="gemini",
    args=["--experimental-acp", "--allowed-mcp-server-names", "nanobanana"],
    cwd="/tmp",
)

MCP Configuration Comparison

Agent Dynamic MCP (via ACP) Pre-configured MCP
claude-code-acp Supported Supported
Gemini CLI Not supported Use --allowed-mcp-server-names

claude-code-acp supports dynamic MCP configuration:

client = AcpClient(
    command="claude-code-acp",
    cwd="/tmp",
    mcp_servers=[{
        "name": "nanobanana",
        "command": "uvx",
        "args": ["nanobanana"],
        "env": {"GEMINI_API_KEY": "your-key"},
    }],
)

Architecture

                    ┌─────────────────────────────────────────┐
                    │        Editors / ACP Clients            │
                    │     (Zed, Neovim, JetBrains, etc.)      │
                    └──────────────┬──────────────────────────┘
                                   │ ACP (stdio)
                                   ▼
┌──────────────────────────────────────────────────────────────┐
│                    claude-code-acp                            │
│                                                              │
│  ┌────────────────┐  ┌─────────────┐  ┌───────────────────┐ │
│  │ ClaudeAcpAgent │  │ ClaudeClient│  │    AcpClient      │ │
│  │  (ACP Server)  │  │ (Python API)│  │  (ACP Client)     │ │
│  └───────┬────────┘  └──────┬──────┘  └─────┬─────────────┘ │
│          │                  │               │               │
│          ▼                  ▼               ▼               │
│     Claude CLI         Claude CLI     Any ACP Agent         │
│    (Agent SDK)        (Agent SDK)    ┌──────────────┐       │
│                                      │ claude-code  │       │
│  ┌─────────────────────────────┐     │ gemini       │       │
│  │    AcpProxyServer           │     │ custom agent │       │
│  │  (copilot-acp-proxy)       │     └──────────────┘       │
│  │  Copilot SDK → ACP backend │                             │
│  └─────────────────────────────┘                             │
└──────────────────────────────────────────────────────────────┘

Component Summary

Component Role ACP? Connects to
ClaudeAcpAgent Server Yes (Server) Editors connect to it
ClaudeClient Python API No Claude CLI directly
AcpClient Client Yes (Client) Any ACP agent
AcpProxyServer Proxy Translates Copilot SDK <-> ACP backends

What We Built

This project integrates two official Anthropic SDKs:

Component Source Purpose
Agent Client Protocol SDK Anthropic ACP server/client protocol
Claude Agent SDK Anthropic Claude CLI wrapper with streaming

Our Contributions

  1. ClaudeAcpAgent (agent.py) - ACP server bridging Claude Agent SDK with ACP protocol. Handles bidirectional permissions, session management, streaming, MCP servers, model/command enumeration.

  2. ClaudeClient (client.py) - Event-driven Python API with decorator handlers, streaming text deduplication, and simple async/await interface.

  3. AcpClient (acp_client.py) - Full ACP client implementation that can connect to any ACP-compatible agent via subprocess. Includes file/terminal operation interception.

  4. AcpProxyServer (proxy/) - Copilot SDK compatibility layer. Translates Copilot SDK JSON-RPC protocol to ACP, enabling Copilot SDK apps to use Claude, Gemini, or other backends.

Why This Package?

Approach API Key Subscription ACP Support Multi-Agent Event-Driven
Anthropic API directly Required No No No No
Claude Agent SDK No Uses CLI No No Partial
claude-code-acp No Uses CLI Full Yes 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())

Auto-approve Mode

import asyncio
from claude_code_acp import ClaudeClient

async def main():
    client = ClaudeClient(cwd=".")
    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())

Multi-Agent Comparison

import asyncio
from claude_code_acp import AcpClient

async def ask(agent_name, command, args, prompt):
    client = AcpClient(command=command, args=args)

    @client.on_text
    async def on_text(text):
        pass  # collect silently

    async with client:
        response = await client.prompt(prompt)
        print(f"{agent_name}: {response[:100]}...")

async def main():
    await asyncio.gather(
        ask("Claude", "claude-code-acp", [], "What is ACP?"),
        ask("Gemini", "gemini", ["--experimental-acp"], "What is ACP?"),
    )

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 ACP server locally
uv run claude-code-acp

# Run Copilot proxy locally
uv run copilot-acp-proxy --backend gemini

# Run tests
uv run pytest tests/test_unit_*.py -v        # Unit tests (fast)
uv run pytest tests/ -m integration -v        # Integration tests (need Claude CLI)

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.4.2.tar.gz (176.2 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.4.2-py3-none-any.whl (37.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: claude_code_acp-0.4.2.tar.gz
  • Upload date:
  • Size: 176.2 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.4.2.tar.gz
Algorithm Hash digest
SHA256 89a9921ee638a107b71d9aff77067edbba77037fba8ed1fc10e5eecb66e432af
MD5 3a12b87e4ddd9efc745008dfa233f2df
BLAKE2b-256 9f0555cd04f31be2dd398d06599ec5b3a97c9b14acc02a2745fc042ccc6364aa

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for claude_code_acp-0.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1093d4d6514b7a3b0f4a8e0bda5d1ecfc3d1bd4b9c98cc29b779bc727879f965
MD5 33863fa8c116cfaeaf51302d96a40de5
BLAKE2b-256 0ea999b571a4743c43de4b560b4a390443f31f9c488a7fe4c52b614eb0508bd2

See more details on using hashes here.

Provenance

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