Metadata-Version: 2.4
Name: cursor-sdk
Version: 0.1.1
Summary: Python client for the Cursor SDK bridge.
Author: Cursor
License: Proprietary
Project-URL: Homepage, https://cursor.com
Project-URL: Documentation, https://cursor.com/docs/api/sdk
Project-URL: Repository, https://github.com/cursor/cursor
Project-URL: Issues, https://github.com/anysphere/everysphere/issues
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: httpx<1,>=0.27
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
Dynamic: license-file

# cursor-sdk

Python client for the Cursor SDK bridge.

The Python SDK mirrors the public TypeScript SDK concepts from
`@cursor/sdk`, but the main interface is Python-first: simple `Agent`
helpers, explicit clients when needed, snake_case option helpers, typed
dataclasses, and normal iteration for streams and pages. It talks to a
local `cursor-sdk-bridge` process, which embeds the TypeScript SDK and
exposes the stable `sdk.v1` Connect protocol.

## Public beta

The Python SDK is in public beta. APIs may change before general
availability.

## Overview

The SDK wraps local and cloud runtimes behind one interface. You write
the same code regardless of where the agent runs.

| Runtime | What it does | When to use |
| --- | --- | --- |
| Local | Runs the agent through the local SDK bridge. Files come from disk. | Dev scripts and CI checks against a working tree. |
| Cloud (Cursor-hosted) | Runs in an isolated VM with your repo cloned in. Cursor runs the VMs. | When the caller does not have the repo, you want many agents in parallel, or runs need to survive the caller disconnecting. |
| Cloud (self-hosted) | Same shape, but targets a self-hosted pool. | Same reasons as Cursor-hosted, plus code, secrets, and build artifacts must stay in your environment. |

Runtime is picked by which key you pass to `Agent.create()` (`local` or
`cloud`). Use the same `CURSOR_API_KEY` for either.

## Authentication

Set `CURSOR_API_KEY` (or pass `api_key`) before creating an agent.

The SDK accepts user API keys and service account API keys for both
local and cloud runs. Team Admin API keys are not yet supported.

```bash
export CURSOR_API_KEY="your-key"
```

## Installation

```bash
pip install cursor-sdk
```

Platform-specific wheels bundle `cursor-sdk-bridge` and its Node
runtime, so no separate bridge install is needed on macOS arm64/x64,
Linux arm64/x64, or Windows x64.

For source installs, custom bridge builds, or unsupported platforms,
set `CURSOR_SDK_BRIDGE_BIN` to a bridge launcher on disk. To connect
to an already-running bridge, pass `CURSOR_SDK_BRIDGE_URL` and
`CURSOR_SDK_BRIDGE_TOKEN` to `Client(...)`.

### Preview access

The package is installable publicly, but API access is allowlisted
during the preview. If your team is not enabled for
`sdk_python_preview_access`, `Cursor.me()` and agent endpoints raise
`IntegrationNotConnectedError` with a link to request access:
<https://cursor.com/sdk/python>.

## Quick start

```python
import os

from cursor_sdk import Agent, LocalAgentOptions

agent = Agent.create(
    model="composer-2",
    local=LocalAgentOptions(cwd=os.getcwd()),
)
try:
    run = agent.send("Summarize what this repository does")

    for message in run.messages():
        print(message)

    result = run.wait()
    print(result.status, result.result)
finally:
    agent.close()
```

`Agent.create` and explicit-client `client.agents.create` resolve
`api_key` in this order:

1. The `api_key=` keyword argument, if you pass one explicitly.
2. The `CURSOR_API_KEY` environment variable, when env-var fallback is
   enabled (the default for local-bridge clients started by the SDK
   itself; opt in for remote bridges with
   `Client(allow_api_key_env_fallback=True)`).

It raises `ConfigurationError` if neither is set, or if `model` is
missing — `Agent.create()` with no arguments is rejected, not a
silent no-op.

There is no module-level `cursor_sdk.api_key = "..."` global. Use
`CURSOR_API_KEY` or the `api_key=` argument.

## Async usage

The async client mirrors the sync surface and is recommended for servers,
bots, and concurrent agent orchestration. It is powered by
`httpx.AsyncClient`.

```python
import asyncio
from cursor_sdk import AsyncClient, LocalAgentOptions

async def main() -> None:
    async with await AsyncClient.launch_bridge(workspace=".") as client:
        agent = await client.agents.create(
            model="composer-2",
            local=LocalAgentOptions(cwd="."),
        )
        run = await agent.send("Summarize what this repository does")
        async for message in run.messages():
            print(message)
        result = await run.wait()
        print(result.status)

asyncio.run(main())
```

`AsyncAgent`, `AsyncClient`, `AsyncRun`, and `AsyncCursor` are exported
from both `cursor_sdk` and `cursor_sdk.asyncio`. There is no global
async default client. Instantiate `AsyncClient` explicitly (or use
`AsyncClient.launch_bridge(...)` as an async context manager) so each
event loop owns its own handle. Sync and async clients should not be
mixed in the same code path.

## Explicit client lifecycle

The sync helpers (`Agent.*` and `Cursor.*`) start or reuse a module-level
bridge client and close it automatically at process exit. Use
`CursorClient` when you need explicit lifecycle control, a custom bridge
endpoint, custom HTTP options, or multiple workspaces in one process:

```python
from pathlib import Path

from cursor_sdk import CursorClient, LocalAgentOptions

with CursorClient.launch_bridge(workspace=Path.cwd()) as client:
    agent = client.agents.create(
        model="composer-2",
        local=LocalAgentOptions(cwd=Path.cwd()),
    )
    run = agent.send("Summarize what this repository does")
    result = run.wait()
    print(result.status, result.result)
```

## Custom HTTP clients

Both clients accept a custom `httpx` client for proxies, transports, and
other advanced httpx configuration:

```python
import httpx
from cursor_sdk import CursorClient, DefaultHttpxClient

client = CursorClient(
    bridge.endpoint,
    http_client=DefaultHttpxClient(
        proxy="http://my.test.proxy.example.com",
        transport=httpx.HTTPTransport(local_address="0.0.0.0"),
    ),
)
```

```python
from cursor_sdk.asyncio import AsyncClient, DefaultAsyncHttpxClient

async_client = AsyncClient(
    endpoint=bridge.endpoint,
    http_client=DefaultAsyncHttpxClient(proxy="http://my.proxy.example.com"),
)
```

`DefaultHttpxClient` / `DefaultAsyncHttpxClient` retain the SDK's defaults
for `timeout` and `follow_redirects`. Plain `httpx.Client` /
`httpx.AsyncClient` will use httpx's defaults instead.

## Configuring timeouts per call

Both clients expose `with_options(...)`, mirroring the OpenAI SDK. You can
override timeouts and retry behavior for a small group of calls:

```python
short = client.with_options(timeout=5.0, max_retries=2)
short.agents.create(...)

# Async equivalent:
short = async_client.with_options(timeout=5.0, max_retries=2)
await short.agents.create(...)
```

`with_options` returns a shallow copy that shares the underlying transport
with the original. Closing one does not close the other.

## Creating agents

```python
from cursor_sdk import (
    Agent,
    CloudAgentOptions,
    CloudRepository,
    LocalAgentOptions,
)

agent = Agent.create(
    model="composer-2",
    local=LocalAgentOptions(cwd="/path/to/repo"),
)

cloud_agent = Agent.create(
    model="composer-2",
    cloud=CloudAgentOptions(
        repos=[
            CloudRepository(
                url="https://github.com/your-org/your-repo",
                starting_ref="main",
            ),
        ],
        auto_create_pr=True,
    ),
)
```

`agent.agent_id` is populated immediately. Local agents get an `agent-`
ID; cloud agents get a `bc-` ID. `agent.model` is a typed
`ModelSelection`, so `agent.model.id` and `agent.model.params` work
directly.

### Session environment variables

For cloud agents, pass `env_vars` when a run needs short-lived
credentials or other values that should live only with that agent.

```python
agent = Agent.create(
    model="composer-2",
    cloud=CloudAgentOptions(
        repos=[CloudRepository(url="https://github.com/your-org/your-repo")],
        env_vars={
            "STAGING_API_TOKEN": os.environ["STAGING_API_TOKEN"],
        },
    ),
)
```

