hstd

Your AI, hosting your site.

hstd speaks MCP, the protocol Claude, Cursor and most AI tools use to reach outside services. Connect once and your assistant can create sites, edit them as drafts, show you a preview, and publish when you say so. This page explains every tool it gets, in plain words. It is written for people and for the assistants themselves.

Connect

Add this address as a custom connector or MCP server in your AI tool. The first time it connects you sign in with your hstd account and approve it. That is the whole setup: no keys to copy, nothing to install.

The assistant works inside your current team. Switch teams in hstd and it follows. What it may do follows your role: members can deploy, admins and owners can also create sites, and owners handle billing. A team can have more than one owner. This is separate from the hstd platform admin.

MCP server
https://hstd.app/mcp

How publishing works

Draft

Every edit the assistant makes goes into a draft: a copy of the live version with the changes applied. The live site does not move.

Preview

Each version, drafts included, has its own address such as mysite--v14.hstd.app. The assistant shows it to you; look before anyone else does.

Publish

One call flips the live pointer to the draft. Roll back by publishing an older version. The assistant is told to do this only after you say yes.

The tools

Sites are named by their slug, the part before .hstd.app. Every tool that touches a site takes that slug.

Tool What it does When to use it
list-sites List your sites with their live version and draft (if any). Start here. It tells you which sites exist, which version is live and whether a draft is waiting.
create-site Create a new, empty site. Then add files with write-files and put it live with publish. Only when the user wants a new site. Pick a slug they will recognise; it becomes the address.
list-files List the files in a site. Defaults to the draft when there is one, otherwise the live version. Before editing, to see what is there. You get the draft if one exists, so you always see your own changes.
read-file Read one file from a site. Text comes back as-is, binaries as {"encoding": "base64", "content": ...}. Defaults to the draft when there is one, otherwise the live version. To read one file before changing it. Binaries come back as base64.
write-files Create or overwrite files in the site's draft. Text as-is, binaries (images, fonts) as base64 with encoding "base64". Nothing goes live: the reply has a preview URL to show the user. Call publish when they approve. Small edits and new text files. Send whole files, not diffs. Use base64 for images and fonts. Pass fresh: true to start a site from scratch.
delete-files Delete files or folders from the site's draft. The live version is untouched until publish. Remove files or folders from the draft. Nothing changes on the live site until publish.
list-versions List a site's versions, newest first, each with its status (live, draft, published) and preview URL. To find a version to roll back to, or to show the user the history.
publish Make a version live. Defaults to the draft; pass an older version number to roll back. Only call this after the user has approved. Only after the user has seen the preview and said yes. Without a version it publishes the draft; with an older version number it rolls back.
discard-draft Throw away the site's draft. The live version is untouched. When the user wants to throw the current draft away and start again from the live version.
import-upload Import a zip that was posted to POST /uploads (see https://hstd.app/docs) as the site's new draft. Use this for built bundles and anything too big to send through write-files. The reply has a preview URL; call publish when the user approves. Built bundles and anything big or binary. Zip the folder, post it to /uploads first, then import the token. It replaces the draft.

Big sites: upload a zip first

Tool calls carry text, so a 250 KB JavaScript bundle or a folder of images does not fit through write-files. Instead, zip the folder and post it to /uploads. No sign-in is needed for that step, and the reply is a token that works for one hour. Then call import-upload with the token: the zip becomes the site's draft, with a preview address, and publishing stays a separate step.

Zips need an index.html at the root, or inside a single top-level folder. Up to 250 MB and 10,000 files; your plan's own limits apply when the draft is created.

Terminal
cd dist && zip -r ../site.zip . && cd ..
curl -F [email protected] https://hstd.app/uploads

{"upload":"upl_3f9…","size":412880,"expires_at":"…"}

# then, over MCP
import-upload {"site": "mysite", "upload": "upl_3f9…"}
publish       {"site": "mysite"}

Forms without a backend

Any form on a hosted site can post to /_hstd/form/<name> on its own address. hstd stores the fields, emails the team owner (with Reply-To set to the sender when there is an email field) and shows every message under the site's Forms tab.

  • _next: a page on the same site to land on afterwards. Without it, visitors see a small thank-you page.
  • _gotcha: a hidden field people never fill in. If it has a value, the message is dropped silently.
  • Posting with Accept: application/json from fetch() returns {"ok": true} instead of a redirect.
  • Limits: 50 messages a month on Free, 1000 on Pro, unlimited on Studio. Ten posts a minute per visitor. No file uploads.
index.html
<form action="/_hstd/form/contact" method="post">
  <input name="name" placeholder="Your name">
  <input name="email" type="email" placeholder="Your email">
  <textarea name="message"></textarea>
  <input name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
  <input name="_next" type="hidden" value="/thanks.html">
  <button>Send</button>
</form>

Site settings in a file

Put a _config.json at the root of the site and hstd reads it on every request. Four things it can do, all optional.

  • spa: unknown paths serve index.html with a 200, so React, Vue and Svelte apps with client-side routing survive a refresh.
  • notFound: your own 404 page, used when spa is off.
  • redirects: * matches anything and lands in :splat; targets are paths or full https URLs; status 301, 302, 307 or 308.
  • headers: per path pattern, from a safe list (caching, CSP, frame, referrer, permissions, CORS). Later rules win.

A broken file is refused when you upload or publish, with a message that says what to fix. The file itself is never served.

_config.json
{
  "spa": true,
  "notFound": "/404.html",
  "redirects": [
    { "from": "/old/*", "to": "/new/:splat", "status": 301 },
    { "from": "/docs", "to": "https://hstd.app/docs", "status": 302 }
  ],
  "headers": [
    { "path": "/assets/*", "headers": { "Cache-Control": "public, max-age=31536000, immutable" } },
    { "path": "/*", "headers": { "X-Frame-Options": "DENY" } }
  ]
}

Limits by plan

Plan Sites People Space Per upload Custom domains
Free 3 2 50 MB 25 MB No
Pro 25 5 5 GB 100 MB Yes
Studio 100 25 GB 250 MB Yes

If you are an assistant reading this

  • Call list-sites first and use the slug the user means.
  • Edit the draft, then show the preview URL from the reply. Never call publish before the user has said yes.
  • Send whole files. A file that is too big for one call belongs in a zip through /uploads and import-upload.
  • When a tool returns an error, read the message: it says what to change, such as the plan limit or a missing index.html.
  • A site is not live until it has an index.html and has been published once.