---
name: deploy-with-launchcloud
description: >-
  Deploy, publish, or host static web content (HTML/CSS/JS sites, landing pages,
  reports, dashboards, docs, single-page apps) to LaunchCloud using its MCP
  tools. Use this whenever the user asks to deploy, publish, ship, host, or put
  something live on LaunchCloud, to create a LaunchCloud project, to push a new
  version of a site, or to password-protect or gate deployed content. Covers
  creating a project, uploading a deployment as a set of files, access controls,
  and updating a live site. Assumes the LaunchCloud MCP server is already
  connected.
---

# Deploying content with LaunchCloud

LaunchCloud hosts **static web content** behind an optional password or SSO. You
interact with it through the LaunchCloud **MCP server**, which exposes a small
set of tools. Content is organized as **projects**; each project has a series of
**deployments**, and the most recent deployment is what visitors see.

Every project is **always gated** - it can never be open to the public internet.
A project has **exactly one form of auth**: either a password *or* a single auth
provider, never both and never neither. Setting one clears the other.

This skill assumes the LaunchCloud MCP server is already connected in the
client. If the tools below are not available, tell the user to connect the
server (`https://launchcloud.ai/mcp`) and authorize it, then stop.

## Tool names

The server exposes these tools. Your client may namespace them (e.g.
`launchcloud:create_deployment` or `mcp__launchcloud__create_deployment`) -
match by the base name:

| Tool | Purpose |
| --- | --- |
| `list_projects` | List projects (`page`, `pageSize`, `q` name filter - all optional). |
| `fetch_project` | Get one project by `projectId`. |
| `create_project` | Create a project (`name`, plus **at most one** of `password` or `authProviderId`). |
| `set_project_auth` | Set/clear a project's auth. A project has **one** form of auth - setting one clears the other. |
| `delete_project` | Delete a project and all its deployments (irreversible). |
| `list_deployments` | List a project's deployments, newest first. |
| `create_deployment` | Start a deployment and get a signed upload URL per file. You then PUT the bytes yourself. |
| `list_auth_providers` | List the org's auth providers; each has a `configured` flag and an `id` for `authProviderId`. |

## The core workflow

To put content live, you need a **project** (to get its `projectId`) and then a
**deployment** of the files.

### 1. Choose or create the project

Which project a deployment lands in depends on what already exists. Call
`list_projects` first to find out.

- **The user named a specific project or gave a `projectId`.** Resolve it with
  `list_projects` (with `q` set to the name) or `fetch_project` and use it. Don't
  create a duplicate when they clearly mean an existing one.
- **The org has no projects yet.** Create one automatically with `create_project`
  (see auth flow below) and deploy into it - don't make the user pick from an
  empty list. Give it a sensible name from the content or the user's request.
- **The org has existing projects and the user didn't say which.** Ask the user
  to pick which project to deploy into. **Do not guess or default to the newest,
  the only one, or a name that happens to match** - always let the user choose,
  and offer "create a new project" as an option.

### Setting a project's auth (at creation)

Every new project **must** get exactly one form of auth - a project can never be
left open to the public internet. Decide the auth like this, **before** calling
`create_project`:

1. Call `list_auth_providers` and look at the `configured: true` entries.
2. **If one or more providers are configured**, ask the user which they want:
   sign-in through one of those providers, *or* a password. Don't pick for them.
   - Provider chosen → pass that provider's `id` as `authProviderId` (and no
     `password`).
   - Password chosen → see step 4.
3. **If no providers are configured**, the only option is a password - use one.
4. **When a password is used, generate a strong one yourself** - the user should
   not have to invent it - pass it as `password`, and **report the generated
   password back to the user in your reply.** `create_project` never returns the
   password (its response only reports `passwordProtected: true`), so the value
   you generated is the only copy - surface it clearly.

Always set exactly one of `password` or `authProviderId` - never both, and never
neither.

### 2. Deploy the files

**Deploying is two steps: get signed upload URLs, then upload the bytes
yourself.** File contents never pass through `create_deployment`, so the size of
a site is not limited by how much you can write in one message. Never try to
inline file contents into the tool call - it takes no `content` field.

