From c35363d59094bcae1294ab2614c83a52b334fda8 Mon Sep 17 00:00:00 2001 From: Rodaddy Date: Sun, 26 Apr 2026 18:48:35 -0400 Subject: [PATCH] feat: auto-resolve service-prefixed tool names `mcp2cli n8n list_workflows` now resolves to `n8n_list_workflows` automatically since the service is already specified. Eliminates wasted calls from agents that don't know the prefix convention. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/cli/commands/tool-call.ts | 8 +++++--- src/daemon/server.ts | 9 +++++++-- src/schema/introspect.ts | 20 +++++++++++++++++++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/cli/commands/tool-call.ts b/src/cli/commands/tool-call.ts index c806624..db20228 100644 --- a/src/cli/commands/tool-call.ts +++ b/src/cli/commands/tool-call.ts @@ -10,7 +10,7 @@ import { loadConfig } from "../../config/index.ts"; import { connectToService, connectToHttpService } from "../../connection/index.ts"; import { connectToWebSocketService } from "../../connection/websocket-transport.ts"; import { callViaDaemon, getSchemaViaDaemon } from "../../process/index.ts"; -import { getToolSchema } from "../../schema/introspect.ts"; +import { getToolSchema, resolveToolName } from "../../schema/introspect.ts"; import { checkToolAccess, extractPolicy } from "../../access/index.ts"; import { printError } from "../errors.ts"; import { EXIT_CODES } from "../../types/index.ts"; @@ -195,9 +195,11 @@ export async function handleToolCall(args: string[]): Promise { return; // finally block closes connection } - // 6. Call tool via MCP protocol + // 6. Call tool via MCP protocol (auto-resolve prefixed names) + const allTools = await connection.client.listTools(); + const resolvedName = resolveToolName(allTools.tools, parsed.value.toolName, parsed.value.serviceName) ?? parsed.value.toolName; const result = await connection.client.callTool({ - name: parsed.value.toolName, + name: resolvedName, arguments: parsed.value.params, }); diff --git a/src/daemon/server.ts b/src/daemon/server.ts index dd654f7..e27ad2d 100644 --- a/src/daemon/server.ts +++ b/src/daemon/server.ts @@ -16,7 +16,7 @@ import type { DaemonListenConfig, } from "./types.ts"; import { formatToolResult } from "../invocation/format.ts"; -import { listToolsForService, getToolSchema } from "../schema/introspect.ts"; +import { listToolsForService, getToolSchema, resolveToolName } from "../schema/introspect.ts"; import { ConnectionError } from "../connection/errors.ts"; import { ToolError } from "../invocation/errors.ts"; import type { ErrorCode } from "../types/index.ts"; @@ -115,11 +115,16 @@ export function createDaemonServer(opts: DaemonServerOptions) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); + let resolvedTool = body.tool; + try { + const allTools = await conn.client.listTools(); + resolvedTool = resolveToolName(allTools.tools, body.tool, body.service) ?? body.tool; + } catch { /* listTools unavailable, use original name */ } let sdkResult: Awaited>; try { sdkResult = await Promise.race([ conn.client.callTool({ - name: body.tool, + name: resolvedTool, arguments: body.params, }), new Promise((_, reject) => { diff --git a/src/schema/introspect.ts b/src/schema/introspect.ts index 5b9b980..8408b6a 100644 --- a/src/schema/introspect.ts +++ b/src/schema/introspect.ts @@ -77,6 +77,23 @@ export async function listToolsForService( return allTools; } +/** + * Resolve a tool name, auto-prefixing with serviceName if exact match fails. + * e.g., "list_workflows" with service "n8n" resolves to "n8n_list_workflows" + */ +export function resolveToolName( + tools: { name: string }[], + toolName: string, + serviceName?: string, +): string | null { + if (tools.some((t) => t.name === toolName)) return toolName; + if (serviceName) { + const prefixed = `${serviceName}_${toolName}`; + if (tools.some((t) => t.name === prefixed)) return prefixed; + } + return null; +} + /** Get full schema for a specific tool by name */ export async function getToolSchema( client: Client, @@ -84,7 +101,8 @@ export async function getToolSchema( serviceName?: string, ): Promise { const response = await client.listTools(); - const tool = response.tools.find((t) => t.name === toolName); + const resolved = resolveToolName(response.tools, toolName, serviceName); + const tool = resolved ? response.tools.find((t) => t.name === resolved) : null; if (!tool) return null;