Overview

Cursor CLI supports ACP (Agent Client Protocol) for advanced integrations. You can run agent acp and connect a custom client over stdio using JSON-RPC.

Learn more in the official Agent Client Protocol docs.

ACP is intended for building custom clients and integrations. For normal terminal
workflows, use the interactive CLI with agent.

Start ACP server

Start Cursor CLI in ACP mode:

agent acp

Transport and message format

  • Transport: stdio
  • Protocol envelope: JSON-RPC 2.0
  • Framing: newline-delimited JSON (one message per line)
  • Direction:
  • Client writes requests/notifications to stdin
  • Cursor CLI writes responses/notifications to stdout
  • Logs may be written to stderr

Request flow

Typical ACP session flow:

  1. initialize
  2. authenticate with methodId: "cursor_login"
  3. session/new (or session/load)
  4. session/prompt
  5. Handle session/update notifications while the model streams output
  6. Handle session/request_permission by returning a decision
  7. Optionally send session/cancel

Authentication

Cursor CLI advertises cursor_login as the ACP auth method. In practice, you can pre-authenticate before startup using existing CLI auth paths:

  • agent login
  • --api-key (or CURSOR_API_KEY)
  • --auth-token (or CURSOR_AUTH_TOKEN)

You can also pass endpoint and TLS options from the root CLI command:

agent --api-key "$CURSOR_API_KEY" acp
agent -e https://api2.cursor.sh acp
agent -k acp

Sessions, modes, and permissions

Sessions

  • Create a session with session/new
  • Resume an existing conversation with session/load

Modes

ACP sessions support the same core modes as CLI:

  • agent (full tool access)
  • plan (planning, read-only behavior)
  • ask (Q\&A/read-only behavior)

Permissions

When tools need approval, Cursor sends session/request_permission. Clients should return one of:

  • allow-once
  • allow-always
  • reject-once

If your client does not answer permission requests, tool execution can block.

MCP servers

ACP supports MCP servers defined in a project-level or user-level .cursor/mcp.json. Launch agent from your project directory and approve the servers you want to use.

Team-level MCP servers configured through the Cursor dashboard are not supported in ACP mode.

Cursor extension methods

Cursor sends ACP extension methods for richer client UX. There are two types:

  • Blocking methods (cursor/ask_question, cursor/create_plan): The agent waits for a response before continuing. Your client must reply with a JSON-RPC response.
  • Notification methods (cursor/update_todos, cursor/task, cursor/generate_image): The agent sends these as fire-and-forget notifications. Your client can display them but doesn’t need to respond.
Method Type Use
cursor/ask_question Blocking Ask users multiple-choice questions
cursor/create_plan Blocking Request explicit plan approval
cursor/update_todos Notification Notify client about todo state updates
cursor/task Notification Notify client about subagent task completion
cursor/generate_image Notification Notify client about generated image output

cursor/ask_question

Present multiple-choice questions to the user. The agent blocks until the client responds.

Request:

interface CursorAskQuestionRequest {
  toolCallId: string;
  title?: string;
  questions: Array<{
    id: string;
    prompt: string;
    options: Array<{ id: string; label: string }>;
    allowMultiple?: boolean;
  }>;
}

Response:

interface CursorAskQuestionResponse {
  outcome:
    | {
        outcome: "answered";
        answers: Array<{
          questionId: string;
          selectedOptionIds: string[];
        }>;
      }
    | { outcome: "skipped"; reason?: string }
    | { outcome: "cancelled" };
}

Example request:

{
  "toolCallId": "call_123",
  "title": "Need input",
  "questions": [
    {
      "id": "q1",
      "prompt": "Which mode should I use?",
      "options": [
        { "id": "agent", "label": "Agent" },
        { "id": "plan", "label": "Plan" }
      ],
      "allowMultiple": false
    }
  ]
}

cursor/create_plan

Request plan approval from the user. The agent blocks until the client accepts or rejects the plan.

Request:

interface CursorCreatePlanRequest {
  toolCallId: string;
  name?: string;
  overview?: string;
  plan: string;
  todos: Array<{
    id: string;
    content: string;
    status: "pending" | "in_progress" | "completed" | "cancelled";
  }>;
  isProject?: boolean;
  phases?: Array<{
    name: string;
    todos: Array<{
      id: string;
      content: string;
      status: "pending" | "in_progress" | "completed" | "cancelled";
    }>;
  }>;
}
  • plan: A markdown string describing the full plan.
  • phases: Optional grouping of todos into named phases for larger plans.

Response:

interface CursorCreatePlanResponse {
  outcome:
    | { outcome: "accepted"; planUri?: string }
    | { outcome: "rejected"; reason?: string }
    | { outcome: "cancelled" };
}

Example request:

{
  "toolCallId": "call_124",
  "name": "Refactor tabs layout",
  "overview": "Tighten layout behavior and preserve existing UX.",
  "plan": "1. Inspect current tab sizing logic.\n2. Update layout calculations.\n3. Verify editor behavior.",
  "todos": [
    { "id": "todo-1", "content": "Inspect current tab sizing logic", "status": "completed" },
    { "id": "todo-2", "content": "Update layout calculations", "status": "in_progress" },
    { "id": "todo-3", "content": "Verify editor behavior", "status": "pending" }
  ],
  "isProject": false
}

cursor/update_todos

Update the client’s todo list. Sent as a notification; no response required.

Request:

interface CursorUpdateTodosRequest {
  toolCallId: string;
  todos: Array<{
    id: string;
    content: string;
    status: "pending" | "in_progress" | "completed" | "cancelled";
  }>;
  merge: boolean;
}
  • merge: When true, merge these todos into the existing list. When false, replace the entire list.

Response:

interface CursorUpdateTodosResponse {
  outcome:
    | {
        outcome: "accepted";
        todos: Array<{
          id: string;
          content: string;
          status: "pending" | "in_progress" | "completed" | "cancelled";
        }>;
      }
    | { outcome: "rejected"; reason?: string }
    | { outcome: "cancelled" };
}

Example request:

{
  "toolCallId": "call_125",
  "todos": [
    { "id": "1", "content": "Set up project structure", "status": "completed" },
    { "id": "2", "content": "Add authentication", "status": "in_progress" },
    { "id": "3", "content": "Write unit tests", "status": "pending" }
  ],
  "merge": true
}

cursor/task

Notify the client about a subagent task. Sent as a notification; no response required.

Request:

interface CursorTaskRequest {
  toolCallId: string;
  description: string;
  prompt: string;
  subagentType:
    | "unspecified"
    | "computer_use"
    | "explore"
    | "video_review"
    | "browser_use"
    | "shell"
    | "vm_setup_helper"
    | { custom: string };
  model?: string;
  agentId?: string;
  durationMs?: number;
}
  • subagentType: The type of subagent to run. Use { custom: "your_type" } for custom subagent types.
  • agentId: Set this to resume a previously created subagent.
  • durationMs: How long the task ran, included in the response.

Response:

interface CursorTaskResponse {
  outcome:
    | { outcome: "completed"; agentId?: string; durationMs?: number }
    | { outcome: "rejected"; reason?: string }
    | { outcome: "cancelled" };
}

Example request:

{
  "toolCallId": "call_126",
  "description": "Explore codebase",
  "prompt": "Find where authentication is handled and report the file paths.",
  "subagentType": "explore"
}

cursor/generate_image

Notify the client about a generated image. Sent as a notification; no response required.

Request:

interface CursorGenerateImageRequest {
  toolCallId: string;
  description: string;
  filePath?: string;
  referenceImagePaths?: string[];
}
  • filePath: Suggested file path for the generated image.
  • referenceImagePaths: Paths to reference images used as input.

Response:

interface CursorGenerateImageResponse {
  outcome:
    | { outcome: "generated"; filePath: string; imageData?: string }
    | { outcome: "rejected"; reason?: string }
    | { outcome: "cancelled" };
}

Example request:

{
  "toolCallId": "call_127",
  "description": "Minimal flat app icon for a note-taking app",
  "filePath": "/tmp/icon.png",
  "referenceImagePaths": ["/tmp/reference.png"]
}

Minimal Node.js client

This example shows the minimum control flow for a custom ACP client:

import { spawn } from "node:child_process";
import readline from "node:readline";

const agent = spawn("agent", ["acp"], { stdio: ["pipe", "pipe", "inherit"] });

let nextId = 1;
const pending = new Map();

function send(method, params) {
  const id = nextId++;
  agent.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
  return new Promise((resolve, reject) => pending.set(id, { resolve, reject }));
}