**Step A - call `create_deployment`** with the `projectId` and a `paths` array
listing the relative path of every file, e.g. `index.html`, `assets/app.css`,
`images/logo.png`. Directory structure is preserved.

**Each deployment is a complete snapshot** - list every file the site needs
(HTML, CSS, JS, images, fonts…), not just what changed. The newest deployment
fully replaces the previous live version.

**Every deployment must include `index.html` at the root of `paths`** - it is
the entry point served at the project's URL. Never call `create_deployment`
without one. If the content the user gave you has no root `index.html` (e.g. a
build output whose entry point is named differently, or a single page under
another name), add or rename a file so `index.html` exists at the deployment
root before deploying.

The response gives you a `deploymentId` and an `uploads` array with one entry
per path:

- `url` - a signed URL that accepts a single `PUT`. Use it **verbatim**,
  query string included; it is signed for that exact URL and nothing else.
- `headers` - headers to send with the `PUT` (a `Content-Type`).
- `path` - the file this URL writes to, so you can match it to your content.

The URLs expire an hour after they are issued.

**Step B - PUT each file's raw bytes to its `url`**, sending the headers from
`headers`. Nothing else is needed: the URL is already signed, so there is no
signing, hashing, or auth header for you to add. (If an upload is ever rejected
with a 403, retry it with an `x-amz-content-sha256` header set to the lowercase
hex SHA-256 of that file's bytes.)

Upload files in any order, in parallel if you can. **The deployment is live from
the moment step A returns**, so the project serves 404s for files that have not
landed yet - **upload `index.html` last** so visitors don't see a broken page
mid-deploy. Verify every `PUT` returned a 2xx before telling the user the site
is live; a failed upload leaves that file missing with no other signal.

For a site of more than 200 files, call `create_deployment` again with the
`deploymentId` it returned to sign the next batch against the *same* deployment.
Only omit `deploymentId` when you want to start a new deployment.

After a successful deploy, **always print the live URL** so the user can open
the site. The URL is `https://<projectId>.launchcloud.ai` - build it from the
project's `id`. The response does not include the URL, so you must construct and
surface it yourself every time; never leave the user to find it in the
dashboard. Report the deployment along with this URL (and the password, if you
generated one).

### 3. Updating a live site

To publish a new version, run the same two steps again on the **same**
`projectId` with the full set of files. There is no separate "promote" step -
the latest deployment is live. Use `list_deployments` to show history.

## Allowed file types

Only static web content is accepted; anything else is rejected before it is
stored. Allowed extensions:

- **Markup & data:** `html`, `htm`, `css`, `js`, `mjs`, `cjs`, `map`, `json`,
  `xml`, `txt`, `csv`, `md`, `webmanifest`
- **Images:** `svg`, `png`, `jpg`, `jpeg`, `gif`, `webp`, `avif`, `ico`, `bmp`
- **Fonts:** `woff`, `woff2`, `ttf`, `otf`, `eot`
- **Media:** `mp4`, `webm`, `mp3`, `ogg`, `wav`
- **Other:** `pdf`, `wasm`

There is **no server-side build step** - deploy the final built output. If the
user has a framework project (Next.js, Vite, etc.), have them run their static
export/build first (e.g. `vite build`, `next build && next export`) and deploy
the resulting `dist`/`out` directory's contents. Do not try to deploy raw
`.tsx`/`.jsx`/source files.

## Access controls

A project's content is **always** gated - it can never be public - via **one** of
two mutually exclusive ways:

- **Password** - visitors must enter a password.
- **Auth provider** - visitors sign in through one of the org's configured
  providers (`GOOGLE` or `SAML`), referenced by `authProviderId`. Providers have
  no display name; identify them by `providerType` and `id` from
  `list_auth_providers`, and only offer ones with `configured: true`.

Manage these with `set_project_auth`. **Setting one form clears the other** -
linking an `authProviderId` removes any password, and setting a `password`
unlinks any provider - so a project never has both. To switch auth, set the new
form and pass `null` for the old one to be explicit. **Never clear the last form
of auth** - a project must always keep a password or a provider, so don't remove
one without setting the other.

When you set a password with `set_project_auth`, generate a strong one and report
it to the user - the tool response only confirms `passwordProtected: true` and
never echoes the value back.

## Correct usage examples

Create a password-protected project and deploy a one-page site. The password is
generated by you and must be surfaced to the user - the response only reports
`passwordProtected`, not the value:

```
create_project { "name": "Launch announcement", "password": "brave-otter-lamp-92" }
→ returns { "id": "clx0abc123", "passwordProtected": true, "authProviderId": null, ... }
// Tell the user: password is brave-otter-lamp-92

create_deployment {
  "projectId": "clx0abc123",
  "paths": ["index.html", "styles.css", "logo.png"]
}
→ returns {
    "deploymentId": 42,
    "projectId": "clx0abc123",
    "expiresAt": "2026-08-07T12:00:00.000Z",
    "uploads": [
      { "path": "index.html", "method": "PUT",
        "url": "https://uploads.launchcloud.ai/clx0abc123/42/index.html?Expires=…&Signature=…&Key-Pair-Id=…",
        "headers": { "Content-Type": "text/html; charset=utf-8" } },
      { "path": "styles.css", "method": "PUT", "url": "…", "headers": { "Content-Type": "text/css; charset=utf-8" } },
      { "path": "logo.png",   "method": "PUT", "url": "…", "headers": { "Content-Type": "image/png" } }
    ]
  }
```

Then upload the bytes — `styles.css` and `logo.png` first, `index.html` last:

```bash
curl -X PUT "<url for styles.css>" \
  -H "Content-Type: text/css; charset=utf-8" \
  --data-binary @styles.css
```

```
// Tell the user the live URL: https://clx0abc123.launchcloud.ai
```

Ship an update to an existing project:

```
list_projects { "q": "Launch announcement" }   // resolve the id
create_deployment { "projectId": "clx0abc123", "paths": [ …full site again… ] }
// …then PUT every file again, index.html last
```

Switch a project from a password to Google SSO (setting the provider clears the
password; passing `password: null` makes the intent explicit):

```
list_auth_providers {}
→ [ { "id": 3, "providerType": "GOOGLE", "configured": true }, ... ]
set_project_auth { "projectId": "clx0abc123", "password": null, "authProviderId": 3 }
```

## Guardrails

- **Confirm before destructive actions.** `delete_project` is irreversible and
  removes all deployments - confirm with the user first. Deploying overwrites the
  live version; if the user might not intend to replace production, confirm.
- **Never invent a `projectId` or `authProviderId`** - always resolve real ids
  via `list_projects` / `fetch_project` / `list_auth_providers`.
- **Let the user pick the project when more than one exists.** Auto-create only
  when the org has no projects; otherwise ask which project to deploy into rather
  than guessing.
- **Exactly one form of auth per project - never zero.** A project can never be
  open to the public internet, so always set a password or a provider (never
  both). When providers are configured, ask the user which they want.
- **Every deployment needs a root `index.html`.** It is the entry point served
  at the project URL - never call `create_deployment` without one. If the source
  content lacks it, add or rename a file so `index.html` exists at the root.
- **Finish the uploads, and check they succeeded.** `create_deployment` only
  hands out URLs - the deploy is not done until every file has been `PUT` and
  returned a 2xx. A deployment whose files never landed serves 404s, and nothing
  will tell the user why. Never report a site as live before the uploads finish.
- **Upload `index.html` last.** The deployment is live as soon as it is created,
  so the entry point landing last keeps visitors from seeing a page whose assets
  are still missing.
- **Always print the live URL after a deploy.** Every successful
  `create_deployment` must be followed by the site's URL,
  `https://<projectId>.launchcloud.ai`, built from the project id - the response
  never includes it, so construct and surface it yourself every time.
- **Always report a generated password.** If you set a password, you created the
  value - the tools never return it, so the user gets it only if you say it.
- **Don't put secrets in deployed files.** Everything in a deployment is served
  as public static content (subject only to the project's password/SSO gate).
- For deploying a whole local directory from a terminal or CI, the LaunchCloud
  **CLI** (`lc deployments create --project-id <id> <path>`) walks the folder and
  uploads everything automatically - suggest it when the user is working from a
  local build rather than through the assistant.
