sitedrop
Publishes static sites to Cloudflare's edge network using Workers, R2 storage, and D1 database, providing instant atomic deployments and live URLs.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@sitedropPublish the static site in ./dist and give me the live URL"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
sitedrop
English · 简体中文
Self-hosted static site publishing for AI coding agents. One Cloudflare Worker, one HTTP call, a live URL:
POST /api/sites → https://sitedrop.you.workers.dev/amber-canyon/Your agent (Claude Code, Cursor, Codex, …) publishes the page it just built, gets a URL back, and shows it to you. No build step, no git push, no wait.
Everything runs in your Cloudflare account: the Worker, the D1 database and the R2 bucket. There is no sitedrop service and no account to sign up for.
One MCP tool.
publish, nothing else to learn.File bytes never pass through the model. The tool returns a signed curl command; the agent runs it and the bytes go straight from disk to R2.
Instant and atomic. Publishing is a single pointer flip — visitors see the old version or the new one, never a half-uploaded site.
Free plan first. Designed inside 10 ms CPU and 50 subrequests per request.
~2,500 lines, one runtime dependency (
fflate, for zip).
Deploy
Or from a clone:
npm install
npx wrangler secret put ADMIN_TOKEN # openssl rand -hex 32
npx wrangler secret put SESSION_SECRET # openssl rand -hex 32
npx wrangler deployThe D1 database and R2 bucket are declared without ids, so wrangler deploy
provisions them on first run. Tables are created on the first request — there
is no migration step.
Then open https://<your-worker>/admin, sign in with ADMIN_TOKEN, and create
a token for each agent.
Note on R2: Cloudflare's free R2 tier generally requires a payment card on file before the bucket can be created, even though nothing is charged at these volumes. This is not documented by Cloudflare but is consistently reported. D1 and Workers have no such requirement.
Custom domain
Point a hostname you own at the Worker from the Cloudflare dashboard —
Workers & Pages → sitedrop → Settings → Domains & Routes → Add → Custom
domain — which also creates the DNS record and issues the certificate. This
is deliberately not in wrangler.jsonc: a hostname belongs to one deployment,
and hardcoding it would break every fork and the Deploy button. Custom domains
live on the Worker rather than in the config, so later wrangler deploy runs
leave them alone.
With a custom domain, also pin the canonical origin:
npx wrangler secret put BASE_URL # https://sitedrop.example.comA secret rather than a vars entry for the same reason — and because
wrangler deploy replaces the whole vars block on every deploy, while
secrets persist. See Security model for what it buys.
Continuous deployment
.github/workflows/deploy.yml deploys main once the tests pass. It stays
inert until the repository has the secrets below, so forks are unaffected: they
run the tests and skip the deploy step.
To enable it on your own copy, create a Cloudflare API token (My Profile → API Tokens → Create Token → Custom token) with:
Scope | Permission | Level |
Account | Workers Scripts | Edit |
Account | D1 | Edit |
Account | Workers R2 Storage | Edit |
Account | Account Settings | Read |
Then add it under Settings → Secrets and variables → Actions:
CLOUDFLARE_API_TOKEN— required.CLOUDFLARE_ACCOUNT_ID— only if the token can see more than one account.
No zone permissions are needed: the token only updates the Worker, and the custom domain above is already attached to it.
Related MCP server: G4 Data Model MCP Server
Connect an agent
The admin UI prints a ready-made snippet for each client when you create a token. For Claude Code:
claude mcp add --transport http sitedrop https://<your-worker>/mcp \
--header "Authorization: Bearer sd_..."For clients that cannot send custom headers, the token can go in the path
instead: https://<your-worker>/mcp/sd_.... It is more leak-prone (URLs end up
in logs and config files), so prefer the header.
What the agent sees
publish takes metadata only — slug, title, password, spa — and returns
a short-lived upload URL plus the exact commands to run:
Site will be live at: https://sitedrop.you.workers.dev/amber-canyon/
Upload within 15 minutes by running ONE of these in the shell:
# a single HTML file:
curl -fsS -H 'Content-Type: text/html' --data-binary @index.html 'https://.../api/upload/sdt_…'
# a whole directory (needs the zip binary):
(cd DIR && zip -qr - .) | curl -fsS -H 'Content-Type: application/zip' --data-binary @- 'https://.../api/upload/sdt_…'
# large sites, more than 40 files, or no zip binary:
curl -fsSL https://.../cli -o /tmp/sitedrop && sh /tmp/sitedrop publish DIR --ticket 'sdt_…'The ticket is an HMAC-signed, single-slug, 15-minute credential. Nothing is stored server-side for it, and the long-lived agent token never appears in the transcript.
For a small page the agent already holds in memory, publish(html: "…") skips
the round trip and returns the live URL immediately.
CLI
The Worker serves its own CLI with the deployment URL baked in — POSIX sh and
curl, nothing else:
curl -fsSL https://<your-worker>/cli -o sitedrop
export SITEDROP_TOKEN=sd_...
sh sitedrop publish ./dist # directory: uploaded file by file
sh sitedrop publish page.html # single page
sh sitedrop list
sh sitedrop password my-site hunter2 # or --remove
sh sitedrop rollback my-site
sh sitedrop delete my-siteIt prints the URL on stdout and everything else on stderr, so
URL=$(sh sitedrop publish ./dist) just works. It is never interactive: a
missing argument is an error, not a prompt. Publishing a directory writes
.sitedrop.json next to it so the next publish updates the same URL.
REST
Full schema at /openapi.json. The essentials:
# single page
curl -H "Authorization: Bearer $SITEDROP_TOKEN" -H 'Content-Type: text/html' \
--data-binary @index.html "$BASE/api/sites?slug=my-site"
# a zip
(cd dist && zip -qr - .) | curl -H "Authorization: Bearer $SITEDROP_TOKEN" \
-H 'Content-Type: application/zip' --data-binary @- "$BASE/api/sites?slug=my-site"
# JSON envelope
curl -H "Authorization: Bearer $SITEDROP_TOKEN" -H 'Content-Type: application/json' \
-d '{"slug":"my-site","files":[{"path":"index.html","content":"<h1>hi</h1>"}]}' \
"$BASE/api/sites"Options travel as query parameters (slug, title, spa, indexable,
sandbox), as JSON body fields, or — for passwords — in the
X-Sitedrop-Password header. An empty password removes it; omitting it leaves
it unchanged.
Sites larger than one request go through the batch endpoints
(POST /api/sites/:slug/versions → PUT …/files/<path> → POST …/finalize),
which is what the CLI uses for directories. There is no size limit on that path.
How it works
Worker ── D1 ── sites / versions / tokens (metadata, pointer flip)
└──── R2 ── sites/<site>/<version>/<path> (one object per file)Publishing writes every R2 object first, then runs one
db.batch()that demotes the old version, promotes the new one and repoints the site. Readers therefore never observe a partial publish. Concurrent publishes to the same slug both succeed; one of them wins the pointer.Versions are immutable prefixes in R2. The last three are kept, so rollback is another pointer flip and never re-uploads anything. Older versions are deleted in
waitUntilusing the manifest stored on the version row.Serving costs two indexed row reads (slug lookup + version join) and one R2
getwith the request's conditional headers, soIf-None-Matchbecomes a 304 without transferring the body.No KV, no Durable Objects. KV's negative caching would make a publish-then-fetch 404 for up to a minute; D1 and R2 are strongly consistent.
Caching, and what it actually saves
Cloudflare's cache sits in front of the Worker (cache: { enabled: true }),
so a hit is answered at the edge without invoking it at all — no CPU, no D1 row
read, no R2 GET. Public site responses are written to make use of that:
Response |
|
HTML |
|
assets |
|
password-protected |
|
404s, redirects, the gate, all of |
|
Browsers keep revalidating HTML on every navigation, so a publish is visible on
the next reload, while every other visitor is served from the edge. What makes
the long shared TTL safe is that publish, rollback, option changes and delete all
purge the site's cache tag. Tune it with EDGE_CACHE_SECONDS; 0 sends every
request to the Worker.
It does not reduce your request count. Cloudflare is explicit: "all requests to your Worker are billed at the standard Workers request rate — the same per-rate as any other request to your Worker — whether the response comes from cache or from your Worker." Caching cuts CPU time, D1 rows read, R2 Class B operations and latency. It does not lower the free plan's 100,000 requests/day — if you are hitting that ceiling, the $5/month paid plan (10M requests included) is the lever, not the cache.
The free plan allows 5 purges/minute (bucket of 25), which is ample for one purge
per publish but not for a runaway republish loop. A rejected purge is logged
rather than raised — a publish must not fail over the cache — so the worst case
is a site serving its previous version for up to EDGE_CACHE_SECONDS. Deploying
the Worker also clears everything, because the Worker version is part of the
cache key unless you turn on cross_version_cache.
Security model
sitedrop is single-tenant: every valid agent token may publish, update or delete any site. Tokens are for handing to your own agents, not for sharing.
Tokens are stored as SHA-256 hashes; the plaintext is shown once at creation.
last_used_atis recorded (throttled to one write per hour).The admin UI never sets a cookie — it holds the admin token in
sessionStorageand sends it as a header — so nothing on this origin can ride an ambient admin session.Site passwords gate previews, they are not accounts. They are hashed with HMAC-SHA256 under a server-side key rather than a real KDF, because the free plan's 10 ms CPU budget cannot run PBKDF2/scrypt at a useful cost factor. A database leak alone does not allow offline cracking (the key never leaves the Worker), but do not reuse a password that matters.
The unlock cookie is bound to the current password hash: changing the password signs out everyone immediately.
A protected site's subresources are refused when the
Referersays the request came from a different path on this deployment, which stops another page on the same origin from riding the unlock cookie. Requests with noRefererare allowed — that is a deliberate, documented gap.All sites share one origin.
localStorage,sessionStorageand cookie jars are per-origin, so a page published at/a/can read what a page at/b/stored. Do not host untrusted code next to something sensitive; turn on the per-site sandbox CSP (admin UI → Options) for pages you did not write.Uploaded paths are rejected for traversal (including percent-encoded), backslashes, control characters and padded segments. Executables (
.exe,.sh,.dll, …) are skipped with a warning.Zip archives are checked against their central directory before anything is inflated, so a zip bomb is rejected without running the decompressor.
Every site response carries
X-Robots-Tag: noindexunless the site opts in, and/robots.txtdisallows everything.
Limits
Defaults, all configurable in wrangler.jsonc:
Variable | Default | Meaning |
| 3600 | edge TTL for public sites; |
| 40 | files in one request (zip or JSON) |
| 2 MB | archive size |
| 5 MB | one file |
| 25 MB | whole site |
| 3 | versions kept for rollback |
These keep a single request inside the free plan's 10 ms CPU and 50 subrequests. Bigger sites are not blocked — they go through the batch API, which the CLI uses automatically. On a paid plan you can raise all of them.
Cache purges are budgeted (5/minute on the free plan), so sitedrop issues exactly one per publish.
Development
npm install
cp .dev.vars.example .dev.vars # fill in ADMIN_TOKEN and SESSION_SECRET
npm run dev
npm run typecheck
npm test # vitest, runs in workerd via miniflare
./scripts/conformance.sh # MCP conformance suite
ADMIN_TOKEN=… ./scripts/smoke.sh https://<your-worker> # against a real deployMCP implementation notes
The server is a hand-written stateless Streamable HTTP endpoint (~200 lines, no SDK). Deliberate deviations:
never responds with SSE — every reply is one JSON body;
ignores
Mcp-Session-IdandLast-Event-ID, because there is no session;GET/DELETE /mcpreturn 405 (there is no stream to open);401carries noWWW-Authenticate, so clients do not start an OAuth discovery flow this server does not implement;JSON-RPC batching is refused with
-32600, per the 2025-06-18 spec;business failures (bad slug, too many files, …) come back as
isError: truetool results that say whether retrying helps — not as protocol errors.
Set BASE_URL to the deployment's canonical origin to enable strict
DNS-rebinding protection; without it the origin check falls back to the
request's own Host header.
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityCmaintenanceA deployable MCP server on Cloudflare Workers that allows you to create and expose custom tools to AI assistants without requiring authentication.
- Flicense-qualityDmaintenanceEnables deployment of a customizable MCP server on Cloudflare Workers without authentication. Allows users to define custom tools and connect to clients like Claude Desktop or Cloudflare AI Playground for remote tool execution.
- Flicense-qualityDmaintenanceA self-hosted MCP server that enables AI assistants to deploy and manage static websites directly on EdgeOne Pages platform with KV storage support.
- AlicenseAqualityFmaintenanceMCP server for Shipstatic — deploy and manage static sites from AI agents. Works with Claude Code, Cursor, VS Code Copilot, and any MCP-compatible client.154685MIT
Related MCP Connectors
Hosted MCP for creating, checking, deploying, and hosting static sites for AI agents.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
AI agent website builder. Create and publish link-in-bio sites via MCP or REST API.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/t0saki/sitedrop'
If you have feedback or need assistance with the MCP directory API, please join our Discord server