Skip to content

Commit 99b53b2

Browse files
carmandaleclaude
andcommitted
Merge upstream/main: sync with cameroncooke/XcodeBuildMCP
Merge upstream commits bringing session-aware pattern to additional tools: - getsentry#125: Migrate clean/list_schemes/show_build_settings to session defaults - getsentry#122: Migrate test_sim and get_sim_app_path to session defaults ## Conflict Resolution Resolved conflicts in 3 files by keeping our improved implementation: ### 1. src/utils/typed-tool-factory.ts - KEPT: Our simplified factory (48 lines vs upstream's 100+ lines) - KEPT: Removed requirements DSL (Zod-first validation) - KEPT: Simplified API (positional params vs config object) - KEPT: exclusivePairs as 4th parameter (critical for XOR field pruning) ### 2. src/mcp/tools/simulator/test_sim.ts - KEPT: Our comprehensive implementation with utilities - KEPT: mapPlatformStringToEnum for type safety - KEPT: logUseLatestOSWarning utility - KEPT: Simplified factory API - KEPT: macOS platform rejection validation ### 3. src/mcp/tools/simulator/__tests__/test_sim.test.ts - KEPT: Our comprehensive 500+ line test suite (23 session tests + empty string tests) - UPSTREAM HAD: Basic session tests only ## Upstream Tool Migration Updated 4 upstream tools to use our simplified factory API: - list_schemes.ts - Updated to positional params, removed .omit() bug - show_build_settings.ts - Updated to positional params, removed .omit() bug - get_sim_app_path.ts - Updated to positional params, removed .omit() bug - clean.ts - Updated to positional params, removed .omit() bug ## Test Fixes Fixed test expectations in 4 upstream test files: - list_schemes.test.ts - Updated for correct public schema (no .omit()) - show_build_settings.test.ts - Updated for correct public schema - clean.test.ts - Updated for correct public schema - get_sim_app_path.test.ts - Fixed fake path issues ## Upstream Benefits from Our Improvements Upstream tools now benefit from: - ✅ Simplified factory API (cleaner code) - ✅ Fixed .omit() bug (explicit params work correctly) - ✅ XOR field pruning (explicit overrides session for XOR fields) - ✅ Better error handling (no server crashes) - ✅ Shared utilities (platform-utils, simulator-validation, shared-schemas) ## Quality Validation ✅ TypeCheck: Zero errors ✅ Lint: Zero errors ✅ Build: Successful (91 test files) ✅ Tests: 1,233/1,233 passing (100% pass rate) ## Commits Merged from Upstream - 4bbfe5d: feat(project-discovery): migrate clean/list/show to session defaults - 79736a3: feat(simulator): migrate test_sim and get_sim_app_path to session defaults Total: 2 upstream commits + our local improvements merged successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2 parents 0170eb1 + 4bbfe5d commit 99b53b2

9 files changed

