serve-mcp
This server is a local, MCP-controlled artifact shelf for AI agents to publish, manage, and preview content at stable browser URLs.
Publish Artifacts (artifact_publish): Snapshot and publish files, folders, or inline content, supporting:
File paths, inline content, or folders (as static sites with index resolution)
Stable slugs — custom or auto-generated from title/filename
Revisions — push updates to the same slug via
updateExisting: true, creating immutable revision historyTagging, descriptions, and script control (off by default; per-artifact opt-in)
Provenance capture: git branch, commit, and remote recorded automatically
List Publications (artifact_list): Filter by content kind (HTML, Markdown, JSON, CSV, image, SVG, etc.), tags, or text search; paginate and sort by date or title.
Delete Publications (artifact_delete): Permanently remove a slug and all its revisions/stored files.
Secure Previews: Content is sandboxed in iframes with strict CSPs (script-src 'none'). Markdown renders with GFM and Mermaid diagrams, CSVs become tables, and JSON is pretty-printed.
Multi-Agent Support: Multiple MCP processes share one shelf with automatic port discovery and coordination.
Deployment & Access: Install as a persistent service (launchd/systemd), configure host/port, and enable Tailscale/LAN access with automatic URL advertisement. Restrict path publishing to allowed root directories for security.
CLI: Check server status, manage configuration, publish content, and list artifacts without an agent.
Click on "Install 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., "@serve-mcppublish the file ./report.html as 'Weekly Report'"
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.
serve-mcp
A local, MCP-controlled artifact shelf. Your AI agents publish the HTML, Markdown, folders, CSV, and JSON they generate; you get stable, safely-rendered browser URLs to look at them. Built for a machine running several agents at once: each agent gets its own MCP process, and they all publish to one shared shelf.


