@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.
- package/LICENSE +21 -0
- package/README.md +75 -44
- package/dist/index.js +46 -12
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
node bin/drop2run-mcp.mjs
|
|
42
|
+
npx drop2run login
|
|
54
43
|
```
|
|
55
44
|
|
|
56
|
-
|
|
45
|
+
Or create a token at <https://dropto.run/account/tokens> and set it in the environment:
|
|
57
46
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
`
|
|
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
|
-
|
|
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
|
|
599
|
-
const
|
|
600
|
-
|
|
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:
|
|
648
|
+
{ name: "drop2run", version: SERVER_VERSION },
|
|
627
649
|
{
|
|
628
650
|
instructions: [
|
|
629
651
|
"Publishes static sites to Drop2Run.",
|
|
630
652
|
"",
|
|
631
|
-
"Use
|
|
632
|
-
"
|
|
633
|
-
"publish over an existing one. list_sites
|
|
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
|
-
"
|
|
668
|
+
"publish_files",
|
|
642
669
|
{
|
|
643
|
-
title: "Publish
|
|
644
|
-
description: "Publishes one 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
|
-
|
|
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
|
-
({
|
|
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.
|
|
4
|
-
"description": "MCP server that publishes a folder or
|
|
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": {
|