These values are encrypted at rest, injected into the cloud agent's
shell, and deleted with the agent. `env_vars` cannot be used with a
caller-supplied `agent_id`. Variable names cannot start with `CURSOR_`.

### Model parameters

Use `model.params` to pass per-model options such as reasoning effort or
max mode. Parameter IDs and values vary by model. Use
`Cursor.models.list()` to discover supported parameters and preset
variants for your account.

```python
from cursor_sdk import Agent, Cursor, LocalAgentOptions, ModelSelection, ModelParameterValue

models = Cursor.models.list()
composer = next((m for m in models if m.id == "composer-2"), None)
print(composer.parameters if composer else [])

agent = Agent.create(
    model=ModelSelection(
        id="composer-2",
        params=[ModelParameterValue(id="thinking", value="high")],
    ),
    local=LocalAgentOptions(cwd=os.getcwd()),
)
```

### Passing raw wire dicts (advanced)

The SDK still accepts raw dicts in addition to typed dataclasses, which
can be convenient for short scripts or for piping in externally-supplied
JSON. Snake_case keys are normalized, and bridge-shaped camelCase keys
remain available as an escape hatch:

```python
agent = Agent.create(
    {
        "api_key": os.environ["CURSOR_API_KEY"],
        "model": {"id": "composer-2"},
        "local": {"cwd": os.getcwd()},
    }
)
```

The dataclass form is preferred for application code — IDE
autocomplete, type checking, and consistency with the rest of the
Python surface all work better against the typed shapes.

## SDKAgent

The handle returned by `Agent.create()` and `Agent.resume()`.

```python
run = agent.send("Find the bug in src/auth.py")
agent.reload()
artifacts = agent.list_artifacts()
content = agent.download_artifact(artifacts[0].path)
agent.close()
```

Use `with` for automatic cleanup:

```python
with Agent.create(model="composer-2", local=LocalAgentOptions(cwd=os.getcwd())) as agent:
    run = agent.send("Explain this repository")
    run.wait()
```

When you use the static `Agent.*` or `Cursor.*` helpers without passing a
`client=`, the SDK starts or reuses a module-level bridge client. It is
closed automatically at process exit, and you can close it explicitly with
`close_default_client()`.

## Agent.prompt()

One-shot convenience: creates an agent, sends a single prompt, waits for
the run to finish, and disposes.

```python
result = Agent.prompt(
    "What does the auth middleware do?",
    AgentOptions(
        model="composer-2",
        local=LocalAgentOptions(cwd=os.getcwd()),
    ),
)
```

## Sending messages

Each `agent.send()` returns a `Run`. The agent retains conversation
context across runs; the run is the unit of work for one prompt.

```python
run = agent.send("Find the bug in src/auth.py")

for message in run.messages():
    if getattr(message, "type", "") == "assistant":
        print(message.message)

run2 = agent.send("Fix it and add a regression test")
run2.wait()
```

To send images alongside text:

```python
run = agent.send(
    {
        "text": "What's in this screenshot?",
        "images": [{"data": base64_png, "mimeType": "image/png"}],
    }
)
```

### Waiting without streaming

```python
result = run.wait()
print(result.status)
print(result.result)
print(result.model)
print(result.duration_ms)
print(result.git)
```

### Cancelling a run

```python
run.cancel()
```

### Reading run state

```python
print(run.id)
print(run.status)

stop = run.on_did_change_status(lambda status: print(status))
stop()

turns = run.conversation()
```

### Per-run model override

```python
run = agent.send(
    "Plan the refactor",
    SendOptions(
        model=ModelSelection(
            id="composer-2",
            params=[ModelParameterValue(id="thinking", value="high")],
        ),
    ),
)
```

The `model` passed to `agent.send()` overrides the selection for that
run and becomes sticky on the agent. Subsequent sends without an
override continue to use the new model.

### Streaming raw deltas

Pass `on_delta` and `on_step` callbacks in `SendOptions`. The Python SDK
sets the bridge's `enableDeltas` / `enableSteps` flags and calls the
callbacks as matching stream events arrive.

```python
from cursor_sdk import SendOptions

run = agent.send(
    "Refactor the utils module",
    SendOptions(
        on_delta=lambda update: print(update.text)
        if update.type == "text-delta"
        else None,
        on_step=lambda step: print(step.type),
    ),
)
run.wait()
```

## Stream events

`run.messages()` and its older alias `run.stream()` yield typed SDK
message dataclasses. Discriminate on `message.type`. All messages
include `agent_id` and `run_id` when the runtime provides them.

`run.events()` yields lower-level `RunStreamEvent` envelopes. Use it
when you need offsets, terminal result envelopes, or raw interaction
updates.

Supported message dataclasses:

- `SDKSystemMessage`
- `SDKUserMessageEvent`
- `SDKAssistantMessage`
- `SDKThinkingMessage`
- `SDKToolUseMessage`
- `SDKStatusMessage`
- `SDKTaskMessage`
- `SDKRequestMessage`

Tool payload fields are unstable. Treat `args` and `result` as
`object`/`dict` and parse defensively.

## Interaction updates

`InteractionUpdate` is the raw delta type passed to `on_delta`. The
Python SDK exposes typed dataclasses for the documented update variants,
including `TextDeltaUpdate`, `ThinkingDeltaUpdate`,
`ToolCallStartedUpdate`, `ToolCallCompletedUpdate`,
`PartialToolCallUpdate`, `TokenDeltaUpdate`, `StepStartedUpdate`,
`StepCompletedUpdate`, `TurnEndedUpdate`, `SummaryUpdate`, and shell
output deltas. Unknown future update types fall back to
`UnknownInteractionUpdate`.

The concrete update / step subclasses live in `cursor_sdk.events`:

```python
from cursor_sdk.events import TextDeltaUpdate, ToolCallStartedUpdate

if isinstance(update, TextDeltaUpdate):
    print(update.text)
```

They remain importable from `cursor_sdk` too for backward
compatibility, but new code should import from `cursor_sdk.events` so
the package root stays focused on the high-level surface.

## Conversation types

`run.conversation()` returns a typed per-turn view parsed from the bridge
conversation JSON:

- `ConversationTurn`
- `AgentConversationTurn`
- `ShellConversationTurn`
- `ConversationStep`
- `AssistantMessage`
- `ThinkingMessage`
- `ShellCommand`
- `ShellOutput`

## Resuming agents

```python
agent = Agent.resume("bc-abc123")
run = agent.send("Also update the changelog")
run.wait()
```

Inline MCP servers are not persisted across resume. Pass them again on
resume if the run needs those servers.

## Inspecting agents and runs

```python
from cursor_sdk import CursorClient

with CursorClient.launch_bridge(workspace=os.getcwd()) as client:
    agents = client.agents.list(runtime="local", cwd=os.getcwd())
    for agent_info in agents.auto_paging_iter():
        print(agent_info.agent_id)

    info = client.agents.get(agents.items[0].agent_id)
    runs = client.agents.list_runs(info.agent_id)
    run = client.agents.get_run(runs.items[0].id)
```

Use `Agent.get(agent_id).list_messages()` to read the message history of a
specific agent.

List endpoints return `ListResult[T]`. You can use `.items` and
`.next_cursor` directly, iterate the current page with `for item in page`,
or iterate all pages with `.auto_paging_iter()`. Async list endpoints
return `AsyncListResult[T]`; `async for item in page` walks the current
page, and `async for item in page.auto_paging_iter()` walks every page
in the result set — symmetric with the sync API.

### Cloud agent lifecycle

Cloud agents stay in your team's workspace until you archive or delete
them. Archived agents remain readable and can be restored. Each
operation has two forms:

```python
# By id (no agent handle required):
Agent.archive(agent_id)
Agent.unarchive(agent_id)
Agent.delete(agent_id)

# On an existing agent handle:
agent.archive()
agent.unarchive()
agent.delete()
```

The earlier `Agent.archive_agent(...)` / `Agent.unarchive_agent(...)` /
`Agent.delete_agent(...)` and `Agent.lifecycle.archive(...)` /
`Agent.lifecycle.unarchive(...)` / `Agent.lifecycle.delete(...)`
spellings still work but emit `DeprecationWarning` and will be removed
in a future release.

## The Cursor namespace

Account-level and catalog reads. All methods take optional `api_key` and
otherwise fall back to `CURSOR_API_KEY`.

