From 2c3148d4414d149039c2b7dc5e15100ee825ae04 Mon Sep 17 00:00:00 2001 From: MeloMei Date: Mon, 22 Jun 2026 14:21:06 +0800 Subject: [PATCH] fix(mcp): use ":" separator for tool keys to prevent underscore collisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tool keys were built as sanitize(server) + "_" + sanitize(tool), but sanitize() preserves underscores. Different (server, tool) pairs could produce identical keys, silently overwriting each other. The prompts and resources path in catalog.ts already uses ":" as separator — apply the same pattern for tool registration. Fixes #33306 --- packages/opencode/src/mcp/index.ts | 2 +- packages/opencode/test/mcp/lifecycle.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 08d58118c992..28d76177124e 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -643,7 +643,7 @@ export const layer = Layer.effect( } const timeout = requestTimeout(s, clientName, mcpConfig, defaultTimeout) for (const mcpTool of listed) { - const key = McpCatalog.sanitize(clientName) + "_" + McpCatalog.sanitize(mcpTool.name) + const key = McpCatalog.sanitize(clientName) + ":" + McpCatalog.sanitize(mcpTool.name) result[key] = McpCatalog.convertTool(mcpTool, client, timeout) } } diff --git a/packages/opencode/test/mcp/lifecycle.test.ts b/packages/opencode/test/mcp/lifecycle.test.ts index 34304624e680..a2c4f0955adc 100644 --- a/packages/opencode/test/mcp/lifecycle.test.ts +++ b/packages/opencode/test/mcp/lifecycle.test.ts @@ -359,7 +359,7 @@ it.instance( command: ["echo", "test"], }) - expect(Object.keys(yield* mcp.tools())).toEqual(["paged-server_tool-one", "paged-server_tool-two"]) + expect(Object.keys(yield* mcp.tools())).toEqual(["paged-server:tool-one", "paged-server:tool-two"]) expect(Object.keys(yield* mcp.prompts())).toEqual(["paged-server:prompt-one", "paged-server:prompt-two"]) expect(Object.keys(yield* mcp.resources())).toEqual(["paged-server:resource-one", "paged-server:resource-two"]) expect(serverState.listToolsCalls).toBe(2) @@ -913,7 +913,7 @@ it.instance( expect(statusName(result.status, "tools-only-server")).toBe("connected") expect(serverState.listToolsCalls).toBe(1) - expect(Object.keys(yield* mcp.tools())).toEqual(["tools-only-server_test_tool"]) + expect(Object.keys(yield* mcp.tools())).toEqual(["tools-only-server:test_tool"]) expect(yield* mcp.prompts()).toEqual({}) expect(yield* mcp.resources()).toEqual({}) expect(serverState.listPromptsCalls).toBe(0) @@ -1105,8 +1105,8 @@ it.instance( const tools = yield* mcp.tools() const keys = Object.keys(tools) - // Server name dots should be replaced with underscores - expect(keys.some((k) => k.startsWith("my_special-server_"))).toBe(true) + // Server name dots should be replaced with underscores, separator is ":" + expect(keys.some((k) => k.startsWith("my_special-server:"))).toBe(true) // Tool name dots should be replaced with underscores expect(keys.some((k) => k.endsWith("tool_b"))).toBe(true) expect(keys.length).toBe(2)