aft.page

Deploy HTML, a folder, or a public GitHub repo to a live HTTPS URL. No account needed; claim the site to keep the same URL across updates.

Hosted MCP Server

npx add-mcp 'https://mcp.aft.page/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

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 to aft.page

The agent should call deploy and return a live URL.

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).

InputTypeRequiredNotes
htmlstringone ofFull document; prefer <!DOCTYPE html>…</html>
filesarrayone ofBuilt static files. Paths relative; UTF-8 or base64.
preferred_slugstringnoHint like about-me. First hit never overwrites — collision gets a suffix. Required with edit_token.
edit_tokenstringnoFrom 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?token=…
slug: …
deploy: dep_…
files: 1
Claim within 24 hours or drop again.

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, the anonymous token is retired: the owner mints one with POST /v1/sites/{slug}/deploy-token while signed in and hands it to the agent (DELETE revokes it).

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 deploy with html or files.
  • Vite/React: npm run build, then dist/ only. Next static export: out/.
  • Pass preferred_slug when sensible. Persist .aft/state.json; later deploys send edit_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?token=…",
  "owned": false,
  "notice": "Claim within 24 hours or drop again."
}

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

URLBehavior
https://{slug}.aft.page/Primary — index.html
https://{slug}.aft.page/{path}Static asset

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

  1. Prefer valid ?slug= / preferred_slug when provided.
  2. Else allocate a random unique slug.
  3. 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.
  4. First POST never overwrites — append a short word suffix on collision. PATCH with edit_token updates that slug in place.
  5. Reserved: www, api, app, admin, docs, status, …

Claim / manage

https://aft.page/claim?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 — first one free). Keep editToken to prove you deployed.

Limits (today)

LimitValue
Files per deploy500
Per-file size25 MB
Total payload100 MB
AuthNone (anonymous)
Unclaimed retentionDeleted after 30d idle. Visit, PATCH, or claim to keep.
OverwriteNever
Custom domains / private inviteAfter claim · first one free (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

  1. Prefer MCP deploy (html or files).
  2. Return the live URL.
  3. Optionally link the preview shell.
  4. Do not invent auth steps that do not exist yet.

Troubleshoot

SymptomFix
MCP tools missingReload host; check absolute path; Node 20+
reserved_slugChange title / slug
Size errorsStay under 25 MB / file, 100 MB total
Unstyled CSSEnsure path ends in .css; re-deploy
Site 404Wait for DNS; confirm the slug