Add DEPLOYMENT-MODES.md with canonical mode taxonomy. Update CLI.md, DEVELOPING.md, PRODUCT.md, and SPEC-implementation.md with local_trusted/ authenticated nomenclature. Revise humans-and-permissions plan with Better Auth choice, bootstrap flow, unified invite semantics, and expanded criteria. Add implementation guide and additional plan documents for cursor cloud adapter and deployment auth mode consolidation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13 KiB
Cursor Cloud Agent Adapter — Technical Plan
Overview
This document defines the V1 design for a Paperclip adapter that integrates with Cursor Background Agents via the Cursor REST API.
Primary references:
- https://docs.cursor.com/background-agent/api/overview
- https://docs.cursor.com/background-agent/api
- https://docs.cursor.com/background-agent/api/webhooks
Unlike claude_local and codex_local, this adapter is not a local subprocess.
It is a remote orchestration adapter with:
- launch/follow-up over HTTP
- webhook-driven status updates when possible
- polling fallback for reliability
- synthesized stdout events for Paperclip UI/CLI
Key V1 Decisions
- Auth to Cursor API uses
Authorization: Bearer <CURSOR_API_KEY>. - Callback URL must be publicly reachable by Cursor VMs:
- local: Tailscale URL
- prod: public server URL
- Agent callback auth to Paperclip uses a bootstrap exchange flow (no long-lived Paperclip key in prompt).
- Webhooks are V1, polling remains fallback.
- Skill delivery is fetch-on-demand from Paperclip endpoints, not full SKILL.md prompt injection.
Cursor API Reference (Current)
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:
- Treat
CREATINGandRUNNINGas non-terminal. - Treat
FINISHEDas success terminal. - Treat
ERRORas failure terminal. - Treat unknown non-active statuses as terminal failure and preserve raw status in
resultJson.
Webhook facts relevant to V1:
- Cursor emits
statusChangewebhooks. - Terminal webhook statuses include
ERRORandFINISHED. - Webhook signatures use HMAC SHA256 (
X-Webhook-Signature: sha256=...).
Operational limits:
/v0/repositories: 1 req/user/min, 30 req/user/hour.- MCP not supported in Cursor background agents.
Package Structure
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).
API Client (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:
- send
Authorization: Bearer ...on all requests - throw typed
CursorApiErrorwithstatus, parsed body, and request context - preserve raw unknown fields for debugging in error metadata
Adapter Config Contract (src/index.ts)
export const type = "cursor_cloud";
export const label = "Cursor Cloud Agent";
V1 config fields:
repository(required): GitHub repo URLref(optional, defaultmain)model(optional, allow empty = auto)autoCreatePr(optional, defaultfalse)branchName(optional)promptTemplate/bootstrapPromptTemplatepollIntervalSec(optional, default10)timeoutSec(optional, default0)graceSec(optional, default20)paperclipPublicUrl(optional override; elsePAPERCLIP_PUBLIC_URLenv)enableWebhooks(optional, defaulttrue)env.CURSOR_API_KEY(required, secret_ref preferred)env.CURSOR_WEBHOOK_SECRET(required ifenableWebhooks=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.
Paperclip Callback + Auth Flow (V1)
Cursor agents run remotely, so we cannot inject local env like PAPERCLIP_API_KEY.
Public URL
The adapter must resolve a callback base URL in this order:
adapterConfig.paperclipPublicUrlprocess.env.PAPERCLIP_PUBLIC_URL
If empty, fail testEnvironment and runtime execution with a clear error.
Bootstrap Exchange
Goal: avoid putting long-lived Paperclip credentials in prompt text.
Flow:
- Before launch/follow-up, Paperclip mints a one-time bootstrap token bound to:
agentIdcompanyIdrunId- short TTL (for example 10 minutes)
- Adapter includes only:
paperclipPublicUrl- exchange endpoint path
- bootstrap token
- Cursor agent calls:
POST /api/agent-auth/exchange
- Paperclip validates bootstrap token and returns a run-scoped bearer JWT.
- Cursor agent uses returned bearer token for all Paperclip API calls.
This keeps long-lived keys out of prompt and supports clean revocation by TTL.
Skills Delivery Strategy (V1)
Do not inline full SKILL.md content into the prompt.
Instead:
- Prompt includes a compact instruction to fetch skills from Paperclip.
- After auth exchange, agent fetches:
GET /api/skills/indexGET /api/skills/paperclipGET /api/skills/paperclip-create-agentwhen needed
- Agent loads full skill content on demand.
Benefits:
- avoids prompt bloat
- keeps skill docs centrally updatable
- aligns with how local adapters expose skills as discoverable procedures
Execution Flow (src/server/execute.ts)
Step 1: Resolve Config and Secrets
- parse adapter config via
asString/asBoolean/asNumber/parseObject - resolve
env.CURSOR_API_KEY - resolve
paperclipPublicUrl - validate webhook secret when webhooks enabled
Step 2: Session Resolution
Session identity is Cursor agentId (stored in sessionParams).
Reuse only when repository matches.
Step 3: Render Prompt
Render template as usual, then append a compact callback block:
- public Paperclip URL
- bootstrap exchange endpoint
- bootstrap token
- skill index endpoint
- required run header behavior
Step 4: Launch/Follow-up
- on resume:
POST /followup - else:
POST /agents - include webhook object when enabled:
url: <paperclipPublicUrl>/api/adapters/cursor-cloud/webhookssecret: CURSOR_WEBHOOK_SECRET
Step 5: Progress + Completion
Use hybrid strategy:
- webhook events are primary status signal
- polling is fallback and transcript source (
/conversation)
Emit synthetic events to stdout (init, status, assistant, user, result).
Completion logic:
- success:
status === FINISHED - failure:
status === ERRORor unknown terminal - timeout: stop agent, mark timedOut
Step 6: Result Mapping
AdapterExecutionResult:
exitCode: 0on success,1on terminal failureerrorMessagepopulated on failure/timeoutsessionParams: { agentId, repository }provider: "cursor"usageandcostUsd: unavailable/nullresultJson: include raw status/target/conversation snapshot
Also ensure result event is emitted to stdout before return.
Webhook Handling (src/server/webhook.ts + server route)
Add a server endpoint to receive Cursor webhook deliveries.
Responsibilities:
- Verify HMAC signature from
X-Webhook-Signature. - Deduplicate by
X-Webhook-ID. - Validate event type (
statusChange). - Route by Cursor
agentIdto active Paperclip run context. - Append
heartbeat_run_eventsentries for audit/debug. - Update in-memory run signal so execute loop can short-circuit quickly.
Security:
- reject invalid signature (
401) - reject malformed payload (
400) - always return quickly after persistence (
2xx)
Environment Test (src/server/test.ts)
Checks:
CURSOR_API_KEYpresent- key validity via
GET /v0/me - repository configured and URL shape valid
- model exists (if set) via
/v0/models paperclipPublicUrlpresent and reachable shape-valid- webhook secret present/length-valid when webhooks enabled
Repository-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.
UI + CLI
UI parser (src/ui/parse-stdout.ts)
Handle event types:
initstatusassistantuserresult- fallback
stdout
On failure results, set isError=true and include error text.
Config builder (src/ui/build-config.ts)
- map
CreateConfigValues.url -> repository - preserve env binding shape (
plain/secret_ref) - include defaults (
pollIntervalSec,timeoutSec,graceSec,enableWebhooks)
Adapter fields (ui/src/adapters/cursor-cloud/config-fields.tsx)
Add controls for:
- repository
- ref
- model
- autoCreatePr
- branchName
- poll interval
- timeout/grace
- paperclip public URL override
- enable webhooks
- env bindings for
CURSOR_API_KEYandCURSOR_WEBHOOK_SECRET
CLI formatter (src/cli/format-event.ts)
Format synthetic events similarly to local adapters. Highlight terminal failures clearly.
Server Registration and Cross-Layer Contract Sync
Adapter registration
server/src/adapters/registry.tsui/src/adapters/registry.tscli/src/adapters/registry.ts
Shared contract updates (required)
- add
cursor_cloudtopackages/shared/src/constants.ts(AGENT_ADAPTER_TYPES) - ensure validators accept it (
packages/shared/src/validators/agent.ts) - update UI labels/maps where adapter names are enumerated, including:
ui/src/components/agent-config-primitives.tsxui/src/components/AgentProperties.tsxui/src/pages/Agents.tsx
- consider onboarding wizard support for adapter selection (
ui/src/components/OnboardingWizard.tsx)
Without these updates, create/edit flows will reject the new adapter even if package code exists.
Cancellation Semantics
Long-polling HTTP adapters must support run cancellation.
V1 requirement:
- register a cancellation handler per running adapter invocation
cancelRunshould invoke that handler (abort fetch/poll loop + optional Cursor stop call)
Current process-only cancellation maps are insufficient by themselves for Cursor.
Comparison with 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 |
V1 Limitations
- Cursor does not expose token/cost usage in API responses.
- Conversation stream is text-only (
user_message/assistant_message). - MCP/tool-call granularity is unavailable.
- Webhooks currently deliver status-change events, not full transcript deltas.
Future Enhancements
- Reduce polling frequency further when webhook reliability is high.
- Attach image payloads from Paperclip context.
- Add richer PR metadata surfacing in Paperclip UI.
- Add webhook replay UI for debugging.
Implementation Checklist
Adapter package
packages/adapters/cursor-cloud/package.jsonexports wiredpackages/adapters/cursor-cloud/tsconfig.jsonsrc/index.tsmetadata + configuration docsrc/api.tsbearer-auth client + typed errorssrc/server/execute.tshybrid webhook/poll orchestrationsrc/server/parse.tsstream parser + not-found detectionsrc/server/test.tsenv diagnosticssrc/server/webhook.tssignature verification + payload helperssrc/server/index.tsexports + session codecsrc/ui/parse-stdout.tssrc/ui/build-config.tssrc/ui/index.tssrc/cli/format-event.tssrc/cli/index.ts
App integration
- register adapter in server/ui/cli registries
- add
cursor_cloudto shared adapter constants/validators - add adapter labels in UI surfaces
- add Cursor webhook route on server (
/api/adapters/cursor-cloud/webhooks) - add auth exchange route (
/api/agent-auth/exchange) - add skill serving routes (
/api/skills/index,/api/skills/:name) - add generic cancellation hook for non-subprocess adapters
Tests
- api client auth/error mapping
- terminal status mapping (
FINISHED,ERROR, unknown terminal) - session codec round-trip
- config builder env binding handling
- webhook signature verification + dedupe
- bootstrap exchange happy path + expired/invalid token
Verification
pnpm -r typecheckpnpm test:runpnpm build