Skip to content

Commit 683a467

Browse files
committed
feat(simulator): migrate simulator tools to session defaults
1 parent 9b506d1 commit 683a467

56 files changed

Lines changed: 1080 additions & 1515 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/session-aware-migration-todo.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,22 @@ Reference: `docs/session_management_plan.md`
3333
- [x] `src/mcp/tools/simulator/get_sim_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `simulatorId`, `simulatorName`, `configuration`, `useLatestOS`, `arch`.
3434

3535
## Simulator Runtime Actions
36-
- [ ] `src/mcp/tools/simulator/boot_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
37-
- [ ] `src/mcp/tools/simulator/install_app_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
38-
- [ ] `src/mcp/tools/simulator/launch_app_sim.ts` — session defaults: `simulatorId`, `simulatorName` (hydrate `simulatorUuid`).
39-
- [ ] `src/mcp/tools/simulator/launch_app_logs_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
40-
- [ ] `src/mcp/tools/simulator/stop_app_sim.ts` — session defaults: `simulatorId`, `simulatorName` (hydrate `simulatorUuid`).
41-
- [ ] `src/mcp/tools/simulator/record_sim_video.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
36+
- [x] `src/mcp/tools/simulator/boot_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
37+
- [x] `src/mcp/tools/simulator/install_app_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
38+
- [x] `src/mcp/tools/simulator/launch_app_sim.ts` — session defaults: `simulatorId`, `simulatorName` (hydrate `simulatorUuid`).
39+
- [x] `src/mcp/tools/simulator/launch_app_logs_sim.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
40+
- [x] `src/mcp/tools/simulator/stop_app_sim.ts` — session defaults: `simulatorId`, `simulatorName` (hydrate `simulatorUuid`).
41+
- [x] `src/mcp/tools/simulator/record_sim_video.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
4242

4343
## Simulator Management
44-
- [ ] `src/mcp/tools/simulator-management/erase_sims.ts` — session defaults: `simulatorId` (covers `simulatorUdid`).
45-
- [ ] `src/mcp/tools/simulator-management/set_sim_location.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
46-
- [ ] `src/mcp/tools/simulator-management/reset_sim_location.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
47-
- [ ] `src/mcp/tools/simulator-management/set_sim_appearance.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
48-
- [ ] `src/mcp/tools/simulator-management/sim_statusbar.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
44+
- [x] `src/mcp/tools/simulator-management/erase_sims.ts` — session defaults: `simulatorId` (covers `simulatorUdid`).
45+
- [x] `src/mcp/tools/simulator-management/set_sim_location.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
46+
- [x] `src/mcp/tools/simulator-management/reset_sim_location.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
47+
- [x] `src/mcp/tools/simulator-management/set_sim_appearance.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
48+
- [x] `src/mcp/tools/simulator-management/sim_statusbar.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
4949

5050
## Simulator Logging
51-
- [ ] `src/mcp/tools/logging/start_sim_log_cap.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
51+
- [x] `src/mcp/tools/logging/start_sim_log_cap.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
5252

5353
## AXe UI Testing Tools
5454
- [ ] `src/mcp/tools/ui-testing/button.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
# xcode-build-server files
3+
buildServer.json
4+
.compile
5+
6+
# Local build artifacts
7+
.build/

