Artifacts
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ArtifactsCreate a markdown artifact with the quarterly goals"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Artifacts
Artifacts turns agent-generated Markdown, HTML, and text into persistent links. Its byte-native
storage core also recognizes PNG, JPEG, GIF, WebP, SVG, and PDF files up to 10 MiB each. Images and
PDFs use the same stable /a/:id links as documents and are served inline with content-type,
filename, revalidation, and content-sniffing protections. It includes a streamable HTTP MCP server
for creating and managing documents, plus a small server-rendered web interface for reading and
finding them.
Artifacts is unversioned pre-alpha software. It has no compatibility guarantees: configuration, storage, MCP tools, and URLs may change or be removed without a migration path.
By default, Artifacts has no authentication or authorization. Run it that way only on a trusted internal network or loopback interface. To reach it from elsewhere, set an owner password (see Owner login) and serve it over HTTPS, or put an authentication-capable proxy in front of it.
Prerequisites
Docker with a running Docker daemon for the recommended quickstart.
Node.js 24 or newer and pnpm 11 for local development.
Related MCP server: serve-mcp
Docker quickstart
From the repository root:
corepack enable
pnpm install --frozen-lockfile
pnpm docker:check
pnpm docker:startWait until docker ps reports the artifacts container as healthy, then open
http://localhost:4242. The MCP endpoint is http://localhost:4242/mcp.
pnpm docker:logs
pnpm docker:stopThe helper builds locally, binds only to 127.0.0.1, and uses the deterministic container name
artifacts. If an ignored docker-compose.yaml exists, the same commands delegate to Docker
Compose so operators can maintain a private topology without committing it.
Connect an agent
Replace http://localhost:4242 in these examples with the URL that your trusted network or proxy
uses to reach the deployment.
Codex:
codex mcp add artifacts --url http://localhost:4242/mcpClaude Code:
claude mcp add --transport http --scope user artifacts http://localhost:4242/mcpWhen an owner password is set, /mcp requires an API key. Log in, open API keys in the
gallery header, and create a key. The page shows the key once. Put it in an environment variable,
here ARTIFACTS_API_KEY, and then add the server:
codex mcp add artifacts --url http://localhost:4242/mcp --bearer-token-env-var ARTIFACTS_API_KEY
claude mcp add --transport http --scope user artifacts http://localhost:4242/mcp \
--header 'Authorization: Bearer ${ARTIFACTS_API_KEY}'Both agents read the variable each time they connect, so the key is not stored in their
configuration. Keep the single quotes in the Claude Code command: they stop the shell from
expanding the variable, and Claude Code expands ${ARTIFACTS_API_KEY} itself.
/mcp refuses a request whose Origin header names any origin but the server's own, with or
without an owner password. Command-line and desktop MCP clients, such as Codex and Claude Code, send
no Origin and work. A browser-based MCP client sends the origin of its own page, so it is refused.
For example, MCP Inspector in direct connection mode cannot connect.
The server exposes add_artifact, remove_artifact, list_artifacts, list_collections, and
get_artifact. Artifacts are immutable: there is no update tool. A revision or variation is a new
artifact, usually in the same collection, so earlier versions keep their links.
Text artifacts use content; binary artifacts use canonical mediaType, a safe filename, and
strictly encoded contentBase64. Binary content is omitted from add, list, and ordinary get
responses. Pass includeContent: true to get_artifact when the bytes are actually needed. The
decoded size limit is 10 MiB, and the HTTP body guard may reject an oversized base64 request before
an MCP tool result can be returned.
Use an optional shared collection to keep a document and its independent image or PDF artifacts
discoverable together. list_artifacts filters collections case-insensitively, while
list_collections returns their stored display names. An embedding workflow is:
Add the binary artifact and retain the absolute
urlreturned by the server.Put that URL in an HTML
<img>element or Markdown image expression.Add the containing document with the same collection.
To replace an embedded file, add the new file and then a new version of the document that embeds it. Removing a file can break every document that embeds it; collections group artifacts for discovery but do not create ownership or cascading deletion.
HTML artifacts are served with a
Content-Security-Policy: sandbox allow-scripts allow-popups allow-popups-to-escape-sandbox allow-modals
header (ADR-0006). Their scripts run, but in an
opaque origin that is separate from the Artifacts server, and their requests carry Origin: null.
As a result, an HTML artifact cannot use localStorage, sessionStorage, IndexedDB, or cookies,
cannot read responses from other Artifacts routes, and cannot submit forms. The /mcp endpoint
refuses requests with Origin: null or any other origin but its own, so artifact scripts and other
web pages cannot create or remove artifacts. Links with target="_blank" and window.open() open a
new window outside the artifact's sandbox, so another site loads as an ordinary page (another
HTML artifact still gets its own sandbox). alert(), confirm(), and
prompt() dialogs work.
Images, stylesheets, and classic scripts that the page loads by URL still work, including embedded
/a/:id images. Loads that use CORS, such as <script type="module"> or elements with a
crossorigin attribute, fail for files from this server; files from other hosts load when the host
sends Access-Control-Allow-Origin: *. Frames inside an HTML artifact inherit its sandbox. A PDF in
an <iframe>, <embed>, or <object> does not display, so link to the PDF instead. A Markdown
artifact in an <iframe> shows no syntax highlighting or Mermaid diagrams. When an owner password
is set, the browser does not send the session cookie on requests from the sandbox, so embedded
/a/:id files load through signed URLs instead. See Owner login.
Optional artifact skill
The bundled skill teaches an agent how to publish documents, files, and their revisions, how to embed returned file URLs, and how to rediscover related artifacts through collections. From the repository root, install it for the client you use:
Codex:
mkdir -p ~/.agents/skills
ln -s "$PWD/skills/artifact" ~/.agents/skills/artifactClaude Code:
mkdir -p ~/.claude/skills
ln -s "$PWD/skills/artifact" ~/.claude/skills/artifactRestart the client after installing the skill. The skill is optional; MCP tools work without it.
Configuration and persistence
The zero-configuration Docker path needs no .env file. Copy .env.example to .env only to
override Docker defaults.
Variable | Default | Purpose |
|
| Loopback host and container port used by the Docker helper. |
|
| Base URL returned for artifacts; must be an absolute HTTP(S) URL without credentials, query, or fragment. |
|
| Docker volume name or existing absolute host directory for document bodies. |
|
| Separate Docker volume name or existing absolute host directory for SQLite. |
|
| Separate Docker volume name or existing absolute host directory for rendered gallery previews. |
| unset | Turns on owner login; see Owner login. Passed to the container by name, so the value does not appear on the Docker command line. |
| unset | With owner login, the header that your proxy sets to the client's address, such as |
Named volumes survive container replacement and pnpm docker:stop. The three mounts must be
different. Absolute bind-mount directories must exist and be writable from the container by the
non-root node user (uid 1000); pnpm docker:start probes each before replacing an existing
container. The previews mount holds derived data only: it needs no backup, and a container started
without it simply re-renders every preview at boot.
For a consistent backup, stop the service and treat the SQLite database and stored files as one
backup set. With the default named volumes, export both into a newly created backups directory:
pnpm docker:stop
mkdir -p backups
docker run --rm --mount source=artifacts-files,target=/source,readonly --mount "type=bind,source=$PWD/backups,target=/backup" alpine tar -czf /backup/artifacts-files.tgz -C /source .
docker run --rm --mount source=artifacts-database,target=/source,readonly --mount "type=bind,source=$PWD/backups,target=/backup" alpine tar -czf /backup/artifacts-database.tgz -C /source .For bind mounts, copy the two configured directories while the service is stopped. Restore both sources from the same backup before restarting.
Database migrations run automatically at startup from the SQL files in migrations/. The
binary-artifact schema recognizes PNG, JPEG, GIF, WebP, SVG, and PDF content with a 10 MiB
per-artifact limit. It can be downgraded only while every row is still a legacy text document and neither filename nor collection metadata
has been stored. Once binary rows or new metadata exist, the guarded down migration refuses without
changing the database; restore both persistence sources from the same pre-migration snapshot
instead. Pre-migration builds cannot safely read an upgraded database. The down migration is a
programmatic recovery primitive, not an operator CLI command; no supported pnpm command invokes
it.
Direct development uses .env.development and intentionally separate paths:
Variable | Default |
|
|
|
|
|
|
|
|
|
|
| probed: Alpine |
| unset: no login |
| unset: the connection's address |
See .env.development.example for copyable overrides. Docker mount variables and direct-runtime
path variables are deliberately different; one is not an alias for the other.
Owner login
Set ARTIFACTS_OWNER_PASSWORD to require a login. When it is unset, there is no login and every
route is open, as described above. A blank value, or one shorter than 16 characters, stops the
server at startup. Use a long, random password.
With a password set:
Every page and every
/a/:idlink redirects to/loginuntil the owner logs in. The login form,/assets/*, and the root icons stay public, so the Docker health check still passes.A login lasts 7 days. Log out in the gallery header ends it.
Login allows 10 attempts from one client address in any 15-minute window, and 100 from all addresses together. After that the form answers
429with aRetry-Afterheader. A successful login clears the count for its own address only.The client address is the connection's address. Behind a reverse proxy, every connection comes from the proxy, so all clients share one count. Set
ARTIFACTS_CLIENT_ADDRESS_HEADERto the header that your proxy sets to the client's address:CF-Connecting-IPbehind Cloudflare, orX-Real-IPorX-Forwarded-Forbehind a proxy that you configure to set it. For a list header, the last entry counts. The server reads no other header, and a request without the named header counts against its connection's address. Any client can send this header, so make sure that the server is reachable only through the proxy./mcpanswers401unless the request carriesAuthorization: Bearer <key>with a key from the API keys page. A session cookie does not open/mcp.HTML artifacts still show embedded artifacts. When an HTML artifact is added, the server records each
/a/:idURL in thesrc,srcset,poster, orhrefof animg,source,iframe,video,audio, orlinkelement. The URL must be root-relative or start withARTIFACTS_PUBLIC_BASE_URL, and it may have a fragment but no query. Each view replaces those URLs with signed URLs that work for 10 to 15 minutes. Views in the same clock five minutes get the same URLs, so the browser can reuse cached embeds. URLs that scripts build, and CSSurl(), are not signed and do not load.Gallery previews still render. The renderer reads each artifact through a signed URL that works for 60 seconds and for that one artifact only. An expired or altered signature gets
403.Set
ARTIFACTS_PUBLIC_BASE_URLto the URL the browser uses. Login and logout forms are accepted only from that origin or the origin the request arrived on, and anhttps:base URL marks the session cookieSecure.
Sessions, API keys, and the hourly keys that sign URLs are stored in the SQLite database, API keys only as SHA-256 hashes. Changing the password and restarting ends every session but keeps API keys; revoke them on the API keys page, one at a time or all at once. The page shows when each key was created and last used. The design is recorded in ADR-0001.
Gallery previews
The index shows a screenshot of every artifact. A headless Chromium renders each artifact's own page after it is created, off the request path, and the JPEG is stored under the thumbnails directory. Until it exists the card shows a drawn placeholder for the file kind, and a PDF keeps its placeholder because headless Chromium downloads PDFs instead of drawing them. Previews are derived data: the server re-renders any that are missing at startup, so the thumbnails directory needs no backup and no mount. Without a Chromium the server logs one notice and keeps the placeholders.
A preview is a screenshot of the static document: the render context has JavaScript disabled, so
an artifact's scripts never run on the server host, and a Mermaid fence shows as its source in the
thumbnail. Declarative loads (images, frames, stylesheets) may only GET from the server's own
origin; everything else, including /mcp and anything on loopback or the container network, is
blocked at a proxy nobody listens on, and a render that is still busy after 15 seconds is
abandoned. A load of a URL under ARTIFACTS_PUBLIC_BASE_URL, such as an artifact embedded by the
URL that add_artifact returned, is redirected to the same path on the server's own origin, so the
embed shows in the preview without the renderer reaching the public address.
Development
corepack enable
pnpm install --frozen-lockfile
pnpm devBefore opening a pull request:
pnpm fmt
pnpm lint
pnpm test
pnpm buildpnpm test runs two Vitest projects. node runs the ordinary tests. workers runs
*.workers.test.ts files inside workerd, the local Workers runtime, through
@cloudflare/vitest-pool-workers, with Miniflare providing D1 and R2 bindings locally. It needs
no Cloudflare account. The D1 and R2 adapters run the same store contract suites there that the
SQLite and filesystem adapters run under Node, and the D1 tests apply the shared migrations/
files first.
pnpm start runs an existing build with development storage defaults. See
CONTRIBUTING.md for the contribution workflow.
Troubleshooting
Docker CLI not found: install Docker Desktop or Docker Engine, then rerunpnpm docker:check.Docker daemon is unavailable: start Docker Desktop or the Docker service.Docker Compose v2 is unavailable: install the Compose plugin or remove/rename the localdocker-compose.yamlto use the bare-Docker path.A mount error before startup: use different valid volume names, or create the absolute files and database directories first. Commas are not supported in mount sources.
Returned links point at the wrong host: set
ARTIFACTS_PUBLIC_BASE_URLto the externally reachable base URL and restart.Port 4242 is occupied: set
ARTIFACTS_PORTand the matchingARTIFACTS_PUBLIC_BASE_URLin.env.
Architecture
Artifacts is a Node.js 24 TypeScript application built on Hono. The same process serves the web UI,
artifact routes, static assets, and the streamable HTTP MCP endpoint at /mcp. Metadata lives in
SQLite while byte-native content lives in a separate files directory. The MCP adapter uses bounded
base64 for binary transport; base64 is not part of the storage or service model. Markdown is rendered once, when it
is created, and its HTML is stored in SQLite as derived data, so a view parses no Markdown; a missing rendering, or
one from an older renderer version, is rendered again on the next view. Code highlighting and Mermaid diagrams are
rendered in the browser. Allowlisted images and PDFs
are served directly from /a/:id; SVG responses receive an additional restrictive content security
policy, and HTML artifacts are served in a script-enabled CSP sandbox with an opaque origin. Gallery
previews are screenshots taken by Alpine's Chromium package, driven by playwright-core from a serial
in-process queue. The Docker image is a two-stage Alpine build that
runs as the non-root node user; the application writes only to its two persistence mounts and
the derived thumbnails directory, and Chromium keeps its own scratch profile under the container's
temporary directory.
The metadata and content adapters are deliberately narrow seams for a future hosting requirement, not a configurable backend system. PostgreSQL, object storage, multipart browser uploads, presigned uploads, tenancy, permissions, and public hosting remain out of scope. Owner login protects one owner's instance; it does not share artifacts with anyone else. Artifacts is a preview tool, not a general-purpose file-sharing service.
Security and license
Read SECURITY.md before deploying. Artifacts is available under the MIT License.
This server cannot be deployed
Maintenance
Related MCP Connectors
- mcpOAuthio.artifacta
Artifact store for AI agents. Hosted OAuth at mcp.artifacta.io/mcp; local stdio via npm/PyPI.
MarkupBase turns AI-generated Markdown and HTML into durable, versioned artifacts that people can review and discuss. Its MCP server lets agents publish new versions, preserve contextual comments, include hosted images, and respond to feedback through secure account-linked identities, creating a clear human review boundary without requiring real-time editing.
Private, expiring artifact hosting for AI agents. Upload via API or MCP; links die on their own.
Publish HTML, files, or a URL to a permanent public URL, then update it — from any MCP agent.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceA local, agent-to-agent artifact exchange for LLM workflows. Enables MCP-capable tools like Claude, Codex, and Gemini to publish, list, read, update, and continue from artifacts without copying content through chat.33 npmMIT
- AlicenseAqualityCmaintenanceA local MCP-controlled artifact shelf for agents to publish generated content with stable preview URLs. Provides a shared SQLite registry and HTTP gallery for human browsing.313 npm1MIT
- AlicenseNot gradedqualityCmaintenanceEnables agents to publish HTML artifacts to permanent, shareable, versioned pages on Artifyde via MCP.45 npmMIT
- AlicenseNot gradedqualityAmaintenanceThis MCP server enables AI agents to manage artifacts across sessions by providing tools for searching, retrieving, and registering entries in a persistent ledger, ensuring consistency and traceability of agent outputs.MIT