An opencode plugin that adds Cursor as a native provider. Your Cursor models appear in the model picker; you chat with them the same way you use any other provider.
It uses the official Cursor SDK (@cursor/sdk) to list your account's models live and run chats through Cursor's local agent runtime. For delegated or background workflows it also ships two permission-gated tools (cursor_delegate, cursor_cloud_agent) — see Delegation tools.
⚠️ Security. When you chat with acursor/*model, Cursor runs its own tools — includingshell,write,edit, anddelete— directly in your working directory, outside opencode's permission system. Read Security before you use it.
- opencode 1.17+
- Node.js 22+ on your
PATH— opencode runs on Bun; the plugin needs a Node sidecar to host the Cursor SDK (see Runtime). - A Cursor account and API key (from the Cursor dashboard).
curl -fsSL https://raw.githubusercontent.com/stablekernel/opencode-cursor/main/install.sh | bashRegisters the plugin in your global opencode.json (~/.config/opencode/opencode.json), checks
for Node.js 22+, and offers to set CURSOR_API_KEY. Flags:
--project— write./opencode.jsonin the current directory instead.--yes/-y— non-interactive.
npm install @stablekernel/opencode-cursorAdd to your opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@stablekernel/opencode-cursor"]
}The plugin injects the provider block automatically. If you need explicit control:
{
"provider": {
"cursor": {
"npm": "@stablekernel/opencode-cursor",
"name": "Cursor",
"options": { "apiKey": "{env:CURSOR_API_KEY}" }
}
}
}opencode auth login # choose "Cursor", paste your key from the Cursor dashboardOr set the environment variable:
export CURSOR_API_KEY="key_..."The key is validated on first use (model discovery / first call), not at login time.
opencode models(or the model picker) lists your Cursor models ascursor/<id>.- Pick a model and chat — the Cursor local agent runs in your project directory.
- Run the
cursor_refresh_modelstool to force a live catalog refresh.
The plugin also registers two delegation tools:
cursor_delegate— hand a discrete subtask to a local Cursor agent as a permission-gated tool call (your primary model stays in control).cursor_cloud_agent— launch a Cursor cloud agent on a remote repo that can run for minutes and optionally open a PR.
⚠️ The provider path is unsandboxed and not gated by opencode permissions. When you chat with acursor/*model, Cursor runs its own tools — includingshell,write,edit, anddelete— directly in your working directory. opencode'spermissionrules (e.g.edit: deny,bash: ask) do not apply to them.Options if you need a permission boundary:
- Set
sandbox: trueinprovider.cursor.optionsto run Cursor's tools in Cursor's sandbox.- Use
cursor_delegateinstead of the provider path — it is gated by opencode'spermissionconfig.
See SECURITY.md for the full threat model.
Option (provider.cursor.options) |
Default | Meaning |
|---|---|---|
apiKey |
CURSOR_API_KEY |
Cursor API key |
cwd |
process.cwd() |
Directory the local agent operates in |
mode |
"agent" |
Default conversation mode ("agent" or "plan") |
params |
— | Default model params, e.g. { thinking: "high" } |
settingSources |
— | Cursor settings layers to load: ["project","user","all",...] — pulls in your Cursor skills, rules, and .cursor/mcp.json |
sandbox |
— | Run the agent's tools in Cursor's sandbox |
agents |
— | Cursor subagent definitions |
session |
"auto" |
Session reuse strategy — see Session reuse |
forwardMcp |
true |
Forward opencode's configured MCP servers to the Cursor agent |
mcpServers |
— | Extra MCP servers (Cursor McpServerConfig shape); merged with forwarded ones |
toolDisplay |
"blocks" |
How Cursor's internal tool activity is shown — see Tool display |
| Environment variable | Default | Meaning |
|---|---|---|
CURSOR_API_KEY |
— | API key fallback |
OPENCODE_CURSOR_MODEL_CACHE_TTL_MS |
86400000 |
Model-list cache lifetime (ms) |
OPENCODE_CURSOR_DEBUG |
— | Set to 1 for trace logging on stderr |
OPENCODE_CURSOR_SIDECAR |
— | 1 = always use Node sidecar; 0 = never |
opencode re-sends the full conversation transcript on every turn. session: "auto" (the default)
fingerprints the conversation and resumes the same Cursor agent when nothing has changed, so you
only pay for the new message. It falls back to a fresh agent + full transcript on edits, reverts,
or compaction.
| Situation | What happens |
|---|---|
| First turn | Fresh agent, full transcript, pool it |
| System prompt differs (title gen, other side calls) | Ephemeral fresh agent; pooled agent untouched |
| Clean continuation (one new user message) | Agent.resume — sends only the new message |
| Forwarded MCP server set changed | Fresh agent + full transcript, re-pooled |
| Message edited/reverted or conversation compacted | Fresh agent + full transcript, re-pooled |
session: true is an alias for "auto". session: false disables reuse (always fresh agent,
full transcript every turn).
Fingerprint records persist to ~/.cache/opencode-cursor/session-pool.json, so session reuse
survives opencode restarts.
The plugin auto-generates model variants for each reasoning/effort level a model advertises.
Selecting a variant in the model picker sends its settings through providerOptions.cursor.
opencode's plan agent (Tab) maps to Cursor's plan mode automatically — no manual config
needed.
To set controls statically per model:
{ "provider": { "cursor": { "models": {
"composer-2.5": { "options": { "params": { "thinking": "high" } } }
} } } }With forwardMcp: true (default), the Cursor agent uses the same MCP servers configured in
opencode. The server list is updated live per turn, so enabling or disabling an MCP server takes
effect on the next message.
opencode config.mcp |
→ Cursor |
|---|---|
{ type: "local", command: [cmd, ...args], environment } |
{ type: "stdio", command, args, env } |
{ type: "remote", url, headers } |
{ type: "http", url, headers } |
Remote with registered OAuth clientId |
{ type: "http", url, auth: { CLIENT_ID, … } } |
Disabled entries (enabled: false) are skipped. Remote servers requiring OAuth without a
shareable clientId are also skipped (a one-time toast says which). Disable forwarding with
forwardMcp: false.
Note: This forwards MCP servers. opencode's own skills and subagents are not exposed to the Cursor agent. To load your local Cursor skills/rules, use
settingSources: ["project","user"].
Both tools resolve the API key from your opencode auth login session (or CURSOR_API_KEY) and
are gated by opencode's permission config:
{ "permission": { "cursor_delegate": "ask", "cursor_cloud_agent": "ask" } }Runs one Cursor turn as a permission-gated tool call. Your primary opencode model hands off a discrete subtask and gets the result back.
| Arg | Required | Meaning |
|---|---|---|
prompt |
✅ | The subtask to delegate |
model |
✅ | Cursor model id |
mode |
— | "agent" or "plan" |
thinking |
— | Thinking level (e.g. "high") |
cwd |
— | Working directory |
sandbox |
— | Run in Cursor's sandbox |
agentId |
— | Resume a specific Cursor agent |
Launches a background Cursor cloud agent on a remote repo. Can run for minutes and optionally open a PR.
| Arg | Required | Meaning |
|---|---|---|
prompt |
✅ | The task |
repoUrl |
✅ | Target repository URL (e.g. https://github.com/owner/repo) |
startingRef |
— | Branch/ref to start from |
model |
— | Cursor model id |
mode |
— | "agent" or "plan" |
thinking |
— | Thinking level |
autoCreatePR |
— | Open a PR when finished |
workOnCurrentBranch |
— | Operate on the current branch instead of a new one |
toolDisplay controls how Cursor's internal tool activity appears in opencode:
"blocks"(default) — structured, collapsible tool blocks with inputs and outputs. Common Cursor tools are mapped to their opencode equivalents (edit→ diff viewer,shell→ bash console, etc.). Requires opencode 1.17+."reasoning"— compact inline lines ([tool] write {"path":…}). Works on any host; use this on older opencode versions.
To force the fallback:
{ "provider": { "cursor": { "options": { "toolDisplay": "reasoning" } } } }opencode runs on Bun, which has an node:http2 incompatibility with the Cursor
SDK's streaming RPC. The plugin transparently hosts the Cursor SDK in a short-lived Node child
process when running under Bun. Under Node it runs in-process.
This is why Node.js 22+ on your PATH is required. If Node isn't found, the plugin warns once
and falls back to in-process (native Cursor tools will misbehave until Node is available).
Override with OPENCODE_CURSOR_SIDECAR=1 (always sidecar) or OPENCODE_CURSOR_SIDECAR=0 (never).
- Native Cursor tools hang / "Tool execution aborted" (
NGHTTP2_FRAME_SIZE_ERROR). Node isn't on yourPATH. Install Node.js 22+, or force the sidecar withOPENCODE_CURSOR_SIDECAR=1. - "Running under Bun without a usable Node sidecar" warning. Install Node.js 22+, or set
OPENCODE_CURSOR_SIDECAR=0to accept in-process behavior and silence the warning. - "Could not locate the bindings file" /
node_sqlite3.nodenot found. The@cursor/sdknative sqlite3 addon was skipped during Bun install. The plugin self-heals on first load (needs Node onPATH). If that fails,cdinto the printed sqlite3 directory and runnpx prebuild-install -r napi. - Plugin enabled but no
cursorprovider/models appear. Stale opencode plugin cache. Pin an exact version (@stablekernel/opencode-cursor@<version>) or delete~/.cache/opencode/packages/and restart. - Only the four fallback models appear. The live catalog loads after the first authenticated
use. Restart opencode once after login, or run
cursor_refresh_models. - Invalid or expired key. Validated on first use — that's where the error surfaces.
- Need more detail? Set
OPENCODE_CURSOR_DEBUG=1.
Issues and pull requests are welcome. See CONTRIBUTING.md for dev setup, test/typecheck/build commands, and the release process. Report bugs at the issue tracker; for security reports see SECURITY.md.
MIT