@helmdeck/mcp-bridge 0.3.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/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @helmdeck/mcp-bridge
2
+
3
+ Stdio MCP bridge for [helmdeck](https://github.com/tosin2013/helmdeck).
4
+ Connects Claude Code, Claude Desktop, OpenClaw, and Gemini CLI to a
5
+ helmdeck control plane via the [Model Context Protocol](https://modelcontextprotocol.io).
6
+
7
+ ## Quick start
8
+
9
+ ```sh
10
+ # One-shot, no install
11
+ HELMDECK_URL=https://helmdeck.example HELMDECK_TOKEN=... npx @helmdeck/mcp-bridge
12
+
13
+ # Or install globally
14
+ npm install -g @helmdeck/mcp-bridge
15
+ helmdeck-mcp
16
+ ```
17
+
18
+ ## Configuration
19
+
20
+ The bridge reads two environment variables:
21
+
22
+ | Variable | Description |
23
+ | --- | --- |
24
+ | `HELMDECK_URL` | Base URL of the helmdeck control plane (e.g. `https://helmdeck.example`) |
25
+ | `HELMDECK_TOKEN` | Bearer JWT issued from the Management UI's API Tokens panel |
26
+
27
+ ## Client snippets
28
+
29
+ The control plane exposes ready-to-paste configuration snippets at
30
+ `GET /api/v1/connect/{client}` for each supported client. Example:
31
+
32
+ ```sh
33
+ curl -H "Authorization: Bearer $HELMDECK_TOKEN" \
34
+ "$HELMDECK_URL/api/v1/connect/claude-code"
35
+ ```
36
+
37
+ Supported clients: `claude-code`, `claude-desktop`, `openclaw`, `gemini-cli`.
38
+
39
+ ## How it works
40
+
41
+ `postinstall` downloads the platform-matching `helmdeck-mcp` binary
42
+ from the corresponding GitHub Release, verifies its SHA256 against
43
+ `checksums.txt`, and places it in `bin/`. Set
44
+ `HELMDECK_MCP_SKIP_DOWNLOAD=1` to skip the download (useful in CI
45
+ images that bake the binary in another way).
46
+
47
+ ## License
48
+
49
+ Apache-2.0
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ // Launcher shim for @helmdeck/mcp-bridge.
3
+ //
4
+ // npm sets up the `helmdeck-mcp` bin symlink at install time, *before*
5
+ // postinstall runs, so this file must exist in the published tarball.
6
+ // At runtime it execs the platform-native binary that postinstall
7
+ // downloaded into the same directory; if the binary is missing it
8
+ // prints a clear remediation hint instead of a cryptic spawn error.
9
+
10
+ "use strict";
11
+
12
+ const path = require("path");
13
+ const fs = require("fs");
14
+ const { spawnSync } = require("child_process");
15
+
16
+ const binName = process.platform === "win32" ? "helmdeck-mcp.exe" : "helmdeck-mcp";
17
+ const binPath = path.join(__dirname, binName);
18
+
19
+ if (!fs.existsSync(binPath)) {
20
+ console.error(
21
+ `[helmdeck-mcp] native binary not found at ${binPath}.\n` +
22
+ `This usually means the postinstall step was skipped (HELMDECK_MCP_SKIP_DOWNLOAD=1)\n` +
23
+ `or the download failed. Re-run:\n\n` +
24
+ ` npm rebuild @helmdeck/mcp-bridge\n`
25
+ );
26
+ process.exit(127);
27
+ }
28
+
29
+ const result = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
30
+ if (result.error) {
31
+ console.error(`[helmdeck-mcp] failed to spawn ${binPath}: ${result.error.message}`);
32
+ process.exit(1);
33
+ }
34
+ process.exit(result.status === null ? 1 : result.status);
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@helmdeck/mcp-bridge",
3
+ "version": "0.3.0",
4
+ "description": "Stdio MCP bridge that connects Claude Code, Claude Desktop, OpenClaw, and Gemini CLI to a helmdeck control plane.",
5
+ "bin": {
6
+ "helmdeck-mcp": "bin/helmdeck-mcp.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "scripts/",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "postinstall": "node scripts/postinstall.js"
15
+ },
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/tosin2013/helmdeck.git",
22
+ "directory": "npm"
23
+ },
24
+ "homepage": "https://github.com/tosin2013/helmdeck",
25
+ "bugs": {
26
+ "url": "https://github.com/tosin2013/helmdeck/issues"
27
+ },
28
+ "license": "Apache-2.0",
29
+ "keywords": [
30
+ "mcp",
31
+ "model-context-protocol",
32
+ "helmdeck",
33
+ "claude",
34
+ "gemini",
35
+ "agent",
36
+ "bridge"
37
+ ]
38
+ }
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+ // postinstall: download the helmdeck-mcp binary matching the host's
3
+ // platform/arch from the GitHub release that corresponds to this
4
+ // package's version, verify its checksum against checksums.txt, and
5
+ // drop it into bin/ next to the launcher shim.
6
+ //
7
+ // Skipped automatically when HELMDECK_MCP_SKIP_DOWNLOAD=1 (so CI
8
+ // images that bake the binary in another way don't double-fetch).
9
+
10
+ "use strict";
11
+
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+ const https = require("https");
15
+ const crypto = require("crypto");
16
+ const zlib = require("zlib");
17
+ const { execSync } = require("child_process");
18
+
19
+ if (process.env.HELMDECK_MCP_SKIP_DOWNLOAD === "1") {
20
+ console.log("[helmdeck-mcp] HELMDECK_MCP_SKIP_DOWNLOAD=1, skipping binary download");
21
+ process.exit(0);
22
+ }
23
+
24
+ const pkg = require("../package.json");
25
+ const VERSION = `v${pkg.version}`;
26
+ const REPO = "tosin2013/helmdeck";
27
+
28
+ const PLATFORM_MAP = {
29
+ linux: "linux",
30
+ darwin: "darwin",
31
+ win32: "windows",
32
+ };
33
+ const ARCH_MAP = {
34
+ x64: "amd64",
35
+ arm64: "arm64",
36
+ };
37
+
38
+ const os = PLATFORM_MAP[process.platform];
39
+ const arch = ARCH_MAP[process.arch];
40
+ if (!os || !arch) {
41
+ console.error(`[helmdeck-mcp] unsupported platform ${process.platform}/${process.arch}`);
42
+ process.exit(1);
43
+ }
44
+
45
+ const ext = os === "windows" ? "zip" : "tar.gz";
46
+ const archiveName = `helmdeck-mcp_${pkg.version}_${os}_${arch}.${ext}`;
47
+ const baseURL = `https://github.com/${REPO}/releases/download/${VERSION}`;
48
+ const archiveURL = `${baseURL}/${archiveName}`;
49
+ const checksumsURL = `${baseURL}/checksums.txt`;
50
+
51
+ const binDir = path.join(__dirname, "..", "bin");
52
+ fs.mkdirSync(binDir, { recursive: true });
53
+
54
+ function get(url) {
55
+ return new Promise((resolve, reject) => {
56
+ https
57
+ .get(url, { headers: { "User-Agent": "helmdeck-mcp-postinstall" } }, (res) => {
58
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
59
+ resolve(get(res.headers.location));
60
+ return;
61
+ }
62
+ if (res.statusCode !== 200) {
63
+ reject(new Error(`GET ${url} -> ${res.statusCode}`));
64
+ return;
65
+ }
66
+ const chunks = [];
67
+ res.on("data", (c) => chunks.push(c));
68
+ res.on("end", () => resolve(Buffer.concat(chunks)));
69
+ res.on("error", reject);
70
+ })
71
+ .on("error", reject);
72
+ });
73
+ }
74
+
75
+ (async () => {
76
+ try {
77
+ console.log(`[helmdeck-mcp] downloading ${archiveURL}`);
78
+ const archive = await get(archiveURL);
79
+
80
+ console.log(`[helmdeck-mcp] verifying checksum`);
81
+ const checksums = (await get(checksumsURL)).toString("utf8");
82
+ const wantLine = checksums.split("\n").find((l) => l.endsWith(" " + archiveName));
83
+ if (!wantLine) {
84
+ throw new Error(`checksum entry for ${archiveName} not found in checksums.txt`);
85
+ }
86
+ const want = wantLine.split(/\s+/)[0];
87
+ const got = crypto.createHash("sha256").update(archive).digest("hex");
88
+ if (got !== want) {
89
+ throw new Error(`checksum mismatch: want ${want}, got ${got}`);
90
+ }
91
+
92
+ const archivePath = path.join(binDir, archiveName);
93
+ fs.writeFileSync(archivePath, archive);
94
+
95
+ console.log(`[helmdeck-mcp] extracting`);
96
+ if (ext === "zip") {
97
+ // Defer to a built-in unzip on each OS rather than ship a JS unzip dep.
98
+ execSync(`powershell -Command "Expand-Archive -Force '${archivePath}' '${binDir}'"`, {
99
+ stdio: "inherit",
100
+ });
101
+ } else {
102
+ // Stream-decompress + untar inline so we don't shell out on Linux/Darwin.
103
+ const tar = zlib.gunzipSync(archive);
104
+ extractTar(tar, binDir);
105
+ }
106
+ fs.unlinkSync(archivePath);
107
+
108
+ const binName = os === "windows" ? "helmdeck-mcp.exe" : "helmdeck-mcp";
109
+ const binPath = path.join(binDir, binName);
110
+ if (!fs.existsSync(binPath)) {
111
+ throw new Error(`expected ${binPath} after extraction`);
112
+ }
113
+ if (os !== "windows") {
114
+ fs.chmodSync(binPath, 0o755);
115
+ }
116
+
117
+ // bin/helmdeck-mcp.js is shipped in the tarball — npm has
118
+ // already symlinked it as the package's bin entry by the time
119
+ // postinstall runs, so we only need to drop the native binary
120
+ // next to it.
121
+ console.log(`[helmdeck-mcp] installed ${binPath}`);
122
+ } catch (err) {
123
+ console.error(`[helmdeck-mcp] postinstall failed: ${err.message}`);
124
+ process.exit(1);
125
+ }
126
+ })();
127
+
128
+ // Minimal POSIX-tar extractor: enough for goreleaser archives, which
129
+ // only contain regular files. Does not handle long-name extensions
130
+ // because goreleaser doesn't emit them for our binary names.
131
+ function extractTar(buf, dest) {
132
+ let offset = 0;
133
+ while (offset + 512 <= buf.length) {
134
+ const header = buf.slice(offset, offset + 512);
135
+ if (header[0] === 0) break;
136
+ const name = header.slice(0, 100).toString("utf8").replace(/\0.*$/, "");
137
+ const sizeStr = header.slice(124, 136).toString("utf8").replace(/\0.*$/, "").trim();
138
+ const size = parseInt(sizeStr, 8);
139
+ const type = header[156];
140
+ offset += 512;
141
+ if ((type === 0 || type === 0x30) && name) {
142
+ const out = path.join(dest, path.basename(name));
143
+ fs.writeFileSync(out, buf.slice(offset, offset + size));
144
+ }
145
+ offset += Math.ceil(size / 512) * 512;
146
+ }
147
+ }