Agent Client Protocol (ACP) avec Cursor

L'Agent Client Protocol (ACP) est un standard ouvert qui permet à un client d'éditeur et à un agent de codage de communiquer via une interface partagée basée sur JSON-RPC. Au lieu que chaque éditeur construise son propre pont vers chaque agent, le client et l'agent parlent tous deux ACP, et l'appariement devient interchangeable. Cursor peut jouer le rôle de l'agent dans cette conversation via cursor-agent, ce qui signifie que tout client compatible ACP peut piloter l'agent de Cursor. Ce guide explique le protocole, le cycle de vie de session, comment exposer Cursor via ACP, le flux de messages en pratique, comment créer votre propre client, et où se situent les lacunes actuelles.
Qu'est-ce que l'Agent Client Protocol
ACP est un standard ouvert qui définit comment un client d'éditeur communique avec un agent de codage. Le protocole a été popularisé par Zed, qui l'utilise pour permettre à son éditeur de piloter différents agents, mais il est délibérément agnostique vis-à-vis de l'éditeur et de l'agent.
Les choix de conception fondamentaux sont simples :
- Le transport est JSON-RPC sur stdio. Le client lance l'agent comme sous-processus et échange des messages JSON-RPC délimités par des retours à la ligne sur stdin et stdout.
- Chacun des deux côtés peut envoyer des requêtes et des notifications. Le client demande à l'agent de démarrer une session et d'envoyer des prompts ; l'agent demande au client la permission d'exécuter des outils et transmet la progression en continu.
- Les sessions sont explicites. Le travail se déroule à l'intérieur d'une session créée avec
newSession, qui porte un répertoire de travail et des options.
Parce que le transport est du stdio simple et que les messages sont du JSON simple, ACP fonctionne partout où un processus peut être lancé : éditeurs locaux, outils de terminal et exécuteurs CI sans interface.
MCP connecte un agent à des outils et des sources de données externes. ACP connecte un client d'éditeur à un agent. Ils résolvent des problèmes différents et sont souvent utilisés ensemble : un agent ACP peut lui-même être un client MCP.
Architecture ACP
L'architecture comporte trois parties : le client, l'agent et le transport entre eux. Le client possède l'interface et les permissions ; l'agent possède le modèle et les outils.
| Couche | Rôle | Exemples |
|---|---|---|
| Client | Affiche la conversation, recueille la saisie de l'utilisateur, répond aux demandes de permission | Zed, un éditeur personnalisé, un script sans interface |
| Agent | Exécute le modèle, exécute les outils, diffuse les mises à jour | cursor-agent acp, autres agents ACP |
| Transport | Déplace les messages JSON-RPC dans les deux sens | JSON délimité par des retours à la ligne sur stdio |
Le cycle de vie de session est une séquence fixe. Une fois que vous le comprenez, le flux de messages se lit comme un script.
initialize -> client and agent agree on protocol version and capabilities
newSession -> client asks the agent to create a session with a cwd and options
prompt -> client sends user input into the session
session/update -> agent streams progress, tool calls, and messages back (notification)
permission request-> agent asks the client to approve a tool action
cancel -> client aborts the current turn
L'agent n'agit jamais sur une ressource protégée sans d'abord demander au client. Ce aller-retour de permission est ce qui maintient une interface aux commandes d'un agent autonome.
Pourquoi ACP compte
ACP compte parce qu'il découple l'éditeur de l'agent. La valeur se manifeste à trois endroits.
Portabilité de l'éditeur
Si Cursor et un autre client parlent tous deux ACP, vous pouvez utiliser le même agent dans l'un ou l'autre sans intégration sur mesure. Votre configuration d'agent, votre choix de modèle et vos règles voyagent avec l'agent, pas avec l'éditeur.
Réutiliser l'agent de Cursor dans n'importe quel client compatible
Plutôt que de réimplémenter le comportement de l'agent dans chaque éditeur, vous pouvez pointer n'importe quel client ACP vers cursor-agent et obtenir le raisonnement, l'utilisation d'outils et la sélection de modèles de Cursor dans l'interface de ce client. C'est particulièrement utile pour les équipes standardisées sur un éditeur qui n'est pas Cursor mais qui veulent le comportement d'agent de Cursor.
Automatisation sans interface
Parce que le transport est du stdio, vous pouvez piloter l'agent depuis un script ou une tâche CI sans aucun éditeur. Cela rend possibles des exécutions d'agent scriptées et reproductibles dans des pipelines.
# A headless run is just a client that writes JSON-RPC to the agent's stdin
cat initialize.json newSession.json prompt.json | cursor-agent acp
Lorsque vous pilotez l'agent depuis un script, il n'y a pas d'humain pour cliquer sur « approuver » un appel d'outil. Décidez de votre mode de permission à l'avance, et ne passez jamais par défaut à l'exécution automatique dans un environnement avec des identifiants sensibles.
Exposer Cursor via ACP avec cursor-agent
cursor-agent est la CLI qui peut jouer le rôle du côté agent ACP de la connexion. Installez-la de la même manière que vous installez la CLI Cursor.
# Install via npm
npm install -g cursor-agent
# Or install via the Cursor CLI script
curl https://cursor.com/install -fsS | bash
# Confirm it is on your PATH
cursor-agent --version
Exécuter en mode ACP
Le point d'entrée ACP est une sous-commande. Lorsque vous l'exécutez, le processus parle JSON-RPC sur stdio et attend qu'un client se connecte.
# Start the agent as an ACP server, talking JSON-RPC over stdio
cursor-agent acp
# Useful flags
cursor-agent acp --cwd /path/to/project # set the working directory
cursor-agent acp --model claude-sonnet-4-5 # pin a model for the session
cursor-agent acp --verbose # log messages for debugging
Authentification
L'agent a besoin d'identifiants avant de pouvoir servir des prompts. Connectez-vous une fois et les identifiants sont stockés localement.
# Interactive login, opens a browser flow
cursor-agent login
# Check current auth status
cursor-agent status
# Log out and clear stored credentials
cursor-agent logout
Si l'agent démarre mais que chaque prompt échoue, la cause est presque toujours une authentification manquante ou expirée. Exécutez cursor-agent status avant de déboguer quoi que ce soit d'autre.
Configurer une session
Une session est créée avec un répertoire de travail et des options qui régissent le comportement de l'agent pour cette conversation.
Sélection du modèle
Le modèle peut être épinglé au lancement ou choisi par session. Le passer par session permet à un seul processus d'agent de servir différents clients avec différentes préférences de modèle.
{
"method": "session/new",
"params": {
"cwd": "/home/dev/project",
"model": "claude-sonnet-4-5",
"mcpServers": []
}
}
Rapport d'utilisation de la fenêtre de contexte
Certains clients affichent à quel point la fenêtre de contexte est remplie. L'agent rapporte l'utilisation des tokens dans le cadre des mises à jour de session là où le client le prend en charge.
{
"method": "session/update",
"params": {
"sessionId": "sess_01HXYZ",
"update": {
"type": "usage",
"inputTokens": 12480,
"outputTokens": 2310,
"contextLimit": 200000
}
}
}
Modes de permission et répertoire de travail
Les modes de permission décident si l'agent demande avant d'exécuter un outil. Dans un client interactif, le défaut est de demander. Dans l'automatisation sans interface, vous choisissez délibérément.
- Ask-per-tool : l'agent envoie une demande de permission et attend. Le plus sûr, idéal pour les éditeurs interactifs.
- Auto-run : l'agent exécute à l'intérieur du répertoire de travail sans demander. Rapide, et approprié seulement dans un bac à sable.
- Read-only : l'agent peut lire et chercher mais jamais écrire. Bon pour les sessions de révision uniquement.
Le cwd dans newSession délimite ce que l'agent traite comme le projet. Les fichiers en dehors ne devraient pas être touchés.
Exemple de flux de messages JSON-RPC
Ce qui suit est une conversation minimale mais complète. Chaque message est une ligne de JSON sur le fil.
Initialize
Le client ouvre avec initialize, et l'agent répond avec ses capacités et sa version de protocole.
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": {
"fs": { "readTextFile": true, "writeTextFile": true },
"terminal": true
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": 1,
"agentCapabilities": {
"promptCapabilities": { "image": false },
"loadSession": true
}
}
}
New Session
Une fois les capacités convenues, le client crée une session et obtient en retour un ID.
{
"jsonrpc": "2.0",
"id": 2,
"method": "session/new",
"params": { "cwd": "/home/dev/project", "mcpServers": [] }
}
{
"jsonrpc": "2.0",
"id": 2,
"result": { "sessionId": "sess_01HXYZ" }
}
Prompt
Le client envoie le tour de l'utilisateur. Notez qu'il s'agit d'une requête ; l'agent l'accuse réception, mais le contenu réel arrive sous forme de notifications en streaming.
{
"jsonrpc": "2.0",
"id": 3,
"method": "session/prompt",
"params": {
"sessionId": "sess_01HXYZ",
"content": [{ "type": "text", "text": "Add input validation to the signup route." }]
}
}
Mises à jour en streaming
Pendant que l'agent travaille, il émet des notifications session/update sans ID, car elles sont unidirectionnelles.
{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"sessionId": "sess_01HXYZ",
"update": { "type": "agent_message_chunk", "text": "Reading src/routes/signup.ts" }
}
}
Demande de permission
Avant une action protégée, l'agent demande au client. Le client répond via le mécanisme de requête JSON-RPC standard.
{
"jsonrpc": "2.0",
"id": "perm-1",
"method": "session/request_permission",
"params": {
"sessionId": "sess_01HXYZ",
"toolCall": {
"title": "Write file",
"path": "src/routes/signup.ts"
}
}
}
{
"jsonrpc": "2.0",
"id": "perm-1",
"result": { "outcome": { "outcome": "selected", "optionId": "allow" } }
}
Cancel
Pour interrompre un tour, le client envoie session/cancel. L'agent arrête le travail en cours et termine la requête de prompt avec une raison d'arrêt d'annulation.
{
"jsonrpc": "2.0",
"method": "session/cancel",
"params": { "sessionId": "sess_01HXYZ" }
}
Créer votre propre client ACP
Un client minimal lance l'agent, écrit des lignes JSON-RPC sur stdin et analyse le JSON délimité par des retours à la ligne depuis stdout. L'extrait ci-dessous montre la forme en TypeScript.
import { spawn } from "node:child_process";
import { createInterface } from "node:readline";
const agent = spawn("cursor-agent", ["acp", "--cwd", process.cwd()], {
stdio: ["pipe", "pipe", "inherit"],
});
let nextId = 1;
function send(method: string, params: unknown): void {
const message = { jsonrpc: "2.0", id: nextId++, method, params };
agent.stdin.write(JSON.stringify(message) + "\n");
}
// Read newline-delimited JSON-RPC from the agent
const reader = createInterface({ input: agent.stdout });
reader.on("line", (line) => {
if (!line.trim()) return;
const msg = JSON.parse(line);
// Notifications have no id
if (!("id" in msg)) {
if (msg.method === "session/update") {
console.log("update:", JSON.stringify(msg.params.update));
}
return;
}
// A request from the agent needs a reply
if (msg.method === "session/request_permission") {
const reply = {
jsonrpc: "2.0",
id: msg.id,
result: { outcome: { outcome: "selected", optionId: "allow" } },
};
agent.stdin.write(JSON.stringify(reply) + "\n");
return;
}
console.log("response:", JSON.stringify(msg.result ?? msg.error));
});
// Kick off the session
send("initialize", { protocolVersion: 1, clientCapabilities: {} });
send("session/new", { cwd: process.cwd(), mcpServers: [] });
Les règles clés à retenir : chaque requête a un id et attend une réponse correspondante, les notifications n'ont jamais d'id, et l'agent peut aussi vous envoyer des requêtes, donc votre lecteur doit gérer les deux directions.
Quand une intégration client se comporte mal, journalisez chaque ligne dans les deux sens avant de toucher à la logique. Les bugs ACP sont généralement un id manquant, un retour à la ligne perdu ou une réponse jamais envoyée, et le fil brut rend les trois évidents.
Limites et lacunes actuelles
ACP est jeune, et toutes les capacités de Cursor ne sont pas exposées à travers lui. Sachez à quoi vous renoncez avant de construire dessus.
Pas de personnalisation du prompt système
Les options de session ACP ne permettent pas actuellement à un client de remplacer ou d'étendre le prompt système de l'agent comme le fait .cursor/rules dans l'IDE. Les règles configurées dans le projet sont toujours appliquées par l'agent, mais le client ne peut pas injecter les siennes.
Rapport incomplet de la fenêtre de contexte
Le rapport d'utilisation existe mais n'est pas garanti complet ou en temps réel. Un client qui veut un compteur de contexte exact ne devrait pas se fier uniquement aux mises à jour d'utilisation ACP.
Différences de parité des fonctionnalités vs l'IDE
Plusieurs fonctionnalités de l'IDE sont indisponibles ou réduites via ACP :
- Pas de révision visuelle du diff des modifications proposées dans le client, sauf si le client l'implémente.
- Les agents en arrière-plan et les agents cloud ne sont pas pilotés par cette voie.
- Certains comportements d'outils et de MCP diffèrent du mode agent de l'IDE.
- La complétion Tab est un système séparé et ne fait pas du tout partie d'ACP.
Dépannage
Agent introuvable
Si le client lance cursor-agent et obtient ENOENT, le binaire n'est pas dans le PATH du processus qui effectue le lancement. Utilisez un chemin absolu dans l'appel de lancement, et rappelez-vous qu'un client GUI peut hériter d'un PATH différent de celui de votre terminal.
# Find the absolute path and use it in the client config
which cursor-agent
Erreurs d'authentification
Chaque prompt qui échoue avec une erreur d'authentification signifie que l'agent n'a pas d'identifiants valides. Exécutez cursor-agent status, puis cursor-agent login, et vérifiez que le client ne s'exécute pas sous un utilisateur ou un répertoire personnel différent de celui avec lequel vous vous êtes connecté.
Aucune mise à jour en streaming
Si la requête de prompt revient mais que les mises à jour n'arrivent jamais, le client n'analyse probablement pas correctement les notifications. Les notifications n'ont pas d'id, donc un lecteur qui ne gère que les réponses les abandonne. Confirmez que votre lecteur de lignes émet sur le retour à la ligne et que votre analyseur gère les messages sans id.
Le client ne reçoit jamais de demandes de permission
Si l'agent exécute des outils sans demander, la session a été créée avec un mode de permission auto-run, ou le client n'a jamais annoncé la capacité de gérer les demandes de permission lors de initialize. Vérifiez à la fois les options de session et les capacités que vous avez envoyées.
| Symptôme | Cause | Solution |
|---|---|---|
ENOENT au lancement | Binaire de l'agent absent du PATH | Utilisez un chemin absolu dans l'appel de lancement |
| Chaque prompt échoue | Authentification manquante ou expirée | Exécutez cursor-agent status puis cursor-agent login |
Aucun message session/update | Le lecteur ne gère que les réponses avec un id | Gérez les notifications sans id |
| Aucune demande de permission | Mode auto-run ou capacité client manquante | Réglez ask-per-tool et annoncez la capacité |
| Le prompt bloque indéfiniment | Réponse à une requête jamais envoyée | Répondez avec le id correspondant à chaque requête de l'agent |
| Erreurs d'analyse de réponse | Messages concaténés sans retours à la ligne | Écrivez et lisez exactement un objet JSON par ligne |
ACP transforme la relation éditeur-agent en contrat, et ce contrat est ce qui rend l'agent de Cursor utilisable depuis n'importe où qui parle le protocole. Commencez par le flux de messages ci-dessus, journalisez le fil, et l'intégration devient une petite quantité de plomberie plutôt qu'une réécriture.