src/mcp/tools/logging/__tests__/start_sim_log_cap.test.ts

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* Tests for start_sim_log_cap plugin
33
*/
4-
import { describe, it, expect, beforeEach } from 'vitest';
4+
import { describe, it, expect } from 'vitest';
55
import { z } from 'zod';
66
import plugin, { start_sim_log_capLogic } from '../start_sim_log_cap.ts';
77
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
@@ -33,51 +33,30 @@ describe('start_sim_log_cap plugin', () => {
3333

3434
it('should validate schema with valid parameters', () => {
3535
const schema = z.object(plugin.schema);
36-
expect(
37-
schema.safeParse({ simulatorUuid: 'test-uuid', bundleId: 'com.example.app' }).success,
38-
).toBe(true);
39-
expect(
40-
schema.safeParse({
41-
simulatorUuid: 'test-uuid',
42-
bundleId: 'com.example.app',
43-
captureConsole: true,
44-
}).success,
45-
).toBe(true);
46-
expect(
47-
schema.safeParse({
48-
simulatorUuid: 'test-uuid',
49-
bundleId: 'com.example.app',
50-
captureConsole: false,
51-
}).success,
52-
).toBe(true);
36+
expect(schema.safeParse({ bundleId: 'com.example.app' }).success).toBe(true);
37+
expect(schema.safeParse({ bundleId: 'com.example.app', captureConsole: true }).success).toBe(
38+
true,
39+
);
40+
expect(schema.safeParse({ bundleId: 'com.example.app', captureConsole: false }).success).toBe(
41+
true,
42+
);
5343
});
5444

5545
it('should reject invalid schema parameters', () => {
5646
const schema = z.object(plugin.schema);
57-
expect(schema.safeParse({ simulatorUuid: null, bundleId: 'com.example.app' }).success).toBe(
47+
expect(schema.safeParse({ bundleId: null }).success).toBe(false);
48+
expect(schema.safeParse({ captureConsole: true }).success).toBe(false);
49+
expect(schema.safeParse({}).success).toBe(false);
50+
expect(schema.safeParse({ bundleId: 'com.example.app', captureConsole: 'yes' }).success).toBe(
5851
false,
5952
);
60-
expect(
61-
schema.safeParse({ simulatorUuid: undefined, bundleId: 'com.example.app' }).success,
62-
).toBe(false);
63-
expect(schema.safeParse({ simulatorUuid: 'test-uuid', bundleId: null }).success).toBe(false);
64-
expect(schema.safeParse({ simulatorUuid: 'test-uuid', bundleId: undefined }).success).toBe(
53+
expect(schema.safeParse({ bundleId: 'com.example.app', captureConsole: 123 }).success).toBe(
6554
false,
6655
);
67-
expect(
68-
schema.safeParse({
69-
simulatorUuid: 'test-uuid',
70-
bundleId: 'com.example.app',
71-
captureConsole: 'yes',
72-
}).success,
73-
).toBe(false);
74-
expect(
75-
schema.safeParse({
76-
simulatorUuid: 'test-uuid',
77-
bundleId: 'com.example.app',
78-
captureConsole: 123,
79-
}).success,
80-
).toBe(false);
56+
57+
const withSimId = schema.safeParse({ simulatorId: 'test-uuid', bundleId: 'com.example.app' });
58+
expect(withSimId.success).toBe(true);
59+
expect('simulatorId' in (withSimId.data as any)).toBe(false);
8160
});
8261
});
8362

@@ -98,7 +77,7 @@ describe('start_sim_log_cap plugin', () => {
9877

9978
const result = await start_sim_log_capLogic(
10079
{
101-
simulatorUuid: 'test-uuid',
80+
simulatorId: 'test-uuid',
10281
bundleId: 'com.example.app',
10382
},
10483
mockExecutor,
@@ -122,7 +101,7 @@ describe('start_sim_log_cap plugin', () => {
122101

123102
const result = await start_sim_log_capLogic(
124103
{
125-
simulatorUuid: 'test-uuid',
104+
simulatorId: 'test-uuid',
126105
bundleId: 'com.example.app',
127106
},
128107
mockExecutor,
@@ -148,7 +127,7 @@ describe('start_sim_log_cap plugin', () => {
148127

149128
const result = await start_sim_log_capLogic(
150129
{
151-
simulatorUuid: 'test-uuid',
130+
simulatorId: 'test-uuid',
152131
bundleId: 'com.example.app',
153132
captureConsole: true,
154133
},
@@ -208,7 +187,7 @@ describe('start_sim_log_cap plugin', () => {
208187

209188
await start_sim_log_capLogic(
210189
{
211-
simulatorUuid: 'test-uuid',
190+
simulatorId: 'test-uuid',
212191
bundleId: 'com.example.app',
213192
captureConsole: true,
214193
},
@@ -277,7 +256,7 @@ describe('start_sim_log_cap plugin', () => {
277256

278257
await start_sim_log_capLogic(
279258
{
280-
simulatorUuid: 'test-uuid',
259+
simulatorId: 'test-uuid',
281260
bundleId: 'com.example.app',
282261
captureConsole: false,
283262
},

src/mcp/tools/logging/start_sim_log_cap.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import { z } from 'zod';
88
import { startLogCapture } from '../../../utils/log-capture/index.ts';
99
import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/command.ts';
1010
import { ToolResponse, createTextContent } from '../../../types/common.ts';
11-
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
11+
import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts';
1212

1313
// Define schema as ZodObject
1414
const startSimLogCapSchema = z.object({
15-
simulatorUuid: z
15+
simulatorId: z
1616
.string()
17+
.uuid()
1718
.describe('UUID of the simulator to capture logs from (obtained from list_simulators).'),
1819
bundleId: z.string().describe('Bundle identifier of the app to capture logs for.'),
1920
captureConsole: z
@@ -30,11 +31,15 @@ export async function start_sim_log_capLogic(
3031
_executor: CommandExecutor = getDefaultCommandExecutor(),
3132
logCaptureFunction: typeof startLogCapture = startLogCapture,
3233
): Promise<ToolResponse> {
33-
const paramsWithDefaults = {
34-
...params,
35-
captureConsole: params.captureConsole ?? false,
36-
};
37-
const { sessionId, error } = await logCaptureFunction(paramsWithDefaults, _executor);
34+
const captureConsole = params.captureConsole ?? false;
35+
const { sessionId, error } = await logCaptureFunction(
36+
{
37+
simulatorUuid: params.simulatorId,
38+
bundleId: params.bundleId,
39+
captureConsole,
40+
},
41+
_executor,
42+
);
3843
if (error) {
3944
return {
4045
content: [createTextContent(`Error starting log capture: ${error}`)],
@@ -44,16 +49,23 @@ export async function start_sim_log_capLogic(
4449
return {
4550
content: [
4651
createTextContent(
47-
`Log capture started successfully. Session ID: ${sessionId}.\n\n${paramsWithDefaults.captureConsole ? 'Note: Your app was relaunched to capture console output.' : 'Note: Only structured logs are being captured.'}\n\nNext Steps:\n1. Interact with your simulator and app.\n2. Use 'stop_sim_log_cap' with session ID '${sessionId}' to stop capture and retrieve logs.`,
52+
`Log capture started successfully. Session ID: ${sessionId}.\n\n${captureConsole ? 'Note: Your app was relaunched to capture console output.' : 'Note: Only structured logs are being captured.'}\n\nNext Steps:\n1. Interact with your simulator and app.\n2. Use 'stop_sim_log_cap' with session ID '${sessionId}' to stop capture and retrieve logs.`,
4853
),
4954
],
5055
};
5156
}
5257

58+
const publicSchemaObject = startSimLogCapSchema.omit({ simulatorId: true } as const).strict();
59+
5360
export default {
5461
name: 'start_sim_log_cap',
5562
description:
5663
'Starts capturing logs from a specified simulator. Returns a session ID. By default, captures only structured logs.',
57-
schema: startSimLogCapSchema.shape, // MCP SDK compatibility
58-
handler: createTypedTool(startSimLogCapSchema, start_sim_log_capLogic, getDefaultCommandExecutor),
64+
schema: publicSchemaObject.shape, // MCP SDK compatibility
65+
handler: createSessionAwareTool<StartSimLogCapParams>({
66+
internalSchema: startSimLogCapSchema as unknown as z.ZodType<StartSimLogCapParams>,
67+
logicFunction: start_sim_log_capLogic,
68+
getExecutor: getDefaultCommandExecutor,
69+
requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }],
70+
}),
5971
};

src/mcp/tools/simulator-management/__tests__/erase_sims.test.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ import { z } from 'zod';
33
import eraseSims, { erase_simsLogic } from '../erase_sims.ts';
44
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
55

6-
describe('erase_sims tool (UDID or ALL only)', () => {
6+
describe('erase_sims tool (ID or ALL only)', () => {
77
describe('Export Field Validation (Literal)', () => {
88
it('should have correct name', () => {
99
expect(eraseSims.name).toBe('erase_sims');
1010
});
1111

1212
it('should have correct description', () => {
13-
expect(eraseSims.description).toContain('Provide exactly one of: simulatorUdid or all=true');
14-
expect(eraseSims.description).toContain('shutdownFirst');
13+
expect(eraseSims.description).toBe('Erases one simulator or all simulators.');
1514
});
1615

1716
it('should have handler function', () => {
@@ -20,27 +19,27 @@ describe('erase_sims tool (UDID or ALL only)', () => {
2019

2120
it('should validate schema fields (shape only)', () => {
2221
const schema = z.object(eraseSims.schema);
23-
// Valid
24-
expect(
25-
schema.safeParse({ simulatorUdid: '123e4567-e89b-12d3-a456-426614174000' }).success,
26-
).toBe(true);
2722
expect(schema.safeParse({ all: true }).success).toBe(true);
28-
// Shape-level schema does not enforce selection rules; handler validation covers that.
23+
expect(schema.safeParse({ shutdownFirst: true }).success).toBe(true);
24+
25+
const withSimId = schema.safeParse({ simulatorId: '123e4567-e89b-12d3-a456-426614174000' });
26+
expect(withSimId.success).toBe(true);
27+
expect('simulatorId' in (withSimId.data as any)).toBe(false);
2928
});
3029
});
3130

3231
describe('Single mode', () => {
3332
it('erases a simulator successfully', async () => {
3433
const mock = createMockExecutor({ success: true, output: 'OK' });
35-
const res = await erase_simsLogic({ simulatorUdid: 'UD1' }, mock);
34+
const res = await erase_simsLogic({ simulatorId: 'UD1' }, mock);
3635
expect(res).toEqual({
3736
content: [{ type: 'text', text: 'Successfully erased simulator UD1' }],
3837
});
3938
});
4039

4140
it('returns failure when erase fails', async () => {
4241
const mock = createMockExecutor({ success: false, error: 'Booted device' });
43-
const res = await erase_simsLogic({ simulatorUdid: 'UD1' }, mock);
42+
const res = await erase_simsLogic({ simulatorId: 'UD1' }, mock);
4443
expect(res).toEqual({
4544
content: [{ type: 'text', text: 'Failed to erase simulator: Booted device' }],
4645
});
@@ -50,7 +49,7 @@ describe('erase_sims tool (UDID or ALL only)', () => {
5049
const bootedError =
5150
'An error was encountered processing the command (domain=com.apple.CoreSimulator.SimError, code=405):\nUnable to erase contents and settings in current state: Booted\n';
5251
const mock = createMockExecutor({ success: false, error: bootedError });
53-
const res = await erase_simsLogic({ simulatorUdid: 'UD1' }, mock);
52+
const res = await erase_simsLogic({ simulatorId: 'UD1' }, mock);
5453
expect((res.content?.[1] as any).text).toContain('Tool hint');
5554
expect((res.content?.[1] as any).text).toContain('shutdownFirst: true');
5655
});
@@ -61,7 +60,7 @@ describe('erase_sims tool (UDID or ALL only)', () => {
6160
calls.push(cmd);
6261
return { success: true, output: 'OK', error: '', process: { pid: 1 } as any };
6362
};
64-
const res = await erase_simsLogic({ simulatorUdid: 'UD1', shutdownFirst: true }, exec as any);
63+
const res = await erase_simsLogic({ simulatorId: 'UD1', shutdownFirst: true }, exec as any);
6564
expect(calls).toEqual([
6665
['xcrun', 'simctl', 'shutdown', 'UD1'],
6766
['xcrun', 'simctl', 'erase', 'UD1'],

src/mcp/tools/simulator-management/__tests__/reset_sim_location.test.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,14 @@ describe('reset_sim_location plugin', () => {
1919
expect(typeof resetSimLocationPlugin.handler).toBe('function');
2020
});
2121

22-
it('should have correct schema validation', () => {
22+
it('should hide simulatorId from public schema', () => {
2323
const schema = z.object(resetSimLocationPlugin.schema);
2424

25-
expect(
26-
schema.safeParse({
27-
simulatorUuid: 'abc123',
28-
}).success,
29-
).toBe(true);
25+
expect(schema.safeParse({}).success).toBe(true);
3026

31-
expect(
32-
schema.safeParse({
33-
simulatorUuid: 123,
34-
}).success,
35-
).toBe(false);
36-
37-
expect(schema.safeParse({}).success).toBe(false);
27+
const withSimId = schema.safeParse({ simulatorId: 'abc123' });
28+
expect(withSimId.success).toBe(true);
29+
expect('simulatorId' in (withSimId.data as any)).toBe(false);
3830
});
3931
});
4032

@@ -47,7 +39,7 @@ describe('reset_sim_location plugin', () => {
4739

4840
const result = await reset_sim_locationLogic(
4941
{
50-
simulatorUuid: 'test-uuid-123',
42+
simulatorId: 'test-uuid-123',
5143
},
5244
mockExecutor,
5345
);
@@ -70,7 +62,7 @@ describe('reset_sim_location plugin', () => {
7062

7163
const result = await reset_sim_locationLogic(
7264
{
73-
simulatorUuid: 'test-uuid-123',
65+
simulatorId: 'test-uuid-123',
7466
},
7567
mockExecutor,
7668
);
@@ -90,7 +82,7 @@ describe('reset_sim_location plugin', () => {
9082

9183
const result = await reset_sim_locationLogic(
9284
{
93-
simulatorUuid: 'test-uuid-123',
85+
simulatorId: 'test-uuid-123',
9486
},
9587
mockExecutor,
9688
);
@@ -123,7 +115,7 @@ describe('reset_sim_location plugin', () => {
123115

124116
await reset_sim_locationLogic(
125117
{
126-
simulatorUuid: 'test-uuid-123',
118+
simulatorId: 'test-uuid-123',
127119
},
128120
capturingExecutor,
129121
);

0 commit comments

Comments
 (0)