Skip to main content
Glama
README.md
<p align="center"><img src="https://dropthis.app/icon-512.png" width="76" height="76" alt="dropthis" /></p>

# @dropthis/mcp

Official [MCP](https://modelcontextprotocol.io) server for [dropthis](https://dropthis.app) — publish content (HTML, files, or a URL to fetch) and get back a permanent public URL, from any MCP-compatible agent (Claude Code, Claude Desktop, Cursor, Windsurf, opencode, ChatGPT, n8n, …).

Two ways to connect:

- **Local (stdio)** — `npx @dropthis/mcp`, authenticated with your `sk_` API key. Best for coding agents.
- **Remote (hosted)** — `https://mcp.dropthis.app/mcp` over Streamable HTTP, with OAuth for chat clients or a static `sk_` Bearer for automation. Best for ChatGPT / claude.ai / n8n.

## Local / stdio

Add to your MCP client config:

```json
{
  "mcpServers": {
    "dropthis": {
      "command": "npx",
      "args": ["-y", "@dropthis/mcp"],
      "env": { "DROPTHIS_API_KEY": "sk_..." }
    }
  }
}
```

Get an API key from your dropthis account. The server reads `DROPTHIS_API_KEY` (and optional `DROPTHIS_BASE_URL`). Set `DROPTHIS_READ_ONLY=true` to register only the read tools — a safe way to give an agent read access to your drops with no write/delete risk. Set `DROPTHIS_TOOLSETS` (e.g. `drops`) to trim the tool surface to what a session needs ([Toolsets](#toolsets-trim-the-tool-surface)). Claude Desktop users can install the one-click `.mcpb` bundle instead (the API key is stored in your OS keychain).

See [docs/setup.md](docs/setup.md) for per-client instructions (Claude Code, Cursor, Windsurf, VS Code, …).

## Remote (hosted) connector

Point an MCP-capable client at the hosted endpoint:

```
https://mcp.dropthis.app/mcp
```

One-click install (the deep-link opens the editor and adds the remote server):

- **Cursor:** [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=dropthis&config=eyJ1cmwiOiJodHRwczovL21jcC5kcm9wdGhpcy5hcHAvbWNwIn0=)
- **VS Code:** [Add to VS Code](vscode:mcp/install?%7B%22name%22%3A%22dropthis%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fmcp.dropthis.app%2Fmcp%22%7D)

- **Chat clients (ChatGPT, claude.ai)** authenticate via OAuth 2.1 — add the connector URL, sign in with a 6-digit email code (no password), then approve the consent screen, which shows the requested permissions and lets you pick the workspace to publish into.
- **Automation (n8n, CI, custom agents)** sends a static `Authorization: Bearer sk_...` header and skips OAuth. See [docs/n8n.md](docs/n8n.md).
- **Read-only:** append `?readonly=true` to the endpoint (`https://mcp.dropthis.app/mcp?readonly=true`) to register only the read tools, even for a write-capable credential.
- Manage or disconnect the connector later from the console's Connected apps page at https://app.dropthis.app.

## Tools

| Tool | Purpose |
|------|---------|
| `dropthis_publish` | Publish new content (`content` \| `source_url` \| `files` \| `file`/`paths`) → permanent URL |
| `dropthis_update_content` | Publish a new content version to an existing drop, keeping its URL |
| `dropthis_update_settings` | Update a drop's settings (title, visibility, password, noindex, expiry, metadata) and routing (move onto a custom domain / back to the shared pool, rename the vanity slug) |
| `dropthis_get` | Fetch one drop's metadata |
| `dropthis_get_content` | Read back what a drop is serving — its file manifest, or one file's content (the read half of the edit loop) |
| `dropthis_resolve` | Turn a drop's public URL or slug into its `drop_…` id (owner-scoped) — the recovery path for the id-based tools |
| `dropthis_list` | List your drops (paginated) |
| `dropthis_list_deployments` | List a drop's deployment (version) history |
| `dropthis_restore_deployment` | Roll a drop back to a prior deployment's content (Pro+ version history) — mints a new deployment copying it and makes it current, URL unchanged |
| `dropthis_analytics` | Read a drop's approximate view analytics (Keep+ for counts, Pro+ for country/referer/daily breakdowns) |
| `dropthis_delete` | Delete a drop (requires `confirm: true`) |
| `dropthis_account` | Authenticated account profile (id, email, status) including the account's limits for preflight sizing |
| `dropthis_domains_connect` | Connect a custom domain (`path` or `dedicated` mode) and get the CNAME record to create |
| `dropthis_domains_verify` | Re-check a domain's DNS/TLS and advance its status toward `live` |
| `dropthis_domains_get` | Read one domain's stored state and DNS instructions (no live DNS check) |
| `dropthis_domains_list` | List the account's custom domains and which is the default publish target |
| `dropthis_domains_update` | Repoint a dedicated domain to another drop, or set/clear the default path domain |
| `dropthis_domains_delete` | Disconnect a custom domain (requires `confirm: true`) |
| `dropthis_workspaces` | List the workspaces this connection can publish to, and which is active |
| `dropthis_use_workspace` | Switch the active workspace for this connection (persists server-side) |
| `dropthis_create_workspace` | Create a new team workspace (you become its owner) |
| `dropthis_rename_workspace` | Rename a team workspace or change its slug |
| `dropthis_delete_workspace` | Delete a team workspace (owner only; requires `confirm: true`) |
| `dropthis_members` | List a team workspace's members and their roles |
| `dropthis_invite_member` | Invite an email to a team workspace as admin or member |
| `dropthis_update_member_role` | Change a member's role (owner/admin/member) |
| `dropthis_remove_member` | Remove a member from a workspace, or leave it (requires `confirm: true`) |
| `dropthis_invitations` | List your own pending workspace invitations |
| `dropthis_accept_invitation` | Accept a workspace invitation and join (by token or invitation id) — the teammate join path |

`file`/`paths` are local/stdio only — the hosted Worker omits them from the tool schemas entirely. `dropthis_update_content` accepts the same content inputs as `dropthis_publish`; a `source_url` is fetched server-side (SSRF-guarded) and stored as the new version. The id-based tools (`dropthis_update_content`, `dropthis_update_settings`, `dropthis_get`, `dropthis_get_content`, `dropthis_analytics`, `dropthis_delete`, `dropthis_list_deployments`, `dropthis_restore_deployment`) require the full `drop_…` id from the publish response and are **strict id-only**: pass a URL or slug and the tool returns an error pointing you at `dropthis_resolve`, which turns a public URL/slug back into the `drop_…` id (owner-scoped). Resolve once, then edit by id — persist the `drop_…` id, since URLs and slugs are locators that can drift. Connections whose OAuth grant lacks the `drops:write` scope see only the read tools.

`dropthis_account` returns your account's exact limits.

The team-management tools (`dropthis_create_workspace`, `dropthis_rename_workspace`, `dropthis_delete_workspace`, `dropthis_members`, `dropthis_invite_member`, `dropthis_update_member_role`, `dropthis_remove_member`, `dropthis_invitations`, `dropthis_accept_invitation`) need a team-scoped credential (ADR-0068) — a publish-only key cannot manage teams. Find a workspace id with `dropthis_workspaces` and a member's `accountId` with `dropthis_members`. `dropthis_accept_invitation` is the teammate's join path: list your invites with `dropthis_invitations`, then accept by its invitation id (or by the raw token from the invite email).

### Workspaces (teams)

Every drop lands in a **workspace** (ADR-0067). A fresh login homes to your personal workspace; teams are shared workspaces with members and roles. Each connection has one **active workspace**, and publishes go there by default:

- `dropthis_workspaces` lists the workspaces this connection can act in and flags the active one.
- `dropthis_use_workspace` switches the active workspace; the choice **persists server-side** across reconnects.
- `dropthis_publish` takes a per-call `workspace` override — write-side targeting for a new drop.
- `dropthis_list` takes a per-call `workspace` selector — list another workspace you belong to without switching.
- id-addressed ops (`update_content`, `update_settings`, `delete`, …) always act in the drop's own workspace (a drop never moves workspace).
- `dropthis_account` echoes the active workspace (name, kind, your role).

A static `sk_` API key is **pinned** to the one workspace it was minted in — `dropthis_use_workspace` and the per-call `workspace` override/selector do not apply to it. To act in another workspace with an `sk_` key, mint a key in that workspace. OAuth (remote) connections are not pinned and can switch freely.

### Permissions & read-only

Write tools (publish, update, delete, domain and team management) require write access; the read tools never do:

- A static `Authorization: Bearer sk_...` key is **always write-capable** (account-scoped).
- An **OAuth** (remote connector) grant must hold the `drops:write` scope to see the write tools. The read tools need no scope, so a grant without `drops:write` — or any session in read-only mode — sees the read tools alone. A `403 insufficient_scope` from a write tool means re-authenticate to request `drops:write`.

There are two ways to force a **read-only** session even with a write-capable credential — only the read tools are registered, so there is zero write/delete risk:

- **Local / stdio:** set the env var `DROPTHIS_READ_ONLY=true`.
- **Remote:** append `?readonly=true` to the endpoint (`https://mcp.dropthis.app/mcp?readonly=true`).

The read tools that stay available in a read-only session are: `dropthis_get`, `dropthis_list`, `dropthis_list_deployments`, `dropthis_get_content`, `dropthis_analytics`, `dropthis_resolve`, `dropthis_account`, `dropthis_domains_list`, `dropthis_domains_get`, `dropthis_workspaces`, `dropthis_members`, and `dropthis_invitations`. Everything else is a write tool and is hidden (`dropthis_restore_deployment`, a version-history rollback, needs the `drops:write` scope).

### Toolsets (trim the tool surface)

All 29 tools load by default, but a publish-and-edit session rarely needs the 17 domains/workspaces/members/invitations tools. Register a subset to cut the per-session tool prose:

- **Local / stdio:** set `DROPTHIS_TOOLSETS` to a comma list, e.g. `DROPTHIS_TOOLSETS=drops`.
- **Remote:** append `?toolsets=` to the endpoint (beside `?readonly=`), e.g. `https://mcp.dropthis.app/mcp?toolsets=drops` or `?toolsets=drops,account`.

Values: `drops` (the core publish/edit/read tools — 11), `domains` (6), `workspaces` (workspaces + members + invitations — 11), and `account` (1). Unset, empty, or all-unrecognized → all toolsets (the default). Unknown tokens are ignored. `?readonly=` and `?toolsets=` compose: each hides tools independently, so `?toolsets=drops&readonly=true` leaves only the drop read tools. The tool list is fixed at connect — restart the stdio server (or reconnect the remote connector) after changing the value. A hosted claude.ai connector has no query-param UI, so set it in the connector URL you add.

### Multi-file bundles (`files` input)

A `files` bundle holds up to 200 files. Each file has a `path` (bundle-relative, used verbatim in HTML/CSS `src`/`href` attributes) and exactly one content source:

| Field | Use for | Notes |
|-------|---------|-------|
| `content` | UTF-8 text: HTML, CSS, JS, JSON, SVG, markdown | Text is sent inline |
| `source_url` | Remote assets: images, video, PDFs, fonts | Server fetches the URL; no bytes pass through your agent. Optional `content_type`, `size_bytes`, `checksum_sha256`, `transform` |
| `content_base64` | Small inline binary blobs only | Capped at 64 KiB encoded; rejected with a corrective error above that threshold pointing at `source_url` |

Never base64-encode an image that is already publicly reachable — use `source_url` instead. Example bundle with mixed sources:

```json
{
  "files": [
    { "path": "index.html", "content": "<html>…<img src='hero.jpg'>…</html>" },
    { "path": "hero.jpg", "source_url": "https://cdn.example.com/hero.jpg" },
    { "path": "logo.png", "source_url": "https://cdn.example.com/logo.png", "content_type": "image/png" }
  ]
}
```

**Image transforms.** A `source_url` image can carry a `transform` so the server resizes/re-encodes it on ingest — point at a big original and store a small web-optimised derivative, no scratch bucket needed. Fits inside the box, never upscales, strips metadata. Only with `source_url`; omit `size_bytes`/`checksum_sha256` (the stored object reflects the output).

```json
{
  "files": [
    { "path": "hero.jpg", "source_url": "https://cdn.example.com/original.png", "transform": { "width": 1080, "quality": 78, "format": "jpeg" } }
  ]
}
```

### Editing a bundle — `update_content` is a partial update

`dropthis_update_content` **patches by default** (`mode: "patch"`). The files you send are upserted by path; every file you don't mention is **carried forward unchanged**. So to fix one page, send only that page — a previously-bundled image stays put:

```json
{
  "drop_id": "drop_…",
  "files": [{ "path": "index.html", "content": "<html>…edited…</html>" }]
}
```

To remove a file, list its path in `delete_paths`:

```json
{
  "drop_id": "drop_…",
  "files": [{ "path": "index.html", "content": "<html>…</html>" }],
  "delete_paths": ["assets/old-hero.jpg"]
}
```

To swap the **entire** content set in one call, use `mode: "replace"` — the files you send become the whole drop and everything else is dropped (`delete_paths` is invalid in replace mode).

## Example prompts

Natural-language asks that exercise the main tools (the agent picks the tool):

- **Publish** — "Publish this HTML report and give me the link." → `dropthis_publish` returns a permanent `url` and the `drop_…` id.
- **Self-contained bundle** — "Publish this landing page with its four images" (HTML + `files[]` where each image is a `source_url`). → one drop, images fetched server-side and stored, no hot-linking.
- **Edit in place** — "Change the headline on that drop." → `dropthis_get_content` reads the current file, you edit it, `dropthis_update_content` ships a new version at the same URL.
- **Recover the id** — "Update https://abc123.dropthis.app/." → `dropthis_resolve` turns the URL into its `drop_…` id, then `dropthis_update_content`.
- **Custom domain** — "Serve my drops at reports.example.com." → `dropthis_domains_connect` (path mode) returns the CNAME, then `dropthis_domains_verify` until live.

## Privacy

dropthis is a hosted service: content you publish is sent to and stored by dropthis and served at a public URL. See the privacy policy at https://dropthis.app/privacy and the terms at https://dropthis.app/terms.

## Support

- Issues and feature requests: https://github.com/dropthis-dev/dropthis-mcp/issues
- Email: support@dropthis.app

## Known limitations

- **Plan caps** apply per drop and per account (size, TTL, custom domains, passwords) across the Free / Keep / Pro / Business tiers — call `dropthis_account` to read the active limits before a large publish.
- **Binary bytes can't ride in a tool call**: inline `content_base64` is capped at 64 KiB. For images/video/PDFs/fonts use `source_url` (the server fetches a public URL), or, on the local stdio server only, `file`/`paths`.
- **`source_url` must be publicly reachable** over http(s); the server fetches it (SSRF-guarded). A private/localhost URL won't resolve.
- **id-only edits**: the URL slug is not an id — keep the `drop_…` id from publish (or recover it with `dropthis_resolve`).
- **Setting a password is Pro-only**; clearing one (`null`) is always allowed.

## Develop

```bash
npm install
npm run dev          # tsx src/bin.ts (needs DROPTHIS_API_KEY)
npm run dev:worker   # wrangler dev (remote Worker)
npm test             # vitest (node) + vitest-pool-workers
npm run typecheck    # tsc (node + worker tsconfigs)
npm run lint         # biome
npm run build        # tsup → dist/bin.cjs (the npm package)
npm run build:worker # wrangler deploy --dry-run (bundle check)
```

One TypeScript codebase, two outputs: the stdio npm package and the Cloudflare Worker. Both are thin clients over the dropthis REST API (`@dropthis/node` for stdio, `@dropthis/node/edge` for the Worker); tool handlers are unit-tested against an injected fake client.

## License

MIT

TDQS

A4.2/5.0

Scored across 29 tools

Disambiguation5/5

Each tool targets a distinct resource and action, with no overlapping purposes. The read siblings dropthis_get and dropthis_get_content are explicitly differentiated as settings vs served content, and publish/update/delete/restore are clearly separated.

Naming Consistency3/5

All tools share the dropthis_ prefix, but the verb/noun ordering varies: drops use bare verbs (publish, get, list, delete), content uses verb_noun (update_content, get_content), and domains use noun_verb (domains_connect, domains_verify). This mix is readable but not fully consistent.

Tool Count3/5

29 tools is heavy, but the scope spans five sub-domains (drops, deployments, domains, workspaces, members/invitations). Each tool has a distinct role, so the count is justifiably high, though it exceeds the typical comfortable range of 3-15.

Completeness4/5

The drop lifecycle (create/read/update/delete, versioning/rollback, analytics) and domain management are complete, and workspaces/members/invitations cover team collaboration. Minor gaps include no way to decline or revoke invitations and no raw_url retrieval for existing drops.

Maintenance

ActivitySlowing
ResponsivenessSyncing