Skip to content

Commit af358e2

Browse files
committed
🐛 fix(cli): resolve correct tool via direct invocation instead of flat catalog lookup
CLI tool subcommands like `simulator list` were resolving to the wrong handler because multiple workflows share the same CLI name (e.g. "list", "build", "test"). The flat byCliName map meant last-inserted won. Instead of re-resolving by name, pass the ToolDefinition directly from the workflow-scoped yargs handler via a new invokeDirect() method. For daemon wire communication, use mcpName (unique within the catalog) instead of the ambiguous cliName. Also skip the bridge availability check (xcrun --find mcpbridge) in CLI mode since xcode-ide has availability.cli: false, avoiding an unwanted Xcode authorization prompt.
1 parent 51c878f commit af358e2

4 files changed

Lines changed: 28 additions & 8 deletions

File tree

src/cli/register-tool-commands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ function registerToolSubcommand(
175175
const args = { ...toolParams, ...jsonArgs };
176176

177177
// Invoke the tool
178-
const response = await invoker.invoke(tool.cliName, args, {
178+
const response = await invoker.invokeDirect(tool, args, {
179179
runtime: 'cli',
180180
cliExposedWorkflowIds,
181181
socketPath,

src/runtime/__tests__/tool-invoker.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe('DefaultToolInvoker CLI routing', () => {
115115
env: undefined,
116116
}),
117117
);
118-
expect(daemonClientMock.invokeTool).toHaveBeenCalledWith('start-sim-log-cap', {
118+
expect(daemonClientMock.invokeTool).toHaveBeenCalledWith('start_sim_log_cap', {
119119
value: 'hello',
120120
});
121121
expect(directHandler).not.toHaveBeenCalled();

src/runtime/tool-catalog.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
} from '../visibility/exposure.ts';
1313
import { getConfig } from '../utils/config-store.ts';
1414
import { log } from '../utils/logging/index.ts';
15-
import { getMcpBridgeAvailability } from '../integrations/xcode-tools-bridge/core.ts';
1615

1716
export function createToolCatalog(tools: ToolDefinition[]): ToolCatalog {
1817
// Build lookup maps for fast resolution, deduplicating by mcpName so that
@@ -232,12 +231,14 @@ export async function buildDaemonToolCatalogFromManifest(opts?: {
232231
}
233232

234233
async function buildCliPredicateContext(): Promise<PredicateContext> {
235-
const bridge = await getMcpBridgeAvailability();
234+
// Skip bridge availability check in CLI mode — xcode-ide workflow has
235+
// availability.cli: false so the bridge result is unused, and the
236+
// xcrun --find mcpbridge call triggers an unwanted Xcode auth prompt.
236237
return {
237238
runtime: 'cli',
238239
config: getConfig(),
239240
runningUnderXcode: false,
240241
xcodeToolsActive: false,
241-
xcodeToolsAvailable: bridge.available,
242+
xcodeToolsAvailable: false,
242243
};
243244
}

src/runtime/tool-invoker.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ToolCatalog, ToolInvoker, InvokeOptions } from './types.ts';
1+
import type { ToolCatalog, ToolDefinition, ToolInvoker, InvokeOptions } from './types.ts';
22
import type { ToolResponse } from '../types/common.ts';
33
import { createErrorResponse } from '../utils/responses/index.ts';
44
import { DaemonClient } from '../cli/daemon-client.ts';
@@ -64,8 +64,27 @@ export class DefaultToolInvoker implements ToolInvoker {
6464
);
6565
}
6666

67-
const tool = resolved.tool;
67+
return this.executeTool(resolved.tool, args, opts);
68+
}
6869

70+
/**
71+
* Invoke a tool directly, bypassing catalog resolution.
72+
* Used by CLI where the correct ToolDefinition is already known
73+
* from workflow-scoped yargs routing.
74+
*/
75+
async invokeDirect(
76+
tool: ToolDefinition,
77+
args: Record<string, unknown>,
78+
opts: InvokeOptions,
79+
): Promise<ToolResponse> {
80+
return this.executeTool(tool, args, opts);
81+
}
82+
83+
private async executeTool(
84+
tool: ToolDefinition,
85+
args: Record<string, unknown>,
86+
opts: InvokeOptions,
87+
): Promise<ToolResponse> {
6988
const xcodeIdeRemoteToolName = tool.xcodeIdeRemoteToolName;
7089
const isDynamicXcodeIdeTool =
7190
tool.workflow === 'xcode-ide' && typeof xcodeIdeRemoteToolName === 'string';
@@ -149,7 +168,7 @@ export class DefaultToolInvoker implements ToolInvoker {
149168
}
150169

151170
try {
152-
const response = await client.invokeTool(tool.cliName, args);
171+
const response = await client.invokeTool(tool.mcpName, args);
153172
return opts.runtime === 'cli' ? enrichNextStepsForCli(response, this.catalog) : response;
154173
} catch (error) {
155174
return createErrorResponse(

0 commit comments

Comments
 (0)