drops-mcp 0.1.0 → 0.1.2

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 (3) hide show
  1. package/install.mjs +63 -37
  2. package/mcp.mjs +6 -6
  3. package/package.json +22 -6
package/install.mjs CHANGED
@@ -2,69 +2,95 @@
2
2
  /**
3
3
  * drops-install — wire the drops MCP server (and `drop` CLI) into your agents.
4
4
  *
5
- * Open-source, self-hosted counterpart to a one-command installer: detects the agent
6
- * CLIs you have and registers the `drops` MCP server, symlinks `drop` onto your PATH,
7
- * and prints copy-paste config for GUI MCP clients.
5
+ * Open-source one-command installer. Unlike hosted installers there's NO sign-in step
6
+ * (the managed tier is anonymous), so it's fewer steps. It:
7
+ * - registers the `drops` MCP in CLI agents (Claude Code, Codex, OpenCode, Amp)
8
+ * - writes/merges MCP config for GUI clients (Cursor, Claude Desktop, Windsurf)
9
+ * - symlinks `drop` onto your PATH and installs the skill for Claude/OpenClaw
8
10
  *
9
- * node skill/install.mjs # register into detected agents
10
- * node skill/install.mjs --print # just print config, change nothing
11
- *
12
- * Run `drop setup` (or `drop init` + `drop setup`) afterwards to provision the Blob token.
11
+ * npx drops-install # configure everything detected
12
+ * npx drops-install --print # show what it would do, change nothing
13
13
  */
14
14
 
15
15
  import { spawnSync } from "node:child_process";
