Give your agent a "publish this" button. It returns a link.
publ is a hosting layer for AI-generated artifacts. An agent calls a single MCP tool to publish an HTML page, Markdown doc, or file — and instantly gets a shareable link that opens in any browser. Re-publishing keeps the same link and versions the content. A web dashboard manages your artifacts, usage, and MCP connection.
A Cyt Software product — sibling-in-spirit to GitShare (share private repos via link), but for agent output. Open source and self-hostable.
📄 Full spec, decisions, and roadmap: PRD.md
┌─────────┐ publish_artifact(...) ┌──────────────┐
│ Agent │ ───────────────────────▶ │ publ │ stores content + version
│ (MCP) │ ◀─────────────────────── │ /mcp │ returns https://<slug>.…
└─────────┘ { url: "…" } └──────────────┘
│ serves
┌───────────┐ opens the link ▼
│ Anyone │ ──────────────────▶ <slug>.<usercontent-domain>
└───────────┘ (own origin · strict CSP · no cookies)
- Your agent publishes an artifact over MCP and gets back a link.
- You send the link to anyone — it opens in their browser.
- The agent can update it (same link, new version), list, fetch, revoke, or delete.
The publish → serve loop runs end-to-end on a local stack. MCP tools:
| Tool | Does |
|---|---|
publish_artifact |
Publish inline HTML / Markdown / text → returns the share URL |
update_artifact |
New version, same URL (old versions stay at /v/<n>) |
list_artifacts |
Your artifacts with URLs + view counts |
get_artifact |
Fetch content + metadata (latest or a specific version) |
revoke_link |
Disable the public link (visitors get 410); content retained |
delete_artifact |
Permanently delete the artifact and purge its bytes |
- Markdown renders to a clean, themed reader page.
- Each artifact is served from its own origin with a strict CSP and no cookies (so artifact JS can't touch your session — see PRD §8).
- Per-account bearer-token auth; every tool is scoped to the calling user (no cross-account access).
Deferred to Phase 2 and rejected loudly until then: password-protected links,
expires_atenforcement, and large-file (presigned) uploads.
Agent ──Bearer token──▶ FastAPI app ── /mcp FastMCP (streamable HTTP)
Dashboard ──session───────▶ ── /api REST API
Recipient ────────────────▶ <slug>.<usercontent-domain> ── serve layer (strict CSP, no cookies)
One FastAPI deployable, dispatched by Host. Storage is behind an interface — local filesystem for dev, Backblaze B2 in Phase 2, swapped via one env var.
Stack: FastAPI + uv (Python 3.12) · async SQLAlchemy + Alembic · PostgreSQL · FastMCP v3 · Next.js 15 + pnpm + Tailwind + Radix · Docker → Dokploy.
# Minimal backend/.env for local dev (USERCONTENT_DOMAIN=localhost enables *.localhost serving).
# See .env.example for the full list of variables.
cat > backend/.env <<'ENV'
ENV=development
USERCONTENT_DOMAIN=localhost
DATABASE_URL=postgresql+asyncpg://pubspace:pubspace@localhost:5433/pubspace
STORAGE_BACKEND=local
SESSION_SECRET=dev-secret-not-for-prod
ENV
docker compose up -d db # Postgres (host port 5433, dev override)
cd backend
uv sync
uv run alembic upgrade head
uv run python -m app.scripts.seed you@example.com # prints an API token + connect command
uv run uvicorn app.main:app --reload --port 8000Connect your agent and publish. Claude Code:
claude mcp add --transport http publ http://localhost:8000/mcp/ \
--header "Authorization: Bearer <token-from-seed>"OpenCode (~/.config/opencode/opencode.json):
{ "mcp": { "publ": {
"type": "remote",
"url": "http://localhost:8000/mcp/",
"enabled": true,
"headers": { "Authorization": "Bearer <token-from-seed>" }
} } }Then ask your agent to publish_artifact(title="Test", type="html", content="<h1>It works</h1>"). It returns http://<slug>.localhost:8000 — open it in any browser.
Run the dashboard too:
cd frontend
corepack enable # one-time (uses the pinned pnpm)
pnpm install
pnpm dev # http://localhost:3000Local only: dev uses local-filesystem storage and serves artifacts under
*.localhost. Nothing is exposed to the public internet. Backblaze B2 + a dedicated usercontent domain land in Phase 2. To run a real deployment, seeSELF_HOSTING.md.
publ/
├─ PRD.md # product spec, decisions, roadmap
├─ docker-compose.yml # db + backend + frontend (override exposes db on 5433)
├─ backend/ # FastAPI (uv)
│ └─ app/
│ ├─ main.py # app factory: host-routes usercontent, mounts /mcp + /api
│ ├─ mcp/ # FastMCP server + DB-backed bearer-token auth
│ ├─ services/ # publishing logic (create/version/serve, ownership checks)
│ ├─ storage/ # Storage interface + LocalStorage (B2 in Phase 2)
│ ├─ serve/ # usercontent serve layer (CSP, no cookies)
│ ├─ models/ · api/ # ORM models · REST routes
│ └─ scripts/seed.py # mint a user + API token
└─ frontend/ # Next.js 15 + pnpm + Tailwind + Radix
- Phase 0 — Scaffold ✅ monorepo, CI, migrations, MCP mounted.
- Phase 1 — Publish loop ✅ publish/update/list/get/revoke/delete, versioning, Markdown rendering, local storage, token auth + ownership. (dashboard UI still stubbed)
- Phase 2 — B2 storage + large files, dedicated usercontent domain, dashboard auth (magic link) + token management UI, password/expiry, OAuth connector, billing.
See PRD.md §11 for detail.
Contributions welcome — see CONTRIBUTING.md and our CODE_OF_CONDUCT.md. Found a vulnerability? Please follow SECURITY.md (don't open a public issue).
To be finalized. The hosted publ.sh service is intended as the commercial offering; this repository is the open core.