diff --git a/src/cli/doctor/checks/tools-lsp.ts b/src/cli/doctor/checks/tools-lsp.ts index 945621367..d55b5ec46 100644 --- a/src/cli/doctor/checks/tools-lsp.ts +++ b/src/cli/doctor/checks/tools-lsp.ts @@ -1,9 +1,11 @@ -import { getAllServers } from "../../../tools/lsp/config" +import { createLspMcpConfig } from "../../../mcp/lsp" export function getInstalledLspServers(): Array<{ id: string; extensions: string[] }> { - const servers = getAllServers() + const lspMcpConfig = createLspMcpConfig() - return servers - .filter((s) => s.installed && !s.disabled) - .map((s) => ({ id: s.id, extensions: s.extensions })) + if (!lspMcpConfig) { + return [] + } + + return [{ id: "lsp-tools-mcp", extensions: ["*"] }] } diff --git a/src/mcp/index.ts b/src/mcp/index.ts index bc9da4d31..77431d3e5 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -1,6 +1,7 @@ import { createWebsearchConfig } from "./websearch" import { context7 } from "./context7" import { grep_app } from "./grep-app" +import { createLspMcpConfig, type LocalMcpConfig } from "./lsp" import type { OhMyOpenCodeConfig } from "../config/schema" export { McpNameSchema, type McpName } from "./types" @@ -13,8 +14,10 @@ type RemoteMcpConfig = { oauth?: false } +type BuiltinMcpConfig = RemoteMcpConfig | LocalMcpConfig + export function createBuiltinMcps(disabledMcps: string[] = [], config?: OhMyOpenCodeConfig) { - const mcps: Record = {} + const mcps: Record = {} if (!disabledMcps.includes("websearch")) { const websearchConfig = createWebsearchConfig(config?.websearch) @@ -31,5 +34,12 @@ export function createBuiltinMcps(disabledMcps: string[] = [], config?: OhMyOpen mcps.grep_app = grep_app } + if (!disabledMcps.includes("lsp")) { + const lspConfig = createLspMcpConfig() + if (lspConfig) { + mcps.lsp = lspConfig + } + } + return mcps } diff --git a/src/mcp/lsp.ts b/src/mcp/lsp.ts new file mode 100644 index 000000000..de7a82a85 --- /dev/null +++ b/src/mcp/lsp.ts @@ -0,0 +1,48 @@ +import { existsSync } from "node:fs" +import { resolve } from "node:path" +import { fileURLToPath } from "node:url" + +const SUBMODULE_REL = "vendor/lsp-tools-mcp" +const CLI_REL = "dist/cli.js" +const PROJECT_LSP_CONFIG = ".opencode/lsp.json" + +export type LocalMcpConfig = { + type: "local" + command: string[] + enabled: boolean + environment?: Record +} + +function resolveLspCliPathCandidates(): string[] { + const candidates: string[] = [] + + try { + const currentFilePath = fileURLToPath(import.meta.url) + candidates.push(resolve(currentFilePath, "..", "..", "..", SUBMODULE_REL, CLI_REL)) + candidates.push(resolve(currentFilePath, "..", "..", SUBMODULE_REL, CLI_REL)) + candidates.push(resolve(currentFilePath, "..", SUBMODULE_REL, CLI_REL)) + } catch { + // ignore and fall through to cwd-based candidate + } + + candidates.push(resolve(process.cwd(), SUBMODULE_REL, CLI_REL)) + + return candidates +} + +export function createLspMcpConfig(): LocalMcpConfig | null { + const cliPath = resolveLspCliPathCandidates().find((candidatePath) => existsSync(candidatePath)) + + if (!cliPath) { + return null + } + + return { + type: "local", + command: ["node", cliPath, "mcp"], + enabled: true, + environment: { + LSP_TOOLS_MCP_PROJECT_CONFIG: PROJECT_LSP_CONFIG, + }, + } +} diff --git a/src/mcp/types.ts b/src/mcp/types.ts index b3a24b8a7..f5e7f59a8 100644 --- a/src/mcp/types.ts +++ b/src/mcp/types.ts @@ -1,6 +1,6 @@ import { z } from "zod" -export const McpNameSchema = z.enum(["websearch", "context7", "grep_app"]) +export const McpNameSchema = z.enum(["websearch", "context7", "grep_app", "lsp"]) export type McpName = z.infer diff --git a/src/mcp/zauc-mocks-mcp-index/index.test.ts b/src/mcp/zauc-mocks-mcp-index/index.test.ts index f14215b56..d69ec1609 100644 --- a/src/mcp/zauc-mocks-mcp-index/index.test.ts +++ b/src/mcp/zauc-mocks-mcp-index/index.test.ts @@ -14,6 +14,7 @@ describe("createBuiltinMcps", () => { expect(result.websearch).toBeDefined() expect(result.context7).toBeDefined() expect(result.grep_app).toBeDefined() + expect(result.lsp).toBeDefined() }) test("should filter out disabled MCPs", () => { @@ -27,11 +28,12 @@ describe("createBuiltinMcps", () => { expect(result.websearch).toBeUndefined() expect(result.context7).toBeDefined() expect(result.grep_app).toBeDefined() + expect(result.lsp).toBeDefined() }) test("should return empty array when all MCPs are disabled", () => { // given - disable all known MCPs - const disabledMcps = ["websearch", "context7", "grep_app"] + const disabledMcps = ["websearch", "context7", "grep_app", "lsp"] // when const result = createBuiltinMcps(disabledMcps) @@ -41,6 +43,7 @@ describe("createBuiltinMcps", () => { expect(remainingMcpNames).not.toContain("websearch") expect(remainingMcpNames).not.toContain("context7") expect(remainingMcpNames).not.toContain("grep_app") + expect(remainingMcpNames).not.toContain("lsp") expect(remainingMcpNames).toEqual([]) }) })