Skip to content

jaredboynton/cursor-oauth-opencode

Repository files navigation

cursor-oauth-opencode

OpenCode plugin that connects to Cursor's API, giving you access to Cursor models inside OpenCode with full tool-calling support.

Install

npx cursor-oauth-opencode setup --global
opencode auth login --provider cursor

For project-local setup:

npx cursor-oauth-opencode setup --project
opencode auth login --provider cursor

The setup command is idempotent. It adds the npm plugin entry and fallback model registries for both provider.cursor and provider.cursor-code in opencode.json, preserving existing user model overrides. Login stays on the primary provider only (opencode auth login --provider cursor).

Providers

The plugin registers two OpenCode providers that share one OAuth login, proxy, and model catalog. Mode is selected in the model picker:

Provider Picker path Behavior
Cursor cursor/<model> Native tool passthrough (code mode OFF)
Cursor Code cursor-code/<model> Single code sandbox tool (code mode ON)

Breaking default: cursor/* is native-tools / code mode OFF. To keep the previous sandbox-collapse behavior, pick cursor-code/<model>. There is no auto-rewrite of a previously selected cursor/* model.

Fleet-wide kill switch (forces OFF even for cursor-code/*): set CURSOR_OPENCODE_CODE_MODE=0 or provider.cursor.plugin.codeMode.enabled: false. That knob is read only under provider.cursor.plugin — a block under provider.cursor-code.plugin has no effect.

Reasoning variants

Reasoning-tier Cursor models are listed once in the picker; effort is chosen in OpenCode's Select variant menu (not as separate model slugs):

Picker model Variants Notes
grok-4.5-fast low / medium / high Cursor slug suffixes are shifted (high…-xhigh); non-fast grok-4.5 is omitted
claude-4.6-opus, gpt-5.4, glm-5.2, … only levels Cursor exposes e.g. opus → high, gpt-5.4 → medium
composer-2.5 / composer-2.5-fast default only Non-effort axis; stay as separate models

Legacy full slugs (grok-4.5-fast-high, …) still resolve as aliases. After upgrade, re-run setup so opencode.json seeds the collapsed base ids.

Manual config

If you do not want to use the setup command, add this to ~/.config/opencode/opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    "cursor-oauth-opencode@latest"
  ],
  "provider": {
    "cursor": {
      "name": "Cursor",
      "npm": "@ai-sdk/openai-compatible",
      "api": "http://127.0.0.1:65535/v1",
      "models": {
        "composer-2.5-fast": {
          "name": "Composer 2.5 Fast",
          "reasoning": true,
          "temperature": true,
          "attachment": false,
          "tool_call": true,
          "limit": {
            "context": 200000,
            "output": 64000
          }
        }
      }
    },
    "cursor-code": {
      "name": "Cursor Code",
      "npm": "@ai-sdk/openai-compatible",
      "api": "http://127.0.0.1:65535/v1/code",
      "models": {
        "composer-2.5-fast": {
          "name": "Composer 2.5 Fast",
          "reasoning": true,
          "temperature": true,
          "attachment": false,
          "tool_call": true,
          "limit": {
            "context": 200000,
            "output": 64000
          }
        }
      }
    }
  }
}

The fallback API URLs are placeholders so OpenCode can list the providers before login. After auth, the plugin starts a local proxy and replaces Cursor models with live Cursor model discovery. cursor-code must keep the /v1/code path suffix (merge-safety if a request falls back to provider.api).

Authenticate

opencode auth login --provider cursor

This opens Cursor OAuth in the browser. Tokens are stored in ~/.local/share/opencode/auth.json and refreshed automatically. Both providers use that single cursor auth entry.

Use

Start OpenCode and select a model under Cursor (native tools) or Cursor Code (sandbox code tool). The plugin starts a local OpenAI-compatible proxy on demand and routes requests through Cursor's gRPC API.

How it works

  1. OAuth — browser-based login to Cursor via PKCE (cursor provider only).
  2. Model discovery — queries Cursor's gRPC API for all available models.
  3. Local proxy — translates chat completions into Cursor's protobuf/HTTP/2 Connect protocol. Path selects mode: /v1/* (native) vs /v1/code/* (sandbox).
  4. Code mode (cursor-code) — advertises only the MCP code tool to the Cursor model (allowed-tools hard-forced to mcpToolCall); the model writes one async JavaScript program that the proxy runs in a Node child sandbox, bridging each await tools.X() wave to OpenCode's tools while the Cursor bridge stays parked. Native read/list/grep/mutations are rejected with coaching; a leaked native shell or AskQuestion uses the controlled client bridge and is counted by the probe. cursor/* passes client tools through as MCP tools (subject to native policy below).
  5. Request continuity — every plugin window and OpenCode session carries an immutable proxy scope. Parked tool calls route by session plus pending call ids. A valid Cursor checkpoint is authoritative when workspace identity and client-history prefix match; missing, corrupt, incomplete, cross-workspace, or history-diverged checkpoints append one model-visible recovery marker and rebuild from the client ledger/history instead of borrowing another workspace's state.

HTTP/2 transport runs through a Node child process (h2-daemon.mjs / legacy h2-bridge.mjs) because Bun's node:http2 support is not reliable against Cursor's API.

Protocol version and tool policy

These knobs live under provider.cursor.plugin (env overrides last). They describe how this plugin talks to Cursor's private Agent Run protocol — that surface is not a stable public API and can change without notice.

Knob Default Env Behavior
(client version) manifest pin cli-2026.01.09-231024f CURSOR_OPENCODE_CLIENT_VERSION Advertised x-cursor-client-version is the checked-in protocol manifest pin, not "newest installed cursor-agent". The env value is an explicit development override only.
requestContext.inline true CURSOR_OPENCODE_INLINE_REQUEST_CONTEXT When on, the initial Run carries MCP tools plus inline RequestContext (same catalog used for refresh replies). Set false / 0 to roll back to refresh-only context.
nativePolicy legacy CURSOR_OPENCODE_NATIVE_POLICY legacy: model-visible MCP declarations match the full client catalog. dedupe-canary: omit MCP declarations for native-eligible read/list/grep roles while keeping the full execution catalog for bridges. Code mode always declares only code.
(Run retries) 3 CURSOR_OPENCODE_RUN_RETRIES Maximum automatic retries for zero-byte transport failures and retryable Connect errors. Before output, the original action is replayed; after output, a safe latest checkpoint is resumed with ResumeAction. Set 0 to disable.
(retry backoff) 500 ms CURSOR_OPENCODE_RUN_RETRY_BACKOFF_MS Delay before retrying resource_exhausted / unavailable Connect failures.

Declaration vs execution catalogs: the proxy keeps the complete client tool list internally for shell/question/MCP bridges even when dedupe-canary hides duplicate read/list/grep MCP definitions from the model.

Circuit breaker: if dedupe-canary hits an unhandled or duplicate/closed exec, or a transport close before turnEnded, new Runs in that proxy context fall back to legacy declarations. In-flight execs still finish with their matching native result/error type.

Exec lifecycle / cancel: each controlled exec gets a heartbeat while active, a typed result or throw, and exactly one streamClose. Client abort (Esc) sends protocol CancelAction and tears down the owned bridge, execs, timers, and code sandbox.

Prompt shaping (caveman + reasoning containment)

On every outbound Cursor Run the proxy can:

  • Caveman-compress tool-description prose and the OpenCode system prompt (plugin.caveman.level / CURSOR_OPENCODE_CAVEMAN, default full; system inherits via plugin.caveman.system / CURSOR_OPENCODE_CAVEMAN_SYSTEM).
  • Append a verbatim reasoning guard for runaway-reasoning models (claude-fable-5, claude-opus-4-8; CURSOR_OPENCODE_REASONING_GUARD=0).
  • Inject a coalesced harness appendix (interstitial text, overthink <system-reminder>, throttled continuity, single-shot [reasoning tail] / [thinking exhaustion] notes) for those same models.

Savings counters live under CURSOR_OPENCODE_SAVINGS=1 (plugin log file).

Architecture

OpenCode  -->  /v1/chat/completions       (cursor = code OFF)
          -->  /v1/code/chat/completions  (cursor-code = code ON)
                                              |
                                    Bun.serve (proxy)
                                              |
                                    Node child (h2-daemon / h2-bridge)
                                              |
                                     HTTP/2 Connect stream
                                              |
                                    api2.cursor.sh gRPC
                                      /agent.v1.AgentService/Run

Tool call flow (code mode, cursor-code)

1. Proxy advertises ONE code tool to the Cursor model (its description embeds
   OpenCode's real tool catalog)
2. Model emits code({script}); proxy runs the script in a Node child sandbox
3. Each await tools.X() wave -> proxy emits OpenAI tool_calls SSE, parks BOTH
   the Cursor bridge and the sandbox
4. OpenCode executes the tools, sends results in the follow-up request
5. Proxy feeds results to the parked sandbox; the next wave repeats step 3
6. Script returns -> its value becomes the code tool result; the Cursor bridge
   resumes and streams the model's final answer

With cursor/* (or the kill switch forcing OFF) the proxy passes each client tool through directly: the model calls a tool via mcpArgs, the proxy emits OpenAI tool_calls, pauses the H2 stream, and resumes with mcpResult on the follow-up request.

Develop locally

bun install
bun run build
bun run test
bun run test:serial  # serial diagnostics
bun run test:smoke   # parallel smoke groups only

The default test gate runs protocol snapshot/header/surface checks in parallel, then six isolated Bun smoke groups with five-way parallelism through Vitest. Tests sharing module state remain serial within a group. Run one smoke group with bun run test:group <name>; names: core, index-context, code-mode, streaming, transport, utilities.

Logs

Plugin diagnostics never print to the terminal (they would paint over the OpenCode TUI). They land in ~/.cache/cursor-oauth-opencode/plugin.log. Override the path with CURSOR_OPENCODE_LOG_FILE; set CURSOR_OPENCODE_LOG_STDERR=1 to also mirror lines to stderr when debugging outside the TUI.

Requirements

About

OpenCode plugin for Cursor OAuth and models

Resources

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors