This document tracks Cursor as a first-class TermAl agent via its ACP (Agent Client Protocol) mode.
Reference: agent-integration-comparison.md
Implemented in TermAl via the shared ACP adapter.
Cursor sessions now support live model discovery, session-scoped /model and
/mode controls, Prompt-tab settings, and standard approval handling through
the same ACP runtime plumbing that also serves Gemini.
Cursor CLI is now wired through session creation, runtime spawning, message dispatch, and frontend rendering. The open work is protocol coverage and UX polish, not basic agent support.
Cursor CLI’s agent acp subcommand exposes a JSON-RPC 2.0 over stdio server
that is structurally very close to the Codex app-server adapter TermAl already
implements. This means the existing Codex adapter can serve as a near-direct
template for the Cursor adapter.
Source: https://cursor.com/docs/cli/acp
| Property | Value |
|---|---|
| Transport | stdio (stdin/stdout) |
| Envelope | JSON-RPC 2.0 |
| Framing | Newline-delimited JSON (one message per line) |
| Logs | stderr (ignored by protocol) |
cursor agent acp
Authenticate before first use with one of:
cursor agent login # interactive browser login
cursor agent acp --api-key <key> # API key
cursor agent acp --auth-token <token> # auth token
# or environment variables:
CURSOR_API_KEY=...
CURSOR_AUTH_TOKEN=...
Client cursor agent acp
│ │
│──── initialize ──────────────────────────>│
│<─── initialize result ───────────────────│
│ │
│──── authenticate ────────────────────────>│
│<─── authenticate result ─────────────────│
│ │
│──── session/new ─────────────────────────>│
│<─── { sessionId } ──────────────────────│
│ │
│──── session/prompt ──────────────────────>│
│<─── session/update (notification) ───────│ (streaming, repeats)
│<─── session/request_permission ──────────│ (if tool needs approval)
│──── permission response ─────────────────>│
│<─── session/update (notification) ───────│ (streaming continues)
│<─── session/prompt result ───────────────│ { stopReason }
│ │
│──── session/prompt (next turn) ──────────>│
│ ... │
│ │
│──── session/cancel (optional) ───────────>│
initializeEstablishes protocol version and capabilities.
// Client → Server
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-01-01",
"clientInfo": { "name": "termal", "version": "0.1.0" },
"clientCapabilities": {
"fs": { "readTextFile": true, "writeTextFile": true },
"terminal": true
}
}
}
authenticate{
"jsonrpc": "2.0",
"id": 2,
"method": "authenticate",
"params": { "methodId": "cursor_login" }
}
session/newCreates a new conversation session.
{
"jsonrpc": "2.0",
"id": 3,
"method": "session/new",
"params": {
"cwd": "/projects/my-app",
"mcpServers": []
}
}
// Response: { "sessionId": "..." }
session/loadResumes an existing session by ID.
{
"jsonrpc": "2.0",
"id": 3,
"method": "session/load",
"params": { "sessionId": "<previous-session-id>" }
}
session/promptSends a user message. Returns when the turn completes.
{
"jsonrpc": "2.0",
"id": 4,
"method": "session/prompt",
"params": {
"sessionId": "<session-id>",
"prompt": [{ "type": "text", "text": "Fix the auth middleware" }]
}
}
// Response: { "stopReason": "end_turn" }
session/cancelInterrupts the current turn. This is a JSON-RPC notification, so it has no
request id and Cursor does not send a response.
{
"jsonrpc": "2.0",
"method": "session/cancel",
"params": { "sessionId": "<session-id>" }
}
session/update notificationsWhile a prompt is being processed, the server emits JSON-RPC notifications
(no id field):
{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"sessionUpdate": "agent_message_chunk",
"content": { "text": "I'll investigate the auth..." }
}
}
session/request_permissionWhen a tool requires approval, the server sends a JSON-RPC request:
// Server → Client
{
"jsonrpc": "2.0",
"id": 100,
"method": "session/request_permission",
"params": {
"toolName": "edit_file",
"description": "Edit src/auth.ts",
"options": ["allow-once", "allow-always", "reject-once"]
}
}
// Client → Server
{
"jsonrpc": "2.0",
"id": 100,
"result": {
"outcome": { "outcome": "selected", "optionId": "allow-once" }
}
}
Cursor advertises additional notification methods for richer UX:
| Method | Purpose |
|---|---|
cursor/ask_question |
Multiple-choice prompts to the user |
cursor/create_plan |
Plan approval flow |
cursor/update_todos |
Progress/task notifications |
cursor/task |
Sub-agent completion events |
cursor/generate_image |
Image output notifications |
Cursor supports three modes that can be selected at session creation:
| Mode | Description |
|---|---|
agent |
Full tool access — reads, writes, commands |
plan |
Read-only planning — proposes changes without executing |
ask |
Q&A only — explores code, answers questions |
| Cursor concept | TermAl equivalent | Notes |
|---|---|---|
session/update agent_message_chunk |
TextDeltaEvent |
Streaming text into chat bubble |
session/request_permission |
ApprovalMessage |
Maps to approve/reject/approve-for-session |
session/cancel |
Turn interrupt | Same as Claude’s control_request interrupt |
session/load |
Session resume | Like --resume for Claude, thread/resume for Codex |
cursor/update_todos |
Could map to a new message type | Optional enhancement |
| Modes (agent/plan/ask) | New Cursor-specific session setting | Surface in session creation UI |
Add a Cursor variant to each of these enums in src/main.rs:
Agent — Cursor (with name() → "Cursor", avatar() → "CR")SessionRuntime — Cursor(CursorRuntimeHandle)RuntimeToken — Cursor(String)KillableRuntime — Cursor(CursorRuntimeHandle)TurnDispatch — PersistentCursor { command, sender, session_id }CursorRuntimeHandle — runtime_id, input_tx: Sender<CursorRuntimeCommand>, processCursorRuntimeCommand — Prompt(CursorPromptCommand) |
ApprovalResponse(…) |
Cancel |
CursorPromptCommand — prompt: String, mode: CursorMode, session_id: String, attachmentsCursorPendingApproval — request_id, tool_name, description, optionsCursorMode — Agent |
Plan |
Ask |
resolve_cursor_executable() — find_command_on_path("cursor")spawn_cursor_runtime() — spawn cursor agent acp, set up stdin/stdout/stderr
pipes, run writer/reader/stderr/wait threads (follow spawn_codex_runtime pattern)cursor_initialize_handshake() — send initialize + authenticate, await responsescursor_create_session() — send session/new or session/loadhandle_cursor_acp_message() — parse incoming JSON-RPC messages, dispatch:
session/update → map to TextDeltaEvent / message cardssession/request_permission → map to ApprovalMessagecursor/* notifications → map to appropriate message typessend_cursor_json_rpc() — write JSON-RPC message to stdin, manage pending request mapdeliver_turn_dispatch() — add PersistentCursor armdispatch_turn() / start_turn_on_record() — handle Agent::Cursorupdate_session_settings() — handle Cursor mode selection"cursor" in Agent::from_strAgentType in types.ts: "Claude" | "Codex" | "Cursor"allow-once → Approve, allow-always → Approve for session,
reject-once → Reject)session/loadsession/cancelsession/update payload shapes — need to test against a live
cursor agent acp process to catalogue all sessionUpdate variants beyond
agent_message_chunk (file diffs, command executions, thinking, etc.).session/prompt accept image content blocks?mcpServers param, or let Cursor use its own .cursor/mcp.json?& prefix.
Should TermAl surface this capability?