mandala-computer-mcp 0.3.0 → 0.5.0
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.
- package/README.md +462 -37
- package/dist/api.d.ts +12 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +262 -42
- package/dist/api.js.map +1 -1
- package/dist/artifacts.d.ts +63 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +81 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +35 -21
- package/dist/cli.js.map +1 -1
- package/dist/credentials.d.ts +33 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +395 -0
- package/dist/credentials.js.map +1 -0
- package/dist/errors.d.ts +53 -10
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +122 -35
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +11 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +213 -95
- package/dist/events.js.map +1 -1
- package/dist/executions.d.ts +43 -0
- package/dist/executions.d.ts.map +1 -0
- package/dist/executions.js +162 -0
- package/dist/executions.js.map +1 -0
- package/dist/format.d.ts +11 -11
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +159 -16
- package/dist/format.js.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +4 -0
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +17 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +17 -0
- package/dist/limits.js.map +1 -0
- package/dist/paths.d.ts +42 -2
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +54 -3
- package/dist/paths.js.map +1 -1
- package/dist/poll.d.ts +5 -1
- package/dist/poll.d.ts.map +1 -1
- package/dist/poll.js +120 -16
- package/dist/poll.js.map +1 -1
- package/dist/results.d.ts +97 -0
- package/dist/results.d.ts.map +1 -0
- package/dist/results.js +263 -0
- package/dist/results.js.map +1 -0
- package/dist/server.d.ts +3 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +61 -9
- package/dist/server.js.map +1 -1
- package/dist/stdio.d.ts +5 -1
- package/dist/stdio.d.ts.map +1 -1
- package/dist/stdio.js +10 -4
- package/dist/stdio.js.map +1 -1
- package/dist/tool-filters.d.ts +38 -0
- package/dist/tool-filters.d.ts.map +1 -0
- package/dist/tool-filters.js +129 -0
- package/dist/tool-filters.js.map +1 -0
- package/dist/tools/account.d.ts +3 -0
- package/dist/tools/account.d.ts.map +1 -0
- package/dist/tools/account.js +122 -0
- package/dist/tools/account.js.map +1 -0
- package/dist/tools/activities.d.ts +3 -0
- package/dist/tools/activities.d.ts.map +1 -0
- package/dist/tools/activities.js +140 -0
- package/dist/tools/activities.js.map +1 -0
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/agent.js +72 -5
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/artifacts.d.ts +3 -0
- package/dist/tools/artifacts.d.ts.map +1 -0
- package/dist/tools/artifacts.js +97 -0
- package/dist/tools/artifacts.js.map +1 -0
- package/dist/tools/chat.d.ts +6 -0
- package/dist/tools/chat.d.ts.map +1 -0
- package/dist/tools/chat.js +192 -0
- package/dist/tools/chat.js.map +1 -0
- package/dist/tools/computers.d.ts.map +1 -1
- package/dist/tools/computers.js +300 -247
- package/dist/tools/computers.js.map +1 -1
- package/dist/tools/directory.d.ts +13 -0
- package/dist/tools/directory.d.ts.map +1 -0
- package/dist/tools/directory.js +88 -0
- package/dist/tools/directory.js.map +1 -0
- package/dist/tools/events.d.ts.map +1 -1
- package/dist/tools/events.js +66 -22
- package/dist/tools/events.js.map +1 -1
- package/dist/tools/executions.d.ts +3 -0
- package/dist/tools/executions.d.ts.map +1 -0
- package/dist/tools/executions.js +87 -0
- package/dist/tools/executions.js.map +1 -0
- package/dist/tools/guest.d.ts.map +1 -1
- package/dist/tools/guest.js +57 -17
- package/dist/tools/guest.js.map +1 -1
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +63 -5
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/results.d.ts +30 -0
- package/dist/tools/results.d.ts.map +1 -0
- package/dist/tools/results.js +106 -0
- package/dist/tools/results.js.map +1 -0
- package/dist/tools/secrets.d.ts +58 -0
- package/dist/tools/secrets.d.ts.map +1 -0
- package/dist/tools/secrets.js +204 -0
- package/dist/tools/secrets.js.map +1 -0
- package/dist/tools/signals.d.ts +3 -0
- package/dist/tools/signals.d.ts.map +1 -0
- package/dist/tools/signals.js +116 -0
- package/dist/tools/signals.js.map +1 -0
- package/dist/tools/snapshots.d.ts.map +1 -1
- package/dist/tools/snapshots.js +213 -167
- package/dist/tools/snapshots.js.map +1 -1
- package/dist/tools/ssh.d.ts +3 -0
- package/dist/tools/ssh.d.ts.map +1 -0
- package/dist/tools/ssh.js +186 -0
- package/dist/tools/ssh.js.map +1 -0
- package/dist/tools/templates.d.ts.map +1 -1
- package/dist/tools/templates.js +9 -13
- package/dist/tools/templates.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,17 +12,54 @@ model looks at the screen and clicks what it sees.
|
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
MCP requires Node 20.3 or newer. The **TypeScript CLI** login setup requires
|
|
16
|
+
Node 22 or newer (`mandala-computer` on npm). Sign in once, then add the MCP
|
|
17
|
+
server:
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
```sh
|
|
20
|
+
npm install -g mandala-computer
|
|
21
|
+
mandala login
|
|
22
|
+
claude mcp add mandala -- npx -y mandala-computer-mcp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
On Node 20, MCP can use an existing saved profile or an environment key.
|
|
26
|
+
The TypeScript CLI guides you through browser approval and saves an API key in
|
|
27
|
+
`~/.mandala/credentials.json`. MCP only reads that file; it never issues or
|
|
28
|
+
approves a device, writes credentials, or starts login automatically. The
|
|
29
|
+
Python distribution also provides a `mandala` command; use the TypeScript
|
|
30
|
+
CLI for `login`.
|
|
31
|
+
|
|
32
|
+
To select a separate profile and workspace:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
mandala login --profile Work --workspace Research
|
|
36
|
+
claude mcp add mandala -- npx -y mandala-computer-mcp --profile Work
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`--profile` takes precedence over `MANDALA_PROFILE`, then the saved default.
|
|
40
|
+
Names are case-sensitive. An explicit key or nonempty `MANDALA_API_KEY` takes
|
|
41
|
+
precedence over every profile and avoids accessing the credential store.
|
|
42
|
+
Empty explicit keys fail; an empty or whitespace-only environment key is absent.
|
|
43
|
+
You can still use a key from **Settings → API keys** with `MANDALA_API_KEY`.
|
|
44
|
+
|
|
45
|
+
Saved credentials require POSIX protection: a real directory owned by you with
|
|
46
|
+
mode `0700`, and an owned regular file with mode `0600` and one link. Symlinks,
|
|
47
|
+
hardlinks, unsafe permissions, malformed files and unsupported protection are
|
|
48
|
+
refused before any API request. Windows file loading is unsupported; explicit
|
|
49
|
+
or environment keys remain available. Only your home directory's store is read.
|
|
50
|
+
|
|
51
|
+
A saved profile also binds its API base URL. `--base-url` or `MANDALA_BASE_URL`
|
|
52
|
+
must match that stored base after canonicalization, including the entire path
|
|
53
|
+
prefix and port. An explicitly empty base flag is invalid with saved credentials.
|
|
54
|
+
Each stdio session resolves once: restart to pick up another saved key. Revoking
|
|
55
|
+
the key in **Settings → API keys** makes later calls fail; MCP does not switch
|
|
56
|
+
profiles, reread the file, or retry a refused action. Run login explicitly when
|
|
57
|
+
new credentials are needed.
|
|
21
58
|
|
|
22
59
|
**Claude Code** — as a plugin, which installs the server and a skill together:
|
|
23
60
|
|
|
24
61
|
```sh
|
|
25
|
-
export MANDALA_API_KEY
|
|
62
|
+
# Run mandala login first, or export MANDALA_API_KEY in this shell.
|
|
26
63
|
/plugin marketplace add mandalacomputer/mcp
|
|
27
64
|
/plugin install mandala-computer@mandala
|
|
28
65
|
```
|
|
@@ -34,9 +71,9 @@ right answer at all, that it costs money until it is suspended or stopped, that
|
|
|
34
71
|
which refusals are worth a second try. It is a description of *when and how*,
|
|
35
72
|
not a second client; once the server is installed it stays out of the way.
|
|
36
73
|
`MANDALA_MODEL_KEY`, if exported alongside, is passed through and turns on
|
|
37
|
-
`run_agent
|
|
74
|
+
`run_agent` when the configured filters permit it.
|
|
38
75
|
|
|
39
|
-
|
|
76
|
+
To use an environment key instead of a saved profile:
|
|
40
77
|
|
|
41
78
|
```sh
|
|
42
79
|
claude mcp add mandala -e MANDALA_API_KEY=com_… -- npx -y mandala-computer-mcp
|
|
@@ -49,8 +86,7 @@ claude mcp add mandala -e MANDALA_API_KEY=com_… -- npx -y mandala-computer-mcp
|
|
|
49
86
|
"mcpServers": {
|
|
50
87
|
"mandala": {
|
|
51
88
|
"command": "npx",
|
|
52
|
-
"args": ["-y", "mandala-computer-mcp"]
|
|
53
|
-
"env": { "MANDALA_API_KEY": "com_…" }
|
|
89
|
+
"args": ["-y", "mandala-computer-mcp"]
|
|
54
90
|
}
|
|
55
91
|
}
|
|
56
92
|
}
|
|
@@ -59,8 +95,11 @@ claude mcp add mandala -e MANDALA_API_KEY=com_… -- npx -y mandala-computer-mcp
|
|
|
59
95
|
**Cursor, Windsurf and the rest** take the same three fields — `command`,
|
|
60
96
|
`args`, `env` — in whichever file they keep their MCP servers in.
|
|
61
97
|
|
|
62
|
-
|
|
63
|
-
|
|
98
|
+
Your MCP client starts this as a subprocess. It uses the saved profile’s API
|
|
99
|
+
base, or `https://app.mandala.computer/api/v1` by default with an environment key.
|
|
100
|
+
Programmatic local hosts can call `runStdio({ profile: 'Work' })`; the exported
|
|
101
|
+
`StdioConfig` allows a local key or profile. `createServer` still requires an
|
|
102
|
+
explicit API key, and `HttpConfig` has no local credential options.
|
|
64
103
|
|
|
65
104
|
## Use
|
|
66
105
|
|
|
@@ -95,6 +134,12 @@ entirely.
|
|
|
95
134
|
|
|
96
135
|
## The tools
|
|
97
136
|
|
|
137
|
+
This is the unfiltered inventory. The filters below can withhold tools from
|
|
138
|
+
both listing and calling; workflows in this README apply only when the needed
|
|
139
|
+
tools are available. The offline fixture checks exercised operation coverage;
|
|
140
|
+
verification against the live publication is a separate required CI gate.
|
|
141
|
+
Parameter and response-mode support remains a separate contract.
|
|
142
|
+
|
|
98
143
|
**Choosing a machine** — `list_templates`, `list_sizes`, `list_computers`, `get_computer`,
|
|
99
144
|
`use_computer`, `wait_for_computer`, `get_desktop_url`
|
|
100
145
|
|
|
@@ -105,9 +150,15 @@ entirely.
|
|
|
105
150
|
**Driving the desktop** — `screenshot`, `click`, `type_text`, `press_key`,
|
|
106
151
|
`scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`, `wait`
|
|
107
152
|
|
|
108
|
-
**Inside the guest** — `exec`, `exec_poll`, `exec_kill`, `
|
|
153
|
+
**Inside the guest** — `exec`, `exec_poll`, `exec_kill`, `get_execution`,
|
|
154
|
+
`read_execution_output`, `open_url`,
|
|
109
155
|
`list_windows`, `window_action`, `read_clipboard`, `write_clipboard`,
|
|
110
|
-
`read_file`, `write_file`
|
|
156
|
+
`read_file`, `write_file`, `list_directory`
|
|
157
|
+
|
|
158
|
+
**Retained versions** — `retain_execution_output`, `get_result`, `read_result_output`,
|
|
159
|
+
`delete_result`, `publish_artifact`, `get_artifact`, `read_artifact`, `delete_artifact`
|
|
160
|
+
|
|
161
|
+
**Passive metadata** — `list_activities`, `get_activity`, `get_activity_results`, `read_signals`
|
|
111
162
|
|
|
112
163
|
**Being told rather than asking** — `wait_for_event`, `poll_events`,
|
|
113
164
|
`wait_for_file_change`
|
|
@@ -121,14 +172,118 @@ entirely.
|
|
|
121
172
|
|
|
122
173
|
**Building one** — `build_template`, `list_builds`, `get_build`, `watch_build`
|
|
123
174
|
|
|
175
|
+
**Account quota** — `get_account`
|
|
176
|
+
|
|
124
177
|
**Spending** — `get_usage`
|
|
125
178
|
|
|
126
179
|
**Being told somewhere else** — `list_webhooks`, `create_webhook`,
|
|
127
180
|
`get_webhook`, `update_webhook`, `rotate_webhook_secret`, `test_webhook`,
|
|
128
181
|
`list_webhook_deliveries`, `delete_webhook`
|
|
129
182
|
|
|
130
|
-
**
|
|
183
|
+
**SSH access** — `list_ssh_keys`, `add_ssh_key`, `remove_ssh_key`,
|
|
184
|
+
`get_computer_ssh`, `set_computer_ssh`. Keys belong to the person the API key
|
|
185
|
+
was issued to, not to the account, and are accepted by every computer with SSH
|
|
186
|
+
on, on every account where that person is an owner or member. A
|
|
187
|
+
workspace-scoped key can read keys but not add or remove them.
|
|
188
|
+
|
|
189
|
+
**Secret bindings** — `get_computer_secrets`, `set_computer_secrets`, and
|
|
190
|
+
`create_computer`'s `secrets`. Which of the account's secrets a computer
|
|
191
|
+
receives, at which revision, and where: as an environment variable (`env`) or as
|
|
192
|
+
a file under `/run/mandala-secrets/user/files` (`file`). Values never cross these
|
|
193
|
+
tools. A set replaces the whole list (`[]` removes every binding) and reaches the
|
|
194
|
+
guest at the computer's next start or restart; send the `version` a read
|
|
195
|
+
answered to have it refused with 409 if the list changed since. A secret bound
|
|
196
|
+
as a file is also rewritten on a running computer when its value is replaced.
|
|
197
|
+
|
|
198
|
+
**Delegating** — `run_agent`, `run_agent_chat`, registered only when a model key is present:
|
|
131
199
|
`MANDALA_MODEL_KEY` on stdio, or the caller's own `X-Model-Key` header over HTTP.
|
|
200
|
+
Both must also survive the configured filters.
|
|
201
|
+
|
|
202
|
+
### Tool filters
|
|
203
|
+
|
|
204
|
+
Set `MANDALA_READ_ONLY=1` to register only tools whose existing
|
|
205
|
+
`annotations.readOnlyHint` is exactly `true`. It accepts `1`, `true`, `yes`,
|
|
206
|
+
`on` for on and `0`, `false`, `no`, `off`, empty or unset for off, ignoring
|
|
207
|
+
surrounding whitespace and letter case. Any other value fails at startup.
|
|
208
|
+
Safety is not inferred from a tool's name or HTTP method: `read_file` and
|
|
209
|
+
`cursor_position` can wake and bill a computer, so they are withheld. So are
|
|
210
|
+
mixed read/write tools such as `wait_for_computer` and `snapshot_schedule`.
|
|
211
|
+
`screenshot`, `read_clipboard` and `list_windows` retain their read-only hints.
|
|
212
|
+
|
|
213
|
+
Set `MANDALA_TAGS=input,guest` to select a union of named tool groups. Names
|
|
214
|
+
are **lowercase only**, comma-separated, trimmed and deduplicated. Empty or
|
|
215
|
+
unset means no tag filter; empty comma-separated entries are ignored. An
|
|
216
|
+
unknown nonempty tag fails before stdio connects or HTTP starts listening,
|
|
217
|
+
with an error listing all valid tags.
|
|
218
|
+
|
|
219
|
+
| Tag | Tools |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| `account` | `get_account` |
|
|
222
|
+
| `computers` | `list_computers`, `get_computer`, `use_computer`, `wait_for_computer`, `get_desktop_url`, `list_sizes` |
|
|
223
|
+
| `lifecycle` | `create_computer`, `start_computer`, `stop_computer`, `suspend_computer`, `restart_computer`, `update_computer`, `clone_computer`, `delete_computer`, `move_computer`, `list_moves` |
|
|
224
|
+
| `input` | `screenshot`, `click`, `type_text`, `press_key`, `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`, `wait` |
|
|
225
|
+
| `guest` | `exec`, `exec_poll`, `exec_kill`, `open_url`, `list_windows`, `window_action`, `read_clipboard`, `write_clipboard` |
|
|
226
|
+
| `files` | `list_directory`, `read_file`, `write_file`, `wait_for_file_change` |
|
|
227
|
+
| `executions` | `get_execution`, `read_execution_output` |
|
|
228
|
+
| `results` | `get_activity_results`, `retain_execution_output`, `get_result`, `read_result_output`, `delete_result` |
|
|
229
|
+
| `artifacts` | `publish_artifact`, `get_artifact`, `read_artifact`, `delete_artifact` |
|
|
230
|
+
| `snapshots` | All snapshot tools listed above, including `get_retention` |
|
|
231
|
+
| `templates` | `list_templates`, all your-own-template tools and all build tools listed above |
|
|
232
|
+
| `events` | `wait_for_event`, `poll_events`, `wait_for_file_change` |
|
|
233
|
+
| `usage` | `get_usage` |
|
|
234
|
+
| `webhooks` | All webhook tools listed above |
|
|
235
|
+
| `ssh` | All SSH tools listed above |
|
|
236
|
+
| `secrets` | `get_computer_secrets`, `set_computer_secrets` |
|
|
237
|
+
| `agent` | `run_agent`, `run_agent_chat` |
|
|
238
|
+
| `activities` | `list_activities`, `get_activity`, `get_activity_results` |
|
|
239
|
+
| `signals` | `read_signals` |
|
|
240
|
+
|
|
241
|
+
The selected tags form a union, then intersect with read-only and the existing
|
|
242
|
+
lifecycle and model-key restrictions. `MANDALA_NO_LIFECYCLE=1` still withholds
|
|
243
|
+
its five tools even when their tags are selected; selecting `agent` cannot
|
|
244
|
+
enable either agent tool without a per-session model key. Both agent tools are
|
|
245
|
+
withheld by read-only, but remain available with lifecycle disabled alone.
|
|
246
|
+
`MANDALA_TAGS=files MANDALA_READ_ONLY=1` exposes only `list_directory`.
|
|
247
|
+
An `agent` selection with read-only is a valid empty tool list.
|
|
248
|
+
`activities,results` includes `get_activity_results` once; `signals` is separate
|
|
249
|
+
from the active `events` socket. Filters never grant API privileges: the API
|
|
250
|
+
checks current member/owner and workspace authorization on every request.
|
|
251
|
+
|
|
252
|
+
`use_computer` is not read-only. When it is withheld, pass `computer_id`
|
|
253
|
+
explicitly, or bind a computer with `MANDALA_COMPUTER_ID` at stdio startup.
|
|
254
|
+
HTTP callers must supply their own computer selection. These variables apply
|
|
255
|
+
to both transports and the plugin forwards them. Embedders can pass
|
|
256
|
+
`readOnly: true` and `tags: ['input', 'guest']` in `ServerConfig`; the server
|
|
257
|
+
does not read the environment itself.
|
|
258
|
+
|
|
259
|
+
### Current account quota
|
|
260
|
+
|
|
261
|
+
`get_account` takes no arguments and reads `GET /api/v1/account` once with the
|
|
262
|
+
caller's account credential. Viewer or stronger access is required. It reports
|
|
263
|
+
account-wide aggregates, including for a workspace-scoped key, without resource
|
|
264
|
+
identities. It needs no selected computer or model key, opens no event stream,
|
|
265
|
+
and remains available with read-only and no-lifecycle filters. Select the
|
|
266
|
+
`account` tag to expose it on its own.
|
|
267
|
+
|
|
268
|
+
The report includes the effective plan, pool ceilings, per-computer maxima,
|
|
269
|
+
Windows capability, current consumption and remaining quota. Configured CPU and
|
|
270
|
+
disk include kept computers regardless of power state; running or reserved RAM
|
|
271
|
+
includes current reservations. CPU, MB, GB and snapshot bytes retain the API's
|
|
272
|
+
units. `get_usage` separately reports historical metered consumption over time.
|
|
273
|
+
|
|
274
|
+
Quota is **advisory**: `observed_at` is an observation time, not a reservation or
|
|
275
|
+
consistency token. Later create, resize, start or snapshot requests can still be
|
|
276
|
+
refused, and existing 402 messages remain unchanged. Snapshot headroom is against
|
|
277
|
+
**indexed stored bytes**; it does not include in-flight capture reservations and
|
|
278
|
+
does not establish that a new capture will fit.
|
|
279
|
+
|
|
280
|
+
`complete.computers` and `complete.snapshots` are independent. An incomplete group
|
|
281
|
+
has `null` for all its consumption and remaining figures, meaning **unknown**,
|
|
282
|
+
while the other group can retain numeric values. Complete zero usage and zero
|
|
283
|
+
remaining quota remain numeric zeros. Verified plan ceilings remain available in
|
|
284
|
+
a partial report, including a no-plan account that still has retained usage.
|
|
285
|
+
Malformed reports and HTTP failures remain errors, never an empty account.
|
|
286
|
+
The tool prints unknown and advisory guidance before the projected public fields.
|
|
132
287
|
|
|
133
288
|
## Things worth knowing
|
|
134
289
|
|
|
@@ -157,9 +312,11 @@ time — `screenshot`, `click`, `screenshot` — puts an image in the calling
|
|
|
157
312
|
model's context for every step. `run_agent` hands a task in plain language to
|
|
158
313
|
the platform's own loop instead, which screenshots, decides and clicks inside
|
|
159
314
|
the platform and answers with a sentence and the list of what it did. It is
|
|
160
|
-
registered only when a model key is present (see [Configuration](#configuration))
|
|
161
|
-
bills that key for
|
|
162
|
-
the
|
|
315
|
+
registered only when a model key is present (see [Configuration](#configuration))
|
|
316
|
+
and bills that key for the run. `max_steps` bounds the WORK rather than the
|
|
317
|
+
bill: a step is one action on the desktop, one model reply can ask for several
|
|
318
|
+
and spends a step on each, a paused turn costs tokens and no step, and not every
|
|
319
|
+
step takes a screenshot. It defaults to 20 and is capped at 100 here.
|
|
163
320
|
|
|
164
321
|
**A screenshot is how you find out what the screen looks like.** A click that
|
|
165
322
|
landed and a click that did nothing produce the same tool result, so a model
|
|
@@ -355,6 +512,107 @@ foreground comes back as a timeout, with the work still going inside the guest
|
|
|
355
512
|
and its output unreadable. With a handle you get the exit code and the output,
|
|
356
513
|
and `exec_kill` stops it.
|
|
357
514
|
|
|
515
|
+
**Stable background reads.** When an accepted `exec` returns `execution_id`,
|
|
516
|
+
use `get_execution` for its last observed `running`, `exited` or `lost` state.
|
|
517
|
+
Only `exited` carries an `ended_at` and signed `exit_code`; `running` does not
|
|
518
|
+
prove the computer is awake, and `lost` establishes neither success nor failure.
|
|
519
|
+
Older replies can omit the ID. A malformed supplied ID leaves the accepted
|
|
520
|
+
command and its valid PID usable, but supplies no stable identity: do not replay
|
|
521
|
+
the command to obtain one. PID polls/kills never reconstruct this association.
|
|
522
|
+
|
|
523
|
+
`read_execution_output` takes that ID and **both** `stdout_offset` and
|
|
524
|
+
`stderr_offset` byte positions. Start each reader at zero, then pass its own
|
|
525
|
+
returned positions. For example:
|
|
526
|
+
|
|
527
|
+
```json
|
|
528
|
+
{"execution_id":"exec_0123456789abcdef0123456789abcdef","stdout_offset":0,"stderr_offset":0,"limit":4096}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Each stream is bounded to 4,096 bytes by default, at most 16,384. Complete
|
|
532
|
+
lossless UTF-8 appears as `stdout`/`stderr` with its BOM preserved. NUL, binary
|
|
533
|
+
and split UTF-8 chunks remain exact canonical `stdout_b64`/`stderr_b64`; nothing
|
|
534
|
+
is trimmed or replaced. `stdout_more` and `stderr_more` are independent, and
|
|
535
|
+
false means EOF at that moment, not that the command has finished.
|
|
536
|
+
|
|
537
|
+
The separate `diagnostic`/`diagnostic_b64` repeats on every read. At most 4,096
|
|
538
|
+
of its up-to-65,536 available bytes are displayed: inspect
|
|
539
|
+
`diagnostic_available_bytes`, `diagnostic_displayed_bytes`, and
|
|
540
|
+
`diagnostic_display_truncated`. The independent `diagnostic_truncated` flag is
|
|
541
|
+
the platform's capture limitation. Diagnostics never advance either cursor.
|
|
542
|
+
Neither new tool consumes output from another reader or the shared `exec_poll`
|
|
543
|
+
cursor, and both carry read-only, non-destructive, idempotent annotations.
|
|
544
|
+
|
|
545
|
+
These are single requests with cancellation, no automatic resume, retry,
|
|
546
|
+
watcher, capture or command replay. Metadata reads no guest files. Output reads
|
|
547
|
+
perform guest I/O without refreshing activity and are unsuitable for passive
|
|
548
|
+
Activities/history. Files are mutable guest data, not retained artifacts;
|
|
549
|
+
handles can vanish on restart, replacement or cleanup, and observed exits
|
|
550
|
+
expire after ten minutes. An unavailable read is an error, not empty output.
|
|
551
|
+
Every new-tool result is bounded to 256 KiB of serialized data.
|
|
552
|
+
|
|
553
|
+
**Explicit immutable retained versions.** `retain_execution_output` accepts an
|
|
554
|
+
`execution_id` and captures one version of its volatile output with one POST.
|
|
555
|
+
It performs guest I/O, without resuming or replaying the command. Its optional
|
|
556
|
+
`max_bytes_per_stream` defaults to 1 MiB (maximum 4 MiB); `retention_seconds`
|
|
557
|
+
defaults to 86400 (maximum 604800). Diagnostics are separate, up to 64 KiB.
|
|
558
|
+
Each capture creates a version; there is no automatic capture or retry.
|
|
559
|
+
|
|
560
|
+
`get_result` reads finite metadata by `result_id`. `read_result_output` requires
|
|
561
|
+
`result_id`, `stream` (`stdout`, `stderr` or `diagnostic`) and `offset`, with
|
|
562
|
+
`limit` defaulting to 4096 and capped at 16384 bytes. It reads one independent
|
|
563
|
+
page, with exact `offset`, `next_offset`, `eof` and returned `bytes` count.
|
|
564
|
+
Content is lossless `text` (BOM preserved) or canonical `base64` for binary,
|
|
565
|
+
controls and split UTF-8. EOF means the end of this retained prefix, not task
|
|
566
|
+
completion. A page alone does not verify the full manifest hash.
|
|
567
|
+
`delete_result` deletes that version once; a repeated 404 remains unavailable.
|
|
568
|
+
|
|
569
|
+
Existing synchronous `exec` accepts `retain_output: true` or a strict object
|
|
570
|
+
with those same two options. False or absence leaves default behavior alone.
|
|
571
|
+
It cannot be combined with `background: true`. A canonical returned `result_id`
|
|
572
|
+
confirms optional retention; no execution ID is fabricated. Missing, malformed
|
|
573
|
+
or unsupported optional metadata leaves the command outcome unchanged and does
|
|
574
|
+
not authorize replay. Retained-prefix truncation and upstream response
|
|
575
|
+
truncation are distinct. Synchronous results have no diagnostic stream; an
|
|
576
|
+
explicit diagnostic read can return 409.
|
|
577
|
+
|
|
578
|
+
**Nominated file versions.** `publish_artifact` requires an absolute `path`,
|
|
579
|
+
`expected_size` and `expected_sha256` supplied by the caller. For example:
|
|
580
|
+
|
|
581
|
+
```json
|
|
582
|
+
{"path":"/tmp/empty.txt","expected_size":0,"expected_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
Paths preserve legal Unicode, spaces and Linux backslashes; Windows drive and
|
|
586
|
+
UNC paths are also accepted, subject to the platform's current OS proof.
|
|
587
|
+
Publication performs one nominated guest-file capture, without a client-side
|
|
588
|
+
stat, list, read, hash or exec preflight. `max_bytes` defaults to 8 MiB and is at
|
|
589
|
+
most 64 MiB; expected size must fit. Retention has the bounds above. Optional
|
|
590
|
+
`execution_id` records verified **caller selection**, not proof the execution
|
|
591
|
+
created the file. Every publication creates an independent immutable version.
|
|
592
|
+
|
|
593
|
+
`get_artifact` reads metadata. `read_artifact` first reads that metadata and,
|
|
594
|
+
only if the complete size fits `max_bytes` (default 4096, maximum 16384), reads
|
|
595
|
+
the entire retained object and verifies its exact length and SHA-256. Over-cap
|
|
596
|
+
objects return metadata with a refusal before any content request; use an SDK
|
|
597
|
+
whole download with an adequate cap. There are no partial artifacts, Range
|
|
598
|
+
requests, local destinations, filenames, image/HTML previews or guest fallbacks.
|
|
599
|
+
Verified content uses the same lossless `text`/`base64` presentation.
|
|
600
|
+
`delete_artifact` deletes only that stored version, not the guest file; repeat
|
|
601
|
+
404s remain truthful failures.
|
|
602
|
+
|
|
603
|
+
All eight retained tools resolve the computer selection once. Reads are passive
|
|
604
|
+
but require current authorization and scope availability; retained bytes can be
|
|
605
|
+
unavailable when the host cannot verify access, after expiry or deletion, or
|
|
606
|
+
when storage is unavailable. They never wake a guest or substitute live output.
|
|
607
|
+
New operations have one 90-second budget across headers, bodies, and both
|
|
608
|
+
artifact requests. An MCP client can impose an earlier deadline. Cancellation,
|
|
609
|
+
redirects and malformed responses never trigger a retry; a lost publication
|
|
610
|
+
response means commitment is **unconfirmed**, not undone. Each entire serialized
|
|
611
|
+
retained tool result is bounded to 256 KiB. Reads are marked read-only;
|
|
612
|
+
capture/publication create new versions; deletes are destructive with an
|
|
613
|
+
idempotent deletion effect. Activities result detail remains outside this MCP
|
|
614
|
+
runtime.
|
|
615
|
+
|
|
358
616
|
Past about **two minutes** it does not even come back as a timeout. A proxy in
|
|
359
617
|
front of the platform abandons a request that has produced no response for
|
|
360
618
|
roughly that long and answers 524, which arrives as `GatewayTimeoutError` —
|
|
@@ -455,6 +713,66 @@ about storage instead.
|
|
|
455
713
|
guest agent answers 409. The platform's own error messages come through
|
|
456
714
|
unedited, because they are written to be acted on.
|
|
457
715
|
|
|
716
|
+
HTTP failures preserve their actual response status. An unsupported method on a
|
|
717
|
+
known path is `MethodNotAllowedError` (405), with the received `Allow` value when
|
|
718
|
+
available. A missing computer, snapshot, route or guest file remains
|
|
719
|
+
`NotFoundError` (404); other guest failures keep their own status and message.
|
|
720
|
+
Neither response causes an automatic retry or method switch.
|
|
721
|
+
|
|
722
|
+
Embedders can inspect optional diagnostics on every `APIError`:
|
|
723
|
+
|
|
724
|
+
```ts
|
|
725
|
+
import { Api, APIError, MethodNotAllowedError } from 'mandala-computer-mcp';
|
|
726
|
+
|
|
727
|
+
const api = new Api(process.env.MANDALA_API_KEY!);
|
|
728
|
+
try {
|
|
729
|
+
await api.json('GET', 'account');
|
|
730
|
+
} catch (error) {
|
|
731
|
+
if (error instanceof APIError) {
|
|
732
|
+
console.error({ status: error.status, requestId: error.requestId,
|
|
733
|
+
reason: error.reason, allow: error.allow,
|
|
734
|
+
wwwAuthenticate: error.wwwAuthenticate });
|
|
735
|
+
if (error instanceof MethodNotAllowedError) {
|
|
736
|
+
// Inspect error.allow before correcting the request method.
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
`requestId` uses a nonblank `X-Request-ID` header first, then the top-level
|
|
743
|
+
`request_id` body field. It is an opaque diagnostic, never an idempotency key.
|
|
744
|
+
The raw body remains available on `error.body`, including any differing body ID
|
|
745
|
+
and nested chat accounting. `allow` and `wwwAuthenticate` come only from received
|
|
746
|
+
headers. HEAD failures can carry these fields with no body. Older servers,
|
|
747
|
+
intermediaries and connection failures may supply none of them. Existing
|
|
748
|
+
constructor arguments retain their meanings; an optional trailing
|
|
749
|
+
`APIErrorMetadata` object adds these three fields.
|
|
750
|
+
|
|
751
|
+
MCP error results include supplied `reason`, `request_id`, `allow`,
|
|
752
|
+
`www_authenticate` and `retry_after_ms` as labelled JSON metadata. Diagnostic
|
|
753
|
+
strings are limited to 128, 256, 512 and 512 characters respectively; an
|
|
754
|
+
oversized field is omitted with a notice, so a shortened Allow is never
|
|
755
|
+
presented as complete. Tool-specific warnings about partial work, retained
|
|
756
|
+
publication, execution reads and explicit template continuation still apply.
|
|
757
|
+
Serialized JSON object or array prefixes are not displayed as error prose.
|
|
758
|
+
Valid scalar error messages retain their wording; embedders still have the
|
|
759
|
+
original `APIError.message` and `APIError.body` for diagnostics. Native agent
|
|
760
|
+
error frames retain their supplied numeric status even without a reason or
|
|
761
|
+
request ID, independently of the successful HTTP stream carrying them.
|
|
762
|
+
|
|
763
|
+
For a 401, `missing` means a platform credential was not supplied; `invalid`
|
|
764
|
+
means the supplied platform credential was not accepted; `revoked` means its
|
|
765
|
+
authority no longer holds. Unknown reasons stay visible. An unclassified 401
|
|
766
|
+
alone does not identify whether the account key or model key was refused,
|
|
767
|
+
including a nested chat failure. A 403 remains a permission or authority
|
|
768
|
+
refusal. Inspect recorded work before another run; no key fallback, login or
|
|
769
|
+
automatic replay is performed. Nested error reasons and in-band stream errors
|
|
770
|
+
do not grant permission to replay a partly completed run.
|
|
771
|
+
|
|
772
|
+
For desktop events, use the exact returned `events_url`, including its desktop
|
|
773
|
+
capability, in a WebSocket client. A REST Bearer key alone is not sufficient;
|
|
774
|
+
the HTTP events response provides JSON guidance rather than another login flow.
|
|
775
|
+
|
|
458
776
|
**Desktop links are credentials.** `get_desktop_url` returns the watch-only URL
|
|
459
777
|
by default — the platform drops input on that socket, so it is safe to hand to
|
|
460
778
|
somebody. `control: true` returns the full-control one, which is root-equivalent
|
|
@@ -556,7 +874,9 @@ for whatever is checking that the process is up.
|
|
|
556
874
|
bearer token and is used only for their session; there is no store, and nothing
|
|
557
875
|
outlives a session but a digest of the key — kept so that a later request can be
|
|
558
876
|
shown to come from the same holder, which means a leaked session id on its own
|
|
559
|
-
is not enough to drive somebody else's desktop.
|
|
877
|
+
is not enough to drive somebody else's desktop. HTTP startup and requests never
|
|
878
|
+
read the operator's credential store, `MANDALA_API_KEY` or `MANDALA_PROFILE`.
|
|
879
|
+
`--profile` is local-only and ignored with `--http`.
|
|
560
880
|
|
|
561
881
|
That is also why anyone can run their own: point the same container at the same
|
|
562
882
|
API and it works, with no secret to provision.
|
|
@@ -579,17 +899,20 @@ naming the fix.
|
|
|
579
899
|
|
|
580
900
|
| Variable | Meaning |
|
|
581
901
|
| --- | --- |
|
|
582
|
-
| `MANDALA_API_KEY` |
|
|
583
|
-
| `
|
|
902
|
+
| `MANDALA_API_KEY` | Optional local API key, taking precedence over saved credentials. Ignored by HTTP; each caller sends their own bearer token. |
|
|
903
|
+
| `MANDALA_PROFILE` | Local saved profile; `--profile` overrides it. Otherwise the file default is selected. Ignored by HTTP. |
|
|
904
|
+
| `MANDALA_BASE_URL` | With a saved profile, must match its stored base. Otherwise defaults to `https://app.mandala.computer/api/v1`. |
|
|
584
905
|
| `MANDALA_COMPUTER_ID` | Bind a computer at startup, so `use_computer` is not needed. **stdio only** — under `--http` it is ignored rather than bound into every caller's session, since it names a machine on the operator's account. |
|
|
585
|
-
| `MANDALA_MODEL_KEY` | An Anthropic key. Enables `run_agent
|
|
906
|
+
| `MANDALA_MODEL_KEY` | An Anthropic key. Enables `run_agent` and `run_agent_chat` when filters permit them, which runs the platform's own loop on that key. **stdio only** — under `--http` each caller sends their own as `X-Model-Key`, and this variable is ignored. |
|
|
586
907
|
| `MANDALA_NO_LIFECYCLE` | `1`, `true`, `yes` or `on` withholds `create_computer`, `clone_computer`, `clone_snapshot`, `delete_computer` and `delete_snapshot` — every tool that makes a computer or destroys one. `0`, `false`, `no`, `off` or unset leaves them registered. Any other value is **refused at startup** rather than read as off: a typo here would otherwise leave those tools in place on a server whose operator believes they are gone. The `--no-lifecycle` flag reads the same vocabulary and refuses the same way, except that it has no spelling for *unset*: `--no-lifecycle=` is refused rather than ignored, so a launcher template whose variable did not expand stops instead of quietly leaving the tools registered. |
|
|
587
908
|
| `PORT`, `HOST` | For `--http`. Default `3000`, `127.0.0.1`. |
|
|
909
|
+
| `MANDALA_READ_ONLY` | Keep only tools annotated `readOnlyHint: true`; strict boolean parsing as described under Tool filters. |
|
|
910
|
+
| `MANDALA_TAGS` | Comma-separated lowercase tool tags; see Tool filters for the inventory and intersection rules. |
|
|
588
911
|
| `MANDALA_ALLOWED_HOSTS`, `MANDALA_ALLOWED_ORIGINS` | Comma-separated. Which `Host` and `Origin` values this server answers to. On a loopback bind the host list defaults to the address it was given, so DNS-rebinding protection is on without configuration; set this when serving under a name. |
|
|
589
912
|
|
|
590
|
-
Every one of these but the model key has a flag as well, and a flag overrides
|
|
913
|
+
Every one of these but the model key and the two tool filters has a flag as well, and a flag overrides
|
|
591
914
|
the environment: `--http`, `--port`, `--host`, `--base-url`, `--computer`,
|
|
592
|
-
`--allowed-hosts`, `--allowed-origins`, `--no-lifecycle`, plus `--help` and
|
|
915
|
+
`--allowed-hosts`, `--allowed-origins`, `--no-lifecycle`, local `--profile`, plus `--help` and
|
|
593
916
|
`--version`. `--key` exists for a caller launching several servers under
|
|
594
917
|
different keys, and warns when used, because an argument vector is readable by
|
|
595
918
|
`ps`, lands in shell history and is recorded verbatim by any exec audit —
|
|
@@ -601,17 +924,116 @@ it when a stretch of pixel work would otherwise cost the calling model a
|
|
|
601
924
|
screenshot per step — ten clicks stop being ten images. It bills your Anthropic
|
|
602
925
|
key, and the platform never stores that key.
|
|
603
926
|
|
|
927
|
+
### Passive directories, activity history and signals
|
|
928
|
+
|
|
929
|
+
`list_directory` takes an exact absolute guest `path`. Unicode, spaces and
|
|
930
|
+
punctuation survive query encoding. It requires an already running computer,
|
|
931
|
+
does not resume it or extend its idle timer, and still contacts the guest with
|
|
932
|
+
ordinary rate/capacity admission. The result preserves `path`, `entries` with
|
|
933
|
+
`name`, `type` and optional `size_bytes`, `truncated` and `skipped`. An unavailable
|
|
934
|
+
entry has no inferred type or zero size. Symlinks are not followed, and a final
|
|
935
|
+
symlink directory is refused. No file content is read. A partial listing is an
|
|
936
|
+
unordered subset: at most 512 examined entries/128 KiB, with no continuation
|
|
937
|
+
token. Narrow the path when names are omitted; do not automatically rescan.
|
|
938
|
+
|
|
939
|
+
`list_activities` returns one newest-first history page with a fixed watermark.
|
|
940
|
+
Pass an opaque `cursor` for continuation, or `changes:true` with a cursor for
|
|
941
|
+
late final updates to older rows. False/absent `changes` is omitted on the wire;
|
|
942
|
+
there is no `limit` argument. Preserve `next_cursor`, `changes_cursor`, `gap`
|
|
943
|
+
and health fields. A gap requires refreshing history. The tools keep no cursor
|
|
944
|
+
cache and never drain pages automatically. `get_activity` takes an `activity_id`
|
|
945
|
+
shaped as `act_` plus 32 lowercase hex digits, a request identity rather than an
|
|
946
|
+
idempotency key. Recorded state, scope, revision, timestamps, execution identity
|
|
947
|
+
and status remain distinct: accepted background work is not finished execution,
|
|
948
|
+
and a dispatched error may have had effects. History is selected, retained,
|
|
949
|
+
best-effort metadata, not all guest work or an agent identity.
|
|
950
|
+
|
|
951
|
+
`get_activity_results` returns at most eight newest metadata links, preserving
|
|
952
|
+
revision, availability, association, byte/truncation metadata and observations.
|
|
953
|
+
`more:true` has no continuation cursor and does not mean all versions were
|
|
954
|
+
returned. An unavailable item is not an empty available item. Caller-selected
|
|
955
|
+
artifact association does not prove creation or command success. This tool
|
|
956
|
+
never follows a link to content, files, captures, downloads or execution polling.
|
|
957
|
+
|
|
958
|
+
`read_signals` reads one passive daemon page, independently of the active event
|
|
959
|
+
socket. Omit `since` or send an empty string for a head-only baseline with no
|
|
960
|
+
replay. Optional `limit` is 1–100; omitting it uses the API default 50. Preserve
|
|
961
|
+
the returned `cursor` even when `events` is empty: filtered-out rows may advance
|
|
962
|
+
it. Retention is ephemeral; expired/restarted/migrated cursors can return an
|
|
963
|
+
explicit reset gap. Baselines and gaps do not prove there was no earlier work
|
|
964
|
+
or that a task succeeded. Unsupported (501) and unavailable (503) responses
|
|
965
|
+
remain errors, never empty history or new checkpoints. There is no watcher,
|
|
966
|
+
guest action, retry loop or automatic checkpoint cache in this tool.
|
|
967
|
+
|
|
968
|
+
### JSON chat with your own Anthropic key
|
|
969
|
+
|
|
970
|
+
`run_agent_chat` drives the selected computer using the same BYOK Anthropic loop
|
|
971
|
+
as `run_agent`. It accepts a nonempty array of textual OpenAI-shaped `messages`,
|
|
972
|
+
including string content or arrays of `{type:"text",text:"..."}` parts. The
|
|
973
|
+
**last user message** supplies the task; system messages supply standing
|
|
974
|
+
instructions. Earlier user/assistant conversation is not replayed. Optional
|
|
975
|
+
`model` is sent unchanged and must name an Anthropic model. `max_steps` is 1–100,
|
|
976
|
+
default 20. This is computer control, not hosted general-purpose inference or a
|
|
977
|
+
chat UI; it adds no key storage or model billing service.
|
|
978
|
+
|
|
979
|
+
This tool deliberately sends `stream:false` and uses the JSON response so the
|
|
980
|
+
result preserves the underlying `agent.stop`, step count and token usage.
|
|
981
|
+
Only explicit `end_turn` consistent with the completion's finish reason can
|
|
982
|
+
report success. Limits, refusal, missing or conflicting terminal fields remain
|
|
983
|
+
errors with valid partial results. Nested failures preserve the agent computer,
|
|
984
|
+
recorded steps and native usage, including separate cache-read and cache-write
|
|
985
|
+
token counts, alongside OpenAI-shaped aggregate usage. Malformed native detail
|
|
986
|
+
is explicitly marked incomplete. A failed call may include completed, billed
|
|
987
|
+
work: inspect it before deciding what remains, and do not automatically replay.
|
|
988
|
+
Neither agent tool starts the computer or switches endpoints after a failure.
|
|
989
|
+
|
|
990
|
+
The caller supplies the model key through `MANDALA_MODEL_KEY` on stdio or their
|
|
991
|
+
own `X-Model-Key` header on HTTP. The account Authorization remains separate;
|
|
992
|
+
HTTP never falls back to the operator's model key. Waiting heartbeats count
|
|
993
|
+
notifications, not completed actions. Long-running clients need a
|
|
994
|
+
`progressToken` plus `resetTimeoutOnProgress`; otherwise choose a smaller
|
|
995
|
+
`max_steps`. That bound is neither a time cap nor a spend cap.
|
|
996
|
+
|
|
604
997
|
## Development
|
|
605
998
|
|
|
606
999
|
```sh
|
|
607
|
-
npm
|
|
608
|
-
|
|
1000
|
+
npm ci
|
|
1001
|
+
npx vitest run # deterministic offline tests, including the synthetic contract
|
|
1002
|
+
npm test # offline tests plus the separate private mirror check
|
|
609
1003
|
npm run build
|
|
610
1004
|
npm run lint
|
|
611
1005
|
```
|
|
612
1006
|
|
|
613
|
-
CI runs the suite on Node 20, 22, 24 and 26
|
|
614
|
-
|
|
1007
|
+
CI runs the offline suite on Node 20, 22, 24 and 26. A separate Node 22
|
|
1008
|
+
`published-openapi` job runs on pull requests and main pushes:
|
|
1009
|
+
|
|
1010
|
+
```sh
|
|
1011
|
+
npx vitest run --config vitest.openapi.config.ts
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
It anonymously fetches the fixed publication at
|
|
1015
|
+
`https://app.mandala.computer/api/docs/openapi.json` once, with a 20-second
|
|
1016
|
+
overall deadline and an 8 MiB body ceiling, then exercises the real MCP tools
|
|
1017
|
+
and requires request evidence for every published `/api/v1` operation. Every
|
|
1018
|
+
exercise variant must succeed and dispatch a request, including later variants
|
|
1019
|
+
of a tool that already dispatched successfully.
|
|
1020
|
+
Non-v1 operations are explicitly excluded and reported. The check uses OpenAPI
|
|
1021
|
+
server inheritance and literal-path precedence, not the local route allowlist.
|
|
1022
|
+
Each path is appended to its effective server base; repeated prefixes are not
|
|
1023
|
+
removed. Fully prefixed paths work with an absent or root server.
|
|
1024
|
+
It fails on a blocked fetch, redirect, invalid document or missing operation;
|
|
1025
|
+
there is no fixture fallback, credential requirement or skip branch.
|
|
1026
|
+
|
|
1027
|
+
The committed OpenAPI fixture is synthetic, assembled from the public MCP route
|
|
1028
|
+
inventory, and is never presented as a downloaded publication. Offline green
|
|
1029
|
+
proves deterministic implementation coverage only. Anonymous CI at
|
|
1030
|
+
[commit 1b04314](https://github.com/mandalacomputer/mcp/commit/1b04314e84f5701e2e15c65cbb1c44a0c14a4948)
|
|
1031
|
+
returned HTTP 200 and matched all 56 operations in the fetched v1 contract using
|
|
1032
|
+
123 requests from 83 tools. That result applies to that commit and publication;
|
|
1033
|
+
every subsequent head must pass the gate independently. These counts are
|
|
1034
|
+
observations, never fixed thresholds or exemptions. A blocked or red public gate
|
|
1035
|
+
must not be merged. Parameters and response modes remain separate contracts,
|
|
1036
|
+
with documented parameter exceptions and JSON-only chat support.
|
|
615
1037
|
|
|
616
1038
|
### Where the platform's rules live
|
|
617
1039
|
|
|
@@ -624,17 +1046,20 @@ change to the platform's route table, not a wider pass-through here.
|
|
|
624
1046
|
|
|
625
1047
|
The platform allowlists every route `/api/v1` will answer and 404s the rest.
|
|
626
1048
|
`test/allowlist.ts` mirrors that table, and the tests assert two things: that
|
|
627
|
-
every
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1049
|
+
every successful exercised call lands on an allowlisted route, and that no
|
|
1050
|
+
mirrored operation remains unexercised (`UNIMPLEMENTED` is empty). Tool names
|
|
1051
|
+
and exercise entries must match in both directions, and every tool must make
|
|
1052
|
+
an observed HTTP request during its callback. The independent public gate also
|
|
1053
|
+
detects a new published operation when the mirror has not yet been updated.
|
|
631
1054
|
|
|
632
1055
|
`npm run check:surface` goes further and diffs the mirror against the platform's
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1056
|
+
published surface manifest — a file the platform generates from its own tables
|
|
1057
|
+
and commits like a lockfile — whenever the platform repository happens to be
|
|
1058
|
+
checked out next door, or wherever `MANDALA_PLATFORM_REPO` points. Without it
|
|
1059
|
+
the script says it is skipping and exits 0, which is what it does for anyone
|
|
1060
|
+
outside the platform team; a manifest it cannot read is a failure, never a
|
|
1061
|
+
comparison of nothing. The diff is enforced from the platform's own CI, which
|
|
1062
|
+
checks this repository out beside itself and runs the same script.
|
|
638
1063
|
|
|
639
1064
|
```
|
|
640
1065
|
check:surface — the mirror matches the platform (N routes, N parameters, from …).
|
package/dist/api.d.ts
CHANGED
|
@@ -145,6 +145,14 @@ export declare class Api {
|
|
|
145
145
|
* Routes where an empty body IS the answer use `send`.
|
|
146
146
|
*/
|
|
147
147
|
json<T = unknown>(method: string, path: string, opts?: RequestOptions): Promise<T>;
|
|
148
|
+
/** Existing exec decoding plus observed status, used only to confirm optional retention. */
|
|
149
|
+
jsonWithStatus<T>(method: string, path: string, opts?: RequestOptions): Promise<{
|
|
150
|
+
status: number;
|
|
151
|
+
value: T;
|
|
152
|
+
}>;
|
|
153
|
+
/** Complete bounded responses for immutable retained protocols; never a successful prefix. */
|
|
154
|
+
boundedBytes(method: string, path: string, limit: number, status: number, opts?: RequestOptions): Promise<BoundedBytes>;
|
|
155
|
+
boundedJson(method: string, path: string, limit: number, status: number, opts?: RequestOptions): Promise<unknown>;
|
|
148
156
|
/**
|
|
149
157
|
* A request whose answer may legitimately be nothing.
|
|
150
158
|
*
|
|
@@ -196,4 +204,8 @@ export declare class Api {
|
|
|
196
204
|
export declare function causes(err: unknown, depth?: number): Generator<Record<string, unknown>>;
|
|
197
205
|
/** The filename the platform put on a download, if it put one there. */
|
|
198
206
|
export declare function filenameFrom(disposition: string | null): string | undefined;
|
|
207
|
+
export type BoundedBytes = {
|
|
208
|
+
bytes: Uint8Array;
|
|
209
|
+
headers: Record<string, string>;
|
|
210
|
+
};
|
|
199
211
|
//# sourceMappingURL=api.d.ts.map
|