Cursor Integration Guide
Technology: cursor · Category: tooling · Last reviewed: 2026-09-27
Source: https://tech-stack.codeamanilabs.org/guide/cursor
Insight:
Cursor is a VS Code fork built around an agent, not an autocomplete plugin — Tab,
Ctrl+Kinline edit, and a multi-file Agent all share one index of your repo. On Windows the whole experience hinges on one setup decision: install Cursor on Windows, keep the repo on the Linux side of WSL, and let theanysphere.remote-wslextension run the extension host, terminal, and agent inside Ubuntu — otherwise every command the agent runs crosses the 9P boundary and crawls. For codeAmani it needs no new config file: Cursor readsCLAUDE.mdexactly the way it readsAGENTS.mdand always applies it, so this repo's conventions are already the agent's rules.
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
Cursor Integration Guide
Focus: Using Cursor as the day-to-day editor for a WSL Ubuntu dev box driven from Windows — the remote-WSL connection, the three AI surfaces (Tab,
Ctrl+K, Agent), rules that reuse this repo'sCLAUDE.md, MCP servers, theagentCLI, and scripting the agent from code with the Cursor TypeScript SDK (@cursor/sdk). Grounded incursor.com/docs; reviewed 2026-09-27.
Overview
Cursor is an AI code editor from Anysphere, built as a fork of VS Code. Because it's a fork, everything you know about VS Code still holds — keybindings, settings JSON, the command palette, the extension host model, and remote development. What's bolted on is a coding agent that shares one index of your codebase across three distinct surfaces.
The three surfaces are worth separating in your head, because they behave differently and honour different config:
| Surface | Shortcut | Scope | Reads rules? |
|---|---|---|---|
| Tab | Tab to accept |
Autocomplete + multi-line + cross-file jumps | No |
Inline edit (Cmd-K) |
Ctrl+K (Win/Linux), Cmd+K (Mac) |
The selection you highlighted | No |
| Agent | Ctrl+I / Ctrl+L |
Whole repo, multi-file, runs terminal commands | Yes |
agent CLI |
terminal | Whole repo, headless-capable, CI-friendly | Yes |
@cursor/sdk |
your TypeScript | Local tree or cloud VM, programmatic | Yes (local: with settingSources) |
And it is a distinct product from the two neighbours already documented here:
| Cursor | VS Code | Visual Studio | |
|---|---|---|---|
| What it is | VS Code fork, agent-first | Microsoft's editor | Microsoft's full Windows IDE |
| Extension registry | Open VSX via Cursor's marketplace proxy | Microsoft Marketplace | VSIX / NuGet |
| WSL extension | anysphere.remote-wsl (first-party rebuild) |
ms-vscode-remote.remote-wsl |
n/a — Windows-native |
| Agent config | .cursor/rules/*.mdc, AGENTS.md, CLAUDE.md |
per-extension | per-extension |
flowchart LR
A["Your repo"] --> B["Cursor's codebase index"]
B --> C["Tab<br/>autocomplete + jumps"]
B --> D["Ctrl+K<br/>inline edit on selection"]
B --> E["Agent<br/>multi-file + terminal"]
B --> F["agent CLI<br/>terminal + CI"]
G[".cursor/rules/*.mdc<br/>AGENTS.md · CLAUDE.md"] --> E
G --> F
H[".cursor/mcp.json<br/>MCP servers"] --> E
H --> F
I[".cursorignore"] --> B
See also:
wsl/for the WSL platform itself (install,.wslconfig, the filesystem rule, systemd) andvisual-studio/for the unrelated .NET IDE. This guide only covers Cursor.
Official Documentation
| Resource | URL |
|---|---|
| Docs home | https://cursor.com/docs |
| Quickstart | https://cursor.com/docs/get-started/quickstart |
Rules (.cursor/rules, AGENTS.md) |
https://cursor.com/docs/rules |
| MCP in Cursor | https://cursor.com/docs/mcp |
| CLI install | https://cursor.com/docs/cli/installation |
| Agents Window (Cursor 3) | https://cursor.com/docs/agent/agents-window |
| Ignore file reference | https://cursor.com/docs/reference/ignore-file |
| TypeScript SDK | https://cursor.com/docs/sdk/typescript |
| Download | https://cursor.com/download |
| Machine-readable sitemap | https://cursor.com/llms.txt |
Every docs page has a
.mdtwin — append.mdto any URL (https://cursor.com/docs/rules.md) to get clean markdown.https://cursor.com/llms.txtlists all of them.
Install
Install the editor on Windows, not inside the distro. Cursor is a GUI app; the Linux side only ever runs its headless server.
# 1. Editor — download the Windows .exe from https://cursor.com/download and run it.
# (macOS: .dmg · Linux: apt/dnf repo or AppImage)
# 2. CLI agent — run this INSIDE your WSL Ubuntu shell
curl https://cursor.com/install -fsS | bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
agent --version
# CLI on native Windows PowerShell (only if you also want it outside WSL)
irm 'https://cursor.com/install?win32=true' | iex
The CLI's install docs list its supported targets as "macOS, Linux and Windows (WSL)" — WSL is a first-class install target for the agent binary even though the editor is Windows-native.
Cursor + WSL Ubuntu
This is the part with no official docs page, so it's where most of the time gets lost. The mechanism is inherited wholesale from VS Code Remote: the UI runs on Windows, a server runs inside the distro, and everything that touches your code — extension host, language servers, integrated terminal, and the agent's shell commands — executes on the Linux side.
flowchart TB
subgraph WIN["Windows"]
U["Cursor UI<br/>renderer + Tab client"]
X["UI extensions<br/>themes, keymaps"]
end
subgraph LIN["WSL · Ubuntu"]
S["Cursor server<br/>~/.cursor-server"]
W["Workspace extensions<br/>ESLint, Prettier, Tailwind"]
T["Integrated terminal<br/>bash · node · pnpm · git"]
R["Your repo<br/>/home/you/code/app"]
end
U <-->|"anysphere.remote-wsl"| S
S --> W
S --> T
T --> R
W --> R
R -.->|"AVOID: /mnt/c crossing<br/>9P protocol, ~20x slower"| WIN
1. Put the repo on the Linux filesystem
Non-negotiable, and the single biggest performance lever (see wsl/ §5):
# GOOD — native ext4, full speed
mkdir -p ~/code && cd ~/code
git clone git@github.com:codeAmani-Labs/your-app.git
cd your-app
# BAD — /mnt/c/... crosses the 9P boundary on every stat(), and the agent
# stats a LOT. npm install and next dev will crawl.
2. Set the distro Cursor will attach to
Cursor connects to the default WSL distribution:
wsl --list --verbose
wsl --set-default Ubuntu-24.04
3. Connect
Three routes, all equivalent:
# From a WSL shell — needs the PATH shim from step 4
cd ~/code/your-app
cursor .
- Command palette:
Ctrl+Shift+P→ WSL: Connect to WSL (or Connect to WSL using Distro…), then File → Open Folder and pick/home/you/code/your-app. - Remote indicator: the coloured button in the bottom-left status bar → Connect to WSL.
On first connect Cursor prompts to install anysphere.remote-wsl — Cursor's own rebuild of the WSL extension. Accept it. It installs the server into ~/.cursor-server inside the distro. Cursor ships first-party Anysphere replacements for Microsoft-Marketplace-only extensions precisely because Cursor's marketplace is backed by Open VSX, and ms-vscode-remote.remote-wsl is not on Open VSX.
4. The cursor shell shim
cursor . from inside WSL is the fastest way in, but the shim lives in the Windows install and isn't on the Linux PATH by default:
# ~/.bashrc — adjust <WINUSER> to your Windows username
export PATH="$PATH:/mnt/c/Users/<WINUSER>/AppData/Local/Programs/cursor/resources/app/bin"
source ~/.bashrc
cd ~/code/your-app && cursor . # opens Windows Cursor, attached to WSL
cursor --disable-extensions # bisect a slow/conflicting extension
5. Verify you are actually remote
Cheap check, and worth doing before you blame the agent for anything:
# In Cursor's integrated terminal (Ctrl+`)
uname -a # expect: Linux ... microsoft-standard-WSL2
pwd # expect: /home/you/code/your-app — NOT /mnt/c/...
which node # expect: /home/you/.nvm/... or /usr/bin/node — NOT /mnt/c/...
The status bar should read WSL: Ubuntu-24.04. If which node resolves to /mnt/c/..., Windows binaries are leaking onto the Linux PATH via interop and the agent will invoke node.exe across the filesystem boundary — the classic 9P thrashing symptom. Install Node inside the distro:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node -v && npm -v
Or drop appendWindowsPath = false into /etc/wsl.conf and restart the distro to stop Windows PATH inheritance entirely.
6. Install extensions on the right side
Same split as VS Code: UI extensions (themes, keymaps) install on Windows; workspace extensions (ESLint, Prettier, Tailwind IntelliSense, the TypeScript server) must be installed in the WSL: Ubuntu scope. Open the Extensions panel (Ctrl+Shift+X) while connected and look for the Install in WSL: Ubuntu-24.04 button. An ESLint installed only on the Windows side will silently lint nothing.
7. Cursor 3: use the Editor window, not the Agents Window
Cursor 3 (released 2 April 2026) added the Agents Window — an agent-first shell for running parallel local/cloud agents across repos. It supports local, cloud, and remote SSH environments; WSL connections are not supported there yet. If you land in the Agents Window and see "Extension 'WSL' is required to open the remote window", switch back:
Ctrl+Shift+P→ Open IDE (from the Agents Window), or- launch the classic editor shell directly, then
Ctrl+Shift+P→ Open Agents Window only for non-WSL work.
The classic editor is also the right choice when you want VS Code extensions and split panes, which is exactly the Next.js/Tailwind workflow.
WSL troubleshooting
| Symptom | Cause / fix |
|---|---|
Extension 'WSL' is required to open the remote window |
You're in the Agents Window — it can't do WSL yet. Switch to the editor (Open IDE). |
| Everything is slow, disk pegged | Repo on /mnt/c, or Windows binaries on the Linux PATH. Move to ~/code; set appendWindowsPath = false. |
cursor: command not found in WSL |
Add the Windows resources/app/bin to PATH (step 4). |
| Connects to the wrong distro | wsl --set-default Ubuntu-24.04, then reconnect. |
| Extension "does nothing" | Installed on the Windows side only — reinstall into the WSL: Ubuntu scope. |
| Agent prompts time out only in WSL windows | Known anysphere.cursor-agent-exec extension-host issue in remote windows. Reload the window; fall back to the agent CLI in the integrated terminal, which is unaffected. |
| Remote server wedged | Close the window, wsl --shutdown from PowerShell, reopen. Nuke ~/.cursor-server to force a clean server reinstall. |
The three AI surfaces
Tab — autocomplete that moves
Grey ghost text ahead of the caret. Tab accepts, Esc rejects, Ctrl+→ accepts word-by-word. Two behaviours worth knowing:
- Jump-in-file: after accepting, press
Tabagain and Tab predicts where you're going to edit next and moves the caret there. - Cross-file edits: when a change in one file requires an edit in another, a portal window appears at the bottom of the editor offering the jump.
Toggle it from the Tab status indicator (bottom-right): snooze for a duration, disable globally, or disable per file extension — turning it off for markdown and json is the usual first move.
Ctrl+K — inline edit on a selection
1. Select the code
2. Ctrl+K (Cmd+K on Mac)
3. "Convert this to a server action and validate the body with zod"
4. Enter → applied in place; type a follow-up and Enter again to refine
5. Alt+Enter switches to question mode instead of edit mode
Ctrl+L on a selection promotes it into Agent with that code as context — the escape hatch when a "quick edit" turns out to be multi-file.
Agent — the multi-file worker
Ctrl+I opens the panel. Four modes, cycled with Shift+Tab (or Ctrl+. for the menu):
| Mode | Use for | Edits files? |
|---|---|---|
| Agent | Building, refactoring, fixing | Yes |
| Ask | Understanding architecture | No (read-only) |
| Plan | Multi-file features you want to review first | Yes, after you approve the plan |
| Debug | Bugs needing runtime evidence | Yes |
Ctrl+/ cycles models. Hover any prior message → Restore Checkpoint rolls the working tree back to that point. Queue follow-ups while it works; drag to reorder. Custom subagents are markdown files in .cursor/agents/.
When connected to WSL, every terminal command the Agent runs executes inside Ubuntu against your Linux toolchain. That's the whole point of the setup:
pnpm dev,npx supabase,psql, andgitall behave the way CI does.
Rules — and why codeAmani needs almost none
Cursor has four rule sources, applied in precedence order Team → Project → User:
| Source | Where | Scope |
|---|---|---|
| Project rules | .cursor/rules/*.mdc |
Version-controlled, glob-scoped |
AGENTS.md / CLAUDE.md |
repo root | Always applied, every conversation |
| User rules | Cursor Settings, or ~/.cursor/rules |
Your machine / your account |
| Team rules | Cursor dashboard | Team + Enterprise plans |
The one fact that saves you a file: Cursor reads CLAUDE.md the same way it reads AGENTS.md, picks it up automatically from the project root, and applies it to every conversation regardless of alwaysApply frontmatter. This repo already has a CLAUDE.md full of conventions — TypeScript everywhere, named exports, execFileSync(cmd, [args]), Stripe-by-default payments, webhook signature verification. Cursor is already reading it. Do not duplicate it into a rules file; duplication is how the two drift.
Use .cursor/rules/*.mdc only for the thing CLAUDE.md can't do: glob-scoped rules.
---
globs: app/**/*.tsx, app/**/*.ts
alwaysApply: false
---
- Server Components by default. Add "use client" only for interactivity.
- Never import lib/stripe.ts or lib/supabase.ts from a client component —
it drags the secret key into the bundle.
- Route handlers validate the body with zod before touching the database.
- Follow the file layout in CLAUDE.md: app/api/stripe/*, app/api/webhooks/*.
---
globs: app/api/webhooks/**, app/api/mpesa/**
alwaysApply: false
---
- Verify the signature BEFORE parsing or trusting the payload
(Stripe signing secret; Svix for Clerk; callback validation for M-Pesa).
- Read the raw body — never a pre-parsed JSON object.
- Handlers are idempotent: dedupe on the provider's event id.
Frontmatter drives when a rule loads:
alwaysApply |
description |
globs |
Behaviour |
|---|---|---|---|
true |
— | — | Always included |
false |
— | provided | Auto-attached when a matching file is in context |
false |
provided | — | Agent pulls it in when the description looks relevant |
false |
— | — | Only when you @-mention it |
Rule files must use .mdc. A .md file inside .cursor/rules/ is silently ignored. Create them with /create-rule in chat rather than by hand.
.cursorrulesis legacy. The single root-level.cursorrulesfile still works but is documented as deprecated — migrate its contents into a.cursor/rules/*.mdcrule set to Always Apply, then delete it. If you're starting today, skip it entirely.Rules apply to Agent only. Not to Tab, not to inline edit, not to Bugbot PR reviews. Style conventions you actually want enforced belong in ESLint/Prettier, which run in the WSL extension host and gate the build.
.cursorignore
Sits next to .gitignore (which Cursor already respects) and blocks files from indexing and from the agent:
node_modules/
.next/
dist/
*.min.js
.env*
reports/
packages/dashboard/lib/content.generated.json
.env files, .git/, and lock files are excluded by default. Treat it as a noise filter, not a security boundary — Cursor's own docs say so, and terminal commands plus MCP tools run outside Cursor's file-access controls and can still read ignored files. Secrets belong in .env.local and Hazina, never in the tree.
MCP servers in Cursor
Cursor speaks MCP with three transports — stdio (local, Cursor spawns it), SSE, and Streamable HTTP (remote, OAuth) — and supports tools, prompts, resources, roots, elicitation, and the MCP Apps UI extension.
| Config | Path | Scope |
|---|---|---|
| Project | .cursor/mcp.json |
This repo |
| Global | ~/.cursor/mcp.json |
Everywhere |
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
},
"tech-stack": {
"command": "node",
"args": ["${workspaceFolder}/packages/mcp-server/dist/index.js"]
},
"supabase": {
"url": "https://mcp.supabase.com/mcp",
"headers": { "Authorization": "Bearer ${env:SUPABASE_ACCESS_TOKEN}" }
}
}
}
Interpolation resolves in command, args, env, url, and headers: ${env:NAME}, ${userHome}, ${workspaceFolder}, ${workspaceFolderBasename}, ${pathSeparator} / ${/}.
WSL gotcha: when the window is remote,
${userHome}and~/.cursor/mcp.jsonresolve to the Linux home, andcommandruns in the Linux shell. A stdio server configured with a Windows path (C:\...ornode.exe) will not start. Install MCP server dependencies inside the distro and use POSIX paths.${workspaceFolder}is the folder containing.cursor/mcp.json, so project-scoped config travels correctly.
For servers that hand you a fixed Client ID instead of supporting dynamic registration (Figma, Linear), add a static auth block:
{
"mcpServers": {
"figma": {
"url": "https://mcp.figma.com/mcp",
"auth": { "CLIENT_ID": "your-client-id", "scopes": ["read"] }
}
}
}
See MASTER_MCP_CONFIG.md for the canonical codeAmani server list — the same mcpServers shape drops straight into .cursor/mcp.json.
The agent CLI
Same agent, same modes, in a terminal — which in this setup means inside WSL, so it inherits the Linux toolchain automatically. It also sidesteps the remote extension-host flakiness entirely.
agent # interactive session
agent "refactor lib/stripe.ts to use the 2026 Checkout API"
agent --mode=plan "add M-Pesa STK push to the checkout route"
agent --mode=ask "where does the webhook signature get verified?"
agent ls # list past chats
agent resume # resume the latest
agent --continue # continue the previous session
agent update # upgrade in place
Headless / CI:
export CURSOR_API_KEY=... # from https://cursor.com/dashboard/api
agent -p "review these changes for security issues" --output-format text
agent -p --force "add JSDoc to lib/domain/dispatch.ts" # --force = apply, not propose
Without --force, print mode only proposes changes. /sandbox (or --sandbox enabled|disabled) controls command execution and network access; when a command needs sudo, the CLI shows a masked prompt and pipes the password straight to sudo over IPC — the model never sees it. Prefix a message with & to hand the conversation off to a Cloud Agent.
Terminal config lives at ~/.cursor/ in the distro; system-wide hooks at /etc/cursor/hooks.json on Linux/WSL. If Shift+Enter doesn't insert a newline in Windows Terminal, run /setup-terminal, or use Ctrl+J — the universal fallback that survives tmux and SSH.
Cursor TypeScript SDK — @cursor/sdk
The same agent that runs in the IDE, the agent CLI, and Cursor Web is callable from your own TypeScript. Reach for it when the agent should be triggered by code, not a person: CI auto-fix bots, bug-triage workers, code-review passes, repo-wide codemods, or an agent embedded in a product. Source: cursor.com/docs/sdk/typescript (.md twin available); runnable examples in the Cursor Cookbook.
Two runtimes, one interface
| Runtime | Where the agent loop runs | Files come from | Use when |
|---|---|---|---|
Local (local: {...}) |
Inline in your Node process | Your disk (local.cwd) |
Dev scripts, CI checks against a checked-out tree |
Cloud (cloud: {...}) |
Isolated Cursor-hosted VM | Repo cloned into the VM | Caller has no checkout, many agents in parallel, runs must survive disconnects, auto-PRs |
"Local" is the agent loop, not the model. Inference always goes through Cursor's hosted models in both modes. Local only keeps files and tool execution on your machine.
The runtime is picked by which key you pass to Agent.create(). Both use the same CURSOR_API_KEY. Local IDs look like agent-<uuid>, cloud IDs like bc-<uuid>, and Agent.resume(id) auto-detects the runtime from the prefix.
| Concept | What it is |
|---|---|
| Agent | Durable container: conversation state, workspace config, settings. Survives many prompts. |
| Run | One prompt submission (agent.send()), with its own stream, status, result, and cancel. |
| SDKMessage | Normalized stream event, same shape for both runtimes. |
Install + auth
npm install @cursor/sdk # scoped — the bare "cursor/sdk" does not exist
export CURSOR_API_KEY="..." # user key: cursor.com/dashboard/api
# service-account key: cursor.com/dashboard/team-settings
- Node.js ≥ 22.13 (
engineson@cursor/sdk@1.0.x). The default local store needsnode:sqlite. - Ships per-platform
@cursor/sdk-<os>-<arch>binaries (sandbox helper + ripgrep). Install inside WSL if the script runs against a Linux checkout. - User and service-account keys work. Team Admin keys do not yet. Service-account keys bill to the owning team; user keys bill to that user's plan. SDK spend appears under the SDK tag in the usage dashboard.
- The SDK does not read credentials from an installed Cursor app. Resolution order: explicit
apiKey→CURSOR_API_KEY→ a key minted byCursor.auth.login()(browser flow, stored in~/.cursor/sdk/auth.json, 90-day TTL).
Quick start — local agent, streamed
import { Agent } from "@cursor/sdk";
await using agent = await Agent.create({
apiKey: process.env.CURSOR_API_KEY!,
model: { id: "composer-2.5" },
local: { cwd: process.cwd() },
});
const run = await agent.send("Find the bug in lib/stripe.ts");
for await (const event of run.stream()) {
switch (event.type) {
case "assistant":
for (const block of event.message.content) {
if (block.type === "text") process.stdout.write(block.text);
}
break;
case "tool_call":
console.error(`[tool] ${event.name}: ${event.status}`);
break;
}
}
// Same agent, conversation context carries over.
const fix = await agent.send("Fix it and add a regression test");
const result = await fix.wait();
console.log(result.status, result.result, result.usage?.totalTokens);
await usingdisposes the agent when the block exits; outside that syntax callagent.close().run.wait()resolves to aRunResultwhose final text isresult.result. There is notext/messages/contentfield. For the step-by-step transcript userun.conversation().- One-shot convenience:
await Agent.prompt("What does the auth middleware do?", { apiKey, model, local })does create → send → wait → dispose in one call.
⚠ Headless means auto-approve. A default local agent runs shell, edit, and write tool calls with no human in the loop. Gate it before pointing it at anything real. Options are listed under Guardrails below.
Cloud agent that opens a PR
import { Agent } from "@cursor/sdk";
const agent = await Agent.create({
apiKey: process.env.CURSOR_API_KEY!,
model: { id: "composer-2.5" },
name: "dependabot-triage",
cloud: {
repos: [{ url: "https://github.com/codeAmani-Labs/your-app", startingRef: "main" }],
autoCreatePR: true,
metadata: { ticket_id: "ENG-456" }, // your own tags, returned by Agent.list/get
envVars: { STAGING_API_TOKEN: process.env.STAGING_API_TOKEN! }, // encrypted, deleted with the agent
},
});
const run = await agent.send("Upgrade next to the latest 15.x patch and fix any type errors");
const result = await run.wait();
console.log(result.git?.branches[0]?.prUrl);
cloud.repostakes 1–20 repos. Omit it or pass[]for a no-repo research agent, which must be enabled for your account and can't be created with a repo-scoped key.cloud.envVarsnames can't start withCURSOR_, and can't be combined with a caller-suppliedagentId. For per-run secrets, pass env vars onagent.send()instead.- SDK-started cloud agents are hidden from the default agent list. Use Filter › Source › SDK in Cursor Web.
IntegrationNotConnectedErrormeans the repo's GitHub/GitLab integration isn't connected to your Cursor team. Logerr.helpUrl, because the default message omits it.- A second
send()while a cloud run is active throwsAgentBusyError, which is not retryable. Wait,run.cancel(), or pollAgent.listRuns()first.
Guardrails for headless runs
| Knob | Scope | What it does |
|---|---|---|
tools: ["read", "grep", "glob", "ls"] |
local | Allowlist built-in tools; [] = text-only |
disallowedTools: ["shell"] |
local | Deny list; deny wins over tools. "mcp" also removes custom tools, "task" disables subagents |
local.sandboxOptions: { enabled: true } |
local | Writes confined to cwd + temp; outbound network denied except hosts in .cursor/sandbox.json; bubblewrap on Linux/WSL |
local.autoReview: true |
local | Routes Shell/MCP/Fetch calls through the IDE's Auto-review classifier; blocked calls are denied, not escalated. Best-effort, not a security boundary |
.cursor/hooks.json |
local + cloud | File-based policy (beforeShellExecution, preToolUse, …). There is no programmatic hook callback |
| Cloud VM | cloud | Always isolated; sandboxOptions doesn't apply |
tools, disallowedTools, and systemPrompt are not persisted, so pass them again on Agent.resume(). Stack the layers. A CI review bot should be read-only and sandboxed:
const reviewer = await Agent.create({
apiKey: process.env.CURSOR_API_KEY!,
model: { id: "composer-2.5" },
tools: ["read", "grep", "glob", "ls"],
local: { cwd: process.cwd(), sandboxOptions: { enabled: true }, settingSources: ["project"] },
});
settingSources: ["project"] makes the local agent load this repo's .cursor/ config, which includes rules, .cursor/mcp.json, and .cursor/agents/*.md. Without it, only inline config is loaded. Cloud agents always load project/team/plugins and ignore the field.
Custom tools — your functions, no MCP server
const agent = await Agent.create({
apiKey: process.env.CURSOR_API_KEY!,
model: { id: "composer-2.5" },
local: {
cwd: process.cwd(),
customTools: {
get_deploy_status: {
description: "Current Vercel deployment status for a project.",
inputSchema: {
type: "object",
properties: { project: { type: "string" } },
required: ["project"],
},
annotations: { readOnlyHint: true },
async execute({ project }) {
const res = await fetch(`https://internal.example/deploys/${project}`);
return await res.json(); // string | JSON | { content, isError?, structuredContent? }
},
},
},
},
});
Custom tools are registered as an MCP server named custom-user-tools, reach subagents, run in your process (so they can use anything your code can), and skip interactive approval. Treat each one like a public API route: validate args, and keep secrets in the closure, never in the return value. Local agents only; cloud rejects them.
MCP servers and subagents
await Agent.create({
apiKey: process.env.CURSOR_API_KEY!,
model: { id: "composer-2.5" },
local: { cwd: process.cwd() },
mcpServers: {
context7: { type: "stdio", command: "npx", args: ["-y", "@upstash/context7-mcp"] },
supabase: {
type: "http",
url: "https://mcp.supabase.com/mcp",
headers: { Authorization: `Bearer ${process.env.SUPABASE_ACCESS_TOKEN!}` },
},
},
agents: {
"security-reviewer": {
description: "Reviews diffs for secrets, injection, and missing webhook verification.",
prompt: "Follow CLAUDE.md security rules. Report findings; do not edit.",
model: "inherit",
},
},
});
- Precedence: per-
send()servers replace (not merge) creation-time ones, then plugins →.cursor/mcp.json→~/.cursor/mcp.json(the file layers are gated bysettingSources). - Inline
mcpServersare not persisted acrossAgent.resume(), deliberately, since they carry secrets. Re-pass them, or use file-based config for servers that should survive. - Local OAuth MCP servers only work if you've already signed in from the Cursor app, because the SDK can't open a browser for them. On cloud, HTTP
headers/authstay in Cursor's backend, while stdioenvvalues enter the VM. - Subagents from
.cursor/agents/*.mdare picked up too; inline definitions win on name clashes.
Models, cost, and errors
- Discover ids and params with
Cursor.models.list(); per-model options go inmodel.params(e.g.[{ id: "fast", value: "true" }]).composer-2is retired and reroutes tocomposer-2.5.autoroutes by Cursor Router mode. - Per-run tokens:
run.usage/result.usage. Billed dollars:agent.getUsage()orAgent.getUsage(agentId). - Every error extends
CursorSdkErrorwithisRetryable,code,status, andrequestId. Branch retries onisRetryable, not on message text.RateLimitErrorandNetworkErrorare the transient ones. - Bundling to one file (
bun build --compile, esbuild): import@cursor/sdk/bundledand shipnode_modules/@cursor/sdk-<os>-<arch>/beside the binary, or sandboxing throwsConfigurationError.
Known limitations (as of SDK 1.0.x)
- Custom tools, Auto-review, custom stores,
tools/disallowedTools, andsystemPromptare local-only. listArtifacts()/downloadArtifact()are cloud-only (local returns[]/ throws).run.steer(text)only lands on local runs; cloud always returnsrevert_to_followup.systemPromptreplaces Cursor's built-in prompt entirely (tool protocol included), and must be enabled per account.
codeAmani notes
CLAUDE.mdis already the rule file. Cursor auto-loads it and always applies it. One source of truth for both Claude Code and Cursor; add.cursor/rules/*.mdconly for glob-scoped guidance (app/**,app/api/webhooks/**) thatCLAUDE.mdcan't express.- Secrets stay server-side and out of context.
.env*is ignored by default, but.cursorignoreis explicitly not a security boundary — an agent-run terminal command or MCP tool can still read those files. Keep live credentials in Hazina and.env.local; never paste a key into the chat panel. Rungitleaksbefore any push, per house policy. - Two agents, one repo, no conflict. Cursor's agent and Claude Code both run inside the same WSL distro against the same Linux checkout. Cursor earns its keep on tight edit loops (
Tab,Ctrl+K, a scoped Agent refactor with instant diffs); Claude Code stays primary for long multi-step work underCLAUDE.mdand the tech-stack MCP. Don't run both against the same working tree simultaneously — checkpoint restores and mid-flight edits fight. - Model routing. Cursor's picker exposes Anthropic, OpenAI, Google, xAI, and Cursor's own Composer models. The house policy still holds: Claude for complex reasoning and code gen; reach for a cheaper tier on mechanical edits.
Ctrl+/cycles models mid-conversation — use it, the default is rarely the right cost tier for a rename. - Extension supply chain. Cursor pulls extensions from Open VSX through its own proxy (
marketplace.cursorapi.com) with automated malware scanning, not the Microsoft Marketplace. The samepublisher.extensionID can resolve to a different publisher than on the Microsoft Marketplace. Treat extension IDs like dependencies.extensions.installCooldownHoursadds a delay before installing freshly published versions. - Next.js 15 on WSL. With the repo on
/home, Turbopack's file watching,next dev, andpnpm installrun at native speed. Ports forward to Windows automatically, solocalhost:3000in a Windows browser hits the WSL dev server — which is what you want for Chrome DevTools MCP verification. - Kenya-targeted projects. Nothing Cursor-specific, but the mobile-first, low-bandwidth constraints from
AFRICAN_MARKET_GUIDE.mdare exactly the kind of thing to put in a glob-scoped rule onapp/**(budget the JS bundle, lazy-load below the fold) so the agent doesn't cheerfully add a 200 KB chart library to a page a Nairobi user loads on 3G. - SDK = a new server-side secret and a new autonomous actor.
CURSOR_API_KEYlives in Hazina / Vercel env, never in client bundles. Use a service-account key for CI bots so spend and PRs attribute to the team, not a person. Never run a default (auto-approve) local SDK agent against a tree holding.env.local— combinetools/disallowedToolswithsandboxOptions, and prefer cloud agents that open PRs for write-capable automation so a human still reviews before merge. - Provenance unaffected. Cursor is an editor; it ships no artifact. SLSA policy (
supply-chain/) attaches to what the repo publishes, not to what edited it.
Troubleshooting
| Issue | Fix |
|---|---|
| Blank screen on startup | Restart; on Windows run as administrator; Ctrl+Shift+P → Clear Editor History |
| Update stuck | Ctrl+Shift+P → Cursor: Attempt Update, restart |
| Tab suggesting nothing | Check the Tab status indicator — it may be snoozed or disabled for that file extension |
| A rule "isn't working" | Rules apply to Agent only. Also confirm .mdc, not .md, and that globs actually match |
| MCP server won't start in a WSL window | The command runs in the Linux shell — POSIX paths and Linux-installed deps only |
| Editor sluggish | cursor --disable-extensions, then re-enable one at a time |
| Want the raw docs | Append .md to any docs URL; full index at https://cursor.com/llms.txt |
Official docs: