@drop2run/mcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -44
  3. package/dist/index.js +46 -12
  4. package/package.json +2 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Drop2Run
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,37 +1,19 @@
1
1
  # @drop2run/mcp
2
2
 
3
- An MCP server that publishes to [Drop2Run](https://dropto.run) from inside a chat. Three tools:
3
+ An MCP server that lets Claude — or any MCP client — publish a static site to
4
+ [Drop2Run](https://dropto.run). Give it a folder or an HTML document; it returns a live HTTPS URL.
4
5
 
5
- | Tool | What it does |
6
- |---|---|
7
- | `publish_html` | Takes one HTML document, publishes it as a whole site, returns the URL |
8
- | `publish_dir` | Takes an absolute folder path and publishes it |
9
- | `list_sites` | Lists the account's sites, so a publish can go to one of them |
10
-
11
- ## Getting a token
6
+ Full documentation at [dropto.run/docs/mcp](https://dropto.run/docs/mcp).
12
7
 
13
- The server authenticates with a personal access token — there is no browser in a chat, so there is no
14
- cookie to use. Create one at <https://dropto.run/account/tokens>. It is shown once and stored as a hash,
15
- so it cannot be recovered afterwards.
8
+ ## Setup
16
9
 
17
- Then either put it in the environment:
10
+ **Claude Code** — one command:
18
11
 
12
+ ```bash
13
+ claude mcp add drop2run -s user -- npx -y @drop2run/mcp
19
14
  ```
20
- DROP2RUN_TOKEN=d2r_...
21
- ```
22
-
23
- or in `~/.config/drop2run/config.json`:
24
-
25
- ```json
26
- { "token": "d2r_..." }
27
- ```
28
-
29
- The environment wins. That file is deliberately the same one the `drop2run` CLI reads, so
30
- `drop2run login` covers both — and so the two can never disagree about which account is yours.
31
-
32
- Point it at a different API with `DROP2RUN_API_URL`, or `apiBaseUrl` in the same file.
33
15
 
34
- ## Wiring it up
16
+ **Claude Desktop** — add this to `claude_desktop_config.json`, then restart the app:
35
17
 
36
18
  ```json
37
19
  {
@@ -44,31 +26,80 @@ Point it at a different API with `DROP2RUN_API_URL`, or `apiBaseUrl` in the same
44
26
  }
45
27
  ```
46
28
 
47
- That is the whole configuration — `npx` fetches the package, and there is nothing to install globally.
29
+ That file is at `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, and
30
+ `%APPDATA%\Claude\claude_desktop_config.json` on Windows.
31
+
32
+ Nothing is installed globally either way — `npx` fetches the package when the client starts it.
33
+
34
+ ## Sign in
48
35
 
49
- To run it from a checkout instead, build first, because `dist/` is not in the repository:
36
+ The server has no browser and no dashboard session, so it authenticates with a personal access token.
37
+ Two ways to give it one.
38
+
39
+ Through the CLI, which stores it in `~/.config/drop2run/config.json` with mode `0600`:
50
40
 
51
41
  ```bash
52
- cd packages/mcp && npm run build
53
- node bin/drop2run-mcp.mjs
42
+ npx drop2run login
54
43
  ```
55
44
 
56
- ## Two decisions worth knowing about
45
+ Or create a token at <https://dropto.run/account/tokens> and set it in the environment:
57
46
 
58
- **Publishing without naming a site creates a new one.** The obvious alternative — reuse the most recent —
59
- would silently replace whatever the account last published, and a tool a model calls on somebody's
60
- behalf is the worst place for that default. "Put this online" is not "and overwrite my last site".
47
+ ```
48
+ DROP2RUN_TOKEN=d2r_...
49
+ ```
50
+
51
+ A token is shown once and stored as a hash, so it cannot be read back later — create a new one if you
52
+ lose it. The environment takes precedence over the file, and the file is the one the `drop2run` CLI
53
+ reads, so signing in once covers both.
54
+
55
+ The token is read on every call rather than at startup, so one added while the client is running takes
56
+ effect without a restart.
57
+
58
+ ## The tools
59
+
60
+ | Tool | What it does |
61
+ |---|---|
62
+ | `publish_files` | Publishes files Claude wrote — a page, a markdown note, several files together |
63
+ | `publish_dir` | Publishes a folder, given its absolute path |
64
+ | `list_sites` | Lists the sites on the account |
65
+
66
+ Both publish tools take an optional `site` — a subdomain or site id to publish over. Leave it out and a
67
+ new site is created.
68
+
69
+ ## Behaviour worth knowing
70
+
71
+ **Publishing without a `site` creates a new one.** It never replaces your most recent site by default:
72
+ "put this online" is not "and overwrite what I published last time".
73
+
74
+ **A site can be documents instead of a built site.** A publish needs an `index.html` at the top level,
75
+ or at least one `.md`, `.markdown` or `.pdf` file — those are served through the reader. So a single
76
+ note is a whole site, and Claude does not have to wrap it in HTML to publish it.
77
+
78
+ **`publish_files` is text.** A PDF or an image has to come off a disk with `publish_dir`, because these
79
+ files arrive as JSON strings.
80
+
81
+ **The URL comes back immediately; every edge has it in about a minute.** The reply also says whether a
82
+ new version was published, or every file already matched what the site serves.
83
+
84
+ **Default subdomains are not indexed.** Anything on `*.dropto.live` is served with
85
+ `X-Robots-Tag: noindex`. Attach a custom domain if you want the site in search results.
86
+
87
+ **Your token only ever reaches the Drop2Run API.** Files are uploaded to a separate storage host that
88
+ needs no credential from us, and none is sent there.
89
+
90
+ ## Environment
91
+
92
+ | Variable | Effect |
93
+ |---|---|
94
+ | `DROP2RUN_TOKEN` | The personal access token. Takes precedence over the config file |
95
+ | `DROP2RUN_API_URL` | Point at a different API. Defaults to `https://dropto.run/api` |
61
96
 
62
- **A token never reaches the storage host.** The bearer credential goes to the control plane only. The
63
- file uploads are PUTs to presigned URLs on another host, which need nothing from us — a token attached
64
- there would be an account credential handed to a service that never asked. There is a test for it,
65
- because it is the kind of thing a later refactor tidies into a shared header helper.
97
+ ## If something is wrong
66
98
 
67
- ## What the tarball contains
99
+ **"No Drop2Run access token."** — the server is running but has no token. Run `npx drop2run login`, or
100
+ set `DROP2RUN_TOKEN`. No restart needed.
68
101
 
69
- `bin/` and `dist/` only. `dist/index.js` is a bundle: the publish engine this package shares with the
70
- `drop2run` CLI is folded in at build time, so nothing in the published files imports a package npm
71
- cannot fetch.
102
+ **Claude Desktop cannot start it** — Desktop does not inherit your shell's `PATH`. Use the absolute path
103
+ to `npx` (`which npx`) as `command`.
72
104
 
73
- The two real dependencies — the MCP SDK and zod — stay external and are installed by npm. Inlining an
74
- SDK would mean shipping a copy that never gets a security update.
105
+ Needs Node 20 or newer. MIT licensed.
package/dist/index.js CHANGED
@@ -595,10 +595,32 @@ async function publish(credentials, source, site) {
595
595
  function publishDirectory(credentials, directory, site) {
596
596
  return publish(credentials, directorySource(directory), site);
597
597
  }
598
- function publishHtml(credentials, html, site) {
599
- const bytes = new TextEncoder().encode(html);
600
- return publish(credentials, async () => [{ path: "index.html", bytes }], site);
598
+ function cleanPath(path) {
599
+ const cleaned = path.trim().replaceAll("\\", "/").replace(/^\.?\//, "");
600
+ if (cleaned === "" || cleaned.endsWith("/")) {
601
+ throw new Error(`"${path}" does not name a file.`);
602
+ }
603
+ if (cleaned.split("/").includes("..") || /^[a-zA-Z]:/.test(cleaned)) {
604
+ throw new Error(
605
+ `"${path}" is not a path inside the site. Give a path relative to the site's root, such as index.html or docs/guide.md.`
606
+ );
607
+ }
608
+ return cleaned;
601
609
  }
610
+ async function publishFiles(credentials, files, site) {
611
+ const encoder = new TextEncoder();
612
+ const collected = files.map((file) => ({
613
+ path: cleanPath(file.path),
614
+ bytes: encoder.encode(file.content)
615
+ }));
616
+ const seen = /* @__PURE__ */ new Set();
617
+ for (const file of collected) {
618
+ if (seen.has(file.path)) throw new Error(`Two files were given the same path: ${file.path}`);
619
+ seen.add(file.path);
620
+ }
621
+ return publish(credentials, async () => collected, site);
622
+ }
623
+ const SERVER_VERSION = "0.2.0";
602
624
  function say(text, isError = false) {
603
625
  return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
604
626
  }
@@ -623,14 +645,19 @@ function describe(result) {
623
645
  }
624
646
  function createServer() {
625
647
  const server = new McpServer(
626
- { name: "drop2run", version: "0.0.0" },
648
+ { name: "drop2run", version: SERVER_VERSION },
627
649
  {
628
650
  instructions: [
629
651
  "Publishes static sites to Drop2Run.",
630
652
  "",
631
- "Use publish_html for a single page you have written, and publish_dir for a folder that is",
632
- "already built. Leave `site` unset to publish to a brand-new site; pass a subdomain to",
633
- "publish over an existing one. list_sites shows what already exists.",
653
+ "Use publish_files for anything you wrote here — a page, a markdown note, several files",
654
+ "together — and publish_dir for a folder that already exists on disk. Leave `site` unset to",
655
+ "publish to a brand-new site; pass a subdomain to publish over an existing one. list_sites",
656
+ "shows what already exists.",
657
+ "",
658
+ "A site needs an index.html at its top level, or at least one .md, .markdown or .pdf file,",
659
+ "which publishes as a documents site and is read through a viewer. So one page goes at",
660
+ "index.html, and a single note is a whole publish that needs no wrapping in HTML.",
634
661
  "",
635
662
  "Publishing over an existing site replaces what it serves, so ask before doing that to a",
636
663
  "site the person did not name."
@@ -638,16 +665,23 @@ function createServer() {
638
665
  }
639
666
  );
640
667
  server.registerTool(
641
- "publish_html",
668
+ "publish_files",
642
669
  {
643
- title: "Publish a page",
644
- description: "Publishes one HTML page as a whole site and returns its URL. The page becomes index.html.",
670
+ title: "Publish files you wrote",
671
+ description: "Publishes one or more files written here — markdown, HTML, CSS, JSON — as a site, and returns its URL. A single page goes at index.html; a single .md, .markdown or .pdf file is a site on its own, served through the documents viewer. Text only: a PDF or an image has to be published from disk with publish_dir.",
645
672
  inputSchema: {
646
- html: z.string().min(1).describe("The complete HTML document to publish."),
673
+ files: z.array(
674
+ z.object({
675
+ path: z.string().min(1).describe("Path inside the site, such as index.html or docs/guide.md."),
676
+ content: z.string().describe("The file's contents.")
677
+ })
678
+ ).min(1).describe("The files to publish."),
647
679
  site: z.string().optional().describe("Subdomain or site id to publish over. Omit to create a new site.")
648
680
  }
649
681
  },
650
- ({ html, site }) => withCredentials(async (credentials) => describe(await publishHtml(credentials, html, site)))
682
+ ({ files, site }) => withCredentials(
683
+ async (credentials) => describe(await publishFiles(credentials, files, site))
684
+ )
651
685
  );
652
686
  server.registerTool(
653
687
  "publish_dir",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@drop2run/mcp",
3
- "version": "0.1.0",
4
- "description": "MCP server that publishes a folder or a page to Drop2Run from inside a chat.",
3
+ "version": "0.2.0",
4
+ "description": "MCP server that publishes a folder or an HTML page to Drop2Run and returns a live URL.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {