Returning images & artifacts¶
Console output is text-only. Artifacts are how sandboxed code hands typed
payloads — images, audio, CSVs, arbitrary binary — back to the MCP client.
Images are the headline use case: the MCP spec defines an ImageContent tool
result block (base64 data + mimeType), and image/* artifacts are returned
as exactly that, so a model connected through MCP can see the image rather
than read about it.
Store an artifact from JavaScript¶
Call the artifact(key, mime, bytes) global anywhere in your code:
const png = renderChart(); // Uint8Array of PNG bytes
artifact("chart", "image/png", png);
key— caller-chosen identifier (≤ 256 bytes). Writing the same key again overwrites it.mime— atype/subtypemime type, e.g.image/png,text/csv,application/octet-stream.bytes— aUint8Array, any TypedArray, anArrayBuffer, or a string (UTF-8 encoded). Max 16 MiB per artifact.
Invalid arguments throw a TypeError/Error you can catch in JS. Artifacts
are stored in the server's execution database, so they persist across
executions (and server restarts) and are shared across sessions — keys are a
single global namespace.
Fetch an artifact over MCP¶
get_artifact(key) returns two content blocks: a JSON metadata block
(key, mime_type, size_bytes, created_at, execution_id, encoding)
followed by the payload rendered by mime type:
| Stored mime | MCP content block |
|---|---|
image/* |
ImageContent (base64 data + mimeType — the model sees the image) |
audio/* |
AudioContent (base64 data + mimeType) |
| anything else, valid UTF-8 | TextContent with the raw text |
| anything else, binary | TextContent carrying base64 |
list_artifacts() returns metadata for everything stored.
Discover what an execution produced¶
In async (stateful) mode, a completed execution lists what it emitted in the
artifacts field of get_execution:
{ "tool": "get_execution", "arguments": { "execution_id": "01J9W…" } }
// Response: { "status": "completed", …,
// "artifacts": [ { "key": "chart", "mime_type": "image/png",
// "size_bytes": 48213, … } ] }
Then fetch the payload:
{ "tool": "get_artifact", "arguments": { "key": "chart" } }
In stateless mode, run_js attaches emitted artifacts directly to its own
tool result as content blocks (up to 8 MiB of payloads inline; larger
artifacts are listed in the result JSON with "inline": false and stay
retrievable via get_artifact).
Fetch raw bytes over REST¶
The REST API serves artifact payloads verbatim — no base64 — with the stored
mime type as Content-Type:
curl http://localhost:8080/api/artifacts # metadata list
curl http://localhost:8080/api/artifacts/chart -o chart.png
Sizing images for models¶
Model providers cap image inputs (Claude, for example, rejects images over
~5 MB or 8000×8000 px, and tokens scale with pixel count), and base64 inflates
payloads by ~33%. Downscale or compress in JS before calling artifact() —
a chart rarely needs to be wider than ~1500 px.