diff --git a/src/mcp/tool-bridge.ts b/src/mcp/tool-bridge.ts index 3d4f249..7529738 100644 --- a/src/mcp/tool-bridge.ts +++ b/src/mcp/tool-bridge.ts @@ -4,6 +4,8 @@ import type { McpClientManager } from "./client-manager.js"; const log = createLogger("mcp:tool-bridge"); +export const MCP_TOOL_PREFIX = "mcp__"; + interface DiscoveredMcpTool { name: string; serverName: string; @@ -76,10 +78,10 @@ export function buildMcpToolDefinitions(tools: DiscoveredMcpTool[]): any[] { return defs; } -function namespaceMcpTool(serverName: string, toolName: string): string { +export function namespaceMcpTool(serverName: string, toolName: string): string { const sanitizedServer = serverName.replace(/[^a-zA-Z0-9]/g, "_"); const sanitizedTool = toolName.replace(/[^a-zA-Z0-9]/g, "_"); - return `mcp__${sanitizedServer}__${sanitizedTool}`; + return `${MCP_TOOL_PREFIX}${sanitizedServer}__${sanitizedTool}`; } function mcpSchemaToZod(inputSchema: Record | undefined, z: any): any { diff --git a/src/plugin.ts b/src/plugin.ts index 4a174bc..4c99c2b 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -33,7 +33,12 @@ import { SkillResolver } from "./tools/skills/resolver.js"; import { autoRefreshModels } from "./models/sync.js"; import { readMcpConfigs, readSubagentNames } from "./mcp/config.js"; import { McpClientManager } from "./mcp/client-manager.js"; -import { buildMcpToolHookEntries, buildMcpToolDefinitions } from "./mcp/tool-bridge.js"; +import { + MCP_TOOL_PREFIX, + buildMcpToolHookEntries, + buildMcpToolDefinitions, + namespaceMcpTool, +} from "./mcp/tool-bridge.js"; import { createOpencodeClient } from "@opencode-ai/sdk"; import { ToolRegistry as CoreRegistry } from "./tools/core/registry.js"; import { LocalExecutor } from "./tools/executors/local.js"; @@ -65,10 +70,16 @@ const log = createLogger("plugin"); interface McpToolSummary { serverName: string; toolName: string; + callName?: string; description?: string; params?: string[]; } +function getMcpToolDefinitionName(mcpToolDefs: any[], index: number): string | undefined { + const name = mcpToolDefs[index]?.function?.name; + return typeof name === "string" && name.length > 0 ? name : undefined; +} + export function buildAvailableToolsSystemMessage( lastToolNames: string[], lastToolMap: Array<{ id: string; name: string }>, @@ -85,16 +96,23 @@ export function buildAvailableToolsSystemMessage( } if (mcpToolSummaries && mcpToolSummaries.length > 0) { - const servers = new Map(); - for (const s of mcpToolSummaries) { + const summariesWithCallNames = mcpToolSummaries.map((summary, index) => ({ + ...summary, + callName: summary.callName + ?? getMcpToolDefinitionName(mcpToolDefs, index) + ?? namespaceMcpTool(summary.serverName, summary.toolName), + })); + + const servers = new Map>(); + for (const s of summariesWithCallNames) { const list = servers.get(s.serverName) ?? []; list.push(s); servers.set(s.serverName, list); } const lines: string[] = [ - "MCP TOOLS — Use via Shell with the `mcptool` CLI.", - "Syntax: mcptool call [json-args]", + `MCP TOOLS — Use via direct tool calls (\`${MCP_TOOL_PREFIX}__\`).`, + "These tools are exposed as first-class tool calls (e.g. mcp__filesystem__read_file).", "", ]; @@ -102,12 +120,8 @@ export function buildAvailableToolsSystemMessage( lines.push(`Server: ${server}`); for (const t of tools) { const paramHint = t.params?.length ? ` (params: ${t.params.join(", ")})` : ""; - lines.push(` - ${t.toolName}${paramHint}${t.description ? " — " + t.description : ""}`); - } - if (tools.length > 0) { - const ex = tools[0]; - const exArgs = ex.params?.length ? ` '{"${ex.params[0]}":"..."}'` : ""; - lines.push(` Example: mcptool call ${server} ${ex.toolName}${exArgs}`); + const sourceHint = t.callName === t.toolName ? "" : ` (server: ${t.serverName}; tool: ${t.toolName})`; + lines.push(` - ${t.callName}${paramHint}${t.description ? " — " + t.description : ""}${sourceHint}`); } lines.push(""); } @@ -1877,6 +1891,7 @@ export const CursorPlugin: Plugin = async ({ $, directory, worktree, client, ser mcpToolSummaries = tools.map((t) => ({ serverName: t.serverName, toolName: t.name, + callName: namespaceMcpTool(t.serverName, t.name), description: t.description, params: t.inputSchema ? Object.keys((t.inputSchema as any).properties ?? {}) diff --git a/tests/unit/plugin-mcp-system-transform.test.ts b/tests/unit/plugin-mcp-system-transform.test.ts index 84e92df..4ac3c89 100644 --- a/tests/unit/plugin-mcp-system-transform.test.ts +++ b/tests/unit/plugin-mcp-system-transform.test.ts @@ -12,7 +12,7 @@ describe("Plugin MCP system transform", () => { expect(systemMessage).toContain("skill_search -> search"); }); - it("includes mcptool Shell instructions when summaries provided", () => { + it("includes direct MCP tool call instructions when summaries provided", () => { const systemMessage = buildAvailableToolsSystemMessage( ["read", "write"], [], @@ -37,15 +37,41 @@ describe("Plugin MCP system transform", () => { ], ); - expect(systemMessage).toContain("mcptool call"); + expect(systemMessage).toContain("direct tool calls"); + expect(systemMessage).toContain("mcp__"); + expect(systemMessage).toContain("mcp__hybrid_memory__memory_search"); expect(systemMessage).toContain("hybrid-memory"); expect(systemMessage).toContain("memory_search"); expect(systemMessage).toContain("memory_stats"); expect(systemMessage).toContain("query, limit"); - expect(systemMessage).toContain("Shell"); }); - it("includes multiple servers in Shell instructions", () => { + it("prints exact callable MCP tool names when server or tool names are sanitized", () => { + const systemMessage = buildAvailableToolsSystemMessage( + [], + [], + [ + { + type: "function", + function: { name: "mcp__test_filesystem__list_directory" }, + }, + ], + [ + { + serverName: "test-filesystem", + toolName: "list-directory", + description: "List dir", + params: ["path"], + }, + ], + ); + + expect(systemMessage).toContain("mcp__test_filesystem__list_directory"); + expect(systemMessage).toContain("server: test-filesystem"); + expect(systemMessage).toContain("tool: list-directory"); + }); + + it("includes multiple servers in MCP tool instructions", () => { const systemMessage = buildAvailableToolsSystemMessage( [], [],