← Back to dashboard

Cursor Integration Guide

What is Cursor?

The real model

Agent-first VS Code fork whose Windows story is entirely a WSL story — and whose rules file you already wrote.

Install the editor on Windows, keep the repo at ~/code on ext4, and let anysphere.remote-wsl run the server in the distro; a repo on /mnt/c pays the 9P penalty on every stat() and the agent stats constantly. Check `which node` in the integrated terminal — if it resolves to /mnt/c the agent is shelling out to node.exe across the boundary, and `appendWindowsPath = false` in /etc/wsl.conf is the fix. `cursor .` needs the Windows resources/app/bin directory on the Linux PATH. Cursor 3's Agents Window still cannot do WSL, so stay in the editor window (Ctrl+Shift+P → Open IDE). Rules apply to Agent only — never to Tab, inline edit, or Bugbot — and a .md file inside .cursor/rules/ is silently ignored because the extension must be .mdc. For codeAmani the headline is that Cursor reads CLAUDE.md exactly like AGENTS.md and always applies it, so this repo's conventions are already the agent's rules: don't mirror them into a deprecated .cursorrules, add .mdc files only for glob-scoped guidance like app/api/webhooks/** verifying signatures before parsing.

Seven Cursor primitives

One codebase index, three editor surfaces, a CLI, a TypeScript SDK — and a config layer that this repo mostly already has.

Text
 ██████╗██╗   ██╗██████╗ ███████╗ ██████╗ ██████╗
██╔════╝██║   ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
██║     ██║   ██║██████╔╝███████╗██║   ██║██████╔╝
██║     ██║   ██║██╔══██╗╚════██║██║   ██║██╔══██╗
╚██████╗╚██████╔╝██║  ██║███████║╚██████╔╝██║  ██║
 ╚═════╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝ ╚═════╝ ╚═╝  ╚═╝

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's CLAUDE.md, MCP servers, the agent CLI, and scripting the agent from code with the Cursor TypeScript SDK (@cursor/sdk). Grounded in cursor.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:

SurfaceShortcutScopeReads rules?
TabTab to acceptAutocomplete + multi-line + cross-file jumpsNo
Inline edit (Cmd-K)Ctrl+K (Win/Linux), Cmd+K (Mac)The selection you highlightedNo
AgentCtrl+I / Ctrl+LWhole repo, multi-file, runs terminal commandsYes
agent CLIterminalWhole repo, headless-capable, CI-friendlyYes
@cursor/sdkyour TypeScriptLocal tree or cloud VM, programmaticYes (local: with settingSources)

And it is a distinct product from the two neighbours already documented here:

CursorVS CodeVisual Studio
What it isVS Code fork, agent-firstMicrosoft's editorMicrosoft's full Windows IDE
Extension registryOpen VSX via Cursor's marketplace proxyMicrosoft MarketplaceVSIX / NuGet
WSL extensionanysphere.remote-wsl (first-party rebuild)ms-vscode-remote.remote-wsln/a — Windows-native
Agent config.cursor/rules/*.mdc, AGENTS.md, CLAUDE.mdper-extensionper-extension

See also: wsl/ for the WSL platform itself (install, .wslconfig, the filesystem rule, systemd) and visual-studio/ for the unrelated .NET IDE. This guide only covers Cursor.

Official Documentation

Every docs page has a .md twin — append .md to any URL (https://cursor.com/docs/rules.md) to get clean markdown. https://cursor.com/llms.txt lists 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.

Bash
# 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
PowerShell
# 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.

1. Put the repo on the Linux filesystem

Non-negotiable, and the single biggest performance lever (see wsl/ §5):

Bash
# 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:

PowerShell
wsl --list --verbose
wsl --set-default Ubuntu-24.04

3. Connect

Three routes, all equivalent:

Bash
# 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:

Bash
# ~/.bashrc — adjust <WINUSER> to your Windows username
export PATH="$PATH:/mnt/c/Users/<WINUSER>/AppData/Local/Programs/cursor/resources/app/bin"
Bash
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:

Bash
# 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:

Bash
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

SymptomCause / fix
Extension 'WSL' is required to open the remote windowYou're in the Agents Window — it can't do WSL yet. Switch to the editor (Open IDE).
Everything is slow, disk peggedRepo on /mnt/c, or Windows binaries on the Linux PATH. Move to ~/code; set appendWindowsPath = false.
cursor: command not found in WSLAdd the Windows resources/app/bin to PATH (step 4).
Connects to the wrong distrowsl --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 windowsKnown 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 wedgedClose 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 Tab again 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

Text
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):

ModeUse forEdits files?
AgentBuilding, refactoring, fixingYes
AskUnderstanding architectureNo (read-only)
PlanMulti-file features you want to review firstYes, after you approve the plan
DebugBugs needing runtime evidenceYes

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, and git all behave the way CI does.


Rules — and why codeAmani needs almost none

Cursor has four rule sources, applied in precedence order Team → Project → User:

SourceWhereScope
Project rules.cursor/rules/*.mdcVersion-controlled, glob-scoped
AGENTS.md / CLAUDE.mdrepo rootAlways applied, every conversation
User rulesCursor Settings, or ~/.cursor/rulesYour machine / your account
Team rulesCursor dashboardTeam + 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.

Markdown
---
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/*.
Markdown
---
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:

alwaysApplydescriptionglobsBehaviour
true——Always included
false—providedAuto-attached when a matching file is in context
falseprovided—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.

.cursorrules is legacy. The single root-level .cursorrules file still works but is documented as deprecated — migrate its contents into a .cursor/rules/*.mdc rule 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:

Text
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.

ConfigPathScope
Project.cursor/mcp.jsonThis repo
Global~/.cursor/mcp.jsonEverywhere
JSON
{
  "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.json resolve to the Linux home, and command runs in the Linux shell. A stdio server configured with a Windows path (C:\... or node.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:

JSON
{
  "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.

Bash
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:

Bash
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

RuntimeWhere the agent loop runsFiles come fromUse when
Local (local: {...})Inline in your Node processYour disk (local.cwd)Dev scripts, CI checks against a checked-out tree
Cloud (cloud: {...})Isolated Cursor-hosted VMRepo cloned into the VMCaller 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.

ConceptWhat it is
AgentDurable container: conversation state, workspace config, settings. Survives many prompts.
RunOne prompt submission (agent.send()), with its own stream, status, result, and cancel.
SDKMessageNormalized stream event, same shape for both runtimes.

Install + auth

Bash
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 (engines on @cursor/sdk@1.0.x). The default local store needs node: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 by Cursor.auth.login() (browser flow, stored in ~/.cursor/sdk/auth.json, 90-day TTL).

Quick start — local agent, streamed

TypeScript
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 using disposes the agent when the block exits; outside that syntax call agent.close().
  • run.wait() resolves to a RunResult whose final text is result.result. There is no text/messages/content field. For the step-by-step transcript use run.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

TypeScript
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.repos takes 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.envVars names can't start with CURSOR_, and can't be combined with a caller-supplied agentId. For per-run secrets, pass env vars on agent.send() instead.
  • SDK-started cloud agents are hidden from the default agent list. Use Filter › Source › SDK in Cursor Web.
  • IntegrationNotConnectedError means the repo's GitHub/GitLab integration isn't connected to your Cursor team. Log err.helpUrl, because the default message omits it.
  • A second send() while a cloud run is active throws AgentBusyError, which is not retryable. Wait, run.cancel(), or poll Agent.listRuns() first.

Guardrails for headless runs

KnobScopeWhat it does
tools: ["read", "grep", "glob", "ls"]localAllowlist built-in tools; [] = text-only
disallowedTools: ["shell"]localDeny list; deny wins over tools. "mcp" also removes custom tools, "task" disables subagents
local.sandboxOptions: { enabled: true }localWrites confined to cwd + temp; outbound network denied except hosts in .cursor/sandbox.json; bubblewrap on Linux/WSL
local.autoReview: truelocalRoutes 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.jsonlocal + cloudFile-based policy (beforeShellExecution, preToolUse, …). There is no programmatic hook callback
Cloud VMcloudAlways 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:

TypeScript
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

TypeScript
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

TypeScript
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 by settingSources).
  • Inline mcpServers are not persisted across Agent.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/auth stay in Cursor's backend, while stdio env values enter the VM.
  • Subagents from .cursor/agents/*.md are 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 in model.params (e.g. [{ id: "fast", value: "true" }]). composer-2 is retired and reroutes to composer-2.5. auto routes by Cursor Router mode.
  • Per-run tokens: run.usage / result.usage. Billed dollars: agent.getUsage() or Agent.getUsage(agentId).
  • Every error extends CursorSdkError with isRetryable, code, status, and requestId. Branch retries on isRetryable, not on message text. RateLimitError and NetworkError are the transient ones.
  • Bundling to one file (bun build --compile, esbuild): import @cursor/sdk/bundled and ship node_modules/@cursor/sdk-<os>-<arch>/ beside the binary, or sandboxing throws ConfigurationError.

Known limitations (as of SDK 1.0.x)

  • Custom tools, Auto-review, custom stores, tools/disallowedTools, and systemPrompt are local-only.
  • listArtifacts() / downloadArtifact() are cloud-only (local returns [] / throws).
  • run.steer(text) only lands on local runs; cloud always returns revert_to_followup.
  • systemPrompt replaces Cursor's built-in prompt entirely (tool protocol included), and must be enabled per account.

codeAmani notes

  • CLAUDE.md is 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/*.mdc only for glob-scoped guidance (app/**, app/api/webhooks/**) that CLAUDE.md can't express.
  • Secrets stay server-side and out of context. .env* is ignored by default, but .cursorignore is 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. Run gitleaks before 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 under CLAUDE.md and 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 same publisher.extension ID can resolve to a different publisher than on the Microsoft Marketplace. Treat extension IDs like dependencies. extensions.installCooldownHours adds a delay before installing freshly published versions.
  • Next.js 15 on WSL. With the repo on /home, Turbopack's file watching, next dev, and pnpm install run at native speed. Ports forward to Windows automatically, so localhost:3000 in 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.md are exactly the kind of thing to put in a glob-scoped rule on app/** (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_KEY lives 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 — combine tools/disallowedTools with sandboxOptions, 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

IssueFix
Blank screen on startupRestart; on Windows run as administrator; Ctrl+Shift+P → Clear Editor History
Update stuckCtrl+Shift+P → Cursor: Attempt Update, restart
Tab suggesting nothingCheck 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 windowThe command runs in the Linux shell — POSIX paths and Linux-installed deps only
Editor sluggishcursor --disable-extensions, then re-enable one at a time
Want the raw docsAppend .md to any docs URL; full index at https://cursor.com/llms.txt