fix(mcp-npm): v0.1.1 skip npm shim when resolving Go binary
CI / Go server (push) Canceled after 0s
CI / Web + core packages (push) Canceled after 0s
CI / Desktop (Tauri) cargo check (push) Canceled after 0s

npx installs a bin also named opsxdb-mcp; the launcher now only execs
native ELF/Mach-O/PE binaries so it no longer recurses into itself.
This commit is contained in:
logan committed 2026-10-15 13:30:05 +08:00
1 parent 979efb606d
commit 0aeee2b692
3 files changed
+79 -9

No files matched your search

+6 -2
View File
@@ -2,14 +2,16 @@
MCP (Model Context Protocol) launcher for OpsxDB. 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 ## Install
```bash ```bash
npm i -g @rlwj-lab/opsxdb-mcp npm i -g @rlwj-lab/opsxdb-mcp
# or # or
npx -y @rlwj-lab/opsxdb-mcp npx -y @rlwj-lab/opsxdb-mcp@0.1.1
``` ```
## Build the Go binary ## 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 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 ## Cursor / Claude Desktop
```json ```json
+72 -6
View File
@@ -7,11 +7,16 @@ const os = require('os');
/** /**
* Resolve the OpsxDB Go MCP binary path. * Resolve the OpsxDB Go MCP binary path.
* Order: OPSXDB_MCP_BIN → PATH → common install locations. * 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} * @returns {string|null}
*/ */
function resolveBinary() { function resolveBinary() {
const envBin = process.env.OPSXDB_MCP_BIN; 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'); const onPath = findOnPath('opsxdb-mcp');
if (onPath) return onPath; if (onPath) return onPath;
@@ -25,7 +30,7 @@ function resolveBinary() {
'/opt/homebrew/bin/opsxdb-mcp', '/opt/homebrew/bin/opsxdb-mcp',
]; ];
for (const c of candidates) { for (const c of candidates) {
if (isExecutable(c)) return c; if (isGoMcpBinary(c)) return c;
} }
return null; return null;
} }
@@ -35,16 +40,38 @@ function findOnPath(name) {
for (const dir of pathEnv.split(path.delimiter)) { for (const dir of pathEnv.split(path.delimiter)) {
if (!dir) continue; if (!dir) continue;
const p = path.join(dir, name); const p = path.join(dir, name);
if (isExecutable(p)) return p; if (isGoMcpBinary(p)) return p;
if (process.platform === 'win32' && isExecutable(p + '.exe')) return p + '.exe'; if (process.platform === 'win32') {
const pe = p + '.exe';
if (isGoMcpBinary(pe)) return pe;
}
} }
return null; return null;
} }
function isExecutable(filePath) { function selfCliPath() {
return path.resolve(__dirname, '..', 'bin', 'cli.js');
}
function isSameFile(a, b) {
try { 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); const st = fs.statSync(filePath);
if (!st.isFile()) return false; if (!st.isFile()) return false;
if (isSameFile(filePath, selfCliPath())) return false;
if (!isLikelyNativeBinary(filePath)) return false;
fs.accessSync(filePath, fs.constants.X_OK); fs.accessSync(filePath, fs.constants.X_OK);
return true; return true;
} catch { } 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() { function missingBinaryMessage() {
return `opsxdb-mcp: Go binary not found. 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 cd server && go build -o ../bin/opsxdb-mcp ./cmd/opsxdb-mcp
Then either: 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 - export OPSXDB_MCP_BIN=/absolute/path/to/opsxdb-mcp
Cursor / Claude Desktop example: Cursor / Claude Desktop example:
@@ -89,4 +154,5 @@ Config files (same as native binary):
module.exports = { module.exports = {
resolveBinary, resolveBinary,
missingBinaryMessage, missingBinaryMessage,
isGoMcpBinary,
}; };
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@rlwj-lab/opsxdb-mcp", "name": "@rlwj-lab/opsxdb-mcp",
"version": "0.1.0", "version": "0.1.1",
"description": "MCP server for opsxdb", "description": "MCP server for opsxdb",
"main": "index.js", "main": "index.js",
"bin": { "bin": {