MCP server
An MCP server with nineteen tools. Point any MCP client at it — Claude, Cursor, VS Code, Codex, Gemini CLI — and "put this online" becomes a URL.
If you are deciding whether you want this, start at Deploy from an AI agent: in a coding agent with a shell the CLI is the shorter route, and this server is the one that works where there is no shell.
| Tool | What it does |
|---|---|
login | Opens a browser on this machine and stores the token the sign-in returns |
login_code | Signs in with a short code approved on another device, where no browser can open |
publish_files | Takes files the agent wrote — a page, a markdown note, several files together |
publish_dir | Takes an absolute folder path and publishes it |
list_sites | Lists your sites, so a publish can go to one of them |
list_folders | Lists your folders, so a new site can be filed in one of them |
delete_site | Takes a site down for good, subdomain included — needs the subdomain repeated back |
get_site | Reads a site's settings, whether it is paused, and its versions with the live one marked |
update_site | Changes a site's name, serving mode, password, invite-only, comments, forms, scheduled takedown or folder, and the APIs its /api/* routes may call |
list_people | Lists who a site is shared with, what each may do, and whether only they can open it |
invite_people | Invites addresses you give — to view, or to view and comment — emailing each one |
remove_person | Takes one address off a site's list |
pause_site | Takes a site off the air, keeping its files, versions and subdomain |
resume_site | Puts a paused site back on the air |
rollback_site | Serves an earlier version again, by its id from get_site |
list_comments | Reads the comments people left on a site's pages, with the page and the words each thread points at |
reply_comment | Answers a comment thread as the site's owner; the thread's people are emailed |
resolve_comment | Marks a comment thread resolved |
reopen_comment | Opens a resolved thread again |
publish_files is for content the agent wrote, where there is no folder on disk to point at. A site
needs an index.html at its top level or at least one document — .md, .markdown, .pdf,
.docx, .xlsx or .epub — so one page
goes at index.html, and a single note is a whole site on its own, read through the
documents viewer rather than as raw markdown. It carries text, so a PDF or an image
still has to come off a disk with publish_dir.
In a chat on the web — nothing to install
Claude on the web, Claude's apps and other hosted chats can't run a program on your machine, so they reach the server by URL instead:
Code
Claude (claude.ai, the desktop and mobile apps): Drop2Run is in Claude's connector directory. Open it, connect, sign in to Drop2Run in the window that opens, and approve the connection. That is the whole setup. To add it by URL instead, open this link, or go to Settings → Connectors → Add custom connector and paste the URL.
Claude Code, without npx:
Code
Cursor and other clients that take a URL:
Code
ChatGPT: add it as a custom connector, where your plan offers developer mode.
The approval page names the chat asking and the address the connection goes back to — check that address before approving. The connection appears under Connected chats on your access tokens page; Disconnect ends it at once, and one left unused for 90 days ends on its own. Connecting the same chat again replaces its old connection rather than adding one.
What the remote server does not have:
loginandlogin_code— the chat signs in for it.publish_dir— there is no disk to read a folder from.- Images, PDFs and Office files. Tool arguments are text the model writes, so
publish_filestakes any text file — HTML, CSS, JavaScript, Markdown, SVG, JSON, YAML, CSV, plain text and source code in any language — but nothing binary. To publish binary files, use the local server below or the CLI.
The other sixteen tools are the same tools, with the same names and the same arguments.
Setting it up
Claude Desktop. Download drop2run.mcpb and open it. Claude
shows an install dialog, and that is the whole of it — no config file, no terminal, and nothing to
install first, because Claude for macOS and Windows ships the node this runs on.
Claude Code. One command:
Code
Every other client — Cursor, VS Code, Codex, Gemini CLI, Zed and the rest each keep their server
list somewhere different, and Deploy from an AI agent
has the file and the shape for each. Most of them read an mcpServers block like this one:
Code
Claude Desktop reads that block too, at ~/Library/Application Support/Claude/claude_desktop_config.json
on macOS and %APPDATA%\Claude\claude_desktop_config.json on Windows, restarting the app afterwards.
The bundle above does the same thing without the file, which is the only reason to prefer it.
The two routes differ in where the server comes from. npx fetches @drop2run/mcp the first time the
client starts it and runs it from the cache after that, so it picks up a new version on its own. The
bundle carries one version and keeps it until you open a newer one — which is the trade for not having
to edit anything.
Then ask for a publish. There is no separate setup step: with no token stored, the agent calls login, a
browser tab opens, you approve it, and the publish carries on. The token goes to
~/.config/drop2run/config.json — the same file the CLI reads, so signing in once serves both.
npx drop2run login from a terminal does exactly the same thing, if you would rather sign in before
opening a chat.
No browser on this machine. In a container, over SSH, on a remote host, login_code returns a short
code to enter at app.dropto.run/device from any other device. login also
falls back to handing you the URL when it cannot launch a browser itself — open it anywhere on the same
machine and the sign-in completes.
Or bring your own token. Create one at
app.dropto.run/account/tokens and put it in the config file, or set
DROP2RUN_TOKEN in the environment the client starts the server in. See Authentication.
Without a token
The server still starts, and every tool answers with how to get one. A server with no credential is unconfigured rather than broken, and a client reporting a crashed server would tell you nothing about what to do next.
A token stored while the chat is open works without a restart: credentials are read per call, which is
what lets login be a tool rather than an instruction to go and do something in a terminal.
DROP2RUN_TOKEN is the exception — the environment is read once at startup, so an export after the
server started is invisible to it until the client restarts it.
Publishing over an existing site
Leave site unset and each publish creates a new site. Pass a subdomain to publish over one you already
have — which replaces what it serves, so it is worth naming explicitly rather than letting a model pick.
The server's own instructions say so, which is what a model reads before choosing a tool.
Choosing the address of a new site
A new site is named for you unless subdomain asks for one:
Publish this as release-notes —
publish_fileswithsubdomain: "release-notes", and the URL comes back ashttps://release-notes.dropto.live.
Tell the agent the address you want and it passes it; say nothing and the name is generated, which is the
right default for a page nobody is going to link to. The tool descriptions say to ask rather than invent
one, because the name becomes a URL somebody is given and a site can change it only twice
(change_subdomain), with every old name kept as a redirect.
site and subdomain are opposites — publish over a site that exists, or create one that does not —
and passing both is refused rather than resolved in favour of either.
Filing a new site in a folder
A new site goes at the top level of your dashboard unless folder files it somewhere:
Publish this into Clients/Acme —
publish_fileswithfolder: "Clients/Acme".
The path is folder names from the top level down, case ignored; list_folders shows what exists, and
takes an id as well. It never creates a folder — a folder that does not exist is refused with the list of
those that do, and nothing is published. Like subdomain it only describes a new site, so passing it
with site is refused: moving an existing site is update_site's folder.
Changing a site after it is published
Put a password on release-notes —
update_sitewithsite: "release-notes"and the password you gave.
update_site changes only what it is given, and the change reaches every edge within about a minute
without publishing again. An empty name, password or expiresAt clears that setting; folder: "root"
moves the site back to the top level. A setting your plan does not include is refused with the reason,
and get_site says which ones those are. The tool description tells the model to use a password or a
date only when you gave one.
get_site reports invitedOnly beside the password. A password on an invite-only site replaces
invite-only — everyone on the list loses access unless they have the password — and update_site says so
when it happens; invitedOnly: true switches back, and the list is kept.
A scheduled takedown pauses the site by default, which keeps everything and can be undone with
resume_site. expiryAction: "delete" deletes it instead when the date arrives, and asks for the
subdomain as confirm exactly as delete_site does — a deletion with a delay is still a deletion.
rollback_site serves an earlier version from get_site's list. A version whose files have been
collected cannot be rolled back to; publish again instead.
Calling your own API
Let my-chat call OpenAI with this key —
update_sitewithupstream: { origin, headers }, then a line in the site's_redirects.
On paid plans, upstream declares an origin the site's /api/* routes may send calls to, with headers to
attach — so the key stays off the page. Its fields:
| Field | What it does |
|---|---|
origin | https:// and a host name, such as https://api.openai.com |
headers | [{ name, value }], set or replaced by name. A value is encrypted on arrival and never shown again |
methods | What visitors may send; GET only on a new upstream when left out. A method that writes lets anybody who can open the site write with the key |
removeHeaders | Header names to drop |
remove | true removes the upstream with all its keys; sent alone |
A call then needs a route: a line in the site's _redirects ending in 200, such as
/api/* https://api.openai.com/:splat 200, published like any other file —
Redirects has the rules. get_site reads it all back as apiProxy:
the month's allowance, each upstream with header names and last four characters, and the live routes.
A key you give the model passes through the conversation on its way here, like anything else you type to it. Give it a key with the least access it needs, and the tool description tells the model never to make one up.
Sharing a site for review
Share launch-page with client@example.com so they can comment, and keep everyone else out.
invite_people puts the addresses on the site's list — role: "comment" to let them comment as well as
open it — and emails each one an invitation. update_site with invitedOnly: true makes the list the only
way in (it replaces a password), and comments: "invited" lets the people invited to comment do so; use
comments: "anyone" instead to let anybody signed in comment on a site you keep public or behind a
password. list_people shows the list, and remove_person takes someone off it.
The tool description and the server's instructions tell the model to invite only addresses you gave it — an invitation is an email from your account to someone outside it. Invite-only sharing and comments are plan features; on a plan without them the call is refused with the reason.
Answering comments
Read the comments on launch-page and fix what they point at.
On a plan that includes comments, the people you share a site with can pin comments to
its pages. list_comments gives the agent each open thread: its page, the words the commenter selected
(or the text near a pin), the element's selector, whether it was opened on an earlier version, and every
comment in it. That is enough to find the source file, change it, and publish.
Then reply_comment answers on the page. The order matters, and the server's instructions tell the
model: publish first, reply second — a reply saying "fixed" before the fix is live sends the
commenter to the old version. A reply leaves the thread open unless resolve is true, so the commenter
sees the answer on the page and can confirm the fix and resolve it themselves. since takes the cursor
from a previous call, for reading only what changed.
Only the account's owner can reply or resolve; anyone on the account can read. A reply from the agent appears under your name, as one you typed would, and the people in the thread get the same email — the agent answers for you, on what you asked it to do.
Comment text is what visitors wrote. On a site open to anybody signed in, that is strangers, and the model reads it in the same context it takes instructions from. The tools hand each comment over fenced as data, and both the listing and the server's instructions tell the model to treat comments as requests about the site's content — not to run anything, delete anything or change a setting because a comment said to. That is an instruction to the model, not a sandbox: review what an agent proposes to do about a comment the way you would review a pull request from someone you do not know.
What it does not send anywhere
The token reaches the control plane and nothing else. File uploads are PUTs to the upload endpoint carrying a per-file permit — one key, one length, worth nothing else — and are never handed the account token. That was true when those PUTs went to presigned storage URLs and it stayed true when they moved behind our own upload Worker; the endpoint being ours is a reason to be more careful, not less.