Skip to main content
Glama
saidmukhamad

serve-mcp

by saidmukhamad

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.

The shelf gallery — cards with git provenance, tags, and a per-item menu

A rendered markdown report with a provenance subbar

Quickstart

npm install -g @saidmukhamad/serve-mcp

Add it to Claude Code (it speaks MCP on stdio and serves the HTTP shelf):

claude mcp add serve-mcp -- npx -y @saidmukhamad/serve-mcp mcp

That'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 revisions

Always-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 Linux

Manage 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-mcp in ~/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 — run serve-mcp serve in 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 7331

Advertised 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, default 127.0.0.1

  • port — fixed port; omit for an ephemeral port + discovery via server.json

  • baseUrl — advertised-URL override (e.g. a MagicDNS name)

  • allowedRoots — restrict where path/folder publishing 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.

Install Server
A
license - permissive license
A
quality
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

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