mandala-computer-mcp 0.6.0 → 0.8.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.
Files changed (72) hide show
  1. package/README.md +268 -27
  2. package/dist/api.d.ts +26 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +23 -0
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli.d.ts +7 -0
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +57 -1
  9. package/dist/cli.js.map +1 -1
  10. package/dist/errors.d.ts +114 -3
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js +253 -3
  13. package/dist/errors.js.map +1 -1
  14. package/dist/format.d.ts +73 -2
  15. package/dist/format.d.ts.map +1 -1
  16. package/dist/format.js +81 -3
  17. package/dist/format.js.map +1 -1
  18. package/dist/http.d.ts +32 -0
  19. package/dist/http.d.ts.map +1 -1
  20. package/dist/http.js +440 -57
  21. package/dist/http.js.map +1 -1
  22. package/dist/paths.d.ts +75 -7
  23. package/dist/paths.d.ts.map +1 -1
  24. package/dist/paths.js +88 -12
  25. package/dist/paths.js.map +1 -1
  26. package/dist/server.d.ts +1 -1
  27. package/dist/server.d.ts.map +1 -1
  28. package/dist/server.js +3 -1
  29. package/dist/server.js.map +1 -1
  30. package/dist/tool-filters.d.ts +4 -4
  31. package/dist/tool-filters.d.ts.map +1 -1
  32. package/dist/tool-filters.js +13 -1
  33. package/dist/tool-filters.js.map +1 -1
  34. package/dist/tools/account.d.ts +14 -0
  35. package/dist/tools/account.d.ts.map +1 -1
  36. package/dist/tools/account.js +118 -0
  37. package/dist/tools/account.js.map +1 -1
  38. package/dist/tools/agent.d.ts.map +1 -1
  39. package/dist/tools/agent.js +19 -25
  40. package/dist/tools/agent.js.map +1 -1
  41. package/dist/tools/chat.d.ts.map +1 -1
  42. package/dist/tools/chat.js +46 -4
  43. package/dist/tools/chat.js.map +1 -1
  44. package/dist/tools/computers.d.ts.map +1 -1
  45. package/dist/tools/computers.js +297 -51
  46. package/dist/tools/computers.js.map +1 -1
  47. package/dist/tools/guest.d.ts.map +1 -1
  48. package/dist/tools/guest.js +27 -4
  49. package/dist/tools/guest.js.map +1 -1
  50. package/dist/tools/input.d.ts +9 -0
  51. package/dist/tools/input.d.ts.map +1 -1
  52. package/dist/tools/input.js +247 -16
  53. package/dist/tools/input.js.map +1 -1
  54. package/dist/tools/operations.d.ts +49 -0
  55. package/dist/tools/operations.d.ts.map +1 -0
  56. package/dist/tools/operations.js +255 -0
  57. package/dist/tools/operations.js.map +1 -0
  58. package/dist/tools/secrets.d.ts +10 -1
  59. package/dist/tools/secrets.d.ts.map +1 -1
  60. package/dist/tools/secrets.js +63 -18
  61. package/dist/tools/secrets.js.map +1 -1
  62. package/dist/tools/snapshots.d.ts.map +1 -1
  63. package/dist/tools/snapshots.js +54 -18
  64. package/dist/tools/snapshots.js.map +1 -1
  65. package/dist/tools/ssh.d.ts.map +1 -1
  66. package/dist/tools/ssh.js +19 -2
  67. package/dist/tools/ssh.js.map +1 -1
  68. package/dist/tools/templates.js +2 -2
  69. package/dist/tools/templates.js.map +1 -1
  70. package/dist/tools/webhooks.js +1 -1
  71. package/dist/tools/webhooks.js.map +1 -1
  72. package/package.json +1 -1
package/README.md CHANGED
@@ -145,10 +145,12 @@ Parameter and response-mode support remains a separate contract.
145
145
 
146
146
  **Lifecycle** — `create_computer`, `start_computer`, `stop_computer`,
147
147
  `suspend_computer`, `restart_computer`, `update_computer`, `clone_computer`,
148
- `delete_computer`, `move_computer`, `list_moves`
148
+ `delete_computer`, `move_computer`, `list_moves`, `get_operation`,
149
+ `list_operations`, `wait_for_operation` — see [Lifecycle operations](#lifecycle-operations)
149
150
 
150
- **Driving the desktop** — `screenshot`, `click`, `type_text`, `press_key`,
151
- `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`, `wait`
151
+ **Driving the desktop** — `screenshot`, `click`, `type_text`, `paste_text`,
152
+ `press_key`, `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`,
153
+ `wait`
152
154
 
153
155
  **Inside the guest** — `exec`, `exec_poll`, `exec_kill`, `get_execution`,
154
156
  `read_execution_output`, `open_url`,
@@ -185,6 +187,10 @@ which says nothing about the path; `isTransient` is false for both.
185
187
 
186
188
  **Account quota** — `get_account`
187
189
 
190
+ **Who you are** — `whoami`, `list_api_keys`, `list_workspaces`,
191
+ `get_workspace`, `list_workspace_members`. Minting and revoking API keys are
192
+ deliberately not tools — see [Who you are, and API keys](#who-you-are-and-api-keys).
193
+
188
194
  **Spending** — `get_usage`
189
195
 
190
196
  **Being told somewhere else** — `list_webhooks`, `create_webhook`,
@@ -197,11 +203,12 @@ was issued to, not to the account, and are accepted by every computer with SSH
197
203
  on, on every account where that person is an owner or member. A
198
204
  workspace-scoped key can read keys but not add or remove them.
199
205
 
200
- **Secrets** — `list_secrets`, `get_secret`, `create_secret`, `replace_secret`,
201
- `delete_secret` for the account's secret store, and `get_computer_secrets`,
202
- `set_computer_secrets` and `create_computer`'s `secrets` for which of them a
203
- computer receives. A value goes in through `create_secret` or `replace_secret`
204
- and never comes back out. No route answers one, and a store tool's result
206
+ **Secrets** — `list_secrets`, `get_secret`, `create_secret`, `set_secret`,
207
+ `replace_secret`, `delete_secret` for the account's secret store, and
208
+ `get_computer_secrets`, `set_computer_secrets` and `create_computer`'s `secrets`
209
+ for which of them a computer receives. A value goes in through `create_secret`,
210
+ `set_secret` (create the name, or replace its value if the scope holds it —
211
+ names match ignoring ASCII case) or `replace_secret`, and never comes back out. No route answers one, and a store tool's result
205
212
  holds only the decoded documented fields (success) or the status, the `reason`
206
213
  word and a sentence of its own (refusal). It never includes the platform's
207
214
  response text, and the `Api` keeps none for these routes. The bindings carry
@@ -211,13 +218,41 @@ binding list (`[]` removes every binding) and reaches the guest at the
211
218
  computer's next start or restart; send the `version` a read answered to have it
212
219
  refused with 409 if the list changed since. A replaced value also reaches a
213
220
  running computer: a file binding's file is rewritten in place, and on an image
214
- that supports it an env binding reaches new shells and `exec` with
215
- `desktop: true` (programs already running keep the old value until a restart).
216
- A command that needs a bound variable should use `desktop: true`. Whether a
217
- plain root `exec` sees it depends on the platform version. `replace_secret`
221
+ that supports it an env binding reaches new shells, and every `exec`, plain
222
+ or with `desktop: true`, runs with the bound variables as they are at that
223
+ moment (programs already running keep the old value until a restart).
224
+ `replace_secret`
218
225
  and `delete_secret` need the current `revision_id`, and a stale one is a 409.
219
226
  `delete_secret` also needs `confirm: true`: a computer still bound to a deleted
220
- secret cannot start again until that binding is removed.
227
+ secret cannot start again until that binding is removed. A bound computer runs,
228
+ and its guest answers, a few seconds before its secrets land, so
229
+ `wait_for_computer(until="guest")` on one also waits until they have;
230
+ `get_computer` shows the gap as "its secrets are still on their way in".
231
+
232
+ **A proxy for the browsers** — `browser_proxy: {server, bypass}` on
233
+ `create_computer` and `update_computer` sends a Linux computer's browsers
234
+ (Chromium, Chrome, Firefox) through a proxy; nothing else on the computer uses
235
+ it. On `update_computer` it goes alone, replaces the setting whole, and `null`
236
+ removes it. Which proxies are accepted is the platform's rule, and its refusal
237
+ comes back as it is. A running computer has a change within seconds:
238
+ `wait_for_computer(until="guest")` waits until its browsers have it, and
239
+ `get_computer` shows the gap as "its browser proxy is still being applied".
240
+
241
+ **A proxy for all outbound traffic** — `egress_proxy: {server,
242
+ credentials_secret_id}` on `create_computer` and `update_computer` sends ALL of
243
+ a computer's outbound TCP (`exec`, terminals, package managers and browsers
244
+ alike) through a proxy, taken on its host so nothing inside the computer can
245
+ opt out. The server is `http://`, `https://` or `socks5://` with an explicit
246
+ port; there is no bypass list. `credentials_secret_id` names a secret holding
247
+ `user:password` that is not bound to the computer and never reaches it: the
248
+ computer's host signs in to the proxy with it. It fails closed (proxy down or
249
+ refusing, or credentials not on the host yet: the connection fails, nothing goes
250
+ direct), drops UDP to the internet and ICMP, and does not proxy DNS lookups.
251
+ On `update_computer` it goes alone — beside any other field it is refused
252
+ before a request is sent — replaces the setting whole, and `null` removes it.
253
+ `get_computer` names the proxy and, while the host waits for its credentials,
254
+ says "its egress proxy is waiting for credentials; connections are closed until
255
+ they arrive".
221
256
 
222
257
  **Delegating** — `run_agent`, `run_agent_chat`, registered only when a model key is present:
223
258
  `MANDALA_MODEL_KEY` on stdio, or the caller's own `X-Model-Key` header over HTTP.
@@ -242,10 +277,10 @@ with an error listing all valid tags.
242
277
 
243
278
  | Tag | Tools |
244
279
  | --- | --- |
245
- | `account` | `get_account` |
280
+ | `account` | `get_account`, `whoami`, `list_api_keys`, `list_workspaces`, `get_workspace`, `list_workspace_members` |
246
281
  | `computers` | `list_computers`, `get_computer`, `use_computer`, `wait_for_computer`, `get_desktop_url`, `list_sizes` |
247
- | `lifecycle` | `create_computer`, `start_computer`, `stop_computer`, `suspend_computer`, `restart_computer`, `update_computer`, `clone_computer`, `delete_computer`, `move_computer`, `list_moves` |
248
- | `input` | `screenshot`, `click`, `type_text`, `press_key`, `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`, `wait` |
282
+ | `lifecycle` | `create_computer`, `start_computer`, `stop_computer`, `suspend_computer`, `restart_computer`, `update_computer`, `clone_computer`, `delete_computer`, `move_computer`, `list_moves`, `get_operation`, `list_operations`, `wait_for_operation` |
283
+ | `input` | `screenshot`, `click`, `type_text`, `paste_text`, `press_key`, `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`, `wait` |
249
284
  | `guest` | `exec`, `exec_poll`, `exec_kill`, `open_url`, `list_windows`, `window_action`, `read_clipboard`, `write_clipboard` |
250
285
  | `files` | `list_directory`, `read_file`, `write_file`, `wait_for_file_change` |
251
286
  | `executions` | `get_execution`, `read_execution_output` |
@@ -257,7 +292,7 @@ with an error listing all valid tags.
257
292
  | `usage` | `get_usage` |
258
293
  | `webhooks` | All webhook tools listed above |
259
294
  | `ssh` | All SSH tools listed above |
260
- | `secrets` | `list_secrets`, `get_secret`, `create_secret`, `replace_secret`, `delete_secret`, `get_computer_secrets`, `set_computer_secrets` |
295
+ | `secrets` | `list_secrets`, `get_secret`, `create_secret`, `set_secret`, `replace_secret`, `delete_secret`, `get_computer_secrets`, `set_computer_secrets` |
261
296
  | `agent` | `run_agent`, `run_agent_chat` |
262
297
  | `activities` | `list_activities`, `get_activity`, `get_activity_results` |
263
298
  | `signals` | `read_signals` |
@@ -280,6 +315,116 @@ to both transports and the plugin forwards them. Embedders can pass
280
315
  `readOnly: true` and `tags: ['input', 'guest']` in `ServerConfig`; the server
281
316
  does not read the environment itself.
282
317
 
318
+ ### Lifecycle operations
319
+
320
+ Every accepted create, clone, start, stop, suspend, restart, snapshot restore,
321
+ resize, move and delete records a lifecycle operation, and the tool that made the call
322
+ says its `operation_id` — in its sentence, as `(operation op_…)`, and in the
323
+ JSON. `move_computer` carries it onto the outcome it reports. It is absent where
324
+ the platform could not record one; the call happened either way.
325
+
326
+ `get_operation` reads one, and `list_operations` pages through them newest
327
+ first (`computer_id`, `limit`, `cursor`, `idempotency_key`; `computer_id` is not
328
+ defaulted to the selected computer, and `idempotency_key` finds the operation a
329
+ call sent with that key recorded, even when the call's answer was lost). `wait_for_operation` polls one until it is final: it
330
+ answers on `succeeded`, and a `failed` one is an error carrying the platform's
331
+ `error.code` (`start_failed`, `build_failed`, `computer_gone`, `move_failed`,
332
+ `resize_not_applied`, `lost`, and more may be added) and its sentence.
333
+
334
+ `succeeded` means the platform finished its step, not that the desktop has
335
+ booted: `wait_for_computer` is still the wait for a desktop that answers. Most
336
+ operations are already `succeeded` when their tool answers; a clone is
337
+ `running` until its disk is copied, and a move until it lands. All three are
338
+ reads, and all three stay registered when the lifecycle tools are withheld,
339
+ since start, stop, suspend and restart record operations too.
340
+
341
+ The eleven lifecycle tools (`create_computer`, `clone_computer`,
342
+ `start_computer`, `stop_computer`, `suspend_computer`, `restart_computer`,
343
+ `update_computer`, `move_computer`, `delete_computer`, `restore_snapshot`,
344
+ `clone_snapshot`) take an optional `idempotency_key`. Leave it out and a fresh
345
+ one is sent for you; a failure whose outcome is unknown names the key it went
346
+ with. What to do next depends on who answered:
347
+
348
+ - **The answer was lost** (the connection dropped after the request went out,
349
+ or a proxy in front of the platform gave up), or the platform answers
350
+ `idempotency_in_progress`: call the same tool again with the same
351
+ `idempotency_key`. The platform answers with the first call's result, or says
352
+ it is still running; the step is not done twice.
353
+ - **The platform answered a `5xx` that names no `operation_id`**: nothing may
354
+ have been done. A refusal given before the call was sent anywhere — another
355
+ launch already in progress, no host to place it on, the template catalogue
356
+ incomplete — names none, and the platform releases its key. Call the same
357
+ tool again with the same `idempotency_key`: the platform carries it out if
358
+ the key was released, or answers `idempotency_outcome_unknown` if not, and
359
+ only then does the case below apply.
360
+ - **The platform answered a `5xx` that names an `operation_id`**, or
361
+ `idempotency_outcome_unknown`: the key is spent, and resending with it only
362
+ answers `idempotency_outcome_unknown`.
363
+ The platform then keeps the call's operation `pending` for up to an hour,
364
+ whatever happened, while its host may be carrying it out. What to read
365
+ depends on the tool:
366
+ - `start_computer`, `stop_computer`, `suspend_computer`,
367
+ `delete_computer`, `move_computer` and `update_computer`: read
368
+ `get_computer` (and `get_operation` with the `operation_id` the error
369
+ named: `succeeded` means the step happened, `running` means wait on it,
370
+ and `pending` alone is no reason to wait). If the step did not take
371
+ effect, send the call again with a new key, or none.
372
+ - `restart_computer`, which `get_computer` cannot show: a computer reads
373
+ `running` before and after a reset. Read the operation instead
374
+ (`get_operation` with the `operation_id` the error named, or
375
+ `list_operations` with the key): `succeeded` means the restart happened,
376
+ `running` means wait on it. Anything else leaves the restart possibly
377
+ done, so ask the user before sending it again with a new key, or none; a
378
+ second restart resets the guest again.
379
+ - `create_computer`, `clone_computer`, `clone_snapshot` and
380
+ `restore_snapshot`, where a read of the computer made straight away can
381
+ show no effect for a build or restore that then lands, and a second one
382
+ is a second computer or a second overwrite: read the operation first —
383
+ `get_operation` with the `operation_id` the error named, or
384
+ `list_operations` with the key — and while it is `pending` or `running`,
385
+ wait (`wait_for_operation`) and do not resend with any key. Only once it
386
+ is final as `failed` (`error.code` `lost`, for one) or no longer found, AND
387
+ `get_computer` (`list_computers` after a create or a clone) shows the step
388
+ did not happen, send the call again with a new key, or none. If no
389
+ operation is found at all, read the computer again after a few minutes
390
+ before resending.
391
+ - **`idempotency_key_reused`**: a different call already used that key, and
392
+ nothing was done. Send this one with a new key, or none.
393
+
394
+ Keys last 24 hours.
395
+
396
+ ### Who you are, and API keys
397
+
398
+ `whoami` takes no arguments and reads `GET /api/v1/whoami`: the person this
399
+ server's API key was issued to, the account and role it acts with (the role as
400
+ it is now), the workspace it is confined to, and the key itself — including
401
+ `manage_keys`, whether it may manage API keys. It needs no permission and any
402
+ role, and a suspended account can call it; the answer says so when the account
403
+ is suspended. Behind the hosted server's OAuth sign-in, the key is the Connected
404
+ app's (`prefix` `oauth`).
405
+
406
+ `list_api_keys` lists the key holder's API keys on the account, newest first —
407
+ never a raw key. Each carries `minted_by_key_id`: the key that minted it over
408
+ the API, or `null` for one minted from the dashboard. Revoking a key does not
409
+ revoke the keys it minted. It needs this server's key to have the **Manage keys**
410
+ permission, which only a person can turn on, in the dashboard; without it the
411
+ tool answers the platform's 403, whose sentence says exactly that. A Connected
412
+ app's key never has the permission.
413
+
414
+ **Minting and revoking keys are not offered here, on purpose.** A mint answers
415
+ the new key in full, once, and a model's context — the conversation, the
416
+ client's logs, whatever the transcript is shared with — is the worst place for a
417
+ long-lived credential and one nobody can take it back from. A revoke is
418
+ irreversible, and on a model's reading of a list it can cut off the person's CI,
419
+ another agent, or this very session. People do both from the dashboard or with
420
+ the `mandala api-keys` CLI.
421
+
422
+ `list_workspaces`, `get_workspace` and `list_workspace_members` read the
423
+ account's workspaces, which partition its computers. A key confined to a
424
+ workspace lists only that one, and is refused (403) the member list, which is
425
+ the whole account's: workspaces do not divide people. All three are reads;
426
+ workspaces are created, renamed and deleted in the dashboard.
427
+
283
428
  ### Current account quota
284
429
 
285
430
  `get_account` takes no arguments and reads `GET /api/v1/account` once with the
@@ -331,6 +476,12 @@ anything and keep the hint. When a stretch of work is over, `suspend_computer` (
331
476
  suspend catches the ones a model forgets, but only after 30 minutes untouched.
332
477
  `get_usage` is what says what any of it cost.
333
478
 
479
+ **A screenshot need not be the whole screen.** `screenshot` takes `region` (a
480
+ crop, in the screen pixels `click` takes, before any scaling), `scale` (0 to 1),
481
+ `format` (`png` or `jpeg`) and `quality` (JPEG, 1-100). A cropped or scaled
482
+ picture is in its own pixel space, so the tool says alongside it how to turn a
483
+ position in it back into screen coordinates.
484
+
334
485
  **Ten clicks need not be ten screenshots.** Driving the desktop one tool at a
335
486
  time — `screenshot`, `click`, `screenshot` — puts an image in the calling
336
487
  model's context for every step. `run_agent` hands a task in plain language to
@@ -733,9 +884,15 @@ listing leaves it out — every ordinary caller is asking "what can I restore".
733
884
  `list_snapshots(include_unfinished: true)` is the flag for when the question is
734
885
  about storage instead.
735
886
 
736
- **A 409 usually clears; a 400 never does.** A guest still booting or a busy
737
- guest agent answers 409. The platform's own error messages come through
738
- unedited, because they are written to be acted on.
887
+ **Read the `reason`, not the status.** Whether a refusal clears on its own is
888
+ the platform's `reason` word, and each tool's answer says what it means:
889
+ `contention` (something was in flight) and `starting` (the guest agent is still
890
+ inside its boot window) clear, so the same call works in a moment;
891
+ `unavailable`, `running`, `unsupported`, `exists` and `revoked` do not. A 409
892
+ does not mean "wait": `exec`, input and the clipboard on a stopped computer
893
+ answer 409 `unavailable`, and `start_computer` is what fixes that, while
894
+ `running` needs the computer stopped first. The platform's own error messages
895
+ come through unedited, because they are written to be acted on.
739
896
 
740
897
  HTTP failures preserve their actual response status. An unsupported method on a
741
898
  known path is `MethodNotAllowedError` (405), with the received `Allow` value when
@@ -973,7 +1130,8 @@ address.
973
1130
  Settings → Connected apps.
974
1131
  - **A bearer is checked with the platform before it gets anything.** An
975
1132
  initialize makes no session until the platform accepts its token — a `2xx`
976
- from `GET ssh-keys`, which every valid credential gets — so invented tokens
1133
+ from `GET whoami`, which every valid credential gets, a suspended account's
1134
+ included — so invented tokens
977
1135
  cannot fill the session pool. A `401` is the challenge above; any other
978
1136
  answer is `503` and nothing is remembered. Refused initializes are budgeted
979
1137
  per source address, 20 a minute. Past that, a token already accepted still
@@ -983,6 +1141,65 @@ address.
983
1141
  carrying a request is checked again before it is dispatched, with an
984
1142
  acceptance cached for 60 s under the token's digest, so an expired or
985
1143
  revoked token is a clean `401` before any stream opens.
1144
+ - **One bearer holds at most 16 sessions**, and a suspended account's bearer
1145
+ one (enough to ask `whoami`), so no single token can fill the pool. At that
1146
+ limit, an initialize closes the bearer's least recently used idle session to
1147
+ make room, once its own session exists (a refused initialize closes
1148
+ nothing), and a client whose old session is gone initializes again; if none
1149
+ is idle, the answer is `429` with `Retry-After`. One race differs: if the
1150
+ idle session it planned to close is put to work before the new session is
1151
+ made, the new one is dropped instead and the initialize is answered `404`
1152
+ (`Session not found`, no `Retry-After`), which an SDK client reports as a
1153
+ failed connect. Other bearers' sessions are never touched. An account
1154
+ suspended after its bearer opened sessions keeps only one: the next request
1155
+ on any of them closes the bearer's other idle sessions, as does its next
1156
+ initialize, and one still serving a request goes on a later request once
1157
+ it is idle. A
1158
+ `429` names what the bearer holds, so one still over its limit is told to
1159
+ wait for its busy sessions rather than to close one. `runHttp` takes the 16
1160
+ as `maxSessionsPerBearer`, and the CLI reads it from
1161
+ `MANDALA_MCP_MAX_SESSIONS_PER_TOKEN` (a whole number from 1 to 256, the
1162
+ whole pool; anything else is refused at startup; a token is also held to
1163
+ its account's ceiling, below). A self-hosted `--http` server applies it
1164
+ too, but checks no bearer with the platform, so there any string is its own
1165
+ bucket: the per-bearer cap spreads one key's clients, and what bounds an
1166
+ untrusted caller is the process-wide cap (256), and the default loopback
1167
+ bind until you expose the port.
1168
+ - **One account holds at most 128 sessions, half the pool, across all its
1169
+ tokens.** Sessions are counted by the account id the platform's `whoami`
1170
+ names for each token, so an account with many keys or grants counts once;
1171
+ a token whose `whoami` names no account counts as an account of its own.
1172
+ Initializes in flight are counted too, so a burst across one account's keys
1173
+ cannot pass the ceiling together. At the ceiling, an initialize makes room
1174
+ by closing idle sessions of two kinds only: the account's sessions whose
1175
+ token the platform has already refused on a request made there, first,
1176
+ and then the requesting token's own least recently used,
1177
+ as at its own cap and even while it is under that cap. When that is not
1178
+ enough the answer is `429` with `Retry-After`, saying it is the account's
1179
+ limit and naming only that number. A live session of another token is
1180
+ never closed for the ceiling, nor any session of another account, and the
1181
+ race above applies here too (`404`). A refused session counts until it is
1182
+ closed, so revoking keys never lets an account past its ceiling; a session
1183
+ whose token was revoked but never sent a request again is not known to be
1184
+ refused, and counts until it idles out. So does the session a token
1185
+ leaves behind when the client refreshes it (see below): it is neither the
1186
+ new token's own nor refused, so an account that holds more than half its
1187
+ ceiling when its clients refresh can be answered `429` for up to the
1188
+ session idle timeout (30 minutes), longer than its `Retry-After` suggests,
1189
+ unless those clients close their old sessions. A revoked token's session
1190
+ idles out within about the acceptance window (60 s) plus that timeout even
1191
+ while its holder keeps sending notifications, holding the stream, or
1192
+ sending `HEAD /mcp` or a `DELETE /mcp` the server refuses: traffic that
1193
+ carries no request is never checked with the platform, so it keeps a
1194
+ session alive only while the token's acceptance is still cached. Suspension is held per
1195
+ token, as above, not per account. A full pool (256) is answered `503` with
1196
+ `Retry-After`, whoever holds it, and nothing is closed to make room in it.
1197
+ `runHttp` takes the ceiling as `maxSessionsPerAccount`, and the CLI reads
1198
+ it from `MANDALA_MCP_MAX_SESSIONS_PER_ACCOUNT` (a whole number from 1 to
1199
+ 256; anything else is refused at startup). A self-hosted server verifies
1200
+ no account, so there each token is an account of its own, held to the
1201
+ ceiling as well as to its own cap; to let one token hold more than 128
1202
+ sessions, raise both.
986
1203
  - **A token the platform refuses during a call comes back as that same
987
1204
  `401`**, not as a tool error, so the client refreshes or authorizes again —
988
1205
  the answer is held until its first byte for this. The one case that cannot
@@ -995,7 +1212,11 @@ address.
995
1212
  `404 Unknown session`, the MCP spec's signal to initialize again. An access
996
1213
  token says nothing this server can check about which grant it came from, so
997
1214
  rebinding would let any valid credential that learned a session id take over
998
- its bound computer, buffered events and retained results.
1215
+ its bound computer, buffered events and retained results. The old session
1216
+ is not closed by the refresh: it still counts against the token's account
1217
+ ceiling (above) until the client closes it with `DELETE /mcp` under the old
1218
+ token (only the token's digest is compared, so an expired one still works)
1219
+ or it idles out after 30 minutes.
999
1220
  - `X-Mandala-MCP-Service` carries `MANDALA_MCP_SERVICE_SECRET` on every
1000
1221
  platform request, only ever to `MANDALA_BASE_URL`, so a token cannot be
1001
1222
  replayed at the API directly by the app it was issued to. A client's own
@@ -1017,8 +1238,10 @@ address.
1017
1238
  | `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. |
1018
1239
  | `MANDALA_MCP_RESOURCE_METADATA_URL` | `--http` only. The OAuth protected-resource metadata URL this server is published under. Set, it answers OAuth clients as described under Hosted, with OAuth; unset, callers bring an API key. |
1019
1240
  | `MANDALA_MCP_SERVICE_SECRET` | `--http` only. Sent as `X-Mandala-MCP-Service` on every platform request, to `MANDALA_BASE_URL` only. Never logged. |
1241
+ | `MANDALA_MCP_MAX_SESSIONS_PER_TOKEN` | `--http` only. How many live sessions one bearer token may hold. Default 16; a whole number from 1 to 256 (the server's whole session pool), and anything else is refused at startup. A suspended account's token is held to one whatever this says. A token is also held to its account's limit, below. |
1242
+ | `MANDALA_MCP_MAX_SESSIONS_PER_ACCOUNT` | `--http` only. How many live sessions one account may hold across all its tokens. Default 128, half the pool; a whole number from 1 to 256, and anything else is refused at startup. Hosted (the metadata URL set), the account is the one the platform's `whoami` names for the token; self-hosted, or when `whoami` names none, each token is an account of its own. Past it an initialize is refused `429`, after closing only the token's own idle sessions or the account's refused ones. Suspension is not counted here; it holds each token to one. |
1020
1243
 
1021
- Every one of these but the model key, the two tool filters and the two OAuth settings has a flag as well, and a flag overrides
1244
+ Every one of these but the model key, the two tool filters, the two OAuth settings and the two session limits has a flag as well, and a flag overrides
1022
1245
  the environment: `--http`, `--port`, `--host`, `--base-url`, `--computer`,
1023
1246
  `--allowed-hosts`, `--allowed-origins`, `--no-lifecycle`, local `--profile`, plus `--help` and
1024
1247
  `--version`. `--key` exists for a caller launching several servers under
@@ -1085,7 +1308,13 @@ default 20. This is computer control, not hosted general-purpose inference or a
1085
1308
  chat UI; it adds no key storage or model billing service.
1086
1309
 
1087
1310
  This tool deliberately sends `stream:false` and uses the JSON response so the
1088
- result preserves the underlying `agent.stop`, step count and token usage.
1311
+ result preserves the underlying `agent.stop`, step count and token usage: the
1312
+ streamed form of this route carries no usage and no `agent` block. The price is
1313
+ time. **On the hosted API a request that does not stream is cut at the edge after
1314
+ about 120 seconds** (a bodiless `524`), which stops the run where it was and loses
1315
+ its result, usage and steps; the default 20 steps can take longer than that. Use
1316
+ `run_agent`, which streams, for anything that may. The tool answers a `524` by
1317
+ saying so rather than as a generic gateway failure.
1089
1318
  Only explicit `end_turn` consistent with the completion's finish reason can
1090
1319
  report success. Limits, refusal, missing or conflicting terminal fields remain
1091
1320
  errors with valid partial results. Nested failures preserve the agent computer,
@@ -1098,9 +1327,21 @@ Neither agent tool starts the computer or switches endpoints after a failure.
1098
1327
  The caller supplies the model key through `MANDALA_MODEL_KEY` on stdio or their
1099
1328
  own `X-Model-Key` header on HTTP. The account Authorization remains separate;
1100
1329
  HTTP never falls back to the operator's model key. Waiting heartbeats count
1101
- notifications, not completed actions. Long-running clients need a
1102
- `progressToken` plus `resetTimeoutOnProgress`; otherwise choose a smaller
1103
- `max_steps`. That bound is neither a time cap nor a spend cap.
1330
+ notifications, not completed actions. A `progressToken` plus
1331
+ `resetTimeoutOnProgress` keeps the MCP client waiting, but not the hosted edge
1332
+ above; `max_steps` is neither a time cap nor a spend cap.
1333
+
1334
+ On both agent tools a failure status is read for what it is on these routes
1335
+ rather than for what it means elsewhere. The platform's own re-check of the
1336
+ caller is a `401` or `403` carrying `reason: "revoked"`, and only that gets
1337
+ account advice. A `402`, `504` or `529` is the model provider's own status for
1338
+ the configured model key (its billing, timeout or overloaded error), so the
1339
+ advice is to check the model-provider account, not the Mandala plan; a `403`
1340
+ without `revoked` may be the provider's permission error for that key. The
1341
+ exception is a refusal `run_agent_chat` is given before any run started — a flat
1342
+ `{error: string}` body, from the role gate, a bad body, a suspension or the
1343
+ meter: nothing ran, and it keeps the usual role and plan advice every other tool
1344
+ gets.
1104
1345
 
1105
1346
  ## Development
1106
1347
 
package/dist/api.d.ts CHANGED
@@ -4,6 +4,25 @@ export { isSecretStoreRoute } from './secret-errors.js';
4
4
  export declare const DEFAULT_BASE_URL = "https://app.mandala.computer/api/v1";
5
5
  /** Anthropic's own key, forwarded for the one route that runs a model. */
6
6
  export declare const MODEL_KEY_HEADER = "X-Model-Key";
7
+ /**
8
+ * The header every lifecycle call carries so that sending it again cannot do
9
+ * it twice (platform OPL-5127). The platform records a call that carries one
10
+ * before carrying it out, and for 24 hours answers the same key with the same
11
+ * request from that record instead of doing the call again: `409` with `code:
12
+ * "idempotency_in_progress"` while it runs, the original answer once it has
13
+ * finished. A different request under the same key is a `422`.
14
+ */
15
+ export declare const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
16
+ /** The platform's rule for a key: 1 to 255 characters, each printable ASCII other than a space. */
17
+ export declare const IDEMPOTENCY_KEY_PATTERN: RegExp;
18
+ /**
19
+ * The key for ONE lifecycle tool call — the caller's, already checked by the
20
+ * tool's input schema, or a fresh random one — made once, before the request,
21
+ * so that whatever re-sends this call re-sends the same key.
22
+ */
23
+ export declare const idempotencyKeyFor: (given?: string) => string;
24
+ /** The header that carries it. */
25
+ export declare const idempotencyHeaders: (key: string) => Record<string, string>;
7
26
  /**
8
27
  * Proof, to the platform, that a request comes through the hosted MCP service
9
28
  * (OPL-4982). The platform takes an OAuth access token only alongside it, so a
@@ -19,7 +38,7 @@ export type RequestOptions = {
19
38
  body?: unknown;
20
39
  /** Raw bytes as the request body, for the file upload. Mutually exclusive with `body`. */
21
40
  raw?: Uint8Array;
22
- /** Extra headers for this call only — currently just the model key. */
41
+ /** Extra headers for this call only: the model key, and a lifecycle call's Idempotency-Key. */
23
42
  headers?: Record<string, string>;
24
43
  signal?: AbortSignal;
25
44
  };
@@ -65,6 +84,12 @@ export type Bytes = {
65
84
  * differently" and "there is no offset that will work on this file".
66
85
  */
67
86
  unrangeable: boolean;
87
+ /**
88
+ * `X-GC-Frame`, when the platform labelled what it served: `suspended` is a
89
+ * screenshot answered with the saved frame of a suspended computer rather
90
+ * than a capture of its screen.
91
+ */
92
+ frame?: string;
68
93
  };
69
94
  /**
70
95
  * The longest foreground guest exec waits 600 seconds before it answers.
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAmE,MAAM,QAAQ,CAAC;AAsBhG,OAAO,EAAsB,KAAK,WAAW,EAA0B,MAAM,mBAAmB,CAAC;AAEjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,eAAO,MAAM,gBAAgB,wCAAwC,CAAC;AAEtE,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAExD,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,QAAyB,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,KAAG,MAA+B,CAAC;AAEnF,kCAAkC;AAClC,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAEpE,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,0BAA0B,CAAC;AA2BtD,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0FAA0F;IAC1F,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,+FAA+F;IAC/F,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD;;;;;;;OAOG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAkCF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,SAAU,CAAC;AACnD,6EAA6E;AAC7E,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,mBAAmB,OAG9B,CAAC;AAWH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,aAAa,QAAO,OAAO,UAAU,CAAC,KAG7B,CAAC;AAEvB,iDAAiD;AACjD,MAAM,MAAM,QAAQ,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAExD;;;;;;;;;;;GAWG;AACH;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAEF,qBAAa,GAAG;;IACd,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAUvB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,MAAyB,EAClC,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,UAAe;IAsD1B;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,GAAG;IAiW1C;;;;;;;;;;;;OAYG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAe5F,4FAA4F;IACtF,cAAc,CAAC,CAAC,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,CAAC;IAexC,8FAA8F;IACxF,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,YAAY,CAAC;IA2DlB,WAAW,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,OAAO,CAAC;IAuBnB;;;;;;OAMG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAazB;;;;;;;;;;;;;;OAcG;IACG,OAAO,CAAC,CAAC,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC;QAAE,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAmB/D,kFAAkF;IAC5E,KAAK,CACT,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,cAAmB,EACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC,GACpD,OAAO,CAAC,KAAK,CAAC;IAgGjB;;;;;;;;;;OAUG;IACH,IAAI,OAAO,IAAI,WAAW,CA6FzB;IAED;;;;;;OAMG;IACI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,cAAc,CAAC,QAAQ,CAAC;CAyG9F;AAyCD;;;;;;;;;GASG;AACH,wBAAiB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAQnF;AAkgBD,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAmD3E;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC"}
package/dist/api.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { Agent, Headers as UndiciHeaders, fetch as undiciFetch } from 'undici';
2
3
  import { CancelledError, ConnectivityError, ConnectivityInterruptedError, createOnlyRefusal, errorForStatus, isCreateOnlyUpload, MandalaError, platformSaid, RangeNotSatisfiableError, RedirectError, } from './errors.js';
3
4
  import * as P from './paths.js';
@@ -7,6 +8,27 @@ export { isSecretStoreRoute } from './secret-errors.js';
7
8
  export const DEFAULT_BASE_URL = 'https://app.mandala.computer/api/v1';
8
9
  /** Anthropic's own key, forwarded for the one route that runs a model. */
9
10
  export const MODEL_KEY_HEADER = 'X-Model-Key';
11
+ /**
12
+ * The header every lifecycle call carries so that sending it again cannot do
13
+ * it twice (platform OPL-5127). The platform records a call that carries one
14
+ * before carrying it out, and for 24 hours answers the same key with the same
15
+ * request from that record instead of doing the call again: `409` with `code:
16
+ * "idempotency_in_progress"` while it runs, the original answer once it has
17
+ * finished. A different request under the same key is a `422`.
18
+ */
19
+ export const IDEMPOTENCY_KEY_HEADER = 'Idempotency-Key';
20
+ /** The platform's rule for a key: 1 to 255 characters, each printable ASCII other than a space. */
21
+ export const IDEMPOTENCY_KEY_PATTERN = /^[\x21-\x7e]{1,255}$/;
22
+ /**
23
+ * The key for ONE lifecycle tool call — the caller's, already checked by the
24
+ * tool's input schema, or a fresh random one — made once, before the request,
25
+ * so that whatever re-sends this call re-sends the same key.
26
+ */
27
+ export const idempotencyKeyFor = (given) => given ?? randomUUID();
28
+ /** The header that carries it. */
29
+ export const idempotencyHeaders = (key) => ({
30
+ [IDEMPOTENCY_KEY_HEADER]: key,
31
+ });
10
32
  /**
11
33
  * Proof, to the platform, that a request comes through the hosted MCP service
12
34
  * (OPL-4982). The platform takes an OAuth access token only alongside it, so a
@@ -721,6 +743,7 @@ export class Api {
721
743
  : bytes.length,
722
744
  unrangeable: (resp.headers.get('accept-ranges') ?? '').trim().toLowerCase() === 'none',
723
745
  window,
746
+ frame: resp.headers.get('x-gc-frame')?.trim().toLowerCase() || undefined,
724
747
  };
725
748
  }
726
749
  // --- the account's secret store (OPL-4984) ------------------------------