A small hub where teams publish AI-generated artifacts, get them auto-tagged and described, collect structured feedback, and share them via time-boxed links — with first-class MCP support for Claude Desktop.
Teams generate mockups, diagrams, notes, and documents with AI tools — but the outputs end up in:
Artifact Hub is the smallest thing that demonstrates the full loop a coaching/AI-content team cares about:
produce → publish → review → share
Web service is the sole owner of the uploads disk. MCP publishes by POSTing to the web API; reads go to Postgres directly.
hub_| Table | Purpose |
|---|---|
hub_artifacts | id, title, type enum, file_path, tags[], author_email, timestamps |
hub_feedback | author_name, content, rating 1–5, nullable parent_id (reply support) |
hub_share_links | nanoid(21) token, expires_at, max_views, view_count |
hub_api_keys | SHA-256 hashed personal access tokens for MCP clients |
The hub_ prefix is load-bearing — the local dev DB is shared with another project, so drizzle.config.ts uses tablesFilter: ["hub_*"]. Migrations are generate-only; db:push is banned because it tries to drop sequences it doesn't own.
HTML / image / PDF are first-class (render inline). Everything else degrades to a download button.
Only a title is required. Tags and description are inferred at upload time. Users can override — most don't.
Inline in the DB, not scattered across Slack threads. A single "Summarize" button condenses long discussions.
Signed tokens with TTL + optional view cap. No account required for external reviewers.
| Tool | Backend |
|---|---|
publish_artifact | POST to web /api/artifacts with x-api-key |
get_artifact | Postgres direct |
search_artifacts | Postgres (ilike + tag filter) |
list_my_artifacts | Postgres (scoped to token's user) |
add_feedback | Postgres direct |
summarize_feedback | Postgres + Anthropic Sonnet |
create_share_link | Postgres (nanoid + TTL) |
The token is the identity. Artifacts published via MCP auto-attach to the Google account the token was created under — no authorEmail argument anywhere.
Enforced in the browser before streaming, re-enforced in the route handler with a 413 if bypassed.
MIME allowlist · magic-byte match against declared MIME · PE/ELF/Mach-O header block · EICAR test-string block.
When VIRUSTOTAL_API_KEY is set, every upload is posted to /api/v3/files and polled for up to 15 s. Any malicious or suspicious count rejects. Timeout / missing key falls through — VT is defense-in-depth, not the primary gate.
HTML artifacts render in a sandboxed iframe (sandbox="allow-scripts allow-same-origin"). DOMPurify on ingest is next, so we can drop allow-scripts.
At upload: title + first 2000 chars → JSON array of 3–6 lowercase tags. Parsed with /\[[\s\S]*\]/ to strip hallucinated prose.
1–2 sentence summary of what the artifact is. Fills in when the user left the field blank.
On demand, when ≥2 feedback entries exist. Formats rows into numbered lines with ratings, asks Claude for consensus / disagreement / actionable suggestions.
LLM output never blocks the write path. If the Anthropic API is down, publish still succeeds with empty tags and null description — cheap to backfill later.
mainBuild: npm install && npm run build
Start: npm start (prestart runs db:migrate)
next-server idles at 120–180 MB — fits the 512 MB tier. next dev spikes past the cap and OOMs.
Build: npm install
Start: npx tsx src/mcp-server/index.ts
Reachable at /mcp over StreamableHTTP. Fresh McpServer + transport per request.
Uploads live on a 5 GB Render persistent disk at /var/data. Durable across deploys, but single-region and coupled to one service — first thing to swap for production.
saveFile/readFile interface. Removes single-region coupling.parent_artifact_id on hub_artifacts. Turns the hub from archival into iteration.allow-scripts from the HTML iframe sandbox.pgvector similarity over content, keep tag filter as a boolean clause.See WRITEUP.md § Walkthrough for a 9-step demo script.
Navigate with ← →, scroll / two-finger swipe on a trackpad, or jump to first/last with Home / End.