Server data from the Official MCP Registry
Self-hosted artifact publishing: POST HTML/JSX/Markdown/zip, get an unguessable URL on your domain
Self-hosted artifact publishing: POST HTML/JSX/Markdown/zip, get an unguessable URL on your domain
artifacts-host is a well-designed self-hosted artifact publishing service with solid security fundamentals. Authentication is properly enforced via bearer tokens with timing-safe comparison. The codebase demonstrates good input validation, path traversal protection, and appropriate Content Security Policy headers. Minor code quality concerns (broad error handling, some input validation edge cases) and a reasonable permission scope appropriate to the service's purpose prevent a higher score, but no critical vulnerabilities were identified. Supply chain analysis found 4 known vulnerabilities in dependencies (0 critical, 3 high severity).
4 files analyzed · 10 issues found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
This plugin requests these system permissions. Most are normal for its category.
Set these up before or after installing:
Environment variable: ARTIFACTS_API_KEY
Environment variable: BASE_URL
Add this to your MCP configuration file:
{
"mcpServers": {
"io-github-kuyazee-artifacts": {
"env": {
"BASE_URL": "your-base-url-here",
"ARTIFACTS_API_KEY": "your-artifacts-api-key-here"
},
"args": [
"-y",
"artifacts-host"
],
"command": "npx"
}
}
}From the project's GitHub README.
Self-hosted, Claude-style artifact publishing. POST an HTML page, a React component, a Markdown doc, or a whole zipped static site — get back a public, unguessable, non-crawlable URL on your own domain. Built for coding agents (MCP server included) and humans (drag-and-drop web UI included).

AI assistants generate a lot of shareable output — dashboards, prototypes, reports, little apps. Claude's hosted artifacts are great, but the URLs live on someone else's infrastructure. This is the ~600-line self-hosted version:
/data.curl.noindex everywhere, bearer-token writes, optional expiry./mcp — publish, update, rename, disable/enable, set expiry, list, delete./ — drag-and-drop or paste to publish, manage everything, locked behind your API key.expiresAt → 410), delete.X-Robots-Tag: noindex, nofollow on every response, deny-all robots.txt.index.html required, static-only extension whitelist, traversal/symlink rejection, size and file-count limits.git clone https://github.com/kuyazee/artifacts && cd artifacts
ARTIFACTS_API_KEY=$(openssl rand -hex 32) BASE_URL=https://artifacts.example.com docker compose up -d
docker run -d -p 3000:3000 -v artifacts-data:/data \
-e ARTIFACTS_API_KEY=$(openssl rand -hex 32) \
-e BASE_URL=https://artifacts.example.com \
ghcr.io/kuyazee/artifacts:latest
npm ci
ARTIFACTS_API_KEY=$(openssl rand -hex 32) BASE_URL=https://artifacts.example.com node server.js
Then publish something:
curl -s -X POST https://artifacts.example.com/api/artifacts \
-H "Authorization: Bearer $ARTIFACTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "<h1>hello</h1>", "type": "html", "slug": "hello"}'
# {"slug":"hello","url":"https://artifacts.example.com/a/hello"}
Works on any Dockerfile-based PaaS (Coolify, CapRover, Dokploy, Railway…): expose port 3000, mount a volume at /data, set the two env vars, health check GET /healthz.
| Env var | Required | Default | Purpose |
|---|---|---|---|
ARTIFACTS_API_KEY | yes | — | Bearer token for all writes and the MCP endpoint |
BASE_URL | recommended | http://localhost:3000 | Public origin used in returned URLs |
DATA_DIR | no | /data | Where artifacts are stored (plain files) |
PORT | no | 3000 | Listen port |
POST /api/artifacts {content, type: html|jsx|tsx|md, slug?, title?, expiresAt?} → 201 {slug, url}
POST /api/artifacts/zip raw zip body (?slug=&title=&expiresAt=) → 201 {slug, url, files}
PUT /api/artifacts/:slug {content, type, title?, expiresAt?} → {slug, url}
PATCH /api/artifacts/:slug {slug?, disabled?, expiresAt?} → {slug, url} (rename / disable / expiry)
DELETE /api/artifacts/:slug → {deleted}
GET /api/artifacts list → [{slug, type, title, createdAt, updatedAt}]
GET /a/:slug rendered artifact (public)
GET /a/:slug/source original uploaded source, text/plain (public)
All /api/* and /mcp calls need Authorization: Bearer $ARTIFACTS_API_KEY. Body limits: 10 MB JSON, 50 MB zip. POST with an existing slug → 409 (use PUT to update).
Disabled artifacts return 404; expired ones (expiresAt in the past) return 410. Both keep their content — re-enable or clear/extend the expiry to serve again.
Publish a file:
jq -n --rawfile c page.html '{content: $c, type: "html"}' | \
curl -s -X POST https://artifacts.example.com/api/artifacts \
-H "Authorization: Bearer $ARTIFACTS_API_KEY" \
-H "Content-Type: application/json" -d @-
POST /api/artifacts/zip with the raw zip as the body deploys a whole static site (HTML + CSS + JS + images) under /a/{slug}/:
curl -s -X POST "https://artifacts.example.com/api/artifacts/zip?slug=my-site" \
-H "Authorization: Bearer $ARTIFACTS_API_KEY" \
-H "Content-Type: application/zip" \
--data-binary @site.zip
# {"slug":"my-site","url":"https://artifacts.example.com/a/my-site/","files":12}
The archive is validated before anything is stored:
index.html at the root (a single shared top-level folder is stripped automatically, so zip -r site.zip my-project/ works as-is)../), absolute paths, and symlinks are rejected__MACOSX/, .DS_Store, Thumbs.db are ignoredRename, disable/enable, expiry, and delete all work the same as single-file artifacts. PUT (inline content) is refused on zip sites — delete and re-upload instead. The web UI accepts dropped .zip files. No MCP tool (binary payload) — agents should use the curl call above.
Everything the API does, from your terminal. Ships with the repo (cli.js, no extra dependencies):
export ARTIFACTS_URL=https://artifacts.example.com
export ARTIFACTS_API_KEY=...
npx github:kuyazee/artifacts publish page.html --slug hello # or: node cli.js ...
# https://artifacts.example.com/a/hello
npx github:kuyazee/artifacts deploy ./my-site --slug my-site # zips a directory and deploys it
# https://artifacts.example.com/a/my-site/ (12 files)
artifacts publish <file> [--slug s] [--title t] [--expires ISO] [--type html|jsx|tsx|md]
artifacts deploy <dir|zip> [--slug s] [--title t] [--expires ISO]
artifacts update <slug> <file>
artifacts list
artifacts rename <slug> <new-slug>
artifacts disable <slug> | enable <slug>
artifacts expire <slug> <ISO-date|never>
artifacts delete <slug>
artifacts source <slug> [-o file]
Type is inferred from the file extension; --url/--key flags override the env vars.
Upload a single React component with a default export. Imports of react, react-dom, recharts, lucide-react are pinned; any other package import resolves via https://esm.sh/<pkg>?external=react,react-dom automatically. Tailwind classes work out of the box.
import { useState } from 'react';
import { Rocket } from 'lucide-react';
export default function Demo() {
const [n, setN] = useState(0);
return (
<button className="m-8 px-4 py-2 rounded bg-blue-600 text-white" onClick={() => setN(n + 1)}>
<Rocket className="inline w-4 h-4 mr-2" />clicked {n}
</button>
);
}
Note: rendering uses esm.sh + Tailwind CDN, so artifacts need internet to render and take ~1–3 s on first load.
Streamable HTTP endpoint at /mcp, bearer-authenticated. Tools:
| Tool | Args | Returns |
|---|---|---|
publish_artifact | content, type, slug?, title?, expiresAt? | public URL |
update_artifact | slug, content, type, title? | public URL |
rename_artifact | slug, newSlug | new public URL |
disable_artifact | slug | confirmation (URL serves 404, content kept) |
enable_artifact | slug | confirmation |
set_artifact_expiry | slug, expiresAt (ISO 8601 or null to clear) | confirmation |
list_artifacts | — | JSON list |
delete_artifact | slug | confirmation |
claude mcp add --transport http artifacts https://artifacts.example.com/mcp \
--header "Authorization: Bearer ${ARTIFACTS_API_KEY}" --scope user
~/.codex/config.toml)[mcp_servers.artifacts]
url = "https://artifacts.example.com/mcp"
bearer_token_env_var = "ARTIFACTS_API_KEY"
No MCP needed — one curl call (see REST API above). Suggested snippet for a global CLAUDE.md / AGENTS.md:
To publish an HTML/JSX/Markdown page publicly, use the
artifactsMCPpublish_artifacttool, orPOST https://artifacts.example.com/api/artifactswithAuthorization: Bearer $ARTIFACTS_API_KEYand JSON{content, type, slug?}. The returned URL is public but unguessable and non-indexed.
Single-user service. Uploaded HTML executes on this origin, so:
X-Robots-Tag: noindex, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, and a CSP that limits external requests to esm.sh and major CDNs.See SECURITY.md for what counts as a vulnerability and how to report one.
PRs welcome — see CONTRIBUTING.md. The whole test suite is one shell script (.github/workflows/smoke.sh) you can run against a local dev server.
MIT © 2026 Zonily Jame
Be the first to review this server!
by Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
by Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
by Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.