Quickstart
npm install -g @saidmukhamad/serve-mcpAdd it to Claude Code (it speaks MCP on stdio and serves the HTTP shelf):
claude mcp add serve-mcp -- npx -y @saidmukhamad/serve-mcp mcpThat's it. Ask an agent to publish something and open the URL it returns.
Related MCP server: Artifacta MCP Server
How it works
MCP = control plane (artifact_publish, artifact_list, resources)
HTTP = human preview plane (gallery, /p/:slug, sandboxed previews)
Registry = SQLite (stable publications -> immutable revisions)
Store = snapshots (nothing is served live from your workspace)The tools
artifact_publish
{
"source": { "type": "path", "path": "./report.md" }, // or content / folder
"title": "Training run explanation",
"slug": "training-run-explanation", // stable /p/<slug>; generated if omitted
"updateExisting": true, // add a revision; false = conflict on an existing slug
"tags": ["ml", "report"],
"renderer": { "options": { "allowScripts": false } } // scripts stay off unless asked
}Returns the preview URL, a raw URL, an MCP resource_link, and structured artifact/publication objects. Publications are stable slots; every publish is an immutable revision at /p/:slug/r/:artifactId.
artifact_list
Filter by query, tags, kind; paginate with cursor; order by createdAt | updatedAt | title.
artifact_delete
Remove a publication by slug — all revisions and stored files go with it. Irreversible.
Resources
Only registry://publications (JSON list of everything on the shelf) appears in resources/list, so host UIs stay clean no matter how many publications exist. Two more resolve when read directly (tool results link to them): publication://<slug> (compact JSON) and artifact://<id> (raw source of a revision).
Multi-agent port discovery
N agents, one shelf, no coordination. The first serve-mcp to start binds a port (--port/SERVE_MCP_PORT, else ephemeral) and records its reachable URL in <dataDir>/server.json; every other serve-mcp process — agents, the CLI — finds that record, checks the pid is alive, and publishes into the running shelf instead of starting its own.
Rendering & safety
Sources are snapshotted into the store (~/.local/share/serve-mcp), so nothing is served from your workspace and revisions never change. Markdown/MDX renders through Sätteri (GFM, frontmatter, live mermaid diagrams); JSON pretty-prints; CSV becomes a table; folders serve as static sites.
Mermaid runs client-side but stays inside the security model: the frame's CSP allows only the shelf's own mermaid script via a per-request nonce, so script tags inside the markdown itself still never execute.
Every HTML, Markdown, and SVG preview is served inside a sandboxed iframe with Content-Security-Policy: script-src 'none' — agent-generated content cannot run scripts, phone home, or touch cookies. Scripts are an explicit per-artifact opt-in (renderer.options.allowScripts: true), which loosens the sandbox to allow-scripts for that one artifact.
The server binds 127.0.0.1 unless you opt into 0.0.0.0, and there is no auth — only expose it to networks you trust (a Tailscale tailnet qualifies; the open internet does not). Restrict path publishing with SERVE_MCP_ALLOWED_ROOTS=/path/a:/path/b.
Folder navigation
Folders behave like a classic file server: each directory serves its own index.html / index.md / README.md (or pass entrypoint), dir redirects to dir/ so relative and ../ links resolve, and directories without an index get a browsable listing with a ../ entry. In-folder Markdown/CSV/JSON render on the fly, and any file is downloadable with ?raw.
Provenance capture
Every publish records where it came from — source directory plus git branch, remote, and commit — read straight from .git files (no git subprocess, works even without git installed). This shows on the gallery cards and the preview subbar.
HTTP routes
GET / gallery (search, pinned, recent)
GET /p/:slug latest revision, rendered
GET /p/:slug/r/:artifactId a specific revision
GET /raw/:artifactId original source
GET /meta/:artifactId artifact metadata JSON
GET /api/publications JSON list (query, cursor, limit)
DELETE /api/publications/:slug remove a publication and all its revisionsAlways-on shelf
The shelf normally lives as long as some MCP session is running (the first one starts it). To keep it up permanently — one server every agent and human on the machine works against:
serve-mcp config port 7331 # fixed port, stable URLs
serve-mcp service install # launchd on macOS, systemd --user on LinuxManage it with serve-mcp service start|stop|restart|status|logs|uninstall. Everything is user-level — no root/admin:
macOS — launchd agent (
io.github.saidmukhamad.serve-mcpin~/Library/LaunchAgents), KeepAlive supervision, logs in<dataDir>/serve.log.Linux — systemd user unit,
Restart=on-failure, logs in the journal. To survive logout:loginctl enable-linger $USER. On WSL, systemd user services are often unavailable — runserve-mcp servein tmux instead.Windows — Task Scheduler task registered from XML (no admin): starts hidden at logon via
wscript, restarts on failure, logs in<dataDir>\serve.log.
The service pins the current runtime and package paths (shown on install) — re-run service install after upgrading either.
Tailscale / LAN access
To reach the shelf from other machines:
serve-mcp config host 0.0.0.0
serve-mcp config port 7331Advertised URLs then pick the best reachable name automatically: MagicDNS name (learned via reverse DNS through Quad100 and verified with the system resolver, so it's only used when peers can actually resolve it) → Tailscale IP (100.64.0.0/10) → first LAN address. Tailnet detection needs no Tailscale tooling — it keys off the interface's CGNAT address and Tailscale's ULA prefix.
CLI
serve-mcp # status: running shelf + what's on it
serve-mcp config host 0.0.0.0 # set config (host, port, baseUrl)
serve-mcp serve # HTTP shelf only
serve-mcp publish ./report.md --title "Report" # publish without an agent
serve-mcp list # (also discovers a running shelf)Config
serve-mcp config shows it, serve-mcp config <key> <value> sets it (empty value unsets). Stored in <dataDir>/config.json (default ~/.local/share/serve-mcp/config.json), everything optional:
{
"host": "0.0.0.0",
"port": 7331,
"baseUrl": "http://my-machine.tailnet.ts.net:7331",
"allowedRoots": ["~/projects", "/srv/artifacts"]
}host— bind host, default127.0.0.1port— fixed port; omit for an ephemeral port + discovery viaserver.jsonbaseUrl— advertised-URL override (e.g. a MagicDNS name)allowedRoots— restrict wherepath/folderpublishing may read from (default: anywhere readable)
Env vars (SERVE_MCP_HOST, SERVE_MCP_PORT, SERVE_MCP_BASE_URL, SERVE_MCP_DATA_DIR, SERVE_MCP_ALLOWED_ROOTS) override the file; --host/--port flags override both.
Development
npm install
npm test # typecheck + node:test — core, http, mcp round-trip
npm start # HTTP server
npm run build # tsc -> dist/Written in TypeScript; dev and tests run .ts directly via Node's native type stripping. The published package runs on Node ≥ 22.5 (built-in node:sqlite); developing needs Node ≥ 22.18 (type stripping).
MIT.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/saidmukhamad/serve-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server