# Cheldrop MCP Server — Plan & Build Brief

> **Status: shipped 14 August 2026** (build plan Phase 7). The transport, auth,
> guardrails, upgrade nudges, error format and Settings UI below are what was
> built. The **tool list is superseded** by the build plan's Phase 7 list:
> `get_settings`, `list_sites`, `create_site`, `adopt_folder`, `publish_now`,
> `set_password`, `delete_site`. `publish_files` and `publish_folder` were
> deliberately not built — with Live site folders, writing files into the folder
> *is* the publish interface, and a second path to it would be a second set of
> rules about what a publish means. The docs page shipped with the feature; the
> landing-page section is still pending, per "What to add when this ships".

## What this is

A local MCP (Model Context Protocol) server that Cheldrop exposes, allowing Claude Desktop to publish files and folders on behalf of the user. The user configures it once, and can then prompt Claude naturally — "publish ~/Projects/mockup to preview.myclient.com" — and Claude does the work through Cheldrop's existing publish pipeline.

This is a **free feature**, shipping in the version after the current one.

---

## Files to create / modify

| File | Action | Notes |
|---|---|---|
| `main/mcp-server.js` | Create | The MCP server — tools, safety guards, upgrade nudges |
| `main/ipc.js` | Modify | Start/stop the MCP server with the app |
| Settings UI | Modify | Add MCP secret display + copy button |

**Do not touch** `landing/` or `docs/` until the MCP feature is built and working.

---

## Architecture

- HTTP server on `localhost:3847` (or similar fixed port), started by Electron on app launch
- Requires a shared secret in each request header: `x-cheldrop-mcp-secret`
- Secret is generated on first launch, stored in electron-store, shown in Settings UI with a copy button
- Claude Desktop connects via `claude_desktop_config.json` (user sets up once)
- Server calls the existing `publish()` and `deleteSite()` functions from `cloudflare.js` directly — no duplication of logic

---

## Tools exposed

### `list_sites`
Returns all published sites from the local DB. Read-only, no guards needed.

**Response fields:** `id`, `subdomain`, `domain`, `backend`, `publishMode`, `liveUrl`, `publishedAt`, `fileCount`

---

### `get_settings`
Returns whether the app is configured and ready. Read-only.

**Response fields:** `configured` (bool), `available_domains` (string[]), `license_status` (`free` | `active`)

Note: never expose the raw API token.

---

### `publish_files`
Publishes a list of file paths to a subdomain/domain.

**Parameters:**
- `files`: string[] — absolute filesystem paths
- `subdomain`: string
- `domain`: string — must be in user's Cloudflare account
- `backend`: `workers` | `r2` (optional, defaults to `workers`)
- `show_toc`: bool (optional, default true)
- `show_toolbar`: bool (optional, default true)
- `confirm_overwrite`: bool (optional, default false)

**Safety logic:**
1. Validate domain is in stored zones — error if not, list available domains
2. Validate all file paths exist on disk
3. Check if site already exists in DB
4. If site exists and `confirm_overwrite` is not `true` → return `status: "site_exists"` with site details and `requires_confirmation: true`. Do NOT publish.
5. If `confirm_overwrite: true` → publish
6. Check license for R2 or site limit — return upgrade nudge if blocked

---

### `publish_folder`
Publishes a folder path to a subdomain/domain.

**Parameters:**
- `folder`: string — absolute filesystem path to folder
- `subdomain`: string
- `domain`: string
- `backend`: `workers` | `r2` (optional)
- `confirm_overwrite`: bool (optional, default false)

**Safety logic:** same as `publish_files` above. Also validates the folder path exists and is a directory.

---

### `delete_site`
Deletes a site from Cloudflare and the local DB.

**Parameters:**
- `id`: string — site ID in format `subdomain.domain`
- `confirmed`: bool (optional, default false)

**Safety logic:**
- First call without `confirmed: true` → return site details + warning, do nothing
- Second call with `confirmed: true` → delete

---

## Upgrade nudges

The server checks license status before publish operations. When a free-tier limit is hit, the response includes an `upgrade` field alongside the error:

```json
{
  "ok": false,
  "error": "Free tier is limited to 1 site.",
  "upgrade": {
    "reason": "Unlimited sites",
    "cta": "Upgrade to Business Class — $49 one-time, no subscription.",
    "url": "https://cheldrop.com/#pricing"
  }
}
```

**Triggers:**
- Site limit hit (free tier, trying to publish a new subdomain when 1 already exists)
- Password protection requested (free tier)
- R2 backend requested (free tier)
- File over 25 MB detected (any tier — inform about R2 as the solution, nudge upgrade if free)

**Never show upgrade nudge on:** `list_sites`, `get_settings`, or any successful operation.

---

## Error response format

All errors should be human-readable and actionable — Claude will relay them verbatim to the user.

```json
{
  "ok": false,
  "error": "Domain example.com is not in your Cloudflare account. Available domains: mydomain.com, otherdomain.com"
}
```

```json
{
  "ok": false,
  "error": "No API token configured. Open Cheldrop → Settings to add your Cloudflare token."
}
```

```json
{
  "status": "site_exists",
  "requires_confirmation": true,
  "site": {
    "id": "preview.myclient.com",
    "live_url": "https://preview.myclient.com",
    "last_published": "2025-03-20T14:32:00Z",
    "file_count": 6,
    "backend": "workers"
  },
  "message": "This will replace everything currently live on the site. Call again with confirm_overwrite: true to proceed."
}
```

---

## Settings UI addition

In the Settings page, add a new section (below API token, above license):

- Label: **MCP Server**
- Show the port (`localhost:3847`) and the secret key (masked by default, reveal on click)
- Copy button for the full config snippet users need for `claude_desktop_config.json`
- Status indicator: running / not running

---

## What to add when this ships (not before)

### Docs page
A new page in the app's docs (or at cheldrop.com/docs/mcp):
- One-time setup: where to find the secret, what to paste into `claude_desktop_config.json`
- What Claude can do (publish files, publish folder, list sites, delete with confirmation)
- What Claude can't do (operate without the app running, bypass confirmations)
- 3–4 example prompts

### Landing page section
New section between Features and Pricing:
- Heading: "Works with Claude"
- Angle: the only file publishing tool Claude can operate directly
- Short: 2–3 sentences + one example prompt + link to docs
- Do not add until MCP is built and working

---

## Prompt for a new Claude conversation

Paste this at the start of a new conversation, with this file and the brand skill attached:

---

I'm building a local MCP server for Cheldrop — a Mac/Windows Electron desktop app (React, Vite, electron-store, lowdb) that publishes files to Cloudflare Workers static assets and R2 via a custom API pipeline.

Read the attached plan document (`mcp-plan.md`) in full before writing any code.

Key files in the project (read these first):
- `main/ipc.js` — Electron IPC handlers, store and DB access patterns
- `main/cloudflare.js` — `publish()` and `deleteSite()` functions you'll be calling
- `main/renderer.js` — file rendering pipeline (reference only)

Project folder: `/Users/christian/Developer/Cheldrop/app`
Brand skill: `/Users/christian/Documents/Prosjekter/Cheldrop/docs/cheldrop-brand-skill.md`

Build in this order:
1. `main/mcp-server.js` — the full MCP server per the plan
2. Wire it into app startup in `main/ipc.js` (or wherever the app initialises)
3. Add the Settings UI section (secret display + copy button)

Start by reading `main/ipc.js` and `main/cloudflare.js`.
