@thehammer/danx-dashboard-mcp 0.1.26 → 0.1.27
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/dist/entrypoint.js +35 -0
- package/dist/entrypoint.test.js +43 -0
- package/dist/index.js +5 -5
- package/package.json +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
/**
|
|
4
|
+
* True when this module is the process entrypoint (the published bin) rather
|
|
5
|
+
* than an `import` — the DX-1606 tool-defs generator + its drift test import
|
|
6
|
+
* the module to introspect tool schemas without booting the stdio server, so
|
|
7
|
+
* `boot()` + `server.connect()` must run ONLY for the real bin invocation.
|
|
8
|
+
*
|
|
9
|
+
* Why the realpath resolve matters (DX-1647): `import.meta.url` reports the
|
|
10
|
+
* module's REALPATH — Node resolves symlinks for ESM module URLs by default.
|
|
11
|
+
* When the bin runs via a SYMLINK — both `npx` and global installs link
|
|
12
|
+
* `node_modules/.bin/danx-dashboard-mcp` → `dist/index.js` — `process.argv[1]`
|
|
13
|
+
* is the symlink path, which `pathToFileURL` would NOT match against the
|
|
14
|
+
* realpath `import.meta.url` carries. So resolve argv[1]'s realpath first; then
|
|
15
|
+
* the entrypoint is detected under BOTH direct `node dist/index.js` and the
|
|
16
|
+
* symlinked `npx -y @thehammer/danx-dashboard-mcp` the worker spawns.
|
|
17
|
+
*
|
|
18
|
+
* Without this, npx imports the module, registers the tools, and exits 0
|
|
19
|
+
* WITHOUT starting the server — every dispatch then fails the worker's
|
|
20
|
+
* "MCP loaded" preflight (`declared-not-loaded=[danx-dashboard]`).
|
|
21
|
+
*/
|
|
22
|
+
export function isEntrypointModule(moduleUrl, argvPath) {
|
|
23
|
+
if (typeof argvPath !== "string" || argvPath.length === 0)
|
|
24
|
+
return false;
|
|
25
|
+
let resolved;
|
|
26
|
+
try {
|
|
27
|
+
resolved = realpathSync(argvPath);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// argv[1] may not resolve on disk (synthetic paths in tests) — fall back to
|
|
31
|
+
// the raw path so a non-symlinked match still holds.
|
|
32
|
+
resolved = argvPath;
|
|
33
|
+
}
|
|
34
|
+
return moduleUrl === pathToFileURL(resolved).href;
|
|
35
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { mkdtempSync, writeFileSync, symlinkSync, rmSync, realpathSync, } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { isEntrypointModule } from "./entrypoint.js";
|
|
7
|
+
describe("isEntrypointModule (DX-1647)", () => {
|
|
8
|
+
let dir;
|
|
9
|
+
let real;
|
|
10
|
+
let link;
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
dir = mkdtempSync(join(tmpdir(), "entrypoint-test-"));
|
|
13
|
+
real = join(dir, "index.js");
|
|
14
|
+
writeFileSync(real, "// stub entry\n");
|
|
15
|
+
link = join(dir, "danx-dashboard-mcp"); // mimics node_modules/.bin symlink
|
|
16
|
+
symlinkSync(real, link);
|
|
17
|
+
});
|
|
18
|
+
afterAll(() => {
|
|
19
|
+
rmSync(dir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
// import.meta.url always reports the module REALPATH — model that here.
|
|
22
|
+
const moduleUrl = () => pathToFileURL(realpathSync(real)).href;
|
|
23
|
+
it("true when argv[1] is the real file (direct `node index.js`)", () => {
|
|
24
|
+
expect(isEntrypointModule(moduleUrl(), real)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
it("true when argv[1] is a SYMLINK to the file (npx / global bin) — the fleet regression", () => {
|
|
27
|
+
// The symlink path !== the realpath, but the entrypoint MUST still be
|
|
28
|
+
// detected, or npx imports the module and exits without booting the server.
|
|
29
|
+
expect(link).not.toBe(realpathSync(link));
|
|
30
|
+
expect(isEntrypointModule(moduleUrl(), link)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
it("false when argv[1] is undefined (module imported, not run as bin)", () => {
|
|
33
|
+
expect(isEntrypointModule(moduleUrl(), undefined)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
it("false when argv[1] is an empty string", () => {
|
|
36
|
+
expect(isEntrypointModule(moduleUrl(), "")).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it("false when argv[1] points at a different real file", () => {
|
|
39
|
+
const other = join(dir, "other.js");
|
|
40
|
+
writeFileSync(other, "// other\n");
|
|
41
|
+
expect(isEntrypointModule(moduleUrl(), other)).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
* agent reads `body.error` + structured fields to decide next action.
|
|
49
49
|
* 5xx and network failures throw — never silently swallowed.
|
|
50
50
|
*/
|
|
51
|
-
import {
|
|
51
|
+
import { isEntrypointModule } from "./entrypoint.js";
|
|
52
52
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
53
53
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
54
54
|
import { z } from "zod";
|
|
@@ -382,10 +382,10 @@ async function main() {
|
|
|
382
382
|
// Boot the stdio server ONLY when run as the entrypoint (the published bin).
|
|
383
383
|
// Importing this module (the tool-defs generator + its drift test) registers
|
|
384
384
|
// the tools on `server` without reading env or attaching stdin — so the
|
|
385
|
-
// schemas can be introspected without spawning a dispatch.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (
|
|
385
|
+
// schemas can be introspected without spawning a dispatch. The check is
|
|
386
|
+
// symlink-aware (DX-1647) so it holds under the symlinked `npx` bin the worker
|
|
387
|
+
// spawns, not just a direct `node dist/index.js`.
|
|
388
|
+
if (isEntrypointModule(import.meta.url, process.argv[1])) {
|
|
389
389
|
main().catch((err) => {
|
|
390
390
|
console.error(`[danx-dashboard-mcp] fatal: ${err.message}`);
|
|
391
391
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehammer/danx-dashboard-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"description": "Stdio MCP server wrapping danxbot's dashboard /api/issues/* normalized DB-backed HTTP routes for dispatched agents (DX-704 Phase 2).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|