From a606d341fb023f80554a601fa613c1231aa2a607 Mon Sep 17 00:00:00 2001 From: Phuc Chu <90998525+PhucChuHoang@users.noreply.github.com> Date: Tue, 29 Sep 2026 21:55:14 +0700 Subject: [PATCH] fix(memory): start MCP server when launched through the bin symlink (#302) npx and global installs run the ai-devkit-memory bin symlink, so process.argv[1] is the link path while import.meta.url is the resolved file. The strict comparison never matched and the process exited without starting the server. Compare real paths via isMainModule(), returning false when argv[1] is missing or cannot be resolved. --- packages/memory/src/entrypoint.ts | 15 ++++++ packages/memory/src/index.ts | 4 +- packages/memory/tests/unit/entrypoint.test.ts | 48 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 packages/memory/src/entrypoint.ts create mode 100644 packages/memory/tests/unit/entrypoint.test.ts diff --git a/packages/memory/src/entrypoint.ts b/packages/memory/src/entrypoint.ts new file mode 100644 index 00000000..6b0294db --- /dev/null +++ b/packages/memory/src/entrypoint.ts @@ -0,0 +1,15 @@ +import { realpathSync } from "fs"; +import { fileURLToPath } from "url"; + +// npx and global installs launch the bin symlink, so argv[1] is the link while +// import.meta.url is the resolved file. Compare real paths. +export function isMainModule(argv1: string | undefined, moduleUrl: string): boolean { + if (!argv1) { + return false; + } + try { + return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl)); + } catch { + return false; + } +} diff --git a/packages/memory/src/index.ts b/packages/memory/src/index.ts index 2ab33334..a8fc866b 100644 --- a/packages/memory/src/index.ts +++ b/packages/memory/src/index.ts @@ -1,13 +1,13 @@ #!/usr/bin/env node -import { fileURLToPath } from "url"; +import { isMainModule } from "./entrypoint.js"; import { runServer } from "./server.js"; export * from "./api.js"; // Only start MCP server when this file is run directly as a binary // Not when imported as a library (e.g., by CLI commands) -if (process.argv[1] === fileURLToPath(import.meta.url)) { +if (isMainModule(process.argv[1], import.meta.url)) { runServer().catch((error: Error) => { console.error("Failed to start server:", error); process.exit(1); diff --git a/packages/memory/tests/unit/entrypoint.test.ts b/packages/memory/tests/unit/entrypoint.test.ts new file mode 100644 index 00000000..a85b9ca0 --- /dev/null +++ b/packages/memory/tests/unit/entrypoint.test.ts @@ -0,0 +1,48 @@ +import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { pathToFileURL } from "url"; +import { isMainModule } from "../../src/entrypoint"; + +describe("isMainModule", () => { + let dir: string; + let modulePath: string; + let moduleUrl: string; + + beforeEach(() => { + dir = mkdtempSync(join(tmpdir(), "memory-entrypoint-")); + modulePath = join(dir, "index.js"); + writeFileSync(modulePath, ""); + moduleUrl = pathToFileURL(modulePath).href; + }); + + afterEach(() => { + rmSync(dir, { recursive: true, force: true }); + }); + + it("returns true when argv[1] is the module file", () => { + expect(isMainModule(modulePath, moduleUrl)).toBe(true); + }); + + it("returns true when argv[1] is a bin symlink to the module file", () => { + const binPath = join(dir, "ai-devkit-memory"); + symlinkSync(modulePath, binPath); + + expect(isMainModule(binPath, moduleUrl)).toBe(true); + }); + + it("returns false when argv[1] is a different file", () => { + const otherPath = join(dir, "cli.js"); + writeFileSync(otherPath, ""); + + expect(isMainModule(otherPath, moduleUrl)).toBe(false); + }); + + it("returns false when argv[1] is undefined", () => { + expect(isMainModule(undefined, moduleUrl)).toBe(false); + }); + + it("returns false when argv[1] does not exist", () => { + expect(isMainModule(join(dir, "missing.js"), moduleUrl)).toBe(false); + }); +});