Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/cli/commands/tool-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -195,9 +195,11 @@ export async function handleToolCall(args: string[]): Promise<void> {
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,
});

Expand Down
9 changes: 7 additions & 2 deletions src/daemon/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<ReturnType<typeof conn.client.callTool>>;
try {
sdkResult = await Promise.race([
conn.client.callTool({
name: body.tool,
name: resolvedTool,
arguments: body.params,
}),
new Promise<never>((_, reject) => {
Expand Down
20 changes: 19 additions & 1 deletion src/schema/introspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,32 @@ 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,
toolName: string,
serviceName?: string,
): Promise<SchemaOutput | null> {
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;

Expand Down
Loading