mandala-computer-mcp 0.5.0 → 0.7.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 +277 -20
- package/dist/api.d.ts +73 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +271 -22
- package/dist/api.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +17 -0
- package/dist/cli.js.map +1 -1
- package/dist/errors.d.ts +183 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +377 -10
- package/dist/errors.js.map +1 -1
- package/dist/format.d.ts +90 -0
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +102 -2
- package/dist/format.js.map +1 -1
- package/dist/http.d.ts +42 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +392 -6
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +74 -3
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +89 -10
- package/dist/paths.js.map +1 -1
- package/dist/secret-errors.d.ts +59 -0
- package/dist/secret-errors.d.ts.map +1 -0
- package/dist/secret-errors.js +199 -0
- package/dist/secret-errors.js.map +1 -0
- package/dist/secret-store.d.ts +115 -0
- package/dist/secret-store.d.ts.map +1 -0
- package/dist/secret-store.js +105 -0
- package/dist/secret-store.js.map +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -1
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +6 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +1 -1
- package/dist/session.js.map +1 -1
- package/dist/tool-filters.d.ts +4 -4
- package/dist/tool-filters.d.ts.map +1 -1
- package/dist/tool-filters.js +22 -2
- package/dist/tool-filters.js.map +1 -1
- package/dist/tools/account.d.ts +14 -0
- package/dist/tools/account.d.ts.map +1 -1
- package/dist/tools/account.js +118 -0
- package/dist/tools/account.js.map +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/agent.js +7 -2
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/computers.d.ts.map +1 -1
- package/dist/tools/computers.js +277 -57
- package/dist/tools/computers.js.map +1 -1
- package/dist/tools/guest.d.ts.map +1 -1
- package/dist/tools/guest.js +140 -22
- package/dist/tools/guest.js.map +1 -1
- package/dist/tools/input.d.ts +11 -0
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +277 -20
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/operations.d.ts +44 -0
- package/dist/tools/operations.d.ts.map +1 -0
- package/dist/tools/operations.js +232 -0
- package/dist/tools/operations.js.map +1 -0
- package/dist/tools/secrets.d.ts +42 -13
- package/dist/tools/secrets.d.ts.map +1 -1
- package/dist/tools/secrets.js +306 -15
- package/dist/tools/secrets.js.map +1 -1
- package/dist/tools/snapshots.d.ts.map +1 -1
- package/dist/tools/snapshots.js +44 -15
- package/dist/tools/snapshots.js.map +1 -1
- package/dist/tools/ssh.d.ts.map +1 -1
- package/dist/tools/ssh.js +19 -2
- package/dist/tools/ssh.js.map +1 -1
- package/dist/tools/templates.js +1 -1
- package/dist/tools/templates.js.map +1 -1
- package/dist/tools/webhooks.js +1 -1
- package/dist/tools/webhooks.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -145,16 +145,29 @@ 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`, `
|
|
151
|
-
`scroll`, `drag`, `move_mouse`, `mouse_button`, `cursor_position`,
|
|
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`,
|
|
155
157
|
`list_windows`, `window_action`, `read_clipboard`, `write_clipboard`,
|
|
156
158
|
`read_file`, `write_file`, `list_directory`
|
|
157
159
|
|
|
160
|
+
`write_file` replaces a file already at the path. With `overwrite: false` it
|
|
161
|
+
creates the file only if nothing is there, and a path that is taken is refused
|
|
162
|
+
without that attempt writing anything (Linux computers only). Incomplete
|
|
163
|
+
contents are never published at the path, but a failure while publishing or
|
|
164
|
+
answering can leave the complete file there, so after any error read the path
|
|
165
|
+
before retrying or overwriting. `read_file` and `write_file` take `no_wake: true`
|
|
166
|
+
to refuse (409) rather than resume a computer that is not running. For
|
|
167
|
+
embedders, the `Api` raises that refusal as `FileExistsError`, and a
|
|
168
|
+
create-only 409 whose reason could not be read as `CreateOnlyConflictError`,
|
|
169
|
+
which says nothing about the path; `isTransient` is false for both.
|
|
170
|
+
|
|
158
171
|
**Retained versions** — `retain_execution_output`, `get_result`, `read_result_output`,
|
|
159
172
|
`delete_result`, `publish_artifact`, `get_artifact`, `read_artifact`, `delete_artifact`
|
|
160
173
|
|
|
@@ -174,6 +187,10 @@ Parameter and response-mode support remains a separate contract.
|
|
|
174
187
|
|
|
175
188
|
**Account quota** — `get_account`
|
|
176
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
|
+
|
|
177
194
|
**Spending** — `get_usage`
|
|
178
195
|
|
|
179
196
|
**Being told somewhere else** — `list_webhooks`, `create_webhook`,
|
|
@@ -186,14 +203,56 @@ was issued to, not to the account, and are accepted by every computer with SSH
|
|
|
186
203
|
on, on every account where that person is an owner or member. A
|
|
187
204
|
workspace-scoped key can read keys but not add or remove them.
|
|
188
205
|
|
|
189
|
-
**
|
|
190
|
-
`
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
212
|
+
holds only the decoded documented fields (success) or the status, the `reason`
|
|
213
|
+
word and a sentence of its own (refusal). It never includes the platform's
|
|
214
|
+
response text, and the `Api` keeps none for these routes. The bindings carry
|
|
215
|
+
only secret ids, revisions and names. A computer receives a secret as an environment variable (`env`) or as a
|
|
216
|
+
file under `/run/mandala-secrets/user/files` (`file`). A set replaces the whole
|
|
217
|
+
binding list (`[]` removes every binding) and reaches the guest at the
|
|
218
|
+
computer's next start or restart; send the `version` a read answered to have it
|
|
219
|
+
refused with 409 if the list changed since. A replaced value also reaches a
|
|
220
|
+
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`
|
|
225
|
+
and `delete_secret` need the current `revision_id`, and a stale one is a 409.
|
|
226
|
+
`delete_secret` also needs `confirm: true`: a computer still bound to a deleted
|
|
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".
|
|
197
256
|
|
|
198
257
|
**Delegating** — `run_agent`, `run_agent_chat`, registered only when a model key is present:
|
|
199
258
|
`MANDALA_MODEL_KEY` on stdio, or the caller's own `X-Model-Key` header over HTTP.
|
|
@@ -218,10 +277,10 @@ with an error listing all valid tags.
|
|
|
218
277
|
|
|
219
278
|
| Tag | Tools |
|
|
220
279
|
| --- | --- |
|
|
221
|
-
| `account` | `get_account` |
|
|
280
|
+
| `account` | `get_account`, `whoami`, `list_api_keys`, `list_workspaces`, `get_workspace`, `list_workspace_members` |
|
|
222
281
|
| `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` |
|
|
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` |
|
|
225
284
|
| `guest` | `exec`, `exec_poll`, `exec_kill`, `open_url`, `list_windows`, `window_action`, `read_clipboard`, `write_clipboard` |
|
|
226
285
|
| `files` | `list_directory`, `read_file`, `write_file`, `wait_for_file_change` |
|
|
227
286
|
| `executions` | `get_execution`, `read_execution_output` |
|
|
@@ -233,7 +292,7 @@ with an error listing all valid tags.
|
|
|
233
292
|
| `usage` | `get_usage` |
|
|
234
293
|
| `webhooks` | All webhook tools listed above |
|
|
235
294
|
| `ssh` | All SSH tools listed above |
|
|
236
|
-
| `secrets` | `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` |
|
|
237
296
|
| `agent` | `run_agent`, `run_agent_chat` |
|
|
238
297
|
| `activities` | `list_activities`, `get_activity`, `get_activity_results` |
|
|
239
298
|
| `signals` | `read_signals` |
|
|
@@ -256,6 +315,108 @@ to both transports and the plugin forwards them. Embedders can pass
|
|
|
256
315
|
`readOnly: true` and `tags: ['input', 'guest']` in `ServerConfig`; the server
|
|
257
316
|
does not read the environment itself.
|
|
258
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`**, or `idempotency_outcome_unknown`: the key
|
|
354
|
+
is spent, and resending with it only answers `idempotency_outcome_unknown`.
|
|
355
|
+
The platform then keeps the call's operation `pending` for up to an hour,
|
|
356
|
+
whatever happened, while its host may be carrying it out. What to read
|
|
357
|
+
depends on the tool:
|
|
358
|
+
- `start_computer`, `stop_computer`, `suspend_computer`,
|
|
359
|
+
`delete_computer`, `move_computer` and `update_computer`: read
|
|
360
|
+
`get_computer` (and `get_operation` with the `operation_id` the error
|
|
361
|
+
named: `succeeded` means the step happened, `running` means wait on it,
|
|
362
|
+
and `pending` alone is no reason to wait). If the step did not take
|
|
363
|
+
effect, send the call again with a new key, or none.
|
|
364
|
+
- `restart_computer`, which `get_computer` cannot show: a computer reads
|
|
365
|
+
`running` before and after a reset. Read the operation instead
|
|
366
|
+
(`get_operation` with the `operation_id` the error named, or
|
|
367
|
+
`list_operations` with the key): `succeeded` means the restart happened,
|
|
368
|
+
`running` means wait on it. Anything else leaves the restart possibly
|
|
369
|
+
done, so ask the user before sending it again with a new key, or none; a
|
|
370
|
+
second restart resets the guest again.
|
|
371
|
+
- `create_computer`, `clone_computer`, `clone_snapshot` and
|
|
372
|
+
`restore_snapshot`, where a read of the computer made straight away can
|
|
373
|
+
show no effect for a build or restore that then lands, and a second one
|
|
374
|
+
is a second computer or a second overwrite: read the operation first —
|
|
375
|
+
`get_operation` with the `operation_id` the error named, or
|
|
376
|
+
`list_operations` with the key — and while it is `pending` or `running`,
|
|
377
|
+
wait (`wait_for_operation`) and do not resend with any key. Only once it
|
|
378
|
+
is final as `failed` (`error.code` `lost`, for one) or no longer found, AND
|
|
379
|
+
`get_computer` (`list_computers` after a create or a clone) shows the step
|
|
380
|
+
did not happen, send the call again with a new key, or none. If no
|
|
381
|
+
operation is found at all, read the computer again after a few minutes
|
|
382
|
+
before resending.
|
|
383
|
+
- **`idempotency_key_reused`**: a different call already used that key, and
|
|
384
|
+
nothing was done. Send this one with a new key, or none.
|
|
385
|
+
|
|
386
|
+
Keys last 24 hours.
|
|
387
|
+
|
|
388
|
+
### Who you are, and API keys
|
|
389
|
+
|
|
390
|
+
`whoami` takes no arguments and reads `GET /api/v1/whoami`: the person this
|
|
391
|
+
server's API key was issued to, the account and role it acts with (the role as
|
|
392
|
+
it is now), the workspace it is confined to, and the key itself — including
|
|
393
|
+
`manage_keys`, whether it may manage API keys. It needs no permission and any
|
|
394
|
+
role, and a suspended account can call it; the answer says so when the account
|
|
395
|
+
is suspended. Behind the hosted server's OAuth sign-in, the key is the Connected
|
|
396
|
+
app's (`prefix` `oauth`).
|
|
397
|
+
|
|
398
|
+
`list_api_keys` lists the key holder's API keys on the account, newest first —
|
|
399
|
+
never a raw key. Each carries `minted_by_key_id`: the key that minted it over
|
|
400
|
+
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
|
|
403
|
+
tool answers the platform's 403, whose sentence says exactly that. A Connected
|
|
404
|
+
app's key never has the permission.
|
|
405
|
+
|
|
406
|
+
**Minting and revoking keys are not offered here, on purpose.** A mint answers
|
|
407
|
+
the new key in full, once, and a model's context — the conversation, the
|
|
408
|
+
client's logs, whatever the transcript is shared with — is the worst place for a
|
|
409
|
+
long-lived credential and one nobody can take it back from. A revoke is
|
|
410
|
+
irreversible, and on a model's reading of a list it can cut off the person's CI,
|
|
411
|
+
another agent, or this very session. People do both from the dashboard or with
|
|
412
|
+
the `mandala api-keys` CLI.
|
|
413
|
+
|
|
414
|
+
`list_workspaces`, `get_workspace` and `list_workspace_members` read the
|
|
415
|
+
account's workspaces, which partition its computers. A key confined to a
|
|
416
|
+
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.
|
|
419
|
+
|
|
259
420
|
### Current account quota
|
|
260
421
|
|
|
261
422
|
`get_account` takes no arguments and reads `GET /api/v1/account` once with the
|
|
@@ -307,6 +468,12 @@ anything and keep the hint. When a stretch of work is over, `suspend_computer` (
|
|
|
307
468
|
suspend catches the ones a model forgets, but only after 30 minutes untouched.
|
|
308
469
|
`get_usage` is what says what any of it cost.
|
|
309
470
|
|
|
471
|
+
**A screenshot need not be the whole screen.** `screenshot` takes `region` (a
|
|
472
|
+
crop, in the screen pixels `click` takes, before any scaling), `scale` (0 to 1),
|
|
473
|
+
`format` (`png` or `jpeg`) and `quality` (JPEG, 1-100). A cropped or scaled
|
|
474
|
+
picture is in its own pixel space, so the tool says alongside it how to turn a
|
|
475
|
+
position in it back into screen coordinates.
|
|
476
|
+
|
|
310
477
|
**Ten clicks need not be ten screenshots.** Driving the desktop one tool at a
|
|
311
478
|
time — `screenshot`, `click`, `screenshot` — puts an image in the calling
|
|
312
479
|
model's context for every step. `run_agent` hands a task in plain language to
|
|
@@ -709,9 +876,15 @@ listing leaves it out — every ordinary caller is asking "what can I restore".
|
|
|
709
876
|
`list_snapshots(include_unfinished: true)` is the flag for when the question is
|
|
710
877
|
about storage instead.
|
|
711
878
|
|
|
712
|
-
**
|
|
713
|
-
|
|
714
|
-
|
|
879
|
+
**Read the `reason`, not the status.** Whether a refusal clears on its own is
|
|
880
|
+
the platform's `reason` word, and each tool's answer says what it means:
|
|
881
|
+
`contention` (something was in flight) and `starting` (the guest agent is still
|
|
882
|
+
inside its boot window) clear, so the same call works in a moment;
|
|
883
|
+
`unavailable`, `running`, `unsupported`, `exists` and `revoked` do not. A 409
|
|
884
|
+
does not mean "wait": `exec`, input and the clipboard on a stopped computer
|
|
885
|
+
answer 409 `unavailable`, and `start_computer` is what fixes that, while
|
|
886
|
+
`running` needs the computer stopped first. The platform's own error messages
|
|
887
|
+
come through unedited, because they are written to be acted on.
|
|
715
888
|
|
|
716
889
|
HTTP failures preserve their actual response status. An unsupported method on a
|
|
717
890
|
known path is `MethodNotAllowedError` (405), with the received `Allow` value when
|
|
@@ -719,6 +892,25 @@ available. A missing computer, snapshot, route or guest file remains
|
|
|
719
892
|
`NotFoundError` (404); other guest failures keep their own status and message.
|
|
720
893
|
Neither response causes an automatic retry or method switch.
|
|
721
894
|
|
|
895
|
+
`isTransient(err)` answers "is this worth sending again unchanged". A `503` is
|
|
896
|
+
transient only for a GET or HEAD. Any change answered `503` may or may not have
|
|
897
|
+
happened, because the platform answers a failure after the request was sent the
|
|
898
|
+
same way. So `isTransient` is false for it whatever `reason` it carries, the
|
|
899
|
+
tool's answer says to read the current state first, and `error.method` records
|
|
900
|
+
the method. A `reason` this version does not
|
|
901
|
+
know, such as a new word, is treated as no classification. `reasonKind` returns
|
|
902
|
+
`undefined` for it, and the status decides.
|
|
903
|
+
|
|
904
|
+
The secret store has typed calls on `api.secrets`: `list`, `create`, `get`,
|
|
905
|
+
`replace` and `delete`. Each answer is decoded strictly to the documented
|
|
906
|
+
fields, and none of them carries a value:
|
|
907
|
+
|
|
908
|
+
```ts
|
|
909
|
+
const { secrets } = await api.secrets.list();
|
|
910
|
+
const s = await api.secrets.create({ name: 'OPENAI_API_KEY', value: key });
|
|
911
|
+
await api.secrets.replace(s.id, { value: next, revisionId: s.revision_id });
|
|
912
|
+
```
|
|
913
|
+
|
|
722
914
|
Embedders can inspect optional diagnostics on every `APIError`:
|
|
723
915
|
|
|
724
916
|
```ts
|
|
@@ -746,7 +938,7 @@ and nested chat accounting. `allow` and `wwwAuthenticate` come only from receive
|
|
|
746
938
|
headers. HEAD failures can carry these fields with no body. Older servers,
|
|
747
939
|
intermediaries and connection failures may supply none of them. Existing
|
|
748
940
|
constructor arguments retain their meanings; an optional trailing
|
|
749
|
-
`APIErrorMetadata` object adds these three fields
|
|
941
|
+
`APIErrorMetadata` object adds these three fields, and `method`.
|
|
750
942
|
|
|
751
943
|
MCP error results include supplied `reason`, `request_id`, `allow`,
|
|
752
944
|
`www_authenticate` and `retry_after_ms` as labelled JSON metadata. Diagnostic
|
|
@@ -895,6 +1087,69 @@ not list it, and every request is refused with a 403. Set
|
|
|
895
1087
|
is in force, so a `403` on a deployment that worked before has a line above it
|
|
896
1088
|
naming the fix.
|
|
897
1089
|
|
|
1090
|
+
### Hosted, with OAuth
|
|
1091
|
+
|
|
1092
|
+
The endpoint at `https://app.mandala.computer/mcp` is this server run with
|
|
1093
|
+
`--http` behind the platform's own proxy, with the platform as its OAuth 2.1
|
|
1094
|
+
authorization server. Nobody pastes a key:
|
|
1095
|
+
|
|
1096
|
+
```sh
|
|
1097
|
+
claude mcp add --transport http mandala https://app.mandala.computer/mcp
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
Two variables make an HTTP server behave that way, and without them nothing
|
|
1101
|
+
changes — self-hosters keep bringing API keys:
|
|
1102
|
+
|
|
1103
|
+
```sh
|
|
1104
|
+
MANDALA_MCP_RESOURCE_METADATA_URL=https://app.mandala.computer/.well-known/oauth-protected-resource/mcp \
|
|
1105
|
+
MANDALA_MCP_SERVICE_SECRET=… \
|
|
1106
|
+
MANDALA_ALLOWED_HOSTS=app.mandala.computer \
|
|
1107
|
+
npx mandala-computer-mcp --http --host 127.0.0.1 --port 3000
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
In this mode `X-Forwarded-For` is believed from a loopback peer only, so the
|
|
1111
|
+
proxy in front must be on the same machine and must set it to the real client
|
|
1112
|
+
address.
|
|
1113
|
+
|
|
1114
|
+
- **Every `/mcp` request needs a bearer**, initialize and `tools/list`
|
|
1115
|
+
included. One without is answered `401` with exactly
|
|
1116
|
+
`WWW-Authenticate: Bearer resource_metadata="<that URL>", scope="mcp:tools"`,
|
|
1117
|
+
which is how a client finds where to authorize. `/healthz` stays open.
|
|
1118
|
+
- **The bearer is passed through unchanged** — an `mcpat_…` access token, or an
|
|
1119
|
+
API key, which the platform still accepts. The access token is good for this
|
|
1120
|
+
endpoint only. It is not an API key: sent to the API directly, the platform
|
|
1121
|
+
refuses it with `401` (`reason: "invalid"`). A person revokes it under
|
|
1122
|
+
Settings → Connected apps.
|
|
1123
|
+
- **A bearer is checked with the platform before it gets anything.** An
|
|
1124
|
+
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
|
|
1126
|
+
cannot fill the session pool. A `401` is the challenge above; any other
|
|
1127
|
+
answer is `503` and nothing is remembered. Refused initializes are budgeted
|
|
1128
|
+
per source address, 20 a minute. Past that, a token already accepted still
|
|
1129
|
+
passes and a new one is still checked, but only one every 5 s per address;
|
|
1130
|
+
the rest get `429` with `Retry-After`. The budget comes back when its minute
|
|
1131
|
+
is up. A POST
|
|
1132
|
+
carrying a request is checked again before it is dispatched, with an
|
|
1133
|
+
acceptance cached for 60 s under the token's digest, so an expired or
|
|
1134
|
+
revoked token is a clean `401` before any stream opens.
|
|
1135
|
+
- **A token the platform refuses during a call comes back as that same
|
|
1136
|
+
`401`**, not as a tool error, so the client refreshes or authorizes again —
|
|
1137
|
+
the answer is held until its first byte for this. The one case that cannot
|
|
1138
|
+
work: a token that dies mid-call AFTER the stream has committed (the SDK's
|
|
1139
|
+
15 s keep-alive, a progress notification or a partial result). That call
|
|
1140
|
+
ends as a tool error, the session remembers the refusal, and the next
|
|
1141
|
+
request on that token gets the `401` without reaching the platform.
|
|
1142
|
+
- **A refreshed token starts a new session.** A session is bound to the digest
|
|
1143
|
+
of the bearer that opened it, and a different bearer on it is answered
|
|
1144
|
+
`404 Unknown session`, the MCP spec's signal to initialize again. An access
|
|
1145
|
+
token says nothing this server can check about which grant it came from, so
|
|
1146
|
+
rebinding would let any valid credential that learned a session id take over
|
|
1147
|
+
its bound computer, buffered events and retained results.
|
|
1148
|
+
- `X-Mandala-MCP-Service` carries `MANDALA_MCP_SERVICE_SECRET` on every
|
|
1149
|
+
platform request, only ever to `MANDALA_BASE_URL`, so a token cannot be
|
|
1150
|
+
replayed at the API directly by the app it was issued to. A client's own
|
|
1151
|
+
header of that name is never forwarded.
|
|
1152
|
+
|
|
898
1153
|
### Configuration
|
|
899
1154
|
|
|
900
1155
|
| Variable | Meaning |
|
|
@@ -909,8 +1164,10 @@ naming the fix.
|
|
|
909
1164
|
| `MANDALA_READ_ONLY` | Keep only tools annotated `readOnlyHint: true`; strict boolean parsing as described under Tool filters. |
|
|
910
1165
|
| `MANDALA_TAGS` | Comma-separated lowercase tool tags; see Tool filters for the inventory and intersection rules. |
|
|
911
1166
|
| `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
|
+
| `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
|
+
| `MANDALA_MCP_SERVICE_SECRET` | `--http` only. Sent as `X-Mandala-MCP-Service` on every platform request, to `MANDALA_BASE_URL` only. Never logged. |
|
|
912
1169
|
|
|
913
|
-
Every one of these but the model key
|
|
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
|
|
914
1171
|
the environment: `--http`, `--port`, `--host`, `--base-url`, `--computer`,
|
|
915
1172
|
`--allowed-hosts`, `--allowed-origins`, `--no-lifecycle`, local `--profile`, plus `--help` and
|
|
916
1173
|
`--version`. `--key` exists for a caller launching several servers under
|
package/dist/api.d.ts
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
1
1
|
import { Agent } from 'undici';
|
|
2
|
+
import { type SecretStore } from './secret-store.js';
|
|
3
|
+
export { isSecretStoreRoute } from './secret-errors.js';
|
|
2
4
|
export declare const DEFAULT_BASE_URL = "https://app.mandala.computer/api/v1";
|
|
3
5
|
/** Anthropic's own key, forwarded for the one route that runs a model. */
|
|
4
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>;
|
|
26
|
+
/**
|
|
27
|
+
* Proof, to the platform, that a request comes through the hosted MCP service
|
|
28
|
+
* (OPL-4982). The platform takes an OAuth access token only alongside it, so a
|
|
29
|
+
* token cannot be replayed at the API directly by the app it was issued to.
|
|
30
|
+
*
|
|
31
|
+
* Only this server's own value is ever sent. A header of this name arriving
|
|
32
|
+
* from anywhere else — a caller, or a per-call header set — is dropped in
|
|
33
|
+
* `#fetch`, so a client cannot supply the secret or learn anything by trying.
|
|
34
|
+
*/
|
|
35
|
+
export declare const SERVICE_HEADER = "X-Mandala-MCP-Service";
|
|
5
36
|
export type RequestOptions = {
|
|
6
37
|
query?: Record<string, string | number | boolean | undefined>;
|
|
7
38
|
body?: unknown;
|
|
8
39
|
/** Raw bytes as the request body, for the file upload. Mutually exclusive with `body`. */
|
|
9
40
|
raw?: Uint8Array;
|
|
10
|
-
/** Extra headers for this call only
|
|
41
|
+
/** Extra headers for this call only: the model key, and a lifecycle call's Idempotency-Key. */
|
|
11
42
|
headers?: Record<string, string>;
|
|
12
43
|
signal?: AbortSignal;
|
|
13
44
|
};
|
|
@@ -53,6 +84,12 @@ export type Bytes = {
|
|
|
53
84
|
* differently" and "there is no offset that will work on this file".
|
|
54
85
|
*/
|
|
55
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;
|
|
56
93
|
};
|
|
57
94
|
/**
|
|
58
95
|
* The longest foreground guest exec waits 600 seconds before it answers.
|
|
@@ -114,10 +151,32 @@ export type SSEEvent = {
|
|
|
114
151
|
* result goes into a model's context and from there into transcripts, and an
|
|
115
152
|
* API key is every computer on the account, forever.
|
|
116
153
|
*/
|
|
154
|
+
/**
|
|
155
|
+
* What an embedding transport adds to every request one client makes.
|
|
156
|
+
*
|
|
157
|
+
* Both halves exist for the hosted install (OPL-4982), where this server stands
|
|
158
|
+
* between an OAuth client and the platform: the platform takes an access token
|
|
159
|
+
* only from a caller that also proves it is this service, and a refused token
|
|
160
|
+
* has to reach the MCP client as an HTTP 401 rather than as a tool error.
|
|
161
|
+
*/
|
|
162
|
+
export type ApiOptions = {
|
|
163
|
+
/**
|
|
164
|
+
* Sent as {@link SERVICE_HEADER} with every request to the configured base
|
|
165
|
+
* URL, and nowhere else. Never logged and never put on an error, like the
|
|
166
|
+
* key itself.
|
|
167
|
+
*/
|
|
168
|
+
serviceSecret?: string;
|
|
169
|
+
/**
|
|
170
|
+
* Called when the platform refuses THE BEARER: a 401 carrying a `Bearer`
|
|
171
|
+
* challenge. A 401 without one is a model provider refusing a model key,
|
|
172
|
+
* which says nothing about this client's credential, and is not reported.
|
|
173
|
+
*/
|
|
174
|
+
onBearerRefused?: () => void;
|
|
175
|
+
};
|
|
117
176
|
export declare class Api {
|
|
118
177
|
#private;
|
|
119
178
|
readonly baseUrl: string;
|
|
120
|
-
constructor(apiKey: string, baseUrl?: string, signal?: AbortSignal);
|
|
179
|
+
constructor(apiKey: string, baseUrl?: string, signal?: AbortSignal, options?: ApiOptions);
|
|
121
180
|
/**
|
|
122
181
|
* This same client, with every request bound to one tool call's cancellation.
|
|
123
182
|
*
|
|
@@ -182,6 +241,18 @@ export declare class Api {
|
|
|
182
241
|
}>;
|
|
183
242
|
/** For the two routes whose body is not JSON: the screenshot and the download. */
|
|
184
243
|
bytes(method: string, path: string, opts?: RequestOptions, maxBytes?: number | ((contentType: string) => number)): Promise<Bytes>;
|
|
244
|
+
/**
|
|
245
|
+
* The account's secret store, `GET|POST secrets` and `GET|PUT|DELETE
|
|
246
|
+
* secrets/:id`, as five typed calls.
|
|
247
|
+
*
|
|
248
|
+
* Typed, and decoded strictly, because a value goes IN here and the caller
|
|
249
|
+
* must be able to rely on nothing coming back: every answer is reduced to the
|
|
250
|
+
* documented fields before it is returned, so no answer can hand a value on.
|
|
251
|
+
* A malformed answer is a {@link MandalaError}; for a change it says the
|
|
252
|
+
* change may have been made. Bound to this client, so
|
|
253
|
+
* `api.with(signal).secrets` carries the signal.
|
|
254
|
+
*/
|
|
255
|
+
get secrets(): SecretStore;
|
|
185
256
|
/**
|
|
186
257
|
* The agent route, which answers with a stream of steps rather than a result.
|
|
187
258
|
*
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|