Lines changed: 426 additions & 61 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Session-Aware Migration TODO
2+
3+
_Audit date: October 6, 2025_
4+
5+
Reference: `docs/session_management_plan.md`
6+
7+
## Utilities
8+
- [x] `src/mcp/tools/utilities/clean.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
9+
10+
## Project Discovery
11+
- [x] `src/mcp/tools/project-discovery/list_schemes.ts` — session defaults: `projectPath`, `workspacePath`.
12+
- [x] `src/mcp/tools/project-discovery/show_build_settings.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`.
13+
14+
## Device Workflows
15+
- [ ] `src/mcp/tools/device/build_device.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
16+
- [ ] `src/mcp/tools/device/test_device.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `deviceId`, `configuration`.
17+
- [ ] `src/mcp/tools/device/get_device_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
18+
- [ ] `src/mcp/tools/device/install_app_device.ts` — session defaults: `deviceId`.
19+
- [ ] `src/mcp/tools/device/launch_app_device.ts` — session defaults: `deviceId`.
20+
- [ ] `src/mcp/tools/device/stop_app_device.ts` — session defaults: `deviceId`.
21+
22+
## Device Logging
23+
- [ ] `src/mcp/tools/logging/start_device_log_cap.ts` — session defaults: `deviceId`.
24+
25+
## macOS Workflows
26+
- [ ] `src/mcp/tools/macos/build_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
27+
- [ ] `src/mcp/tools/macos/build_run_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
28+
- [ ] `src/mcp/tools/macos/test_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`.
29+
- [ ] `src/mcp/tools/macos/get_mac_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`.
30+
31+
## Simulator Build/Test/Path
32+
- [x] `src/mcp/tools/simulator/test_sim.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `simulatorId`, `simulatorName`, `configuration`, `useLatestOS`.
33+
- [x] `src/mcp/tools/simulator/get_sim_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `simulatorId`, `simulatorName`, `configuration`, `useLatestOS`, `arch`.
34+
35+
## 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`).
42+
43+
## 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`).
49+
50+
## Simulator Logging
51+
- [ ] `src/mcp/tools/logging/start_sim_log_cap.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
52+
53+
## AXe UI Testing Tools
54+
- [ ] `src/mcp/tools/ui-testing/button.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
55+
- [ ] `src/mcp/tools/ui-testing/describe_ui.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
56+
- [ ] `src/mcp/tools/ui-testing/gesture.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
57+
- [ ] `src/mcp/tools/ui-testing/key_press.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
58+
- [ ] `src/mcp/tools/ui-testing/key_sequence.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
59+
- [ ] `src/mcp/tools/ui-testing/long_press.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
60+
- [ ] `src/mcp/tools/ui-testing/screenshot.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
61+
- [ ] `src/mcp/tools/ui-testing/swipe.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
62+
- [ ] `src/mcp/tools/ui-testing/tap.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
63+
- [ ] `src/mcp/tools/ui-testing/touch.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).
64+
- [ ] `src/mcp/tools/ui-testing/type_text.ts` — session defaults: `simulatorId` (hydrate `simulatorUuid`).

src/mcp/tools/project-discovery/__tests__/list_schemes.test.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,38 @@
44
* Using dependency injection for deterministic testing
55
*/
66

7-
import { describe, it, expect } from 'vitest';
7+
import { describe, it, expect, beforeEach } from 'vitest';
88
import { z } from 'zod';
99
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
1010
import plugin, { listSchemesLogic } from '../list_schemes.ts';
11+
import { sessionStore } from '../../../../utils/session-store.ts';
1112

1213
describe('list_schemes plugin', () => {
14+
beforeEach(() => {
15+
sessionStore.clear();
16+
});
17+
1318
describe('Export Field Validation (Literal)', () => {
1419
it('should have correct name', () => {
1520
expect(plugin.name).toBe('list_schemes');
1621
});
1722

1823
it('should have correct description', () => {
19-
expect(plugin.description).toBe(
20-
"Lists available schemes for either a project or a workspace. Provide exactly one of projectPath or workspacePath. Example: list_schemes({ projectPath: '/path/to/MyProject.xcodeproj' })",
21-
);
24+
expect(plugin.description).toBe('Lists schemes for a project or workspace.');
2225
});
2326

2427
it('should have handler function', () => {
2528
expect(typeof plugin.handler).toBe('function');
2629
});
2730

28-
it('should validate schema with valid inputs', () => {
31+
it('should have correct public schema (all fields optional for session integration)', () => {
2932
const schema = z.object(plugin.schema);
30-
expect(schema.safeParse({ projectPath: '/path/to/MyProject.xcodeproj' }).success).toBe(true);
31-
expect(schema.safeParse({ projectPath: '/Users/dev/App.xcodeproj' }).success).toBe(true);
32-
});
3333

34-
it('should validate schema with invalid inputs', () => {
35-
const schema = z.object(plugin.schema);
36-
// Base schema allows empty object - XOR validation is in refinements
37-
expect(schema.safeParse({}).success).toBe(true);
38-
expect(schema.safeParse({ projectPath: 123 }).success).toBe(false);
39-
expect(schema.safeParse({ projectPath: null }).success).toBe(false);
40-
expect(schema.safeParse({ workspacePath: 123 }).success).toBe(false);
34+
// Public schema allows all fields to be optional
35+
// Session defaults or handler validation will catch missing required ones
36+
expect(schema.safeParse({ projectPath: '/path/to/project.xcodeproj' }).success).toBe(true);
37+
expect(schema.safeParse({ workspacePath: '/path/to/workspace.xcworkspace' }).success).toBe(true);
38+
expect(schema.safeParse({}).success).toBe(true); // All optional
4139
});
4240
});
4341

@@ -232,7 +230,7 @@ describe('list_schemes plugin', () => {
232230

233231
it('should handle validation when testing with missing projectPath via plugin handler', async () => {
234232
// Note: Direct logic function calls bypass Zod validation, so we test the actual plugin handler
235-
// to verify Zod validation works properly. The createTypedTool wrapper handles validation.
233+
// to verify Zod validation works properly. The createSessionAwareTool wrapper handles validation.
236234
const result = await plugin.handler({});
237235
expect(result.isError).toBe(true);
238236
expect(result.content[0].text).toContain('Parameter validation failed');
@@ -244,6 +242,7 @@ describe('list_schemes plugin', () => {
244242
it('should error when neither projectPath nor workspacePath provided', async () => {
245243
const result = await plugin.handler({});
246244
expect(result.isError).toBe(true);
245+
expect(result.content[0].text).toContain('Parameter validation failed');
247246
expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
248247
});
249248

@@ -253,7 +252,8 @@ describe('list_schemes plugin', () => {
253252
workspacePath: '/path/to/workspace.xcworkspace',
254253
});
255254
expect(result.isError).toBe(true);
256-
expect(result.content[0].text).toContain('mutually exclusive');
255+
expect(result.content[0].text).toContain('Parameter validation failed');
256+
expect(result.content[0].text).toContain('projectPath and workspacePath are mutually exclusive');
257257
});
258258

259259
it('should handle empty strings as undefined', async () => {
@@ -262,6 +262,7 @@ describe('list_schemes plugin', () => {
262262
workspacePath: '',
263263
});
264264
expect(result.isError).toBe(true);
265+
expect(result.content[0].text).toContain('Parameter validation failed');
265266
expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
266267
});
267268
});

src/mcp/tools/project-discovery/__tests__/show_build_settings.test.ts

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,41 @@
1-
import { describe, it, expect } from 'vitest';
1+
import { describe, it, expect, beforeEach } from 'vitest';
22
import { z } from 'zod';
33
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
44
import plugin, { showBuildSettingsLogic } from '../show_build_settings.ts';
5+
import { sessionStore } from '../../../../utils/session-store.ts';
56

67
describe('show_build_settings plugin', () => {
8+
beforeEach(() => {
9+
sessionStore.clear();
10+
});
711
describe('Export Field Validation (Literal)', () => {
812
it('should have correct name', () => {
913
expect(plugin.name).toBe('show_build_settings');
1014
});
1115

1216
it('should have correct description', () => {
13-
expect(plugin.description).toBe(
14-
"Shows build settings from either a project or workspace using xcodebuild. Provide exactly one of projectPath or workspacePath, plus scheme. Example: show_build_settings({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
15-
);
17+
expect(plugin.description).toBe('Shows xcodebuild build settings.');
1618
});
1719

1820
it('should have handler function', () => {
1921
expect(typeof plugin.handler).toBe('function');
2022
});
2123

22-
it('should have schema object', () => {
23-
expect(plugin.schema).toBeDefined();
24-
expect(typeof plugin.schema).toBe('object');
24+
it('should have correct public schema (all fields optional for session integration)', () => {
25+
const schema = z.object(plugin.schema);
26+
27+
// Public schema: projectPath and workspacePath are optional, scheme is required
28+
// But scheme can come from session defaults or explicit parameters
29+
expect(schema.safeParse({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme' }).success).toBe(true);
30+
expect(schema.safeParse({ workspacePath: '/path/to/workspace.xcworkspace', scheme: 'MyScheme' }).success).toBe(true);
31+
32+
// Missing scheme should fail (scheme is required in public schema)
33+
expect(schema.safeParse({ projectPath: '/path/to/project.xcodeproj' }).success).toBe(false);
34+
expect(schema.safeParse({}).success).toBe(false);
35+
36+
// Verify the schema has the expected fields
37+
const schemaKeys = Object.keys(plugin.schema).sort();
38+
expect(schemaKeys).toEqual(['projectPath', 'scheme', 'workspacePath'].sort());
2539
});
2640
});
2741

@@ -51,7 +65,7 @@ describe('show_build_settings plugin', () => {
5165

5266
expect(result.isError).toBe(true);
5367
expect(result.content[0].text).toContain('Parameter validation failed');
54-
expect(result.content[0].text).toContain('projectPath');
68+
expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
5569
});
5670

5771
it('should return success with build settings', async () => {
@@ -169,6 +183,7 @@ describe('show_build_settings plugin', () => {
169183
});
170184

171185
expect(result.isError).toBe(true);
186+
expect(result.content[0].text).toContain('Parameter validation failed');
172187
expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
173188
});
174189

@@ -180,7 +195,8 @@ describe('show_build_settings plugin', () => {
180195
});
181196

182197
expect(result.isError).toBe(true);
183-
expect(result.content[0].text).toContain('mutually exclusive');
198+
expect(result.content[0].text).toContain('Parameter validation failed');
199+
expect(result.content[0].text).toContain('projectPath and workspacePath are mutually exclusive');
184200
});
185201

186202
it('should work with projectPath only', async () => {
@@ -214,6 +230,28 @@ describe('show_build_settings plugin', () => {
214230
});
215231
});
216232

233+
describe('Session requirement handling', () => {
234+
it('should require scheme when not provided', async () => {
235+
const result = await plugin.handler({
236+
projectPath: '/path/to/MyProject.xcodeproj',
237+
} as any);
238+
239+
expect(result.isError).toBe(true);
240+
expect(result.content[0].text).toContain('Parameter validation failed');
241+
expect(result.content[0].text).toContain('scheme');
242+
});
243+
244+
it('should surface project/workspace requirement even with scheme default', async () => {
245+
sessionStore.setDefaults({ scheme: 'MyScheme' });
246+
247+
const result = await plugin.handler({});
248+
249+
expect(result.isError).toBe(true);
250+
expect(result.content[0].text).toContain('Parameter validation failed');
251+
expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
252+
});
253+
});
254+
217255
describe('showBuildSettingsLogic function', () => {
218256
it('should return success with build settings', async () => {
219257
const calls: any[] = [];

src/mcp/tools/project-discovery/list_schemes.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { CommandExecutor } from '../../../utils/execution/index.ts';
1111
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
1212
import { createTextResponse } from '../../../utils/responses/index.ts';
1313
import { ToolResponse } from '../../../types/common.ts';
14-
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
14+
import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts';
1515
import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';
1616

1717
// Unified schema: XOR between projectPath and workspacePath
@@ -109,14 +109,18 @@ export async function listSchemesLogic(
109109
}
110110
}
111111

112+
// Public schema = all fields optional (session defaults can provide values)
113+
// This allows agents to provide parameters explicitly OR rely on session defaults
114+
const publicSchemaObject = baseSchemaObject;
115+
112116
export default {
113117
name: 'list_schemes',
114-
description:
115-
"Lists available schemes for either a project or a workspace. Provide exactly one of projectPath or workspacePath. Example: list_schemes({ projectPath: '/path/to/MyProject.xcodeproj' })",
116-
schema: baseSchemaObject.shape,
117-
handler: createTypedTool<ListSchemesParams>(
118-
listSchemesSchema as z.ZodType<ListSchemesParams>,
118+
description: 'Lists schemes for a project or workspace.',
119+
schema: publicSchemaObject.shape,
120+
handler: createSessionAwareTool<ListSchemesParams>(
121+
listSchemesSchema as unknown as z.ZodType<ListSchemesParams>,
119122
listSchemesLogic,
120123
getDefaultCommandExecutor,
124+
[['projectPath', 'workspacePath']],
121125
),
122126
};

src/mcp/tools/project-discovery/show_build_settings.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { CommandExecutor } from '../../../utils/execution/index.ts';
1111
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
1212
import { createTextResponse } from '../../../utils/responses/index.ts';
1313
import { ToolResponse } from '../../../types/common.ts';
14-
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
14+
import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts';
1515
import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';
1616

1717
// Unified schema: XOR between projectPath and workspacePath
@@ -102,14 +102,16 @@ export async function showBuildSettingsLogic(
102102
}
103103
}
104104

105+
const publicSchemaObject = baseSchemaObject;
106+
105107
export default {
106108
name: 'show_build_settings',
107-
description:
108-
"Shows build settings from either a project or workspace using xcodebuild. Provide exactly one of projectPath or workspacePath, plus scheme. Example: show_build_settings({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
109-
schema: baseSchemaObject.shape,
110-
handler: createTypedTool<ShowBuildSettingsParams>(
111-
showBuildSettingsSchema as z.ZodType<ShowBuildSettingsParams>,
109+
description: 'Shows xcodebuild build settings.',
110+
schema: publicSchemaObject.shape,
111+
handler: createSessionAwareTool<ShowBuildSettingsParams>(
112+
showBuildSettingsSchema as unknown as z.ZodType<ShowBuildSettingsParams>,
112113
showBuildSettingsLogic,
113114
getDefaultCommandExecutor,
115+
[['projectPath', 'workspacePath']],
114116
),
115117
};

0 commit comments

Comments
 (0)