MCP Tool Reference
The thin.host MCP server exposes 9 tools — 3 for website hosting, 5 for domain provisioning, and send_email for transactional email. All tool descriptions are written for agent reasoning — status fields are enums, not prose.
Connecting
Same tools, three transports — see the MCP quickstart for the commands:
| Transport | Where | Auth | Publish path |
|---|---|---|---|
| Hosted (Streamable HTTP) | https://thin.host/mcp | Bearer API key, or OAuth (PKCE; discovery at /.well-known/oauth-authorization-server) | publish_website_from_url / update_website_from_url, or POST /v1/websites/archive |
Local (stdio) via uvx | your machine | THIN_HOST_API_KEY env | publish_website / update_website read local folders directly |
| Claude Desktop bundle | your machine | key embedded at download | as local |
The hosted endpoint is stateless JSON-RPC over POST (no sessions, no SSE). publish_website and update_website are hidden there because "local" would mean the server's disk.
publish_website
Local server only.
Publish a local HTML file, directory, ZIP, or static build folder (a Docusaurus build/, Astro/Vite dist/, Next out/) as a live hosted website. Use right after generating a site so the user gets a shareable URL instead of loose files. HTML files become pages (index.html → /, about.html → /about, docs/intro/index.html → /docs/intro, 404.html → the site's not-found page); css/js/images/fonts become assets served relative to the site root. Big builds are uploaded in several requests automatically. Docusaurus builds are detected and their base_url reported — a warning comes back when it will not resolve at the returned URL (see Docusaurus & static builds). Precondition: the API key must be created from the thin.host dashboard so it is linked to an account — an unlinked key gets account_not_linked.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
source_path | string | yes | Absolute path to an .html file, a site/build directory, or a .zip |
title | string | yes | Site title (shown in the dashboard) |
slug | string | no | URL slug (site lives at /s/<slug>/); auto-generated if omitted |
Returns
{
"url": "https://thin.host/s/my-site/",
"id": "uuid",
"slug": "my-site",
"title": "My Site",
"page_count": 2, "asset_count": 1,
"pages": ["/", "/about"],
"assets": ["/css/style.css"],
"requests": 1,
"skipped_files": [],
"framework": "docusaurus", "base_url": "/",
"warning": "This Docusaurus build uses baseUrl '/' … (only when it will not resolve at url)",
"next_action": "The site is live at ... — share that URL."
}
update_website
Local server only.
Push updated pages/assets to an existing hosted site (upsert by path). Local files replace the same paths on the site. With sync=false (default) site files not in the upload are left untouched; with sync=true the upload is the whole site and everything else is deleted afterwards — use it whenever you redeploy a generator build folder, or each build's renamed chunks accumulate. Redirect pages authored in the dashboard are never pruned (kept_redirects), and page-level protection survives because pages are updated in place. See Docusaurus & static builds.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
website | string | yes | The site's id, slug, or live URL |
source_path | string | yes | Absolute path to an .html file, a site directory, or a .zip |
title | string | no | New site title |
sync | boolean | no | Treat the upload as the complete site and remove everything else (default false) |
Returns
{
"url": "https://thin.host/s/my-docs/",
"updated_page_count": 277, "updated_asset_count": 400,
"updated_pages": ["/", "/404", "/docs/intro", "… 274 more"],
"removed_assets": ["/assets/js/main.0a1b2c3d.js"], "removed_pages": [], "kept_redirects": [],
"requests": 5,
"framework": "docusaurus", "base_url": "/"
}
publish_website_from_url
Publish a site from a public URL — a .zip of the site or build folder (cd build && zip -r ../site.zip .), or a single .html page. Everything else matches publish_website: page/asset mapping, batching, Docusaurus baseUrl detection and its warning. Private, loopback and cloud-metadata addresses are refused; the limit is 150MB.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
source_url | string | yes | Public http(s) URL of a .zip archive or an .html file |
title | string | yes | Site title |
slug | string | no | URL slug (site lives at /s/<slug>/) |
Returns
Same shape as publish_website.
update_website_from_url
Redeploy an existing site from a URL. sync defaults to true here — a zipped build is the whole site — so files the archive does not contain are removed afterwards (dashboard redirect pages are kept). Pass sync=false to upsert only.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
website | string | yes | The site's id, slug, or live URL |
source_url | string | yes | Public http(s) URL of a .zip archive or an .html file |
title | string | no | New site title |
sync | boolean | no | Treat the archive as the whole site (default true) |
Returns
Same shape as update_website.
list_websites
List the account's hosted websites with their live URLs, newest first.
Parameters
| Name | Type | Required | Default |
|---|---|---|---|
limit | int | no | 50 |
Returns
{
"websites": [ { "slug": "my-site", "url": "https://thin.host/s/my-site/", "page_count": 2, "custom_domains": [] } ],
"total": 1
}
provision_domain
Provision a new custom domain for an AI-generated site or app.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
domain | string | yes | Custom domain (e.g. "mysite.com") |
origin_url | string | yes | Current URL where the site is hosted |
user_email | string | yes | End user's email for billing |
idempotency_key | string | no | Prevent duplicate provisions on retry |
Returns
{
"provision_id": "uuid",
"status": "pending_payment",
"dns_instructions": {
"type": "CNAME",
"name": "mysite.com",
"value": "cname.thin.host",
"human_readable": "Set a CNAME record for mysite.com pointing to cname.thin.host"
},
"claim_url": "https://thin.host/claim/abc123...",
"expected_completion_seconds": 300
}
check_domain_status
Check current provisioning status. Call this to poll until ready: true.
Parameters
| Name | Type | Required |
|---|---|---|
provision_id | string | yes |
Returns
{
"status": "active",
"dns_status": "resolved",
"ssl_status": "active",
"payment_status": "paid",
"ready": true
}
update_origin
Change where a domain points to. Use when the user redeploys to a new URL.
Parameters
| Name | Type | Required |
|---|---|---|
provision_id | string | yes |
new_origin_url | string | yes |
Returns
{ "status": "active", "updated_at": "2026-04-25T02:00:00Z" }
release_domain
Tear down a provisioned domain. Not reversible.
Parameters
| Name | Type | Required |
|---|---|---|
provision_id | string | yes |
Returns
{ "status": "released", "released_at": "2026-04-25T03:00:00Z" }
list_domains
List all domains provisioned through your platform.
Parameters
| Name | Type | Required | Default |
|---|---|---|---|
status_filter | string | no | all |
limit | int | no | 50 |
Returns
{
"domains": [ { "id": "...", "hostname": "...", "status": "...", "ready": true }, ... ],
"total": 42,
"has_more": false
}
send_email
Send a single transactional notification email (sign-in link, receipt, alert). Precondition: the account must be allowlisted for email and the from address must use a verified sending domain — omit from to use the default verified sender. Unapproved accounts get email_not_enabled; a bad sender gets invalid_from_domain. Surface those errors to the user instead of retrying. Transactional only — not for marketing or bulk.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
to | string | yes | Recipient email address |
subject | string | yes | Subject line |
text | string | one of text/html | Plain-text body |
html | string | one of text/html | HTML body |
from_address | string | no | Sender on a verified domain; omit for the default |
reply_to | string | no | Reply-To header |
Returns
{
"id": "<provider-message-id>",
"status": "sent",
"to": "user@example.com",
"from_address": "no-reply@mail.thin.host"
}
Common failure error_codes: email_not_enabled, invalid_from_domain, recipient_suppressed (prior bounce/complaint — do not retry), rate_limited (retryable after the window resets), invalid_to, missing_subject, empty_body. There is also a drop-in Claude skill wrapping this tool + the REST endpoint.
Error Format
All tools return structured errors when something goes wrong:
{
"error_code": "domain_taken",
"error_message": "The domain mysite.com is already provisioned",
"agent_should_retry": false,
"suggested_next_tool_call": "Choose a different domain"
}