Skip to content

Conversation

@cameroncooke
Copy link
Collaborator

@cameroncooke cameroncooke commented Aug 13, 2025

This PR unifies the project/workspace tool variants and the ID/name simulator tool variants.

Summary by CodeRabbit

  • New Features

    • Unified device and macOS tools (build, test, build & run, get app path) now accept exactly one of projectPath or workspacePath.
    • Added list_schemes and show_build_settings (project/workspace aware).
    • New “iOS Device Development” and “macOS Development” workflows.
    • CLI supports static analysis and JSON output; auto-generates Tools reference.
  • Refactor

    • Simplified tool names (e.g., set_sim_location, reset_sim_location).
    • Consolidated/removed legacy per-workflow variants; updated “Next Steps” guidance.
  • Documentation

    • TOOLS.md rewritten; new PHASE1 plan; updated manual/testing guides; added verification report.
  • Chores

    • Scripts migrated to TSX; added analysis/docs-update scripts; refined ESLint config.

- Fix isError assertion to use toBeUndefined() instead of toBe(false)
- Update error message expectations to match 'iOS Simulator Build' prefix
- Correct invalid simulatorId test to check for destination error message
- Update exception handling tests with proper error message format

All 25 tests now pass. Full test suite (1495 tests) passing.
@cursor
Copy link
Contributor

cursor bot commented Aug 13, 2025

I refactored a widely duplicated schema helper function into a shared utility, significantly improving code maintainability. Additionally, I unified several project discovery tool names for better consistency and updated all relevant documentation and tests to reflect these changes.

  • I refactored the nullifyEmptyStrings helper function from 15 individual tool files (e.g., src/mcp/tools/device/build_device.ts, src/mcp/tools/macos/build_macos.ts, src/mcp/tools/simulator/build_simulator.ts) into a new shared utility file: src/utils/schema-helpers.ts.
  • I unified the names of project discovery tools: list_schems_proj and list_schems_ws are now list_schemes; show_build_set_proj and show_build_set_ws are now show_build_settings.
  • I updated docs/MANUAL_TESTING.md and docs/TESTING.md to reflect the new unified tool names for list_schemes and show_build_settings.
  • I modified src/mcp/tools/project-discovery/list_schemes.ts and its tests to suggest the new unified tool names (build_macos, build_simulator_name, show_build_settings) in next-step messages.
  • I removed redundant as unknown as z.ZodType<Params> type assertions from createTypedTool calls across numerous tool files, leveraging improved type inference.
  • I enhanced the isReExportFile function in scripts/analysis/tools-analysis.ts to correctly strip multi-line and inline comments, improving the accuracy of re-export file detection.
  • I added explicit else if and error handling for projectPath vs workspacePath parameters in get_device_app_pathLogic and get_macos_app_pathLogic for improved robustness.

6 tasks completed

  • Extract duplicated nullifyEmptyStrings function to shared utility module
  • Update tool descriptions and next steps to use unified tool names (list_schemes instead of list_schemes_ws/list_schemes_proj)
  • Fix inconsistent import path extensions in test files
  • Remove unnecessary type assertions and improve type safety
  • Fix comment filtering logic in tools-analysis.ts
  • Update tests to match new tool responses and metadata
Open in Cursor Open in Web

Learn more about Cursor Agents

Co-authored-by: web <web@cameroncooke.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (6)
src/mcp/tools/device/test_device.ts (1)

255-268: Inconsistent parameter handling in createTypedTool callback.

