Skip to content

Commit 0b21460

Browse files
committed
feat: consolidate test_simulator_id/name into unified test_simulator
- Unified tool accepts both simulatorId and simulatorName (XOR) - Added warning for useLatestOS with simulatorId - Reduces MCP context usage with single tool interface
1 parent 74929d5 commit 0b21460

2 files changed

Lines changed: 33 additions & 114 deletions

File tree

src/mcp/tools/simulator/test_simulator.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
/**
2+
* Simulator Test Plugin: Test Simulator (Unified)
3+
*
4+
* Runs tests for a project or workspace on a simulator by UUID or name.
5+
* Accepts mutually exclusive `projectPath` or `workspacePath`.
6+
* Accepts mutually exclusive `simulatorId` or `simulatorName`.
7+
*/
8+
19
import { z } from 'zod';
2-
import { handleTestLogic } from '../../../utils/index.js';
10+
import { handleTestLogic, log } from '../../../utils/index.js';
311
import { XcodePlatform } from '../../../utils/index.js';
412
import { ToolResponse } from '../../../types/common.js';
513
import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/command.js';
@@ -22,7 +30,11 @@ const baseSchemaObject = z.object({
2230
projectPath: z.string().optional().describe('Path to the .xcodeproj file'),
2331
workspacePath: z.string().optional().describe('Path to the .xcworkspace file'),
2432
scheme: z.string().describe('The scheme to use (Required)'),
25-
simulatorName: z.string().describe("Name of the simulator to use (e.g., 'iPhone 16') (Required)"),
33+
simulatorId: z
34+
.string()
35+
.optional()
36+
.describe('UUID of the simulator to use (obtained from listSimulators)'),
37+
simulatorName: z.string().optional().describe("Name of the simulator to use (e.g., 'iPhone 16')"),
2638
configuration: z.string().optional().describe('Build configuration (Debug, Release, etc.)'),
2739
derivedDataPath: z
2840
.string()
@@ -44,8 +56,8 @@ const baseSchemaObject = z.object({
4456
// Apply preprocessor to handle empty strings
4557
const baseSchema = z.preprocess(nullifyEmptyStrings, baseSchemaObject);
4658

47-
// Apply XOR validation: exactly one of projectPath OR workspacePath required
48-
const testSimulatorNameSchema = baseSchema
59+
// Apply XOR validation: exactly one of projectPath OR workspacePath, and exactly one of simulatorId OR simulatorName required
60+
const testSimulatorSchema = baseSchema
4961
.refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
5062
message: 'Either projectPath or workspacePath is required.',
5163
})
@@ -54,22 +66,31 @@ const testSimulatorNameSchema = baseSchema
5466
});
5567

5668
// Use z.infer for type safety
57-
type TestSimulatorNameParams = z.infer<typeof testSimulatorNameSchema>;
69+
type TestSimulatorParams = z.infer<typeof testSimulatorSchema>;
5870

59-
export async function test_simulator_nameLogic(
60-
params: TestSimulatorNameParams,
71+
export async function test_simulatorLogic(
72+
params: TestSimulatorParams,
6173
executor: CommandExecutor,
6274
): Promise<ToolResponse> {
75+
// Log warning if useLatestOS is provided with simulatorId
76+
if (params.simulatorId && params.useLatestOS !== undefined) {
77+
log(
78+
'warning',
79+
`useLatestOS parameter is ignored when using simulatorId (UUID implies exact device/OS)`,
80+
);
81+
}
82+
6383
return handleTestLogic(
6484
{
6585
projectPath: params.projectPath,
6686
workspacePath: params.workspacePath,
6787
scheme: params.scheme,
88+
simulatorId: params.simulatorId,
6889
simulatorName: params.simulatorName,
6990
configuration: params.configuration ?? 'Debug',
7091
derivedDataPath: params.derivedDataPath,
7192
extraArgs: params.extraArgs,
72-
useLatestOS: params.useLatestOS ?? false,
93+
useLatestOS: params.simulatorId ? false : (params.useLatestOS ?? false),
7394
preferXcodebuild: params.preferXcodebuild ?? false,
7495
platform: XcodePlatform.iOSSimulator,
7596
},
@@ -78,15 +99,15 @@ export async function test_simulator_nameLogic(
7899
}
79100

80101
export default {
81-
name: 'test_simulator_name',
102+
name: 'test_simulator',
82103
description:
83-
'Runs tests on a simulator by name using xcodebuild test and parses xcresult output. Works with both Xcode projects (.xcodeproj) and workspaces (.xcworkspace). IMPORTANT: Requires either projectPath or workspacePath, plus scheme and simulatorName. Example: test_simulator_name({ projectPath: "/path/to/MyProject.xcodeproj", scheme: "MyScheme", simulatorName: "iPhone 16" })',
104+
'Runs tests on a simulator by UUID or name using xcodebuild test and parses xcresult output. Works with both Xcode projects (.xcodeproj) and workspaces (.xcworkspace). IMPORTANT: Requires either projectPath or workspacePath, plus scheme and either simulatorId or simulatorName. Example: test_simulator({ projectPath: "/path/to/MyProject.xcodeproj", scheme: "MyScheme", simulatorName: "iPhone 16" })',
84105
schema: baseSchemaObject.shape, // MCP SDK compatibility
85106
handler: async (args: Record<string, unknown>): Promise<ToolResponse> => {
86107
try {
87108
// Runtime validation with XOR constraints
88-
const validatedParams = testSimulatorNameSchema.parse(args);
89-
return await test_simulator_nameLogic(validatedParams, getDefaultCommandExecutor());
109+
const validatedParams = testSimulatorSchema.parse(args);
110+
return await test_simulatorLogic(validatedParams, getDefaultCommandExecutor());
90111
} catch (error) {
91112
if (error instanceof z.ZodError) {
92113
// Format validation errors in a user-friendly way

src/mcp/tools/simulator/test_simulator_id.ts

Lines changed: 0 additions & 102 deletions
This file was deleted.

0 commit comments

Comments
 (0)