cursor-cloud-mcp 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cursor_cloud_mcp-0.3.1/.gitignore +14 -0
- cursor_cloud_mcp-0.3.1/LICENSE +21 -0
- cursor_cloud_mcp-0.3.1/PKG-INFO +213 -0
- cursor_cloud_mcp-0.3.1/README.md +187 -0
- cursor_cloud_mcp-0.3.1/pyproject.toml +60 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/__init__.py +3 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/__main__.py +11 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/activity.py +158 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/artifacts.py +156 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/budget.py +34 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/catalog.py +155 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/client.py +884 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/compat.py +74 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/config.py +111 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/errors.py +140 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/fixture.py +393 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/models.py +701 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/present.py +342 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/redaction.py +89 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/server.py +776 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/sessions.py +410 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/shapes.py +35 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/slicing.py +22 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/stream.py +513 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/supervision.py +321 -0
- cursor_cloud_mcp-0.3.1/src/cursor_cloud_mcp/validation.py +181 -0
- cursor_cloud_mcp-0.3.1/tests/__init__.py +0 -0
- cursor_cloud_mcp-0.3.1/tests/conftest.py +18 -0
- cursor_cloud_mcp-0.3.1/tests/fixtures/fixture_server.py +19 -0
- cursor_cloud_mcp-0.3.1/tests/test_client.py +308 -0
- cursor_cloud_mcp-0.3.1/tests/test_compute.py +637 -0
- cursor_cloud_mcp-0.3.1/tests/test_interface.py +258 -0
- cursor_cloud_mcp-0.3.1/tests/test_reliability.py +532 -0
- cursor_cloud_mcp-0.3.1/tests/test_stdio.py +201 -0
- cursor_cloud_mcp-0.3.1/tests/test_supervision.py +784 -0
- cursor_cloud_mcp-0.3.1/tests/test_tools.py +623 -0
- cursor_cloud_mcp-0.3.1/uv.lock +769 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yoch Melka
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cursor-cloud-mcp
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Minimal stdio MCP server for the Cursor Cloud Agents API v1
|
|
5
|
+
Project-URL: Homepage, https://github.com/yoch/cursor-cloud-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/yoch/cursor-cloud-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/yoch/cursor-cloud-mcp/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/yoch/cursor-cloud-mcp#readme
|
|
9
|
+
Author: Yoch Melka
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: cloud-agents,cursor,mcp,model-context-protocol
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Requires-Dist: httpx<0.29,>=0.28.1
|
|
24
|
+
Requires-Dist: mcp<3,>=2.3.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# cursor-cloud-mcp
|
|
28
|
+
|
|
29
|
+
To have an agent install or use this MCP server, give it [`AGENT_GUIDE.md`](https://github.com/yoch/cursor-cloud-mcp/blob/main/AGENT_GUIDE.md).
|
|
30
|
+
|
|
31
|
+
Local MCP server, over stdio, that exposes seventeen tools for the Cursor Cloud Agents v1 REST API. A single implementation serves Claude Code, Codex CLI and OpenCode. It lets a calling agent create a Cloud session, choose the model and the reasoning level, send commands, read progress and produced files, then archive or delete the session. It is not an orchestration platform.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Python 3.12 or newer. The package is published on PyPI as `cursor-cloud-mcp`. The simplest is to let the MCP client start it with `uvx`, which needs neither a clone nor an absolute path:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uvx cursor-cloud-mcp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The first start downloads the package and its dependencies, then `uvx` reuses its cache. To pin the installed version, or for a client whose startup timeout is short, install it once and register the `cursor-cloud-mcp` command instead:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv tool install cursor-cloud-mcp # or: pipx install cursor-cloud-mcp
|
|
45
|
+
uv tool upgrade cursor-cloud-mcp # later, to update it
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A client started from a desktop launcher may not inherit the shell's `PATH`: if the command is not found, register the absolute path printed by `command -v uvx` (or `command -v cursor-cloud-mcp`).
|
|
49
|
+
|
|
50
|
+
## Development
|
|
51
|
+
|
|
52
|
+
In a copy of this repository, with `uv`:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv sync
|
|
56
|
+
uv run pytest
|
|
57
|
+
uv run cursor-cloud-mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The repository environment's binary is `.venv/bin/cursor-cloud-mcp`. You can also run `uv run python -m cursor_cloud_mcp`.
|
|
61
|
+
|
|
62
|
+
To check the wheel in a clean environment:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv build
|
|
66
|
+
uv venv /tmp/cursor-cloud-mcp-wheel
|
|
67
|
+
uv pip install --python /tmp/cursor-cloud-mcp-wheel/bin/python dist/*.whl
|
|
68
|
+
/tmp/cursor-cloud-mcp-wheel/bin/cursor-cloud-mcp
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Releases: `__version__` in `src/cursor_cloud_mcp/__init__.py` is the only version number. Pushing a tag `vX.Y.Z` equal to it runs `.github/workflows/release.yml`, which checks, tests and builds the package, then publishes it on PyPI through Trusted Publishing (no stored token).
|
|
72
|
+
|
|
73
|
+
## Variables
|
|
74
|
+
|
|
75
|
+
| Variable | Role |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `CURSOR_API_KEY` | Key read only from the process environment. If absent, the server starts and lists its tools; the first Cursor call fails with a clear error. |
|
|
78
|
+
| `CURSOR_MCP_ALLOW_WRITES` | `0` by default. `1` allows creation, follow-up runs, cancellation and archiving. |
|
|
79
|
+
| `CURSOR_MCP_ALLOW_DELETE` | `0` by default. `1` allows `cursor_delete_agent`, in addition to `CURSOR_MCP_ALLOW_WRITES=1` and `confirm_agent_id`. |
|
|
80
|
+
| `CURSOR_MCP_FORWARD_ENV` | Comma-separated list of names whose values `forward_env` may read from this process. The value does not pass through the tool argument. |
|
|
81
|
+
| `CURSOR_MCP_LOG_LEVEL` | `INFO` by default. Logs go to stderr. |
|
|
82
|
+
| `CURSOR_MCP_FIXTURE` | `1` replaces the API with local responses. Refused if combined with `CURSOR_API_KEY`. Startup announces it on stderr. |
|
|
83
|
+
|
|
84
|
+
The server does not load a `.env` file. An empty value, `${...}` or `{env:...}` is refused, without being logged. No tool changes `CURSOR_MCP_ALLOW_WRITES`: you must edit the environment and restart the client.
|
|
85
|
+
|
|
86
|
+
Logs may contain the tool name, its outcome (`ok`, a business error code, `unexpected:<type>` or `cancelled`), the duration, identifiers, the HTTP status and a request id. They contain neither the prompt, nor the body, nor the key, nor the Authorization header. As a second barrier, the formatter masks the key, the values of `CURSOR_MCP_FORWARD_ENV` and the `env_vars` values passed during the life of the process, including in tracebacks. A value shorter than 8 characters is masked only as a whole word, so as not to mangle the rest of the text (`en` does not touch `agent`). The key and `CURSOR_MCP_FORWARD_ENV` stay masked permanently; the 4096 most recent `env_vars` values are masked too. The same masking applies to the values of errors returned to the caller, without changing the shape of the JSON.
|
|
87
|
+
|
|
88
|
+
## Tools
|
|
89
|
+
|
|
90
|
+
Reads: `cursor_get_account`, `cursor_list_models`, `cursor_list_repositories`, `cursor_list_agents`, `cursor_supervise`, `cursor_get_agent`, `cursor_list_runs`, `cursor_get_run`, `cursor_read_run_events`, `cursor_get_usage`, `cursor_list_artifacts`, `cursor_read_artifact`.
|
|
91
|
+
|
|
92
|
+
Mutations: `cursor_create_agent`, `cursor_create_run`, `cursor_cancel_run`, `cursor_archive_agent`, `cursor_delete_agent`.
|
|
93
|
+
|
|
94
|
+
Responses are compact JSON, identical in the text and in `structuredContent`. A field with no value is omitted rather than rendered as `null`. Errors are pure JSON too: the text of an `isError` result is the error object itself (`code`, `message`, and context fields). Only a malformed argument, rejected by the MCP SDK before the tool runs, still comes back as plain text.
|
|
95
|
+
|
|
96
|
+
`cursor_list_models` returns a compact catalog, cached for ten minutes: for each model, `params` (possible values of each parameter), `defaults` (values of the default variant), `reasoning_param` (actual name of the reasoning level: `effort`, `reasoning_effort` or `reasoning`) and `aliases`. `restricted_combinations` signals that some of the combinations do not exist. With `model_id`, the tool returns only that model, with its valid variants. Each model publishes its own list of variants, that is, the combinations it accepts. Returned for all the models, they pushed the catalog beyond 240 KB per call; the compact form brings it to about 9, and the variants remain the reference for the pre-send check.
|
|
97
|
+
|
|
98
|
+
`cursor_create_agent` can start with no repository, with one repository (`repository` and `starting_ref`) or with up to twenty repositories (`repositories`, elements `{url, starting_ref}`). `starting_ref` is a branch name, sent as is in `startingRef`. A full 40- or 64-character SHA is refused locally: the API answered `400 validation_error` to a SHA on October 1, 2026, although the REST documentation says a reference can be a SHA. This refusal is a dated workaround, to be requalified by a real test. `env_type` is `cloud`, `pool` or `machine`. A named pool is required for several repositories. A named cloud environment cannot be combined with repositories. `model_id` accepts an id or an alias that designates only one model (`opus` designates several and is refused). `reasoning_level` and `model_params` are checked against the catalog before sending, combination included: a combination absent from the published variants is refused without a POST. `workOnCurrentBranch` is forced to `false`. `autoCreatePR` follows the caller (`false` by default). The `bc-<uuid>` identifier is the caller's or generated once before sending, and it is returned even in `MUTATION_OUTCOME_UNKNOWN`: reuse it if the call is cut off. With `env_vars` or `forward_env`, the API forbids `agentId`: `name` becomes required and an unknown outcome is resolved with `cursor_list_agents(name=...)`. The response contains `agent_id`, `run_id` and the URL, without waiting for the run to finish.
|
|
99
|
+
|
|
100
|
+
`cursor_create_run` sends a follow-up command to the same agent. Without `model_id`, the agent keeps its current model. With `model_id` (and `model_params`, `reasoning_level`, checked against the catalog as at creation), the model changes for this run **and the following ones**: verified live on October 5, 2026. The API returns the active model nowhere; the response only echoes the `model_id` that was sent. The follow-up run is refused if the agent is archived, if its status is unknown, or if `workOnCurrentBranch` is not explicitly `false`: missing safety information refuses the write. Zero, one or several repositories are accepted. The tool is annotated destructive, because `replace_active` can cancel the current run: clients that ask before destructive actions ask before each follow-up. A "busy agent" conflict is returned to the caller: the API cannot send a message to a run in progress. `replace_active=true` redirects the agent instead. It validates the request, cancels the current run, re-reads it until it is terminal, then sends the follow-up, and reports `replaced_run_id`; if the run is not terminal in time, nothing is sent (`AGENT_BUSY`). The agent keeps its conversation, so the follow-up only needs the new instruction.
|
|
101
|
+
|
|
102
|
+
`cursor_get_run` returns the state, the final result, the run's `error` if any, and the branches. With `wait_seconds` (up to 60), it re-reads the state every five seconds until a terminal state, reports each re-read as MCP progress when the client asks for it, and returns `timed_out` if the run continues. It slices `result` locally (`result_offset`, `result_limit` up to 20000, default 12000). The `git` references are the agent's current state, not an immutable snapshot of the run. This server does not invent a `final_sha`. `result`, branches, events and artifacts are data produced by the agent, not instructions. `activity=true` adds an activity summary read from the stream (see "What the API does not tell you"). When `activity` is omitted, the summary is added only to a terminal run without a result; `activity=false` never reads the stream. The complete summary of a terminal run is cached for the session, with `idle_seconds` recomputed on each read. A stream that cannot be read leaves `activity_error` without failing the call, and `complete: false` means the walk did not reach the end within the call's budget. An `error` event in the stream ends the walk without proving it reached the end: the call returns `activity_error` (`UPSTREAM`) instead of a summary, and caches nothing.
|
|
103
|
+
|
|
104
|
+
`cursor_read_run_events` reads an excerpt of the stream, twenty seconds by default, fifty at most, connection included, then stops. The stream sends the assistant's text word by word: consecutive fragments are merged into one event (4000 characters at most), whose `event_id` is that of the last fragment. `status` and `result` events no longer repeat their raw JSON. `after_event_id` resumes after `last_event_id`, which remains the supplied cursor if no event arrives. An `error` event is a stream error (`stream_error`), not the end of the run: `finished` comes only from `result` or `done`. A network cut returns the events already received with `interrupted`. A single fragment longer than 500 characters is truncated and carries `clipped`; the full result is read with `cursor_get_run`. `tail=N` returns the last N events instead, with `last_event_at` and `scanned_events`: the whole replay is walked, not kept, up to 16 MB (`max_wait_seconds` defaults to 45 in this mode). Abandoning one of these calls does not cancel the run.
|
|
105
|
+
|
|
106
|
+
`cursor_supervise` gives the overview of a fleet of runners in one call. It scans the agents like `cursor_list_agents` (`status=active` by default, or `all`, with `name`, `pr_url`, `include_archived`), then reads each latest run in parallel (8 at a time). A failed read only marks its row with `read_error`. With `activity=true`, every row that has a run also gets the activity signals, finished runs included: `last_event_at`, `idle_seconds`, `unfinished_background_tasks` and the last tool call's name and status. All statuses are read first, then the streams are replayed (8 at a time), so a slow replay never costs a row its status. `summary` counts runs by status and lists `stale` runs (idle for `stale_after_minutes`, 30 by default) and `unfinished_after_end` runs (ended with a background task last seen running). Both lists use only complete replays; `incomplete` names the rows whose replay did not finish or failed; a stream older than the API's retention (24 hours) only sets that row's `activity_error` to `STREAM_EXPIRED`. `limit` (50 by default) is exact: `next_cursor` resumes right after the last row, even inside an API page, and must be passed back unchanged.
|
|
107
|
+
|
|
108
|
+
Observed limitation: during a real trial, an agent wrote `artifacts/result.txt` in its VM and the stream confirmed it, but `cursor_list_artifacts` stayed empty and the download answered `404 artifact_not_found`. For a computation result, ask the agent to put it in its final response (`cursor_get_run`) or in a Git branch.
|
|
109
|
+
|
|
110
|
+
`cursor_list_artifacts` lists the files under `artifacts/`. `cursor_read_artifact` reads a UTF-8 text of at most 5 MB, without sending the Cursor key to the storage, and only if the host ends with `.amazonaws.com`. For a binary or a file that is too large, it returns the presigned URL (about fifteen minutes) with `text_unavailable`; `url_only=true` returns the URL without downloading.
|
|
111
|
+
|
|
112
|
+
`cursor_get_usage` copies the tokens and the cost returned by the API, in cents of a dollar (`raw_cents`, `charged_cents`), in total and per run. A missing cost stays missing.
|
|
113
|
+
|
|
114
|
+
`cursor_archive_agent` archives an agent, or unarchives it with `unarchive=true`: this is reversible. `cursor_delete_agent` is permanent.
|
|
115
|
+
|
|
116
|
+
Agent and run lists return one page. `has_more` is false when `nextCursor` is absent. `include_archived` filters the agent list when provided: `true` adds the archived agents, which are otherwise absent. The order of agents is not guaranteed by creation date (observed live), and the API does not filter by name: `name` walks up to five pages of one hundred agents, filters locally (substring, case-insensitive), returns at most `limit` matches and reports `scanned`; `next_cursor` resumes right after the last match, even inside an API page, and must be passed back unchanged with the same `name`. `pr_url` is an API filter: it returns the agent linked to that pull request. `cursor_list_repositories` accepts `query`, a local filter on URLs, and reports `total_count`. Each agent carries its `url` (`https://cursor.com/agents/bc-...`), the direct link to the web interface.
|
|
117
|
+
|
|
118
|
+
Each tool call has an absolute budget, shared by all its sub-operations (catalog, POST, re-reads, pauses): 45 seconds by default, 95 for the repository list, agent creation and sending a follow-up run, 45 for cancellation, `max_wait_seconds` for the stream, and `wait_seconds` (at least 45) for waiting on a run, plus 45 when `cursor_get_run` reads the activity summary, capped at 95 in total; 90 for `cursor_supervise` with `activity`. No budget exceeds 95 seconds. Each HTTP request is also bounded (40 seconds, 90 for a creation POST): a real creation exceeded 40 seconds. A mutation is not sent if less than 5 seconds of budget remain: the tool then returns `TIMEOUT` without having sent anything. An MCP client's timeout must exceed these budgets. The examples set Codex to 100 seconds and OpenCode to 100000 milliseconds. A client that cuts earlier may abandon a creation that was already sent and, if it retries without the same `agent_id`, pay for a second one.
|
|
119
|
+
|
|
120
|
+
## What the API does not tell you
|
|
121
|
+
|
|
122
|
+
Measured on October 6, 2026, on real runs. The left column is an API limit, which this server cannot fix; the right column is what it does about it.
|
|
123
|
+
|
|
124
|
+
| API limit | What this server does |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `FINISHED` means the agent ended its turn, not that its job is done | `activity.background_tasks`: background commands seen in the stream (`run_terminal_cmd` with `isBackground`), with their last observed state from `await` (`running` or `complete`); `unfinished_background_tasks` counts those last seen running |
|
|
127
|
+
| A `RUNNING` run keeps `updatedAt` at creation time | `activity.last_event_at` and `idle_seconds`: stream event ids are millisecond timestamps |
|
|
128
|
+
| An `ERROR` run has neither `error` nor `result` | the activity summary is added automatically: last assistant text, last tool call, background tasks |
|
|
129
|
+
| No way to read the end of a stream: a made-up `Last-Event-ID` returns no past event | `tail` and `activity` walk the whole replay (839 KB for a 7-hour run) without keeping it |
|
|
130
|
+
| No end-of-replay marker | the walk stops on the run's result, the first live event (newer than the connection), or the server's first heartbeat (30 to 36 s after connecting, always after the replay); never on a silence, since replays pause up to 1.3 s |
|
|
131
|
+
| A run in progress cannot receive a message (`agent_busy`) | `cursor_create_run(replace_active=true)`: cancel, confirm, follow up on the same agent, which keeps its conversation |
|
|
132
|
+
| No listing of runs across agents | `cursor_supervise`: one call, parallel reads |
|
|
133
|
+
|
|
134
|
+
These signals are what the stream last showed, not a view inside the VM: a background task last seen `running` may have been killed since.
|
|
135
|
+
|
|
136
|
+
## Heavy computation
|
|
137
|
+
|
|
138
|
+
The API does not choose the CPU, RAM or GPU size of a Cursor VM. For a heavy computation, create the agent with `env_type` `pool` or `machine`: these are self-hosted workers, on the user's machines. A hosted Cursor VM remains `env_type` `cloud`, with or without a repository.
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
cursor_list_models
|
|
142
|
+
→ cursor_create_agent(prompt, model_id, reasoning_level, env_type, env_name, name)
|
|
143
|
+
→ keep agent_id and run_id
|
|
144
|
+
→ cursor_get_run(wait_seconds=60), repeat while timed_out; cursor_read_run_events to follow progress
|
|
145
|
+
→ cursor_get_run for the final text, cursor_get_usage for the cost
|
|
146
|
+
→ cursor_list_artifacts then cursor_read_artifact
|
|
147
|
+
→ cursor_create_run on the same agent if a follow-up run is needed
|
|
148
|
+
→ cursor_archive_agent, then cursor_delete_agent only with both guards
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Secret values go through `forward_env`, whose names are listed in `CURSOR_MCP_FORWARD_ENV`. `env_vars` is suitable only for values the calling agent can already see. Neither is logged. Both are incompatible with a caller-supplied `agent_id`.
|
|
152
|
+
|
|
153
|
+
## GitHub loop
|
|
154
|
+
|
|
155
|
+
This MCP server does not replace GitHub. The caller pushes the desired commit to a branch, checks that the branch head is that SHA, then chains:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
Push the commit to a branch and check its head
|
|
159
|
+
→ cursor_create_agent(..., starting_ref=branch-name)
|
|
160
|
+
→ keep agent_id and run_id
|
|
161
|
+
→ cursor_get_run(..., wait_seconds=60) until a terminal state
|
|
162
|
+
→ re-read GitHub: HEAD, diff, checks, reviews
|
|
163
|
+
→ cursor_create_run(...) on the same agent if fixes are needed, with `model_id` to change model.
|
|
164
|
+
→ re-read GitHub after the new run
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`FINISHED` proves neither that the tests ran nor that the pull request is correct. An unknown state is not a success. After a mutation timeout or cut, the `MUTATION_OUTCOME_UNKNOWN` code forbids an automatic replay: re-read the agent whose identifier is returned. Changing that identifier may create a duplicate. A client cut does not cancel the Cloud run.
|
|
168
|
+
|
|
169
|
+
Cancellation does not delete commits that were already pushed. It is asynchronous: the server re-reads the run up to four times, two seconds apart, within its budget. `outcome` distinguishes `cancelled` (state `CANCELLED` re-read, the only case where `outcome_confirmed` is true), `ended_without_cancel` (the run ended otherwise, for example `FINISHED` during the race), `still_running` and `unknown` (re-read impossible). A cut after a mutation is sent, including while reading the response body, yields `MUTATION_OUTCOME_UNKNOWN` with the known `agent_id`, `run_id`, `previous_latest_run_id`, HTTP status and request id.
|
|
170
|
+
|
|
171
|
+
## Configurations
|
|
172
|
+
|
|
173
|
+
The fragments in [`examples/`](https://github.com/yoch/cursor-cloud-mcp/blob/main/examples) start the server with `uvx cursor-cloud-mcp`. With `uv tool install`, the command becomes `cursor-cloud-mcp` without arguments. For a development copy, use the absolute path of `.venv/bin/cursor-cloud-mcp`.
|
|
174
|
+
|
|
175
|
+
To allow a real mutation, set `CURSOR_MCP_ALLOW_WRITES` to `1` in the relevant client's configuration, then restart that client. The key is passed through the environment, never as a command-line argument.
|
|
176
|
+
|
|
177
|
+
Entering the key without leaving it in the history:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
read -r -s -p 'Cursor key: ' CURSOR_API_KEY
|
|
181
|
+
printf '\n'
|
|
182
|
+
export CURSOR_API_KEY
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Claude Code, project configuration `.mcp.json`: see `examples/claude.mcp.json`. Check with `claude mcp list`, `claude mcp get cursor_cloud` and `/mcp`. The project file may ask for approval. For a user configuration, the form consistent with the installed help is:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
claude mcp add --transport stdio --scope user cursor_cloud -- uvx cursor-cloud-mcp
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Then provide `CURSOR_API_KEY` in the process environment, not in the command. `CURSOR_MCP_ALLOW_WRITES` goes in the JSON's `env` entry, not on the command line with the key.
|
|
192
|
+
|
|
193
|
+
Codex: `examples/codex.config.toml`. The key goes through `env_vars`, not through a `${...}` interpolation in the TOML. Check with `codex mcp list` and `/mcp`.
|
|
194
|
+
|
|
195
|
+
OpenCode: `examples/opencode.json`. The key uses `{env:CURSOR_API_KEY}`. The public documentation describes `timeout` as the tool discovery timeout. On OpenCode 2.0.20, `opencode debug config` loads a single numeric `timeout` both as the catalog timeout and as the execution timeout. The example sets it to 100000 ms, above the 90-second timeout of the repository list. Check with `opencode debug config`, then a tool call in a session. `opencode mcp list` may not display a server that the project configuration does load.
|
|
196
|
+
|
|
197
|
+
## Troubleshooting
|
|
198
|
+
|
|
199
|
+
- The server lists its tools but every call says the key is missing: the MCP process environment does not contain `CURSOR_API_KEY`. A repository `.env` is not read.
|
|
200
|
+
- The key is displayed as not interpolated: the value is still `${CURSOR_API_KEY}` or `{env:CURSOR_API_KEY}`.
|
|
201
|
+
- A mutation answers `READ_ONLY`: `CURSOR_MCP_ALLOW_WRITES` is not exactly `1`, or the client was not restarted.
|
|
202
|
+
- `CONTINUATION_REFUSED`: the agent is archived, its status is unknown, or `workOnCurrentBranch` is not explicitly `false` (true or absent).
|
|
203
|
+
- `DELETE_DISABLED`: `CURSOR_MCP_ALLOW_DELETE` is not exactly `1`.
|
|
204
|
+
- `STREAM_EXPIRED`: the stream can no longer be replayed. Read `cursor_get_run`.
|
|
205
|
+
- `MUTATION_OUTCOME_UNKNOWN`: do not resend the same creation with a new identifier. Call `cursor_get_agent` with the returned identifier. Without an `agent_id`, look for the agent with `cursor_list_agents(name=...)`.
|
|
206
|
+
- `GET /v1/repositories` can be slow and is heavily rate-limited (1 request per minute, 30 per hour). The five-minute cache covers only the current process.
|
|
207
|
+
- stderr announces `SIMULATED MODE` when `CURSOR_MCP_FIXTURE=1`. This variable with a real key prevents any call.
|
|
208
|
+
- stdout must remain the MCP channel. If a client reports invalid JSON, look for a `print` or a log that is not on stderr.
|
|
209
|
+
- "bad interpreter" or "No such file" after moving a development copy: a virtual environment keeps absolute paths in its scripts. Run `uv sync` again in the new place, or register `uv run --directory /path/to/cursor-cloud-mcp cursor-cloud-mcp` as the command.
|
|
210
|
+
|
|
211
|
+
## Out of scope
|
|
212
|
+
|
|
213
|
+
No MCP HTTP server, no database, no local shell, no reading of the local checkout, no pull request merging, no images, no remote MCP servers in the VM, no custom subagents declared in the request, no budget cap enforced by this process. The API also does not allow setting the CPU, RAM or GPU size of a Cursor VM.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# cursor-cloud-mcp
|
|
2
|
+
|
|
3
|
+
To have an agent install or use this MCP server, give it [`AGENT_GUIDE.md`](https://github.com/yoch/cursor-cloud-mcp/blob/main/AGENT_GUIDE.md).
|
|
4
|
+
|
|
5
|
+
Local MCP server, over stdio, that exposes seventeen tools for the Cursor Cloud Agents v1 REST API. A single implementation serves Claude Code, Codex CLI and OpenCode. It lets a calling agent create a Cloud session, choose the model and the reasoning level, send commands, read progress and produced files, then archive or delete the session. It is not an orchestration platform.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Python 3.12 or newer. The package is published on PyPI as `cursor-cloud-mcp`. The simplest is to let the MCP client start it with `uvx`, which needs neither a clone nor an absolute path:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uvx cursor-cloud-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The first start downloads the package and its dependencies, then `uvx` reuses its cache. To pin the installed version, or for a client whose startup timeout is short, install it once and register the `cursor-cloud-mcp` command instead:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv tool install cursor-cloud-mcp # or: pipx install cursor-cloud-mcp
|
|
19
|
+
uv tool upgrade cursor-cloud-mcp # later, to update it
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A client started from a desktop launcher may not inherit the shell's `PATH`: if the command is not found, register the absolute path printed by `command -v uvx` (or `command -v cursor-cloud-mcp`).
|
|
23
|
+
|
|
24
|
+
## Development
|
|
25
|
+
|
|
26
|
+
In a copy of this repository, with `uv`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv sync
|
|
30
|
+
uv run pytest
|
|
31
|
+
uv run cursor-cloud-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The repository environment's binary is `.venv/bin/cursor-cloud-mcp`. You can also run `uv run python -m cursor_cloud_mcp`.
|
|
35
|
+
|
|
36
|
+
To check the wheel in a clean environment:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv build
|
|
40
|
+
uv venv /tmp/cursor-cloud-mcp-wheel
|
|
41
|
+
uv pip install --python /tmp/cursor-cloud-mcp-wheel/bin/python dist/*.whl
|
|
42
|
+
/tmp/cursor-cloud-mcp-wheel/bin/cursor-cloud-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Releases: `__version__` in `src/cursor_cloud_mcp/__init__.py` is the only version number. Pushing a tag `vX.Y.Z` equal to it runs `.github/workflows/release.yml`, which checks, tests and builds the package, then publishes it on PyPI through Trusted Publishing (no stored token).
|
|
46
|
+
|
|
47
|
+
## Variables
|
|
48
|
+
|
|
49
|
+
| Variable | Role |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `CURSOR_API_KEY` | Key read only from the process environment. If absent, the server starts and lists its tools; the first Cursor call fails with a clear error. |
|
|
52
|
+
| `CURSOR_MCP_ALLOW_WRITES` | `0` by default. `1` allows creation, follow-up runs, cancellation and archiving. |
|
|
53
|
+
| `CURSOR_MCP_ALLOW_DELETE` | `0` by default. `1` allows `cursor_delete_agent`, in addition to `CURSOR_MCP_ALLOW_WRITES=1` and `confirm_agent_id`. |
|
|
54
|
+
| `CURSOR_MCP_FORWARD_ENV` | Comma-separated list of names whose values `forward_env` may read from this process. The value does not pass through the tool argument. |
|
|
55
|
+
| `CURSOR_MCP_LOG_LEVEL` | `INFO` by default. Logs go to stderr. |
|
|
56
|
+
| `CURSOR_MCP_FIXTURE` | `1` replaces the API with local responses. Refused if combined with `CURSOR_API_KEY`. Startup announces it on stderr. |
|
|
57
|
+
|
|
58
|
+
The server does not load a `.env` file. An empty value, `${...}` or `{env:...}` is refused, without being logged. No tool changes `CURSOR_MCP_ALLOW_WRITES`: you must edit the environment and restart the client.
|
|
59
|
+
|
|
60
|
+
Logs may contain the tool name, its outcome (`ok`, a business error code, `unexpected:<type>` or `cancelled`), the duration, identifiers, the HTTP status and a request id. They contain neither the prompt, nor the body, nor the key, nor the Authorization header. As a second barrier, the formatter masks the key, the values of `CURSOR_MCP_FORWARD_ENV` and the `env_vars` values passed during the life of the process, including in tracebacks. A value shorter than 8 characters is masked only as a whole word, so as not to mangle the rest of the text (`en` does not touch `agent`). The key and `CURSOR_MCP_FORWARD_ENV` stay masked permanently; the 4096 most recent `env_vars` values are masked too. The same masking applies to the values of errors returned to the caller, without changing the shape of the JSON.
|
|
61
|
+
|
|
62
|
+
## Tools
|
|
63
|
+
|
|
64
|
+
Reads: `cursor_get_account`, `cursor_list_models`, `cursor_list_repositories`, `cursor_list_agents`, `cursor_supervise`, `cursor_get_agent`, `cursor_list_runs`, `cursor_get_run`, `cursor_read_run_events`, `cursor_get_usage`, `cursor_list_artifacts`, `cursor_read_artifact`.
|
|
65
|
+
|
|
66
|
+
Mutations: `cursor_create_agent`, `cursor_create_run`, `cursor_cancel_run`, `cursor_archive_agent`, `cursor_delete_agent`.
|
|
67
|
+
|
|
68
|
+
Responses are compact JSON, identical in the text and in `structuredContent`. A field with no value is omitted rather than rendered as `null`. Errors are pure JSON too: the text of an `isError` result is the error object itself (`code`, `message`, and context fields). Only a malformed argument, rejected by the MCP SDK before the tool runs, still comes back as plain text.
|
|
69
|
+
|
|
70
|
+
`cursor_list_models` returns a compact catalog, cached for ten minutes: for each model, `params` (possible values of each parameter), `defaults` (values of the default variant), `reasoning_param` (actual name of the reasoning level: `effort`, `reasoning_effort` or `reasoning`) and `aliases`. `restricted_combinations` signals that some of the combinations do not exist. With `model_id`, the tool returns only that model, with its valid variants. Each model publishes its own list of variants, that is, the combinations it accepts. Returned for all the models, they pushed the catalog beyond 240 KB per call; the compact form brings it to about 9, and the variants remain the reference for the pre-send check.
|
|
71
|
+
|
|
72
|
+
`cursor_create_agent` can start with no repository, with one repository (`repository` and `starting_ref`) or with up to twenty repositories (`repositories`, elements `{url, starting_ref}`). `starting_ref` is a branch name, sent as is in `startingRef`. A full 40- or 64-character SHA is refused locally: the API answered `400 validation_error` to a SHA on October 1, 2026, although the REST documentation says a reference can be a SHA. This refusal is a dated workaround, to be requalified by a real test. `env_type` is `cloud`, `pool` or `machine`. A named pool is required for several repositories. A named cloud environment cannot be combined with repositories. `model_id` accepts an id or an alias that designates only one model (`opus` designates several and is refused). `reasoning_level` and `model_params` are checked against the catalog before sending, combination included: a combination absent from the published variants is refused without a POST. `workOnCurrentBranch` is forced to `false`. `autoCreatePR` follows the caller (`false` by default). The `bc-<uuid>` identifier is the caller's or generated once before sending, and it is returned even in `MUTATION_OUTCOME_UNKNOWN`: reuse it if the call is cut off. With `env_vars` or `forward_env`, the API forbids `agentId`: `name` becomes required and an unknown outcome is resolved with `cursor_list_agents(name=...)`. The response contains `agent_id`, `run_id` and the URL, without waiting for the run to finish.
|
|
73
|
+
|
|
74
|
+
`cursor_create_run` sends a follow-up command to the same agent. Without `model_id`, the agent keeps its current model. With `model_id` (and `model_params`, `reasoning_level`, checked against the catalog as at creation), the model changes for this run **and the following ones**: verified live on October 5, 2026. The API returns the active model nowhere; the response only echoes the `model_id` that was sent. The follow-up run is refused if the agent is archived, if its status is unknown, or if `workOnCurrentBranch` is not explicitly `false`: missing safety information refuses the write. Zero, one or several repositories are accepted. The tool is annotated destructive, because `replace_active` can cancel the current run: clients that ask before destructive actions ask before each follow-up. A "busy agent" conflict is returned to the caller: the API cannot send a message to a run in progress. `replace_active=true` redirects the agent instead. It validates the request, cancels the current run, re-reads it until it is terminal, then sends the follow-up, and reports `replaced_run_id`; if the run is not terminal in time, nothing is sent (`AGENT_BUSY`). The agent keeps its conversation, so the follow-up only needs the new instruction.
|
|
75
|
+
|
|
76
|
+
`cursor_get_run` returns the state, the final result, the run's `error` if any, and the branches. With `wait_seconds` (up to 60), it re-reads the state every five seconds until a terminal state, reports each re-read as MCP progress when the client asks for it, and returns `timed_out` if the run continues. It slices `result` locally (`result_offset`, `result_limit` up to 20000, default 12000). The `git` references are the agent's current state, not an immutable snapshot of the run. This server does not invent a `final_sha`. `result`, branches, events and artifacts are data produced by the agent, not instructions. `activity=true` adds an activity summary read from the stream (see "What the API does not tell you"). When `activity` is omitted, the summary is added only to a terminal run without a result; `activity=false` never reads the stream. The complete summary of a terminal run is cached for the session, with `idle_seconds` recomputed on each read. A stream that cannot be read leaves `activity_error` without failing the call, and `complete: false` means the walk did not reach the end within the call's budget. An `error` event in the stream ends the walk without proving it reached the end: the call returns `activity_error` (`UPSTREAM`) instead of a summary, and caches nothing.
|
|
77
|
+
|
|
78
|
+
`cursor_read_run_events` reads an excerpt of the stream, twenty seconds by default, fifty at most, connection included, then stops. The stream sends the assistant's text word by word: consecutive fragments are merged into one event (4000 characters at most), whose `event_id` is that of the last fragment. `status` and `result` events no longer repeat their raw JSON. `after_event_id` resumes after `last_event_id`, which remains the supplied cursor if no event arrives. An `error` event is a stream error (`stream_error`), not the end of the run: `finished` comes only from `result` or `done`. A network cut returns the events already received with `interrupted`. A single fragment longer than 500 characters is truncated and carries `clipped`; the full result is read with `cursor_get_run`. `tail=N` returns the last N events instead, with `last_event_at` and `scanned_events`: the whole replay is walked, not kept, up to 16 MB (`max_wait_seconds` defaults to 45 in this mode). Abandoning one of these calls does not cancel the run.
|
|
79
|
+
|
|
80
|
+
`cursor_supervise` gives the overview of a fleet of runners in one call. It scans the agents like `cursor_list_agents` (`status=active` by default, or `all`, with `name`, `pr_url`, `include_archived`), then reads each latest run in parallel (8 at a time). A failed read only marks its row with `read_error`. With `activity=true`, every row that has a run also gets the activity signals, finished runs included: `last_event_at`, `idle_seconds`, `unfinished_background_tasks` and the last tool call's name and status. All statuses are read first, then the streams are replayed (8 at a time), so a slow replay never costs a row its status. `summary` counts runs by status and lists `stale` runs (idle for `stale_after_minutes`, 30 by default) and `unfinished_after_end` runs (ended with a background task last seen running). Both lists use only complete replays; `incomplete` names the rows whose replay did not finish or failed; a stream older than the API's retention (24 hours) only sets that row's `activity_error` to `STREAM_EXPIRED`. `limit` (50 by default) is exact: `next_cursor` resumes right after the last row, even inside an API page, and must be passed back unchanged.
|
|
81
|
+
|
|
82
|
+
Observed limitation: during a real trial, an agent wrote `artifacts/result.txt` in its VM and the stream confirmed it, but `cursor_list_artifacts` stayed empty and the download answered `404 artifact_not_found`. For a computation result, ask the agent to put it in its final response (`cursor_get_run`) or in a Git branch.
|
|
83
|
+
|
|
84
|
+
`cursor_list_artifacts` lists the files under `artifacts/`. `cursor_read_artifact` reads a UTF-8 text of at most 5 MB, without sending the Cursor key to the storage, and only if the host ends with `.amazonaws.com`. For a binary or a file that is too large, it returns the presigned URL (about fifteen minutes) with `text_unavailable`; `url_only=true` returns the URL without downloading.
|
|
85
|
+
|
|
86
|
+
`cursor_get_usage` copies the tokens and the cost returned by the API, in cents of a dollar (`raw_cents`, `charged_cents`), in total and per run. A missing cost stays missing.
|
|
87
|
+
|
|
88
|
+
`cursor_archive_agent` archives an agent, or unarchives it with `unarchive=true`: this is reversible. `cursor_delete_agent` is permanent.
|
|
89
|
+
|
|
90
|
+
Agent and run lists return one page. `has_more` is false when `nextCursor` is absent. `include_archived` filters the agent list when provided: `true` adds the archived agents, which are otherwise absent. The order of agents is not guaranteed by creation date (observed live), and the API does not filter by name: `name` walks up to five pages of one hundred agents, filters locally (substring, case-insensitive), returns at most `limit` matches and reports `scanned`; `next_cursor` resumes right after the last match, even inside an API page, and must be passed back unchanged with the same `name`. `pr_url` is an API filter: it returns the agent linked to that pull request. `cursor_list_repositories` accepts `query`, a local filter on URLs, and reports `total_count`. Each agent carries its `url` (`https://cursor.com/agents/bc-...`), the direct link to the web interface.
|
|
91
|
+
|
|
92
|
+
Each tool call has an absolute budget, shared by all its sub-operations (catalog, POST, re-reads, pauses): 45 seconds by default, 95 for the repository list, agent creation and sending a follow-up run, 45 for cancellation, `max_wait_seconds` for the stream, and `wait_seconds` (at least 45) for waiting on a run, plus 45 when `cursor_get_run` reads the activity summary, capped at 95 in total; 90 for `cursor_supervise` with `activity`. No budget exceeds 95 seconds. Each HTTP request is also bounded (40 seconds, 90 for a creation POST): a real creation exceeded 40 seconds. A mutation is not sent if less than 5 seconds of budget remain: the tool then returns `TIMEOUT` without having sent anything. An MCP client's timeout must exceed these budgets. The examples set Codex to 100 seconds and OpenCode to 100000 milliseconds. A client that cuts earlier may abandon a creation that was already sent and, if it retries without the same `agent_id`, pay for a second one.
|
|
93
|
+
|
|
94
|
+
## What the API does not tell you
|
|
95
|
+
|
|
96
|
+
Measured on October 6, 2026, on real runs. The left column is an API limit, which this server cannot fix; the right column is what it does about it.
|
|
97
|
+
|
|
98
|
+
| API limit | What this server does |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `FINISHED` means the agent ended its turn, not that its job is done | `activity.background_tasks`: background commands seen in the stream (`run_terminal_cmd` with `isBackground`), with their last observed state from `await` (`running` or `complete`); `unfinished_background_tasks` counts those last seen running |
|
|
101
|
+
| A `RUNNING` run keeps `updatedAt` at creation time | `activity.last_event_at` and `idle_seconds`: stream event ids are millisecond timestamps |
|
|
102
|
+
| An `ERROR` run has neither `error` nor `result` | the activity summary is added automatically: last assistant text, last tool call, background tasks |
|
|
103
|
+
| No way to read the end of a stream: a made-up `Last-Event-ID` returns no past event | `tail` and `activity` walk the whole replay (839 KB for a 7-hour run) without keeping it |
|
|
104
|
+
| No end-of-replay marker | the walk stops on the run's result, the first live event (newer than the connection), or the server's first heartbeat (30 to 36 s after connecting, always after the replay); never on a silence, since replays pause up to 1.3 s |
|
|
105
|
+
| A run in progress cannot receive a message (`agent_busy`) | `cursor_create_run(replace_active=true)`: cancel, confirm, follow up on the same agent, which keeps its conversation |
|
|
106
|
+
| No listing of runs across agents | `cursor_supervise`: one call, parallel reads |
|
|
107
|
+
|
|
108
|
+
These signals are what the stream last showed, not a view inside the VM: a background task last seen `running` may have been killed since.
|
|
109
|
+
|
|
110
|
+
## Heavy computation
|
|
111
|
+
|
|
112
|
+
The API does not choose the CPU, RAM or GPU size of a Cursor VM. For a heavy computation, create the agent with `env_type` `pool` or `machine`: these are self-hosted workers, on the user's machines. A hosted Cursor VM remains `env_type` `cloud`, with or without a repository.
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
cursor_list_models
|
|
116
|
+
→ cursor_create_agent(prompt, model_id, reasoning_level, env_type, env_name, name)
|
|
117
|
+
→ keep agent_id and run_id
|
|
118
|
+
→ cursor_get_run(wait_seconds=60), repeat while timed_out; cursor_read_run_events to follow progress
|
|
119
|
+
→ cursor_get_run for the final text, cursor_get_usage for the cost
|
|
120
|
+
→ cursor_list_artifacts then cursor_read_artifact
|
|
121
|
+
→ cursor_create_run on the same agent if a follow-up run is needed
|
|
122
|
+
→ cursor_archive_agent, then cursor_delete_agent only with both guards
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Secret values go through `forward_env`, whose names are listed in `CURSOR_MCP_FORWARD_ENV`. `env_vars` is suitable only for values the calling agent can already see. Neither is logged. Both are incompatible with a caller-supplied `agent_id`.
|
|
126
|
+
|
|
127
|
+
## GitHub loop
|
|
128
|
+
|
|
129
|
+
This MCP server does not replace GitHub. The caller pushes the desired commit to a branch, checks that the branch head is that SHA, then chains:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
Push the commit to a branch and check its head
|
|
133
|
+
→ cursor_create_agent(..., starting_ref=branch-name)
|
|
134
|
+
→ keep agent_id and run_id
|
|
135
|
+
→ cursor_get_run(..., wait_seconds=60) until a terminal state
|
|
136
|
+
→ re-read GitHub: HEAD, diff, checks, reviews
|
|
137
|
+
→ cursor_create_run(...) on the same agent if fixes are needed, with `model_id` to change model.
|
|
138
|
+
→ re-read GitHub after the new run
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`FINISHED` proves neither that the tests ran nor that the pull request is correct. An unknown state is not a success. After a mutation timeout or cut, the `MUTATION_OUTCOME_UNKNOWN` code forbids an automatic replay: re-read the agent whose identifier is returned. Changing that identifier may create a duplicate. A client cut does not cancel the Cloud run.
|
|
142
|
+
|
|
143
|
+
Cancellation does not delete commits that were already pushed. It is asynchronous: the server re-reads the run up to four times, two seconds apart, within its budget. `outcome` distinguishes `cancelled` (state `CANCELLED` re-read, the only case where `outcome_confirmed` is true), `ended_without_cancel` (the run ended otherwise, for example `FINISHED` during the race), `still_running` and `unknown` (re-read impossible). A cut after a mutation is sent, including while reading the response body, yields `MUTATION_OUTCOME_UNKNOWN` with the known `agent_id`, `run_id`, `previous_latest_run_id`, HTTP status and request id.
|
|
144
|
+
|
|
145
|
+
## Configurations
|
|
146
|
+
|
|
147
|
+
The fragments in [`examples/`](https://github.com/yoch/cursor-cloud-mcp/blob/main/examples) start the server with `uvx cursor-cloud-mcp`. With `uv tool install`, the command becomes `cursor-cloud-mcp` without arguments. For a development copy, use the absolute path of `.venv/bin/cursor-cloud-mcp`.
|
|
148
|
+
|
|
149
|
+
To allow a real mutation, set `CURSOR_MCP_ALLOW_WRITES` to `1` in the relevant client's configuration, then restart that client. The key is passed through the environment, never as a command-line argument.
|
|
150
|
+
|
|
151
|
+
Entering the key without leaving it in the history:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
read -r -s -p 'Cursor key: ' CURSOR_API_KEY
|
|
155
|
+
printf '\n'
|
|
156
|
+
export CURSOR_API_KEY
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Claude Code, project configuration `.mcp.json`: see `examples/claude.mcp.json`. Check with `claude mcp list`, `claude mcp get cursor_cloud` and `/mcp`. The project file may ask for approval. For a user configuration, the form consistent with the installed help is:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
claude mcp add --transport stdio --scope user cursor_cloud -- uvx cursor-cloud-mcp
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Then provide `CURSOR_API_KEY` in the process environment, not in the command. `CURSOR_MCP_ALLOW_WRITES` goes in the JSON's `env` entry, not on the command line with the key.
|
|
166
|
+
|
|
167
|
+
Codex: `examples/codex.config.toml`. The key goes through `env_vars`, not through a `${...}` interpolation in the TOML. Check with `codex mcp list` and `/mcp`.
|
|
168
|
+
|
|
169
|
+
OpenCode: `examples/opencode.json`. The key uses `{env:CURSOR_API_KEY}`. The public documentation describes `timeout` as the tool discovery timeout. On OpenCode 2.0.20, `opencode debug config` loads a single numeric `timeout` both as the catalog timeout and as the execution timeout. The example sets it to 100000 ms, above the 90-second timeout of the repository list. Check with `opencode debug config`, then a tool call in a session. `opencode mcp list` may not display a server that the project configuration does load.
|
|
170
|
+
|
|
171
|
+
## Troubleshooting
|
|
172
|
+
|
|
173
|
+
- The server lists its tools but every call says the key is missing: the MCP process environment does not contain `CURSOR_API_KEY`. A repository `.env` is not read.
|
|
174
|
+
- The key is displayed as not interpolated: the value is still `${CURSOR_API_KEY}` or `{env:CURSOR_API_KEY}`.
|
|
175
|
+
- A mutation answers `READ_ONLY`: `CURSOR_MCP_ALLOW_WRITES` is not exactly `1`, or the client was not restarted.
|
|
176
|
+
- `CONTINUATION_REFUSED`: the agent is archived, its status is unknown, or `workOnCurrentBranch` is not explicitly `false` (true or absent).
|
|
177
|
+
- `DELETE_DISABLED`: `CURSOR_MCP_ALLOW_DELETE` is not exactly `1`.
|
|
178
|
+
- `STREAM_EXPIRED`: the stream can no longer be replayed. Read `cursor_get_run`.
|
|
179
|
+
- `MUTATION_OUTCOME_UNKNOWN`: do not resend the same creation with a new identifier. Call `cursor_get_agent` with the returned identifier. Without an `agent_id`, look for the agent with `cursor_list_agents(name=...)`.
|
|
180
|
+
- `GET /v1/repositories` can be slow and is heavily rate-limited (1 request per minute, 30 per hour). The five-minute cache covers only the current process.
|
|
181
|
+
- stderr announces `SIMULATED MODE` when `CURSOR_MCP_FIXTURE=1`. This variable with a real key prevents any call.
|
|
182
|
+
- stdout must remain the MCP channel. If a client reports invalid JSON, look for a `print` or a log that is not on stderr.
|
|
183
|
+
- "bad interpreter" or "No such file" after moving a development copy: a virtual environment keeps absolute paths in its scripts. Run `uv sync` again in the new place, or register `uv run --directory /path/to/cursor-cloud-mcp cursor-cloud-mcp` as the command.
|
|
184
|
+
|
|
185
|
+
## Out of scope
|
|
186
|
+
|
|
187
|
+
No MCP HTTP server, no database, no local shell, no reading of the local checkout, no pull request merging, no images, no remote MCP servers in the VM, no custom subagents declared in the request, no budget cap enforced by this process. The API also does not allow setting the CPU, RAM or GPU size of a Cursor VM.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cursor-cloud-mcp"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Minimal stdio MCP server for the Cursor Cloud Agents API v1"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Yoch Melka" }]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
keywords = ["mcp", "model-context-protocol", "cursor", "cloud-agents"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Environment :: Console",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Topic :: Software Development",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"httpx>=0.28.1,<0.29",
|
|
24
|
+
"mcp>=2.3.0,<3",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/yoch/cursor-cloud-mcp"
|
|
29
|
+
Repository = "https://github.com/yoch/cursor-cloud-mcp"
|
|
30
|
+
Issues = "https://github.com/yoch/cursor-cloud-mcp/issues"
|
|
31
|
+
Documentation = "https://github.com/yoch/cursor-cloud-mcp#readme"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
cursor-cloud-mcp = "cursor_cloud_mcp.__main__:main"
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest==8.4.2",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["hatchling"]
|
|
43
|
+
build-backend = "hatchling.build"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.version]
|
|
46
|
+
path = "src/cursor_cloud_mcp/__init__.py"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/cursor_cloud_mcp"]
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.sdist]
|
|
52
|
+
include = ["src", "tests", "README.md", "LICENSE", "pyproject.toml", "uv.lock"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
addopts = "-q"
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
line-length = 120
|
|
60
|
+
target-version = "py312"
|