This guide describes how an adapter obtains, spawns, and talks to
cursor-sdk-bridge. The wire contract is defined by the protos in
proto/sdk/v1/; this document covers everything around
them: process lifecycle, discovery, and authentication.
The bridge is a local Connect server that wraps Cursor's TypeScript SDK
(@cursor/sdk). It runs as a child process of your adapter, binds a loopback
TCP port, and exposes the sdk.v1 services. Your adapter owns the process:
it spawns the bridge, performs the handshake, issues RPCs, and shuts it down.
Two authentication domains are involved, and they are independent:
- Bridge auth — a per-process bearer token, generated fresh on every launch, that protects the local RPC endpoint. Your adapter learns it during the handshake and must send it on every RPC.
- Cursor auth — the API key the bridge uses to reach Cursor's API.
Always set it explicitly on the request messages that accept one:
AgentOptions.api_keyonCreateAgent/ResumeAgent, andCursorRequestOptions.api_keyon everySdkCursorServicecatalog call (catalog calls hard-require it — seeservices.md). Also putCURSOR_API_KEYin the bridge's environment (some SDK paths read it), but do not rely on the env var alone: not every operation falls back to it on every bridge build — on some, a run on an agent created without an explicitapi_keyfails withInvalid User API Key. Setting the option is correct everywhere.
Prebuilt standalone archives are attached to every
release of this repository.
Download cursor-sdk-bridge-standalone-<os>-<arch>.tar.gz for your platform
from the latest release:
<os>—linux|darwin|win32<arch>—x64|arm64(win32 isx64only)
Release tags vX.Y.Z match the released SDK version — to pin one, download
from that tag's release page instead of latest. Each release also attaches
a SHA256SUMS.txt covering its archives.
The archive unpacks in place (no top-level directory):
bin/cursor-sdk-bridge # self-contained executable (cursor-sdk-bridge.exe on Windows)
proto/sdk/v1/ # the exact proto contract this bridge implements
manifest.json
manifest.json describes the archive:
{
"bridgeVersion": "1.0.0",
"sdkVersion": "1.0.26",
"os": "linux",
"arch": "x64",
"entrypoint": "bin/cursor-sdk-bridge",
"protocol": "sdk.v1",
"distribution": "standalone",
"runtime": "bun-1.3.9"
}Adapters should launch entrypoint and may assert protocol == "sdk.v1"
before doing so. The bridge is also embedded per-platform in the cursor-sdk
Python wheels on PyPI, so a machine with the Python SDK installed already has
a copy.
Spawn the executable with CURSOR_API_KEY in its environment and capture
stderr. The bridge prints exactly one discovery line to stderr once it is
listening — a literal prefix followed by a single JSON object:
cursor-sdk-bridge ready {"schemaVersion":1,"serverVersion":"1.0.0","pid":12345,"transport":"tcp","protocol":"connect","host":"127.0.0.1","port":49152,"url":"http://127.0.0.1:49152","authTokenFile":"/tmp/cursor-sdk-bridge-Xxxxxx/auth-token","workspaceRef":"/home/me/project","stateRoot":"/home/me/.cursor/sdk-agent-store/abc123"}
Parsing rules (all verified against the reference adapters):
-
Scan stderr line by line for the prefix
cursor-sdk-bridge ready(note the trailing space). Lines without the prefix are ordinary diagnostics — forward them to your logs but keep scanning. -
Everything after the prefix is JSON. Fields:
Field Meaning schemaVersionDiscovery payload schema. Reject anything other than 1.serverVersionBridge build version. pidBridge process ID. transportAlways "tcp". Reject other values.protocolAlways "connect". Reject other values.host,port,urlWhere to connect. Prefer url; fall back tohost+port(bracket IPv6 hosts).authTokenFilePath to the bearer-token file (see below). workspaceRefResolved workspace directory the bridge was launched for. stateRootDirectory for bridge-owned durable agent state. maxConcurrentAgents,maxMessageBytesAdvertised limits, present when configured. -
Apply a startup timeout (the reference adapters use 30 seconds). If the process exits before emitting the line, surface the captured stderr as the error message.
-
Treat unknown extra JSON fields as forward-compatible additions and ignore them.
authTokenFile points to a file (mode 0600, in a fresh temp directory)
containing a random base64url token. Read it, trim surrounding whitespace, and
send it as a header on every RPC:
Authorization: Bearer <token>
Connections without a valid token are rejected with the Connect/gRPC code
UNAUTHENTICATED. The token is per-process: a new one is generated each
launch, and it is never printed to stderr by current bridges. (Older bridges
may include an inline authToken field in the discovery JSON; if present,
prefer it, but never log the discovery line verbatim.)
The bridge serves the Connect protocol
and gRPC-Web over HTTP/1.1. Classic gRPC requires HTTP/2 and will not
work; use a Connect client (available for Go, Python via connect-python,
Kotlin, Swift, Node, ...) or plain HTTP POSTs — every RPC is
POST http://<host>:<port>/sdk.v1.<Service>/<Method> with a protobuf or JSON
body.
A good first RPC is SdkBridgeControlService.Ping, followed by GetVersion,
which reports the bridge build version, the protocol version ("sdk.v1"), and
a list of capability strings for feature negotiation.
The bridge binds 127.0.0.1 by default and refuses to bind non-loopback hosts
unless explicitly configured otherwise. Treat the endpoint as local-only.
Optionally set CURSOR_SDK_CLIENT_LANGUAGE=<language> in the bridge's
environment so Cursor can attribute traffic to your adapter's language (the
official adapters set go / python).
| Flag | Env var | Meaning |
|---|---|---|
--host <host> |
CURSOR_SDK_BRIDGE_HOST |
Host to bind (default 127.0.0.1). |
--port <port> |
CURSOR_SDK_BRIDGE_PORT |
Port to bind (default 0 = ephemeral; read the actual port from the ready line). |
--workspace <path> |
CURSOR_SDK_BRIDGE_WORKSPACE |
Workspace root; the default cwd for local agents and store discovery. |
--state-root <path> |
CURSOR_SDK_BRIDGE_STATE_ROOT |
Root directory for durable local agent state (defaults to a per-workspace directory under ~/.cursor/sdk-agent-store/). |
--local-store <json> |
CURSOR_SDK_LOCAL_STORE |
Default local agent store config, as LocalAgentStoreConfig JSON ({"type":"sqlite"}, {"type":"jsonl","rootDir":...}, or {"type":"custom"}). |
--store-callback-url <url> |
CURSOR_SDK_STORE_CALLBACK_URL |
Base URL of the adapter's SdkStoreCallbackService server (required for "custom" stores; must be set together with the token). |
--store-callback-auth-token <token> |
CURSOR_SDK_STORE_CALLBACK_AUTH_TOKEN |
Bearer token the bridge presents on store callbacks. |
--tool-callback-url <url> |
CURSOR_SDK_TOOL_CALLBACK_URL |
Base URL of the adapter's SdkCustomToolCallbackService server (must be set together with the token). |
--tool-callback-auth-token <token> |
CURSOR_SDK_TOOL_CALLBACK_AUTH_TOKEN |
Bearer token the bridge presents on tool callbacks. |
--max-concurrent-agents <count> |
— | Advertised agent concurrency limit. |
--max-message-bytes <bytes> |
— | Advertised max message size. |
--help, -h |
— | Print usage. |
Callback URL/token pairs must be provided together; supplying only one is a
startup error. Tool callbacks can also be registered after startup via
SdkBridgeControlService.SetToolCallback (same-host/loopback only).
Prefer a graceful stop, then escalate:
- Call
SdkBridgeControlService.Shutdown(grace_secondsbounds how long in-flight RPCs may drain;0means immediate) and wait for the process to exit, or sendSIGINT/SIGTERM— the bridge handles both. - If the process does not exit within your timeout (the reference adapters use ~5 seconds), kill it.
Shutdown releases local resources only; durable cloud agent state is not affected.
adapter bridge
│ spawn bin/cursor-sdk-bridge │
│ (CURSOR_API_KEY in env) │
│ ────────────────────────────────────►│
│ │ binds 127.0.0.1:<port>
│ stderr: "cursor-sdk-bridge ready {...}"
│ ◄────────────────────────────────────│
│ read authTokenFile, trim │
│ │
│ POST /sdk.v1.SdkBridgeControlService/Ping
│ Authorization: Bearer <token> │
│ ────────────────────────────────────►│
│ ◄──────────────────── PingResponse ──│
│ │
│ CreateAgent → Send (stream) → ... │
│ │
│ Shutdown / SIGTERM ─────────────────►│
│ process exits
Before bisecting your own code, run the curl smoke test
(smoke-test.md) — the full spawn → Ping → Me →
CreateAgent → Send sequence in JSON mode with no adapter code involved.
It answers "is it me or the bridge?" in one run.
Next: services.md for what each service does,
streaming.md for run streams,
errors.md for the failure model, and
smoke-test.md for the no-adapter-code smoke test.