The built-in cursor provider reaches your Cursor subscription through Cursor’s own
ConnectRPC/protobuf AgentService. The kind = "cursor" native adapter bridges it to the
Anthropic Messages API — streaming, reasoning in streaming responses, native tool calls, and
inline images included. Login and token refresh use api2.cursor.sh; agent turns run over
HTTP/2 against Cursor’s current agent host (agentn.global.api5.cursor.sh).
Quick start
Let a coding agent wire it up for you — shunt add prints an embedded setup blueprint
(offline and read-only; the agent edits the config, the command never does):
shunt add upstream cursor --print | claudeOr follow the manual steps below.
1. Log in
shunt login cursorThis runs the Cursor OAuth flow and writes ~/.shunt/cursor-auth.json, which shunt reads and
auto-refreshes. If the file is missing or expired, shunt returns an authentication_error
telling you to run shunt login cursor again.
2. Configure the upstream
The provider is seeded by default, so no table is required — route a cursor:* model id and
you’re done. To declare it explicitly (for ordered failover), the cursor preset supplies the
native adapter, base_url = "https://api2.cursor.sh", and auth = "cursor_oauth":
[[upstreams]]
name = "anthropic"
provider = "anthropic" # keep Anthropic as the default for unrouted models (e.g. claude-*)
[[upstreams]]
name = "cursor"
provider = "cursor"Ordered [[upstreams]] replace shunt’s built-in providers (including the seeded cursor), so an
explicit config must also declare the anthropic default it still falls back to
(server.default_provider defaults to anthropic).
The legacy [providers.cursor] table form remains supported — but do not mix [[upstreams]]
and [providers.*] in one file.
3. Model ids and agent modes
The prefix selects Cursor’s agent mode (Agent / Plan / Ask) and the suffix is normally the Cursor
model id. Use the wire id, not the display name from cursor-agent models: Auto is default
(routing cursor:auto fails with Unknown model ID: auto). Named models (e.g. cursor:gpt-5.2)
require a paid plan that entitles them; free plans are limited to cursor:default.
Cursor’s composer-2.5-fast picker entry is a parameterized alias rather than a wire id. Route
cursor:composer-2.5-fast (or cursor:composer-2.5[fast=true]) and shunt sends the
composer-2.5 wire id with fast=true model metadata, matching the official CLI.
| Form | Agent mode | Example |
|---|---|---|
cursor:<id> / cursor-agent:<id> |
Agent | cursor:default |
cursor-plan:<id> |
Plan | cursor-plan:default |
cursor-ask:<id> |
Ask | cursor-ask:default |
Legacy bare names are also accepted: cursor, cursor-agent, cursor-composer,
cursor-composer-fast (Agent); cursor-plan, composer-2.5 (Plan); cursor-ask,
composer-2.5-fast (Ask). Any other model id is rejected with an invalid_request_error.
Route directly, or expose a Claude-named alias to model discovery:
[[routes]]
model = "cursor:default"
provider = "cursor"
# Or: advertise + route in one declaration
[[models]]
id = "claude-cursor-default"
display_name = "Cursor Auto"
[models.upstream_model]
cursor = "cursor:default"What the adapter carries
The adapter streams assistant text and reasoning, bridges your client’s tools as native
Cursor MCP tool calls (a tool the model invokes surfaces as an Anthropic tool_use block with
stop_reason: "tool_use"; you run it and send the tool_result back, and shunt re-runs the turn
with that result in history), and forwards inline images (base64 sources; URL images are
skipped). Cursor’s own agentic file/shell tools are not exposed — only the tools your request
advertises.
Composer nevertheless chooses per turn whether to answer through a bridged tool or through one of Cursor’s built-in tools, and a built-in call arrives on the wire with no tool name, carrying only its arguments and a call id. shunt cannot map that onto a tool your client owns, so it fails the turn with an explicit error rather than closing it as if the model had finished:
{"type":"error","error":{"type":"api_error","message":"cursor answered through one of its own built-in tools, which shunt cannot bridge onto a caller-supplied tool; the choice is not deterministic, so retry"}}The choice is not deterministic — retrying the same request usually lands on the bridged path.
Detection does not depend on what the request advertised: shunt recognizes the built-in call from
the response frame alone, so a request that carries no tools can raise the same error whenever
Composer reaches for a built-in. Retrying is the mitigation in both cases.
Verify
shunt check # -> config ok
shunt run
curl -sS http://127.0.0.1:3001/v1/messages \
-H 'anthropic-version: 2023-06-01' \
-H 'content-type: application/json' \
-d '{"model":"cursor:default","max_tokens":16,"messages":[{"role":"user","content":"Reply with OK."}]}'Confirm the response’s x-gateway-upstream header names cursor, then
point Claude Code at shunt.