Skip to content

Commit 174725a

Browse files
cameroncookeclaude
andcommitted
ref(xcode-ide): Simplify bridge tool handler wiring
Extract shared bridge handler lookup so xcode-ide tools follow one path\nfor bridge availability and fallback behavior.\n\nExtract shared remote tool serialization used by both manager and\nstandalone bridge implementations to reduce duplication and drift.\n\nUpdate bridge tool tests/mocks to align with shared exports and keep\ncoverage around standalone fallback behavior. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3da6ff1 commit 174725a

12 files changed

Lines changed: 51 additions & 71 deletions

File tree

docs/TOOLS-CLI.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,4 @@ XcodeBuildMCP provides 73 canonical tools organized into 13 workflow groups.
189189

190190
---
191191

192-
*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-15T19:38:41.303Z UTC*
192+
*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-15T21:09:48.107Z UTC*

docs/TOOLS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,4 @@ This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP prov
204204

205205
---
206206

207-
*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-15T19:38:41.303Z UTC*
207+
*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-15T21:09:48.107Z UTC*

src/integrations/xcode-tools-bridge/core.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { execFile } from 'node:child_process';
22
import process from 'node:process';
33
import { promisify } from 'node:util';
4+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
45
import type { XcodeToolsBridgeClientStatus } from './client.ts';
56

67
const execFileAsync = promisify(execFile);
@@ -18,6 +19,17 @@ export type XcodeToolsBridgeStatus = {
1819
xcodeSessionId: string | null;
1920
};
2021

22+
export function serializeBridgeTool(tool: Tool): Record<string, unknown> {
23+
return {
24+
name: tool.name,
25+
title: tool.title,
26+
description: tool.description,
27+
inputSchema: tool.inputSchema,
28+
outputSchema: tool.outputSchema,
29+
annotations: tool.annotations,
30+
};
31+
}
32+
2133
export interface BuildXcodeToolsBridgeStatusArgs {
2234
workflowEnabled: boolean;
2335
proxiedToolCount: number;

src/integrations/xcode-tools-bridge/manager.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2-
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
32
import { log } from '../../utils/logger.ts';
43
import {
54
createErrorResponse,
@@ -11,6 +10,7 @@ import {
1110
buildXcodeToolsBridgeStatus,
1211
classifyBridgeError,
1312
getMcpBridgeAvailability,
13+
serializeBridgeTool,
1414
type XcodeToolsBridgeStatus,
1515
} from './core.ts';
1616
import { XcodeIdeToolService } from './tool-service.ts';
@@ -160,7 +160,7 @@ export class XcodeToolsBridgeManager {
160160
const tools = await this.service.listTools({ refresh: params.refresh !== false });
161161
const payload = {
162162
toolCount: tools.length,
163-
tools: tools.map((tool) => this.serializeTool(tool)),
163+
tools: tools.map(serializeBridgeTool),
164164
};
165165
return createTextResponse(JSON.stringify(payload, null, 2));
166166
} catch (error) {
@@ -200,17 +200,6 @@ export class XcodeToolsBridgeManager {
200200
}
201201
}
202202

203-
private serializeTool(tool: Tool): Record<string, unknown> {
204-
return {
205-
name: tool.name,
206-
title: tool.title,
207-
description: tool.description,
208-
inputSchema: tool.inputSchema,
209-
outputSchema: tool.outputSchema,
210-
annotations: tool.annotations,
211-
};
212-
}
213-
214203
private createBridgeFailureResponse(code: string, error: unknown): ToolResponse {
215204
const message = error instanceof Error ? error.message : String(error);
216205
return createErrorResponse(code, message);

src/integrations/xcode-tools-bridge/standalone.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import {
33
createTextResponse,
44
type ToolResponse,
55
} from '../../utils/responses/index.ts';
6-
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
76
import {
87
buildXcodeToolsBridgeStatus,
98
classifyBridgeError,
9+
serializeBridgeTool,
1010
type XcodeToolsBridgeStatus,
1111
} from './core.ts';
1212
import { XcodeIdeToolService } from './tool-service.ts';
@@ -75,7 +75,7 @@ export class StandaloneXcodeToolsBridge {
7575
JSON.stringify(
7676
{
7777
toolCount: tools.length,
78-
tools: tools.map((tool) => this.serializeTool(tool)),
78+
tools: tools.map(serializeBridgeTool),
7979
},
8080
null,
8181
2,
@@ -102,15 +102,4 @@ export class StandaloneXcodeToolsBridge {
102102
return createErrorResponse(classifyBridgeError(error, 'call'), message);
103103
}
104104
}
105-
106-
private serializeTool(tool: Tool): Record<string, unknown> {
107-
return {
108-
name: tool.name,
109-
title: tool.title,
110-
description: tool.description,
111-
inputSchema: tool.inputSchema,
112-
outputSchema: tool.outputSchema,
113-
annotations: tool.annotations,
114-
};
115-
}
116105
}

src/mcp/tools/xcode-ide/__tests__/bridge_tools.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ vi.mock('../../../../server/server-state.ts', () => ({
66

77
vi.mock('../../../../integrations/xcode-tools-bridge/core.ts', () => ({
88
buildXcodeToolsBridgeStatus: vi.fn(),
9+
classifyBridgeError: vi.fn(() => 'XCODE_MCP_UNAVAILABLE'),
910
getMcpBridgeAvailability: vi.fn(),
11+
serializeBridgeTool: vi.fn((tool) => tool),
1012
}));
1113

1214
const clientMocks = {

src/mcp/tools/xcode-ide/shared.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { ToolResponse } from '../../../types/common.ts';
2+
import type { XcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts';
3+
import { getServer } from '../../../server/server-state.ts';
4+
import { getXcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts';
5+
import { createErrorResponse } from '../../../utils/responses/index.ts';
6+
7+
export async function withBridgeToolHandler(
8+
callback: (bridge: XcodeToolsBridgeToolHandler) => Promise<ToolResponse>,
9+
): Promise<ToolResponse> {
10+
const bridge = getXcodeToolsBridgeToolHandler(getServer());
11+
if (!bridge) {
12+
return createErrorResponse('Bridge unavailable', 'Unable to initialize xcode tools bridge');
13+
}
14+
return callback(bridge);
15+
}

src/mcp/tools/xcode-ide/xcode_ide_call_tool.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as z from 'zod';
22
import type { ToolResponse } from '../../../types/common.ts';
3-
import { getServer } from '../../../server/server-state.ts';
4-
import { getXcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts';
53
import { createErrorResponse } from '../../../utils/responses/index.ts';
4+
import { withBridgeToolHandler } from './shared.ts';
65

76
const schemaObject = z.object({
87
remoteTool: z.string().min(1).describe('Exact remote Xcode MCP tool name.'),
@@ -23,16 +22,13 @@ const schemaObject = z.object({
2322
type Params = z.infer<typeof schemaObject>;
2423

2524
export async function xcodeIdeCallToolLogic(params: Params): Promise<ToolResponse> {
26-
const bridge = getXcodeToolsBridgeToolHandler(getServer());
27-
if (!bridge) {
28-
return createErrorResponse('Bridge unavailable', 'Unable to initialize xcode tools bridge');
29-
}
30-
31-
return bridge.callToolTool({
32-
remoteTool: params.remoteTool,
33-
arguments: params.arguments ?? {},
34-
timeoutMs: params.timeoutMs,
35-
});
25+
return withBridgeToolHandler((bridge) =>
26+
bridge.callToolTool({
27+
remoteTool: params.remoteTool,
28+
arguments: params.arguments ?? {},
29+
timeoutMs: params.timeoutMs,
30+
}),
31+
);
3632
}
3733

3834
export const schema = schemaObject.shape;

src/mcp/tools/xcode-ide/xcode_ide_list_tools.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as z from 'zod';
22
import type { ToolResponse } from '../../../types/common.ts';
3-
import { getServer } from '../../../server/server-state.ts';
4-
import { getXcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts';
53
import { createErrorResponse } from '../../../utils/responses/index.ts';
4+
import { withBridgeToolHandler } from './shared.ts';
65

76
const schemaObject = z.object({
87
refresh: z
@@ -14,11 +13,7 @@ const schemaObject = z.object({
1413
type Params = z.infer<typeof schemaObject>;
1514

1615
export async function xcodeIdeListToolsLogic(params: Params): Promise<ToolResponse> {
17-
const bridge = getXcodeToolsBridgeToolHandler(getServer());
18-
if (!bridge) {
19-
return createErrorResponse('Bridge unavailable', 'Unable to initialize xcode tools bridge');
20-
}
21-
return bridge.listToolsTool({ refresh: params.refresh });
16+
return withBridgeToolHandler(async (bridge) => bridge.listToolsTool({ refresh: params.refresh }));
2217
}
2318

2419
export const schema = schemaObject.shape;
Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
import type { ToolResponse } from '../../../types/common.ts';
2-
import { getServer } from '../../../server/server-state.ts';
3-
import { getXcodeToolsBridgeToolHandler } from '../../../integrations/xcode-tools-bridge/index.ts';
4-
import { createErrorResponse } from '../../../utils/responses/index.ts';
2+
import { withBridgeToolHandler } from './shared.ts';
53

64
export const schema = {};
75

86
export const handler = async (): Promise<ToolResponse> => {
9-
const bridge = getXcodeToolsBridgeToolHandler(getServer());
10-
if (!bridge) {
11-
return createErrorResponse('Bridge unavailable', 'Unable to initialize xcode tools bridge');
12-
}
13-
return bridge.disconnectTool();
7+
return withBridgeToolHandler(async (bridge) => bridge.disconnectTool());
148
};

0 commit comments

Comments
 (0)