What it is
aft.page hosts small software — one-pagers, prototypes, agent-built HTML, static SPAs — at a shareable HTTPS URL in seconds. The name comes from the aft of a ship: the back of the vessel — take what you built and put it out into the world.
Prefer the MCP tools below. If MCP is unavailable, call the API directly.
Quick start
Thin remote MCP — one deploy tool (html or files),
plus aft_deploys, aft_rollback,
aft_health. No account. Point your host at:
https://mcp.aft.page/mcp
MCP host config (preferred)
Add a server named aft-page with the remote URL
(Cursor, Claude Desktop, and other Streamable HTTP clients):
{
"mcpServers": {
"aft-page": {
"url": "https://mcp.aft.page/mcp"
}
}
}
Reload the host, then ask:
Deploy this HTML to aft.page
The agent should call deploy and return a live
URL.
Local / advanced (stdio)
Optional. Clone the repo and run
apps/mcp over stdio when you need an offline or
patched server. Most users should use the remote URL above.
cd apps/mcp
npm install
npm start
{
"mcpServers": {
"aft-page": {
"command": "npx",
"args": [
"tsx",
"/ABSOLUTE/PATH/TO/aft.page/apps/mcp/src/index.ts"
]
}
}
}
Tools
aft.page does not build your app. Detect first: plain HTML →
upload those files; Vite/React → npm run build then
dist/ only; Next static export → out/.
Never upload src/ or node_modules.
deploy
One tool. Pass html or files —
not both. Goes live immediately. One-file
pages use html; multi-file / SPA build output uses
files (always include index.html).
| Input | Type | Required | Notes |
|---|---|---|---|
html |
string | one of | Full document; prefer <!DOCTYPE html>…</html> |
files |
array | one of | Built static files. Paths relative; UTF-8 or base64. |
preferred_slug |
string | no |
Hint like about-me. First hit never
overwrites — collision gets a suffix. Required with
edit_token.
|
edit_token |
string | no |
From first deploy / .aft/state.json. When
set, MCP PATCHes this slug — same URL, new rollback row.
|
Returns text + structured content:
Live: https://{slug}.aft.page
Claim: https://aft.page/claim?slug=…&token=…
slug: …
deploy: dep_…
files: 1
Unclaimed sites are deleted after 30 days idle. Visit, update, or claim to keep.
Limits: 500 files · 25 MB each · 100 MB total. Paths: no
.., no leading /, no
\.
aft_deploys
List rollback-able deploys for a slug. Needs
edit_token from .aft/state.json. Same
D1 history as the project UI after claim.
aft_rollback
Point the live slug at a prior deploy_id. Same URL.
Unclaimed: edit_token. After claim, token is dead —
session owner/editor only. MCP has no session yet.
aft_health
Ping the deploy API. No inputs. Returns
ok and the API base.
Prompt: deploy_to_aft
Optional prompt that steers the agent:
- Detect first: plain HTML vs JS app (Vite/React/Next).
- Call
deploywithhtmlorfiles. - Vite/React:
npm run build, thendist/only. Next static export:out/. - Pass
preferred_slugwhen sensible. Persist.aft/state.json; later deploys sendedit_token. - Return the live HTTPS URL as the deliverable.
- Do not invent account or repository setup steps.
Arg: html_or_files (optional context string).
API (no MCP)
Base: https://api.aft.page.
CORS: * on deploy + site GET.
GET /health
{ "ok": true }
POST /v1/deploy
Query: optional ?slug=preferred-name. Optional header
X-Aft-Client: mcp|web|extension|curl|cli|mac (metrics).
Three body
shapes:
A) Raw HTML
POST /v1/deploy?slug=hello
Content-Type: text/html; charset=utf-8
<!DOCTYPE html><html><body><h1>Hi</h1></body></html>
B) JSON files (agents)
POST /v1/deploy
Content-Type: application/json
{
"files": [
{ "path": "index.html", "content": "<h1>App</h1>" },
{ "path": "style.css", "content": "body{margin:0}" }
]
}
C) Multipart — fields
file0 + file0_path, …
Success (200)
{
"ok": true,
"slug": "hello",
"deployId": "dep_…",
"url": "https://hello.aft.page",
"files": 1,
"bytes": 42,
"editToken": "aft_edit_…",
"claimUrl": "https://aft.page/claim?slug=hello&token=…",
"owned": false,
"notice": "Unclaimed sites are deleted after 30 days idle. Visit, update, or claim to keep."
}
Errors include no_files,
too_many_files, file_too_large,
payload_too_large, bad_path,
reserved_slug, slug_exhausted,
internal. Invalid ?slug= patterns are
ignored (random unique slug); only reserved names return
reserved_slug.
Serving
| URL | Behavior |
|---|---|
https://{slug}.aft.page/ |
Primary — index.html |
https://{slug}.aft.page/{path} |
Static asset |
https://api.aft.page/s/{slug}/ |
Path fallback |
If the requested object is missing, the Worker soft-falls back to
that deploy’s index.html (SPA-style). Content-Type
follows the file extension when the upload type is missing or
application/octet-stream (so CSS is
text/css).
Slugs
- Prefer valid
?slug=/preferred_slugwhen provided. - Else allocate a random unique slug.
-
Clients (paste UI, extension, agents) may derive a hint from
<title>/<h1>and pass it as?slug=— the Worker does not parse HTML for a name. -
First POST never overwrites — append a short
word suffix on collision. PATCH with
edit_tokenupdates that slug in place. -
Reserved:
www,api,app,admin,docs,status, …
Claim / manage
https://aft.page/claim?slug=…&token=…
The live URL is the product. After deploy, open
claimUrl to add email or Google. Unclaimed sites also
show Claim on the live slug. After claim, Manage opens the project
page (invite, rollback, secrets, custom domains — request access).
Keep editToken to prove you deployed.
Limits (today)
| Limit | Value |
|---|---|
| Files per deploy | 500 |
| Per-file size | 25 MB |
| Total payload | 100 MB |
| Auth | None (anonymous) |
| Unclaimed retention | Deleted after 30d idle. Visit, PATCH, or claim to keep. |
| Overwrite | Never |
| Custom domains / private invite | After claim · request access (Domains tab) |
Examples
Minimal HTML via curl
curl -sS -X POST "https://api.aft.page/v1/deploy?slug=curl-smoke" \
-H "content-type: text/html; charset=utf-8" \
--data-binary @index.html
Agent rule of thumb
- Prefer MCP
deploy(htmlorfiles). - Return the live URL.
- Optionally link the preview shell.
- Do not invent auth steps that do not exist yet.
Troubleshoot
| Symptom | Fix |
|---|---|
| MCP tools missing | Reload host; check absolute path; Node 20+ |
reserved_slug |
Change title / slug |
| Size errors | Stay under 25 MB / file, 100 MB total |
| Unstyled CSS | Ensure path ends in .css; re-deploy |
| Site 404 | Wait for DNS; try /s/{slug}/ on API host |