diff --git a/packages/opsxdb-mcp/README.md b/packages/opsxdb-mcp/README.md index 83ef6d1..fd450c0 100644 --- a/packages/opsxdb-mcp/README.md +++ b/packages/opsxdb-mcp/README.md @@ -2,14 +2,16 @@ MCP (Model Context Protocol) launcher for OpsxDB. -This package ships a small Node CLI that **spawns the Go `opsxdb-mcp` binary** over stdio. Build the binary from the OpsxDB repo, then point the launcher at it. +This package ships a small Node CLI that **spawns the Go `opsxdb-mcp` binary** over stdio. Build the binary from the OpsxDB repo, then point the launcher at it (or put the Go binary on `PATH`). + +> The npm bin is also named `opsxdb-mcp`. The launcher only execs a **native** Go/ELF/Mach-O/PE binary, so it will not recurse into its own npm shim. ## Install ```bash npm i -g @rlwj-lab/opsxdb-mcp # or -npx -y @rlwj-lab/opsxdb-mcp +npx -y @rlwj-lab/opsxdb-mcp@0.1.1 ``` ## Build the Go binary @@ -26,6 +28,8 @@ Put `bin/opsxdb-mcp` on `PATH`, or set: export OPSXDB_MCP_BIN=/absolute/path/to/opsxdb-mcp ``` +If the Go binary is missing, the CLI exits with a clear error (not `command not found`). + ## Cursor / Claude Desktop ```json diff --git a/packages/opsxdb-mcp/dist/resolve-binary.js b/packages/opsxdb-mcp/dist/resolve-binary.js index 92ebc42..eebd4d0 100644 --- a/packages/opsxdb-mcp/dist/resolve-binary.js +++ b/packages/opsxdb-mcp/dist/resolve-binary.js @@ -7,11 +7,16 @@ const os = require('os'); /** * Resolve the OpsxDB Go MCP binary path. * Order: OPSXDB_MCP_BIN → PATH → common install locations. + * Skips this package's Node launcher and any #! script shims (npm/npx bin + * is also named opsxdb-mcp and must not be mistaken for the Go server). * @returns {string|null} */ function resolveBinary() { const envBin = process.env.OPSXDB_MCP_BIN; - if (envBin && isExecutable(envBin)) return path.resolve(envBin); + if (envBin) { + const resolved = path.resolve(envBin); + if (isGoMcpBinary(resolved)) return resolved; + } const onPath = findOnPath('opsxdb-mcp'); if (onPath) return onPath; @@ -25,7 +30,7 @@ function resolveBinary() { '/opt/homebrew/bin/opsxdb-mcp', ]; for (const c of candidates) { - if (isExecutable(c)) return c; + if (isGoMcpBinary(c)) return c; } return null; } @@ -35,16 +40,38 @@ function findOnPath(name) { for (const dir of pathEnv.split(path.delimiter)) { if (!dir) continue; const p = path.join(dir, name); - if (isExecutable(p)) return p; - if (process.platform === 'win32' && isExecutable(p + '.exe')) return p + '.exe'; + if (isGoMcpBinary(p)) return p; + if (process.platform === 'win32') { + const pe = p + '.exe'; + if (isGoMcpBinary(pe)) return pe; + } } return null; } -function isExecutable(filePath) { +function selfCliPath() { + return path.resolve(__dirname, '..', 'bin', 'cli.js'); +} + +function isSameFile(a, b) { try { + return fs.realpathSync(a) === fs.realpathSync(b); + } catch { + return false; + } +} + +/** + * True only for a real Go/native opsxdb-mcp binary (ELF / Mach-O / PE), + * not npm bin shims or this package's Node cli.js. + */ +function isGoMcpBinary(filePath) { + try { + if (!filePath) return false; const st = fs.statSync(filePath); if (!st.isFile()) return false; + if (isSameFile(filePath, selfCliPath())) return false; + if (!isLikelyNativeBinary(filePath)) return false; fs.accessSync(filePath, fs.constants.X_OK); return true; } catch { @@ -52,6 +79,44 @@ function isExecutable(filePath) { } } +function isLikelyNativeBinary(filePath) { + let fd; + try { + fd = fs.openSync(filePath, 'r'); + const buf = Buffer.alloc(8); + const n = fs.readSync(fd, buf, 0, 8, 0); + if (n < 2) return false; + // Reject scripts (npm shims, node shebang) + if (buf[0] === 0x23 && buf[1] === 0x21) return false; // #! + // ELF + if (n >= 4 && buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46) { + return true; + } + // PE + if (buf[0] === 0x4d && buf[1] === 0x5a) return true; + if (n < 4) return false; + const be = buf.readUInt32BE(0); + const le = buf.readUInt32LE(0); + // Mach-O 64 / 32 (both endians) + fat + const macho = new Set([ + 0xfeedfacf, 0xcffaedfe, 0xfeedface, 0xcefaedfe, + 0xcafebabe, 0xbebafeca, 0xcafebabf, 0xbfbafeca, + ]); + if (macho.has(be) || macho.has(le)) return true; + return false; + } catch { + return false; + } finally { + if (fd !== undefined) { + try { + fs.closeSync(fd); + } catch { + /* ignore */ + } + } + } +} + function missingBinaryMessage() { return `opsxdb-mcp: Go binary not found. @@ -63,7 +128,7 @@ Build it from the opsxdb repo: cd server && go build -o ../bin/opsxdb-mcp ./cmd/opsxdb-mcp Then either: - - put bin/opsxdb-mcp on your PATH, or + - put that binary on your PATH (ahead of any npm shim), or - export OPSXDB_MCP_BIN=/absolute/path/to/opsxdb-mcp Cursor / Claude Desktop example: @@ -89,4 +154,5 @@ Config files (same as native binary): module.exports = { resolveBinary, missingBinaryMessage, + isGoMcpBinary, }; diff --git a/packages/opsxdb-mcp/package.json b/packages/opsxdb-mcp/package.json index 1ca4618..560b07c 100644 --- a/packages/opsxdb-mcp/package.json +++ b/packages/opsxdb-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@rlwj-lab/opsxdb-mcp", - "version": "0.1.0", + "version": "0.1.1", "description": "MCP server for opsxdb", "main": "index.js", "bin": {