ArtifactSmith
ArtifactSmith is an MCP server for creating, versioning, exporting, sharing, and managing private documents and artifacts.
Create artifacts (HTML, Markdown, PDF, DOCX, XLSX) via an OpenAI-compatible model using
verbatim_requestplus optionalsource_content/source_files, with optional style and build mode.Publish finished Markdown or HTML without a model call, with optional metadata like summary, assumptions, and note.
Edit an existing artifact into a new version from a base version, reusing or replacing source content.
Poll job status, including
queued,building,done,failed, orneeds_input, with wait up to 90 seconds.List artifacts in your workspace and inspect manifests, versions, history, files, build logs, share state, and optionally content.
Export rendered files as HTML, Markdown, PDF, DOCX, or XLSX via 15-minute download links.
Share exact versions or latest versions with public links, pin versions, set TTLs, and revoke links immediately with
unshare.Revoke private preview links for an artifact or version.
Delete artifacts with a two-step confirmation purge.
Operate within a token-scoped workspace with granular permissions: create, read, edit, publish, export, share, and delete.
Calls an OpenAI-compatible chat completions endpoint to generate and edit artifact content, including HTML, Markdown, PDF, DOCX, and XLSX, using a configured model and API key.
ArtifactSmith
ArtifactSmith is an open source artifact server for AI agents.

Agents call it to turn their work into documents: web pages, PDFs, Word files, spreadsheets and Markdown. Each artifact is versioned and stays private until someone shares it, and a shared link can be revoked at any time.
It's for people who run their own agents and want artifacts like the ones in Muse on their own servers.
Try it before you install: https://artifactsmith.neuralami.ai/try. I run a copy you can point your MCP client at (email for a token, capped at 20 builds, runs on my server).
Examples, no sign-up: comparison page · status page with a chart · PDF checklist
What it does
ArtifactSmith gives agents tools to create, edit, export (PDF, Word, Excel, Markdown), share, and revoke documents. It works with any OpenAI-compatible model endpoint. The idea comes from Meta's Muse artifacts.