function respond(id, result) {
  agent.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
}

const rl = readline.createInterface({ input: agent.stdout });
rl.on("line", line => {
  const msg = JSON.parse(line);

  if (msg.id && (msg.result || msg.error)) {
    const waiter = pending.get(msg.id);
    if (!waiter) return;
    pending.delete(msg.id);
    msg.error ? waiter.reject(msg.error) : waiter.resolve(msg.result);
    return;
  }

  if (msg.method === "session/update") {
    const update = msg.params?.update;
    if (update?.sessionUpdate === "agent_message_chunk" && update.content?.text) {
      process.stdout.write(update.content.text);
    }
    return;
  }

  if (msg.method === "session/request_permission") {
    respond(msg.id, { outcome: { outcome: "selected", optionId: "allow-once" } });
  }
});

const init = async () => {
  await send("initialize", {
    protocolVersion: 1,
    clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
    clientInfo: { name: "acp-minimal-client", version: "0.1.0" }
  });

  await send("authenticate", { methodId: "cursor_login" });
  const { sessionId } = await send("session/new", { cwd: process.cwd(), mcpServers: [] });
  const result = await send("session/prompt", {
    sessionId,
    prompt: [{ type: "text", text: "Say hello in one sentence." }]
  });

  console.log(`\n\n[stopReason=${result.stopReason}]`);
};

init().finally(() => {
  agent.stdin.end();
  agent.kill();
});

IDE integrations

ACP enables Cursor’s AI agent to work with editors beyond the Cursor desktop app. Build or use third-party integrations for your preferred development environment.

Example use cases

  • JetBrains IDEs — Connect IntelliJ IDEA, WebStorm, PyCharm, or other JetBrains IDEs to Cursor’s agent. See the JetBrains integration guide for setup instructions.

  • Neovim (avante.nvim) — Use avante.nvim to connect Neovim to Cursor’s agent through ACP. See Neovim setup below.

  • Zed — Integrate with Zed’s modern editor by spawning agent acp and communicating over stdio. Zed extensions can implement the ACP client protocol to route AI requests to Cursor.

  • Custom editors — Any editor with extension support can implement an ACP client. Spawn the agent process, send JSON-RPC messages over stdio, and handle responses in your editor’s UI.

Neovim (avante.nvim)

avante.nvim is a Neovim plugin that provides an AI-powered coding assistant. It supports ACP, so you can connect it to Cursor’s agent for agentic coding inside Neovim.

Add the following to your lazy.nvim plugin configuration (e.g., ~/.config/nvim/lua/plugins/avante.lua):

return {
  {
    "yetone/avante.nvim",
    event = "VeryLazy",
    version = false,
    build = "make",
    opts = {
      provider = "cursor",
      mode = "agentic",
      acp_providers = {
        cursor = {
          command = os.getenv("HOME") .. "/.local/bin/agent",
          args = { "acp" },
          auth_method = "cursor_login",
          env = {
            HOME = os.getenv("HOME"),
            PATH = os.getenv("PATH"),
          },
        },
      },
    },
    dependencies = {
      "nvim-lua/plenary.nvim",
      "MunifTanjim/nui.nvim",
      "nvim-tree/nvim-web-devicons",
      {
        "MeanderingProgrammer/render-markdown.nvim",
        opts = {
          file_types = { "markdown", "Avante" },
        },
        ft = { "markdown", "Avante" },
      },
    },
  },
}

Key settings:

  • provider: Set to "cursor" to route requests through Cursor’s agent.
  • mode: Set to "agentic" for full tool access (file edits, terminal commands). Use "normal" for chat-only mode.
  • command: Points to the agent binary. The default install path is ~/.local/bin/agent. Adjust if you installed it elsewhere.
  • auth_method: Uses "cursor_login". Run agent login in your terminal first to authenticate.

Building an integration

  1. Spawn agent acp as a child process
  2. Communicate over stdin/stdout using JSON-RPC
  3. Handle session/update notifications to display streaming responses
  4. Respond to session/request_permission when tools need approval
  5. Optionally implement Cursor extension methods for richer UX

See the minimal Node.js client above for a working reference implementation.

MCP in CLI

Manage and use MCP servers from Cursor CLI

MCP Overview

Learn MCP transports, configuration, and server setup