Skip to content

Streams and files

An MCP tool call is a request and a response. Much of Arcane is not: logs and stats arrive over WebSockets, the live feed is Server-Sent Events, image pulls stream progress, and backups are binary files. This page explains how each of those becomes something a tool call can return.

Live streams are actions that connect, listen for a bounded window, and return what arrived. Every window is capped at 120 seconds, and collected output at ARCANE_MCP_MAX_DOWNLOAD_BYTES.

container.logs, project.logs and swarm_service.logs read the matching WebSocket log stream.

ParameterDefaultMeaning
containerId / projectId / serviceId—What to read (required)
tail100Lines from the end of the log, or all
since—RFC 3339 timestamp, Unix timestamp or relative duration (10m)
timestampsfalsePrefix each line with its timestamp
followfalseKeep listening for new lines for seconds after the existing ones
seconds5Longest time to listen
maxLines1000Stop after this many lines

By default the action returns the current tail and stops. With follow: true it also waits for new lines — useful for “watch the logs while I restart it”. For the complete log as a file, use container.logs_download instead.

diagnostics.server_logs returns the Arcane server’s own recent log entries, listening for new ones for seconds (default 2) up to maxLines (default 500).

container.stats, container.stats_all, system.stats, diagnostics.snapshot and diagnostics.actors_stream collect samples messages (default 1, at most 60), waiting at most seconds (default 10). One sample answers “how much memory is it using?”; several show a trend.

stream.snapshot subscribes to Arcane’s multiplexed SSE feed (GET /api/stream) and returns the events received.

ParameterDefaultMeaning
channelsall fiveComma-separated: environments, dashboard, activities, events, version
limit—Maximum activities in each activities-channel snapshot
seconds5How long to listen
maxEvents100Stop after this many events

For history rather than what is happening now, use event.list or activity.list.

container.exec runs one command through Arcane’s container terminal and returns its output and exit code:

{
"action": "container.exec",
"params": { "containerId": "db", "command": "df -h /var/lib/postgresql" }
}
ParameterDefaultMeaning
containerId—Container ID or name (required)
command—Shell command line (required)
shell/bin/shShell that runs it
seconds30Timeout, at most 120

The terminal is an interactive TTY, so the server first silences it (empty prompts, no line editing, stty -echo), then runs the command in a subshell framed by markers that an echo of the typed line cannot contain. The result is {output, exitCode, completed}; on timeout completed is false and a note says the output may be partial. Commands that wait for input are stopped at the timeout. container.exec is a write action, so read-only mode removes it and safe mode previews it.

diagnostics.run_command sends one command over the diagnostics WebSocket: leakScan (goroutine leak scan), dump (a named runtime profile as text) or profile (capture a named profile for seconds and return it base64-encoded). The pprof routes are separate actions (diagnostics.pprof_*).

Some Arcane routes answer with a stream of JSON progress lines rather than one document: image.pull, image.build, project.up, project.pull, project.build and project.redeploy. The action reads the stream until Arcane closes it and returns a summary: the final status, the activity ID, any errors reported along the way, and the last progress lines. The model gets “pulled nginx:1.27, done” rather than thousands of layer updates.

These requests are not cut off by ARCANE_MCP_TIMEOUT, because a large pull can legitimately take minutes.

When a client sends a tool call with a progress token (MCP _meta.progressToken), the long operations above also report progress while they run, as notifications/progress: the progress streams per line (“abc123 Downloading: 45%” when Docker gives a layer’s size), the log, stats and live-feed windows per message received, and container.exec per chunk of output. Notifications are rate-limited to at most two a second, and a final one closes every call (“completed (121 event(s))”, “collected 12 log line(s): stream closed by Arcane”). It works the same on all three tool surfaces. A call without a token gets no notifications, and the result is the same either way.

Routes that return a file — log downloads, Compose and template files, mTLS bundles and certificates, backup archives, pprof profiles — are returned according to their content:

ContentReturned as
Text (text/*, JSON, YAML, XML, PEM, or an untyped body without NUL bytes)The text in content, with filename, contentType, bytes and truncated
Binary, within the capThe same metadata plus an MCP embedded resource (arcane://download/<filename>) carrying the bytes, base64-encoded
Binary, over the capMetadata only, with a note to raise ARCANE_MCP_MAX_DOWNLOAD_BYTES

Text over the cap is truncated at ARCANE_MCP_MAX_DOWNLOAD_BYTES (4 MiB by default) and says so. Raise the limit for large backups, keeping in mind that everything returned lands in the model’s context.

Where an Arcane route takes multipart/form-data, its file fields become file objects in the action’s schema:

{
"filename": "compose.yaml",
"content": "services:\n web:\n image: nginx:1.27\n"
}

Pass the content as UTF-8 text in content, or as base64 in contentBase64 — exactly one of the two. Array file fields (such as files of project.create) take a list of such objects. Other form fields are ordinary parameters. Control characters and invalid UTF-8 in filename are sent percent-encoded (a backspace becomes %08), since a multipart header cannot carry them; Arcane then sees the encoded name.

Routes whose body is application/octet-stream take the same pair at the top level: content for text, contentBase64 for binary.

Arcane moves large files (image archives, volume backups) through chunked upload sessions, and the catalog exposes them as the upload domain:

  1. upload.session_create with the kind, filename and size → an uploadId;
  2. upload.chunk_put for each chunk (index, plus content or contentBase64) — re-sending a chunk is idempotent;
  3. the consuming action with the uploadId, for example image.upload (load a docker save archive) or volume_backup.upload (restore a volume).

upload.session_get reports progress and upload.session_delete abandons a session. Through an assistant this suits files of modest size; every chunk travels through the model’s context as base64.