---
name: thin-host-docusaurus
description: Deploy a Docusaurus (or any static-site generator) build to thin.host hosting from Claude Code — first publish, redeploys that prune stale hashed chunks, preview vs custom-domain baseUrl, password protection. Triggers on "deploy my docs", "host this Docusaurus site", "publish the build folder", "put my docs on a custom domain", "redeploy the docs".
---

# Deploy a Docusaurus site to thin.host

thin.host hosts static build output and gives it a live URL, a custom domain with TLS, optional password/email gating, and a 404 page — driven entirely from the thin.host MCP server (`publish_website`, `update_website`) or REST. This skill is the Docusaurus recipe; the same flow works for Astro, Vite, VitePress, Hugo, and Next `output: 'export'` folders.

## Setup

You need a thin.host API key created from the dashboard (Settings → Create API key) so it is linked to the user's account. Keys made elsewhere get `account_not_linked` — surface that, don't retry.

```bash
export THIN_HOST_API_KEY="th_live_your_key"
```

MCP server — pick one:

```bash
# Hosted (zero install; OAuth sign-in, or the key as a bearer header). Publishes from a zip URL / zip upload.
claude mcp add --transport http thin-host https://thin.host/mcp --header "Authorization: Bearer $THIN_HOST_API_KEY"

# Local (reads the build folder directly — no zipping). Needs uv.
claude mcp add thin-host -e THINHOST_API_URL=https://thin.host/v1 -e THIN_HOST_API_KEY=$THIN_HOST_API_KEY \
  -- uvx thinhost-mcp
```

**Which tools you have tells you which mode you are in.** `publish_website` / `update_website` (take a `source_path`) exist only on the local server. On the hosted server you get `publish_website_from_url` / `update_website_from_url` (take a public `source_url` of a .zip) — and from a shell the simplest hosted path is the archive upload below, which needs no public URL at all.

## The one decision: baseUrl

Docusaurus bakes `baseUrl` into every asset URL (`/assets/js/...`). Where the site will be viewed decides the value:

| Where the user will look | `baseUrl` | Notes |
|---|---|---|
| A custom domain (`docs.example.com`) | `/` | The normal case. Preview at `/s/<slug>/` renders unstyled until the domain is mapped — expected. |
| The thin.host preview URL `https://thin.host/s/<slug>/` | `/s/<slug>/` | Pick the slug first and pass it to `publish_website`. |

Make the config switchable so both builds come from one repo:

```ts
// docusaurus.config.ts
url: 'https://docs.example.com',
baseUrl: process.env.DOCUSAURUS_BASE_URL ?? '/',
```

```bash
# custom-domain build (default)
npm run build
# preview build for thin.host's shared origin
DOCUSAURUS_BASE_URL=/s/my-docs/ npx docusaurus build --out-dir build-preview
```

The publish tools detect Docusaurus and report `base_url`. If it will not resolve at the returned URL they add a `warning` — relay it to the user verbatim instead of debugging a "blank" site.

## First publish

1. Build: `npm run build` (output in `build/`). `onBrokenLinks: 'throw'` is your friend — fix link errors before deploying.
2. Publish the build folder (local server):

```
publish_website(
  source_path="/abs/path/to/repo/build",
  title="Example Docs",
  slug="example-docs"          # required if you built with baseUrl '/s/example-docs/'
)
```

Large builds upload in several requests automatically (the response reports `requests`). Expect a few hundred files for a real docs site; that's normal.

   Hosted server with an **API key** in the shell — zip and upload (30MB cap per upload). If the user connected with OAuth there is no key in the shell; use the URL path below instead:

```bash
cd build && zip -qr ../site.zip . && cd ..
curl -sS -X POST https://thin.host/v1/websites/archive -H "Authorization: Bearer $THIN_HOST_API_KEY" \
  -F file=@site.zip -F title="Example Docs" -F slug=example-docs
```

   Hosted server via the tool (works with OAuth): put `site.zip` at a public URL (a GitHub release asset, object storage, any static host) and call `publish_website_from_url(source_url=..., title=..., slug=...)`. Ask the user for the link if you cannot produce one; do not try to base64 the zip through a tool call.

3. Give the user the `url`. If `warning` is present, give them that too.

## Redeploys — always `sync=true`

Every Docusaurus build renames its hashed chunks. Redeploy with `sync=true` so the upload is treated as the whole site and chunks from earlier builds are deleted:

```
update_website(
  website="example-docs",       # slug, id, or live URL
  source_path="/abs/path/to/repo/build",
  sync=True
)
```

`removed_assets` lists what was pruned. Pages the owner created in the dashboard as redirects (`redirect_to`) are never pruned and come back in `kept_redirects`. Page-level password/email protection set in the dashboard survives a redeploy — the page is updated in place, not recreated.

Hosted equivalents: `update_website_from_url(website=..., source_url=...)` (sync defaults to true), or
`curl -X POST https://thin.host/v1/websites/example-docs/archive -H "Authorization: Bearer $THIN_HOST_API_KEY" -F file=@site.zip` (sync defaults to true).

Never call `update_website` without `sync` for a build folder; the site keeps working but every old chunk stays forever.

## Custom domain

Map the domain from the dashboard: Website → Domains → add `docs.example.com`, then create the CNAME it shows. TLS provisions automatically. Once mapped, the site's `/` baseUrl build is the right one; thin.host also serves `robots.txt`, `sitemap.xml`, and canonical tags on the custom domain and 301s any secondary hostname to the primary.

An agent can instead call `provision_domain` and hand the user the `claim_url`.

## Password / email protection

Set in the dashboard on the website (whole site) or on individual pages. The `/404` page should stay unprotected — a gated 404 is not used for misses.

## REST equivalent

Same thing without MCP, for CI:

```bash
# batches of {pages:[{path,html}], assets:[{path,content_base64}]} — ≤20MB, ≤200 pages, ≤500 assets each
curl -X PATCH https://thin.host/v1/websites/example-docs -H "X-Platform-API-Key: $THIN_HOST_API_KEY" -d @batch1.json
# ... then prune with the full manifest
curl -X POST https://thin.host/v1/websites/example-docs/sync -H "X-Platform-API-Key: $THIN_HOST_API_KEY" \
  -d '{"keep": {"pages": ["/", "/docs/intro", "/404"], "assets": ["/assets/js/main.abc.js", "..."]}}'
```

## Errors

| code | what to do |
|---|---|
| `account_not_linked` | The key isn't from the dashboard. Have the user create one at Settings → Create API key. |
| `slug_taken` | Pick another slug (and rebuild if baseUrl embeds it). |
| `invalid_source` | Path missing or no HTML — did the build run? Check `source_path` is the build output dir. |
| `payload_too_large` | A single file over ~15MB. Compress the video/image or leave it out. |
| `empty_manifest` | Sync was called with no pages; don't sync an empty build. |

Partial failures mid-deploy say which request failed (`requests_completed` of `requests_total`); fix the cause and run `update_website` again with `sync=true` — it is idempotent.