16
- import { existsSync, symlinkSync, mkdirSync, rmSync } from "node:fs";
17
- import { homedir } from "node:os";
16
+ import { existsSync, symlinkSync, mkdirSync, rmSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { homedir, platform } from "node:os";
18
18
  import { join, dirname } from "node:path";
19
19
  import { fileURLToPath } from "node:url";
20
20
 
21
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
+ const SKILL_DIR = __dirname;
22
23
  const MCP = join(__dirname, "mcp.mjs");
23
24
  const DROP = join(__dirname, "drop.mjs");
24
25
  const PRINT = process.argv.includes("--print");
26
+ const HOME = homedir();
27
+ const PLAT = platform();
28
+ const SEP = PLAT === "win32" ? "\\" : "/";
25
29
 
26
- // When installed from npm, wire agents to the published runner (survives upgrades);
27
- // from a local clone, point at the local mcp.mjs.
28
- const PUBLISHED = __dirname.includes(`${require_sep()}node_modules${require_sep()}`);
29
- function require_sep() { return process.platform === "win32" ? "\\" : "/"; }
30
+ // When installed from npm, wire agents to the published runner; from a clone, the local file.
31
+ const PUBLISHED = __dirname.includes(`${SEP}node_modules${SEP}`);
30
32
  const MCP_CMD = PUBLISHED ? ["npx", "-y", "drops-mcp"] : [process.execPath, MCP];
31
33
 
32
34
  const ok = (m) => console.log(`\x1b[32m✓\x1b[0m ${m}`);
33
35
  const dim = (m) => console.log(`\x1b[2m${m}\x1b[0m`);
34
- const has = (cmd) => spawnSync(process.platform === "win32" ? "where" : "which", [cmd], { stdio: "ignore", shell: process.platform === "win32" }).status === 0;
35
-
36
- // CLI agents that support `<cli> mcp add <name> -- <command>`.
37
- const CLI_AGENTS = ["claude", "codex", "opencode", "amp"];
36
+ const has = (cmd) => spawnSync(PLAT === "win32" ? "where" : "which", [cmd], { stdio: "ignore", shell: PLAT === "win32" }).status === 0;
38
37
 
39
38
  console.log("drops-install — wiring the drops MCP into your agents\n");
40
-
41
39
  let wired = 0;
42
- for (const cli of CLI_AGENTS) {
40
+
41
+ // 1. CLI agents via `<cli> mcp add`
42
+ for (const cli of ["claude", "codex", "opencode", "amp"]) {
43
43
  if (!has(cli)) continue;
44
44
  const args = ["mcp", "add", "drops", "--", ...MCP_CMD];
45
45
  if (PRINT) { dim(`${cli} ${args.join(" ")}`); wired++; continue; }
46
- const r = spawnSync(cli, args, { stdio: "inherit", shell: process.platform === "win32" });
46
+ const r = spawnSync(cli, args, { stdio: ["ignore", "ignore", "ignore"], shell: PLAT === "win32" });
47
47
  if (r.status === 0) { ok(`registered drops MCP in ${cli}`); wired++; }
48
- else dim(`skipped ${cli} (add failed — may already exist)`);
48
+ else dim(`skipped ${cli} (already configured?)`);
49
49
  }
50
- if (!wired) dim("no CLI agents detected (claude / codex / opencode / amp)");
51
50
 
52
- // Symlink `drop` onto PATH for shell/agent use.
53
- const bin = join(homedir(), ".local", "bin");
51
+ // 2. GUI MCP clients via their config files (only if the app's dir exists → app installed)
52
+ const appSupport = PLAT === "darwin" ? join(HOME, "Library", "Application Support")
53
+ : PLAT === "win32" ? (process.env.APPDATA || join(HOME, "AppData", "Roaming"))
54
+ : join(HOME, ".config");
55
+ const GUI = [
56
+ { name: "Cursor", dir: join(HOME, ".cursor"), file: join(HOME, ".cursor", "mcp.json"), key: "mcpServers" },
57
+ { name: "Claude Desktop", dir: join(appSupport, "Claude"), file: join(appSupport, "Claude", "claude_desktop_config.json"), key: "mcpServers" },
58
+ { name: "Windsurf", dir: join(HOME, ".codeium", "windsurf"), file: join(HOME, ".codeium", "windsurf", "mcp_config.json"), key: "mcpServers" },
59
+ ];
60
+ function mergeMcp(file, key) {
61
+ let cfg = {};
62
+ if (existsSync(file)) { try { cfg = JSON.parse(readFileSync(file, "utf8")); } catch { return false; } }
63
+ cfg[key] = cfg[key] || {};
64
+ cfg[key].drops = { command: MCP_CMD[0], args: MCP_CMD.slice(1) };
65
+ writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n");
66
+ return true;
67
+ }
68
+ for (const g of GUI) {
69
+ if (!existsSync(g.dir)) continue;
70
+ if (PRINT) { dim(`write ${g.file} → ${g.key}.drops`); wired++; continue; }
71
+ try { mkdirSync(dirname(g.file), { recursive: true }); if (mergeMcp(g.file, g.key)) { ok(`configured ${g.name} (${g.file})`); wired++; } }
72
+ catch (e) { dim(`skipped ${g.name}: ${e.message}`); }
73
+ }
74
+
75
+ if (!wired) dim("no agents detected — add the MCP config below by hand");
76
+
77
+ // 3. `drop` on PATH
78
+ const bin = join(HOME, ".local", "bin");
54
79
  const link = join(bin, "drop");
55
- if (PRINT) {
56
- dim(`ln -s ${DROP} ${link}`);
57
- } else {
58
- try {
59
- mkdirSync(bin, { recursive: true });
60
- if (existsSync(link)) rmSync(link);
61
- symlinkSync(DROP, link);
62
- ok(`linked 'drop' → ${link} (ensure ${bin} is on your PATH)`);
63
- } catch (e) { dim(`could not symlink drop: ${e.message}`); }
80
+ if (PRINT) { dim(`ln -s ${DROP} ${link}`); }
81
+ else {
82
+ try { mkdirSync(bin, { recursive: true }); if (existsSync(link)) rmSync(link); symlinkSync(DROP, link); ok(`linked 'drop' → ${link}`); }
83
+ catch (e) { dim(`could not symlink drop: ${e.message}`); }
64
84
  }
65
85
 
66
- // Copy-paste config for GUI MCP clients (Cursor / Claude Desktop / Windsurf / Zed).
67
- console.log("\nFor GUI MCP clients, add this to your MCP config:\n");
68
- console.log(JSON.stringify({ mcpServers: { drops: { command: MCP_CMD[0], args: MCP_CMD.slice(1) } } }, null, 2));
86
+ // 4. skill auto-discovery for Claude Code / OpenClaw
87
+ const skillsDir = join(HOME, ".claude", "skills");
88
+ if (existsSync(skillsDir) && !PUBLISHED) {
89
+ const dest = join(skillsDir, "drop");
90
+ if (PRINT) { dim(`ln -s ${SKILL_DIR} ${dest}`); }
91
+ else { try { if (existsSync(dest)) rmSync(dest, { recursive: true, force: true }); symlinkSync(SKILL_DIR, dest); ok(`installed skill → ${dest}`); } catch {} }
92
+ }
69
93
 
70
- console.log("\nNext: drop init --domain ... (BYO domain/Blob) then drop setup --token vercel_blob_rw_...");
94
+ console.log("\nGUI clients not auto-detected? Add this to your MCP config:");
95
+ console.log(JSON.stringify({ mcpServers: { drops: { command: MCP_CMD[0], args: MCP_CMD.slice(1) } } }, null, 2));
96
+ console.log("\nReady. Try: drop report.html --managed (zero setup) · docs: https://drops.maxtechera.dev/docs");
package/mcp.mjs CHANGED
@@ -2,14 +2,14 @@
2
2
  /**
3
3
  * drops MCP server — the MCP-native publish primitive for HTML/artifacts your agents make.
4
4
  *
5
- * Open-source, self-hosted counterpart to Stacktree's MCP: an agent calls `publish_html`
6
- * and gets back a branded, password-protected, zero-knowledge link on YOUR own domain.
7
- * Every tool shells out to the battle-tested `drop` CLI (drop.mjs --json) so the MCP and
8
- * CLI share one pipeline. Nothing is hosted by a third party — it's your Vercel Blob.
5
+ * Open-source, self-hosted MCP publish primitive: an agent calls `publish_html` and gets
6
+ * back a branded, password-protected, zero-knowledge link on YOUR own domain. Every tool
7
+ * shells out to the `drop` CLI (drop.mjs --json) so the MCP and CLI share one pipeline.
8
+ * Nothing is hosted by a third party — it's your Vercel Blob.
9
9
  *
10
10
  * Wire it into an agent (stdio):
11
- * claude mcp add drops -- npx -y --prefix <repo>/skill drops-mcp # once published
12
- * # or, from a clone:
11
+ * claude mcp add drops -- npx -y drops-mcp
12
+ * # or, from a checkout:
13
13
  * claude mcp add drops -- node <repo>/skill/mcp.mjs
14
14
  *
15
15
  * Requires `drop setup` to have run (BLOB_READ_WRITE_TOKEN in ~/.drop/.env).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drops-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Open-source artifact sharing — publish HTML/Markdown/files as branded, password-protected, zero-knowledge links on your own domain, from any AI agent. CLI + MCP server. The open-source, self-hosted Stacktree alternative.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,16 +17,32 @@
17
17
  "SETUP.md"
18
18
  ],
19
19
  "keywords": [
20
- "mcp", "ai-agents", "claude", "claude-code", "artifacts", "file-sharing",
21
- "zero-knowledge", "cli", "self-hosted", "staticrypt", "vercel-blob",
22
- "password-protection", "stacktree-alternative", "publish-html"
20
+ "mcp",
21
+ "ai-agents",
22
+ "claude",
23
+ "claude-code",
24
+ "artifacts",
25
+ "file-sharing",
26
+ "zero-knowledge",
27
+ "cli",
28
+ "self-hosted",
29
+ "staticrypt",
30
+ "vercel-blob",
31
+ "password-protection",
32
+ "stacktree-alternative",
33
+ "publish-html"
23
34
  ],
24
- "repository": { "type": "git", "url": "https://github.com/maxtechera/drops-share" },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/maxtechera/drops-share"
38
+ },
25
39
  "homepage": "https://drops.maxtechera.dev",
26
40
  "bugs": "https://github.com/maxtechera/drops-share/issues",
27
41
  "author": "Max Techera (https://maxtechera.dev)",
28
42
  "license": "MIT",
29
- "engines": { "node": ">=18" },
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
30
46
  "dependencies": {
31
47
  "@modelcontextprotocol/sdk": "^1.0.0",
32
48
  "@vercel/blob": "^0.27.0",