Standalone Python client for Cursor CLI in ACP mode (agent acp): JSON-RPC over stdio to drive Composer from your own code — similar in spirit to anthropic or google-genai, but talking to the local agent binary instead of a hosted HTTP API.
- Python 3.11+
- Cursor CLI with
agentonPATH(often~/.local/bin) CURSOR_API_KEYorCURSOR_AUTH_TOKENin the environment (or pass explicitly). From a checkout of this repo, you can pass the key inline (replacexwith your real key):
CURSOR_API_KEY=x python examples/quickstart.pyFrom this monorepo (path dependency):
uv add "cursor-agent @ file:./packages/cursor-agent"Or from PyPI:
pip install cursor-acpThe import name is still cursor_agent (package directory unchanged).
Run the included example from the repo (after an editable install):
pip install -e .
CURSOR_API_KEY=x python examples/quickstart.pyOptional prompt and working directory:
CURSOR_API_KEY=x python examples/quickstart.py --cwd /path/to/project "What does this codebase do?"Equivalent minimal script:
import asyncio
from pathlib import Path
from cursor_agent import CursorAgent
async def main():
agent = CursorAgent(cwd=Path("."))
result = await agent.prompt("Summarize this repository")
print(result.text)
print(result.completed_tool_rounds)
await agent.shutdown()
asyncio.run(main())All extension points live in a single CursorAgentHooks dataclass — pass only
the hooks you need:
from cursor_agent import CursorAgent, CursorAgentHooks
hooks = CursorAgentHooks(
before_process_start=my_setup, # async (cwd: Path) -> None
wrap_command=my_sandbox_wrapper, # async (argv, cwd_str) -> str
on_opaque_tool_call=my_mcp_bridge, # async (conversation_id, on_event) -> None
)
agent = CursorAgent(cwd=Path("."), hooks=hooks)before_process_start— called with the working directory before the ACP subprocess spawns (e.g. write config files).wrap_command— transformsargv+ working-directory string into a shell command (e.g. sandbox wrapping). Defaults toshlex.join(argv).on_opaque_tool_call— invoked when the ACP stream reports a tool call the package cannot handle natively (title starts with"MCP:"). Receives(conversation_id, on_event)so the caller can bridge the call and emit real events.
MIT