From 4dea3261187b95adc97b0c17fd3a9bbbc4252ab8 Mon Sep 17 00:00:00 2001 From: liuguanghui <2285190803@qq.com> Date: Mon, 2 Nov 2026 02:04:54 +0800 Subject: [PATCH] fix(mcp): ignore bare sanitized OpenCode server prefixes --- src/features/mcp/opencode-mcp.test.ts | 52 +++++++++++++++++++++++++++ src/features/mcp/opencode-mcp.ts | 9 ++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/features/mcp/opencode-mcp.test.ts b/src/features/mcp/opencode-mcp.test.ts index 92f9735c9..c92920ffe 100644 --- a/src/features/mcp/opencode-mcp.test.ts +++ b/src/features/mcp/opencode-mcp.test.ts @@ -125,6 +125,58 @@ describe("OpencodeMcp", () => { }); }); + it.each( + [OpencodeMcp, Opencodev1Mcp, MimocodeMcp].flatMap((Mcp) => + [false, true].flatMap((global) => + (Mcp === MimocodeMcp ? [false] : [false, true]).flatMap((envelope) => + ["a.b", "a_b"].map((serverName) => ({ + name: Mcp.name, + Mcp, + global, + envelope, + serverName, + })), + ), + ), + ), + )( + "$name should ignore bare server prefixes (global=$global, envelope=$envelope, server=$serverName)", + ({ Mcp, global, envelope, serverName }) => { + const servers = { [serverName]: { type: "local", command: ["node"] } }; + const mcp = new Mcp({ + outputRoot: testDir, + relativeDirPath: ".", + relativeFilePath: "opencode.json", + global, + fileContent: JSON.stringify({ + mcp: envelope ? { servers } : servers, + tools: { + a_b_: false, + a_b_read: true, + a_b_delete: false, + orphan_read: false, + }, + }), + }); + const imported = mcp.toRulesyncMcp().getJson(); + + expect(imported).toEqual({ + $schema: RULESYNC_MCP_SCHEMA_URL, + mcpServers: { + [serverName]: { + type: "stdio", + command: "node", + enabledTools: ["read"], + disabledTools: ["delete"], + }, + }, + [Mcp === MimocodeMcp ? "mimocode" : Mcp === Opencodev1Mcp ? "opencodev1" : "opencode"]: { + mcpServers: { orphan: { disabledTools: ["read"] } }, + }, + }); + }, + ); + describe("getSettablePaths", () => { it("should return correct paths for local mode", () => { const paths = OpencodeMcp.getSettablePaths(); diff --git a/src/features/mcp/opencode-mcp.ts b/src/features/mcp/opencode-mcp.ts index 984c285ea..fe730e637 100644 --- a/src/features/mcp/opencode-mcp.ts +++ b/src/features/mcp/opencode-mcp.ts @@ -219,8 +219,15 @@ function convertFromOpencodeFormat( remoteType: "sse" | "http", ): McpServers { const serverNames = Object.keys(opencodeMcp); + // A bare sanitized prefix has no tool suffix, so the owner lookup rejects + // it. Drop it here too, or the orphan importer splits a_b_ into server a + // and tool b_ when the listed server is a.b. const orphanTools = Object.fromEntries( - Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({ key, serverNames })), + Object.entries(tools ?? {}).filter( + ([key]) => + !findOpencodeToolOwner({ key, serverNames }) && + !serverNames.some((serverName) => key === opencodeMcpToolPermissionKey(serverName, "")), + ), ); return { ...orphanMcpToolFiltersToRulesync(opencodeMcp, orphanTools),