Use the computer
Call devices first. Pass the selected UUID as deviceId on every hosted operation call. For MCP, discover current inputs with tools/list.
Read or change a file
Use files_read with path, files_write with path and content, or files_edit with path, oldText, and newText. These tools accept UTF-8 text up to 1 MB. Paths must be absolute or start with ~/.
files_write replaces the file. files_edit replaces text that must match exactly once. Read the file before editing and check the destination before overwriting it.
Run a command
Call shell_exec with command and, when needed, cwd and timeoutMs. The timeout is at most 55,000 milliseconds. It runs as the computer user who installed the helper. Shell access includes files outside any file-tool folder limits.
Check stdout, stderr, and code. A nonzero exit code means the command finished with an error.
If the result is outcome_unknown, the command may have run. Inspect operations or the resulting files. Never automatically repeat it. Reusing a request ID rejects duplicate execution; it does not replay the earlier result.
Transfer a file
For files too large for text tools, use a transfer. The agent host must save downloaded bytes itself; hosted MCP cannot write to its filesystem.
For large files:
- Download: call
bulk_export, pollbulk_statusuntil ready, then fetchdownloadUrlwith your own download tool. - Upload: follow the part-upload steps below, then poll
bulk_statusuntilcompletedorfailed. - Stop or clean up: call
bulk_cancel. It deletes temporary cloud data, not a file already delivered to the computer.
Keep the same agent token for the whole job. Download links are private and short-lived. Jobs last up to one hour and support files up to 10 GiB. Temporary cloud storage is not a backup.
For small binary files, call transfer_start, send zero-based chunks with transfer_chunk using transferId, index, and base64, then call transfer_finish. Each chunk is at most 192 KB before base64 encoding. To download, call transfer_pull and set offset to the returned nextOffset until done is true.
Upload parts for a large file
- Call
bulk_uploadwithdeviceIdand the exact bytesize. - Use the returned
partSize. Add a one-basedpartNumberquery parameter touploadUrl, preserving its existing query parameters. PUT each binary part withContent-LengthandX-Content-SHA256, the lowercase hex SHA-256 of that part. For an empty file, send one empty part. - POST to
completeUrlafter all parts succeed. - Call
bulk_importwithdeviceId,transferId, and the destinationpath.
For REST bulk calls, supply a UUID requestId. Export and upload use it as the transfer ID, so inspect that ID with bulk.status if the start response is lost. Never automatically start another job for an unknown outcome.
Use the browser
The person must approve browser access separately. Approval covers every open tab in the selected Chrome or Helium profile, including signed-in pages.
Call browser_call with name: "tools/list", then use a returned tool name and its inputs. The helper starts Chrome DevTools MCP when needed. For a separate browser with copied logins, follow browser sessions. The person imports locally; call profiles/list and pass the chosen profile UUID on each browser call.
If browser_installing is returned, no browser action ran. Wait a few seconds and retry the call while the helper installs Chromium in the background. Do not automatically retry outcome_unknown. Live attach to daily Chrome/Helium is removed; both legacy browser values select the same managed profile.
Use local MCP servers
Call mcp.servers to list servers the person already configured for Claude, Claude Code, Codex, Cursor or Windsurf on that computer. It returns ids, names and sources, never commands or secrets. Call mcp.tools with { server } to see a server's tools, then mcp.call with { server, tool, arguments }. Stdio servers start for the call and stop afterwards. Calls run with the server's own permissions; the Terminal sandbox does not apply.
Read a Terminal result
Terminal commands run in a sandbox by default. Writes outside approved folders and the temp folder, reads of credentials or browser data, and connections to local network addresses fail. The result's sandbox.blocked is true and stderr contains a <sandbox_violations> block saying what was refused. Tell the person; do not try to work around it. update_helper_for_terminal_sandbox means the computer's helper is too old to sandbox: ask the person to rerun setup, or to allow full Terminal access. Temporary connections never include Terminal.
Handle an unavailable computer
- Offline: Choose queue or discard based on the task. Use the queue flow below for authorized work that should run when the computer returns.
- Expired or revoked: Request a new connection and local approval.
The operations tool returns recent operation metadata. The helper also keeps a bounded log at ~/.ezremote/activity.jsonl. Revocation and cancellation block new work but cannot guarantee stopping an operation already sent.
Queue work explicitly
A normal operation reports device_offline without scheduling work. Choose queue
or discard based on the task, including authorized proactive work. Queueing is an
agent decision; it does not need another human confirmation for already authorized
work. Expired and revoked access still blocks work.
- Call
queue_operationwith{ deviceId, operation, args, requestId }. Every operation name fromtools/listcan queue, using its dotted API name such asfiles.readorbulk.export. Generate and save a fresh UUID first. Do not reuse the ID of the earlier offline call. - The default deadline is 30 minutes and the maximum is 12 hours. Set
expiresInSeconds(60 to 43200), orwaitUntilas a duration such as"3 hours", a clock time such as"3:30pm"with an IANAtimeZone, or an ISO time. The job runs as soon as the computer is online and is discarded at the deadline. The reply'swaitUntilstates the interpreted deadline. Computer access and direct-token expiry can shorten it. Queueing never extends computer access. - The immediate reply includes
status: "queued",computerState,jobIdandexpiresAt. This confirms acceptance. Inform the person that the work is waiting. - Call
job_waitonce with{ jobId }. The service waits and returns the final result. Usereply.resulton completion and inspect shell exit codes. Stop oncompleted,failed,cancelled,expiredoroutcome_unknown. Never repeatedly calljob_statusto keep a model busy while waiting. - Use
job_cancelto stop pending work.cancelled: trueprevents dispatch.cancelled: falsemeans it was dispatched or terminal and cannot be stopped by cancellation. Aborting a wait leaves the job queued.
job_wait supports MCP task augmentation. A task-capable host can receive a task
ID immediately, manage the wait outside model turns, and retrieve the result
through tasks/result. Task status and cancellation are authorized by the same
job ownership checks. Without task support, the tool waits in its current HTTP
call. Host timeouts can close that call; resume waiting with the same job ID rather
than submitting again. Waking a closed agent thread requires integration in the
host app. A hosted MCP server alone cannot start a new model turn in another app.
If a submission reply is lost, inspect the saved request ID with job_status.
Duplicate IDs reject execution without replaying a result. For outcome_unknown,
inspect activity or resulting state before deciding what to do.
The helper reconnect wakes the service's queue. Dispatch rechecks permission, folder policy, revocation and the original computer-access deadline. OAuth jobs follow the saved MCP connection across credential renewal; hourly credential expiry does not discard accepted work. Direct-token jobs require that individual token to remain valid. A reconnect never extends a job's deadline. Known undelivered calls can wait again; uncertain sends never retry automatically.
Queued transfer calls still depend on their transfer's existing state and expiry. Queueing a chunk, import or status call cannot recreate expired transfer state. Bulk work uses the public origin saved at submission and the ordinary bulk authorization path, rather than forwarding public bulk arguments directly to the helper.
Arguments and results are capped at 1.5 MB of encoded JSON each. Oversized results report
job_result_too_large after the operation may have run. Results remain until one
hour after the job deadline. Queued and dispatched jobs count toward 32 jobs per computer
and 1,000 globally. Retained completed, cancelled, failed and expired jobs do not
consume active queue capacity. A separate storage budget of 64 MiB per computer
and 256 MiB globally includes reserved result space; job_storage_full means
retained results must expire before more work can be accepted. D1 stores these arguments and results.
REST uses POST https://tunnel.masti.lol/api/jobs with the queue body and an agent bearer token.
/api/jobs/wait waits up to 55 seconds for a result; a client library can repeat
that wait without involving a model. /api/jobs/status inspects once, and
/api/jobs/cancel cancels, each accepting { "jobId": "..." }.
The SDK exposes remote.queue(deviceId, operation, args, { requestId, expiresInSeconds }), remote.waitJob(jobId, { signal, onUpdate }), remote.job
and remote.cancelJob. waitJob uses the server wait endpoint. Pi exposes
ezremote_queue with the same optional deadline and ezremote_job, which waits
by default. Set wait: false to inspect once. Native Pi tools still run on the Pi
host when device is omitted. Configure a scoped agent token through isolated
Pi settings; store only the activated agent token.
Keep a Terminal session
Use shell.start for commands that may exceed 30 seconds, servers, prompts and
REPLs. Save its sessionId. Call shell.read with that ID, a byte cursor
starting at 0 and optional waitMs up to 20000. Save the returned cursor for the
next read unchanged. bytesDropped > 0 means output was lost from the buffer. maxBytes defaults to 65536 and accepts 4–524288 bytes. The cursor advances only over returned bytes; more: true means output remains. Reads preserve UTF-8 character boundaries. running and code
say whether the process has exited. Send a line with shell.write, including its
newline. Use shell.status, shell.list and shell.kill to inspect or stop work.
Each connection has eight sessions, each with a 1 MB output buffer. Sessions end
at connection expiry or after two hours without an agent read, write or status
call. Set idleMinutes on shell.start to 1–1440 minutes; the default is 120. Connection expiry always wins. Exited output is retained for five minutes.
Background jobs (cmd &) end when the session's shell exits; keep the shell in
the foreground, for example with wait, to keep them running. Sessions run through the same
Terminal sandbox as shell.exec. Pipes support interactive line input, but
programs requiring a TTY are unsupported. The person can inspect and kill sessions
from the helper tray or ezremote sessions list and ezremote sessions kill ID.
Read, list and search with coverage
files.read without limit returns the whole file up to 1 MB. Before writing a
file back, check that its read status is complete. For large files, page with a
zero-based line offset and a limit up to 1000, or tail: true. Pass the opaque
cursor to continue. files.list accepts depth up to 20
and limit up to 1000. files.stat returns size, type and modification time.
files.search accepts a literal query, mode: "names" or "content", depth,
limit, timeoutMs up to 3000 and maxBytes up to 16000000. Content search returns
the first matching line per file. Continue with cursor and the same query options.
All these results include status, skipped and scannedBytes. complete covers
the requested range; partial has another page or skipped files; truncated hit
a size or entry budget; timed_out hit the time budget. Follow a cursor when one
is returned and retain skipped-file reports across pages. Empty results establish
absence only when status is complete. Binary files, files over the search byte
budget, inaccessible paths and symlink directories are reported in skipped.
Search scans at most 1 MB per file. Raise the budget or use paged reads to inspect
an oversized file. Filesystem edits during pagination can change results.
Resolve an uncertain outcome
Never retry outcome_unknown blindly. Save the request ID and call MCP
resolve_operation with deviceId and requestId, or POST those fields to
https://tunnel.masti.lol/api/operations/resolve. The SDK provides
remote.resolve(deviceId, requestId). Resolution never runs the operation again.
running describes a mutating request the current helper has admitted. Read-only calls are not journaled and resolve to unknown; they are safe to retry.
refused_expired means it did not start because its deadline passed. finished
includes the saved reply. A resultTruncated reply retains the success or failure
but omits an oversized result. unknown means the helper is offline, has no
retained record, or restarted while the outcome was uncertain. Inspect activity
and resulting state before deciding what to do next. A fresh ID is new work, not
a safe retry. Results remain available for at most 24 hours, 1,024 requests or
16 MB, whichever limit is reached first, and each reply is capped at 64 KB.
Synchronous calls accept deadlineInMs from 1000 to 55000, default 30000. This is
an admission deadline, not cancellation of a running command. Use sessions for
long commands and explicit offline jobs when work should wait for a computer.