doc/plans/2026-02-23-cursor-cloud-adapter.md
This document defines the V1 design for a Paperclip adapter that integrates with Cursor Background Agents via the Cursor REST API.
Primary references:
Unlike claude_local and codex_local, this adapter is not a local subprocess.
It is a remote orchestration adapter with:
Authorization: Bearer <CURSOR_API_KEY>.Base URL: https://api.cursor.com
Authentication header:
Authorization: Bearer <CURSOR_API_KEY>Core endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/v0/agents | POST | Launch agent |
/v0/agents/{id} | GET | Agent status |
/v0/agents/{id}/conversation | GET | Conversation history |
/v0/agents/{id}/followup | POST | Follow-up prompt |
/v0/agents/{id}/stop | POST | Stop/pause running agent |
/v0/models | GET | Recommended model list |
/v0/me | GET | API key metadata |
/v0/repositories | GET | Accessible repos (strictly rate-limited) |
Status handling policy for adapter:
CREATING and RUNNING as non-terminal.FINISHED as success terminal.ERROR as failure terminal.resultJson.Webhook facts relevant to V1:
statusChange webhooks.ERROR and FINISHED.X-Webhook-Signature: sha256=...).Operational limits:
/v0/repositories: 1 req/user/min, 30 req/user/hour.packages/adapters/cursor-cloud/
├── package.json
├── tsconfig.json
└── src/
├── index.ts
├── api.ts
├── server/
│ ├── index.ts
│ ├── execute.ts
│ ├── parse.ts
│ ├── test.ts
│ └── webhook.ts
├── ui/
│ ├── index.ts
│ ├── parse-stdout.ts
│ └── build-config.ts
└── cli/
├── index.ts
└── format-event.ts
package.json uses standard four exports (., ./server, ./ui, ./cli).
src/api.ts)src/api.ts is a typed wrapper over Cursor endpoints.
interface CursorClientConfig {
apiKey: string;
baseUrl?: string; // default https://api.cursor.com
}
interface CursorAgent {
id: string;
name: string;
status: "CREATING" | "RUNNING" | "FINISHED" | "ERROR" | string;
source: { repository: string; ref: string };
target: {
branchName?: string;
prUrl?: string;
url?: string;
autoCreatePr?: boolean;
openAsCursorGithubApp?: boolean;
skipReviewerRequest?: boolean;
};
summary?: string;
createdAt: string;
}
Client requirements:
Authorization: Bearer ... on all requestsCursorApiError with status, parsed body, and request contextsrc/index.ts)export const type = "cursor_cloud";
export const label = "Cursor Cloud Agent";
V1 config fields:
repository (required): GitHub repo URLref (optional, default main)model (optional, allow empty = auto)autoCreatePr (optional, default false)branchName (optional)promptTemplatepollIntervalSec (optional, default 10)timeoutSec (optional, default 0)graceSec (optional, default 20)paperclipPublicUrl (optional override; else PAPERCLIP_PUBLIC_URL env)enableWebhooks (optional, default true)env.CURSOR_API_KEY (required, secret_ref preferred)env.CURSOR_WEBHOOK_SECRET (required if enableWebhooks=true, min 32)Important: do not store Cursor key in plain apiKey top-level field.
Use adapterConfig.env so secret references are supported by existing secret-resolution flow.
Cursor agents run remotely, so we cannot inject local env like PAPERCLIP_API_KEY.
The adapter must resolve a callback base URL in this order:
adapterConfig.paperclipPublicUrlprocess.env.PAPERCLIP_PUBLIC_URLIf empty, fail testEnvironment and runtime execution with a clear error.
Goal: avoid putting long-lived Paperclip credentials in prompt text.
Flow:
agentIdcompanyIdrunIdpaperclipPublicUrlPOST /api/agent-auth/exchangeThis keeps long-lived keys out of prompt and supports clean revocation by TTL.
Do not inline full SKILL.md content into the prompt.
Instead:
GET /api/skills/indexGET /api/skills/paperclipGET /api/skills/paperclip-create-agent when neededBenefits:
src/server/execute.ts)asString/asBoolean/asNumber/parseObjectenv.CURSOR_API_KEYpaperclipPublicUrlSession identity is Cursor agentId (stored in sessionParams).
Reuse only when repository matches.
Render template as usual, then append a compact callback block:
POST /followupPOST /agentsurl: <paperclipPublicUrl>/api/adapters/cursor-cloud/webhookssecret: CURSOR_WEBHOOK_SECRETUse hybrid strategy:
/conversation)Emit synthetic events to stdout (init, status, assistant, user, result).
Completion logic:
status === FINISHEDstatus === ERROR or unknown terminalAdapterExecutionResult:
exitCode: 0 on success, 1 on terminal failureerrorMessage populated on failure/timeoutsessionParams: { agentId, repository }provider: "cursor"usage and costUsd: unavailable/nullresultJson: include raw status/target/conversation snapshotAlso ensure result event is emitted to stdout before return.
src/server/webhook.ts + server route)Add a server endpoint to receive Cursor webhook deliveries.
Responsibilities:
X-Webhook-Signature.X-Webhook-ID.statusChange).agentId to active Paperclip run context.heartbeat_run_events entries for audit/debug.Security:
401)400)2xx)src/server/test.ts)Checks:
CURSOR_API_KEY presentGET /v0/me/v0/modelspaperclipPublicUrl present and reachable shape-validRepository-access verification via /v0/repositories should be optional due strict rate limits.
Use a warning-level check only when an explicit verifyRepositoryAccess option is set.
src/ui/parse-stdout.ts)Handle event types:
initstatusassistantuserresultstdoutOn failure results, set isError=true and include error text.
src/ui/build-config.ts)CreateConfigValues.url -> repositoryplain/secret_ref)pollIntervalSec, timeoutSec, graceSec, enableWebhooks)ui/src/adapters/cursor-cloud/config-fields.tsx)Add controls for:
CURSOR_API_KEY and CURSOR_WEBHOOK_SECRETsrc/cli/format-event.ts)Format synthetic events similarly to local adapters. Highlight terminal failures clearly.
server/src/adapters/registry.tsui/src/adapters/registry.tscli/src/adapters/registry.tscursor_cloud to packages/shared/src/constants.ts (AGENT_ADAPTER_TYPES)packages/shared/src/validators/agent.ts)ui/src/components/agent-config-primitives.tsxui/src/components/AgentProperties.tsxui/src/pages/Agents.tsxui/src/components/OnboardingWizard.tsx)Without these updates, create/edit flows will reject the new adapter even if package code exists.
Long-polling HTTP adapters must support run cancellation.
V1 requirement:
cancelRun should invoke that handler (abort fetch/poll loop + optional Cursor stop call)Current process-only cancellation maps are insufficient by themselves for Cursor.
claude_local| Aspect | claude_local | cursor_cloud |
|---|---|---|
| Execution model | local subprocess | remote API |
| Updates | stream-json stdout | webhook + polling + synthesized stdout |
| Session id | Claude session id | Cursor agent id |
| Skill delivery | local skill dir injection | authenticated fetch from Paperclip skill endpoints |
| Paperclip auth | injected local run JWT env var | bootstrap token exchange -> run JWT |
| Cancellation | OS signals | abort polling + Cursor stop endpoint |
| Usage/cost | rich | not exposed by Cursor API |
user_message/assistant_message).packages/adapters/cursor-cloud/package.json exports wiredpackages/adapters/cursor-cloud/tsconfig.jsonsrc/index.ts metadata + configuration docsrc/api.ts bearer-auth client + typed errorssrc/server/execute.ts hybrid webhook/poll orchestrationsrc/server/parse.ts stream parser + not-found detectionsrc/server/test.ts env diagnosticssrc/server/webhook.ts signature verification + payload helperssrc/server/index.ts exports + session codecsrc/ui/parse-stdout.tssrc/ui/build-config.tssrc/ui/index.tssrc/cli/format-event.tssrc/cli/index.tscursor_cloud to shared adapter constants/validators/api/adapters/cursor-cloud/webhooks)/api/agent-auth/exchange)/api/skills/index, /api/skills/:name)FINISHED, ERROR, unknown terminal)pnpm -r typecheckpnpm test:runpnpm build