fix(mcp): normalize path separators in LSP/ast-grep cli candidate detection (fixes #4151)

On Windows, path.resolve() returns paths with backslash separators. The previous endsWith("dist/cli.js") check uses forward slashes and always returned false on Windows, causing both LSP and ast-grep MCPs to fall through to the bootstrap path even when the dist cli exists. Result: LSP MCP completely unusable on Windows with MODULE_NOT_FOUND for dist/packages/lsp-tools-mcp/dist/cli.js.

Fix: derive a platform-aware suffix at module load time by replacing forward slashes in DIST_CLI_REL / SOURCE_CLI_REL with path.sep, then use that suffix in the endsWith check.

Verification: all 4 src/mcp/lsp.test.ts cases pass on Windows (previously 2 failed); all 13 src/mcp/ast-grep.test.ts cases pass (previously 2 failed). Total: 17/17 src/mcp tests green. bun run typecheck clean.
This commit is contained in:
MoerAI
2026-05-20 11:43:21 +09:00
committed by YeonGyu-Kim
parent fcb96841f8
commit ed6e9955da
5 changed files with 79 additions and 8 deletions
+30 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it } from "bun:test"
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { pathToFileURL } from "node:url"
@@ -66,6 +66,35 @@ describe("createLspMcpConfig", () => {
expect(config.command).toEqual([bunPath, sourceCliPath, "mcp"])
})
it("does not resolve the MCP command from the opened workspace", () => {
// given
const packageRoot = createTemporaryDirectory("omo-lsp-safe-package-root-")
const workspaceRoot = createTemporaryDirectory("omo-lsp-malicious-workspace-")
const moduleFilePath = join(packageRoot, "dist", "index.js")
const workspaceCliPath = join(workspaceRoot, "packages", "lsp-tools-mcp", "dist", "cli.js")
const gitPath = join(packageRoot, "bin", "git")
const bunPath = join(packageRoot, "bin", "bun")
const nodePath = join(packageRoot, "bin", "node")
const npmPath = join(packageRoot, "bin", "npm")
mkdirSync(join(packageRoot, "dist"), { recursive: true })
mkdirSync(join(workspaceRoot, "packages", "lsp-tools-mcp", "dist"), { recursive: true })
writeFileSync(join(packageRoot, "package.json"), JSON.stringify({ name: "oh-my-opencode" }), "utf-8")
writeFileSync(workspaceCliPath, "console.log('malicious')\n", "utf-8")
// when
const config = createLspMcpConfig({
cwd: workspaceRoot,
moduleUrl: pathToFileURL(moduleFilePath).href,
resolveExecutable: createResolver({ bun: bunPath, git: gitPath, node: nodePath, npm: npmPath }),
})
// then
expect(config.enabled).toBe(true)
expect(config.command[1]).not.toBe(workspaceCliPath)
expect(config.command[1]).toBe("-e")
expect(config.command[3]).toBe(packageRoot)
})
it("returns a bootstrap command when no LSP cli entrypoint exists", () => {
// given
const packageRoot = createTemporaryDirectory("omo-lsp-missing-root-")