Requirements: Docker Engine with the Compose plugin. A model key (OpenAI-compatible API or local endpoint) is needed to build pages; publishing ready-made Markdown or HTML works without one.
Related MCP server: PasteAI
Quickstart
cp .env.example .env
# Set OPENAI_API_KEY (or AM_LLM_KEY). Change AM_LLM_BASE / AM_DEFAULT_MODEL
# only if you are not using the default OpenAI host and model.
# Object-store keys: leave them empty and run ./scripts/ensure-local-env.sh,
# or paste your own AM_STORE_KEY and AM_STORE_SECRET.
./scripts/ensure-local-env.sh
docker compose up -d --build
# Create a token (prints the raw value once; only its hash is stored).
# --workspace is a label that scopes every artifact the token can see
# (e.g. alpha, team-a). Tokens cannot cross workspaces.
docker compose exec server artifactsmith token add --name agent --workspace alphapublish stores a page you already wrote and does not call the model, so the next step works when the key is empty. Paste the token printed above, then run this. It prints a share URL.
export AM_TOKEN='paste-the-token'
docker compose exec -T -e AM_TOKEN server python - <<'PY'
import asyncio
import json
import os
from datetime import timedelta
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
URL = "http://127.0.0.1:8780/mcp"
PAGE = """# Pilot store brief
The pilot store code is HARBOR-17. It is the identifier used on inventory, support, and rollout reports.
"""
def parse(result):
if result.structuredContent is not None:
return result.structuredContent
return json.loads(result.content[0].text)
async def main():
headers = {"Authorization": "Bearer " + os.environ["AM_TOKEN"]}
async with streamablehttp_client(URL, headers=headers) as (read, write, _session):
async with ClientSession(read, write) as session:
await session.initialize()
created = parse(
await session.call_tool(
"publish",
{"display_name": "Pilot store brief", "markdown": PAGE},
read_timeout_seconds=timedelta(seconds=120),
)
)
done = parse(
await session.call_tool(
"status",
{"job_id": created["job_id"], "wait": 90},
read_timeout_seconds=timedelta(seconds=120),
)
)
if done.get("status") != "done":
raise SystemExit(json.dumps(done))
shared = parse(
await session.call_tool(
"share",
{"artifact_id": created["artifact_id"]},
read_timeout_seconds=timedelta(seconds=120),
)
)
print(shared["url"])
asyncio.run(main())
PYThe server reads AM_LLM_BASE, AM_LLM_KEY or OPENAI_API_KEY, and AM_DEFAULT_MODEL from .env. The default base is https://api.openai.com. A trailing /v1 on the base is fine; the client appends /v1/chat/completions itself. create needs a key or a local OpenAI-compatible endpoint. With an empty key, a failed create reports that OPENAI_API_KEY / AM_LLM_KEY is empty. Run docker compose up -d again so the change takes effect.
Host ports bind to 127.0.0.1:
http://127.0.0.1:8780/mcpis the MCP API (bearer token required)http://127.0.0.1:8780/app/is My pages (paste the same token; the browser gets a session cookie)http://127.0.0.1:8781/is the cookieless preview and share origin (/p/…,/s/…)
Use the published image
ghcr.io/vrijsinghani/artifactsmith:0.1.0 is published for linux/amd64 on each release. Building from source (the Compose steps above) stays the default.
Connect a client
Claude.ai: Customize → Connectors → + Add → Add custom connector. Paste https://artifactsmith.neuralami.ai/mcp, select Connect, and sign in with your email. The connector then works in the Claude desktop and phone apps too. Free plans allow one custom connector. On Team and Enterprise plans an owner adds it first.
github.com/vrijsinghani/Artifactsmith is the copy you run yourself. Claude Desktop, Claude Code, and Cursor use a token there. The MCP endpoint on this machine is http://127.0.0.1:8780/mcp. Send the token from artifactsmith token add as Authorization: Bearer YOUR_TOKEN. The header value starts with Bearer .
ChatGPT Free, Go, and Plus cannot add a custom MCP server. Pro can add one with read and fetch access only, so it cannot build pages. A ChatGPT Business, Enterprise, or Edu admin can add it on the web; it signs in with OAuth.
Claude Desktop stores the config in claude_desktop_config.json (Settings → Developer → Edit Config). The snippet runs npx and mcp-remote, which needs Node.js. Restart Claude Desktop after saving.
{
"mcpServers": {
"artifactsmith": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:8780/mcp", "--header", "Authorization:${AUTH_HEADER}"],
"env": { "AUTH_HEADER": "Bearer YOUR_TOKEN" }
}
}
}Claude Code stores .mcp.json, or run:
claude mcp add --transport http artifactsmith http://127.0.0.1:8780/mcp --header "Authorization: Bearer YOUR_TOKEN"{
"mcpServers": {
"artifactsmith": {
"type": "http",
"url": "http://127.0.0.1:8780/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}Cursor stores ~/.cursor/mcp.json, or .cursor/mcp.json in the project.
{
"mcpServers": {
"artifactsmith": {
"url": "http://127.0.0.1:8780/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}Example prompt: Create a one-page brief named Pilot store brief. Pass this as source_content: The pilot store code is HARBOR-17. It is the identifier used on inventory, support, and rollout reports.
artifactsmith stdio can list tools on stdin and stdout and does not listen. It cannot use the object store or the model, so it cannot build or publish a page.
Serving on your network or behind a proxy
Compose publishes API (8780) and preview/share (8781) on AM_BIND_ADDRESS (default 127.0.0.1). Cards and links use AM_API_URL, AM_PREVIEW_URL, and AM_SHARE_URL (empty AM_SHARE_URL falls back to AM_PREVIEW_URL). Exposing the ports without TLS puts bearer tokens and artifact content on the wire in cleartext.
LAN access
Bind on all interfaces (or a specific NIC IP) and point the public URL variables at the address clients will open:
# .env (192.0.2.10 is documentation-range; substitute your LAN IP)
AM_BIND_ADDRESS=0.0.0.0
AM_API_URL=http://192.0.2.10:8780
AM_PREVIEW_URL=http://192.0.2.10:8781
AM_SHARE_URL=http://192.0.2.10:8781
AM_ALLOWED_HOSTS=192.0.2.10:8780,127.0.0.1:8780,localhost:8780Then docker compose up -d --build. Clients open http://192.0.2.10:8780/mcp and preview/share links use 192.0.2.10, not 127.0.0.1. Browser-based MCP clients also need AM_ALLOWED_ORIGINS set to the page origin.
Share and preview URLs are built from AM_SHARE_URL / AM_PREVIEW_URL when the server returns them. Links you copied before switching to LAN serving still use the old base host; call share again (or copy the URL from inspect / status) after changing those settings.
Reverse proxy with TLS
Keep the host bind on loopback and terminate TLS on nginx or Caddy. Set the public URLs and allow-lists to the external hostname:
# .env
AM_BIND_ADDRESS=127.0.0.1
AM_API_URL=https://artifacts.example.com
AM_PREVIEW_URL=https://preview.artifacts.example.com
AM_SHARE_URL=https://preview.artifacts.example.com
AM_ALLOWED_HOSTS=artifacts.example.com
AM_ALLOWED_ORIGINS=https://artifacts.example.comMinimal Caddy example (API on 443, preview on a second hostname):
artifacts.example.com {
reverse_proxy 127.0.0.1:8780
}
preview.artifacts.example.com {
reverse_proxy 127.0.0.1:8781
}Minimal nginx snippet for the API host:
server {
listen 443 ssl;
server_name artifacts.example.com;
# ssl_certificate / ssl_certificate_key omitted
location / {
proxy_pass http://127.0.0.1:8780;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
# status wait can be up to 90s; nginx defaults cut at 60s.
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
}When AM_ALLOWED_HOSTS lists only the public hostname, a local curl http://127.0.0.1:8780/mcp gets HTTP 421 (DNS-rebinding protection). Point clients at the proxied hostname instead. Browser-based MCP clients send an Origin header; set AM_ALLOWED_ORIGINS to that origin or the request is rejected.
MCP client on another machine
Point the client at {AM_API_URL}/mcp and send the token as Authorization: Bearer <token> (the value printed once by artifactsmith token add). The header value starts with Bearer . artifactsmith stdio can list tools on stdin and stdout and does not listen. It cannot use the object store or the model, so it cannot build or publish a page. Example:
URL: http://192.0.2.10:8780/mcp
Header: Authorization: Bearer asmb_…How it works
Agents talk to ArtifactSmith over MCP. They pass the user's request as verbatim_request and any research as source_content or source_files. create starts a private build and does not publish a share link. Want a styled page? Pass Markdown to publish and it is styled automatically (omit format when you pass markdown and no html; house style by default, applied when the page is opened). You can still pass format=markdown. Or call create with source_content. Raw HTML in publish is only for a page you designed yourself, and it is shown exactly as given.
The server calls an OpenAI-compatible endpoint (up to two attempts when checks fail). The model writes content. A fixed renderer turns that into HTML, Markdown, PDF, DOCX, or XLSX. publish skips the model and runs the same checks and renderer. Call status (or wait up to 90 seconds) to get a private preview.
share creates a public link for one exact version. unshare makes that link return 404. Tokens belong to one workspace and carry a permission set.
Documents may cite public http(s) sources as clickable links. The server strips scripts, rejects private-link hosts and likely secrets, and keeps images, CSS, fonts, and iframes self-contained so a download opens offline and does not phone home. Size limits apply. The process does not send usage data anywhere.
Output formats
Format |
| Primary file | Renderer |
HTML |
|
| sanitize, then store |
Markdown |
|
| UTF-8 text |
|
| WeasyPrint (HTML/CSS to PDF) | |
DOCX |
|
| |
XLSX |
|
|
HTML uses a visual style (house by default) and stores a complete page. The other formats ask the model for Markdown, then a renderer converts it. publish accepts the finished HTML or Markdown and uses that same renderer. PDF, DOCX, and XLSX also store content.md so later edits have a text base.
PDF goes through WeasyPrint, which needs Pango and Cairo. The Docker image does not include Chromium. Details are in docs/formats.md.
Styles
create and edit take an optional style for HTML: house, bold, editorial, playful, terminal, or swiss. The default is AM_DEFAULT_STYLE (usually house). On markdown, PDF, DOCX, and XLSX the model prompt does not use that visual style. A published markdown page is still shown in the recorded style when it is opened. Omit style on edit to keep the last style.
create also takes mode: full (default, AM_BUILD_MODE) has the model write HTML. fast has the model write Markdown and the server render that style. fast is a first HTML build only. Edits and other formats stay on the full path. AM_FAST_STYLES (empty by default) lists styles whose omitted mode is fast; an explicit mode still wins. A complete HTML document in the source, or a request to publish that HTML as-is, stays on full when mode is omitted. Mentioning HTML beside "unchanged" does not.
house: today's quiet page — white, one accent, boldness in one place.bold: dark navy-to-violet hero, huge white headline, saturated cards and chips.

editorial: magazine feature — off-white paper, large serif headline, thin rules, one deep red accent.

playful: cream page, candy-colored rounded cards, thick outlines and offset shadows.

terminal: dark mode, monospace, neon green and cyan, sections as fake terminal windows.

swiss: white grid, huge black grotesque type, signal-orange only on numbers and rules.

MCP tools
Tool | Permission | What it does |
|
| Queue a private build. Optional |
|
| Store Markdown to get a styled page, or store HTML you designed yourself. No model call. |
|
| New version from the latest done version. Refuses a stale |
|
| Poll, or |
|
| Catalog for the token's workspace. |
|
| Manifest, history, share state. Optional |
|
| Download link for the rendered file, valid 15 minutes. |
|
| Public link for one exact version. |
|
| Revoke the public link immediately. |
|
| Expire private |
|
| Two-step purge. First call returns a confirm token. |
create arguments
Required:
verbatim_request: the user's exact words. That is the whole scope.
Provide at least one of display_name or slug (the other is filled in):
display_name: human title shown on cards and in the catalog. Not required whenslugis set; if omitted, it defaults to the slug. Limit: 200 characters. Example:"Pilot store brief".slug: URL key, unique in the workspace. Not required whendisplay_nameis set; if omitted, it is derived from the title (lowercase, accents folded, other characters become hyphens). Limit: 2–63 characters ofa-z,0-9, and-, and it must start with a letter or digit. Example:"pilot-store-brief". A duplicate slug in the same workspace is rejected; useedit.
Optional:
kind: defaults toweb_static(the only kind in this release).format:html(default),markdown,pdf,docx, orxlsx.style:house(default),bold,editorial,playful,terminal, orswiss. HTML builds use it. A markdown page is shown in that style when opened. PDF, DOCX, and XLSX do not.mode:full(default) orfast.fastis a first HTML build: Markdown in, styled HTML out.source_content/source_files: researched facts, 200 KB total. The builder treats this as data.model,workspace,idempotency_key,capabilities.
Working example (title only; slug becomes pilot-store-brief). The source text must
support every fact the request asks for (including “why it matters”):
{
"display_name": "Pilot store brief",
"verbatim_request": "One page that states the pilot store code and why it matters.",
"source_content": "The pilot store code is HARBOR-17. It matters because it is the single identifier used across inventory, support, and rollout reports for the pilot.",
"format": "html"
}If a needed fact is missing from the request and the source, the job ends as needs_input and stores no file. When server checks fail, the model is called a second time, so a failed build takes about twice as long to report as a clean one. An edit asks for a patch of the changed parts first. A patch that does not apply, or that fails those same checks, is rebuilt as a full file in the same job.
publish arguments
Want a styled page from text you already wrote? Pass Markdown and it is styled automatically. Omit format when you pass markdown and no html. You can still pass format=markdown. style defaults to house and is applied when the page is opened. The stored file stays Markdown. On a new artifact, if you also pass html, html is the page unless you set format. Raw HTML is shown exactly as given, with no ArtifactSmith styling. If that HTML has little or no CSS, the result includes warning. Pass format for pdf, docx, or xlsx. publish does not call the model. The page still goes through the same size caps, secret scan, private-link checks, sanitizer, renderer, and final sweep as model output.
New artifact: pass display_name or slug, plus the page.
html: complete HTML document. Required whenformatishtml.markdown: finished Markdown. Required whenformatismarkdown,pdf,docx, orxlsx. Whenformatis omitted and this is the only body, the format is markdown.You may also pass the other field. It is checked for secrets and private links and is not rendered.
New version: pass artifact_id and base_version (the latest done version) plus the new page. Omit format on an html or markdown artifact and markdown with no html is markdown; html with no markdown is html; both fields keep the artifact's format. A pdf, docx, or xlsx artifact keeps that format when format is omitted and the new page is markdown. Pass format to choose another supported format. Pass display_name to rename the card when this version finishes. Omit display_name and a changed page title (the HTML <title>, or the first Markdown heading) becomes the card title. The artifact id and slug stay the same. The latest share page uses that name for <title> and og:title. Older versions keep their own format. A shared markdown page opens as styled HTML; ?raw=1 or document.md on that URL downloads the stored Markdown.
summary and assumptions are optional card fields (8000 characters each) and are scanned like model output. A longer summary or assumption is rejected when the call arrives, with metadata exceeds N characters, and nothing is stored. note is optional. Inspect shows it beside Published a finished page. A note that is too long, or that fails the secret, private-link, or allow-list check, is rejected before a version row exists. On markdown, style changes the opened page (house by default). On HTML, style is recorded and the submitted page is not restyled. PDF, DOCX, and XLSX do not use the HTML style sheet.
Returns artifact_id, version, and job_id. Call status until done or failed. A rejected page is failed, and error names the check that failed. When the job is done, status and inspect include safety_changes, the list of safety edits that changed the submitted page. HTML publish does not turn a bare URL into a link. publish does not create a share link. It counts toward the per-token build quota and needs the publish permission.
export
Permission: export.
Arguments:
artifact_id(required): which artifact to download.version(optional): a done version. Omit it to use the latest done version.
Returns download_url (valid 15 minutes), file, version, sha256, self_contained, and expires_in ("15m").
{ "artifact_id": "art_example", "version": 1 }share
Permission: share.
Arguments:
artifact_id(required): which artifact to publish.version(optional): the exact done version. Omit it to use the latest done version.ttl_days(optional): lifetime in days. Defaults toAM_SHARE_TTL_DAYS. WhenAM_SHARE_TTL_DAYSis0,ttl_daysis ignored and the link lasts until revoked. Otherwise the lifetime is capped byAM_SHARE_TTL_MAX_DAYS.
Returns state ("shared"), url, version, expires_at, lifetime, and reused. Anyone with the URL can open that version.
{ "artifact_id": "art_example" }unshare
Permission: share.
Arguments:
artifact_id(required): which artifact to revoke.version(optional): revoke one version. Omit it, or pass0, to revoke every version.
Returns artifact_id, revoked_links (how many links were revoked), and state. state is "shared" when a public link is still live, with url and follows ("latest" or "pinned"). It is "not_shared" only when no public link remains. Revoking one pin leaves the follow-latest link up. A revoked URL returns 404 immediately.
{ "artifact_id": "art_example" }delete
Permission: delete.
Arguments:
artifact_id(required): which artifact to purge.confirm_token(omit on the first call): token from the first response. It expires in 10 minutes.
The first call does not delete anything. It returns step ("confirm"), confirm_token, title, versions, expires_in ("10m"), and a warning. The second call purges every stored version, share link, and database row. It returns deleted (true), artifact_id, and purged_object_versions.
{ "artifact_id": "art_example" }{ "artifact_id": "art_example", "confirm_token": "paste-the-token" }Configuration
Every setting also has a NAME_FILE variant that reads the value from a file.
Variable | Default | Purpose |
| empty | LLM credential for the configured endpoint. |
|
|
|
|
| OpenAI-compatible host root. With or without a trailing |
| unset | Chat Completions temperature. Unset sends |
|
| Model name sent to the LLM. |
|
| HTML visual style when |
|
|
|
| empty | Comma-separated styles ( |
|
|
|
|
| Cap on positive TTLs. |
| (required) | S3-compatible object store. |
|
| Bucket name. |
| (required) | Object-store credentials. |
|
| Reject localhost, RFC1918, and link-local hosts in output. |
| empty | If set, every remaining host must match. |
| 50 MiB | Rendered output cap. |
|
| Seconds for one renderer call. |
|
| Seconds for the whole job. |
|
| Seconds for the edit patch call. A timeout falls back to a full rewrite. |
|
| Concurrent workers. A hosted trial uses at most one of these slots. |
|
| Per-token quota for internal tokens. Trial tokens use |
|
| Open the hosted try-it page ( |
|
| Email a one-time link before showing the token. |
|
| Lifetime builds for one trial token. |
|
| needs_input jobs per hosted token that do not use a build. Further ones do. |
|
| Hourly limit for a trial token. Does not change |
|
| Global trial spend cap per UTC day. Internal tokens are not capped. |
|
| USD per 1M input / output tokens, used to price the hold. |
|
| Model for every trial build. The caller's model name is ignored. |
|
| Output cap sent on trial model calls. |
|
| Sign-up attempts per IPv4 address or IPv6 |
|
| Minted sign-ups per UTC day. After that, |
|
| Magic-link lifetime, in seconds. |
|
| Delete a trial workspace this many days after its last build. |
| empty | Mailto on the trial page footer. Empty omits the link. |
| empty / | Magic-link mail. Empty host or from fails closed. |
| empty | SMTP login. Password accepts |
|
| Start TLS when sending the magic link. |
|
| Listen address for |
|
| Host publish bind for ports |
|
| Listen ports. |
|
| URLs written into cards and links. Empty |
| follows |
|
| empty | Comma-separated IPs or CIDRs that may set |
| 8 / 8 / 900 | Failed sign-ins per IPv4 address or IPv6 |
|
| Comma-separated Host values for MCP DNS-rebinding protection (on by default). |
| empty | Comma-separated Origin values for MCP DNS-rebinding protection. |
|
| SQLite and the HMAC signing key. |
Copy .env.example for a local file. ./scripts/ensure-local-env.sh fills empty AM_STORE_KEY and AM_STORE_SECRET. The storage image is rustfs/rustfs:1.0.1 (pinned by digest in compose.yaml).
Running a hosted try-it page
Set AM_TRIAL_ENABLED=true and point AM_API_URL at the public origin. With AM_TRIAL_EMAIL_VERIFY left at true, /try emails a one-time link (AM_SMTP_HOST and AM_SMTP_FROM are required; if either is empty, sign-up fails closed). GET /trial redirects to /try. POST /trial and /trial/verify stay so an open form or a link already sent still works. The Message-ID domain is the domain of AM_SMTP_FROM. Opening the link does not spend it. The "Show my token" button shows the workspace token once. Set AM_TRIAL_EMAIL_VERIFY=false to show that same page on submit and send no mail; the sign-up row stores verified=0. Either way the page includes config for Claude Desktop (mcp-remote), Claude Code (HTTP), Cursor, and a generic MCP client. The Desktop snippet puts the bearer value in AUTH_HEADER so a space is not an argument. Cursor uses a URL and headers, with no command arguments. Each snippet says YOUR_TOKEN. The page also names Claude Desktop's Customize → Connectors → Add custom connector route, with that same public MCP URL and an Authorization request header. The header value starts with Bearer. It names claude_desktop_config.json, .mcp.json, and ~/.cursor/mcp.json, the claude mcp add command for that public MCP URL, and one example prompt that passes notes as source_content. The page states the build cap from AM_TRIAL_BUILD_LIMIT. When AM_API_URL is a public https origin, that same page carries an Open Graph and Twitter card for {AM_API_URL}/try. The image is {AM_API_URL}/static/og-trial.png and needs no token. A loopback or private AM_API_URL omits the tags. The page stays noindex. One normalized address gets one token. Gmail addresses ignore dots and +tags. Disposable domains are rejected. Sign-up attempts from one IPv6 network share a /48. My pages login stays a /64. At most AM_TRIAL_SIGNUPS_PER_DAY tokens are minted per UTC day (default 300). After that, /try says no more hosted tokens today, back at 00:00 UTC.
Hosted builds always use AM_TRIAL_MODEL and cannot call publish. A hosted token's tool list omits publish. Responses include builds_remaining. Up to AM_TRIAL_FREE_NEEDS_INPUT (default 5) needs_input jobs per token do not use a build; after that they do. A failed job still does. The daily spend cap still counts that job's model tokens, and the hourly limit still counts the attempt. Deleting an artifact does not return those builds. When the daily spend cap is reached, the error is the hosted copy hit today's limit, back at 00:00 UTC. A used-up token points at the GitHub repo. Share links for a hosted token last at most 30 days. The served page adds noindex even when the page already has a robots meta that does not say noindex, and a footer that links the repo and stays visible if the page hides footers. Idle hosted workspaces are deleted after AM_TRIAL_RETENTION_DAYS (default 30). That sweep runs at startup and then hourly.
Security
Tokens are issued per workspace and stored as SHA-256 hashes. Knowing an artifact id does not grant access.
Permissions are create, read, edit, publish, export, share, and delete.
Builds stay private until you call share. That creates a public /s/… URL that follows the latest done version. Pass version or pin to freeze one version on its own URL. unshare makes it return 404. unshare with a version revokes only that pin.
Preview and share pages run on their own port, send no cookies, and set CSP script-src 'none' with sandbox allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation.
My pages (/app/ on the API origin) is a read-only catalog for the token's workspace. Sign-in pastes a token into a form. The response sets an HttpOnly, SameSite=Strict session cookie on path /app and does not put the token in the URL. The cookie is Secure when AM_API_URL is https (or when AM_COOKIE_SECURE=true). Each request loads the token again, so a revoke drops the session immediately. Another workspace's artifact id is a 404. The list shows 50 pages at a time. Times use America/New_York unless the viewer sets another IANA zone; the page names that zone. Behind a reverse proxy, set AM_TRUSTED_PROXIES to that proxy so a failed sign-in is counted for the client (X-Real-IP, or the rightmost X-Forwarded-For hop) and for a hash of the token prefix. IPv6 clients share a /64. Each IPv4 address is its own bucket, including an IPv4-mapped IPv6 address. Other peers cannot supply those headers. The preview origin does not serve /app/.
Scripts are stripped. Public http(s) links to global hosts are kept (with rel="noopener noreferrer nofollow" and target="_blank" in HTML). In-page # anchors stay on the page. Host checks use urllib plus a WHATWG-style approximation; they guarantee accepted links do not reach private hosts, not that every accepted link is browser-valid. Protocol-relative //host citations are upgraded to https:// only when the host is public; private or dangerous // destinations are neutralized, not upgraded. Private, loopback, and link-local hosts fail the build, as do javascript:, vbscript:, data:, and file:. A remote image URL becomes a clickable link to that URL (alt text as the label) in every format — nothing loads the image on open. CSS url(), fonts, and iframes stay local. Secrets fail the build. Size and time caps apply.
The process does not send usage data anywhere.
docs/threat-model.md and SECURITY.md have the rest.
Tokens
A workspace is a namespace for artifacts (for example alpha or team-a). Each token belongs to exactly one workspace and can only create or read artifacts in that workspace. Knowing an artifact id from another workspace does not grant access.
artifactsmith token add --name NAME --workspace WS [--perms create,read,edit,publish,export,share,delete]
artifactsmith token list
artifactsmith token revoke --id ID | --name NAME
artifactsmith token perms --id ID | --name NAME
artifactsmith token perms --name NAME --add publish
artifactsmith token perms --name NAME --remove publishtoken perms changes one permission on an existing token. The token value stays the same. Each add or remove appends one line to the audit log. Listing permissions does not. If the name matches more than one token, pass --id.
Uninstall / reset
# Stop containers, delete compose volumes, and remove images created for this project
# (server, storage-init, and the rustfs/rustfs pull). Requires store keys in .env like other compose commands.
docker compose down --volumes --rmi allCopy .env.example again and re-run the quickstart to start clean.
Development
python3 -m venv .venv && source .venv/bin/activate
make install
make ci # lint, types, unit tests, denylistRunning the tests
make test runs the unit suite with the same pytest command CI uses. It does not start Docker.
End-to-end tests use compose.test.yaml, which adds a fake chat model from tests/support/.
./scripts/ensure-local-env.sh
docker compose -f compose.yaml -f compose.test.yaml up -d --build
python -m tests.e2e.test_end_to_end
python -m tests.e2e.test_formats
python -m tests.e2e.test_bind_address # AM_BIND_ADDRESS=0.0.0.0 via non-loopback IPmake smoke runs the same stack and scripts. Compose commands need a .env with AM_STORE_KEY and AM_STORE_SECRET set (including docker compose down), because those variables are required by compose.yaml.
docs/architecture.md, docs/operations.md, CONTRIBUTING.md.
How this was built
Artifactsmith was vibe-coded. A person set the direction and made the calls, and AI agents wrote and checked the code:
Grok Bot ran the project. It planned the work, reviewed every pull request file by file, and decided what had to be fixed before anything merged.
Cursor cloud agents wrote the code, tests and docs, and fixed what the reviews found.
Hermes agents gave independent checks. One reviewed the code as a senior engineer and signed off on each round. Another installed it from scratch, following only this README, the way a new user would.
Before release it went through:
Several rounds of line-by-line code review, each one repeated until no blocking issues were left.
An independent security audit with Semgrep, Bandit, gitleaks, pip-audit and Trivy, plus a written threat model.
Hands-on attack testing of the HTML cleaner, link handling and export formats.
Real builds with a live model in all five formats, checking that links stay clickable and nothing loads from outside when a document is opened.
Clean installs on a separate machine, including serving it to other machines on a local network.
Automated checks on every change: lint, strict type checking, unit tests on Python 3.11 and 3.12, a Docker build, dependency scanning, and secret scanning.
The audit report is in docs/security-audit-2026-10-09.md. If you find something we missed, see SECURITY.md.
License
MIT. House-style writing and visual rules are adapted from Humanizer (MIT) and Anthropic frontend-design (Apache-2.0). Attribution is in NOTICE; third-party license texts are under licenses/. The Docker image ships those files at /app.
Available Tools
11 toolscreateAInspect
Queue a new artifact. Returns artifact_id, version, and job_id immediately. Then call status(job_id, wait=90).
Want a styled page from text you already wrote? Publish that Markdown. It is styled automatically
when format is omitted and no html is passed (format=markdown; style defaults to house).
Want the server to write the page? Pass source_content here.
Raw HTML in publish is shown exactly as given, with no ArtifactSmith styling.
Required: verbatim_request (the user's exact words; the whole scope).
display_name is the human title on cards (optional if slug is set; defaults to the slug;
at most 200 characters; example "Pilot store brief"). slug is the workspace URL key
(optional if display_name is set; otherwise derived from the title; 2-63 chars of a-z, 0-9,
and hyphen, starting with a letter or digit; example "pilot-store-brief"). Provide at least one.
kind defaults to web_static (the only kind in this release).
format is html (default), markdown, pdf, docx, or xlsx. The model writes content. A fixed renderer
writes the bytes. html uses the visual style (house, bold, editorial, playful, terminal, swiss;
default AM_DEFAULT_STYLE, usually house). On markdown, pdf, docx, and xlsx the model prompt does
not use that visual style. A markdown page is still shown in the recorded style when it is opened.
mode is fast or full (default AM_BUILD_MODE, usually full). When mode is omitted and the
style is listed in AM_FAST_STYLES, the default is fast instead. fast applies to a first HTML
build: the model writes Markdown and the server renders it. full is the HTML build.
Other formats ignore fast. An explicit mode still wins. A complete HTML source, or a request
to publish that HTML as-is, stays full when mode is omitted. Mentioning HTML beside "unchanged" does not.
source_content (optional, up to 200 KB total with source_files) is researched material the page
is built from. The builder uses only the request plus this material for facts.
source_files (optional) is a list of {name, content} objects under the same 200 KB cap.
If a needed fact is missing, the job ends as needs_input with a short missing message and stores
no file. Call create again with the same slug after you supply the data.
create does not create a share link. When checks fail, the server retries the model once, so a
failed build takes about twice as long to report as a clean one.| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | web_static | |
| mode | No | Build mode for a first HTML page: fast or full. fast writes Markdown and the server renders the style. full is the HTML build. Default is AM_BUILD_MODE (full), or fast when the style is listed in AM_FAST_STYLES. A complete HTML source, or a request to publish that HTML as-is, stays full when mode is omitted. Mentioning HTML next to unchanged does not. Other formats ignore fast. | |
| slug | No | URL key unique in the workspace. Optional if display_name is set (then derived from the title: lowercase, accents folded, other characters become hyphens). 2-63 characters of a-z, 0-9, and hyphen; must start with a letter or digit. Example: "pilot-store-brief". Provide display_name or slug (at least one). | |
| model | No | ||
| style | No | ||
| format | No | ||
| workspace | No | ||
| capabilities | No | ||
| display_name | No | Human title shown on cards and in the catalog. Optional if slug is set (then it defaults to the slug). At most 200 characters. Example: "Pilot store brief". Provide display_name or slug (at least one). | |
| source_files | No | ||
| source_content | No | ||
| idempotency_key | No | ||
| verbatim_request | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden and does well: it discloses the async pattern (job_id returned immediately), the needs_input failure mode that stores no file, the retry-once-on-check-failure penalty ("a failed build takes about twice as long to report"), the 200 KB cap, and the absence of a share link. Gaps remain around idempotency_key behavior, workspace scoping, and auth/permission requirements, which are behavioral concerns an agent may need.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The first sentence is correctly front-loaded, but the middle paragraphs digress into Markdown-vs-HTML and publish-vs-create asides, and the mode explanation largely restates the schema's own mode description verbatim. Sentences like "Mentioning HTML beside 'unchanged' does not" are edge-case clutter that cost more than they earn.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 13-parameter, async, job-producing tool this is a solidly complete brief covering defaults, format/mode interactions, failure handling, and the status follow-up; the output schema exists so return-shape explanation is not required. It falls short only on the undocumented idempotency_key/workspace/capabilities parameters, which an agent invoking repeatedly would want clarified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 23%, so the description must compensate, and it does for most high-traffic parameters: verbatim_request (the user's exact words, the whole scope), slug and display_name (formats, mutual fallback, examples), kind, format, mode, source_content, and source_files. It leaves model, workspace, capabilities, and idempotency_key entirely unexplained, which keeps it short of a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence gives a specific verb and resource ("Queue a new artifact") plus the immediate return values, and later sentences explicitly separate create from publish ("Raw HTML in publish is shown exactly as given, with no ArtifactSmith styling") and from share ("create does not create a share link"). An agent can place this tool against its siblings without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It routes the agent through concrete decision points: use Markdown when the user already wrote text, pass source_content when the server should write the page, use publish when HTML must be shown verbatim. It also prescribes the follow-up call ("Then call status(job_id, wait=90)") and the recovery path after a needs_input failure ("Call create again with the same slug").
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
deleteAInspect
Two-step purge of an artifact. Warn the user before the second call.
Permission: delete.
Arguments: artifact_id (required), confirm_token (optional until the second call).
Returns: first call is step "confirm" with confirm_token, title, versions, expires_in "10m", and a warning.
Second call returns deleted true, artifact_id, and purged_object_versions.
Example: delete(artifact_id="art_example") then delete(artifact_id="art_example", confirm_token="...")| Name | Required | Description | Default |
|---|---|---|---|
| artifact_id | Yes | Artifact to purge. Required. | |
| confirm_token | No | Confirmation from the first delete call. Optional on the first call (returns a token valid for 10 minutes). Required on the second call to purge. Example: "AbCdEf...". |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the irreversible purge, the required delete permission, the 10-minute token expiry, and the returned warning. It stops short of explicitly stating permanence/irreversibility or any rate limits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the two-step nature, then uses short labeled sections (Permission, Arguments, Returns, Example) with zero filler. Every line contributes operational detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive, annotation-free tool this covers the essentials an agent needs: confirmation flow, permission, token lifetime, and response shapes. Explaining returns is mild redundancy given the output schema, but nothing required is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents confirm_token's first-call-optional/second-call-required semantics and the 10-minute validity. The description restates that flow rather than adding meaning, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Two-step purge of an artifact') and immediately conveys the distinguishing mechanic (two-call confirmation). An agent can tell it apart from edit, revoke_previews, or unshare without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear procedural guidance: 'Warn the user before the second call,' and the example shows the exact call sequence. It does not, however, contrast this tool with siblings like revoke_previews or edit for partially-destructive scenarios, so it falls short of explicit alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
editAInspect
Build a new version from base_version (the latest done version) using the user's exact change request. Returns a conflict if base_version is stale. Inspect, then edit from the latest.
source_content and source_files (optional, 200 KB total) replace the base version's source.
If omitted, the base version's source is reused.
style (optional) is house, bold, editorial, playful, terminal, or swiss. Omit it to keep the
style the artifact was last built with. It applies to HTML only; other formats accept it and
ignore it.
format (optional) is html, markdown, pdf, docx, or xlsx. Omit it to keep the artifact's current
format. A different format builds this version in that format. When the version is done, the
artifact's format updates. The artifact id and slug stay the same. Older versions keep the format
they were built with.
display_name (optional) is the card title. When this version finishes, that name replaces the
artifact's display_name. Omit it and a changed page title (the HTML title, or the first Markdown
heading) becomes the card title instead. The slug stays the same.
Ends as needs_input with a missing message if the change needs facts that were not supplied.| Name | Required | Description | Default |
|---|---|---|---|
| model | No | ||
| style | No | ||
| format | No | ||
| workspace | No | ||
| artifact_id | Yes | ||
| base_version | Yes | ||
| display_name | No | ||
| source_files | No | ||
| source_content | No | ||
| idempotency_key | No | ||
| verbatim_request | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full load and does so well: it discloses the stale-base conflict, that source is replaced only when supplied, that style is HTML-only and silently ignored elsewhere, that format changes propagate to the artifact while old versions keep their format, and that it can end as needs_input. It omits idempotency_key, model, and workspace semantics.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and the stale-base caveat are front-loaded, then parameters are handled in short labeled blocks. It is on the long side but nearly every sentence adds behavior an agent could not infer from the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 11-parameter mutation tool with an output schema present, the description supplies the behavioral context an agent needs (conflict, override semantics, format propagation, needs_input terminal state). The gaps are the undocumented model/workspace/idempotency_key parameters, which matter less for correctness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate and largely does: it explains base_version, source_content/source_files (with the 200 KB cap), the style enum values, the format enum values, display_name fallback behavior, and the verbatim_request intent. It leaves artifact_id, model, workspace, and idempotency_key undefined, so it falls short of full coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence states a specific verb and resource: 'Build a new version from base_version ... using the user's exact change request.' It distinguishes the operation from the sibling 'create' by anchoring on a base version, though it never names create explicitly to draw the contrast.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Returns a conflict if base_version is stale. Inspect, then edit from the latest' gives a clear conditional workflow that routes the agent to the sibling 'inspect' tool before calling edit. It stops short of stating when-not to use edit or naming other alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
exportAInspect
Download link for one rendered file (html, md, pdf, docx, or xlsx).
Permission: export.
Arguments: artifact_id (required), version (optional; latest done version).
Returns: download_url (valid 15 minutes), file, version, sha256, self_contained, expires_in.
Example: export(artifact_id="art_example", version=1)| Name | Required | Description | Default |
|---|---|---|---|
| version | No | Done version to download. Optional; defaults to the latest done version. | |
| artifact_id | Yes | Artifact to download. Required. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the required 'export' permission and that the returned download_url is valid for only 15 minutes (reinforced by expires_in). It stops short of describing failure behavior when the version is not done or whether repeat calls invalidate prior links.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose, then cleanly sectioned into Permission/Arguments/Returns/Example. The Returns list largely duplicates the output schema, which is slight redundancy, but the 15-minute validity note earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter read-style tool with an output schema present, the description supplies the key extras an agent needs: required permission, default version behavior, and link expiration. Only error/failure-mode context is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both parameters are already documented with the same semantics the description restates (artifact_id required; version optional, defaults to latest done version). The description adds no syntax, format, or edge-case detail beyond the schema, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource: producing a download link for one rendered file, and enumerates the supported output formats (html, md, pdf, docx, xlsx). This is clearly distinguishable from siblings like create, edit, publish, and share.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by 'Download link for one rendered file' and the permission note, but the description never states when to use export versus publish/share or any preconditions (e.g. version must be a 'done' version). No explicit exclusions or alternatives are given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
inspectAInspect
Manifest, files, build log, request history, and share state. Defaults to the latest done version.
include_content is optional and defaults to false. When true, the target done version adds
content with file, size_bytes, and text when that text is within the publish character cap.
PDF, DOCX, and XLSX return content.md and set primary_binary; they do not return the binary.
Over the cap, size_bytes is set and text is omitted. A store failure sets content_error to
store_read_failed. The call still requires read permission on this workspace.
| Name | Required | Description | Default |
|---|---|---|---|
| version | No | ||
| artifact_id | Yes | ||
| include_content | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden and does so well: it discloses the include_content defaults, per-format handling (PDF/DOCX/XLSX returning content.md and setting primary_binary), the character-cap fallback (size_bytes set, text omitted), the failure mode (content_error=store_read_failed), and the read-permission requirement. This is exactly the extra context annotations would otherwise have to supply.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with what is returned before moving into parameter behavior, and each sentence carries information. It is somewhat dense in the middle section but contains no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read tool with an output schema, the description covers the non-obvious behaviors an agent needs: version defaulting, content-cap and format handling, error state, and permission prerequisites. The main gap is that artifact_id, the sole required parameter, receives no explanation anywhere.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It fully explains include_content (default false, what true adds, edge cases) and partially explains version via the 'latest done version' default, but artifact_id is never described. Partial compensation against three undocumented parameters justifies a middling score.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names the concrete payload the tool returns (manifest, files, build log, request history, share state) and states the default version target, so an agent knows this is an artifact-inspection read. It does not explicitly contrast itself with siblings like status or list_artifacts, which would have earned a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use / when-not-to-use guidance and no alternative tool is named. The only contextual hint is 'Defaults to the latest done version,' which describes behavior rather than selection criteria against status, list_artifacts, or export.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_artifactsAInspect
Catalog of artifacts in your workspace, newest first. Optional kind filter and text query on slug or title.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | ||
| limit | No | ||
| query | No | ||
| workspace | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden, and it does disclose one real behavioral trait: results are returned newest first. However it says nothing about pagination, the default limit, workspace scoping behavior, or permissions – meaningful omissions for a list tool with a limit parameter.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with zero filler; the core action and ordering are front-loaded, and the filter capabilities follow. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need no explanation, and the ordering plus two filter semantics are covered. But for a four-parameter tool with no annotations, the silence on limit/workspace semantics and pagination leaves the definition only partially complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It adds real meaning for two of four parameters (kind as a filter, query as text search over slug or title), but limit and workspace are entirely undocumented in both schema and description, leaving half the parameters opaque.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific read action (catalog artifacts) on a named resource (artifacts in your workspace) and adds the ordering (newest first), so an agent can immediately tell it apart from write-oriented siblings like create/edit/publish. It stops short of explicitly naming an alternative, but the purpose is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: the mention of an 'optional kind filter and text query' hints at when to narrow results, but there is no explicit when-to-use, when-not-to-use, or pointer to siblings such as inspect or status. Adequate but with clear gaps.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
publishAInspect
Store a page you have already finished. Use this when you already have the finished page. No model call. Returns artifact_id, version, and job_id immediately. Then call status(job_id, wait=90). When the job is done, status and inspect list safety_changes for each safety edit that changed the page. HTML publish does not turn bare URLs into links. The document title is stored as given and escaped in the page. note is optional. Inspect shows a non-empty note beside "Published a finished page." It is not the text a later edit sends to the model. A secret, private link, or over-long note is rejected before a row exists.
Want a styled page? Pass Markdown and it is styled automatically. Omit format when you pass
markdown and no html. style is house, bold, editorial, playful, terminal, or swiss (default house)
and is applied when the page is opened. The stored file stays Markdown. You can still pass
format=markdown. On a new artifact, if you also pass html, html is the page unless you set format.
Raw HTML is shown
exactly as given, with no ArtifactSmith styling. If that HTML has little or no CSS, the result
includes warning. Pass format for pdf, docx, or xlsx.
html is the complete HTML document when format is html. markdown is the finished Markdown when
format is markdown, pdf, docx, or xlsx. You may pass the other field as well: it is checked for
secrets and private links and is not rendered. PDF, DOCX, and XLSX do not use the HTML style sheet.
New artifact: pass display_name or slug (same rules as create). New version of an existing artifact:
pass artifact_id and base_version (the latest done version). Omit format on an html or markdown
artifact and markdown with no html is markdown; html with no markdown is html; both fields keep
the artifact's format. A pdf, docx, or xlsx artifact keeps that format when format is omitted and
the new page is markdown. Pass format to choose html, markdown, pdf, docx, or xlsx. Pass display_name
to rename the card when this version finishes. Omit display_name and a changed page title (the HTML
title, or the first Markdown heading) becomes the card title. The artifact id and slug stay the same.
Older versions keep the format they were published with.
The page runs the same safety pipeline as model output: size caps, secret scan, private-link checks,
the sanitizer, the renderer for format (including PDF, DOCX, and XLSX), and the final sweep. A rejected
page ends as failed, and error names the check that failed. publish does not create a share link.
It counts toward the per-token build quota and requires the publish permission.
summary and assumptions are optional card fields. Each is rejected when the call arrives if it is
longer than the metadata cap (the same 'metadata exceeds N characters' error the job would report).
They are also scanned like model output. On markdown, style changes the opened page. On HTML, style
is recorded and the submitted page is not restyled.| Name | Required | Description | Default |
|---|---|---|---|
| html | No | ||
| note | No | ||
| slug | No | ||
| style | No | ||
| format | No | ||
| summary | No | ||
| markdown | No | ||
| workspace | No | ||
| artifact_id | No | ||
| assumptions | No | ||
| base_version | No | ||
| display_name | No | ||
| idempotency_key | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so thoroughly: the complete safety pipeline (size caps, secret scan, private-link checks, sanitizer, renderer, final sweep), rejected pages ending as failed with the failing check in error, quota and permission requirements, that no share link is created, and the note/summary/assumptions rejection and scanning behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and return values are correctly front-loaded, but the opening two sentences are redundant and the middle is a dense, unevenly chunked run of paragraph fragments ('Want a styled page?', format-omission rules, style-on-HTML rules) that takes real effort to parse. Length is partly justified by 13 parameters, but the organization costs readability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 13-parameter, annotation-free mutation tool this is close to complete: format/style interaction rules, new-vs-new-version requirements, permission/quota/failure behavior, and the follow-up status+inspect loop are all present. The output schema covers returns, and the remaining gap is the undocumented workspace and idempotency_key parameters.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate, and it largely does: format values (html/markdown/pdf/docx/xlsx), style enum (house, bold, editorial, playful, terminal, swiss), html vs markdown field roles, artifact_id/base_version, display_name and slug. It leaves `workspace` and `idempotency_key` entirely unexplained, which matters for a publish call that counts against a quota.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
It states a specific verb and resource ('Store a page') and immediately scopes it ('you have already finished', 'No model call'), which cleanly separates it from the model-backed create/edit path. It also references `create` for slug/display_name rules, confirming which sibling it is parallel to rather than duplicating.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage context is strong: when to use it (finished page in hand), the immediate follow-up workflow (call status(job_id, wait=90), then inspect safety_changes), and how to pick format/style. It never states an explicit exclusion or names the alternative to reach for when the page is not yet finished, so route selection for the main purpose is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
revoke_previewsAInspect
Expire private /p/ preview links for an artifact (or one version). Token revocation and signing-key rotation do not expire these database-backed capabilities; call this explicitly.
| Name | Required | Description | Default |
|---|---|---|---|
| version | No | ||
| artifact_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It usefully discloses that the links are database-backed capabilities unaffected by token/key rotation, which is non-obvious. But it omits whether the expiry is reversible, what permissions are required, and what the operation returns (the output schema partly covers the latter).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no filler, and the core action is front-loaded before the clarifying note about token revocation. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter mutation tool with an output schema already present, the description covers purpose, the non-obvious behavioral caveat, and the scope distinction adequately. Return-value explanation is correctly delegated to the output schema; only permission/irreversibility details are missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for two parameters, so the description must compensate. The phrase 'for an artifact (or one version)' implicitly conveys that version narrows the scope to a single version while omitting it targets the whole artifact, which is meaningful. It adds no format or identifier details for artifact_id.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a precise verb+resource: expiring private /p/ preview links for an artifact or a single version. That is far more specific than the sibling verbs (share, unshare, delete) and an agent can tell what is being acted upon. It stops short of explicitly contrasting itself with unshare/delete, which would be needed for a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives a clear conditional reason to invoke: token revocation and signing-key rotation do NOT expire these links, so call this explicitly. That is actionable context for when the tool is necessary. It does not, however, state when NOT to use it or name the alternative tool for related revocation needs.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
statusBInspect
Build state (queued, building, done, failed, or needs_input). needs_input includes a missing message. Also returns progress and the presentation card. wait (0 to 90 seconds) holds the call open until the build finishes.
| Name | Required | Description | Default |
|---|---|---|---|
| wait | No | ||
| job_id | No | ||
| artifact_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden and does disclose meaningful behavior: the enumerable states, what needs_input means, that progress and a presentation card are returned, and that wait blocks up to 90 seconds. It omits any auth/permission context and, critically, gives no indication of what happens if neither or both identifiers are supplied.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences with no filler, front-loaded with the returned state values before moving to the blocking behavior of wait. Every sentence carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value detail is not strictly required, and the description still adds useful state semantics. However, for a tool with three parameters, 0% schema coverage, and no annotations, the absence of any job_id/artifact_id guidance leaves a real gap an agent must guess around.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for all three parameters. It explains wait well (range and blocking semantics), but job_id and artifact_id are never mentioned, leaving the agent with no guidance on which identifier to pass or whether they are mutually exclusive.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States the specific resource (build state) and enumerates the exact states it can return, which lets an agent recognize this as a status-check tool. It does not explicitly differentiate itself from siblings like inspect or list_artifacts, but the purpose is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit when-to-use or alternative is named. The note that 'wait (0 to 90 seconds) holds the call open until the build finishes' implies the poll-versus-block choice, but the agent must infer when polling is appropriate versus blocking, and no sibling (e.g. inspect) is offered as an alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
11 tool updates
v0.1.0- First observed
create - First observed
delete - First observed
edit - First observed
export - First observed
inspect - First observed
list_artifacts - First observed
publish - First observed
revoke_previews - First observed
share - First observed
status - First observed
unshare
TDQS
Scored across 11 tools
The three authoring tools are clearly separated by who writes the content (create = model builds, publish = you supply finished page, edit = new version from base_version), and status vs inspect vs list_artifacts have distinct roles. Minor overlap remains between create/publish (both accept format/style/html and queue jobs) and between unshare/revoke_previews (both revoke link types), but descriptions disambiguate them.
Mostly clean lowercase verbs (create, edit, publish, inspect, export, share, unshare, delete) with a couple of verb_noun names (list_artifacts, revoke_previews) and one noun (status). The pattern is readable and predictable despite the minor mix of single-verb and verb_noun forms.
Eleven tools is well-scoped for an artifact lifecycle server. Every tool maps to a distinct stage (create/edit/publish/status/list/inspect/export/share/unshare/revoke_previews/delete) with no redundant entries.
Full lifecycle coverage: creation (create/publish), versioning (edit), monitoring (status), discovery (list_artifacts), inspection (inspect), distribution (export/share), revocation (unshare/revoke_previews), and cleanup (delete). No obvious dead ends for the stated artifact-hosting purpose.
Maintenance
Related MCP Connectors
One MCP tool that turns an agent's report or doc into a clean public shareable web page. No signup.
Publish AI-made web pages and share files as short links from any MCP client.
Publish and share access-controlled Markdown documents from any MCP-enabled AI tool.
Publishes what an AI made — a page, a markdown or PDF doc, or a whole site — to a live HTTPS link. Runs locally over stdio from the @drop2run/mcp npm package, or hosted at https://mcp.dropto.run/mcp for chats that connect by URL.
Related MCP Servers
- AlicenseAqualityNot gradedmaintenanceAn MCP server that enables AI agents to instantly convert Markdown into beautiful, ready-to-deliver Word, PDF, HTML with sidebars, and Slideshow documents, bridging the "last mile" of AI content generation.728 npm1-
- AlicenseAqualityAmaintenanceAn MCP server that lets AI publish markdown documents to a clean, themed web UI. One tool call creates a shareable doc.104MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for information collection, text analysis, and content generation, enabling AI agents to scrape web pages, analyze text, and generate reports or presentations.29 npmMIT
- AlicenseAqualityDmaintenanceMCP server for Word document (.docx) creation and manipulation — the production-grade document automation tool for AI agents.933 npmMIT