mandala-computer-mcp 0.7.0 → 0.9.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 (84) hide show
  1. package/README.md +232 -29
  2. package/dist/api.d.ts +6 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +13 -3
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli.d.ts +12 -0
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +76 -1
  9. package/dist/cli.js.map +1 -1
  10. package/dist/credentials.d.ts +55 -0
  11. package/dist/credentials.d.ts.map +1 -1
  12. package/dist/credentials.js +104 -2
  13. package/dist/credentials.js.map +1 -1
  14. package/dist/errors.d.ts +57 -16
  15. package/dist/errors.d.ts.map +1 -1
  16. package/dist/errors.js +112 -42
  17. package/dist/errors.js.map +1 -1
  18. package/dist/format.d.ts +17 -2
  19. package/dist/format.d.ts.map +1 -1
  20. package/dist/format.js +27 -4
  21. package/dist/format.js.map +1 -1
  22. package/dist/http.d.ts +58 -0
  23. package/dist/http.d.ts.map +1 -1
  24. package/dist/http.js +713 -57
  25. package/dist/http.js.map +1 -1
  26. package/dist/paths.d.ts +18 -11
  27. package/dist/paths.d.ts.map +1 -1
  28. package/dist/paths.js +31 -14
  29. package/dist/paths.js.map +1 -1
  30. package/dist/secret-store.d.ts +2 -1
  31. package/dist/secret-store.d.ts.map +1 -1
  32. package/dist/secret-store.js +6 -1
  33. package/dist/secret-store.js.map +1 -1
  34. package/dist/server.d.ts +2 -1
  35. package/dist/server.d.ts.map +1 -1
  36. package/dist/server.js +2 -1
  37. package/dist/server.js.map +1 -1
  38. package/dist/session.d.ts +24 -0
  39. package/dist/session.d.ts.map +1 -1
  40. package/dist/session.js +6 -0
  41. package/dist/session.js.map +1 -1
  42. package/dist/stdio.d.ts.map +1 -1
  43. package/dist/stdio.js +9 -1
  44. package/dist/stdio.js.map +1 -1
  45. package/dist/tool-filters.d.ts +1 -1
  46. package/dist/tool-filters.d.ts.map +1 -1
  47. package/dist/tool-filters.js +3 -0
  48. package/dist/tool-filters.js.map +1 -1
  49. package/dist/tools/account.d.ts.map +1 -1
  50. package/dist/tools/account.js +80 -6
  51. package/dist/tools/account.js.map +1 -1
  52. package/dist/tools/agent.js +12 -23
  53. package/dist/tools/agent.js.map +1 -1
  54. package/dist/tools/chat.d.ts.map +1 -1
  55. package/dist/tools/chat.js +56 -7
  56. package/dist/tools/chat.js.map +1 -1
  57. package/dist/tools/computers.d.ts.map +1 -1
  58. package/dist/tools/computers.js +93 -8
  59. package/dist/tools/computers.js.map +1 -1
  60. package/dist/tools/guest.d.ts.map +1 -1
  61. package/dist/tools/guest.js +28 -3
  62. package/dist/tools/guest.js.map +1 -1
  63. package/dist/tools/input.d.ts.map +1 -1
  64. package/dist/tools/input.js +98 -48
  65. package/dist/tools/input.js.map +1 -1
  66. package/dist/tools/operations.d.ts +7 -2
  67. package/dist/tools/operations.d.ts.map +1 -1
  68. package/dist/tools/operations.js +29 -6
  69. package/dist/tools/operations.js.map +1 -1
  70. package/dist/tools/secrets.d.ts.map +1 -1
  71. package/dist/tools/secrets.js +80 -25
  72. package/dist/tools/secrets.js.map +1 -1
  73. package/dist/tools/snapshots.d.ts.map +1 -1
  74. package/dist/tools/snapshots.js +21 -8
  75. package/dist/tools/snapshots.js.map +1 -1
  76. package/dist/tools/templates.js +2 -2
  77. package/dist/tools/templates.js.map +1 -1
  78. package/dist/tools/webhooks.js +1 -1
  79. package/dist/tools/webhooks.js.map +1 -1
  80. package/dist/version.d.ts +8 -0
  81. package/dist/version.d.ts.map +1 -0
  82. package/dist/version.js +8 -0
  83. package/dist/version.js.map +1 -0
  84. package/package.json +1 -1
package/README.md CHANGED
@@ -40,7 +40,8 @@ claude mcp add mandala -- npx -y mandala-computer-mcp --profile Work
40
40
  Names are case-sensitive. An explicit key or nonempty `MANDALA_API_KEY` takes
41
41
  precedence over every profile and avoids accessing the credential store.
42
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`.
43
+ You can still use a key from **Settings → Credentials → API keys** with
44
+ `MANDALA_API_KEY`.
44
45
 
45
46
  Saved credentials require POSIX protection: a real directory owned by you with
46
47
  mode `0700`, and an owned regular file with mode `0600` and one link. Symlinks,
@@ -52,9 +53,29 @@ A saved profile also binds its API base URL. `--base-url` or `MANDALA_BASE_URL`
52
53
  must match that stored base after canonicalization, including the entire path
53
54
  prefix and port. An explicitly empty base flag is invalid with saved credentials.
54
55
  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.
56
+ the key in **Settings → Credentials → API keys** makes later calls fail; MCP
57
+ does not switch profiles, reread the file, or retry a refused action. Run login
58
+ explicitly when new credentials are needed.
59
+
60
+ A saved profile can also carry a default workspace, set with
61
+ `mandala workspaces use <workspace>` (and removed with `--clear`); the CLI keeps
62
+ it in `~/.mandala/defaults.json`, beside `credentials.json`, which it never
63
+ changes. Started from that profile, the secret tools (`list_secrets`,
64
+ `get_secret`, `create_secret`, `set_secret`, `replace_secret`,
65
+ `delete_secret`) use it when a call leaves out `workspace_id`; an explicit
66
+ `workspace_id` always wins. Their descriptions say so and name the workspace,
67
+ `list_secrets` says when the default chose the scope, and startup prints it on
68
+ stderr. MCP only reads that file, once at startup, with the same checks as
69
+ `credentials.json`. It is not applied when the profile's key is confined to a
70
+ workspace (that key's own scope applies), when the default was saved for
71
+ another account than the profile is logged in to now, or with an explicit or
72
+ environment key. A file that cannot be read (not valid JSON, another version,
73
+ or readable by others) is noted on stderr: `list_secrets` and `get_secret` then
74
+ use account-wide, but `create_secret`, `set_secret`, `replace_secret` and
75
+ `delete_secret` refuse a call without `workspace_id`, sending nothing, rather
76
+ than act account-wide; pass `workspace_id`, or fix or delete the file and
77
+ restart the server. The hosted HTTP server never reads it, and its tools are
78
+ unchanged.
58
79
 
59
80
  **Claude Code** — as a plugin, which installs the server and a skill together:
60
81
 
@@ -152,6 +173,13 @@ Parameter and response-mode support remains a separate contract.
152
173
  `press_key`, `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`,
153
174
  `wait`
154
175
 
176
+ Every one of these that acts — all but `screenshot` and `cursor_position` —
177
+ takes `context: true` and then answers the desktop as it stands just after the
178
+ action: the windows `list_windows` would list and the one with focus, without a
179
+ second call. They are read once, straight after the action, so a window still
180
+ opening may not be listed yet; when they cannot be read the action still
181
+ happened, and the answer says why.
182
+
155
183
  **Inside the guest** — `exec`, `exec_poll`, `exec_kill`, `get_execution`,
156
184
  `read_execution_output`, `open_url`,
157
185
  `list_windows`, `window_action`, `read_clipboard`, `write_clipboard`,
@@ -188,7 +216,8 @@ which says nothing about the path; `isTransient` is false for both.
188
216
  **Account quota** — `get_account`
189
217
 
190
218
  **Who you are** — `whoami`, `list_api_keys`, `list_workspaces`,
191
- `get_workspace`, `list_workspace_members`. Minting and revoking API keys are
219
+ `get_workspace`, `list_workspace_members`, `create_workspace`,
220
+ `rename_workspace`, `delete_workspace`. Minting and revoking API keys are
192
221
  deliberately not tools — see [Who you are, and API keys](#who-you-are-and-api-keys).
193
222
 
194
223
  **Spending** — `get_usage`
@@ -218,10 +247,10 @@ binding list (`[]` removes every binding) and reaches the guest at the
218
247
  computer's next start or restart; send the `version` a read answered to have it
219
248
  refused with 409 if the list changed since. A replaced value also reaches a
220
249
  running computer: a file binding's file is rewritten in place, and on an image
221
- that supports it an env binding reaches new shells and `exec` with
222
- `desktop: true` (programs already running keep the old value until a restart).
223
- A command that needs a bound variable should use `desktop: true`. Whether a
224
- plain root `exec` sees it depends on the platform version. `replace_secret`
250
+ that supports it an env binding reaches new shells, and every `exec`, plain
251
+ or with `desktop: true`, runs with the bound variables as they are at that
252
+ moment (programs already running keep the old value until a restart).
253
+ `replace_secret`
225
254
  and `delete_secret` need the current `revision_id`, and a stale one is a 409.
226
255
  `delete_secret` also needs `confirm: true`: a computer still bound to a deleted
227
256
  secret cannot start again until that binding is removed. A bound computer runs,
@@ -277,7 +306,7 @@ with an error listing all valid tags.
277
306
 
278
307
  | Tag | Tools |
279
308
  | --- | --- |
280
- | `account` | `get_account`, `whoami`, `list_api_keys`, `list_workspaces`, `get_workspace`, `list_workspace_members` |
309
+ | `account` | `get_account`, `whoami`, `list_api_keys`, `list_workspaces`, `get_workspace`, `list_workspace_members`, `create_workspace`, `rename_workspace`, `delete_workspace` |
281
310
  | `computers` | `list_computers`, `get_computer`, `use_computer`, `wait_for_computer`, `get_desktop_url`, `list_sizes` |
282
311
  | `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
312
  | `input` | `screenshot`, `click`, `type_text`, `paste_text`, `press_key`, `scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`, `wait` |
@@ -350,8 +379,16 @@ with. What to do next depends on who answered:
350
379
  `idempotency_in_progress`: call the same tool again with the same
351
380
  `idempotency_key`. The platform answers with the first call's result, or says
352
381
  it is still running; the step is not done twice.
353
- - **The platform answered a `5xx`**, or `idempotency_outcome_unknown`: the key
354
- is spent, and resending with it only answers `idempotency_outcome_unknown`.
382
+ - **The platform answered a `5xx` that names no `operation_id`**: nothing may
383
+ have been done. A refusal given before the call was sent anywhere — another
384
+ launch already in progress, no host to place it on, the template catalogue
385
+ incomplete — names none, and the platform releases its key. Call the same
386
+ tool again with the same `idempotency_key`: the platform carries it out if
387
+ the key was released, or answers `idempotency_outcome_unknown` if not, and
388
+ only then does the case below apply.
389
+ - **The platform answered a `5xx` that names an `operation_id`**, or
390
+ `idempotency_outcome_unknown`: the key is spent, and resending with it only
391
+ answers `idempotency_outcome_unknown`.
355
392
  The platform then keeps the call's operation `pending` for up to an hour,
356
393
  whatever happened, while its host may be carrying it out. What to read
357
394
  depends on the tool:
@@ -398,8 +435,9 @@ app's (`prefix` `oauth`).
398
435
  `list_api_keys` lists the key holder's API keys on the account, newest first —
399
436
  never a raw key. Each carries `minted_by_key_id`: the key that minted it over
400
437
  the API, or `null` for one minted from the dashboard. Revoking a key does not
401
- revoke the keys it minted. It needs this server's key to have the **Manage keys**
402
- permission, which only a person can turn on, in the dashboard; without it the
438
+ revoke the keys it minted. It needs this server's key to be allowed to manage
439
+ keys, which only a person can turn on in the dashboard (Settings → Credentials →
440
+ API keys → the key's menu → **Allow managing keys**); without it the
403
441
  tool answers the platform's 403, whose sentence says exactly that. A Connected
404
442
  app's key never has the permission.
405
443
 
@@ -414,8 +452,15 @@ the `mandala api-keys` CLI.
414
452
  `list_workspaces`, `get_workspace` and `list_workspace_members` read the
415
453
  account's workspaces, which partition its computers. A key confined to a
416
454
  workspace lists only that one, and is refused (403) the member list, which is
417
- the whole account's: workspaces do not divide people. All three are reads;
418
- workspaces are created, renamed and deleted in the dashboard.
455
+ the whole account's: workspaces do not divide people. All three are reads.
456
+
457
+ `create_workspace`, `rename_workspace` and `delete_workspace` change them, and
458
+ need an owner's key that is not confined to a workspace: any key confined to
459
+ one is refused (403), so none of them can touch the workspace this server's own
460
+ key stands in. `delete_workspace` needs `confirm: true` and REVOKES every API
461
+ key confined to the workspace, whoever holds it — a person's CI or another agent
462
+ may be using one — and answers how many; the workspace's computers are kept.
463
+ None of the three is offered to a read-only session (`MANDALA_READ_ONLY`).
419
464
 
420
465
  ### Current account quota
421
466
 
@@ -468,6 +513,14 @@ anything and keep the hint. When a stretch of work is over, `suspend_computer` (
468
513
  suspend catches the ones a model forgets, but only after 30 minutes untouched.
469
514
  `get_usage` is what says what any of it cost.
470
515
 
516
+ **Some defaults here differ from the SDKs', on purpose.** `screenshot`'s
517
+ `fresh` defaults to true (the SDKs default to false), because after a click a
518
+ cached frame can predate the action and a model reading it clicks again. Its
519
+ `width` outside 64-3840 is refused rather than clamped as the API would.
520
+ `wait_for_computer` polls every 2 seconds (an SDK launch polls every 3), and
521
+ refuses a suspended computer and names `start_computer`, where the SDKs'
522
+ `waitForGuest` resumes it.
523
+
471
524
  **A screenshot need not be the whole screen.** `screenshot` takes `region` (a
472
525
  crop, in the screen pixels `click` takes, before any scaling), `scale` (0 to 1),
473
526
  `format` (`png` or `jpeg`) and `quality` (JPEG, 1-100). A cropped or scaled
@@ -580,7 +633,11 @@ predates the feature is not something a caller can act on at all.
580
633
  **`running` does not mean ready.** A computer reports running when the
581
634
  hypervisor has started the VM; the desktop inside comes up seconds later.
582
635
  `wait_for_computer(until="guest")` waits for the software to answer, which is
583
- what `exec`, files and a painted screen actually need.
636
+ what `exec`, files and a painted screen actually need. On a Linux computer it
637
+ also waits for the desktop session to be logged in, so an `exec` with
638
+ `desktop: true` sent next is not refused. That is more than the SDKs'
639
+ `waitForGuest` / `wait_for_guest`, which return once the guest agent answers;
640
+ the value keeps the name `"guest"` because renaming it would break callers.
584
641
 
585
642
  **A resize can be refused with an offer rather than a no.** Growing a computer
586
643
  past what the host it is on can run comes back as a refusal that says another
@@ -1119,10 +1176,11 @@ address.
1119
1176
  API key, which the platform still accepts. The access token is good for this
1120
1177
  endpoint only. It is not an API key: sent to the API directly, the platform
1121
1178
  refuses it with `401` (`reason: "invalid"`). A person revokes it under
1122
- Settings → Connected apps.
1179
+ Settings → Credentials → Connected apps.
1123
1180
  - **A bearer is checked with the platform before it gets anything.** An
1124
1181
  initialize makes no session until the platform accepts its token — a `2xx`
1125
- from `GET ssh-keys`, which every valid credential gets — so invented tokens
1182
+ from `GET whoami`, which every valid credential gets, a suspended account's
1183
+ included — so invented tokens
1126
1184
  cannot fill the session pool. A `401` is the challenge above; any other
1127
1185
  answer is `503` and nothing is remembered. Refused initializes are budgeted
1128
1186
  per source address, 20 a minute. Past that, a token already accepted still
@@ -1132,6 +1190,105 @@ address.
1132
1190
  carrying a request is checked again before it is dispatched, with an
1133
1191
  acceptance cached for 60 s under the token's digest, so an expired or
1134
1192
  revoked token is a clean `401` before any stream opens.
1193
+ - **One bearer holds at most 16 sessions**, and a suspended account one in
1194
+ all (below), so no single token can fill the pool. At that
1195
+ limit, an initialize closes the bearer's least recently used idle session to
1196
+ make room, once its own session exists (a refused initialize closes
1197
+ nothing), and a client whose old session is gone initializes again; if none
1198
+ is idle, the answer is `429` with `Retry-After`. One race differs: if the
1199
+ idle session it planned to close is put to work before the new session is
1200
+ made, the new one is dropped instead and the initialize is answered `404`
1201
+ (`Session not found`, no `Retry-After`), which an SDK client reports as a
1202
+ failed connect. Other bearers' sessions are never touched, except a
1203
+ suspended account's (below). A
1204
+ `429` names what the bearer holds, so one still over its limit is told to
1205
+ wait for its busy sessions rather than to close one. `runHttp` takes the 16
1206
+ as `maxSessionsPerBearer`, and the CLI reads it from
1207
+ `MANDALA_MCP_MAX_SESSIONS_PER_TOKEN` (a whole number from 1 to 256, the
1208
+ whole pool; anything else is refused at startup; a token is also held to
1209
+ its account's ceiling, below). A self-hosted `--http` server applies it
1210
+ too, but checks no bearer with the platform, so there any string is its own
1211
+ bucket: the per-bearer cap spreads one key's clients, and what bounds an
1212
+ untrusted caller is the process-wide cap (256), and the default loopback
1213
+ bind until you expose the port.
1214
+ - **One account holds at most 64 sessions, a quarter of the pool, across
1215
+ all its tokens.** Sessions are counted by the account id the platform's `whoami`
1216
+ names for each token, so an account with many keys or grants counts once;
1217
+ a token whose `whoami` names no account counts as an account of its own.
1218
+ Initializes in flight are counted too, so a burst across one account's keys
1219
+ cannot pass the ceiling together. At the ceiling, an initialize makes room
1220
+ by closing idle sessions of two kinds only: the account's sessions whose
1221
+ token the platform has already refused on a request made there, first,
1222
+ and then the requesting token's own least recently used,
1223
+ as at its own cap and even while it is under that cap. When that is not
1224
+ enough the answer is `429` with `Retry-After`, saying it is the account's
1225
+ limit and naming only that number. A live session of another token is
1226
+ never closed for the ceiling, nor any session of another account, and the
1227
+ race above applies here too (`404`). A refused session counts until it is
1228
+ closed, so revoking keys never lets an account past its ceiling; a session
1229
+ whose token was revoked but never sent a request again is not known to be
1230
+ refused, and counts until it idles out. So does the session a token
1231
+ leaves behind when the client refreshes it (see below): it is neither the
1232
+ new token's own nor refused, so an account that holds more than half its
1233
+ ceiling when its clients refresh can be answered `429` for up to the
1234
+ session idle timeout (30 minutes), longer than its `Retry-After` suggests,
1235
+ unless those clients close their old sessions. A revoked token's session
1236
+ idles out within about the acceptance window (60 s) plus that timeout even
1237
+ while its holder keeps sending notifications, holding the stream, or
1238
+ sending `HEAD /mcp` or a `DELETE /mcp` the server refuses: traffic that
1239
+ carries no request is never checked with the platform, so it keeps a
1240
+ session alive only while the token's acceptance is still cached. A full pool (256) is answered `503` with
1241
+ `Retry-After`, whoever holds it, and nothing is closed to make room in it.
1242
+ The last 64 of the 256 go only to accounts holding less than their share,
1243
+ where a share is the other 192 divided by the accounts holding sessions,
1244
+ the asking one included (initializes in flight count as held): an account
1245
+ at its share once only those 64 are free is answered `503` with
1246
+ `Retry-After` too, saying so, and nothing is closed for it. A lone account
1247
+ therefore stops at 192 even with its ceiling raised, and each newcomer
1248
+ still gets in.
1249
+ `runHttp` takes the ceiling as `maxSessionsPerAccount`, and the CLI reads
1250
+ it from `MANDALA_MCP_MAX_SESSIONS_PER_ACCOUNT` (a whole number from 1 to
1251
+ 256; anything else is refused at startup). A self-hosted server verifies
1252
+ no account, so there each token is an account of its own, held to the
1253
+ ceiling as well as to its own cap; to let one token hold more than 64
1254
+ sessions, raise both.
1255
+ - **One workspace holds at most 16 sessions, a quarter of its account's
1256
+ ceiling, across all its tokens.** A workspace-scoped key is counted by the
1257
+ workspace the platform's `whoami` names for it as well as by its account;
1258
+ a key of the account as a whole (`workspace: null`) is held by no
1259
+ workspace limit. Workspace-scoped keys share their operator's account
1260
+ ceiling above the sub-ceiling: the operator's own sessions and all its end
1261
+ customers' together stay under 64, so a workspace with room is still
1262
+ answered the account's `429` when the account is full. The same workspace
1263
+ id under two accounts is two workspaces. Room is made, and in-flight
1264
+ initializes are counted, as for the account ceiling; past it the answer is
1265
+ `429` with `Retry-After`, saying it is the workspace's limit and naming
1266
+ only that number. `runHttp` takes it as `maxSessionsPerWorkspace` (default
1267
+ a quarter of the account ceiling, whatever that is set to; above it, it is
1268
+ the account ceiling), and the CLI reads it from
1269
+ `MANDALA_MCP_MAX_SESSIONS_PER_WORKSPACE` (a whole number from 1 to 256;
1270
+ anything else is refused at startup). Hosted only: a self-hosted server
1271
+ verifies no workspace.
1272
+ - **A suspended account holds one session in all, across all its tokens**
1273
+ (enough to ask `whoami`, the one call the platform still answers for it).
1274
+ Whether an account is suspended is the platform's latest answer about it
1275
+ from any of its tokens: each check is numbered as it starts, and a slower
1276
+ check that started earlier never overrides a newer one, in either
1277
+ direction. So a newer check that finds the account active lifts the hold at
1278
+ once for every token, and one that finds it suspended holds every token,
1279
+ including one whose own acceptance is still cached. Held, an initialize
1280
+ from any of the account's tokens (a refreshed token, say) closes the
1281
+ account's idle sessions to take the one slot: first those whose token the
1282
+ platform has refused, then the least recently used. A session serving a
1283
+ request is never closed; while the one the account holds is busy, an
1284
+ initialize is answered `429` with `Retry-After`, saying the account is
1285
+ suspended. An account suspended after it opened sessions keeps only one:
1286
+ its next initialize, or the next request, notification or event stream on
1287
+ any of its sessions, closes its other idle sessions, keeping the one being
1288
+ served, and a busy one goes at a later one of these once it is idle.
1289
+ Notifications and streams, which ask the platform nothing, do this only
1290
+ while their token's acceptance is cached (60 s). A self-hosted server asks
1291
+ the platform nothing, so nothing is suspended there.
1135
1292
  - **A token the platform refuses during a call comes back as that same
1136
1293
  `401`**, not as a tool error, so the client refreshes or authorizes again —
1137
1294
  the answer is held until its first byte for this. The one case that cannot
@@ -1144,7 +1301,12 @@ address.
1144
1301
  `404 Unknown session`, the MCP spec's signal to initialize again. An access
1145
1302
  token says nothing this server can check about which grant it came from, so
1146
1303
  rebinding would let any valid credential that learned a session id take over
1147
- its bound computer, buffered events and retained results.
1304
+ its bound computer, buffered events and retained results. The old session
1305
+ is not closed by the refresh: it still counts against the token's account
1306
+ ceiling (above) until the client closes it with `DELETE /mcp` under the old
1307
+ token (only the token's digest is compared, so an expired one still works)
1308
+ or it idles out after 30 minutes. On a suspended account the new token's
1309
+ initialize closes it instead, while it is idle (above).
1148
1310
  - `X-Mandala-MCP-Service` carries `MANDALA_MCP_SERVICE_SECRET` on every
1149
1311
  platform request, only ever to `MANDALA_BASE_URL`, so a token cannot be
1150
1312
  replayed at the API directly by the app it was issued to. A client's own
@@ -1166,9 +1328,14 @@ address.
1166
1328
  | `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. |
1167
1329
  | `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. |
1168
1330
  | `MANDALA_MCP_SERVICE_SECRET` | `--http` only. Sent as `X-Mandala-MCP-Service` on every platform request, to `MANDALA_BASE_URL` only. Never logged. |
1169
-
1170
- 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
1171
- the environment: `--http`, `--port`, `--host`, `--base-url`, `--computer`,
1331
+ | `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 is held to one session in all, across its tokens, whatever this says. A token is also held to its account's limit, below. |
1332
+ | `MANDALA_MCP_MAX_SESSIONS_PER_ACCOUNT` | `--http` only. How many live sessions one account may hold across all its tokens. Default 64, a quarter of 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. A suspended account is held to one session whatever this says. Whatever this says, the last quarter of the pool goes only to accounts holding less than their share of it. |
1333
+ | `MANDALA_MCP_MAX_SESSIONS_PER_WORKSPACE` | `--http`, hosted only. How many live sessions one workspace may hold across all its tokens. Default a quarter of the per-account limit (16 of 64); a whole number from 1 to 256, and anything else is refused at startup; above the per-account limit it is that limit. Counted by the workspace the platform's `whoami` names for a workspace-scoped token; such tokens also share their account's limit, above, with the account's own tokens and its other workspaces. |
1334
+
1335
+ Every one of these but the model key, the two tool filters, the two hosted
1336
+ settings (`MANDALA_MCP_RESOURCE_METADATA_URL` and `MANDALA_MCP_SERVICE_SECRET`)
1337
+ and the three session limits has a flag as well, and a flag overrides the
1338
+ environment: `--http`, `--port`, `--host`, `--base-url`, `--computer`,
1172
1339
  `--allowed-hosts`, `--allowed-origins`, `--no-lifecycle`, local `--profile`, plus `--help` and
1173
1340
  `--version`. `--key` exists for a caller launching several servers under
1174
1341
  different keys, and warns when used, because an argument vector is readable by
@@ -1234,7 +1401,13 @@ default 20. This is computer control, not hosted general-purpose inference or a
1234
1401
  chat UI; it adds no key storage or model billing service.
1235
1402
 
1236
1403
  This tool deliberately sends `stream:false` and uses the JSON response so the
1237
- result preserves the underlying `agent.stop`, step count and token usage.
1404
+ result preserves the underlying `agent.stop`, step count and token usage: the
1405
+ streamed form of this route carries no usage and no `agent` block. The price is
1406
+ time. **On the hosted API a request that does not stream is cut at the edge after
1407
+ about 120 seconds** (a bodiless `524`), which stops the run where it was and loses
1408
+ its result, usage and steps; the default 20 steps can take longer than that. Use
1409
+ `run_agent`, which streams, for anything that may. The tool answers a `524` by
1410
+ saying so rather than as a generic gateway failure.
1238
1411
  Only explicit `end_turn` consistent with the completion's finish reason can
1239
1412
  report success. Limits, refusal, missing or conflicting terminal fields remain
1240
1413
  errors with valid partial results. Nested failures preserve the agent computer,
@@ -1247,9 +1420,21 @@ Neither agent tool starts the computer or switches endpoints after a failure.
1247
1420
  The caller supplies the model key through `MANDALA_MODEL_KEY` on stdio or their
1248
1421
  own `X-Model-Key` header on HTTP. The account Authorization remains separate;
1249
1422
  HTTP never falls back to the operator's model key. Waiting heartbeats count
1250
- notifications, not completed actions. Long-running clients need a
1251
- `progressToken` plus `resetTimeoutOnProgress`; otherwise choose a smaller
1252
- `max_steps`. That bound is neither a time cap nor a spend cap.
1423
+ notifications, not completed actions. A `progressToken` plus
1424
+ `resetTimeoutOnProgress` keeps the MCP client waiting, but not the hosted edge
1425
+ above; `max_steps` is neither a time cap nor a spend cap.
1426
+
1427
+ On both agent tools a failure status is read for what it is on these routes
1428
+ rather than for what it means elsewhere. The platform's own re-check of the
1429
+ caller is a `401` or `403` carrying `reason: "revoked"`, and only that gets
1430
+ account advice. A `402`, `504` or `529` is the model provider's own status for
1431
+ the configured model key (its billing, timeout or overloaded error), so the
1432
+ advice is to check the model-provider account, not the Mandala plan; a `403`
1433
+ without `revoked` may be the provider's permission error for that key. The
1434
+ exception is a refusal `run_agent_chat` is given before any run started — a flat
1435
+ `{error: string}` body, from the role gate, a bad body, a suspension or the
1436
+ meter: nothing ran, and it keeps the usual role and plan advice every other tool
1437
+ gets.
1253
1438
 
1254
1439
  ## Development
1255
1440
 
@@ -1324,8 +1509,26 @@ check:surface — the mirror matches the platform (N routes, N parameters, from
1324
1509
 
1325
1510
  ## See also
1326
1511
 
1327
- - [python-sdk](https://github.com/mandalacomputer/python-sdk) — the Python SDK,
1328
- for writing code against the same API rather than driving it from a model.
1512
+ - [typescript-sdk](https://github.com/mandalacomputer/typescript-sdk) — the
1513
+ TypeScript SDK and the `mandala` CLI, for writing code against the same API
1514
+ rather than driving it from a model.
1515
+ - [python-sdk](https://github.com/mandalacomputer/python-sdk) — the Python SDK
1516
+ and the `mandala-py` CLI, for the same thing from Python.
1517
+
1518
+ ### The same operation under each client's name
1519
+
1520
+ The clients follow their own language's naming, so one operation can go by
1521
+ several names. Where a client has no way to do it, the cell says so with —.
1522
+
1523
+ | Operation | TypeScript SDK | Python SDK | MCP tool | `mandala` CLI | `mandala-py` CLI |
1524
+ |---|---|---|---|---|---|
1525
+ | A command in the background | `execBackground()`, then `execPoll(pid)` and `execKill(pid)` | `start_exec()`, then `background_command(pid).poll()` and `.kill()` | `exec` with `background: true`, then `exec_poll` and `exec_kill` | `computers exec --background`, then `computers exec-poll` and `computers exec-kill` | — |
1526
+ | Delete a computer with its snapshots | `delete({ deleteSnapshots: true, expect })` | `delete(delete_snapshots=True, expect=…)`; `purge_snapshots` is the deprecated name | `delete_computer` with `delete_snapshots` and `expect` | `computers delete --delete-snapshots --expect` | — |
1527
+ | Snapshot holdings (count, bytes, fingerprint) | `holdings()`, or its alias `snapshotHoldings()` | `snapshot_holdings()` | `snapshot_holdings` | `snapshots holdings` | — |
1528
+ | The usage window | `usage.read({ from, to })` | `usage.read(since=…, until=…)`, or `from_=` and `to=` | `get_usage` with `from` and `to` | `usage --from … --to …` | — |
1529
+ | Change a computer (`PATCH`) | `update({ … })` | `rename()`, `resize()`, `set_idle_suspend()`, `set_browser_proxy()`, `set_egress_proxy()` | `update_computer` | `computers rename`, `resize`, `idle-suspend`, `browser-proxy set`/`clear`, `egress-proxy set`/`clear` | `browser-proxy set`/`clear`, `egress-proxy set`/`clear` |
1530
+ | Wait until the computer is ready | `waitForGuest()`, `waitForDesktop()` | `wait_for_guest()`, `wait_for_desktop()` | `wait_for_computer` with `until: "guest"`, which on Linux also waits for the desktop session | `computers wait --until guest` or `--until desktop` | — |
1531
+ | The windows after an input action | `{ context: true }` on an input method, e.g. `click(x, y, [], { context: true })` | `context=True` on an input method, e.g. `click(x, y, context=True)` | `context: true` on an input tool | — | — |
1329
1532
 
1330
1533
  ## Security
1331
1534
 
package/dist/api.d.ts CHANGED
@@ -173,6 +173,12 @@ export type ApiOptions = {
173
173
  */
174
174
  onBearerRefused?: () => void;
175
175
  };
176
+ /**
177
+ * The `User-Agent` every request carries: this server and its version, and
178
+ * the Node it runs on, so the platform can tell which client and release sent
179
+ * a request.
180
+ */
181
+ export declare const USER_AGENT: string;
176
182
  export declare class Api {
177
183
  #private;
178
184
  readonly baseUrl: string;
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":"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"}
package/dist/api.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { Agent, Headers as UndiciHeaders, fetch as undiciFetch } from 'undici';
3
- import { CancelledError, ConnectivityError, ConnectivityInterruptedError, createOnlyRefusal, errorForStatus, isCreateOnlyUpload, MandalaError, platformSaid, RangeNotSatisfiableError, RedirectError, } from './errors.js';
3
+ import { CancelledError, ConnectivityError, ConnectivityInterruptedError, createOnlyRefusal, errorForStatus, isCreateOnlyUpload, MandalaError, messageFromBody, RangeNotSatisfiableError, RedirectError, } from './errors.js';
4
4
  import * as P from './paths.js';
5
5
  import { MalformedSecretAnswerError, sanitizeSecretError, secretRouteOf, } from './secret-errors.js';
6
6
  import { DOCUMENTED_REASONS, secretListOf, secretOf } from './secret-store.js';
7
+ import { SERVER_VERSION } from './version.js';
7
8
  export { isSecretStoreRoute } from './secret-errors.js';
8
9
  export const DEFAULT_BASE_URL = 'https://app.mandala.computer/api/v1';
9
10
  /** Anthropic's own key, forwarded for the one route that runs a model. */
@@ -150,6 +151,12 @@ const NATIVE_FETCH = globalThis.fetch;
150
151
  export const platformFetch = () => globalThis.fetch === NATIVE_FETCH
151
152
  ? undiciFetch
152
153
  : globalThis.fetch;
154
+ /**
155
+ * The `User-Agent` every request carries: this server and its version, and
156
+ * the Node it runs on, so the platform can tell which client and release sent
157
+ * a request.
158
+ */
159
+ export const USER_AGENT = `mandala-computer-mcp/${SERVER_VERSION} node/${process.versions.node}`;
153
160
  export class Api {
154
161
  baseUrl;
155
162
  /** The same thing parsed, so a path is joined onto the path and nothing else. */
@@ -161,7 +168,7 @@ export class Api {
161
168
  #options;
162
169
  constructor(apiKey, baseUrl = DEFAULT_BASE_URL, signal, options = {}) {
163
170
  if (!apiKey) {
164
- throw new MandalaError('No API key. Set MANDALA_API_KEY (create one at Settings → API keys), ' +
171
+ throw new MandalaError('No API key. Set MANDALA_API_KEY (create one at Settings → Credentials → API keys), ' +
165
172
  'or send it as a bearer token when running over HTTP.');
166
173
  }
167
174
  // Validated here rather than at the first request. An unusable base URL is
@@ -204,6 +211,7 @@ export class Api {
204
211
  this.#headers = {
205
212
  Authorization: `Bearer ${apiKey}`,
206
213
  Accept: 'application/json',
214
+ 'User-Agent': USER_AGENT,
207
215
  };
208
216
  }
209
217
  /**
@@ -480,7 +488,9 @@ export class Api {
480
488
  if (truncated)
481
489
  throw new SyntaxError('truncated response body');
482
490
  body = JSON.parse(text);
483
- message = platformSaid(body) ?? text.slice(0, 500);
491
+ // An RFC 9457 `detail`/`title` counts for the message, never as the
492
+ // platform naming the failure: see messageFromBody.
493
+ message = messageFromBody(body) ?? text.slice(0, 500);
484
494
  }
485
495
  catch {
486
496
  message = text.slice(0, 500);