```python
from cursor_sdk import Cursor

me = Cursor.me()
models = Cursor.models.list()
repositories = Cursor.repositories.list()
```

## MCP servers

Agents can pick up MCP servers from inline definitions, project/user
settings, plugins, and dashboard-managed configuration depending on the
runtime.

```python
from cursor_sdk import (
    Agent,
    AgentOptions,
    HttpMcpServerConfig,
    LocalAgentOptions,
    McpAuth,
    StdioMcpServerConfig,
)

agent = Agent.create(
    AgentOptions(
        model="auto",
        local=LocalAgentOptions(cwd=os.getcwd()),
        mcp_servers={
            "docs": HttpMcpServerConfig(
                url="https://example.com/mcp",
                auth=McpAuth(client_id="client-id", scopes=["read", "write"]),
            ),
            "filesystem": StdioMcpServerConfig(
                command="npx",
                args=["-y", "@modelcontextprotocol/server-filesystem", os.getcwd()],
                cwd=os.getcwd(),
            ),
        },
    )
)
```

Flat dicts (`{"type": "http", "url": ...}` / `{"type": "stdio", "command": ...}`)
are still accepted as a quick-script convenience. The nested bridge wire
form (`{"http": {...}}` / `{"stdio": {...}}`) is also accepted as an
escape hatch for callers that already have serialized bridge config.

The SDK also exposes `HttpMcpServerConfig`, `SseMcpServerConfig`, and
`StdioMcpServerConfig` helper dataclasses.

## Subagents

Define named subagents inline:

```python
from cursor_sdk import AgentDefinition

agent = Agent.create(
    {
        "apiKey": os.environ["CURSOR_API_KEY"],
        "model": {"id": "composer-2"},
        "local": {"cwd": os.getcwd()},
        "agents": {
            "code-reviewer": AgentDefinition(
                description="Expert code reviewer for quality and security.",
                prompt="Review code for bugs, security issues, and proven approaches.",
                model="inherit",
            ).to_json()
        },
    }
)
```

Subagents committed to `.cursor/agents/*.md` are also picked up through
the normal Cursor configuration layers.

## Hooks

Hooks are file-based only. There is no programmatic hook callback. Hooks
are a project policy boundary, not a per-run knob.

## Artifacts

```python
artifacts = agent.list_artifacts()
for artifact in artifacts:
    print(artifact.path, artifact.size_bytes)

content = agent.download_artifact(artifacts[0].path)
```

Artifact support is runtime-dependent. Local SDK agents currently return
no artifacts and throw for `download_artifact`.

## Configuration reference

The Python SDK accepts raw bridge dictionaries or helper dataclasses.
Raw dictionaries use the bridge JSON/proto field names (`apiKey`,
`mcpServers`, `autoCreatePr`, `startingRef`, etc.). The TypeScript-doc
spelling `autoCreatePR` is also accepted and normalized to the bridge
wire field. Dataclasses use Python `snake_case` fields.

Helper dataclasses:

- `AgentOptions`
- `CloudAgentOptions`
- `LocalAgentOptions`
- `SendOptions`
- `LocalSendOptions`
- `AgentDefinition`
- `ModelSelection`
- `ModelParameterValue`
- `HttpMcpServerConfig`
- `SseMcpServerConfig`
- `StdioMcpServerConfig`
- `UserMessage`
- `SDKImage`
- `SDKImageDimension`

## Errors

All SDK errors extend `CursorAgentError`. Use `is_retryable` to drive
retry logic.

```python
class CursorAgentError(Exception):
    is_retryable: bool
    code: str | None
    cause: BaseException | None
    proto_error_code: str | None
```

Error subclasses:

- `AuthenticationError`
- `RateLimitError`
- `ConfigurationError`
- `IntegrationNotConnectedError`
- `NetworkError`
- `UnknownAgentError`
- `UnsupportedRunOperationError`

`IntegrationNotConnectedError` exposes `provider` and `help_url`.

## Known limitations

- Tool-call payload schemas are intentionally not strongly typed.
- Inline MCP servers are not persisted across resume.
- Artifact download is not implemented for local agents.
- `local.settingSources` does not apply to cloud agents.
- Hooks are file-based only.