The createTypedTool function is designed to handle parameter validation and pass validated params directly to the logic function with the executor. However, the current implementation:

  1. Takes 3 parameters in its callback but createTypedTool only provides 2 (params and executor)
  2. Manually calls getDefaultCommandExecutor() and getDefaultFileSystemExecutor() instead of using the injected executor
  3. The third parameter to createTypedTool should be the executor getter, not used within the callback
  handler: createTypedTool<TestDeviceParams>(
    testDeviceSchema,
-   (params: TestDeviceParams) => {
+   (params: TestDeviceParams, executor: CommandExecutor) => {
      return testDeviceLogic(
        {
          ...params,
          platform: params.platform ?? 'iOS',
        },
-       getDefaultCommandExecutor(),
+       executor,
        getDefaultFileSystemExecutor(),
      );
    },
    getDefaultCommandExecutor,
  ),

Note: The testDeviceLogic function expects both CommandExecutor and FileSystemExecutor. You may need to update the createTypedTool signature to support multiple executors, or create a composite executor type.

docs/MANUAL_TESTING.md (3)

510-515: Inconsistent tool reference with unified architecture.

The code still references list_schems_proj which should be updated to the unified list_schemes tool based on the PR's unification effort.

-        npx reloaderoo@latest inspect call-tool "list_schems_proj" --params "{\"projectPath\": \"$project_path\"}" -- node build/index.js 2>/dev/null > /tmp/schemes_$$.json
+        npx reloaderoo@latest inspect call-tool "list_schemes" --params "{\"projectPath\": \"$project_path\"}" -- node build/index.js 2>/dev/null > /tmp/schemes_$$.json

556-557: Inconsistent tool reference with unified architecture.

Line 556 still references list_schems_proj which should be updated to list_schemes to match the unified tool architecture described in the PR objectives.

-npx reloaderoo@latest inspect call-tool "list_schems_proj" --params '{"projectPath": "/actual/path/from/discover_projs.xcodeproj"}' -- node build/index.js
+npx reloaderoo@latest inspect call-tool "list_schemes" --params '{"projectPath": "/actual/path/from/discover_projs.xcodeproj"}' -- node build/index.js

657-662: Inconsistent tool references in error testing section.

The error testing examples still use list_schems_proj which should be updated to the unified list_schemes tool.

-npx reloaderoo@latest inspect call-tool list_schems_proj --params '{"projectPath": 123}' -- node build/index.js 2>/dev/null
+npx reloaderoo@latest inspect call-tool list_schemes --params '{"projectPath": 123}' -- node build/index.js 2>/dev/null

 # Test with non-existent paths
 echo "Testing non-existent paths..."
-npx reloaderoo@latest inspect call-tool list_schems_proj --params '{"projectPath": "/nonexistent/path.xcodeproj"}' -- node build/index.js 2>/dev/null
+npx reloaderoo@latest inspect call-tool list_schemes --params '{"projectPath": "/nonexistent/path.xcodeproj"}' -- node build/index.js 2>/dev/null
src/mcp/tools/macos/build_run_macos.ts (1)

212-224: Use injected executor instead of calling getDefaultCommandExecutor in handler

The handler’s logic function ignores the executor injected by createTypedTool and calls getDefaultCommandExecutor directly. This breaks the DI pattern and can trip the test guard that forbids default executor usage in tests.

Apply this diff:

   handler: createTypedTool<BuildRunMacOSParams>(
     buildRunMacOSSchema,
-    (params: BuildRunMacOSParams) =>
+    (params: BuildRunMacOSParams, executor: CommandExecutor) =>
       buildRunMacOSLogic(
         {
           ...params,
           configuration: params.configuration ?? 'Debug',
           preferXcodebuild: params.preferXcodebuild ?? false,
         },
-        getDefaultCommandExecutor(),
+        executor,
       ),
     getDefaultCommandExecutor,
   ),

Optional follow-up: move the defaulting of configuration/prefer into buildRunMacOSLogic (like buildMacOSLogic) to keep handler thin and reduce duplication.

src/mcp/tools/device/get_device_app_path.ts (1)

45-55: Avoid duplicating XcodePlatform constants; import shared enum instead

Defining a local XcodePlatform risks drift and inconsistencies. Reuse the shared enum exported from types/common.js (as other tools do).

Apply this diff:

-import { ToolResponse } from '../../../types/common.js';
+import { ToolResponse, XcodePlatform } from '../../../types/common.js';
@@
-const XcodePlatform = {
-  iOS: 'iOS',
-  watchOS: 'watchOS',
-  tvOS: 'tvOS',
-  visionOS: 'visionOS',
-  iOSSimulator: 'iOS Simulator',
-  watchOSSimulator: 'watchOS Simulator',
-  tvOSSimulator: 'tvOS Simulator',
-  visionOSSimulator: 'visionOS Simulator',
-  macOS: 'macOS',
-};
+// Use shared XcodePlatform from types/common.js
♻️ Duplicate comments (3)
scripts/analysis/tools-analysis.ts (1)

149-159: Consider the existing comment about the comment filtering logic.

The comment filtering logic has an issue that was previously raised - it could incorrectly handle edge cases like inline comments and multi-line comments that span multiple lines. While the current implementation attempts to handle this, it may still have issues with complex cases.

src/mcp/tools/project-scaffolding/__tests__/scaffold_macos_project.test.ts (1)

288-293: Repeat: fix second test block to unified build_run_macos style

Mirror the change in this block as well for consistency.

Apply this diff:

                 nextSteps: [
                   'Important: Before working on the project make sure to read the README.md file in the workspace root directory.',
                   'Build for macOS: build_macos({ workspacePath: "/tmp/test-projects/MyProject.xcworkspace", scheme: "MyProject" })',
-                  'Run and run on macOS: build_run_mac_ws --workspace-path "/tmp/test-projects/MyProject.xcworkspace" --scheme "MyProject"',
+                  'Build & Run on macOS: build_run_macos({ workspacePath: "/tmp/test-projects/MyProject.xcworkspace", scheme: "MyProject" })',
                 ],
src/mcp/tools/device/__tests__/get_device_app_path.test.ts (1)

9-9: Import path extension needs correction.

The import uses .ts extension which is correct for TypeScript imports in this project, but there was a previous comment about inconsistent extension usage. The current .ts extension is actually correct and consistent with TypeScript module resolution.

🧹 Nitpick comments (11)
src/mcp/tools/project-discovery/__tests__/list_schemes.test.ts (5)

28-33: Add positive validation for workspacePath (symmetry with projectPath)

Minor coverage gap: validate the schema accepts a workspacePath-only input as valid, mirroring the projectPath case.

Apply this diff to add the missing assertion:

 it('should validate schema with valid inputs', () => {
   const schema = z.object(plugin.schema);
   expect(schema.safeParse({ projectPath: '/path/to/MyProject.xcodeproj' }).success).toBe(true);
   expect(schema.safeParse({ projectPath: '/Users/dev/App.xcodeproj' }).success).toBe(true);
+  expect(schema.safeParse({ workspacePath: '/path/to/MyProject.xcworkspace' }).success).toBe(true);
 });

34-41: Strengthen invalid schema tests for workspacePath nulls

You verify number types are rejected for both paths; also reject null for workspacePath for parity with projectPath.

Apply this diff:

   expect(schema.safeParse({ projectPath: null }).success).toBe(false);
-  expect(schema.safeParse({ workspacePath: 123 }).success).toBe(false);
+  expect(schema.safeParse({ workspacePath: 123 }).success).toBe(false);
+  expect(schema.safeParse({ workspacePath: null }).success).toBe(false);

144-160: Avoid emitting an empty “Next Steps” content block when no schemes exist

When no schemes are found (but the Schemes: section exists), the response currently includes a trailing empty text block. Prefer omitting the block to reduce noise.

Apply this diff to adjust the test expectation (logic change shown below):

   expect(result).toEqual({
     content: [
       {
         type: 'text',
         text: '✅ Available schemes:',
       },
       {
         type: 'text',
         text: '',
       },
-      {
-        type: 'text',
-        text: '',
-      },
     ],
     isError: false,
   });

Outside this file, change listSchemesLogic to conditionally include the “Next Steps” block only when populated:

// In src/mcp/tools/project-discovery/list_schemes.ts
const content = [
  { type: 'text', text: '✅ Available schemes:' as const },
  { type: 'text', text: schemes.join('\n') as const },
];

if (nextStepsText) {
  content.push({ type: 'text', text: nextStepsText as const });
}

return { content, isError: false };

233-241: Clarify test name to reflect unified XOR requirement

The handler validates both projectPath and workspacePath; rename for precision.

Apply this diff:

-it('should handle validation when testing with missing projectPath via plugin handler', async () => {
+it('should error when neither projectPath nor workspacePath provided (plugin handler)', async () => {

259-266: Also treat whitespace-only strings as “not provided” in XOR validation

You cover empty strings; add a case for whitespace-only input to prevent accidental bypasses.

Apply this diff to extend the test:

 it('should handle empty strings as undefined', async () => {
   const result = await plugin.handler({
     projectPath: '',
     workspacePath: '',
   });
   expect(result.isError).toBe(true);
   expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
 });
+
+it('should handle whitespace-only strings as undefined', async () => {
+  const result = await plugin.handler({
+    projectPath: '   ',
+    workspacePath: '  ',
+  });
+  expect(result.isError).toBe(true);
+  expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
+});

If not already implemented, ensure the schema uses z.string().trim() before XOR refinements so whitespace becomes empty.

src/mcp/tools/device/test_device.ts (2)

156-164: Consider updating the internal log message for clarity.

The log message mentions "(internal)" which could be confusing as this is no longer an internal/private implementation detail but the unified public-facing logic.

  log(
    'info',
-   `Starting test run for scheme ${params.scheme} on platform ${params.platform ?? 'iOS'} (internal)`,
+   `Starting test run for scheme ${params.scheme} on platform ${params.platform ?? 'iOS'}`,
  );

253-253: Update tool description to match unified naming convention.

Based on the PR objectives, the description should be updated to reflect the unified tool naming. The example should also show both projectPath and workspacePath options to make the XOR relationship clearer.

  description:
-   'Runs tests for an Apple project or workspace on a physical device (iPhone, iPad, Apple Watch, Apple TV, Apple Vision Pro) using xcodebuild test and parses xcresult output. Provide exactly one of projectPath or workspacePath. IMPORTANT: Requires scheme and deviceId. Example: test_device({ projectPath: "/path/to/MyProject.xcodeproj", scheme: "MyScheme", deviceId: "device-uuid" })',
+   'Runs tests for an Apple project or workspace on a physical device (iPhone, iPad, Apple Watch, Apple TV, Apple Vision Pro) using xcodebuild test and parses xcresult output. Provide exactly one of projectPath or workspacePath. IMPORTANT: Requires scheme and deviceId. Examples: test_device({ projectPath: "/path/to/MyProject.xcodeproj", scheme: "MyScheme", deviceId: "device-uuid" }) OR test_device({ workspacePath: "/path/to/MyWorkspace.xcworkspace", scheme: "MyScheme", deviceId: "device-uuid" })',
src/mcp/tools/project-discovery/show_build_settings.ts (1)

53-56: Simplify conditional logic for path selection.

The code unnecessarily uses non-null assertions when the values are already type-checked through the schema validation.

-    if (hasProjectPath) {
-      command.push('-project', params.projectPath!);
-    } else {
-      command.push('-workspace', params.workspacePath!);
-    }
+    if (hasProjectPath && params.projectPath) {
+      command.push('-project', params.projectPath);
+    } else if (params.workspacePath) {
+      command.push('-workspace', params.workspacePath);
+    }
src/mcp/tools/macos/build_run_macos.ts (1)

53-73: Minor: defaulting duplicated across layers

configuration is defaulted here and again in _handleMacOSBuildLogic. Not harmful, but consolidating defaults in one place reduces maintenance overhead.

src/mcp/tools/device/get_device_app_path.ts (2)

42-44: Export the params type for consistency with other tools

Other unified tools export their Params types (e.g., BuildMacOSParams). Exporting this supports reuse in tests and downstream code.

Apply this diff:

-// Use z.infer for type safety
-type GetDeviceAppPathParams = z.infer<typeof getDeviceAppPathSchema>;
+// Use z.infer for type safety and export for reuse
+export type GetDeviceAppPathParams = z.infer<typeof getDeviceAppPathSchema>;

12-13: Consistency: import CommandExecutor from utils/command

Elsewhere (e.g., build_macos.ts) CommandExecutor is imported from utils/command.js. Align imports to reduce confusion.

Apply this diff:

-import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.js';
+import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/command.js';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da42459 and d5c9e4e.

📒 Files selected for processing (16)
  • docs/MANUAL_TESTING.md (3 hunks)
  • docs/TESTING.md (3 hunks)
  • scripts/analysis/tools-analysis.ts (1 hunks)
  • src/mcp/tools/device/__tests__/get_device_app_path.test.ts (9 hunks)
  • src/mcp/tools/device/build_device.ts (1 hunks)
  • src/mcp/tools/device/get_device_app_path.ts (5 hunks)
  • src/mcp/tools/device/test_device.ts (6 hunks)
  • src/mcp/tools/macos/build_macos.ts (5 hunks)
  • src/mcp/tools/macos/build_run_macos.ts (6 hunks)
  • src/mcp/tools/macos/get_macos_app_path.ts (6 hunks)
  • src/mcp/tools/macos/test_macos.ts (10 hunks)
  • src/mcp/tools/project-discovery/__tests__/list_schemes.test.ts (11 hunks)
  • src/mcp/tools/project-discovery/list_schemes.ts (1 hunks)
  • src/mcp/tools/project-discovery/show_build_settings.ts (1 hunks)
  • src/mcp/tools/project-scaffolding/__tests__/scaffold_macos_project.test.ts (2 hunks)
  • src/mcp/tools/project-scaffolding/scaffold_macos_project.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/TESTING.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/mcp/tools/device/build_device.ts
  • src/mcp/tools/project-discovery/list_schemes.ts
🧰 Additional context used
🧬 Code Graph Analysis (10)
src/mcp/tools/project-discovery/show_build_settings.ts (5)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/logger.ts (1)
  • log (33-53)
src/utils/validation.ts (1)
  • createTextResponse (35-45)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/utils/command.ts (1)
  • getDefaultCommandExecutor (232-243)
src/mcp/tools/macos/get_macos_app_path.ts (2)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/macos/test_macos.ts (5)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/test-common.ts (1)
  • parseXcresultBundle (60-75)
src/utils/command.ts (3)
  • getDefaultCommandExecutor (232-243)
  • FileSystemExecutor (41-52)
  • getDefaultFileSystemExecutor (249-260)
src/utils/logger.ts (1)
  • log (33-53)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
scripts/analysis/tools-analysis.ts (3)
scripts/check-code-patterns.js (2)
  • __filename (31-31)
  • __dirname (32-32)
src/core/plugin-registry.ts (1)
  • getWorkflowMetadata (90-109)
build-plugins/plugin-discovery.js (3)
  • indexPath (40-40)
  • workflowDirs (31-33)
  • metadata (51-51)
src/mcp/tools/device/get_device_app_path.ts (2)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/project-discovery/__tests__/list_schemes.test.ts (3)
build-plugins/plugin-discovery.js (1)
  • result (172-175)
src/mcp/tools/project-discovery/list_schemes.ts (1)
  • listSchemesLogic (38-109)
src/utils/command.ts (1)
  • createMockExecutor (267-305)
src/mcp/tools/device/test_device.ts (3)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/command.ts (3)
  • getDefaultCommandExecutor (232-243)
  • FileSystemExecutor (41-52)
  • getDefaultFileSystemExecutor (249-260)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/device/__tests__/get_device_app_path.test.ts (2)
build-plugins/plugin-discovery.js (1)
  • result (172-175)
src/utils/command.ts (1)
  • createMockExecutor (267-305)
src/mcp/tools/macos/build_run_macos.ts (3)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/command.ts (1)
  • getDefaultCommandExecutor (232-243)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/macos/build_macos.ts (3)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/utils/command.ts (1)
  • getDefaultCommandExecutor (232-243)
🔇 Additional comments (36)
src/mcp/tools/project-discovery/__tests__/list_schemes.test.ts (3)

10-16: Unification rename reflected correctly

Import path and plugin.name assertion align with the unified tool naming (“list_schemes”). Looks good.


19-21: Description literal matches unified API expectations

The exact description string (with XOR guidance and example) looks correct for the unified tool. Keep it in sync with any future refinements to inputs/examples.

If you’re planning to further unify downstream tool names (per the Phase 2 note in the logic), make sure to update this description’s example accordingly when that change lands.


221-231: Command construction assertion is solid

You assert the full command shape and metadata (“List Schemes”), including showOutput and workingDir. This guards against regressions in the executor integration.

src/mcp/tools/device/test_device.ts (2)

21-21: Good job consolidating the duplicate helper function!

The nullifyEmptyStrings function has been properly extracted to the shared utilities module as suggested in the past review. This resolves the DRY principle violation.


39-47: LGTM! Well-implemented XOR validation for unified parameters.

The schema correctly enforces mutual exclusivity between projectPath and workspacePath while ensuring at least one is provided. The error messages are clear and actionable.

docs/MANUAL_TESTING.md (1)

247-248: Update tool references to match unified variants.

Based on the PR context where tools are being unified, these references to list_schemes and show_build_settings are now correct after unification. The manual testing documentation has been properly updated to reflect the new unified tool names.

src/mcp/tools/project-discovery/show_build_settings.ts (4)

1-7: LGTM! Clear documentation for the unified tool.

The file header clearly describes this as the unified version that accepts mutually exclusive parameters, which aligns well with the PR's unification objectives.


16-31: Well-implemented XOR validation pattern.

The schema correctly implements the XOR pattern between projectPath and workspacePath with clear error messages. The use of nullifyEmptyStrings preprocessor ensures consistent handling of empty string inputs.


83-91: LGTM! Helpful next steps guidance.

The conditional next steps section for workspace users is a nice touch that provides actionable guidance with correct tool references to the unified list_schemes tool.


104-114: Clear and accurate tool export configuration.

The default export correctly exposes the unified tool with an appropriate description that emphasizes the XOR requirement between project and workspace paths. The schema is properly exposed for MCP client discovery.

src/mcp/tools/macos/build_macos.ts (3)

26-56: Good: Unified XOR validation with empty-string normalization

The schema cleanly enforces exactly one of projectPath/workspacePath and normalizes empty strings to undefined. This avoids ambiguous inputs and matches the unified tools pattern.


63-87: Good: Logic composes params and defers to shared executor

Defaulting configuration/prefer flags and delegating to executeXcodeBuildCommand with platform context is clear and testable via dependency injection.


89-99: Handler wiring matches typed-tool factory pattern

Using buildMacOSSchema for validation with baseSchemaObject.shape for MCP compatibility is consistent with the codebase. No issues.

src/mcp/tools/macos/build_run_macos.ts (1)

17-47: Good: Unified XOR schema with nullifyEmptyStrings

Solid input model: mutually exclusive project/workspace with preprocessing to avoid accidental empty-string inputs.

src/mcp/tools/device/get_device_app_path.ts (1)

16-41: Good: Unified schema with XOR and normalization

Input validation and normalization are consistent with the rest of the unified tools. Defaults to iOS are sensible.

src/mcp/tools/macos/get_macos_app_path.ts (8)

1-6: LGTM: Updated documentation reflects unified approach.

The header comments correctly describe the unified functionality that accepts either project or workspace paths with mutual exclusivity.


13-13: Good addition of schema helper import.

The import of nullifyEmptyStrings supports the preprocessing functionality for handling empty string inputs.


15-41: Excellent XOR validation implementation.

The schema design correctly implements mutual exclusivity between projectPath and workspacePath using:

  • nullifyEmptyStrings preprocessing to normalize empty strings to undefined
  • Two refinement checks to ensure exactly one path is provided
  • Clear error messages for validation failures

This follows the unified tool pattern mentioned in the PR objectives.


44-44: Good type safety with inferred types.

Using z.infer<typeof getMacosAppPathSchema> provides compile-time type safety that stays in sync with the schema definition.


58-60: Updated function signature reflects unified approach.

The function name change from get_mac_app_path_projLogic to get_macos_app_pathLogic and parameter type change to GetMacosAppPathParams correctly reflect the unified tool implementation.


70-78: Proper XOR logic implementation.

The conditional logic correctly handles either project or workspace paths, with a defensive error case that should never be reached due to schema validation. This is a clean implementation of the unified approach.


150-154: Enhanced user experience with next steps guidance.

The addition of next steps guidance provides clear actionable instructions for users after successfully retrieving the app path. This improves the tool's usability.


183-192: Updated export configuration aligns with unified naming.

The tool name change from get_mac_app_path_proj to get_macos_app_path and the updated description properly reflect the unified functionality. The schema and handler configuration correctly use the new types and validation.

src/mcp/tools/macos/test_macos.ts (8)

1-6: LGTM: Updated documentation reflects unified approach.

The header comments correctly describe the unified functionality that accepts either project or workspace paths with mutual exclusivity, consistent with other unified tools in this PR.


23-48: Excellent XOR validation implementation.

The schema design correctly implements the unified approach with:

  • Base schema object defining both projectPath and workspacePath as optional
  • nullifyEmptyStrings preprocessing to handle empty string normalization
  • Two refinement checks ensuring exactly one path is provided
  • Clear, descriptive error messages

This follows the same pattern as other unified tools in the PR.


50-50: Good type safety with exported type.

The exported TestMacosParams type provides proper type safety and aligns with the unified naming convention.


216-220: Enhanced function signature with dependency injection.

The updated function signature correctly:

  • Uses the unified TestMacosParams type
  • Accepts both CommandExecutor and FileSystemExecutor for testability
  • Provides default executors for production use
  • Exports the function for direct testing

This follows the dependency injection pattern mentioned in the relevant code snippets.


225-227: Proper filesystem executor usage.

The migration from direct filesystem calls to fileSystemExecutor.mkdtemp and fileSystemExecutor.tmpdir() enables proper mocking in tests and follows the executor pattern used throughout the codebase.


234-250: Clean parameter passing to build command.

The explicit parameter passing (projectPath, workspacePath, scheme, etc.) instead of spreading params provides better control and clarity over what parameters are passed to the build system.


259-259: Consistent filesystem executor usage.

All filesystem operations (stat, rm) have been migrated to use the fileSystemExecutor, maintaining consistency with the dependency injection pattern.

Also applies to: 270-270, 289-289


304-315: Updated export configuration reflects unified tool.

The tool configuration correctly:

  • Updates name from test_macos_proj to test_macos
  • Provides comprehensive description with XOR guidance and examples
  • Uses baseSchemaObject.shape for MCP SDK compatibility
  • Implements proper typed tool wiring with both executors
src/mcp/tools/device/__tests__/get_device_app_path.test.ts (5)

11-26: Updated test descriptions and validations reflect unified tool.

The test suite correctly:

  • Updates plugin name to get_device_app_path
  • Updates description to reflect XOR behavior between projectPath and workspacePath
  • Tests both import references (getDeviceAppPath and get_device_app_pathLogic)

27-54: Comprehensive schema validation tests.

The schema validation tests correctly cover:

  • Both projectPath and workspacePath fields
  • Required scheme field
  • Optional fields like configuration and platform validation
  • Invalid input handling

This ensures the unified tool properly validates XOR requirements.


56-74: Excellent XOR validation test coverage.

The new test suite properly validates:

  • Error when neither path is provided with expected error message
  • Error when both paths are provided with "mutually exclusive" message
  • Proper error response structure with isError: true

This thoroughly tests the core XOR functionality of the unified tool.


185-235: Good test coverage for workspace functionality.

The new workspace test correctly verifies:

  • Command generation with -workspace parameter
  • Proper argument structure for workspace builds
  • Expected output format and mock executor usage

This ensures the unified tool works correctly for both project and workspace inputs.


104-110: All function calls updated to unified logic function.

All test calls have been correctly updated from the old project-specific function to get_device_app_pathLogic, maintaining consistent test coverage for the unified functionality.

Also applies to: 156-163, 209-215, 244-250, 272-278, 297-303, 340-347, 374-380, 398-404

Comment on lines 84 to 101
// Add optional derived data path (only for projects)
if (params.derivedDataPath && params.projectPath) {
command.push('-derivedDataPath', params.derivedDataPath);
}

// Add extra arguments if provided
if (params.extraArgs && Array.isArray(params.extraArgs)) {
// Handle destination for macOS (only for workspaces)
if (params.workspacePath) {
let destinationString = 'platform=macOS';
if (params.arch) {
destinationString += `,arch=${params.arch}`;
}
command.push('-destination', destinationString);
}

// Add extra arguments if provided (only for projects)
if (params.extraArgs && Array.isArray(params.extraArgs) && params.projectPath) {
command.push(...params.extraArgs);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify workspace-specific parameter restrictions.

The logic applies derivedDataPath and extraArgs only to projects, while destination is only applied to workspaces. However, the schema allows these parameters for both project and workspace inputs.


🏁 Script executed:

#!/bin/bash
# Description: Verify if workspace builds support derivedDataPath and extraArgs parameters

# Search for documentation or examples showing workspace parameter usage
rg -A 5 -B 5 "workspace.*derivedDataPath|derivedDataPath.*workspace"
rg -A 5 -B 5 "workspace.*extraArgs|extraArgs.*workspace"

# Look for xcodebuild documentation references
rg -A 10 -B 5 "xcodebuild.*workspace.*derivedDataPath"

Length of output: 186


🌐 Web query:

Does `xcodebuild -derivedDataPath` apply to workspace builds?
xcodebuild -workspace -derivedDataPath
xcodebuild -destination workspace macOS
xcodebuild derivedDataPath workspace vs -project
xcodebuild extra arguments workspace - how to pass custom args to xcodebuild -workspace

💡 Result:

Yes, the -derivedDataPath argument for xcodebuild applies to workspace builds as well as project builds. When you provide the -workspace flag (e.g., xcodebuild -workspace MyWorkspace.xcworkspace ... -derivedDataPath ./DerivedData), xcodebuild will use the specified directory for all build products and derived data instead of the default ~/Library/Developer/Xcode/DerivedData location[3][5][6].

Key points:

  • The -derivedDataPath flag can be used with both -project and -workspace invocations of xcodebuild[3][6].
  • This argument sets the location where Xcode will place derived data, such as build intermediates, products, and other generated files[3].
  • This is confirmed by both documentation and multiple examples where -derivedDataPath is specified in commands that include -workspace[3][5][6].

Example usage with workspace:

xcodebuild -workspace MyWorkspace.xcworkspace -scheme MyScheme -configuration Debug -derivedDataPath ./build

This command will place all build output under ./build rather than under the default Xcode derived data location[5].

Important caveats:

  • The syntax must be -derivedDataPath ./path and not -derivedDataPath=./path; using an equal sign may cause the flag to be ignored, especially in more recent Xcode versions[1].
  • Some bugs and integration issues have been observed when using -derivedDataPath in combination with certain dependencies or features (e.g., Swift Package Manager resolution in Xcode 13+ or third-party integrations like Flipper), causing builds to fail or behave unexpectedly[4][5]. These are not universal, but worth noting for CI/CD setups.
  • The output directories' structure may also be affected by workspace vs. project naming and by the configured build location style (unique, shared, custom), but the root for derived data will always be the one specified by -derivedDataPath[2].

Custom arguments to xcodebuild:

  • You can pass additional arguments (e.g., -destination, -scheme, environment overrides) in the same command as usual with -workspace and -derivedDataPath[3].
  • The placement of -derivedDataPath in the argument list doesn't matter, but its value must follow as a separate argument (not with =)[1][3].

Summary Table:

xcodebuild Option Applies to Workspace? Notes
-derivedDataPath <path> Yes Use as -derivedDataPath ./foo, not with =.
-project <.xcodeproj> Yes Traditional project usage.
-workspace <.xcworkspace> Yes Workspace usage supports -derivedDataPath.

If you need to pass custom (user-defined) build settings, set them as environment variables or append them as KEY=VALUE pairs after the main flags. This works identically on both project and workspace commands[3][6].

Citations:


Apply -derivedDataPath and extraArgs for workspace builds; don't restrict -destination to workspaces

xcodebuild accepts -derivedDataPath and arbitrary extra args with -workspace, so the current guards that only apply those flags when params.projectPath is set are incorrect. Likewise -destination can be used with both project and workspace invocations.

  • File to change: src/mcp/tools/macos/get_macos_app_path.ts (lines ~84-101)
  • Change required:
    • Remove the project-only checks for derivedDataPath and extraArgs.
    • Allow the destination logic for both workspace and project builds (e.g. use params.workspacePath || params.projectPath).

Suggested replacement snippet:

    // Add optional derived data path
    if (params.derivedDataPath) {
      command.push('-derivedDataPath', params.derivedDataPath);
    }

    // Handle destination for macOS (workspace or project)
    if (params.workspacePath || params.projectPath) {
      let destinationString = 'platform=macOS';
      if (params.arch) {
        destinationString += `,arch=${params.arch}`;
      }
      command.push('-destination', destinationString);
    }

    // Add extra arguments if provided
    if (params.extraArgs && Array.isArray(params.extraArgs)) {
      command.push(...params.extraArgs);
    }
🤖 Prompt for AI Agents
In src/mcp/tools/macos/get_macos_app_path.ts around lines 84 to 101, the code
currently only applies -derivedDataPath and extraArgs when params.projectPath is
set and only applies -destination for workspaces; update it to apply
-derivedDataPath whenever params.derivedDataPath is provided, apply -destination
when either params.workspacePath or params.projectPath is present (building
destinationString with optional arch), and apply extraArgs whenever
params.extraArgs is a valid array—remove the project-only guards so these flags
work for both workspace and project invocations.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (7)
src/mcp/tools/device/test_device.ts (2)

255-266: Forward the injected executor instead of instantiating a new default (avoid double-default and ease testing)

The wrapper currently ignores the executor that createTypedTool injects and calls getDefaultCommandExecutor() again. Forward the injected executor to testDeviceLogic and only source the default file system executor here. This avoids redundant default creation and aligns with the factory’s intent.

Apply this diff:

-  handler: createTypedTool<TestDeviceParams>(
-    testDeviceSchema as z.ZodType<TestDeviceParams>,
-    (params: TestDeviceParams) => {
-      return testDeviceLogic(
-        {
-          ...params,
-          platform: params.platform ?? 'iOS',
-        },
-        getDefaultCommandExecutor(),
-        getDefaultFileSystemExecutor(),
-      );
-    },
-    getDefaultCommandExecutor,
-  ),
+  handler: createTypedTool<TestDeviceParams>(
+    testDeviceSchema as z.ZodType<TestDeviceParams>,
+    (params: TestDeviceParams, executor: CommandExecutor) => {
+      return testDeviceLogic(
+        {
+          ...params,
+          platform: params.platform ?? 'iOS',
+        },
+        executor, // use the injected executor from the factory
+        getDefaultFileSystemExecutor(),
+      );
+    },
+    getDefaultCommandExecutor,
+  ),

166-175: Ensure temp directory cleanup happens even on early failures (use finally)

If executeXcodeBuildCommand throws before xcresult parsing, the temporary directory is never removed. Move cleanup to a finally block and guard it to avoid leaking temp dirs on early errors.

One way to implement:

 export async function testDeviceLogic(
   params: TestDeviceParams,
   executor: CommandExecutor = getDefaultCommandExecutor(),
   fileSystemExecutor: FileSystemExecutor = getDefaultFileSystemExecutor(),
 ): Promise<ToolResponse> {
   log(
     'info',
     `Starting test run for scheme ${params.scheme} on platform ${params.platform ?? 'iOS'} (internal)`,
   );
 
-  try {
-    // Create temporary directory for xcresult bundle
-    const tempDir = await fileSystemExecutor.mkdtemp(
-      join(fileSystemExecutor.tmpdir(), 'xcodebuild-test-'),
-    );
-    const resultBundlePath = join(tempDir, 'TestResults.xcresult');
+  let tempDir: string | undefined;
+  const cleanup = async () => {
+    if (!tempDir) return;
+    try {
+      await fileSystemExecutor.rm(tempDir, { recursive: true, force: true });
+    } catch (cleanupError) {
+      log('warn', `Failed to clean up temporary directory: ${cleanupError}`);
+    }
+  };
+
+  try {
+    tempDir = await fileSystemExecutor.mkdtemp(
+      join(fileSystemExecutor.tmpdir(), 'xcodebuild-test-'),
+    );
+    const resultBundlePath = join(tempDir, 'TestResults.xcresult');
@@
-      // Clean up temporary directory
-      await fileSystemExecutor.rm(tempDir, { recursive: true, force: true });
+      // Clean up temporary directory
+      await cleanup();
@@
-      // Clean up temporary directory even if parsing fails
-      try {
-        await fileSystemExecutor.rm(tempDir, { recursive: true, force: true });
-      } catch (cleanupError) {
-        log('warn', `Failed to clean up temporary directory: ${cleanupError}`);
-      }
+      await cleanup();
@@
   } catch (error) {
     const errorMessage = error instanceof Error ? error.message : String(error);
     log('error', `Error during test run: ${errorMessage}`);
     return createTextResponse(`Error during test run: ${errorMessage}`, true);
+  } finally {
+    await cleanup();
   }

Also applies to: 216-241

src/mcp/tools/macos/build_macos.ts (1)

26-55: XOR validation looks correct; suggest adding path extension checks and schema defaults

  • The XOR checks are correct and the preprocess step handles empty strings.
  • Recommend also validating file extensions to catch common misconfigurations early.
  • Consider moving defaults into the schema to avoid duplicating defaults in code and make them visible to callers.

Apply these diffs:

  1. Add extension checks with superRefine:
 const buildMacOSSchema = baseSchema
   .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
     message: 'Either projectPath or workspacePath is required.',
   })
   .refine((val) => !(val.projectPath !== undefined && val.workspacePath !== undefined), {
     message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
-  });
+  })
+  .superRefine((val, ctx) => {
+    if (val.projectPath && !val.projectPath.endsWith('.xcodeproj')) {
+      ctx.addIssue({
+        code: z.ZodIssueCode.custom,
+        path: ['projectPath'],
+        message: 'projectPath must point to a .xcodeproj file',
+      });
+    }
+    if (val.workspacePath && !val.workspacePath.endsWith('.xcworkspace')) {
+      ctx.addIssue({
+        code: z.ZodIssueCode.custom,
+        path: ['workspacePath'],
+        message: 'workspacePath must point to a .xcworkspace file',
+      });
+    }
+  });
  1. Move defaults into the schema:
 const baseSchemaObject = z.object({
   projectPath: z.string().optional().describe('Path to the .xcodeproj file'),
   workspacePath: z.string().optional().describe('Path to the .xcworkspace file'),
   scheme: z.string().describe('The scheme to use'),
-  configuration: z.string().optional().describe('Build configuration (Debug, Release, etc.)'),
+  configuration: z
+    .string()
+    .default('Debug')
+    .describe('Build configuration (Debug, Release, etc.). Defaults to "Debug".'),
   derivedDataPath: z
     .string()
     .optional()
     .describe('Path where build products and other derived data will go'),
   arch: z
     .enum(['arm64', 'x86_64'])
     .optional()
     .describe('Architecture to build for (arm64 or x86_64). For macOS only.'),
-  extraArgs: z.array(z.string()).optional().describe('Additional xcodebuild arguments'),
+  extraArgs: z
+    .array(z.string())
+    .default([])
+    .describe('Additional xcodebuild arguments'),
-  preferXcodebuild: z
-    .boolean()
-    .optional()
-    .describe('If true, prefers xcodebuild over the experimental incremental build system'),
+  preferXcodebuild: z
+    .boolean()
+    .default(false)
+    .describe('If true, prefers xcodebuild over the experimental incremental build system. Defaults to false.'),
 });

Also applies to: 47-47

src/mcp/tools/macos/build_run_macos.ts (4)

212-224: Don’t ignore the injected executor; current handler bypasses mocks and will trip test guard

createTypedTool injects an executor (often a mock in tests). The inline logic function here discards that executor and calls getDefaultCommandExecutor() directly, defeating test isolation and violating the pattern used across tools. Fix by threading the provided executor through.

Apply this diff:

   handler: createTypedTool<BuildRunMacOSParams>(
     buildRunMacOSSchema as z.ZodType<BuildRunMacOSParams>,
-    (params: BuildRunMacOSParams) =>
-      buildRunMacOSLogic(
-        {
-          ...params,
-          configuration: params.configuration ?? 'Debug',
-          preferXcodebuild: params.preferXcodebuild ?? false,
-        },
-        getDefaultCommandExecutor(),
-      ),
+    (params: BuildRunMacOSParams, executor) =>
+      buildRunMacOSLogic(
+        {
+          ...params,
+          configuration: params.configuration ?? 'Debug',
+          preferXcodebuild: params.preferXcodebuild ?? false,
+        },
+        executor,
+      ),
     getDefaultCommandExecutor,
   ),

53-73: Avoid defaulting to the real system executor in helpers; enforce explicit injection

Both helpers should require an executor parameter to prevent accidental use of the real executor in tests. Also coalesce preferXcodebuild to a boolean before passing to executeXcodeBuildCommand.

 async function _handleMacOSBuildLogic(
-  params: BuildRunMacOSParams,
-  executor: CommandExecutor = getDefaultCommandExecutor(),
+  params: BuildRunMacOSParams,
+  executor: CommandExecutor,
 ): Promise<ToolResponse> {
   log('info', `Starting macOS build for scheme ${params.scheme} (internal)`);
 
   return executeXcodeBuildCommand(
     {
       ...params,
       configuration: params.configuration ?? 'Debug',
     },
     {
       platform: XcodePlatform.macOS,
       arch: params.arch,
       logPrefix: 'macOS Build',
     },
-    params.preferXcodebuild,
+    params.preferXcodebuild ?? false,
     'build',
     executor,
   );
 }

75-129: Return a discriminated union from _getAppPathFromBuildSettings to eliminate non-null assertions and improve type narrowing

This strengthens type-safety and avoids the ! assertion at launch time.

 async function _getAppPathFromBuildSettings(
-  params: BuildRunMacOSParams,
-  executor: CommandExecutor = getDefaultCommandExecutor(),
-): Promise<{ success: boolean; appPath?: string; error?: string }> {
+  params: BuildRunMacOSParams,
+  executor: CommandExecutor,
+): Promise<{ success: true; appPath: string } | { success: false; error: string }> {
   try {
     // Create the command array for xcodebuild
     const command = ['xcodebuild', '-showBuildSettings'];
 
     // Add the project or workspace
     if (params.projectPath) {
       command.push('-project', params.projectPath);
     } else if (params.workspacePath) {
       command.push('-workspace', params.workspacePath);
     }
 
     // Add the scheme and configuration
     command.push('-scheme', params.scheme);
     command.push('-configuration', params.configuration ?? 'Debug');
 
     // Add derived data path if provided
     if (params.derivedDataPath) {
       command.push('-derivedDataPath', params.derivedDataPath);
     }
 
     // Add extra args if provided
     if (params.extraArgs && params.extraArgs.length > 0) {
       command.push(...params.extraArgs);
     }
 
     // Execute the command directly
     const result = await executor(command, 'Get Build Settings for Launch', true, undefined);
 
     if (!result.success) {
       return {
-        success: false,
-        error: result.error ?? 'Failed to get build settings',
+        success: false,
+        error: result.error ?? 'Failed to get build settings',
       };
     }
 
     // Parse the output to extract the app path
     const buildSettingsOutput = result.output;
     const builtProductsDirMatch = buildSettingsOutput.match(/BUILT_PRODUCTS_DIR = (.+)$/m);
     const fullProductNameMatch = buildSettingsOutput.match(/FULL_PRODUCT_NAME = (.+)$/m);
 
     if (!builtProductsDirMatch || !fullProductNameMatch) {
-      return { success: false, error: 'Could not extract app path from build settings' };
+      return { success: false, error: 'Could not extract app path from build settings' };
     }
 
     const appPath = `${builtProductsDirMatch[1].trim()}/${fullProductNameMatch[1].trim()}`;
-    return { success: true, appPath };
+    return { success: true, appPath };
   } catch (error) {
     const errorMessage = error instanceof Error ? error.message : String(error);
-    return { success: false, error: errorMessage };
+    return { success: false, error: errorMessage };
   }
 }

166-171: Drop the non-null assertion by leveraging the discriminated union

With the union return type, TS narrows appPath to string when success is true.

-    const appPath = appPathResult.appPath; // We know this is a valid string now
+    const appPath = appPathResult.appPath; // success === true narrows to string
     log('info', `App path determined as: ${appPath}`);
 
     // 4. Launch the app using CommandExecutor
-    const launchResult = await executor(['open', appPath!], 'Launch macOS App', true);
+    const launchResult = await executor(['open', appPath], 'Launch macOS App', true);
🧹 Nitpick comments (7)
src/mcp/tools/device/get_device_app_path.ts (3)

9-9: Deduplicate XcodePlatform: import from shared types instead of redefining locally

Avoid drifting constants by reusing the canonical XcodePlatform export from types/common. This keeps platform strings consistent across tools and prevents future mismatch.

Apply this diff:

- import { ToolResponse } from '../../../types/common.js';
+ import { ToolResponse, XcodePlatform } from '../../../types/common.js';
-const XcodePlatform = {
-  iOS: 'iOS',
-  watchOS: 'watchOS',
-  tvOS: 'tvOS',
-  visionOS: 'visionOS',
-  iOSSimulator: 'iOS Simulator',
-  watchOSSimulator: 'watchOS Simulator',
-  tvOSSimulator: 'tvOS Simulator',
-  visionOSSimulator: 'visionOS Simulator',
-  macOS: 'macOS',
-};

Also applies to: 45-55


119-122: Make build settings parsing more robust to whitespace variations

The current regexes assume exact spacing. Xcodebuild output often includes leading spaces and variable spacing around “=”. Slightly loosening the patterns reduces false negatives.

Apply this diff:

-    const builtProductsDirMatch = buildSettingsOutput.match(/BUILT_PRODUCTS_DIR = (.+)$/m);
-    const fullProductNameMatch = buildSettingsOutput.match(/FULL_PRODUCT_NAME = (.+)$/m);
+    const builtProductsDirMatch = buildSettingsOutput.match(/^\s*BUILT_PRODUCTS_DIR\s*=\s*(.+)$/m);
+    const fullProductNameMatch = buildSettingsOutput.match(/^\s*FULL_PRODUCT_NAME\s*=\s*(.+)$/m);

12-13: Import consistency: prefer importing CommandExecutor from utils/command for symmetry

Elsewhere (e.g., test_device.ts) CommandExecutor and getDefaultCommandExecutor are imported from utils/command. Consider aligning imports here for consistency and clarity on provenance.

Example:

- import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.js';
+ import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/command.js';
src/mcp/tools/device/test_device.ts (1)

69-76: Guard against empty xcresulttool output before JSON.parse

If the command succeeds but produces no output, JSON.parse will throw a confusing error. Provide a clearer failure mode.

Apply this diff:

-    if (!result.success) {
+    if (!result.success) {
       throw new Error(result.error ?? 'Failed to execute xcresulttool');
     }
-
-    // Parse JSON response and format as human-readable
-    const summaryData = JSON.parse(result.output) as Record<string, unknown>;
+    if (!result.output || result.output.trim().length === 0) {
+      throw new Error('xcresulttool returned no output');
+    }
+    // Parse JSON response and format as human-readable
+    const summaryData = JSON.parse(result.output) as Record<string, unknown>;
src/mcp/tools/macos/build_macos.ts (2)

63-86: Remove manual defaults; let Zod handle them

If you adopt schema-level defaults, you can drop processedParams and use params directly. This reduces duplication and keeps behavior centralized in validation.

Apply this diff:

 export async function buildMacOSLogic(
   params: BuildMacOSParams,
   executor: CommandExecutor,
   buildUtilsDeps: BuildUtilsDependencies = defaultBuildUtilsDependencies,
 ): Promise<ToolResponse> {
   log('info', `Starting macOS build for scheme ${params.scheme} (internal)`);

-  const processedParams = {
-    ...params,
-    configuration: params.configuration ?? 'Debug',
-    preferXcodebuild: params.preferXcodebuild ?? false,
-  };
-
   return buildUtilsDeps.executeXcodeBuildCommand(
-    processedParams,
+    params,
     {
       platform: XcodePlatform.macOS,
       arch: params.arch,
       logPrefix: 'macOS Build',
     },
-    processedParams.preferXcodebuild ?? false,
+    params.preferXcodebuild,
     'build',
     executor,
   );
 }

90-99: Schema drift: published schema vs. runtime schema

The exported schema for MCP is baseSchemaObject.shape (no preprocess/refine/defaults), while handler validation uses buildMacOSSchema (with preprocess, XOR, and future defaults if applied). This can surprise clients/docs that rely on the published schema.

Options:

  • Expose the actual Zod schema in the descriptor if your MCP layer supports it (preferred).
  • Or generate the MCP schema from buildMacOSSchema (including defaults) via a converter to keep docs/metadata in sync.
  • At minimum, ensure the description explicitly calls out the XOR constraint and defaults (you already note XOR; consider adding defaults after adopting them).

Would you like me to add a utility to derive the MCP schema from a Zod schema (e.g., via zod-to-json-schema) and update all unified tools to use it?

src/mcp/tools/macos/build_run_macos.ts (1)

40-46: Attach XOR validation errors to the specific fields (not root) for better UX

Current refine messages land at root, which can be less actionable. Use superRefine to target projectPath/workspacePath directly.

-const buildRunMacOSSchema = baseSchema
-  .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
-    message: 'Either projectPath or workspacePath is required.',
-  })
-  .refine((val) => !(val.projectPath !== undefined && val.workspacePath !== undefined), {
-    message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
-  });
+const buildRunMacOSSchema = baseSchema.superRefine((val, ctx) => {
+  const hasProject = val.projectPath !== undefined;
+  const hasWorkspace = val.workspacePath !== undefined;
+  if (!hasProject && !hasWorkspace) {
+    ctx.addIssue({
+      code: z.ZodIssueCode.custom,
+      message: 'Either projectPath or workspacePath is required.',
+      path: ['projectPath'],
+    });
+    ctx.addIssue({
+      code: z.ZodIssueCode.custom,
+      message: 'Either projectPath or workspacePath is required.',
+      path: ['workspacePath'],
+    });
+  }
+  if (hasProject && hasWorkspace) {
+    ctx.addIssue({
+      code: z.ZodIssueCode.custom,
+      message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
+      path: ['projectPath'],
+    });
+    ctx.addIssue({
+      code: z.ZodIssueCode.custom,
+      message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
+      path: ['workspacePath'],
+    });
+  }
+});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d5c9e4e and cfb0d02.

📒 Files selected for processing (9)
  • src/mcp/tools/device/build_device.ts (1 hunks)
  • src/mcp/tools/device/get_device_app_path.ts (5 hunks)
  • src/mcp/tools/device/test_device.ts (6 hunks)
  • src/mcp/tools/macos/build_macos.ts (5 hunks)
  • src/mcp/tools/macos/build_run_macos.ts (6 hunks)
  • src/mcp/tools/macos/get_macos_app_path.ts (6 hunks)
  • src/mcp/tools/macos/test_macos.ts (10 hunks)
  • src/mcp/tools/project-discovery/list_schemes.ts (1 hunks)
  • src/mcp/tools/project-discovery/show_build_settings.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/mcp/tools/device/build_device.ts
  • src/mcp/tools/project-discovery/list_schemes.ts
  • src/mcp/tools/project-discovery/show_build_settings.ts
  • src/mcp/tools/macos/get_macos_app_path.ts
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/mcp/tools/device/test_device.ts (4)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/command.ts (3)
  • getDefaultCommandExecutor (232-243)
  • FileSystemExecutor (41-52)
  • getDefaultFileSystemExecutor (249-260)
src/utils/logger.ts (1)
  • log (33-53)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/device/get_device_app_path.ts (2)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/macos/build_macos.ts (3)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/utils/command.ts (1)
  • getDefaultCommandExecutor (232-243)
src/mcp/tools/macos/test_macos.ts (4)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/test-common.ts (1)
  • parseXcresultBundle (60-75)
src/utils/command.ts (3)
  • getDefaultCommandExecutor (232-243)
  • FileSystemExecutor (41-52)
  • getDefaultFileSystemExecutor (249-260)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/macos/build_run_macos.ts (3)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/command.ts (1)
  • getDefaultCommandExecutor (232-243)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
🔇 Additional comments (17)
src/mcp/tools/device/get_device_app_path.ts (1)

134-137: Verify “Next Steps” tool names reflect unified surfaces

Ensure get_app_bundle_id, install_app_device, and launch_app_device match the current unified tool names and paths post-consolidation. If any were renamed during unification, update these references to prevent user confusion.

Would you like me to scan the repo for any lingering references to pre-unified tool names and suggest fixes?

src/mcp/tools/device/test_device.ts (2)

253-254: Confirm references to upstream tools reflect unified names

The description mentions “deviceId (obtained from list_devices)”. Given this PR unified ID/name simulator tools and project/workspace variants, please verify this reference is still accurate and update if the discovery tool name changed.

I can scan the repo for lingering references to pre-unified names (e.g., *_ws, *_proj, *_by_id, *_by_name) and surface a patch if you want.


21-21: Nice: shared nullifyEmptyStrings utility is being reused

Good reuse of the shared schema helper to normalize empty strings prior to validation. This keeps XOR validation behavior predictable.

src/mcp/tools/macos/build_macos.ts (2)

2-6: Good: clear unified tool description and XOR guidance

The docstring accurately reflects the unified interface and mutual exclusivity. Nice.


14-14: Preprocessing empty strings is the right call

Using nullifyEmptyStrings ensures '' behaves like “unset” before validation. This avoids surprising validation passes on whitespace. Good choice.

src/mcp/tools/macos/test_macos.ts (10)

1-6: LGTM: Header comment correctly updated to reflect unified functionality.

The header documentation now accurately describes the unified nature of the tool, mentioning mutual exclusivity between projectPath and workspacePath.


23-50: Excellent unified schema implementation with proper XOR validation.

The schema design correctly implements the unification pattern:

  • Uses nullifyEmptyStrings preprocessing to normalize input
  • Implements proper XOR validation ensuring exactly one of projectPath or workspacePath is provided
  • Exports the unified TestMacosParams type for external use

68-104: Good addition of executor parameter for testability.

The parseXcresultBundle function now accepts an optional CommandExecutor parameter, which improves testability and follows the established pattern in the codebase.


216-220: Well-designed function signature with proper executor injection.

The testMacosLogic function signature correctly:

  • Uses the unified TestMacosParams type
  • Accepts both CommandExecutor and FileSystemExecutor with proper defaults
  • Maintains backward compatibility through default parameter values

225-227: Proper filesystem abstraction usage.

The temporary directory creation correctly uses the FileSystemExecutor interface, which improves testability and follows the established abstraction pattern.


234-250: Correct parameter passing to executeXcodeBuildCommand.

The function correctly passes the explicit projectPath and workspacePath parameters instead of spreading the entire params object, which aligns with the unified approach.


259-260: Good filesystem executor usage for file existence check.

Using fileSystemExecutor.stat() instead of direct filesystem calls maintains consistency with the abstraction layer.


270-270: Proper cleanup using filesystem executor.

The cleanup operations correctly use the FileSystemExecutor interface for removing temporary directories, maintaining consistency with the abstraction pattern.

Also applies to: 289-289


304-315: Tool export correctly implements unified interface.

The default export properly:

  • Updates the tool name from test_macos_proj to test_macos
  • Provides clear description explaining XOR requirement and includes usage example
  • Uses the unified schema and proper typed tool creation
  • Correctly wires both command and filesystem executors

308-314: Verify schema compatibility assertion — safe (no change required)

testMacosSchema is defined in the same file and export type TestMacosParams = z.infer<typeof testMacosSchema>, so the cast testMacosSchema as z.ZodType<TestMacosParams> is consistent and not introducing an immediate type-safety issue. The repository uses the same pattern in multiple places (examples):

  • src/mcp/tools/macos/test_macos.ts — exports TestMacosParams from testMacosSchema
  • src/mcp/tools/macos/build_macos.ts — buildMacOSSchema as z.ZodType
  • src/mcp/tools/device/test_device.ts — testDeviceSchema as z.ZodType
  • src/mcp/tools/simulator/get_simulator_app_path.ts — getSimulatorAppPathSchema as z.ZodType

Optional recommendation: to avoid masking future mismatches, prefer typing the schema variable directly (declare it as z.ZodType<...>) instead of using as casts.

src/mcp/tools/macos/build_run_macos.ts (2)

17-39: Unified XOR schema and empty-string normalization looks good

The base schema with nullifyEmptyStrings and the mutually exclusive fields are aligned with the unification effort.


209-213: Description updated to reflect unified project/workspace usage—LGTM

Clear guidance on providing exactly one path aligns with the PR objectives to unify tool variants.

Comment on lines +90 to 99
name: 'build_macos',
description:
"Builds a macOS app using xcodebuild from a project or workspace. Provide exactly one of projectPath or workspacePath. Example: build_macos({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
schema: baseSchemaObject.shape, // MCP SDK compatibility
handler: createTypedTool<BuildMacOSParams>(
buildMacOSSchema as z.ZodType<BuildMacOSParams>,
buildMacOSLogic,
getDefaultCommandExecutor,
),
};
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Tool naming is consistent with unification goals; check for outdated references elsewhere

build_macos aligns with the unified naming. Make sure any docs/tests that referenced pre-unified variants are updated.


🏁 Script executed:

#!/bin/bash
# Find outdated references to pre-unified tool names/usages.
# Adjust/extend the patterns as needed for your codebase.

set -euo pipefail

echo "Searching for pre-unified tool names (e.g., *_ws, *_proj, *_id, *_name)..."
rg -n --hidden -g '!.git' -E '\b(list|build|run|test)_[a-z]+_(ws|proj|id|name)\b|list_schemes_ws|projectPath.+workspacePath.+both' || true

echo
echo "Searching for old descriptions mentioning project/workspace variants..."
rg -n --hidden -g '!.git' -E 'project vs\. workspace|workspace vs\. project|workspace-only|project-only' || true

Length of output: 1387


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching tracked files for pre-unified tool name patterns..."
git ls-files -z | xargs -0 grep -n -E --binary-files=without-match '\b(list|build|run|test)_[a-z]+_(ws|proj|id|name)\b|list_schemes_ws|projectPath.+workspacePath.+both' || true

echo
echo "Searching for references to build_macos / buildMacOS / build-macos..."
git ls-files -z | xargs -0 grep -n -E --binary-files=without-match 'build_macos|buildMacOS|build-macos' || true

echo
echo "Searching for descriptions mentioning project/workspace variants (project-only, workspace-only, project vs. workspace)..."
git ls-files -z | xargs -0 grep -n -E --binary-files=without-match 'project vs\. workspace|workspace vs\. project|workspace-only|project-only|workspace only|project only|projectPath|workspacePath' || true

Length of output: 89199


build_macos name is correct — docs/tests still contain pre‑unified tool names; please update or confirm re‑exports

Confirmed build_macos is named correctly, but a repo search found lingering legacy identifiers (patterns like *_proj, *_ws, *_id, *_name) in docs and tests. Please either update these to the unified names or confirm they intentionally reference legacy re-exports.

Files/locations with representative occurrences:

  • docs/PHASE1-TASKS.md
  • docs/MANUAL_TESTING.md
  • docs/TESTING.md
  • docs/TOOLS.md
  • scripts/update-tools-docs.ts
  • src/mcp/tools/project-discovery/tests/list_schemes.test.ts
  • src/mcp/tools/project-discovery/tests/get_mac_bundle_id.test.ts
  • src/mcp/tools/project-discovery/list_schemes.ts
  • src/mcp/tools/project-discovery/show_build_settings.ts
  • src/mcp/tools/device/tests/list_devices.test.ts

Requested action: update those docs/tests to use the unified tool names (or keep them only if you maintain backwards-compatible re-exports and adjust tests to expect that).

🤖 Prompt for AI Agents
In src/mcp/tools/macos/build_macos.ts around lines 90–99 the tool name
build_macos is correct, but the repo contains legacy identifiers (e.g. *_proj,
*_ws, *_id, *_name) in docs and tests; search the listed files and the repo for
those legacy patterns and either (A) update docs and test fixtures to use the
unified tool names (e.g. build_macos and unified param keys) or (B) add explicit
backwards-compatible re-exports/aliases in the tool index and change tests to
expect those aliases; update scripts/update-tools-docs.ts to emit the unified
names, adjust the tests under src/mcp/tools/* to use the new names (or new
expectations for aliases), and run the test/docs build to ensure nothing else
breaks.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

@cursor cursor bot Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⌛ Working on 5 tasks

  • Update list_schemes test to use build_simulator instead of build_simulator_name
  • Update scaffold_macos_project to use build_run_macos instead of build_run_mac_ws
  • Remove workspace restrictions for derivedDataPath and extraArgs in get_macos_app_path.ts
  • Fix executor forwarding issues in build_run_macos and test_device handlers
  • Improve error handling and cleanup in test functions
Open in Cursor Open in Web

Comment on lines +211 to +213
schema: baseSchemaObject.shape, // MCP SDK compatibility
handler: createTypedTool<BuildRunMacOSParams>(
buildRunMacOSSchema as z.ZodType<BuildRunMacOSParams>,
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Double-check MCP-facing schema metadata and repo-wide descriptions for pre-unified names

You’re exposing baseSchemaObject.shape to MCP for compatibility, which won’t encode the XOR constraint. Ensure docs and any consumer-side validations communicate that constraint. Also verify no references to old tool names remain (e.g., list_schemes_ws).

Run this script to find outdated references and spot-check docs:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for pre-unified tool names..."
rg -n --hidden --glob '!*node_modules*' --glob '!*.lock' \
  -e 'list_schemes_ws' \
  -e 'list_schemes_proj' \
  -e 'build_run_mac_proj' \
  -e 'build_run_macos_ws' \
  -e 'get_macos_app_path_proj' \
  -e 'get_macos_app_path_ws' \
  -e 'run_ios_simulator_by_name' \
  -e 'run_ios_simulator_by_id' \
  -e 'boot_simulator_by_name' \
  -e 'boot_simulator_by_id' \
  || echo "No pre-unified names found."

echo
echo "Spot-checking descriptions mentioning old variants or missing XOR guidance..."
rg -n --hidden --glob '!*node_modules*' --glob '!*.lock' \
  -e 'Provide exactly one of projectPath or workspacePath' \
  -e 'projectPath.*workspacePath' \
  docs \
  src/mcp/tools \
  || true

Length of output: 31497


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Showing src/mcp/tools/macos/build_run_macos.ts (first 320 lines) for inspection..."
sed -n '1,320p' src/mcp/tools/macos/build_run_macos.ts || true

Length of output: 8367


Action: Ensure MCP-facing schema encodes the XOR constraint and remove remaining pre-unified names

baseSchemaObject.shape is being exported for MCP compatibility — that shape omits the .refine() XOR (projectPath vs workspacePath), so MCP consumers won't see/enforce the mutually-exclusive requirement. The repo search also found leftover pre-unified name references in tests.

Files/locations needing attention:

  • src/mcp/tools/macos/build_run_macos.ts — default export currently uses:

    schema: baseSchemaObject.shape, // MCP SDK compatibility
    handler: createTypedTool<BuildRunMacOSParams>(
      buildRunMacOSSchema as z.ZodType<BuildRunMacOSParams>,
    

    Recommendation: export metadata that includes the refinements (e.g. expose the refined schema to MCP if the SDK supports it), or else add explicit, machine-readable XOR metadata / clearer schema descriptions so MCP consumers know to enforce "either projectPath OR workspacePath (not both)".

  • Replace/update snippet (example):

    // Option A — if MCP can accept the full Zod schema:
    schema: buildRunMacOSSchema, // includes XOR refinements
    
    // Option B — if MCP requires a plain shape:
    schema: baseSchemaObject.shape, // keep for compatibility
    // — and also add explicit XOR metadata in the exported description/metadata so consumers can validate/enforce it
    

Outdated/pre-unified name references found (examples — update to canonical names):

  • src/mcp/tools/macos/tests/build_macos.test.ts:82,144 — references to "get_macos_app_path_project"
  • src/mcp/tools/macos/tests/build_run_macos.test.ts:201,378,441 — references to "get_macos_app_path_project"

Please update tests/docs to use the canonical unified tool names (or make sure these strings are intentionally left as legacy messages), and ensure MCP-facing schema metadata either encodes the XOR or clearly communicates it to consumers.

🤖 Prompt for AI Agents
In src/mcp/tools/macos/build_run_macos.ts around lines 211-213, the exported
MCP-facing schema currently uses baseSchemaObject.shape which omits the
.refine() XOR constraint (projectPath vs workspacePath) and there are leftover
pre-unified tool name references in tests; fix by either exporting the full
refined schema (schema: buildRunMacOSSchema) if the MCP SDK accepts Zod schemas,
or if MCP requires a plain shape keep baseSchemaObject.shape but augment the
exported metadata/description with explicit machine-readable XOR metadata (e.g.,
a field that states "xor": ["projectPath","workspacePath"] or a clear
description so consumers can enforce it); additionally update tests and docs to
replace pre-unified names (e.g., "get_macos_app_path_project") with the
canonical unified tool names across the listed test files.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

@cursor cursor bot Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I enhanced the robustness of device testing by ensuring proper cleanup of temporary files and improved dependency injection for command execution. I also streamlined macOS app path retrieval logic and updated tool names for consistency across various "Next Steps" suggestions.

  • I refactored testDeviceLogic in src/mcp/tools/device/test_device.ts to use a finally block for tempDir cleanup, ensuring temporary files are always removed even if errors occur during test parsing or execution.
  • I added a check in parseXcresultBundle within src/mcp/tools/device/test_device.ts to throw an error if xcresulttool returns no output, improving error handling.
  • I updated the testDevice and buildRunMacOS tool handlers in src/mcp/tools/device/test_device.ts and src/mcp/tools/macos/build_run_macos.ts respectively, to directly pass the CommandExecutor instance to their logic functions, improving dependency injection.
  • I simplified the get_macos_app_pathLogic function in src/mcp/tools/macos/get_macos_app_path.ts by removing conditional logic for derivedDataPath, destination, and extraArgs based on projectPath or workspacePath, making these parameters universally applicable.
  • I updated "Next Steps" suggestions across src/utils/build-utils.ts and several test files to reflect unified tool names like get_device_app_path, get_macos_app_path, build_simulator, and build_run_macos.

Open in Cursor Open in Web

Learn more about Cursor Agents

…erences

Co-authored-by: web <web@cameroncooke.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (2)
src/mcp/tools/macos/build_run_macos.ts (1)

114-125: Fix app path detection: select the .app target and match the correct BUILT_PRODUCTS_DIR

Using the first match for FULL_PRODUCT_NAME/BUILT_PRODUCTS_DIR can select a framework target or mismatched sections when multiple targets exist, causing launch failures. Parse all matches, select the .app product, and pair it with the nearest preceding BUILT_PRODUCTS_DIR.

Apply this diff:

-    // Parse the output to extract the app path
-    const buildSettingsOutput = result.output;
-    const builtProductsDirMatch = buildSettingsOutput.match(/BUILT_PRODUCTS_DIR = (.+)$/m);
-    const fullProductNameMatch = buildSettingsOutput.match(/FULL_PRODUCT_NAME = (.+)$/m);
-
-    if (!builtProductsDirMatch || !fullProductNameMatch) {
-      return { success: false, error: 'Could not extract app path from build settings' };
-    }
-
-    const appPath = `${builtProductsDirMatch[1].trim()}/${fullProductNameMatch[1].trim()}`;
-    return { success: true, appPath };
+    // Parse the output to extract the app path (robustly pick the application target)
+    const buildSettingsOutput = result.output;
+    const appNameMatches = [...buildSettingsOutput.matchAll(/^FULL_PRODUCT_NAME = (.+)$/gm)];
+    const appNameMatch = [...appNameMatches].reverse().find((m) => m[1].trim().endsWith('.app'));
+    if (!appNameMatch || appNameMatch.index === undefined) {
+      return { success: false, error: 'Could not find FULL_PRODUCT_NAME ending with .app' };
+    }
+    // Find the nearest BUILT_PRODUCTS_DIR before the selected FULL_PRODUCT_NAME line
+    const beforeAppText = buildSettingsOutput.slice(0, appNameMatch.index);
+    const builtDirMatchesBefore = [...beforeAppText.matchAll(/^BUILT_PRODUCTS_DIR = (.+)$/gm)];
+    let builtProductsDir = builtDirMatchesBefore[builtDirMatchesBefore.length - 1]?.[1]?.trim();
+    // Fallback: take the last BUILT_PRODUCTS_DIR in the entire output
+    if (!builtProductsDir) {
+      const allBuiltDirMatches = [...buildSettingsOutput.matchAll(/^BUILT_PRODUCTS_DIR = (.+)$/gm)];
+      builtProductsDir = allBuiltDirMatches[allBuiltDirMatches.length - 1]?.[1]?.trim();
+    }
+    if (!builtProductsDir) {
+      return { success: false, error: 'Could not extract BUILT_PRODUCTS_DIR from build settings' };
+    }
+    const appPath = `${builtProductsDir}/${appNameMatch[1].trim()}`;
+    return { success: true, appPath };
src/mcp/tools/device/test_device.ts (1)

15-19: Wrong import source for FileSystemExecutor (compile/runtime break).

FileSystemExecutor and getDefaultFileSystemExecutor are defined in src/utils/file-system-executor.ts (see relevant snippet), not in utils/command.js. As written, this will break type resolution and likely fail at runtime.

Apply this diff to import from the correct module:

-import {
-  CommandExecutor,
-  getDefaultCommandExecutor,
-  FileSystemExecutor,
-  getDefaultFileSystemExecutor,
-} from '../../../utils/command.js';
+import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/command.js';
+import {
+  FileSystemExecutor,
+  getDefaultFileSystemExecutor,
+} from '../../../utils/file-system-executor.js';
♻️ Duplicate comments (5)
src/mcp/tools/macos/build_run_macos.ts (1)

208-215: MCP-facing schema currently omits XOR; export refined schema or add explicit constraint metadata

This mirrors a prior review concern. Exporting baseSchemaObject.shape hides the XOR constraint from MCP consumers, even though runtime validation uses the refined schema. Prefer exposing the refined schema if the MCP SDK accepts it. Otherwise, keep the shape but add explicit machine-readable metadata so clients can enforce XOR.

Option A — expose refined schema (preferred if supported):

-  schema: baseSchemaObject.shape, // MCP SDK compatibility
+  // Expose the refined schema so MCP consumers see XOR constraints
+  schema: buildRunMacOSSchema,

Option B — keep shape and add constraints metadata (if SDK limitation exists):

   name: 'build_run_macos',
   description:
     "Builds and runs a macOS app from a project or workspace in one step. Provide exactly one of projectPath or workspacePath. Example: build_run_macos({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
-  schema: baseSchemaObject.shape, // MCP SDK compatibility
+  schema: baseSchemaObject.shape, // MCP SDK compatibility
+  // Non-standard extension to communicate XOR to consumers
+  constraints: { xor: ['projectPath', 'workspacePath'] },

Also double-check repo-wide descriptions/tests for pre-unified tool names.

Run this verification to catch stragglers:

#!/bin/bash
set -euo pipefail

echo "Searching for pre-unified tool names..."
rg -n --hidden --glob '!*node_modules*' --glob '!*.lock' \
  -e 'list_schemes_ws' \
  -e 'list_schemes_proj' \
  -e 'build_run_mac_proj' \
  -e 'build_run_macos_ws' \
  -e 'get_macos_app_path_proj' \
  -e 'get_macos_app_path_ws' \
  -e 'run_ios_simulator_by_name' \
  -e 'run_ios_simulator_by_id' \
  -e 'boot_simulator_by_name' \
  -e 'boot_simulator_by_id' \
  || echo "No pre-unified names found."

echo
echo "Spot-check mentions of XOR guidance..."
rg -n --hidden --glob '!*node_modules*' --glob '!*.lock' \
  -e 'Provide exactly one of projectPath or workspacePath' \
  -e 'projectPath.*workspacePath' \
  docs \
  src/mcp/tools \
  || true
src/mcp/tools/macos/get_macos_app_path.ts (3)

84-88: Derived data path now applies to both project and workspace — good fix

This addresses the earlier restriction and aligns with xcodebuild capabilities.


95-98: extraArgs no longer gated by project/workspace — good

Accepting extraArgs regardless of project/workspace fixes the earlier limitation.


89-94: Always pass -destination platform=macOS; add arch only when provided

Previously this flag was applied only for workspaces; now it’s gated on arch. For consistency and to ensure settings resolve deterministically (especially with multi-platform workspaces), pass -destination platform=macOS always and append ,arch=… only when params.arch is set.

-    // Handle destination for macOS when arch is specified
-    if (params.arch) {
-      const destinationString = `platform=macOS,arch=${params.arch}`;
-      command.push('-destination', destinationString);
-    }
+    // Always target macOS; optionally include arch
+    {
+      let destinationString = 'platform=macOS';
+      if (params.arch) {
+        destinationString += `,arch=${params.arch}`;
+      }
+      command.push('-destination', destinationString);
+    }
src/mcp/tools/device/test_device.ts (1)

21-21: Good: duplication removed for nullifyEmptyStrings.

Importing the shared helper conforms to DRY and resolves the prior duplication issue flagged earlier.

🧹 Nitpick comments (9)
src/mcp/tools/macos/build_run_macos.ts (3)

17-21: Strengthen input validation: trim/validate scheme and ensure correct file extensions

To prevent subtle input issues:

  • Trim and require non-empty scheme.
  • Enforce proper extensions for projectPath and workspacePath.

Apply this diff:

-  projectPath: z.string().optional().describe('Path to the .xcodeproj file'),
-  workspacePath: z.string().optional().describe('Path to the .xcworkspace file'),
-  scheme: z.string().describe('The scheme to use'),
+  projectPath: z
+    .string()
+    .transform((s) => s.trim())
+    .refine((p) => p.endsWith('.xcodeproj'), { message: 'projectPath must end with .xcodeproj' })
+    .optional()
+    .describe('Path to the .xcodeproj file'),
+  workspacePath: z
+    .string()
+    .transform((s) => s.trim())
+    .refine((p) => p.endsWith('.xcworkspace'), { message: 'workspacePath must end with .xcworkspace' })
+    .optional()
+    .describe('Path to the .xcworkspace file'),
+  scheme: z
+    .string()
+    .transform((s) => s.trim())
+    .min(1, 'Scheme is required and cannot be blank')
+    .describe('The scheme to use'),

54-56: Avoid double-defaulting configuration and preferXcodebuild

Defaults for configuration and preferXcodebuild are set both in the wrapper (default export handler) and in the internal logic (_handleMacOSBuildLogic). Keep the defaults in a single place (the logic) to reduce drift.

Apply this diff to the handler wrapper:

   handler: createTypedTool<BuildRunMacOSParams>(
     buildRunMacOSSchema as z.ZodType<BuildRunMacOSParams>,
-    (params: BuildRunMacOSParams, executor) =>
-      buildRunMacOSLogic(
-        {
-          ...params,
-          configuration: params.configuration ?? 'Debug',
-          preferXcodebuild: params.preferXcodebuild ?? false,
-        },
-        executor,
-      ),
+    (params: BuildRunMacOSParams, executor) => buildRunMacOSLogic(params, executor),
     getDefaultCommandExecutor,
   ),

Also applies to: 69-70, 213-221


166-171: Launching via open looks fine

Launching the resolved .app bundle directly is appropriate for macOS. If you later need to block until app termination, consider open -W, but current behavior is reasonable.

src/mcp/tools/macos/get_macos_app_path.ts (3)

10-11: Consolidate duplicate imports from utils/index.js

Minor cleanup: merge these two imports into one to avoid duplication.

-import { log } from '../../../utils/index.js';
-
-import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.js';
+import { log, CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.js';

70-78: Unreachable branch handled defensively; consider simplifying

The else branch throwing when neither path is provided should be unreachable due to schema validation. Keeping it is okay for defense-in-depth, but a short comment noting “unreachable due to schema” would reduce future confusion.

     } else {
-      // This should never happen due to schema validation
+      // Unreachable due to schema validation; retained for defense-in-depth
       throw new Error('Neither projectPath nor workspacePath provided');
     }

127-130: Make regexes resilient to varying whitespace

xcodebuild -showBuildSettings output can differ in spacing/indentation. Slightly harden the patterns to reduce false negatives.

-    const builtProductsDirMatch = buildSettingsOutput.match(/BUILT_PRODUCTS_DIR = (.+)$/m);
-    const fullProductNameMatch = buildSettingsOutput.match(/FULL_PRODUCT_NAME = (.+)$/m);
+    const builtProductsDirMatch = buildSettingsOutput.match(/^\s*BUILT_PRODUCTS_DIR\s*=\s*(.+)\s*$/m);
+    const fullProductNameMatch = buildSettingsOutput.match(/^\s*FULL_PRODUCT_NAME\s*=\s*(.+)\s*$/m);
src/mcp/tools/device/test_device.ts (3)

23-27: XOR schema validation looks correct; consider superRefine for clearer, single-point errors.

Current refine rules work, but produce two separate root-level errors in some cases. superRefine can emit a single, clearer message in one pass.

Optional improvement:

-const testDeviceSchema = baseSchema
-  .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
-    message: 'Either projectPath or workspacePath is required.',
-  })
-  .refine((val) => !(val.projectPath !== undefined && val.workspacePath !== undefined), {
-    message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
-  });
+const testDeviceSchema = baseSchema.superRefine((val, ctx) => {
+  const hasProject = val.projectPath !== undefined;
+  const hasWorkspace = val.workspacePath !== undefined;
+  if (!hasProject && !hasWorkspace) {
+    ctx.addIssue({
+      code: z.ZodIssueCode.custom,
+      message: 'Either projectPath or workspacePath is required.',
+      path: [], // root
+    });
+  } else if (hasProject && hasWorkspace) {
+    ctx.addIssue({
+      code: z.ZodIssueCode.custom,
+      message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
+      path: [], // root
+    });
+  }
+});

Also applies to: 39-47, 49-49


169-178: Make cleanup idempotent and avoid redundant invocations.

cleanup() is called in success, error, and finally blocks. That’s functionally safe but redundant and can mask cleanup failures. Make cleanup idempotent and call it only in finally.

Apply this diff:

   let tempDir: string | undefined;
   const cleanup = async (): Promise<void> => {
     if (!tempDir) return;
     try {
       await fileSystemExecutor.rm(tempDir, { recursive: true, force: true });
+      tempDir = undefined; // prevent repeat attempts
     } catch (cleanupError) {
       log('warn', `Failed to clean up temporary directory: ${cleanupError}`);
     }
   };
@@
-      // Clean up temporary directory
-      await cleanup();
+      // cleanup handled in finally
@@
-      await cleanup();
+      // cleanup handled in finally
@@
   } finally {
     await cleanup();
   }

Also applies to: 230-231, 247-248, 255-257


266-274: Avoid double-defaulting platform (set default in one place).

Handler applies a default for platform, and testDeviceLogic/execution also defaults to iOS. Centralize to a single place to reduce drift.

Apply this diff:

   handler: createTypedTool<TestDeviceParams>(
     testDeviceSchema as z.ZodType<TestDeviceParams>,
     (params: TestDeviceParams, executor: CommandExecutor) => {
-      return testDeviceLogic(
-        {
-          ...params,
-          platform: params.platform ?? 'iOS',
-        },
-        executor,
-        getDefaultFileSystemExecutor(),
-      );
+      return testDeviceLogic(params, executor, getDefaultFileSystemExecutor());
     },
     getDefaultCommandExecutor,
   ),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cfb0d02 and 6f95e41.

📒 Files selected for processing (7)
  • src/mcp/tools/device/test_device.ts (8 hunks)
  • src/mcp/tools/macos/build_run_macos.ts (7 hunks)
  • src/mcp/tools/macos/get_macos_app_path.ts (7 hunks)
  • src/mcp/tools/project-discovery/__tests__/list_schemes.test.ts (11 hunks)
  • src/mcp/tools/project-discovery/list_schemes.ts (1 hunks)
  • src/mcp/tools/project-scaffolding/__tests__/scaffold_macos_project.test.ts (2 hunks)
  • src/mcp/tools/project-scaffolding/scaffold_macos_project.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/mcp/tools/project-scaffolding/tests/scaffold_macos_project.test.ts
  • src/mcp/tools/project-scaffolding/scaffold_macos_project.ts
  • src/mcp/tools/project-discovery/list_schemes.ts
  • src/mcp/tools/project-discovery/tests/list_schemes.test.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/mcp/tools/macos/build_run_macos.ts (2)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/device/test_device.ts (6)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/command.ts (3)
  • getDefaultCommandExecutor (232-243)
  • FileSystemExecutor (41-52)
  • getDefaultFileSystemExecutor (249-260)
src/utils/logger.ts (1)
  • log (33-53)
src/utils/template-manager.ts (1)
  • cleanup (135-143)
src/utils/validation.ts (1)
  • createTextResponse (35-45)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/macos/get_macos_app_path.ts (2)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
🔇 Additional comments (10)
src/mcp/tools/macos/build_run_macos.ts (4)

2-2: Unified tool docstrings read clearly — LGTM

The top-level description cleanly communicates the unified behavior and XOR input requirement.

Also applies to: 4-5


15-15: DRY win: shared nullifyEmptyStrings helper import — LGTM

Importing the shared helper eliminates duplication and keeps preprocessing consistent across tools.


38-46: XOR validation is correctly enforced at runtime — LGTM

The preprocess + dual refine sequence correctly ensures “exactly one of projectPath or workspacePath”.


83-88: LGTM: Properly selects project vs workspace for showBuildSettings

The branching correctly appends either -project or -workspace based on validated inputs.

src/mcp/tools/macos/get_macos_app_path.ts (3)

15-41: XOR schema with input normalization looks solid

Good use of nullifyEmptyStrings preprocessing plus XOR validation to ensure exactly one of projectPath/workspacePath is provided. This prevents a whole class of edge cases with empty strings and ambiguous inputs.


147-151: Next steps text is correct — no rename detected

Verified: the launcher is named launch_mac_app and get_app_bundle_id exists. No renaming to launch_macos_app was found, so the Next Steps strings (and snapshots) don’t need updating.

  • src/mcp/tools/macos/launch_mac_app.ts — name: 'launch_mac_app' (line ~81)
  • src/mcp/tools/project-discovery/get_app_bundle_id.ts — name: 'get_app_bundle_id' (line ~129)
  • src/mcp/tools/project-discovery/get_mac_bundle_id.ts — name: 'get_mac_bundle_id' (line ~125)

183-186: Confirm MCP “schema” export shape is correct for docs/SDK consumers

Verified: src/mcp/tools/macos/get_macos_app_path.ts exports schema: baseSchemaObject.shape (losing the .refine XOR checks), while the handler uses the refined getMacosAppPathSchema.

  • File to check:
    • src/mcp/tools/macos/get_macos_app_path.ts
      • getMacosAppPathSchema (with two .refine calls enforcing "exactly one of projectPath/workspacePath") — defined earlier in the file.
      • Exported schema: schema: baseSchemaObject.shape (line ~183) — this does not include the refinements.
  • Repo-wide note: This .shape export pattern is used in many tools (search shows numerous occurrences), so changing behavior may affect MCP SDK/docs pipelines that expect shape-only exports.

Recommendation (pick one based on your docs/SDK pipeline capabilities):

  • If your docs generator/SDK supports full Zod schemas, export the refined schema so docs can reflect the XOR constraint:
    • schema: getMacosAppPathSchema as unknown as z.ZodTypeAny
  • Otherwise (if you must keep .shape for SDK compatibility), ensure the docs/descriptions explicitly state the XOR requirement (the current description already says "Provide exactly one of projectPath or workspacePath" — confirm your docs renderer surfaces that text).
src/mcp/tools/device/test_device.ts (3)

72-74: Good defensive check for empty xcresult output.

Guarding against empty output avoids JSON.parse errors and returns actionable feedback earlier.


2-6: Confirmed: unified tool names (list_devices / list_schemes) are in use — no legacy variants found

Searches of src/ and docs/ show only the unified tool names; I found no occurrences of list_devices_proj, list_devices_ws, list_schemes_ws, or list_schemes_proj.

Key hits (representative)

  • src/mcp/tools/device/list_devices.ts — defines name: 'list_devices'
  • src/mcp/tools/project-discovery/list_schemes.ts — defines name: 'list_schemes'
  • src/mcp/tools/device/test_device.ts — deviceId description references list_devices
  • src/mcp/tools/device/list_schemes.ts, src/mcp/tools/simulator/list_schemes.ts, src/mcp/tools/macos/list_schemes.ts — re-exports to unified list_schemes
  • docs/TOOLS.md, docs/MANUAL_TESTING.md, docs/TESTING.md — examples and usage referencing list_devices / list_schemes

Notes

  • Some docs (docs/PLUGIN_DEVELOPMENT.md, docs/ESLINT_TYPE_SAFETY.md, docs/PHASE1-TASKS.md) mention the _proj/_ws suffixes as naming conventions or historical examples — these are explanatory and not active legacy tool names.

Action

  • No changes required to src/mcp/tools/device/test_device.ts description; referencing list_devices is correct.

262-265: Do not change just this file — schema: baseSchemaObject.shape is intentional for SDK compatibility

The repo consistently exports baseSchemaObject.shape for the exported schema (MCP SDK compatibility) and core/plugin-types.ts expects a shape-like mapping. Replacing this single file with the Zod effect (testDeviceSchema) would be inconsistent and could break consumers/docs tooling. Either update the docs generator to handle ZodEffects (preprocess/refine) or change the plugin typing and update all tools — the latter is a large breaking change.

Files that use baseSchemaObject.shape (representative):

  • src/mcp/tools/device/test_device.ts (current file)
  • src/mcp/tools/device/build_device.ts
  • src/mcp/tools/device/get_device_app_path.ts
  • src/mcp/tools/simulator/test_simulator.ts
  • src/mcp/tools/simulator/build_run_simulator.ts
  • src/mcp/tools/macos/test_macos.ts
  • src/mcp/tools/macos/build_run_macos.ts
  • src/mcp/tools/project-discovery/list_schemes.ts
  • src/core/plugin-types.ts

If you still want to expose the full Zod schema to docs/consumers, you can apply this change across the codebase (and update plugin types/docs generator). Example diff (single-file change — not recommended without broader changes):

 export default {
   name: 'test_device',
   description: 'Runs tests for an Apple project or workspace ...',
-  schema: baseSchemaObject.shape,
+  schema: testDeviceSchema,

Recommendation: Keep schema as baseSchemaObject.shape for compatibility and either (A) enhance the docs generator to understand ZodEffects (preprocess/refine) or (B) plan a coordinated, repo-wide migration to export full ZodTypes and update plugin typings and consumers.

Likely an incorrect or invalid review comment.

Co-authored-by: web <web@cameroncooke.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (12)
src/mcp/tools/macos/__tests__/build_macos.test.ts (4)

31-56: Strengthen schema tests with empty and mixed-type validation

Current validation is solid. Consider adding negative cases for empty strings and mixed-type arrays in extraArgs to catch more real-world invalid inputs early.

Apply within this test block:

       // Test invalid inputs
       expect(buildMacOS.schema.projectPath.safeParse(null).success).toBe(false);
       expect(buildMacOS.schema.workspacePath.safeParse(null).success).toBe(false);
       expect(buildMacOS.schema.scheme.safeParse(null).success).toBe(false);
+      expect(buildMacOS.schema.projectPath.safeParse('').success).toBe(false);
+      expect(buildMacOS.schema.workspacePath.safeParse('').success).toBe(false);
+      expect(buildMacOS.schema.scheme.safeParse('').success).toBe(false);
       expect(buildMacOS.schema.arch.safeParse('invalidArch').success).toBe(false);
       expect(buildMacOS.schema.extraArgs.safeParse('not-array').success).toBe(false);
+      // Mixed-type array should fail
+      expect(buildMacOS.schema.extraArgs.safeParse(['--valid', 123 as any]).success).toBe(false);
       expect(buildMacOS.schema.preferXcodebuild.safeParse('not-boolean').success).toBe(false);

66-86: Success response expectations consistent; minor wording nit

The success assertion matches the expected ToolResponse. However, the phrase "macOS Build build" reads awkwardly (duplicated "build"). Consider simplifying the underlying message to "macOS build succeeded for scheme ..." and update tests accordingly to reduce redundancy.

If you opt to adjust messaging, I can propose a coordinated change across the underlying builder utility and tests.


381-413: Workspace minimal command generation added—great

Validates switch from -project to -workspace while retaining defaults and destination. Consider adding a companion test for workspace with all parameters to mirror the project variant.

Here’s a test you can append in this section:

+    it('should generate correct xcodebuild workspace command with all parameters', async () => {
+      let capturedCommand: string[] = [];
+      const mockExecutor = createMockExecutor({ success: true, output: 'BUILD SUCCEEDED' });
+      const spyExecutor = async (command: string[]) => {
+        capturedCommand = command;
+        return mockExecutor(command);
+      };
+
+      await buildMacOSLogic(
+        {
+          workspacePath: '/path/to/workspace.xcworkspace',
+          scheme: 'MyScheme',
+          configuration: 'Release',
+          arch: 'x86_64',
+          derivedDataPath: '/custom/derived',
+          extraArgs: ['--verbose'],
+          preferXcodebuild: true,
+        },
+        spyExecutor,
+      );
+
+      expect(capturedCommand).toEqual([
+        'xcodebuild',
+        '-workspace',
+        '/path/to/workspace.xcworkspace',
+        '-scheme',
+        'MyScheme',
+        '-configuration',
+        'Release',
+        '-skipMacroValidation',
+        '-destination',
+        'platform=macOS,arch=x86_64',
+        '-derivedDataPath',
+        '/custom/derived',
+        '--verbose',
+        'build',
+      ]);
+    });

432-465: XOR success paths validated via logic

Both project and workspace success paths return non-error responses. Consider adding handler-based success tests as well to verify handler wiring (parsing + delegation) end-to-end.

I can add small handler success tests if you want parity with the error-path handler tests.

src/mcp/tools/device/__tests__/build_device.test.ts (2)

18-20: Reduce brittleness of description assertion; ensure it reflects unified naming

Asserting the entire description string is fragile and will cause churn if copy changes slightly. Prefer checking essential phrases to keep tests stable while still enforcing correctness.

Apply this diff to assert key parts while allowing non-breaking edits:

-    it('should have correct description', () => {
-      expect(buildDevice.description).toBe(
-        "Builds an app from a project or workspace for a physical Apple device. Provide exactly one of projectPath or workspacePath. Example: build_device({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
-      );
-    });
+    it('should have correct description', () => {
+      const { description } = buildDevice;
+      expect(description).toContain('Builds an app from a project or workspace');
+      expect(description).toContain('exactly one of projectPath or workspacePath');
+      expect(description).toContain(
+        "build_device({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
+      );
+    });

55-74: Align XOR validation messages with the description and reduce coupling to exact phrasing

The error text checks are coupled to specific phrasing (“Either ... is required”, “mutually exclusive”). The description already uses “Provide exactly one of projectPath or workspacePath”. Consider aligning error messaging to the same phrasing and loosening the assertion to key phrases.

For example:

- expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
+ expect(result.content[0].text).toContain('exactly one of projectPath or workspacePath');

- expect(result.content[0].text).toContain('mutually exclusive');
+ expect(result.content[0].text).toContain('exactly one of projectPath or workspacePath');

If you prefer keeping both checks, assert both key terms (“projectPath” and “workspacePath”) rather than the exact sentence.

src/mcp/tools/macos/__tests__/get_macos_app_path.test.ts (6)

156-197: DRY the manual executor capturing (optional)

The repeated inline mock executors/call arrays across command-generation tests are clear but repetitive. Consider extracting a tiny helper to reduce duplication and ease future updates.

Example helper:

function makeCapturingExecutor(calls: unknown[]): CommandExecutor {
  return async (...args) => {
    calls.push(args);
    return { success: true, output: 'BUILT_PRODUCTS_DIR = /path/to/build\nFULL_PRODUCT_NAME = MyApp.app', error: undefined, process: { pid: 12345 } };
  };
}

Also applies to: 199-240, 242-285, 287-327


199-240: Command generation (workspace, x86_64) — LGTM

Correct destination string for x86_64. Suggest adding one more test for derivedDataPath/extraArgs with workspace to mirror parity with the project path case.


242-285: Command generation (project, derivedDataPath + extraArgs) — LGTM; add an extraArgs edge case

This covers derivedDataPath and a simple flag. Consider adding an extra arg that takes a value (e.g., '-sdk', 'macosx14.2') to ensure correct tokenization and ordering when appending extraArgs.


347-376: Happy path (workspace) — LGTM; consider a variant with spaces in product name

Success payload and “Next Steps” guidance match the logic contract. Add a case where FULL_PRODUCT_NAME includes spaces (e.g., "My App.app") to guard parsing robustness.


440-447: Missing app-path keys in output — LGTM; add “no output” variant

Covers the “settings present but not the required keys” case. Consider adding a “no output at all” scenario to cover that branch explicitly.

Also applies to: 448-456


464-471: Exception handling — LGTM; add string-throw variant (optional)

Covers thrown Error objects. For completeness with createMockExecutor semantics, add a variant where the executor rejects with a string to ensure uniform error formatting.

Also applies to: 472-480

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6f95e41 and e609e68.

📒 Files selected for processing (4)
  • src/mcp/tools/device/__tests__/build_device.test.ts (9 hunks)
  • src/mcp/tools/macos/__tests__/build_macos.test.ts (15 hunks)
  • src/mcp/tools/macos/__tests__/build_run_macos.test.ts (13 hunks)
  • src/mcp/tools/macos/__tests__/get_macos_app_path.test.ts (15 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/mcp/tools/macos/tests/build_run_macos.test.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/mcp/tools/macos/__tests__/build_macos.test.ts (3)
build-plugins/plugin-discovery.js (1)
  • result (172-175)
src/mcp/tools/macos/build_macos.ts (1)
  • buildMacOSLogic (63-87)
src/utils/command.ts (1)
  • createMockExecutor (267-305)
src/mcp/tools/device/__tests__/build_device.test.ts (3)
build-plugins/plugin-discovery.js (1)
  • result (172-175)
src/utils/command.ts (1)
  • createMockExecutor (267-305)
src/mcp/tools/device/build_device.ts (1)
  • buildDeviceLogic (42-61)
src/mcp/tools/macos/__tests__/get_macos_app_path.test.ts (2)
src/mcp/tools/macos/get_macos_app_path.ts (1)
  • get_macos_app_pathLogic (58-177)
src/utils/command.ts (1)
  • createMockExecutor (267-305)
🔇 Additional comments (27)
src/mcp/tools/macos/__tests__/build_macos.test.ts (10)

11-11: Import updated to unified tool looks correct

Switch to build_macos.js default export (buildMacOS) and named export (buildMacOSLogic) aligns with the unification. The ESM .js extension is appropriate for Vitest + TS in ESM mode.


88-115: Failure response expectations look good

Exact stderr forwarding and the failure summary are asserted precisely, which is valuable for deterministic UX.


123-148: Optional-params path validated; good coverage

Covers configuration, arch, derivedDataPath, extraArgs, and preferXcodebuild. This ensures command construction branches are exercised without relying on mocks.


157-174: Exception and spawn-error handling covered

Both thrown-error paths are validated. This is important for MCP runtime stability.

Also applies to: 183-200


214-235: Project minimal command generation—arguments order and defaults verified

The exact xcodebuild argv array, default Debug, -skipMacroValidation, and macOS destination are correctly asserted. Good.


247-276: All-params command generation for project is correct

Destination with arch, -derivedDataPath, and passthrough extraArgs verified. This matches expected normalization.


288-312: Edge cases (derivedDataPath-only, arm64-only, spaces in path) are well covered

These cases frequently regress; good to see exact argv checks.

Also applies to: 324-346, 358-379


415-430: XOR validation error paths covered via handler

Great: tests assert both "neither provided" and "both provided" error conditions via the public handler, which matches the user-facing contract.


11-11: No syntax error found — build_macos.ts log line is valid

I searched for "Starting macOS build for scheme" and found the correctly-closed template string and parentheses:

  • src/mcp/tools/macos/build_macos.ts — line 68: log('info', Starting macOS build for scheme ${params.scheme} (internal));

The suggested patch is unnecessary — no changes required.


16-16: Plugin metadata matches tests — no action required

Verified src/mcp/tools/macos/build_macos.ts contains the expected metadata: name is 'build_macos' and the description exactly matches the test string (includes the "Provide exactly one of projectPath or workspacePath" XOR note and example).

  • File: src/mcp/tools/macos/build_macos.ts — lines ~90–92 contain:
    • name: 'build_macos'
    • description: "Builds a macOS app using xcodebuild from a project or workspace. Provide exactly one of projectPath or workspacePath. Example: build_macos({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })"
src/mcp/tools/device/__tests__/build_device.test.ts (8)

14-15: LGTM: unified tool name assertion matches the new API

The export’s name check aligns with the unified tool surface (build_device). No issues here.


76-97: LGTM: handler parameter validation tests are precise and cover common failure paths

The tests exercise missing required fields and invalid types through the handler, ensuring Zod feedback is surfaced. Looks good.


100-117: LGTM: happy path (project) via logic with injected executor

Deterministic executor injection, defaulting configuration to Debug, and success assertions are all solid.


161-188: LGTM: command args verification for project-based build is thorough

Ensures the exact xcodebuild invocation, including defaults and destination. Nicely deterministic.


218-245: LGTM: failure response assertion is exact and validates stderr surfacing

The test covers the failure contract precisely, ensuring error output is exposed and the error flag is set.


247-303: LGTM: optional parameters propagate into the command as expected

Validates configuration override, derived data path, and extra args injection. Solid coverage.


196-215: Verified: canonical tool names unchanged — no test update required

Both tool names exist in the repo and match the test expectation.

  • src/mcp/tools/device/get_device_app_path.ts — exports name 'get_device_app_path'
  • src/utils/build-utils.ts — contains "Get App Path: get_device_app_path" and "Get Bundle ID: get_ios_bundle_id"
  • src/mcp/tools/device/tests/build_device.test.ts — expected Next Steps string includes both names

Conclusion: no changes needed to the implementation or this test. (Optional: if you want to reduce brittleness, assert the presence of "Get App Path" and "Get Bundle ID" rather than the full literal.)


28-51: No change required — exported schema already exposes field validators (tests are correct)

I checked src/mcp/tools/device/build_device.ts: baseSchemaObject is defined as a z.object (includes projectPath, workspacePath, scheme, etc.) and the module export sets schema: baseSchemaObject.shape (around line 67). That means buildDevice.schema.projectPath.safeParse(...) is valid and the suggested .shape changes in the original comment are unnecessary.

  • File: src/mcp/tools/device/build_device.ts
    • baseSchemaObject defined (projectPath, workspacePath, scheme...) — ~lines 16–19
    • export uses schema: baseSchemaObject.shape — line 67

Action: ignore the diff in the original comment; no test changes required.

Likely an incorrect or invalid review comment.

src/mcp/tools/macos/__tests__/get_macos_app_path.test.ts (9)

9-9: Public API rename wired correctly (imports and name assertion) — LGTM

Import path and exported names align with the unified tool. The name assertion matches the expected public API identifier.

Also applies to: 11-15


117-154: Command generation (project, minimal params) — LGTM

Covers the -project flow with default Debug configuration. Good parity with the workspace test.


156-197: Command generation (workspace, all params) — LGTM

Correct inclusion of -configuration Release and -destination platform=macOS,arch=arm64.


287-327: Default configuration fallback — LGTM

Asserts ‘Debug’ when configuration is omitted, with -destination present for arch=arm64. Good coverage.


378-407: Happy path (project) — LGTM

Mirrors the workspace case, ensuring consistent behavior for projects.


415-422: Build settings failure path — LGTM

Asserts the exact error text from a failed xcodebuild invocation. Matches the described behavior.

Also applies to: 423-431


96-115: Command generation (workspace, minimal params) — LGTM

Label "Get App Path" confirmed in src/mcp/tools/macos/get_macos_app_path.ts:101. No changes required.


27-52: Schema exposes per-field Zod validators — no change required

Verified: the default export sets schema: baseSchemaObject.shape, and baseSchemaObject defines projectPath, workspacePath, scheme, arch, derivedDataPath, and extraArgs as Zod types. So calls like getMacosAppPath.schema.workspacePath.safeParse(...) are valid.

  • File: src/mcp/tools/macos/get_macos_app_path.ts
    • baseSchemaObject definition (lines ~27–31)
    • default export uses schema: baseSchemaObject.shape (line ~183)

18-20: Description matches source — no action required

Confirmed the description string in src/mcp/tools/macos/get_macos_app_path.ts exactly matches the literal asserted in the test.

  • src/mcp/tools/macos/get_macos_app_path.ts — description at line 182 matches the test literal.
  • src/mcp/tools/macos/tests/get_macos_app_path.test.ts — assertion at lines 18–20 matches the source.

Comment on lines 55 to 74
describe('XOR Validation', () => {
it('should error when neither projectPath nor workspacePath provided', async () => {
const result = await getMacosAppPath.handler({
scheme: 'MyScheme',
});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Either projectPath or workspacePath is required');
});

it('should error when both projectPath and workspacePath provided', async () => {
const result = await getMacosAppPath.handler({
projectPath: '/path/to/project.xcodeproj',
workspacePath: '/path/to/workspace.xcworkspace',
scheme: 'MyScheme',
});

expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('mutually exclusive');
});
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

XOR validation messages — confirm exact strings surfaced by handler

The tests assert substrings: “Either projectPath or workspacePath is required” and “mutually exclusive”. Verify the handler emits these exact messages (the logic function throws “Neither … provided” per snippet, which may differ).

If messages differ, update the handler to standardize on the phrasing used in tests and documentation.


🏁 Script executed:

#!/bin/bash
# Check for user-facing XOR messages
rg -n -A2 -B2 $'Either projectPath or workspacePath is required|mutually exclusive|Neither projectPath nor workspacePath provided' src/mcp/tools/macos/get_macos_app_path.ts

Length of output: 924


XOR validation messages — verified and minor consistency fix recommended

Schema refinements already contain the substrings used in tests, but the runtime fallback throw uses a different phrase — align them for consistency.

  • File: src/mcp/tools/macos/get_macos_app_path.ts
    • Schema refinements (≈ lines 35–41):
      • "Either projectPath or workspacePath is required."
      • "projectPath and workspacePath are mutually exclusive. Provide only one."
    • Runtime fallback (≈ lines 75–78):
      • throw new Error('Neither projectPath nor workspacePath provided');

Suggested change:
Replace

throw new Error('Neither projectPath nor workspacePath provided');

with

throw new Error('Either projectPath or workspacePath is required.');
🤖 Prompt for AI Agents
In src/mcp/tools/macos/get_macos_app_path.ts around lines 75–78, the runtime
fallback error message ('Neither projectPath nor workspacePath provided') is
inconsistent with the schema refinement message; update the thrown Error text to
exactly "Either projectPath or workspacePath is required." so runtime and schema
messages match the tests and ensure consistency.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursoragent please address

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on it! I'll start making changes in this branch.

Open in Cursor Open in Web

Comment on lines 330 to 345
describe('Handler Behavior (Complete Literal Returns)', () => {
it('should return Zod validation error for missing workspacePath', async () => {
const result = await getMacAppPathWs.handler({
scheme: 'MyScheme',
it('should return Zod validation error for missing scheme', async () => {
const result = await getMacosAppPath.handler({
workspacePath: '/path/to/MyProject.xcworkspace',
});

expect(result).toEqual({
content: [
{
type: 'text',
text: 'Error: Parameter validation failed\nDetails: Invalid parameters:\nworkspacePath: Required',
text: 'Error: Parameter validation failed\nDetails: Invalid parameters:\nscheme: Required',
},
],
isError: true,
});
});
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Zod error literal on missing scheme — verify exact formatting and standardize across tools

The exact, multi-line literal is asserted. Ensure the handler uses a consistent zod-to-text error formatting utility across tools to prevent drift.

If not centralized, consider extracting a reusable “formatZodError” helper to keep messaging consistent.


🏁 Script executed:

#!/bin/bash
# Verify standardized zod formatting utility usage and the exact text
rg -n -A3 -B3 $'Parameter validation failed|Invalid parameters' src/mcp/tools/macos/get_macos_app_path.ts

# Optional: find a shared error formatting util if one exists
rg -n $'zod|Zod' src | sed -n '1,200p'

Length of output: 18600


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== src/utils/typed-tool-factory.ts ==="
sed -n '1,240p' src/utils/typed-tool-factory.ts || true
echo
echo "=== src/mcp/tools/macos/get_macos_app_path.ts ==="
sed -n '1,400p' src/mcp/tools/macos/get_macos_app_path.ts || true
echo
echo "=== Files with `if (error instanceof z.ZodError)` ==="
rg -n "if \\(error instanceof z.ZodError\\)" src || true
echo
echo "=== Show excerpts for files that handle ZodError (first 200 lines each) ==="
for f in $(rg -n "if \\(error instanceof z.ZodError\\)" src -l || true); do
  echo "---- $f ----"
  sed -n '1,200p' "$f" || true
  echo
done

Length of output: 31891


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== List src/utils ==="
ls -la src/utils || true
echo

echo "=== Show src/utils/index.ts ==="
sed -n '1,240p' src/utils/index.ts || sed -n '1,240p' src/utils/index.js || true

Length of output: 2197


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for createErrorResponse/createTextResponse helpers ==="
rg -n "createErrorResponse|createTextResponse" src || true
echo

echo "=== src/utils/errors.ts ==="
sed -n '1,240p' src/utils/errors.ts || true
echo

echo "=== src/utils/validation.ts ==="
sed -n '1,240p' src/utils/validation.ts || true
echo

echo "=== Occurrences of 'Invalid parameters' across repo ==="
rg -n "Invalid parameters" || true
echo

echo "=== Show snippets around Zod error formatting in files that handle ZodError ==="
for f in $(rg -n "if \\(error instanceof z.ZodError\\)" src -l || true); do
  echo "---- $f ----"
  rg -n -A2 -B2 "if \\(error instanceof z.ZodError\\)" "$f" || true
  sed -n '1,220p' "$f" | sed -n '1,220p' || true
  echo
done

Length of output: 57356


Standardize Zod validation error formatting across handlers

Short: createTypedTool already formats Zod errors via createErrorResponse (producing the "Error: \nDetails:

Details" literal). Several handlers catch ZodError and build a different literal (Parameter validation failed. Invalid parameters:\n...), causing inconsistent output and brittle tests.

Files to update:

  • src/mcp/tools/simulator/build_simulator.ts — local Zod catch builds Parameter validation failed. Invalid parameters:…
  • src/mcp/tools/simulator/build_run_simulator.ts — same
  • src/mcp/tools/simulator/test_simulator.ts — same
    (typed-tool-factory.ts is already the canonical implementation)

Recommended fixes (pick one):

  • Replace the manual Zod catch in the files above with the same call used by createTypedTool:
    return createErrorResponse(
    'Parameter validation failed',
    Invalid parameters:\n${errorMessages.join('\n')},
    );
  • OR wrap those tools with createTypedTool so Zod handling is centralized.
  • Optionally, extract a small helper (e.g., formatZodErrorResponse) into src/utils/ (validation.ts or errors.ts) to produce the errorMessages and call createErrorResponse from a single place.

Because tests assert the current "Error: ...\nDetails: ..." literal in many places (e.g. src/mcp/tools/macos/tests/get_macos_app_path.test.ts), apply one of the fixes above to avoid future drift.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursoragent what do you think we should do here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

@cursor cursor bot Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I enhanced the build_device tool's testing and improved the robustness of xcodebuild output parsing across several tools. I also refactored a common type definition for better code organization.

  • I added a new test case in src/mcp/tools/device/__tests__/build_device.test.ts to verify the correct xcodebuild command generation when a workspace path is provided, ensuring proper -workspace and -scheme arguments.
  • I refactored the XcodePlatform enum by moving its definition from src/mcp/tools/device/get_device_app_path.ts to src/types/common.js, promoting reusability and centralizing type definitions.
  • I updated the regex patterns for extracting BUILT_PRODUCTS_DIR and FULL_PRODUCT_NAME in src/mcp/tools/device/get_device_app_path.ts, src/mcp/tools/macos/build_run_macos.ts, src/mcp/tools/macos/get_macos_app_path.ts, src/mcp/tools/simulator/build_run_simulator.ts, and src/mcp/tools/simulator/get_simulator_app_path.ts. The new patterns (/^\s*KEY\s*=\s*(.+)$/m) are more robust against varying whitespace in xcodebuild output.
  • I standardized error messages in get_device_app_path.ts and get_macos_app_path.ts to Either projectPath or workspacePath is required. for clarity.

4 tasks completed

  • Add workspace command generation test to build_device.test.ts to verify -workspace flag usage
  • Update XOR validation error message consistency in get_macos_app_path.ts and get_device_app_path.ts
  • Make build settings parsing more robust to whitespace variations
  • Remove duplicate XcodePlatform definition in get_device_app_path.ts
Open in Cursor Open in Web

Learn more about Cursor Agents

cursoragent and others added 2 commits August 14, 2025 18:54
Co-authored-by: web <web@cameroncooke.com>
Co-authored-by: web <web@cameroncooke.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
src/mcp/tools/device/__tests__/build_device.test.ts (1)

256-266: Test labels OK; docs contain stale pre-unified tool name(s) — please update

Checked the repo for pre-unified tool names. The test snippet in src/mcp/tools/device/tests/build_device.test.ts (lines 256–266) already uses the unified names (get_device_app_path, get_ios_bundle_id). I only found stale/typo references in documentation:

  • docs/MANUAL_TESTING.md: lines 510, 556, 657, 661 — "list_schems_proj"
  • docs/TESTING.md: lines 1002, 1048, 1149, 1153 — "list_schems_proj"

Please update those docs to the unified name (e.g., list_schemes) and fix the typo. No code/test changes required otherwise.

♻️ Duplicate comments (2)
src/mcp/tools/device/__tests__/build_device.test.ts (1)

138-188: Workspace command-generation test added — addresses prior review

This directly validates the -workspace path and ensures correct flag composition. It resolves the earlier ask to assert the generated workspace command.

src/mcp/tools/macos/get_macos_app_path.ts (1)

84-99: Addressed earlier review: derivedDataPath/extraArgs now apply to both project and workspace

The previous limitation has been removed; both options are now passed regardless of project/workspace. This resolves the earlier concern.

🧹 Nitpick comments (8)
src/mcp/tools/device/__tests__/build_device.test.ts (3)

17-21: Description assertion aligns with unified surface; consider showing both XOR examples

Good that the description reflects the unified API and XOR requirement. As a minor enhancement, consider including a workspace-based example as well to communicate parity explicitly.


299-355: Add the “optional params for workspace” symmetry test

You assert optional parameters are included in the project command. Mirror this for workspace to guarantee parity and prevent regressions across both entry points.

Apply this diff to add the workspace-optional-params test:

@@
   describe('Handler Behavior (Complete Literal Returns)', () => {
@@
     it('should include optional parameters in command', async () => {
@@
       });
     });
+
+    it('should include optional parameters in workspace command', async () => {
+      const commandCalls: Array<{
+        args: string[];
+        logPrefix: string;
+        silent: boolean;
+        timeout: number | undefined;
+      }> = [];
+
+      const stubExecutor = async (
+        args: string[],
+        logPrefix: string,
+        silent: boolean,
+        timeout?: number,
+      ) => {
+        commandCalls.push({ args, logPrefix, silent, timeout });
+        return {
+          success: true,
+          output: 'Build succeeded',
+          error: undefined,
+          process: { pid: 12345 },
+        };
+      };
+
+      await buildDeviceLogic(
+        {
+          workspacePath: '/path/to/MyProject.xcworkspace',
+          scheme: 'MyScheme',
+          configuration: 'Release',
+          derivedDataPath: '/tmp/derived-data',
+          extraArgs: ['--verbose'],
+        },
+        stubExecutor,
+      );
+
+      expect(commandCalls).toHaveLength(1);
+      expect(commandCalls[0]).toEqual({
+        args: [
+          'xcodebuild',
+          '-workspace',
+          '/path/to/MyProject.xcworkspace',
+          '-scheme',
+          'MyScheme',
+          '-configuration',
+          'Release',
+          '-skipMacroValidation',
+          '-destination',
+          'generic/platform=iOS',
+          '-derivedDataPath',
+          '/tmp/derived-data',
+          '--verbose',
+          'build',
+        ],
+        logPrefix: 'iOS Device Build',
+        silent: true,
+        timeout: undefined,
+      });
+    });

114-117: Nit: Message wording reads as “Build build succeeded”

The prefix “iOS Device Build” followed by action “build succeeded” reads as “iOS Device Build build succeeded.” Consider rephrasing the status strings to avoid repetition (e.g., “iOS Device build succeeded” or “iOS Device Build succeeded”).

Also applies to: 256-262

src/mcp/tools/device/get_device_app_path.ts (2)

16-24: Prefer schema-level default for platform (simplifies logic and metadata)

Set the default platform to iOS at the schema level to avoid sprinkling defaults in logic. This also documents the default closer to the input contract.

Apply this diff to baseOptions:

-  platform: z
-    .enum(['iOS', 'watchOS', 'tvOS', 'visionOS'])
-    .optional()
-    .describe('Target platform (defaults to iOS)'),
+  platform: z
+    .enum(['iOS', 'watchOS', 'tvOS', 'visionOS'])
+    .default('iOS')
+    .describe('Target platform (defaults to iOS)'),

Optionally, you can then remove the fallback in logic (see Line 56).


96-99: Minor consistency: align executor invocation signature with macOS tool

macOS version passes a fourth argument (undefined). If CommandExecutor’s signature expects four params, align here for consistency (no behavior change).

Outside the shown range, adjust:

// Before:
const result = await executor(command, 'Get App Path', true);

// After:
const result = await executor(command, 'Get App Path', true, undefined);
src/mcp/tools/macos/get_macos_app_path.ts (3)

15-25: Capture defaults at the schema layer (configuration; optional: arch rationale remains)

You already default configuration in logic. Prefer declaring defaults in the schema for a tighter contract and cleaner logic.

Apply this diff to set the default for configuration:

-  configuration: z.string().optional().describe('Build configuration (Debug, Release, etc.)'),
+  configuration: z.string().default('Debug').describe('Build configuration (Debug, Release, etc.)'),

You can then remove the fallback in logic (Line 62).


89-94: Always pass a macOS destination; make arch an additive qualifier

To reduce host-dependent variability, pass -destination platform=macOS even when arch isn’t specified, and append ,arch=… only when provided.

Apply this diff:

-    // Handle destination for macOS when arch is specified
-    if (params.arch) {
-      const destinationString = `platform=macOS,arch=${params.arch}`;
-      command.push('-destination', destinationString);
-    }
+    // Always specify destination for macOS; add arch qualifier when provided
+    {
+      let destinationString = 'platform=macOS';
+      if (params.arch) {
+        destinationString += `,arch=${params.arch}`;
+      }
+      command.push('-destination', destinationString);
+    }

46-56: Avoid duplicating platform constants; import shared XcodePlatform

Local XcodePlatform duplicates values available in shared types. Importing the shared enum/const ensures consistency across tools.

Example change (outside the shown range):

// Replace local XcodePlatform const with:
import { XcodePlatform } from '../../../types/common.js';

// And use XcodePlatform.macOS in logging
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e609e68 and 21c6628.

📒 Files selected for processing (3)
  • src/mcp/tools/device/__tests__/build_device.test.ts (9 hunks)
  • src/mcp/tools/device/get_device_app_path.ts (4 hunks)
  • src/mcp/tools/macos/get_macos_app_path.ts (8 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/mcp/tools/device/__tests__/build_device.test.ts (3)
build-plugins/plugin-discovery.js (1)
  • result (172-175)
src/utils/command.ts (1)
  • createMockExecutor (267-305)
src/mcp/tools/device/build_device.ts (1)
  • buildDeviceLogic (42-61)
src/mcp/tools/macos/get_macos_app_path.ts (2)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
src/mcp/tools/device/get_device_app_path.ts (2)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
🔇 Additional comments (10)
src/mcp/tools/device/__tests__/build_device.test.ts (2)

54-73: XOR validation coverage is solid

Nice coverage for neither-provided and both-provided scenarios. This guards the unified surface contract well.


119-136: Workspace happy-path test adds parity with project flow

This validates the workspace execution path successfully. Complements the project path test and ensures both variants behave consistently.

src/mcp/tools/device/get_device_app_path.ts (4)

2-6: Docs accurately reflect unified/XOR inputs — LGTM

The header comment clearly states the unified tool behavior and XOR requirement between projectPath/workspacePath.


34-41: XOR validation between projectPath/workspacePath — LGTM

The refine checks enforce exactly one path is provided. This matches the unification goal and prevents ambiguous invocations.


65-73: Project/workspace flag selection — LGTM

Correctly chooses -project or -workspace and guards against the impossible state post-validation.


108-110: Anchored parsing of build settings — LGTM

The anchored/multiline regex for BUILT_PRODUCTS_DIR and FULL_PRODUCT_NAME is robust for xcodebuild output.

src/mcp/tools/macos/get_macos_app_path.ts (4)

2-6: Docs accurately reflect unified/XOR inputs — LGTM

The header comment aligns with the consolidation goals and clarifies the mutually exclusive inputs.


35-42: XOR validation between projectPath/workspacePath — LGTM

The refine checks ensure exactly one path is provided. Matches the unified tool contract.


70-78: Project/workspace flag selection — LGTM

Correct branching to select -project vs -workspace, with a guard for impossible state after validation.


128-130: Anchored parsing of build settings — LGTM

Anchored, multiline regex for BUILT_PRODUCTS_DIR and FULL_PRODUCT_NAME is robust for xcodebuild output.

Comment on lines 146 to 156
export default {
name: 'get_device_app_path_ws',
name: 'get_device_app_path',
description:
"Gets the app bundle path for a physical device application (iOS, watchOS, tvOS, visionOS) using a workspace. IMPORTANT: Requires workspacePath and scheme. Example: get_device_app_path_ws({ workspacePath: '/path/to/workspace', scheme: 'MyScheme' })",
schema: getDeviceAppPathWsSchema.shape, // MCP SDK compatibility
handler: createTypedTool(
getDeviceAppPathWsSchema,
get_device_app_path_wsLogic,
"Gets the app bundle path for a physical device application (iOS, watchOS, tvOS, visionOS) using either a project or workspace. Provide exactly one of projectPath or workspacePath. Example: get_device_app_path({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme' })",
schema: baseSchemaObject.shape, // MCP SDK compatibility
handler: createTypedTool<GetDeviceAppPathParams>(
getDeviceAppPathSchema as z.ZodType<GetDeviceAppPathParams>,
get_device_app_pathLogic,
getDefaultCommandExecutor,
),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Schema metadata vs runtime schema: XOR not reflected in exported schema

You export schema: baseSchemaObject.shape (for MCP SDK compatibility), but the handler validates against getDeviceAppPathSchema (with preprocess + XOR). This is fine functionally, but the metadata won’t encode the XOR requirement, which can mislead UIs/docs generated from schema.

  • If the MCP layer now supports richer schemas, consider exporting a metadata form that reflects XOR.
  • At minimum, keep the description explicit (you already do) and ensure external docs/tests don’t rely solely on schema to infer XOR.

Run this script to locate any lingering references to pre-unified tool names in docs/tests and double-check descriptions for accuracy:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Scan for old variant names to update references (examples, docs, tests)
rg -nH --no-ignore -e '(_ws|_proj)\b' -e '\blist_schemes_ws\b' -e '\bget_device_app_path_ws\b' -e '\bget_mac(?:os)?_app_path_proj\b' -e '\blaunch_.*_(sim|device)_id\b' -e '\blaunch_.*_(sim|device)_name\b' -e '\binstall_.*_(sim|device)_id\b' -e '\binstall_.*_(sim|device)_name\b' || true

# Specifically check this tool’s mentions
rg -nH --no-ignore -e '\bget_device_app_path_ws\b' -e '\bget_device_app_path_proj\b' || true

Length of output: 10600


Exported schema omits the XOR constraint — update metadata or docs; legacy _ws/_proj references remain

Short: The handler validates using getDeviceAppPathSchema (preprocess + XOR) but the exported metadata is just schema: baseSchemaObject.shape, which does not encode the XOR requirement and can mislead UIs/docs. Either export richer metadata that reflects the XOR or ensure external tooling/docs/tests don’t rely solely on schema.

Files to address

  • Primary (change metadata or docs/tests):
    • src/mcp/tools/device/get_device_app_path.ts — replace or augment schema: baseSchemaObject.shape with metadata that encodes the XOR for projectPath vs workspacePath, or clearly document that the runtime schema enforces XOR.
  • Confirmed no direct leftover tool variants for this tool:
    • No occurrences of get_device_app_path_ws or get_device_app_path_proj were found.
  • Legacy _ws/_proj references found elsewhere (examples to update to canonical unified names):
    • src/mcp/tools/simulator/list_sims.ts
    • src/mcp/tools/simulator/tests/list_sims.test.ts
    • src/mcp/tools/simulator/launch_app_sim_name.ts
    • src/mcp/tools/device/list_devices.ts
    • src/mcp/tools/device/tests/list_devices.test.ts
    • src/mcp/tools/project-scaffolding/scaffold_ios_project.ts
    • src/mcp/resources/tests/simulators.test.ts
    • docs/TOOLS.md
    • docs/PHASE1-TASKS.md
    • docs/MANUAL_TESTING.md
    • docs/PLUGIN_DEVELOPMENT.md
    • docs/ARCHITECTURE.md
    • docs/TESTING.md

Recommendation (pick one)

  • Export a metadata object for the tool that mirrors getDeviceAppPathSchema (including XOR) so generated UIs/docs reflect runtime validation; or
  • Keep the current lightweight schema export but make the XOR requirement explicit in the exported description and update any doc-generation tooling/tests to consult the runtime schema instead of metadata.

Comment on lines 179 to 189
export default {
name: 'get_mac_app_path_proj',
name: 'get_macos_app_path',
description:
"Gets the app bundle path for a macOS application using a project file. IMPORTANT: Requires projectPath and scheme. Example: get_mac_app_path_proj({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme' })",
schema: getMacAppPathProjSchema.shape, // MCP SDK compatibility
handler: createTypedTool(
getMacAppPathProjSchema,
get_mac_app_path_projLogic,
"Gets the app bundle path for a macOS application using either a project or workspace. Provide exactly one of projectPath or workspacePath. Example: get_macos_app_path({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme' })",
schema: baseSchemaObject.shape, // MCP SDK compatibility
handler: createTypedTool<GetMacosAppPathParams>(
getMacosAppPathSchema as z.ZodType<GetMacosAppPathParams>,
get_macos_app_pathLogic,
getDefaultCommandExecutor,
),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Exported schema shape vs handler schema (XOR not encoded in metadata)

As with the device tool, schema: baseSchemaObject.shape omits preprocess/XOR in metadata. Functionally fine (handler validates with the refined schema), but UIs/docs relying on schema won’t reflect XOR.

Ensure external docs/tests communicate the XOR requirement; consider richer schema metadata if the MCP SDK supports it.

Run this to catch lingering pre-unified names and mismatched descriptions/tests:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Pre-unified macOS tool names
rg -nH --no-ignore -e '\bget_mac_app_path_proj\b' -e '\bget_mac_app_path_ws\b' || true

# General project/workspace and id/name variants across repo
rg -nH --no-ignore -e '(_ws|_proj)\b' -e '\blist_schemes_ws\b' -e '\blist_schemes_proj\b' -e '\bget_device_app_path_ws\b' -e '\blaunch_.*_(sim|device)_id\b' -e '\blaunch_.*_(sim|device)_name\b' || true

Length of output: 10623


Exported schema omits XOR metadata — docs contain pre‑unified names; fixes required

The exported schema uses baseSchemaObject.shape (so UIs/docs won’t see the projectPath vs workspacePath XOR), while the handler still validates the refined schema. A repository search also shows lingering pre‑unified _ws/_proj names in docs/tests that should be cleaned or documented.

Files/locations to address

  • src/mcp/tools/macos/get_macos_app_path.ts (lines ~179–189): exports schema: baseSchemaObject.shape — loses XOR metadata.
  • docs/ARCHITECTURE.md:300 — references get_mac_app_path_ws (pre‑unified name).
  • Representative other hits (sweep required): src/mcp/tools/simulator/list_sims.ts (lines ~120,122), src/mcp/tools/device/list_devices.ts (lines ~392–396), docs/PHASE1-TASKS.md, docs/PLUGIN_DEVELOPMENT.md, docs/MANUAL_TESTING.md, and related tests shown in the search output.

Recommended fixes

  • Prefer exporting the refined schema (or attach richer metadata) so the projectPath/workspacePath XOR is encoded in the exported schema if the MCP SDK supports it — e.g., expose the same schema used by the handler.
  • If the SDK cannot support richer schema metadata, update external docs/CLI help and tests to explicitly require “exactly one of projectPath or workspacePath.”
  • Sweep docs/tests to remove or update pre‑unified _ws/_proj tool names (or add compatibility notes/aliases) so examples and docs match the unified tool interface.
🤖 Prompt for AI Agents
In src/mcp/tools/macos/get_macos_app_path.ts around lines 179–189, the exported
schema uses baseSchemaObject.shape which strips the XOR metadata for projectPath
vs workspacePath; replace the exported schema with the refined Zod schema used
by the handler (the same schema passed into createTypedTool — e.g., export
getMacosAppPathSchema or attach that richer schema/metadata) so the XOR
constraint is preserved for UIs/docs; if the MCP SDK cannot consume richer Zod
metadata, add explicit metadata or a wrapper that documents "exactly one of
projectPath or workspacePath" and then sweep and update docs/tests to remove
pre‑unified names (change get_mac_app_path_ws -> get_macos_app_path and update
examples/tests in docs/ARCHITECTURE.md, PHASE1-TASKS.md, PLUGIN_DEVELOPMENT.md,
MANUAL_TESTING.md and related test files) or add compatibility aliases where
necessary.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
src/mcp/tools/macos/build_run_macos.ts (1)

114-124: Make app path extraction resilient when multiple targets are printed

xcodebuild -showBuildSettings often prints settings for multiple targets/configurations. The first match might not belong to the app target. Prefer selecting the last BUILT_PRODUCTS_DIR and pick a FULL_PRODUCT_NAME that ends with .app.

-    const buildSettingsOutput = result.output;
-    const builtProductsDirMatch = buildSettingsOutput.match(/^\s*BUILT_PRODUCTS_DIR\s*=\s*(.+)$/m);
-    const fullProductNameMatch = buildSettingsOutput.match(/^\s*FULL_PRODUCT_NAME\s*=\s*(.+)$/m);
-
-    if (!builtProductsDirMatch || !fullProductNameMatch) {
+    const buildSettingsOutput = result.output;
+    const builtProductsDirMatches = [
+      ...buildSettingsOutput.matchAll(/^\s*BUILT_PRODUCTS_DIR\s*=\s*(.+)$/mg),
+    ];
+    const fullProductNameMatches = [
+      ...buildSettingsOutput.matchAll(/^\s*FULL_PRODUCT_NAME\s*=\s*(.+)$/mg),
+    ];
+    const builtProductsDir = builtProductsDirMatches.at(-1)?.[1].trim();
+    // Prefer an .app product; fall back to the last occurrence if none matches
+    const fullProductName =
+      fullProductNameMatches.map((m) => m[1].trim()).find((n) => n.endsWith('.app')) ??
+      fullProductNameMatches.at(-1)?.[1].trim();
+
+    if (!builtProductsDir || !fullProductName) {
       return { success: false, error: 'Could not extract app path from build settings' };
     }
 
-    const appPath = `${builtProductsDirMatch[1].trim()}/${fullProductNameMatch[1].trim()}`;
+    const appPath = `${builtProductsDir}/${fullProductName}`;

If you want to be even more precise, consider parsing per-target “Build settings for action … target …” blocks and selecting the one with WRAPPER_EXTENSION = app.

♻️ Duplicate comments (1)
src/mcp/tools/macos/build_run_macos.ts (1)

211-214: Expose XOR to MCP consumers (shape omits refine) — choose Option A or B

You export baseSchemaObject.shape for MCP compatibility, which drops the XOR refinements. This can mislead clients and defer errors to runtime handler validation.

Option A — Preferable if MCP accepts Zod schemas:

  • Export the refined schema to encode XOR to consumers.
-  schema: baseSchemaObject.shape, // MCP SDK compatibility
+  schema: buildRunMacOSSchema, // includes XOR refinements

Option B — If MCP requires a plain shape:

  • Keep shape, but add explicit machine-readable metadata alongside a stronger description. For example, add a metadata field your MCP layer can read:
    metadata: { constraints: { xor: [['projectPath', 'workspacePath']] } }
  • If adding metadata isn’t supported, at minimum, keep the current explicit description (good) and surface XOR in any generated docs.

To verify SDK capability and catch any remaining pre-unified names across the repo, run:

#!/bin/bash
set -euo pipefail

echo "Can MCP accept full Zod schemas? Grep for prior usage patterns:"
rg -n --hidden --glob '!*node_modules*' --glob '!*.lock' -e 'schema:\s*buildRunMacOSSchema' -e 'schema:\s*\w+Schema' src || true

echo
echo "Search for pre-unified tool names still lingering:"
rg -n --hidden --glob '!*node_modules*' --glob '!*.lock' \
  -e 'list_schemes_ws' \
  -e 'list_schemes_proj' \
  -e 'build_run_mac_proj' \
  -e 'build_run_macos_ws' \
  -e 'get_macos_app_path_proj' \
  -e 'get_macos_app_path_ws' \
  -e 'run_ios_simulator_by_name' \
  -e 'run_ios_simulator_by_id' \
  -e 'boot_simulator_by_name' \
  -e 'boot_simulator_by_id' \
  || echo "No pre-unified names found."
🧹 Nitpick comments (3)
src/mcp/tools/macos/build_run_macos.ts (3)

17-36: Strengthen schema: enforce non-empty scheme, add safe defaults, and validate path extensions

Current validation allows empty scheme and defers defaults to call sites. Making these constraints explicit in the schema improves UX and removes duplication elsewhere. Also consider validating file extensions to catch common input errors early.

Apply this diff to the base schema:

-const baseSchemaObject = z.object({
-  projectPath: z.string().optional().describe('Path to the .xcodeproj file'),
-  workspacePath: z.string().optional().describe('Path to the .xcworkspace file'),
-  scheme: z.string().describe('The scheme to use'),
-  configuration: z.string().optional().describe('Build configuration (Debug, Release, etc.)'),
+const baseSchemaObject = z.object({
+  projectPath: z
+    .string()
+    .optional()
+    .refine((p) => p === undefined || p.endsWith('.xcodeproj'), {
+      message: 'projectPath must point to a .xcodeproj file',
+    })
+    .describe('Path to the .xcodeproj file'),
+  workspacePath: z
+    .string()
+    .optional()
+    .refine((p) => p === undefined || p.endsWith('.xcworkspace'), {
+      message: 'workspacePath must point to a .xcworkspace file',
+    })
+    .describe('Path to the .xcworkspace file'),
+  scheme: z.string().min(1, 'scheme cannot be empty').describe('The scheme to use'),
+  configuration: z.string().default('Debug').describe('Build configuration (Debug, Release, etc.)'),
   derivedDataPath: z
     .string()
     .optional()
     .describe('Path where build products and other derived data will go'),
   arch: z
     .enum(['arm64', 'x86_64'])
     .optional()
     .describe('Architecture to build for (arm64 or x86_64). For macOS only.'),
   extraArgs: z.array(z.string()).optional().describe('Additional xcodebuild arguments'),
-  preferXcodebuild: z
-    .boolean()
-    .optional()
-    .describe('If true, prefers xcodebuild over the experimental incremental build system'),
+  preferXcodebuild: z
+    .boolean()
+    .default(false)
+    .describe('If true, prefers xcodebuild over the experimental incremental build system'),
 });

59-69: Remove duplicated defaults; rely on schema defaults

Configuration and preferXcodebuild defaults are set both in the schema and again in logic/wrapper. Centralize defaults in the schema to reduce drift.

   return executeXcodeBuildCommand(
     {
-      ...params,
-      configuration: params.configuration ?? 'Debug',
+      ...params,
     },
     {
       platform: XcodePlatform.macOS,
       arch: params.arch,
       logPrefix: 'macOS Build',
     },
-    params.preferXcodebuild ?? false,
+    params.preferXcodebuild,
     'build',
     executor,
   );
-      buildRunMacOSLogic(
-        {
-          ...params,
-          configuration: params.configuration ?? 'Debug',
-          preferXcodebuild: params.preferXcodebuild ?? false,
-        },
-        executor,
-      ),
+      buildRunMacOSLogic(params, executor),

Also applies to: 215-222


170-170: Minor: consider logging stdout/stderr on launch failure for diagnostics

If executor provides separate stdout/stderr, appending them to the failure response can speed up troubleshooting when launch fails.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 21c6628 and dcbf963.

📒 Files selected for processing (1)
  • src/mcp/tools/macos/build_run_macos.ts (8 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/mcp/tools/macos/build_run_macos.ts (2)
src/utils/schema-helpers.ts (1)
  • nullifyEmptyStrings (14-24)
src/utils/typed-tool-factory.ts (1)
  • createTypedTool (29-60)
🔇 Additional comments (3)
src/mcp/tools/macos/build_run_macos.ts (3)

15-15: Good reuse: shared nullifyEmptyStrings helper

Importing the shared nullifyEmptyStrings removes duplication and aligns with DRY. Nice.


38-46: LGTM: XOR (one-of-one) validation is enforced

The two-step refine accurately encodes “exactly one of projectPath or workspacePath.” This is correct at runtime.


2-6: Docs: unified-name usage verified — no action required

Search found updated examples and test hints using the unified names (build_run_macos, build_macos, get_macos_app_path); no remaining outdated references detected.

Key locations checked:

  • docs/TOOLS.md, docs/PHASE1-TASKS.md
  • src/utils/build-utils.ts
  • src/mcp/tools/macos/{build_run_macos.ts, build_macos.ts, get_macos_app_path.ts}
  • src/mcp/tools/macos/tests/{build_run_macos.test.ts, build_macos.test.ts, get_macos_app_path.test.ts}
  • src/mcp/tools/project-scaffolding/scaffold_macos_project.ts and its tests

This commit implements comprehensive tool hint optimization and naming consistency improvements:

## Major Tool Hint Optimizations (~75% context reduction)
- **build-utils.ts**: Reduced verbose Next Steps from ~15 lines to 4 concise lines
- **Removed redundant explanations**: Eliminated mutual exclusivity descriptions already covered in schemas
- **Parameter consistency**: Dynamic parameter detection respects user's original choices (simulatorId vs simulatorName, projectPath vs workspacePath)
- **Context efficiency**: Focused hints on actionable next steps without repeating validation rules

## Tool Naming Consistency & Unification
- **Simulator tools**: Renamed to shorter, consistent patterns:
  - `build_simulator` → `build_sim`
  - `build_run_simulator` → `build_run_sim`
  - `test_simulator` → `test_sim`
  - `get_simulator_app_path` → `get_sim_app_path`
  - `reset_simulator_location` → `reset_sim_location`
  - `set_simulator_location` → `set_sim_location`
- **macOS tools**: Unified naming conventions:
  - `get_macos_app_path` → `get_mac_app_path`
- **Removed duplicate tools**: Eliminated redundant name-based variants (`launch_app_sim_name`, `stop_app_sim_name`)

## Parameter Consistency Fixes
- **launch_app_sim**: Fixed to use dynamic parameter names in hints matching user input
- **list_devices**: Verified parameter-neutral hints without static preferences
- **build-utils**: Implemented dynamic simulator parameter detection

## Test Updates & Validation
- Updated 8+ test files to match new concise hint patterns
- Added parameter consistency test for launch_app_sim
- All 1046 tests passing
- Build verification successful

## Tool Documentation Artifacts
- Added comprehensive tool naming verification documentation
- Maintains compatibility with existing MCP protocol interfaces

This optimization significantly reduces context consumption while maintaining full functionality and improving user experience through consistent, focused tool hints.
@claude
Copy link
Contributor

claude bot commented Aug 14, 2025

Claude finished @cameroncooke's task —— View job


I'll analyze this and get back to you.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🔭 Outside diff range comments (4)
src/mcp/tools/macos/__tests__/build_run_macos.test.ts (1)

469-476: Mismatch with implementation: error label should read “macOS build and run”, not “macOS Build build”.

buildRunMacOSLogic wraps the entire flow and on thrown errors produces:
"Error during macOS build and run: "

Update the expectation accordingly.

Apply this diff:

-      expect(result).toEqual({
-        content: [
-          { type: 'text', text: 'Error during macOS Build build: spawn xcodebuild ENOENT' },
-        ],
-        isError: true,
-      });
+      expect(result).toEqual({
+        content: [
+          { type: 'text', text: 'Error during macOS build and run: spawn xcodebuild ENOENT' },
+        ],
+        isError: true,
+      });
src/mcp/tools/simulator-management/set_sim_location.ts (1)

1-126: Docs & help-texts still reference pre-unified tool names — please fix.

Short: I ran a repo-wide search for legacy tool names and found leftover references in documentation and some CLI/help descriptions. The code tools themselves appear renamed, but the docs and a few help strings need updating.

Files to update (examples from search):

  • docs/TOOLS.md (lines ~33–44) — mentions build_run_simulator, build_simulator, get_simulator_app_path, get_macos_app_path, reset_simulator_location, set_simulator_location, launch_app_sim_name, stop_app_sim_name, test_simulator.
  • docs/PHASE1-TASKS.md — task list still references several old names (build_simulator*, build_run_simulator*, get_macos_app_path, get_simulator_app_path*, test_simulator*, etc.).
  • TOOL_NAMING_VERIFICATION_2025-08-14_22-13.md(.bak) — verification artifact contains the old names and the mapping to new names (useful as a mapping reference).
  • src (help/describe strings) — update if the referenced helper tool was renamed:
    • src/mcp/tools/logging/start_sim_log_cap.ts (line ~17)
    • src/mcp/tools/simulator/boot_sim.ts (line ~10)
    • src/mcp/tools/simulator/launch_app_sim.ts (line ~13)
    • src/mcp/tools/simulator/install_app_sim.ts (line ~16)
    • src/mcp/tools/simulator/stop_app_sim.ts (line ~12)
    • src/mcp/tools/simulator/launch_app_logs_sim.ts (line ~24)
    • src/mcp/tools/simulator-management/reset_sim_location.ts (line ~11)
    • src/mcp/tools/simulator-management/set_sim_location.ts (line ~10)
    • src/mcp/tools/simulator-management/sim_statusbar.ts (line ~10)
    • src/mcp/tools/simulator-management/set_sim_appearance.ts (line ~10)

Actionable next steps:

  • Replace old names in docs with the unified names (e.g., build_sim, build_run_sim, test_sim, get_sim_app_path, get_mac_app_path, reset_sim_location, set_sim_location) and remove references to deleted tools (launch_app_sim_name, stop_app_sim_name). See TOOL_NAMING_VERIFICATION_2025-08-14_22-13.md for mapping.
  • Update or confirm user-facing .describe(...) strings if the referenced helper tool (e.g., list_simulators) was renamed.
eslint.config.js (2)

30-33: 'never' patterns unintentionally ignore variables containing “never”

argsIgnorePattern/varsIgnorePattern are regexes. Using 'never' will ignore any variable whose name contains “never” (e.g., neverUsed), which is likely accidental. Use a regex that matches nothing.

-      '@typescript-eslint/no-unused-vars': ['error', { 
-        argsIgnorePattern: 'never',
-        varsIgnorePattern: 'never' 
-      }],
+      '@typescript-eslint/no-unused-vars': ['error', {
+        argsIgnorePattern: '^$',
+        varsIgnorePattern: '^$'
+      }],

97-116: Unify formatting and environment in tests

  • Apply Prettier in tests to keep formatting enforcement consistent.
  • Add ecmaVersion/sourceType for parity with other blocks.
  • Add Node globals if tests rely on Node (common in CLI/tooling repos).
   {
     files: ['**/*.test.ts'],
     languageOptions: {
-      parser: tseslint.parser,
+      parser: tseslint.parser,
+      ecmaVersion: 2020,
+      sourceType: 'module',
+      globals: { ...globals.node },
       parserOptions: {
         project: './tsconfig.test.json',
       },
     },
+    plugins: {
+      'prettier': prettierPlugin,
+    },
     rules: {
+      'prettier/prettier': 'error',
       '@typescript-eslint/no-explicit-any': 'off',
       '@typescript-eslint/no-unused-vars': 'off',
       '@typescript-eslint/explicit-function-return-type': 'off',
       'prefer-const': 'off',

Note: The added globals import is already suggested above.

♻️ Duplicate comments (4)
src/mcp/tools/project-scaffolding/__tests__/scaffold_ios_project.test.ts (2)

434-436: NextSteps updated to unified sim tooling — keep naming/param consistency across the suite

Same verification as above applies here for the customized names path. Ensure build_sim/build_run_sim and simulatorName align with the unified tool API and docs.


469-471: NextSteps updated for customizeNames=false — verify against unified API and docs

Same checks as Lines 391–393: confirm canonical tool names and parameter naming are consistent and that no legacy tool names persist in code or documentation.

src/mcp/tools/device/__tests__/build_device.test.ts (1)

138-188: Workspace command-generation test added as requested — LGTM

This directly addresses the earlier review request to assert -workspace usage and expected args. Thanks for adding this; it increases confidence in the unified path.

src/mcp/tools/project-discovery/show_build_settings.ts (1)

86-90: Past typo resolved: references the unified list_schemes tool

The previous “list_schems_ws” typo flagged earlier is corrected here to the unified list_schemes reference in the Next Steps.

🧹 Nitpick comments (41)
src/mcp/tools/project-discovery/get_mac_bundle_id.ts (2)

97-99: Escape appPath in the example to avoid malformed output if the path contains quotes

Minor robustness: render the appPath using JSON.stringify so sample command stays valid even when the path contains quotes/spaces.

Apply this diff:

-          text: `Next Steps:
-- Launch: launch_mac_app({ appPath: "${appPath}" })
-- Build again: build_macos({ scheme: "SCHEME_NAME" })`,
+          text: `Next Steps:
+ - Launch: launch_mac_app({ appPath: ${JSON.stringify(appPath)} })
+ - Build again: build_macos({ scheme: "SCHEME_NAME" })`,

125-127: Nit: description mentions mcp0_get_macos_bundle_id (macos) while the tool is get_mac_bundle_id (mac)

For consistency, prefer “…prefixed as mcp0_get_mac_bundle_id.”

Apply this diff:

-    "Extracts the bundle identifier from a macOS app bundle (.app). IMPORTANT: You MUST provide the appPath parameter. Example: get_mac_bundle_id({ appPath: '/path/to/your/app.app' }) Note: In some environments, this tool may be prefixed as mcp0_get_macos_bundle_id.",
+    "Extracts the bundle identifier from a macOS app bundle (.app). IMPORTANT: You MUST provide the appPath parameter. Example: get_mac_bundle_id({ appPath: '/path/to/your/app.app' }) Note: In some environments, this tool may be prefixed as mcp0_get_mac_bundle_id.",

If you want, I can run a repo-wide scan and patch any remaining “macos” vs “mac” inconsistencies in tool names/descriptions.

src/mcp/tools/device/__tests__/index.test.ts (1)

1-3: Update test naming to drop “device-project” terminology

The file header and describe title still reference the pre-unification variant.

- * Tests for device-project workflow metadata
+ * Tests for device workflow metadata
-describe('device-project workflow metadata', () => {
+describe('device workflow metadata', () => {

Also applies to: 7-7

src/mcp/tools/device/list_devices.ts (4)

2-6: Header still says “Device Workspace Plugin”

Doc header should reflect the unified device tooling.

- * Device Workspace Plugin: List Devices
+ * Device Tool: List Devices

336-342: Remove unreachable “Paired but Not Connected” section

State is never set to “Paired (not connected)”, so this group stays empty. Either reintroduce that state or remove the block. Simplest: delete the group.

-      const pairedDevices = uniqueDevices.filter((d) => d.state === 'Paired (not connected)');
-      if (pairedDevices.length > 0) {
-        responseText += '🔗 Paired but Not Connected:\n';
-        for (const device of pairedDevices) {
-          responseText += `\n📱 ${device.name}\n`;
-          responseText += `   UDID: ${device.identifier}\n`;
-          responseText += `   Model: ${device.model ?? 'Unknown'}\n`;
-          responseText += `   Platform: ${device.platform} ${device.osVersion ?? ''}\n`;
-        }
-        responseText += '\n';
-      }

Also applies to: 364-373


321-324: Deduplicate in O(n) using a Map

The current filter+findIndex approach is O(n^2). Use a Map for linear-time deduplication.

-    const uniqueDevices = devices.filter(
-      (device, index, self) => index === self.findIndex((d) => d.identifier === device.identifier),
-    );
+    const uniqueDevices = Array.from(new Map(devices.map((d) => [d.identifier, d])).values());

43-43: Add a type to the devices collection for stronger safety

Avoids any[]. Keeps downstream formatting code type-safe.

-    const devices = [];
+    const devices: Array<{
+      name: string;
+      identifier: string;
+      platform: string;
+      model?: string;
+      osVersion?: string;
+      state: string;
+      connectionType?: string;
+      trustState?: string;
+      developerModeStatus?: string;
+      productType?: string;
+      cpuArchitecture?: string;
+    }> = [];
src/mcp/tools/device/__tests__/list_devices.test.ts (1)

3-7: Modernize test comments to remove “device-workspace” references

Comments still reference pre-unified modules and re-export paths.

- * Tests for list_devices plugin (device-shared)
- * This tests the re-exported plugin from device-workspace
+ * Tests for list_devices plugin (device)
+ * Validates the exported tool metadata and command generation
@@
- * Note: This is a re-export test. Comprehensive handler tests are in device-workspace/list_devices.test.ts
+ * Note: Handler logic is covered in dedicated unit tests for the device toolset

If there are other test headers referencing “device-workspace”, I can sweep and update them consistently.

src/mcp/tools/macos/__tests__/build_macos.test.ts (2)

74-82: Nit: user-facing text reads “macOS Build build …” (duplicate “build”)

Messages like “✅ macOS Build build succeeded …” and “Error during macOS Build build …” are awkward. Consider changing either the logPrefix or the message template to avoid the duplication (e.g., “macOS build succeeded …” or “macOS Build succeeded …”). If you adopt the change in the implementation, update these expectations accordingly.

Apply this test diff if you decide to remove the duplication in the implementation:

-            text: '✅ macOS Build build succeeded for scheme MyScheme.',
+            text: '✅ macOS build succeeded for scheme MyScheme.',
@@
-            text: '❌ macOS Build build failed for scheme MyScheme.',
+            text: '❌ macOS build failed for scheme MyScheme.',
@@
-            text: '✅ macOS Build build succeeded for scheme MyScheme.',
+            text: '✅ macOS build succeeded for scheme MyScheme.',
@@
-            text: 'Error during macOS Build build: Network error',
+            text: 'Error during macOS build: Network error',
@@
-            text: 'Error during macOS Build build: Spawn error',
+            text: 'Error during macOS build: Spawn error',

Also applies to: 106-111, 168-171, 194-196


381-412: Add a workspace “all parameters” command-generation case (parity with project flow)

You have a minimal workspace case. For parity, add a test that exercises workspace + configuration + arch + derivedDataPath + extraArgs to ensure both code paths stay in sync.

Apply this diff to extend the workspace coverage:

     it('should generate correct xcodebuild workspace command with minimal parameters', async () => {
@@
       expect(capturedCommand).toEqual([
@@
       ]);
     });
+
+    it('should generate correct xcodebuild workspace command with all parameters', async () => {
+      let capturedCommand: string[] = [];
+      const mockExecutor = createMockExecutor({ success: true, output: 'BUILD SUCCEEDED' });
+
+      const spyExecutor = async (command: string[]) => {
+        capturedCommand = command;
+        return mockExecutor(command);
+      };
+
+      await buildMacOSLogic(
+        {
+          workspacePath: '/path/to/workspace.xcworkspace',
+          scheme: 'MyScheme',
+          configuration: 'Release',
+          arch: 'arm64',
+          derivedDataPath: '/custom/derived',
+          extraArgs: ['--verbose'],
+          preferXcodebuild: true,
+        },
+        spyExecutor,
+      );
+
+      expect(capturedCommand).toEqual([
+        'xcodebuild',
+        '-workspace',
+        '/path/to/workspace.xcworkspace',
+        '-scheme',
+        'MyScheme',
+        '-configuration',
+        'Release',
+        '-skipMacroValidation',
+        '-destination',
+        'platform=macOS,arch=arm64',
+        '-derivedDataPath',
+        '/custom/derived',
+        '--verbose',
+        'build',
+      ]);
+    });
src/mcp/tools/device/__tests__/build_device.test.ts (4)

18-20: Consider expanding the description example to include workspace usage

The description is accurate for the unified surface. Optionally, include a workspace example to make both flows explicit.

If you choose to update the plugin description accordingly, mirror that here:

  • Example: build_device({ workspacePath: '/path/to/MyProject.xcworkspace', scheme: 'MyScheme' })

28-51: Schema coverage is solid; consider validating file extensions for project/workspace paths

You validate presence and types. Consider enforcing valid Xcode file extensions at the schema level for stronger guarantees (e.g., projectPath must end with .xcodeproj, workspacePath must end with .xcworkspace). This would catch misconfigurations earlier.

Outside this test file, you could implement something akin to:

// In build_device.ts schema (illustrative)
projectPath: z
  .string()
  .endsWith('.xcodeproj', { message: 'projectPath must point to a .xcodeproj' })
  .optional(),
workspacePath: z
  .string()
  .endsWith('.xcworkspace', { message: 'workspacePath must point to a .xcworkspace' })
  .optional(),

If you add these constraints, I can update tests to cover invalid extensions as well.


322-355: Optional params (Release, derivedDataPath, extraArgs) are asserted — add workspace parity

You’ve covered optional params for the -project path. Consider a symmetric test for the -workspace path to fully exercise both branches.

Apply the following to add a workspace variant right after this test:

+    it('should include optional parameters in command (workspace)', async () => {
+      const commandCalls: Array<{
+        args: string[];
+        logPrefix: string;
+        silent: boolean;
+        timeout: number | undefined;
+      }> = [];
+
+      const stubExecutor = async (
+        args: string[],
+        logPrefix: string,
+        silent: boolean,
+        timeout?: number,
+      ) => {
+        commandCalls.push({ args, logPrefix, silent, timeout });
+        return {
+          success: true,
+          output: 'Build succeeded',
+          error: undefined,
+          process: { pid: 12345 },
+        };
+      };
+
+      await buildDeviceLogic(
+        {
+          workspacePath: '/path/to/MyProject.xcworkspace',
+          scheme: 'MyScheme',
+          configuration: 'Release',
+          derivedDataPath: '/tmp/derived-data',
+          extraArgs: ['--verbose'],
+        },
+        stubExecutor,
+      );
+
+      expect(commandCalls).toHaveLength(1);
+      expect(commandCalls[0]).toEqual({
+        args: [
+          'xcodebuild',
+          '-workspace',
+          '/path/to/MyProject.xcworkspace',
+          '-scheme',
+          'MyScheme',
+          '-configuration',
+          'Release',
+          '-skipMacroValidation',
+          '-destination',
+          'generic/platform=iOS',
+          '-derivedDataPath',
+          '/tmp/derived-data',
+          '--verbose',
+          'build',
+        ],
+        logPrefix: 'iOS Device Build',
+        silent: true,
+        timeout: undefined,
+      });
+    });

138-159: DRY the capturing stub executor across tests

The stub executor used to capture args/log settings is repeated in three places. Extract a tiny helper within this file (or a shared test util) to reduce duplication and improve readability.

For example:

function createCapturingExecutor() {
  const calls: Array<{ args: string[]; logPrefix: string; silent: boolean; timeout: number | undefined; }> = [];
  const executor = async (args: string[], logPrefix: string, silent: boolean, timeout?: number) => {
    calls.push({ args, logPrefix, silent, timeout });
    return { success: true, output: 'Build succeeded', error: undefined, process: { pid: 12345 } };
  };
  return { calls, executor };
}

// Usage:
// const { calls, executor } = createCapturingExecutor();
// await buildDeviceLogic({ ... }, executor);
// expect(calls[0]).toEqual({ ... });

Also applies to: 198-212, 307-320

src/mcp/tools/project-discovery/show_build_settings.ts (1)

68-75: Unify the success header to always include the scheme

Currently, the header omits the scheme when using a workspace. For consistency, include the scheme in both cases.

Apply this diff:

-    const content: Array<{ type: 'text'; text: string }> = [
-      {
-        type: 'text',
-        text: hasProjectPath
-          ? `✅ Build settings for scheme ${params.scheme}:`
-          : '✅ Build settings retrieved successfully',
-      },
+    const content: Array<{ type: 'text'; text: string }> = [
+      {
+        type: 'text',
+        text: `✅ Build settings for scheme ${params.scheme}:`,
+      },
src/mcp/tools/project-discovery/list_schemes.ts (3)

65-74: Guard against undefined output before regex match

CommandExecutor results generally include output, but to be defensive (and avoid a potential TypeError on match), compute against result.output ?? ''.

Apply this diff:

-    // Extract schemes from the output
-    const schemesMatch = result.output.match(/Schemes:([\s\S]*?)(?=\n\n|$)/);
+    // Extract schemes from the output
+    const output = result.output ?? '';
+    const schemesMatch = output.match(/Schemes:([\s\S]*?)(?=\n\n|$)/);

80-81: Remove outdated comment about future unification

These references are already unified in this PR. The comment is now stale.

Apply this diff:

-      // Note: After Phase 2, these will be unified tool names too

111-121: Enhance description with a workspace example to reflect unification

Minor docs polish: add a workspacePath example to match the tool’s unified nature.

Apply this diff:

-  description:
-    "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' })",
+  description:
+    "Lists available schemes for either a project or a workspace. Provide exactly one of projectPath or workspacePath. Examples: list_schemes({ projectPath: '/path/to/MyProject.xcodeproj' }) and list_schemes({ workspacePath: '/path/to/MyProject.xcworkspace' })",
src/mcp/tools/project-discovery/__tests__/list_schemes.test.ts (1)

269-335: Workspace flow test coverage is solid; consider adding a negative case

You have positive workspace flow and command-generation tests. Consider a negative workspace case with an output containing no “Schemes:” section to mirror the project negative test, ensuring consistent behavior across both inputs.

src/mcp/tools/macos/__tests__/build_run_macos.test.ts (5)

116-175: Success path builds: verified command line for project flow; consider asserting the launch step.

The first two executor calls are asserted (build + showBuildSettings). Since buildRunMacOSLogic launches the app via open, also assert the 3rd call to ensure we verify the final action.

Apply this diff to add the assertion after the build settings check:

       // Verify build settings command was called
       expect(executorCalls[1]).toEqual({
@@
       });
+
+      // Verify app launch command was called
+      expect(executorCalls[2]).toEqual({
+        command: ['open', '/path/to/build/MyApp.app'],
+        description: 'Launch macOS App',
+        logOutput: true,
+        timeout: undefined,
+      });

193-209: Overly brittle full-structure equality and “Next Steps” messaging.

  • Asserting the entire ToolResponse structure makes tests fragile to benign text tweaks. Prefer partial/contains assertions for messages to reduce churn.
  • The presence of “Next Steps” in a combined build+run tool can be confusing (they look like follow-up actions, yet the tool already launches the app). If intentional (coming from the build step), this is fine—but consider dropping or tailoring those messages for the unified flow.

No action required if this is by design; flagging for awareness.


212-306: Success path builds (workspace flow): assert the launch step as well.

Same rationale as the project flow—add an assertion for the third executor call (open).

Apply this diff after the build settings assertion:

       // Verify build settings command was called
       expect(executorCalls[1]).toEqual({
@@
       });
+
+      // Verify app launch command was called
+      expect(executorCalls[2]).toEqual({
+        command: ['open', '/path/to/build/MyApp.app'],
+        description: 'Launch macOS App',
+        logOutput: true,
+        timeout: undefined,
+      });

479-537: Test name vs. setup mismatch: you pass configuration despite the title saying “when not provided”.

To actually test the defaulting behavior, omit configuration from args.

Apply this diff:

-      const args = {
-        projectPath: '/path/to/project.xcodeproj',
-        scheme: 'MyApp',
-        configuration: 'Debug',
-        preferXcodebuild: false,
-      };
+      const args = {
+        projectPath: '/path/to/project.xcodeproj',
+        scheme: 'MyApp',
+        preferXcodebuild: false,
+      };

(Keep the expectation the same if the default is 'Debug'.)


116-210: Optional: strengthen coverage for arch, derivedDataPath, extraArgs, and preferXcodebuild.

Right now command assertions don’t exercise these flags. Consider adding a case that supplies:

  • arch: 'arm64'
  • derivedDataPath: '/tmp/DerivedData'
  • extraArgs: ['-UseNewBuildSystem=YES']
  • preferXcodebuild: true (if this changes invocation behavior)

Then assert the exact build and showBuildSettings commands for both project and workspace flows.

Also applies to: 212-306

TOOL_NAMING_VERIFICATION_2025-08-14_22-13.md (2)

43-45: Fix duplicate “Detailed Test Results” heading (MD024).

Remove the first duplicate heading to satisfy markdownlint and improve readability.

Apply this diff:

-## Detailed Test Results
-[Updated as tests are completed]
-

1-209: Move timestamped verification report under docs/ or reports/ to avoid repo root churn.

Placing time-stamped verification artifacts at the repo root increases noise over time. Consider relocating to docs/reports/ (or similar) and/or adding a stable, rolling report file.

src/mcp/tools/simulator-management/set_sim_location.ts (2)

8-12: Update schema description to reference unified tool names.

The description mentions “list_simulators”; per this PR, it should reference the unified tool name (likely list_sims).

Apply this diff:

-  simulatorUuid: z
-    .string()
-    .describe('UUID of the simulator to use (obtained from list_simulators)'),
+  simulatorUuid: z
+    .string()
+    .describe('UUID of the simulator to use (obtained from list_sims)'),

18-28: Optional: prefer Zod range constraints over custom extraValidation.

Encoding latitude/longitude ranges in the Zod schema would centralize validation and produce consistent errors via createTypedTool. This is optional since tests assert current custom messages.

If you choose to migrate, here’s a minimal schema tweak (will require updating tests to expect Zod error formatting):

-const setSimulatorLocationSchema = z.object({
-  simulatorUuid: z
-    .string()
-    .describe('UUID of the simulator to use (obtained from list_sims)'),
-  latitude: z.number().describe('The latitude for the custom location.'),
-  longitude: z.number().describe('The longitude for the custom location.'),
-});
+const setSimulatorLocationSchema = z.object({
+  simulatorUuid: z
+    .string()
+    .describe('UUID of the simulator to use (obtained from list_sims)'),
+  latitude: z.number().min(-90).max(90).describe('The latitude for the custom location.'),
+  longitude: z.number().min(-180).max(180).describe('The longitude for the custom location.'),
+});

If you proceed, run tests to capture and update any message expectations that rely on the current custom validation.

Also applies to: 71-98, 104-114

src/mcp/tools/simulator-management/reset_sim_location.ts (3)

9-12: Update schema description to reference the unified tool name.

Swap “list_simulators” for the unified “list_sims” in the param description.

Apply this diff:

-  simulatorUuid: z
-    .string()
-    .describe('UUID of the simulator to use (obtained from list_simulators)'),
+  simulatorUuid: z
+    .string()
+    .describe('UUID of the simulator to use (obtained from list_sims)'),

3-5: Minor: consolidate imports from utils into a single statement.

Not a functional issue, but merging these two imports improves consistency (matches set_sim_location.ts).

Apply this diff:

-import { log } from '../../../utils/index.ts';
-import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.ts';
+import { log, CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.ts';

17-27: Align helper signature with set_sim_location.ts for consistency.

Make extraValidation return ToolResponse | null and (optionally) default the executor to getDefaultCommandExecutor() for parity with the other module.

Apply this diff:

-async function executeSimctlCommandAndRespond(
+async function executeSimctlCommandAndRespond(
   params: ResetSimulatorLocationParams,
   simctlSubCommand: string[],
   operationDescriptionForXcodeCommand: string,
   successMessage: string,
   failureMessagePrefix: string,
   operationLogContext: string,
-  executor: CommandExecutor,
-  extraValidation?: () => ToolResponse | undefined,
+  executor: CommandExecutor = getDefaultCommandExecutor(),
+  extraValidation?: () => ToolResponse | null,
 ): Promise<ToolResponse> {
   if (extraValidation) {
     const validationResult = extraValidation();
     if (validationResult) {
       return validationResult;
     }
   }
src/mcp/tools/simulator-management/__tests__/set_sim_location.test.ts (1)

12-76: Optional: add an integration-style test for the default export’s handler.

You already validate the schema shape and call the logic directly. Add a small test invoking setSimLocation.handler({...}) to exercise the createTypedTool path (runtime parse and execution).

Example:

it('handler should parse and call logic successfully', async () => {
  const mockExecutor = createMockExecutor({ success: true, output: 'ok' });
  const res = await setSimLocation.handler({
    simulatorUuid: 'uuid',
    latitude: 1,
    longitude: 2,
  });
  expect(res).toEqual({
    content: [{ type: 'text', text: 'Successfully set simulator uuid location to 1,2' }],
  });
});
eslint.config.js (3)

13-13: Future-proof src glob to include TSX (if it appears later)

If any TSX sneaks into src (even non-React), this will miss it. Safe to include now.

-    files: ['src/**/*.ts'],
+    files: ['src/**/*.{ts,tsx}'],

75-94: Optional: avoid parsing .js with the TS parser for performance/accuracy

Using @typescript-eslint/parser for .js works but is slower and can be surprising. Consider splitting scripts into two blocks: one for TS with the TS parser and one for JS using the default parser (or rely on @eslint/js recommended). This keeps JS on the fastest path and TS on the type-aware path you intend.


12-20: Consider leveraging typescript-eslint’s type-checked presets

Since you’re opting into type info in src and opting out in scripts, the following could reduce maintenance:

  • In src block: start from tseslint.configs.recommendedTypeChecked (or strictTypeChecked if desired) and then override deltas.
  • In scripts block: start from tseslint.configs.disableTypeChecked to ensure no type-aware rules sneak in.

This preserves your intent but with less manual rule curation.

Also applies to: 61-95

src/mcp/tools/macos/__tests__/get_mac_app_path.test.ts (4)

11-11: Prefer “tool” over “plugin” for naming consistency.

Project-wide terminology uses “tool”; update the describe label to avoid confusion.

-describe('get_mac_app_path plugin', () => {
+describe('get_mac_app_path tool', () => {

17-21: Literal description equality is brittle; consider partial match.

Exact string equality on descriptions tends to break on minor copy edits. Using a contains or regex match keeps intent while reducing churn.

-      expect(getMacAppPath.description).toBe(
-        "Gets the app bundle path for a macOS application using either a project or workspace. Provide exactly one of projectPath or workspacePath. Example: get_mac_app_path({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme' })",
-      );
+      expect(getMacAppPath.description).toContain('Gets the app bundle path for a macOS application');
+      expect(getMacAppPath.description).toContain('projectPath or workspacePath');

154-195: Workspace all-params command: consider adding derivedDataPath/extraArgs coverage.

This case validates arch and configuration. To fully mirror the project “all parameters” test, add a workspace variant that includes derivedDataPath and extraArgs to ensure parity.

   it('should generate correct command with workspace all parameters', async () => {
@@
-  const args = {
-    workspacePath: '/path/to/MyProject.xcworkspace',
-    scheme: 'MyScheme',
-    configuration: 'Release',
-    arch: 'arm64',
-  };
+  const args = {
+    workspacePath: '/path/to/MyProject.xcworkspace',
+    scheme: 'MyScheme',
+    configuration: 'Release',
+    arch: 'arm64',
+    derivedDataPath: '/path/to/derived',
+    extraArgs: ['--verbose'],
+  };
@@
-    [
+    [
       'xcodebuild',
       '-showBuildSettings',
       '-workspace',
       '/path/to/MyProject.xcworkspace',
       '-scheme',
       'MyScheme',
       '-configuration',
       'Release',
       '-destination',
       'platform=macOS,arch=arm64',
+      '-derivedDataPath',
+      '/path/to/derived',
+      '--verbose',
     ],

329-343: Literal equality on validation error is fragile.

This asserts the entire error block. If createTypedTool formats change (e.g., prefix wording), this test will fail. Consider narrowing to essential substrings.

-      expect(result).toEqual({
-        content: [
-          {
-            type: 'text',
-            text: 'Error: Parameter validation failed\nDetails: Invalid parameters:\nscheme: Required',
-          },
-        ],
-        isError: true,
-      });
+      expect(result.isError).toBe(true);
+      expect(result.content[0].type).toBe('text');
+      expect(result.content[0].text).toContain('Parameter validation failed');
+      expect(result.content[0].text).toContain('scheme: Required');
src/mcp/tools/macos/get_mac_app_path.ts (2)

27-41: Prefer a union-based XOR for stronger type inference (nice-to-have).

Refining a single object works, but modeling XOR as a union yields better inferred types (e.g., in logic, TS knows only one of projectPath/workspacePath exists). You can still wrap with preprocess for empty-string nullification.

-const baseSchemaObject = z.object({
-  projectPath: z.string().optional().describe('Path to the .xcodeproj file'),
-  workspacePath: z.string().optional().describe('Path to the .xcworkspace file'),
-  ...baseOptions,
-});
-
-const baseSchema = z.preprocess(nullifyEmptyStrings, baseSchemaObject);
-
-const getMacosAppPathSchema = baseSchema
-  .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
-    message: 'Either projectPath or workspacePath is required.',
-  })
-  .refine((val) => !(val.projectPath !== undefined && val.workspacePath !== undefined), {
-    message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
-  });
+const projectVariant = z.object({
+  projectPath: z.string().describe('Path to the .xcodeproj file'),
+  workspacePath: z.undefined().optional(),
+  ...baseOptions,
+});
+
+const workspaceVariant = z.object({
+  projectPath: z.undefined().optional(),
+  workspacePath: z.string().describe('Path to the .xcworkspace file'),
+  ...baseOptions,
+});
+
+// Preprocess to nullify empty strings before union validation
+const getMacosAppPathSchema = z.preprocess(
+  nullifyEmptyStrings,
+  z.union([projectVariant, workspaceVariant]),
+);

Note: If you keep schema exposure for MCP as schema: baseSchemaObject.shape, you can continue doing so for UI/docs while using the union for runtime validation.


64-66: Remove unused XcodePlatform entries to reduce noise.

Only macOS is used. Consider inlining or scoping to avoid dead constants.

-const XcodePlatform = {
-  iOS: 'iOS',
-  watchOS: 'watchOS',
-  tvOS: 'tvOS',
-  visionOS: 'visionOS',
-  iOSSimulator: 'iOS Simulator',
-  watchOSSimulator: 'watchOS Simulator',
-  tvOSSimulator: 'tvOS Simulator',
-  visionOSSimulator: 'visionOS Simulator',
-  macOS: 'macOS',
-};
+const MACOS_PLATFORM = 'macOS';
@@
-  log('info', `Getting app path for scheme ${params.scheme} on platform ${XcodePlatform.macOS}`);
+  log('info', `Getting app path for scheme ${params.scheme} on platform ${MACOS_PLATFORM}`);

Also applies to: 179-183

Comment on lines +61 to +71
{
// JavaScript and TypeScript files outside the main project (scripts/, etc.)
files: ['**/*.{js,ts}'],
ignores: ['src/**/*', '**/*.test.ts'],
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
parser: tseslint.parser,
// No project reference for scripts - use standalone parsing
},
plugins: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add Node globals for scripts to avoid false positives

Scripts often use Node globals (process, __dirname, etc.). Without globals, core rules like no-undef (on JS) or tooling may misbehave. Add Node globals here.

     languageOptions: {
       ecmaVersion: 2020,
       sourceType: 'module',
       parser: tseslint.parser,
+      globals: { ...globals.node },
       // No project reference for scripts - use standalone parsing
     },

Add the import at the top of the file:

 import eslint from '@eslint/js';
 import tseslint from 'typescript-eslint';
 import prettierPlugin from 'eslint-plugin-prettier';
+import globals from 'globals';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
// JavaScript and TypeScript files outside the main project (scripts/, etc.)
files: ['**/*.{js,ts}'],
ignores: ['src/**/*', '**/*.test.ts'],
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
parser: tseslint.parser,
// No project reference for scripts - use standalone parsing
},
plugins: {
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettierPlugin from 'eslint-plugin-prettier';
import globals from 'globals';
{
// JavaScript and TypeScript files outside the main project (scripts/, etc.)
files: ['**/*.{js,ts}'],
ignores: ['src/**/*', '**/*.test.ts'],
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
parser: tseslint.parser,
globals: { ...globals.node },
// No project reference for scripts - use standalone parsing
},
plugins: {
🤖 Prompt for AI Agents
In eslint.config.js around lines 61 to 71, the configuration for scripts lacks
Node globals which causes false positives for builtins like process and
__dirname; add the Node environment/globals by importing the Node globals or
setting env: { node: true } (and/or globals: { process: 'readonly', __dirname:
'readonly', __filename: 'readonly' }) at the top of the file and inside this
files block so scripts are parsed with Node globals available; also add the
requested import statement at the top of the file as indicated in the review
comment.

Comment on lines +80 to +83
'@typescript-eslint/no-unused-vars': ['warn', {
argsIgnorePattern: 'never',
varsIgnorePattern: 'never'
}],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Same 'never' regex bug for scripts’ no-unused-vars

Mirror the fix from the src block to avoid accidentally ignoring names containing “never”.

-      '@typescript-eslint/no-unused-vars': ['warn', { 
-        argsIgnorePattern: 'never',
-        varsIgnorePattern: 'never' 
-      }],
+      '@typescript-eslint/no-unused-vars': ['warn', {
+        argsIgnorePattern: '^$',
+        varsIgnorePattern: '^$'
+      }],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'@typescript-eslint/no-unused-vars': ['warn', {
argsIgnorePattern: 'never',
varsIgnorePattern: 'never'
}],
'@typescript-eslint/no-unused-vars': ['warn', {
argsIgnorePattern: '^$',
varsIgnorePattern: '^$'
}],
🤖 Prompt for AI Agents
In eslint.config.js around lines 80 to 83, the
argsIgnorePattern/varsIgnorePattern are set to 'never' which matches any
identifier containing "never"; change both patterns to the stricter regex used
in the src block (e.g. '^never$') so only identifiers exactly named "never" are
ignored, mirroring the src fix.

Comment on lines 19 to 22
it('should have correct description', () => {
expect(buildMacProj.description).toBe(
'Builds a macOS app using xcodebuild from a project file.',
expect(buildMacOS.description).toBe(
"Builds a macOS app using xcodebuild from a project or workspace. Provide exactly one of projectPath or workspacePath. Example: build_macos({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify all docs/tests no longer reference pre-unified tool names

The tool description and “Next Steps” strings reflect the unified naming. Please ensure there are no lingering references to legacy variants (e.g., build_mac_proj/build_mac_ws, list_schemes_ws/list_schemes_proj, _by_id/_by_name) across code, tests, and docs.

Run this repo-wide check to find stale names and confirm the referenced follow-up tools exist:

Also applies to: 82-82, 144-145


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for pre-unified names…"
rg -n -S -e 'build_mac_(proj|ws)\b' -e 'list_schemes_(ws|proj)\b' -e '_by_id\b' -e '_by_name\b' -e '\b(list|get|boot|erase|launch)_(sim|simulator)_(id|name)\b' -e 'get_device_(id|name)\b' || true

echo
echo "Verifying unified macOS tool references in tests/docs point to real tool modules…"
# Look for references used in this test and ensure they exist in src
rg -n -S "get_mac_app_path|get_mac_bundle_id|launch_mac_app" | cat

echo
echo "If hits were found above for legacy names, please update those files to the unified names and adjust tests accordingly."

Length of output: 13467


Fix stale test strings: replace get_app_bundle_id → get_mac_bundle_id

Ran the repo-wide check you requested. No matches were found for the legacy patterns (build_mac_proj/build_mac_ws, list_schemes_*, _by_id/_by_name, simulator id/name patterns), but I found lingering pre-unified name(s) in tests that need updating:

  • src/mcp/tools/macos/tests/get_mac_app_path.test.ts
    • ~line 370 and ~line 401: "Next Steps" strings contain get_app_bundle_id({ appPath: ... }) — replace with get_mac_bundle_id({ appPath: ... }).

Docs that mention mcp0_* prefixes (notes about environment-specific prefixes) are informational and can remain as-is unless you want to remove those notes.

🤖 Prompt for AI Agents
In src/mcp/tools/macos/__tests__/build_macos.test.ts around lines 19 to 22 and
also update src/mcp/tools/macos/__tests__/get_mac_app_path.test.ts around lines
~370 and ~401, replace the stale test strings referencing get_app_bundle_id(...)
with the unified get_mac_bundle_id(...) call; search for any
"get_app_bundle_id({ appPath:" occurrences in these tests and update the
function name in the example/Next Steps strings (keeping the rest of the text
identical) so test messages reflect the new API name.

Comment on lines +68 to +77
it('should reject inputs with both projectPath and workspacePath', () => {
const invalidInput = {
projectPath: '/path/to/project.xcodeproj',
workspacePath: '/path/to/workspace.xcworkspace',
scheme: 'MyApp',
};
const schema = z.object(tool.schema);
expect(schema.safeParse(invalidInput).success).toBe(true); // Base schema passes, but runtime validation should fail
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Test titles contradict expectations; add runtime XOR tests or enforce XOR in Zod schema.

  • The titles say “should reject…”, but the assertions expect Zod to pass. This is confusing, even with the inline comment. Either:
    1. Keep base-schema permissive and rename these tests to reflect that runtime validation handles XOR; and add runtime tests that call the handler/logic and assert rejection. Or
    2. Enforce XOR in the Zod schema so these become hard validation failures and change the expectations to .toBe(false).

If you choose option 1, rename titles as below and add two handler/logic tests that assert an error.

If you choose option 2, adopt a refined/union schema (see code snippet after the diff) and update these assertions to expect .success === false.

Suggested minimal change (Option 1: rename tests to avoid confusion):

-    it('should reject inputs with both projectPath and workspacePath', () => {
+    it('should pass base schema for both projectPath and workspacePath (runtime should reject)', () => {
@@
-      expect(schema.safeParse(invalidInput).success).toBe(true); // Base schema passes, but runtime validation should fail
+      expect(schema.safeParse(invalidInput).success).toBe(true); // Base schema intentionally passes; runtime validation should reject
     });
@@
-    it('should reject inputs with neither projectPath nor workspacePath', () => {
+    it('should pass base schema for neither projectPath nor workspacePath (runtime should reject)', () => {
@@
-      expect(schema.safeParse(invalidInput).success).toBe(true); // Base schema passes, but runtime validation should fail
+      expect(schema.safeParse(invalidInput).success).toBe(true); // Base schema intentionally passes; runtime validation should reject
     });

Option 1 follow-up (add runtime XOR tests near here):

it('should reject at runtime when both projectPath and workspacePath are provided', async () => {
  const result = await tool.handler({
    projectPath: '/p.xcodeproj',
    workspacePath: '/w.xcworkspace',
    scheme: 'MyApp',
  }, createMockExecutor({ success: true, output: '', error: '' }));
  expect(result.isError).toBe(true);
  expect(result.content?.some(c => c.type === 'text' && c.text.includes('Provide exactly one'))).toBe(true);
});

it('should reject at runtime when neither projectPath nor workspacePath is provided', async () => {
  const result = await tool.handler({ scheme: 'MyApp' } as any, createMockExecutor({ success: true, output: '', error: '' }));
  expect(result.isError).toBe(true);
  expect(result.content?.some(c => c.type === 'text' && c.text.includes('Provide exactly one'))).toBe(true);
});

Optional (Option 2: enforce XOR at schema-level). In build_run_macos.ts, define mutually exclusive schemas:

const Base = {
  scheme: z.string(),
  configuration: z.string().optional(),
  derivedDataPath: z.string().optional(),
  arch: z.enum(['arm64', 'x86_64']).optional(),
  extraArgs: z.array(z.string()).optional(),
  preferXcodebuild: z.boolean().optional(),
};

const ProjectSchema = z.object({ ...Base, projectPath: z.string() });
const WorkspaceSchema = z.object({ ...Base, workspacePath: z.string() });

export const schema = z.union([ProjectSchema, WorkspaceSchema]); // tool.schema should be this Zod schema, not a raw shape

If you adopt Option 2, also update tests to use tool.schema.safeParse(input) directly (not z.object(tool.schema)).

Also applies to: 78-85


🏁 Script executed:

#!/bin/bash
set -euo pipefail

test_path="src/mcp/tools/macos/__tests__/build_run_macos.test.ts"
tool_path="src/mcp/tools/macos/build_run_macos.ts"

echo "=== Checking test file ==="
if [ -f "$test_path" ]; then
  sed -n '1,240p' "$test_path"
else
  echo "NOT FOUND: $test_path"
fi

echo
echo "=== Checking tool file ==="
if [ -f "$tool_path" ]; then
  sed -n '1,240p' "$tool_path"
else
  echo "NOT FOUND: $tool_path"
fi

echo
echo "=== Searching repo for occurrences of projectPath/workspacePath/schema/handler ==="
rg --hidden -n "projectPath|workspacePath|schema|handler" || true

Length of output: 244209


Fix tests: title/assertion mismatch in build_run_macos tests (handler enforces XOR but tests validate base shape).

Verified: src/mcp/tools/macos/build_run_macos.ts uses a refined Zod schema (buildRunMacOSSchema) with XOR checks in the handler but exports baseSchemaObject.shape as tool.schema. The tests at src/mcp/tools/macos/tests/build_run_macos.test.ts assert z.object(tool.schema).safeParse(...) for the both/none cases (they pass the base shape) while the test titles say "should reject" — this is confusing and needs fixing.

Files to update

  • src/mcp/tools/macos/tests/build_run_macos.test.ts (lines ~68-85) — fix test titles/assertions and add runtime handler tests.
  • (Optional) src/mcp/tools/macos/build_run_macos.ts — if you prefer schema-level enforcement, update exported schema to a union/refined shape.

Suggested fixes

Option 1 (recommended, minimal): keep exported base shape for MCP compatibility, rename the two misleading tests and add handler-level runtime tests that assert the handler rejects invalid XOR cases.

Rename diff (tests):

-    it('should reject inputs with both projectPath and workspacePath', () => {
+    it('should pass base schema for both projectPath and workspacePath (handler should reject at runtime)', () => {
@@
-    it('should reject inputs with neither projectPath nor workspacePath', () => {
+    it('should pass base schema for neither projectPath nor workspacePath (handler should reject at runtime)', () => {

Add runtime XOR tests (near the schema tests):

it('should reject at runtime when both projectPath and workspacePath are provided', async () => {
  const result = await tool.handler(
    {
      projectPath: '/p.xcodeproj',
      workspacePath: '/w.xcworkspace',
      scheme: 'MyApp',
    } as any,
    createMockExecutor({ success: true, output: '', error: '' }),
  );
  expect(result.isError).toBe(true);
  expect(result.content?.some(c => c.type === 'text' && (c.text as string).includes('Either projectPath or workspacePath is required') || (c.text as string).includes('mutually exclusive'))).toBe(true);
});

it('should reject at runtime when neither projectPath nor workspacePath is provided', async () => {
  const result = await tool.handler({ scheme: 'MyApp' } as any, createMockExecutor({ success: true, output: '', error: '' }));
  expect(result.isError).toBe(true);
  expect(result.content?.some(c => c.type === 'text' && (c.text as string).includes('Either projectPath or workspacePath is required'))).toBe(true);
});

Option 2 (alternative): enforce XOR at schema-level and update schema export so z.object(tool.schema).safeParse(...) returns false for invalid combos. Example (in build_run_macos.ts):

const Base = {
  scheme: z.string(),
  configuration: z.string().optional(),
  derivedDataPath: z.string().optional(),
  arch: z.enum(['arm64', 'x86_64']).optional(),
  extraArgs: z.array(z.string()).optional(),
  preferXcodebuild: z.boolean().optional(),
};

const ProjectSchema = z.object({ ...Base, projectPath: z.string() });
const WorkspaceSchema = z.object({ ...Base, workspacePath: z.string() });

export const schema = z.union([ProjectSchema, WorkspaceSchema]).shape; // or adjust to match MCP schema shape expectations

If you take Option 2, update the two tests to expect .success === false.

Summary: current repo pattern is to export the raw shape for MCP compatibility and enforce refinements in the handler — so the simplest, consistent fix is Option 1: rename tests and add two handler-level runtime XOR tests.

🤖 Prompt for AI Agents
In src/mcp/tools/macos/__tests__/build_run_macos.test.ts around lines 68-77, the
test titles/assertions are mismatched: the file currently validates the exported
base schema (z.object(tool.schema)) and therefore the existing assertions that
.safeParse(...).success is true are correct for the base shape, but the test
titles claim the input "should reject". Rename those two schema tests to reflect
that the base shape accepts both/none (e.g., "base schema allows both
projectPath and workspacePath" and "base schema allows neither"), and add two
new runtime handler tests that call tool.handler with the invalid XOR combos
(both set, and neither set) and assert result.isError === true and that
result.content includes a text error mentioning "Either projectPath or
workspacePath is required" or "mutually exclusive"; alternatively, if you prefer
schema-level enforcement, update build_run_macos.ts to export a union/refined
schema enforcing XOR and then change the existing tests to expect .success ===
false for invalid combos.

Comment on lines 179 to 189
export default {
name: 'get_mac_app_path_proj',
name: 'get_mac_app_path',
description:
"Gets the app bundle path for a macOS application using a project file. IMPORTANT: Requires projectPath and scheme. Example: get_mac_app_path_proj({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme' })",
schema: getMacAppPathProjSchema.shape, // MCP SDK compatibility
handler: createTypedTool(
getMacAppPathProjSchema,
get_mac_app_path_projLogic,
"Gets the app bundle path for a macOS application using either a project or workspace. Provide exactly one of projectPath or workspacePath. Example: get_mac_app_path({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme' })",
schema: baseSchemaObject.shape, // MCP SDK compatibility
handler: createTypedTool<GetMacosAppPathParams>(
getMacosAppPathSchema as z.ZodType<GetMacosAppPathParams>,
get_mac_app_pathLogic,
getDefaultCommandExecutor,
),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

End-to-end rename audit for unified tools.

Given the goal to unify tool variants, ensure no stale references (e.g., get_mac_app_path_ws, *_proj/_ws, simulator *_id/_name variants) remain in code/docs/tests.


🏁 Script executed:

#!/bin/bash
# Find pre-unified tool name patterns and common legacy suffixes
rg -n --fixed-strings \
  "get_mac_app_path_ws" \
  "get_mac_app_path_proj" \
  "list_schemes_ws" \
  "list_schemes_proj" \
  "launch_simulator_id" \
  "launch_simulator_name" \
  "boot_simulator_id" \
  "boot_simulator_name" \
  "shutdown_simulator_id" \
  "shutdown_simulator_name" \
  | sed 's/^/Stale reference: /'

Length of output: 798


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for explicit legacy tool names
echo "=== Explicit legacy names ==="
rg -n -F \
  -e "get_mac_app_path_ws" \
  -e "get_mac_app_path_proj" \
  -e "list_schemes_ws" \
  -e "list_schemes_proj" \
  -e "launch_simulator_id" \
  -e "launch_simulator_name" \
  -e "boot_simulator_id" \
  -e "boot_simulator_name" \
  -e "shutdown_simulator_id" \
  -e "shutdown_simulator_name" \
  | sed 's/^/Stale reference: /' || true

# Broader search for common legacy suffixes in likely locations
echo
echo "=== Suffix matches (_proj/_ws/_id/_name) in src/docs/tests ==="
rg -n --hidden -S -g 'src/**' -g 'docs/**' -g 'test/**' -g 'tests/**' -e '_proj\b' -e '_ws\b' -e '_id\b' -e '_name\b' \
  | sed 's/^/Suffix match: /' || true

Length of output: 30251


Action required — remove or alias stale legacy tool names in docs/tests

Brief: the repo still contains legacy *_proj/_ws/_id/_name tool references in documentation and testing material that must be updated or aliased for compatibility.

Files/locations to fix (from grep):

  • docs/ARCHITECTURE.md (e.g. line ~300: get_mac_app_path_ws)
  • docs/TOOLS.md (mentions like launch_app_sim_name, stop_app_sim_name, get_mac_bundle_id, etc.)
  • docs/PHASE1-TASKS.md (cleanup/task list referencing tool_proj/tool_ws and many legacy variants)
  • docs/TESTING.md (many example commands: list_schems_proj, build_sim_id_proj, etc.)
  • docs/MANUAL_TESTING.md (same kinds of example commands)
  • docs/PLUGIN_DEVELOPMENT.md (templates/examples referencing _ws/__proj variants)
  • Misc docs/comments in src/* that include legacy examples / test commands (see docs and testing files above)

Recommended fixes:

  • Update documentation and test examples to use unified tool names (e.g., get_mac_app_path) or add documented backward-compatible aliases/re-exports if workflows expect old names.
  • Remove/replace leftover TODOs and examples that reference legacy variants.
  • After changes, re-run the same repository-wide search to confirm no stale references remain.
🤖 Prompt for AI Agents
In src/mcp/tools/macos/get_mac_app_path.ts around lines 179 to 189,
documentation and tests across the repo still reference legacy tool name
variants (e.g., *_proj, *_ws, *_id, *_name such as get_mac_app_path_ws), which
must be removed or aliased; update the listed docs (docs/ARCHITECTURE.md,
docs/TOOLS.md, docs/PHASE1-TASKS.md, docs/TESTING.md, docs/MANUAL_TESTING.md,
docs/PLUGIN_DEVELOPMENT.md) and any comments/examples in src/* to use the
unified tool names (e.g., get_mac_app_path) OR add backward-compatible
aliases/re-exports for the legacy names with clear docs pointing to the
canonical name, replace/remove leftover TODOs referencing legacy variants, and
then run a repo-wide search (grep) to verify no stale references remain.

Comment on lines +49 to +56
const hasProjectPath = typeof params.projectPath === 'string';
const path = hasProjectPath ? params.projectPath : params.workspacePath;

if (hasProjectPath) {
command.push('-project', params.projectPath!);
} else {
command.push('-workspace', params.workspacePath!);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Expose projectOrWorkspace for consistent “Next Steps” and messages

You already compute hasProjectPath and path. Add a projectOrWorkspace discriminator to reuse in messages and “Next Steps” construction.

Apply this diff:

-    const hasProjectPath = typeof params.projectPath === 'string';
-    const path = hasProjectPath ? params.projectPath : params.workspacePath;
+    const hasProjectPath = typeof params.projectPath === 'string';
+    const projectOrWorkspace = hasProjectPath ? 'project' : 'workspace';
+    const path = hasProjectPath ? params.projectPath : params.workspacePath;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasProjectPath = typeof params.projectPath === 'string';
const path = hasProjectPath ? params.projectPath : params.workspacePath;
if (hasProjectPath) {
command.push('-project', params.projectPath!);
} else {
command.push('-workspace', params.workspacePath!);
}
const hasProjectPath = typeof params.projectPath === 'string';
const projectOrWorkspace = hasProjectPath ? 'project' : 'workspace';
const path = hasProjectPath ? params.projectPath : params.workspacePath;
if (hasProjectPath) {
command.push('-project', params.projectPath!);
} else {
command.push('-workspace', params.workspacePath!);
}
🤖 Prompt for AI Agents
In src/mcp/tools/project-discovery/show_build_settings.ts around lines 49 to 56,
introduce a projectOrWorkspace discriminator so the code can consistently refer
to whether we used a project or workspace in messages and Next Steps. After
computing hasProjectPath and path, add const projectOrWorkspace = hasProjectPath
? 'project' : 'workspace' and then use that variable wherever you build
user-facing strings or Next Steps (instead of re-checking hasProjectPath or
interpolating path directly); leave the existing command.push logic as-is but
replace any message/Next Step construction to reference projectOrWorkspace for
clear, consistent wording.

Comment on lines +82 to +91
// Add next steps for workspace (similar to original workspace implementation)
if (!hasProjectPath && path) {
content.push({
type: 'text',
text: `Next Steps:
- Build the workspace: build_macos({ workspacePath: "${path}", scheme: "${params.scheme}" })
- For iOS: build_sim({ workspacePath: "${path}", scheme: "${params.scheme}", simulatorName: "iPhone 16" })
- List schemes: list_schemes({ workspacePath: "${path}" })`,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Show “Next Steps” for both project and workspace (unification goal)

“Next Steps” are only shown for workspaces. To align with the unification objective and with list_schemes’ behavior, generate them for projects too, using a single template keyed off projectOrWorkspace.

Apply this diff:

-    // Add next steps for workspace (similar to original workspace implementation)
-    if (!hasProjectPath && path) {
-      content.push({
-        type: 'text',
-        text: `Next Steps:
-- Build the workspace: build_macos({ workspacePath: "${path}", scheme: "${params.scheme}" })
-- For iOS: build_sim({ workspacePath: "${path}", scheme: "${params.scheme}", simulatorName: "iPhone 16" })
-- List schemes: list_schemes({ workspacePath: "${path}" })`,
-      });
-    }
+    // Add next steps for both project and workspace cases
+    if (path) {
+      content.push({
+        type: 'text',
+        text: `Next Steps:
+- Build the ${projectOrWorkspace}: build_macos({ ${projectOrWorkspace}Path: "${path}", scheme: "${params.scheme}" })
+- For iOS: build_sim({ ${projectOrWorkspace}Path: "${path}", scheme: "${params.scheme}", simulatorName: "iPhone 16" })
+- List schemes: list_schemes({ ${projectOrWorkspace}Path: "${path}" })`,
+      });
+    }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/mcp/tools/project-discovery/show_build_settings.ts around lines 82 to 91,
the current code only pushes "Next Steps" when hasProjectPath is false
(workspace only); change it to generate the same Next Steps for both projects
and workspaces by using a single template keyed off projectOrWorkspace (the
variable used elsewhere to represent either projectPath or workspacePath).
Replace the hasProjectPath conditional with a check for path (existing path
variable) and build the content.text using projectOrWorkspace as the key name
and params.scheme for scheme (e.g., build_macos({ projectOrWorkspace: "${path}",
scheme: "${params.scheme}" }) etc.), ensuring the three commands (build_macos,
build_sim, list_schemes) are emitted the same way for projects and workspaces.

Comment on lines +104 to +114
export default {
name: 'show_build_settings',
description:
"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' })",
schema: baseSchemaObject.shape,
handler: createTypedTool<ShowBuildSettingsParams>(
showBuildSettingsSchema as z.ZodType<ShowBuildSettingsParams>,
showBuildSettingsLogic,
getDefaultCommandExecutor,
),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Double-check repo for any lingering pre-unified tool names in docs/tests

Given the unification objective, verify there are no leftover references (e.g., list_schems_ws, list_schems_proj, show_build_set_proj, build_simulator_name/id).

Run:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Scanning for pre-unified discovery tool names..."
rg -n -A 1 -B 1 $'list_schems_ws|list_schems_proj|show_build_set_proj'

echo
echo "Scanning for legacy simulator tool variants..."
rg -n -A 1 -B 1 $'build_simulator_name|build_simulator_id|run_simulator_name|run_simulator_id'

echo
echo "Scanning for unified names to ensure consistent usage..."
rg -n -A 1 -B 1 $'list_schemes\\b|show_build_settings\\b|build_macos\\b|build_sim\\b'

Length of output: 30570


Fix docs/tests: replace lingering pre-unified tool names

Verification found leftover legacy names in documentation and a verification backup. Please update these to the unified names.

  • docs/TESTING.md — replace occurrences of list_schems_projlist_schemes (around lines ~1002, 1048, 1149, 1153; includes npx reloaderoo ... call-tool "list_schems_proj" usages).
  • docs/MANUAL_TESTING.md — replace list_schems_projlist_schemes (around lines ~510, 556, 657, 661).
  • docs/PHASE1-TASKS.md — replace show_build_setshow_build_settings (tool listing at top of file). Note: build_simulator_name / build_simulator_id appear here as task history (no code impact), but review if you want them normalized in the task doc.
  • TOOL_NAMING_VERIFICATION_2025-08-14_22-13.md.bak — contains old tool-name references (and duplicated entries); either update the file to use unified names or remove the stale backup.

Suggested quick grep to re-check before merging:
rg -n $'list_schems|show_build_set|build_simulator_name|build_simulator_id'

🤖 Prompt for AI Agents
In src/mcp/tools/project-discovery/show_build_settings.ts around lines 104 to
114 and across the repo update lingering legacy tool names to the unified names:
in docs/TESTING.md replace all occurrences of `list_schems_proj` with
`list_schemes` (notably near lines ~1002, 1048, 1149, 1153 including any npx
reloaderoo call-tool invocations), in docs/MANUAL_TESTING.md replace
`list_schems_proj` → `list_schemes` (around ~510, 556, 657, 661), in
docs/PHASE1-TASKS.md replace `show_build_set` → `show_build_settings` in the
tool listing, and either update or remove
TOOL_NAMING_VERIFICATION_2025-08-14_22-13.md.bak to remove stale legacy names;
after edits run the suggested grep (rg -n
$'list_schems|show_build_set|build_simulator_name|build_simulator_id') to verify
no leftover references remain.

Comment on lines +391 to 393
'Build for simulator: build_sim({ workspacePath: "/tmp/test-projects/MyProject.xcworkspace", scheme: "MyProject", simulatorName: "iPhone 16" })',
'Build and run on simulator: build_run_sim({ workspacePath: "/tmp/test-projects/MyProject.xcworkspace", scheme: "MyProject", simulatorName: "iPhone 16" })',
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Switched to unified function-style sim tools in nextSteps — verify canonical names and params

Good move replacing legacy CLI-style strings with build_sim/build_run_sim. Please double-check that:

  • build_sim and build_run_sim are the canonical, unified tool names exported by the tool registry.
  • The parameter name simulatorName matches the unified schema (if the PR standardized on a single simulator param that accepts either name or ID, update the tests accordingly).
  • Any references to pre-unified names (e.g., *_ws, *_proj, *_id, *_name) are fully removed from code and docs.

Run this script to verify there are no lingering references and that the unified tools exist:

If anything pops, I can draft the necessary code/doc/test updates.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Searching for legacy simulator tool names that should be removed..."
legacy_names=(
  'build_ios_sim_name_ws'
  'build_run_ios_sim_name_ws'
  'build_ios_sim_id_ws'
  'build_run_ios_sim_id_ws'
  'build_ios_sim_name_proj'
  'build_run_ios_sim_name_proj'
  'build_ios_sim_id_proj'
  'build_run_ios_sim_id_proj'
  'list_schemes_ws'
  'list_schemes_proj'
)
for n in "${legacy_names[@]}"; do
  if rg -n --no-heading -S "$n"; then
    echo "Found legacy reference: $n"
  fi
done

echo
echo "2) Confirming unified tool names are defined somewhere (by export name field or usage)..."
rg -n --no-heading -S $'name:\\s*[\'"]build_sim[\'"]|name:\\s*[\'"]build_run_sim[\'"]' -A 2 -B 2 || true
rg -n --no-heading -S $'\\bbuild_sim\\b|\\bbuild_run_sim\\b' -A 2 -B 2 || true

echo
echo "3) Auditing parameter naming for simulator unification..."
rg -n --no-heading -S $'simulatorName|simulatorId|\\bsimulator\\s*:' -A 3 -B 2 || true

echo
echo "4) Grep for any doc or description references to pre-unified tool names..."
fd -t f README* docs --exec rg -n --no-heading -S 'list_schemes_ws|_sim_name_|_sim_id_|_proj\\b|_ws\\b' {} || true

echo
echo "Done. Review any matches above; ideally there should be 0 hits for legacy names."

Length of output: 154834


Verified: build_sim / build_run_sim are canonical — update docs with legacy names

Short summary: I ran repository-wide checks. The unified tools are implemented and tests use the unified params (simulatorId / simulatorName). However there are leftover doc/test examples referencing pre‑unified tool names that should be updated.

Key findings (actionable):

  • Confirmed canonical tools and schema (no code fixes needed):

    • src/mcp/tools/simulator/build_sim.ts — exports name: 'build_sim'
    • src/mcp/tools/simulator/build_run_sim.ts — exports name: 'build_run_sim'
    • Types and helpers use simulatorId / simulatorName (e.g. src/types/common.ts, src/utils/simulator-utils.ts)
    • Tests and scaffold nextSteps already use simulatorName (e.g. src/mcp/tools/project-scaffolding/tests/scaffold_ios_project.test.ts lines ~391-393) — these are correct.
  • Files that need doc/test updates (replace pre‑unified names with canonical names and clarify XOR param guidance):

    • docs/TOOLS.md — contains build_run_simulator, build_simulator, get_simulator_app_path, launch_app_sim_name, stop_app_sim_name, test_simulator; update to build_run_sim, build_sim, get_sim_app_path, launch_app_sim, stop_app_sim, test_sim and correct parameter descriptions (use simulatorId OR simulatorName).
    • docs/MANUAL_TESTING.md — contains example calling build_sim_id_proj; update examples to use build_sim and the unified param names.
    • docs/TESTING.md — same outdated example (build_sim_id_proj) — update.
    • Any other docs/snippets that reference id, name, *_proj/_ws variants (quick grep shows these in the docs set) — replace with unified forms and XOR guidance.

If you want, I can draft a patch to update those docs/examples and adjust wording to reflect "Provide either simulatorId OR simulatorName (mutually exclusive)."

🤖 Prompt for AI Agents
In src/mcp/tools/project-scaffolding/__tests__/scaffold_ios_project.test.ts
around lines 391-393, update any remaining legacy tool names and parameter forms
in docs/tests to the canonical unified names and params: replace
build_run_simulator → build_run_sim, build_simulator → build_sim,
get_simulator_app_path → get_sim_app_path, launch_app_sim_name → launch_app_sim,
stop_app_sim_name → stop_app_sim, test_simulator → test_sim, and change any
examples using simulatorId/simulatorName variants or *_proj/_ws variants to use
simulatorId OR simulatorName (mutually exclusive) and canonical workspace/scheme
naming; apply the same replacements in docs/TOOLS.md, docs/MANUAL_TESTING.md,
docs/TESTING.md and any other doc snippets found by grep to ensure
examples/tests consistently use build_sim / build_run_sim and the XOR parameter
guidance.

@cameroncooke
Copy link
Collaborator Author

✅ RESOLVED - Already Fixed

This comment filtering issue has already been addressed in the current codebase. The logic now properly handles both multi-line comments and inline comments:

  • Line 150: Multi-line comments are removed with content.replace(/\/\*[\s\S]*?\*\//g, '')
  • Lines 152-159: Inline comments are handled by preserving code before //

The current implementation is robust and correctly handles the scenarios mentioned. No further action needed.

@cameroncooke
Copy link
Collaborator Author

❌ INCORRECT - TypeScript ESM Requirement

This suggestion is technically incorrect for this project's configuration. The project uses:

  • "type": "module" in package.json
  • "module": "Node16" in tsconfig.json

In TypeScript ESM projects, import statements MUST use .js" extensions even when importing from .ts" files. This is because:

  1. TypeScript compiles .ts" files to .js" files
  2. Node.js ESM resolver requires the runtime extension (`.js")
  3. Changing to .ts" would cause ERR_MODULE_NOT_FOUND" runtime errors

The current `.js" imports are correct and required for this project configuration.

@cameroncooke
Copy link
Collaborator Author

❌ INCORRECT - Already Extracted

This is a false positive. The nullifyEmptyStrings" function has already been properly extracted to src/utils/schema-helpers.ts" and is imported in all the mentioned files:

  • `src/utils/schema-helpers.ts" - Contains the single implementation
  • test_device.ts" - Imports it: import { nullifyEmptyStrings } from '../../../utils/schema-helpers.js';`
  • get_device_app_path.ts" - Imports it: import { nullifyEmptyStrings } from '../../../utils/schema-helpers.js';`
  • get_macos_app_path.ts" - Imports it: import { nullifyEmptyStrings } from '../../../utils/schema-helpers.js';`

The code correctly follows DRY principles. No duplication exists.

@cameroncooke
Copy link
Collaborator Author

❌ CANNOT VERIFY - Typo Not Found

I cannot locate the alleged typo list_schems_ws" in the current codebase. A search through src/mcp/tools/project-discovery/show_build_settings.ts" does not reveal any instance of this misspelling.

If this typo existed, it appears to have been already corrected. The current code uses the correct spelling `list_schemes" throughout.

@cameroncooke
Copy link
Collaborator Author

✅ RESOLVED - Already Fixed

This parameter restriction issue has been resolved. The current code in get_mac_app_path.ts" correctly applies derivedDataPath" and `extraArgs" for both workspace and project builds:

  • Lines 85-87: derivedDataPath applied after both project/workspace setup
  • Lines 95-98: extraArgs applied after both project/workspace setup

The xcodebuild" command now properly receives these flags regardless of whether -project" or `-workspace" is used. The fix ensures both build types have access to these important configuration options.

@cameroncooke
Copy link
Collaborator Author

✅ RESOLVED - Tool Names Updated

The tool references have been updated to use unified naming conventions. The current code correctly uses:

  • build_sim" instead of build_simulator_name"
  • `build_run_macos" instead of legacy CLI formats
  • Function-style invocations with proper parameter objects

All "Next Steps" text and tool references reflect the current unified tool names. The migration from legacy tool variants to the canonical unified tools is complete.

@cameroncooke
Copy link
Collaborator Author

✅ RESOLVED - Test Expectations Updated

Test expectations have been updated to reflect unified tool names and function-style invocations. The current tests properly expect:

  • build_run_macos" instead of build_run_mac_ws"
  • Function-style calls: `build_run_macos({ workspacePath: "...", scheme: "..." })"
  • Consistent naming across all scaffold and tool references

The test suite now validates the correct unified tool interfaces.

@cameroncooke
Copy link
Collaborator Author

✅ ACKNOWLEDGED - Design Decision

This is a valid observation about MCP protocol limitations. The XOR constraint (projectPath" XOR workspacePath") is enforced via `.refine()" at runtime but not visible in the static schema exposed to MCP clients.

This is an intentional design tradeoff:

  • ✅ Clean API (users don't specify "type" discriminators)
  • ✅ Runtime validation works correctly
  • ❌ MCP clients can't see the mutual exclusivity constraint

The tool description clearly states "Provide exactly one of projectPath or workspacePath" which helps LLMs understand the constraint. This approach balances MCP compatibility with API usability.

@cameroncooke
Copy link
Collaborator Author

📋 CodeRabbit Review Summary

After comprehensive analysis of all inline comments, here's the status:

Issues Already Resolved (3)

  • Comment filtering logic - Multi-line and inline comment handling improved
  • Parameter restrictions - derivedDataPath/extraArgs now work with both workspace and project builds
  • Tool references - All updated to unified naming conventions

Incorrect Suggestions (4)

  • Import extensions - .js" is required for TypeScript ESM, .ts" would break the build
  • nullifyEmptyStrings duplication - Function already properly extracted to shared utility
  • Typo claims - No such typos found in current codebase
  • Test expectations - Already updated to unified tool names

Acknowledged Design Decisions (1)

  • XOR constraint exposure - Intentional MCP compatibility tradeoff with clear documentation

Result: All legitimate issues have been proactively addressed. The codebase is in excellent shape with robust error handling, proper tool unification, and optimized hint patterns.

@cameroncooke cameroncooke merged commit 25d637b into main Aug 15, 2025
17 checks passed
@cameroncooke cameroncooke deleted the feat/unify-project-workspace-tools branch August 15, 2025 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants