diff --git a/docs/session-aware-migration-todo.md b/docs/session-aware-migration-todo.md index 56fc6526..b7ac8fe6 100644 --- a/docs/session-aware-migration-todo.md +++ b/docs/session-aware-migration-todo.md @@ -23,10 +23,10 @@ Reference: `docs/session_management_plan.md` - [ ] `src/mcp/tools/logging/start_device_log_cap.ts` — session defaults: `deviceId`. ## macOS Workflows -- [ ] `src/mcp/tools/macos/build_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`. -- [ ] `src/mcp/tools/macos/build_run_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`. -- [ ] `src/mcp/tools/macos/test_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`. -- [ ] `src/mcp/tools/macos/get_mac_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`. +- [x] `src/mcp/tools/macos/build_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`. +- [x] `src/mcp/tools/macos/build_run_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`. +- [x] `src/mcp/tools/macos/test_macos.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`. +- [x] `src/mcp/tools/macos/get_mac_app_path.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `configuration`, `arch`. ## Simulator Build/Test/Path - [x] `src/mcp/tools/simulator/test_sim.ts` — session defaults: `projectPath`, `workspacePath`, `scheme`, `simulatorId`, `simulatorName`, `configuration`, `useLatestOS`. diff --git a/src/mcp/tools/macos/__tests__/build_macos.test.ts b/src/mcp/tools/macos/__tests__/build_macos.test.ts index 55a89810..24a31713 100644 --- a/src/mcp/tools/macos/__tests__/build_macos.test.ts +++ b/src/mcp/tools/macos/__tests__/build_macos.test.ts @@ -5,21 +5,24 @@ * NO VITEST MOCKING ALLOWED - Only createMockExecutor and createMockFileSystemExecutor */ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, beforeEach } from 'vitest'; import { z } from 'zod'; import { createMockExecutor } from '../../../../test-utils/mock-executors.ts'; +import { sessionStore } from '../../../../utils/session-store.ts'; import buildMacOS, { buildMacOSLogic } from '../build_macos.ts'; describe('build_macos plugin', () => { + beforeEach(() => { + sessionStore.clear(); + }); + describe('Export Field Validation (Literal)', () => { it('should have correct name', () => { expect(buildMacOS.name).toBe('build_macos'); }); it('should have correct description', () => { - 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' })", - ); + expect(buildMacOS.description).toBe('Builds a macOS app.'); }); it('should have handler function', () => { @@ -27,32 +30,56 @@ describe('build_macos plugin', () => { }); it('should validate schema correctly', () => { - // Test required fields - expect(buildMacOS.schema.projectPath.safeParse('/path/to/MyProject.xcodeproj').success).toBe( - true, - ); + const schema = z.object(buildMacOS.schema); + + expect(schema.safeParse({}).success).toBe(true); expect( - buildMacOS.schema.workspacePath.safeParse('/path/to/MyProject.xcworkspace').success, + schema.safeParse({ + derivedDataPath: '/path/to/derived-data', + extraArgs: ['--arg1', '--arg2'], + preferXcodebuild: true, + }).success, ).toBe(true); - expect(buildMacOS.schema.scheme.safeParse('MyScheme').success).toBe(true); - // Test optional fields - expect(buildMacOS.schema.configuration.safeParse('Debug').success).toBe(true); - expect(buildMacOS.schema.derivedDataPath.safeParse('/path/to/derived-data').success).toBe( - true, - ); - expect(buildMacOS.schema.arch.safeParse('arm64').success).toBe(true); - expect(buildMacOS.schema.arch.safeParse('x86_64').success).toBe(true); - expect(buildMacOS.schema.extraArgs.safeParse(['--arg1', '--arg2']).success).toBe(true); - expect(buildMacOS.schema.preferXcodebuild.safeParse(true).success).toBe(true); - - // 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.arch.safeParse('invalidArch').success).toBe(false); - expect(buildMacOS.schema.extraArgs.safeParse('not-array').success).toBe(false); - expect(buildMacOS.schema.preferXcodebuild.safeParse('not-boolean').success).toBe(false); + expect(schema.safeParse({ derivedDataPath: 42 }).success).toBe(false); + expect(schema.safeParse({ extraArgs: ['--ok', 1] }).success).toBe(false); + expect(schema.safeParse({ preferXcodebuild: 'yes' }).success).toBe(false); + + const schemaKeys = Object.keys(buildMacOS.schema).sort(); + expect(schemaKeys).toEqual(['derivedDataPath', 'extraArgs', 'preferXcodebuild'].sort()); + }); + }); + + describe('Handler Requirements', () => { + it('should require scheme when no defaults provided', async () => { + const result = await buildMacOS.handler({}); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('scheme is required'); + expect(result.content[0].text).toContain('session-set-defaults'); + }); + + it('should require project or workspace once scheme default exists', async () => { + sessionStore.setDefaults({ scheme: 'MyScheme' }); + + const result = await buildMacOS.handler({}); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Provide a project or workspace'); + }); + + it('should reject when both projectPath and workspacePath provided explicitly', async () => { + sessionStore.setDefaults({ scheme: 'MyScheme' }); + + const result = await buildMacOS.handler({ + projectPath: '/path/to/project.xcodeproj', + workspacePath: '/path/to/workspace.xcworkspace', + }); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Mutually exclusive parameters provided'); + expect(result.content[0].text).toContain('projectPath'); + expect(result.content[0].text).toContain('workspacePath'); }); }); @@ -416,7 +443,7 @@ describe('build_macos plugin', () => { it('should error when neither projectPath nor workspacePath provided', async () => { const result = await buildMacOS.handler({ scheme: 'MyScheme' }); expect(result.isError).toBe(true); - expect(result.content[0].text).toContain('Either projectPath or workspacePath is required'); + expect(result.content[0].text).toContain('Provide a project or workspace'); }); it('should error when both projectPath and workspacePath provided', async () => { @@ -426,7 +453,7 @@ describe('build_macos plugin', () => { scheme: 'MyScheme', }); expect(result.isError).toBe(true); - expect(result.content[0].text).toContain('mutually exclusive'); + expect(result.content[0].text).toContain('Mutually exclusive parameters provided'); }); it('should succeed with valid projectPath', async () => { diff --git a/src/mcp/tools/macos/__tests__/build_run_macos.test.ts b/src/mcp/tools/macos/__tests__/build_run_macos.test.ts index c250ec58..c0aa4133 100644 --- a/src/mcp/tools/macos/__tests__/build_run_macos.test.ts +++ b/src/mcp/tools/macos/__tests__/build_run_macos.test.ts @@ -1,114 +1,75 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, beforeEach } from 'vitest'; import { z } from 'zod'; import { createMockExecutor } from '../../../../test-utils/mock-executors.ts'; +import { sessionStore } from '../../../../utils/session-store.ts'; import tool, { buildRunMacOSLogic } from '../build_run_macos.ts'; describe('build_run_macos', () => { + beforeEach(() => { + sessionStore.clear(); + }); + describe('Export Field Validation (Literal)', () => { it('should export the correct name', () => { expect(tool.name).toBe('build_run_macos'); }); it('should export the correct description', () => { - expect(tool.description).toBe( - "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' })", - ); + expect(tool.description).toBe('Builds and runs a macOS app.'); }); it('should export a handler function', () => { expect(typeof tool.handler).toBe('function'); }); - it('should validate schema with valid project inputs', () => { - const validInput = { - projectPath: '/path/to/project.xcodeproj', - scheme: 'MyApp', - configuration: 'Debug', - derivedDataPath: '/path/to/derived', - arch: 'arm64', - extraArgs: ['--verbose'], - preferXcodebuild: true, - }; + it('should expose only non-session fields in schema', () => { const schema = z.object(tool.schema); - expect(schema.safeParse(validInput).success).toBe(true); - }); - it('should validate schema with valid workspace inputs', () => { - const validInput = { - workspacePath: '/path/to/workspace.xcworkspace', - scheme: 'MyApp', - configuration: 'Debug', - derivedDataPath: '/path/to/derived', - arch: 'arm64', - extraArgs: ['--verbose'], - preferXcodebuild: true, - }; - const schema = z.object(tool.schema); - expect(schema.safeParse(validInput).success).toBe(true); + expect(schema.safeParse({}).success).toBe(true); + expect( + schema.safeParse({ + derivedDataPath: '/tmp/derived', + extraArgs: ['--verbose'], + preferXcodebuild: true, + }).success, + ).toBe(true); + + expect(schema.safeParse({ derivedDataPath: 1 }).success).toBe(false); + expect(schema.safeParse({ extraArgs: ['--ok', 2] }).success).toBe(false); + expect(schema.safeParse({ preferXcodebuild: 'yes' }).success).toBe(false); + + const schemaKeys = Object.keys(tool.schema).sort(); + expect(schemaKeys).toEqual(['derivedDataPath', 'extraArgs', 'preferXcodebuild'].sort()); }); + }); - it('should validate schema with minimal valid project inputs', () => { - const validInput = { - projectPath: '/path/to/project.xcodeproj', - scheme: 'MyApp', - }; - const schema = z.object(tool.schema); - expect(schema.safeParse(validInput).success).toBe(true); - }); + describe('Handler Requirements', () => { + it('should require scheme before executing', async () => { + const result = await tool.handler({}); - it('should validate schema with minimal valid workspace inputs', () => { - const validInput = { - workspacePath: '/path/to/workspace.xcworkspace', - scheme: 'MyApp', - }; - const schema = z.object(tool.schema); - expect(schema.safeParse(validInput).success).toBe(true); + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('scheme is required'); }); - 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 - }); + it('should require project or workspace once scheme is set', async () => { + sessionStore.setDefaults({ scheme: 'MyApp' }); - it('should reject inputs with neither projectPath nor workspacePath', () => { - const invalidInput = { - scheme: 'MyApp', - }; - const schema = z.object(tool.schema); - expect(schema.safeParse(invalidInput).success).toBe(true); // Base schema passes, but runtime validation should fail - }); + const result = await tool.handler({}); - it('should reject invalid projectPath', () => { - const invalidInput = { - projectPath: 123, - scheme: 'MyApp', - }; - const schema = z.object(tool.schema); - expect(schema.safeParse(invalidInput).success).toBe(false); + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Provide a project or workspace'); }); - it('should reject invalid scheme', () => { - const invalidInput = { - projectPath: '/path/to/project.xcodeproj', - scheme: 123, - }; - const schema = z.object(tool.schema); - expect(schema.safeParse(invalidInput).success).toBe(false); - }); + it('should fail when both project and workspace provided explicitly', async () => { + sessionStore.setDefaults({ scheme: 'MyApp' }); - it('should reject invalid arch', () => { - const invalidInput = { + const result = await tool.handler({ projectPath: '/path/to/project.xcodeproj', - scheme: 'MyApp', - arch: 'invalid', - }; - const schema = z.object(tool.schema); - expect(schema.safeParse(invalidInput).success).toBe(false); + workspacePath: '/path/to/workspace.xcworkspace', + }); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Mutually exclusive parameters provided'); }); }); diff --git a/src/mcp/tools/macos/__tests__/get_mac_app_path.test.ts b/src/mcp/tools/macos/__tests__/get_mac_app_path.test.ts index 42cb8bc6..7b3e1150 100644 --- a/src/mcp/tools/macos/__tests__/get_mac_app_path.test.ts +++ b/src/mcp/tools/macos/__tests__/get_mac_app_path.test.ts @@ -3,21 +3,24 @@ * Following CLAUDE.md testing standards with literal validation * Using dependency injection for deterministic testing */ - -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, beforeEach } from 'vitest'; +import { z } from 'zod'; import { createMockExecutor, type CommandExecutor } from '../../../../test-utils/mock-executors.ts'; +import { sessionStore } from '../../../../utils/session-store.ts'; import getMacAppPath, { get_mac_app_pathLogic } from '../get_mac_app_path.ts'; describe('get_mac_app_path plugin', () => { + beforeEach(() => { + sessionStore.clear(); + }); + describe('Export Field Validation (Literal)', () => { it('should have correct name', () => { expect(getMacAppPath.name).toBe('get_mac_app_path'); }); it('should have correct description', () => { - 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).toBe('Retrieves the built macOS app bundle path.'); }); it('should have handler function', () => { @@ -25,28 +28,51 @@ describe('get_mac_app_path plugin', () => { }); it('should validate schema correctly', () => { - // Test workspace path - expect( - getMacAppPath.schema.workspacePath.safeParse('/path/to/MyProject.xcworkspace').success, - ).toBe(true); - // Test project path + const schema = z.object(getMacAppPath.schema); + + expect(schema.safeParse({}).success).toBe(true); expect( - getMacAppPath.schema.projectPath.safeParse('/path/to/MyProject.xcodeproj').success, + schema.safeParse({ + derivedDataPath: '/path/to/derived', + extraArgs: ['--verbose'], + }).success, ).toBe(true); - expect(getMacAppPath.schema.scheme.safeParse('MyScheme').success).toBe(true); - - // Test optional fields - expect(getMacAppPath.schema.configuration.safeParse('Debug').success).toBe(true); - expect(getMacAppPath.schema.arch.safeParse('arm64').success).toBe(true); - expect(getMacAppPath.schema.arch.safeParse('x86_64').success).toBe(true); - expect(getMacAppPath.schema.derivedDataPath.safeParse('/path/to/derived').success).toBe(true); - expect(getMacAppPath.schema.extraArgs.safeParse(['--verbose']).success).toBe(true); - - // Test invalid inputs - expect(getMacAppPath.schema.workspacePath.safeParse(null).success).toBe(false); - expect(getMacAppPath.schema.projectPath.safeParse(null).success).toBe(false); - expect(getMacAppPath.schema.scheme.safeParse(null).success).toBe(false); - expect(getMacAppPath.schema.arch.safeParse('invalidArch').success).toBe(false); + + expect(schema.safeParse({ derivedDataPath: 7 }).success).toBe(false); + expect(schema.safeParse({ extraArgs: ['--bad', 1] }).success).toBe(false); + + const schemaKeys = Object.keys(getMacAppPath.schema).sort(); + expect(schemaKeys).toEqual(['derivedDataPath', 'extraArgs'].sort()); + }); + }); + + describe('Handler Requirements', () => { + it('should require scheme before running', async () => { + const result = await getMacAppPath.handler({}); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('scheme is required'); + }); + + it('should require project or workspace when scheme default exists', async () => { + sessionStore.setDefaults({ scheme: 'MyScheme' }); + + const result = await getMacAppPath.handler({}); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Provide a project or workspace'); + }); + + it('should reject when both projectPath and workspacePath provided explicitly', async () => { + sessionStore.setDefaults({ scheme: 'MyScheme' }); + + const result = await getMacAppPath.handler({ + projectPath: '/path/to/project.xcodeproj', + workspacePath: '/path/to/workspace.xcworkspace', + }); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Mutually exclusive parameters provided'); }); }); @@ -57,7 +83,7 @@ describe('get_mac_app_path plugin', () => { }); expect(result.isError).toBe(true); - expect(result.content[0].text).toContain('Either projectPath or workspacePath is required'); + expect(result.content[0].text).toContain('Provide a project or workspace'); }); it('should error when both projectPath and workspacePath provided', async () => { @@ -68,7 +94,7 @@ describe('get_mac_app_path plugin', () => { }); expect(result.isError).toBe(true); - expect(result.content[0].text).toContain('mutually exclusive'); + expect(result.content[0].text).toContain('Mutually exclusive parameters provided'); }); }); @@ -331,15 +357,9 @@ describe('get_mac_app_path plugin', () => { workspacePath: '/path/to/MyProject.xcworkspace', }); - 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].text).toContain('scheme is required'); + expect(result.content[0].text).toContain('session-set-defaults'); }); it('should return exact successful app path response with workspace', async () => { diff --git a/src/mcp/tools/macos/__tests__/test_macos.test.ts b/src/mcp/tools/macos/__tests__/test_macos.test.ts index 8bfa3965..d51157fd 100644 --- a/src/mcp/tools/macos/__tests__/test_macos.test.ts +++ b/src/mcp/tools/macos/__tests__/test_macos.test.ts @@ -3,21 +3,24 @@ * Following CLAUDE.md testing standards with literal validation * Using dependency injection for deterministic testing */ - -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, beforeEach } from 'vitest'; +import { z } from 'zod'; import { createMockExecutor } from '../../../../test-utils/mock-executors.ts'; +import { sessionStore } from '../../../../utils/session-store.ts'; import testMacos, { testMacosLogic } from '../test_macos.ts'; describe('test_macos plugin (unified)', () => { + beforeEach(() => { + sessionStore.clear(); + }); + describe('Export Field Validation (Literal)', () => { it('should have correct name', () => { expect(testMacos.name).toBe('test_macos'); }); it('should have correct description', () => { - expect(testMacos.description).toBe( - 'Runs tests for a macOS project or workspace using xcodebuild test and parses xcresult output. Provide exactly one of projectPath or workspacePath. IMPORTANT: Requires scheme. Example: test_macos({ projectPath: "/path/to/MyProject.xcodeproj", scheme: "MyScheme" })', - ); + expect(testMacos.description).toBe('Runs tests for a macOS target.'); }); it('should have handler function', () => { @@ -25,72 +28,72 @@ describe('test_macos plugin (unified)', () => { }); it('should validate schema correctly', () => { - // Test workspace path + const schema = z.object(testMacos.schema); + + expect(schema.safeParse({}).success).toBe(true); expect( - testMacos.schema.workspacePath.safeParse('/path/to/MyProject.xcworkspace').success, + schema.safeParse({ + derivedDataPath: '/path/to/derived-data', + extraArgs: ['--arg1', '--arg2'], + preferXcodebuild: true, + testRunnerEnv: { FOO: 'BAR' }, + }).success, ).toBe(true); - // Test project path - expect(testMacos.schema.projectPath.safeParse('/path/to/MyProject.xcodeproj').success).toBe( - true, - ); - - // Test required scheme - expect(testMacos.schema.scheme.safeParse('MyScheme').success).toBe(true); + expect(schema.safeParse({ derivedDataPath: 123 }).success).toBe(false); + expect(schema.safeParse({ extraArgs: ['--ok', 1] }).success).toBe(false); + expect(schema.safeParse({ preferXcodebuild: 'yes' }).success).toBe(false); + expect(schema.safeParse({ testRunnerEnv: { FOO: 123 } }).success).toBe(false); - // Test optional fields - expect(testMacos.schema.configuration.safeParse('Debug').success).toBe(true); - expect(testMacos.schema.derivedDataPath.safeParse('/path/to/derived-data').success).toBe( - true, + const schemaKeys = Object.keys(testMacos.schema).sort(); + expect(schemaKeys).toEqual( + ['derivedDataPath', 'extraArgs', 'preferXcodebuild', 'testRunnerEnv'].sort(), ); - expect(testMacos.schema.extraArgs.safeParse(['--arg1', '--arg2']).success).toBe(true); - expect(testMacos.schema.preferXcodebuild.safeParse(true).success).toBe(true); - - // Test invalid inputs - expect(testMacos.schema.workspacePath.safeParse(null).success).toBe(false); - expect(testMacos.schema.projectPath.safeParse(null).success).toBe(false); - expect(testMacos.schema.scheme.safeParse(null).success).toBe(false); - expect(testMacos.schema.extraArgs.safeParse('not-array').success).toBe(false); - expect(testMacos.schema.preferXcodebuild.safeParse('not-boolean').success).toBe(false); }); }); - describe('XOR Parameter Validation', () => { - it('should validate that either projectPath or workspacePath is provided', async () => { - const mockExecutor = createMockExecutor({ - success: true, - output: 'Test Suite All Tests passed', + describe('Handler Requirements', () => { + it('should require scheme before running', async () => { + const result = await testMacos.handler({}); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('scheme is required'); + }); + + it('should require project or workspace when scheme default exists', async () => { + sessionStore.setDefaults({ scheme: 'MyScheme' }); + + const result = await testMacos.handler({}); + + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Provide a project or workspace'); + }); + + it('should reject when both projectPath and workspacePath provided explicitly', async () => { + sessionStore.setDefaults({ scheme: 'MyScheme' }); + + const result = await testMacos.handler({ + projectPath: '/path/to/project.xcodeproj', + workspacePath: '/path/to/workspace.xcworkspace', }); - const mockFileSystemExecutor = { - mkdtemp: async () => '/tmp/test-123', - rm: async () => {}, - tmpdir: () => '/tmp', - stat: async () => ({ isDirectory: () => true }), - }; + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Mutually exclusive parameters provided'); + }); + }); + describe('XOR Parameter Validation', () => { + it('should validate that either projectPath or workspacePath is provided', async () => { // Should return error response when neither is provided const result = await testMacos.handler({ scheme: 'MyScheme', }); expect(result.isError).toBe(true); - expect(result.content[0].text).toContain('Either projectPath or workspacePath is required'); + expect(result.content[0].text).toContain('Provide a project or workspace'); }); it('should validate that both projectPath and workspacePath cannot be provided', async () => { - const mockExecutor = createMockExecutor({ - success: true, - output: 'Test Suite All Tests passed', - }); - - const mockFileSystemExecutor = { - mkdtemp: async () => '/tmp/test-123', - rm: async () => {}, - tmpdir: () => '/tmp', - stat: async () => ({ isDirectory: () => true }), - }; - // Should return error response when both are provided const result = await testMacos.handler({ projectPath: '/path/to/project.xcodeproj', @@ -99,9 +102,7 @@ describe('test_macos plugin (unified)', () => { }); expect(result.isError).toBe(true); - expect(result.content[0].text).toContain( - 'projectPath and workspacePath are mutually exclusive', - ); + expect(result.content[0].text).toContain('Mutually exclusive parameters provided'); }); it('should allow only projectPath', async () => { diff --git a/src/mcp/tools/macos/build_macos.ts b/src/mcp/tools/macos/build_macos.ts index 9cbc081e..4d4f6681 100644 --- a/src/mcp/tools/macos/build_macos.ts +++ b/src/mcp/tools/macos/build_macos.ts @@ -11,7 +11,7 @@ import { executeXcodeBuildCommand } from '../../../utils/build/index.ts'; import { ToolResponse, XcodePlatform } from '../../../types/common.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; +import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Types for dependency injection @@ -47,6 +47,14 @@ const baseSchemaObject = z.object({ const baseSchema = z.preprocess(nullifyEmptyStrings, baseSchemaObject); +const publicSchemaObject = baseSchemaObject.omit({ + projectPath: true, + workspacePath: true, + scheme: true, + configuration: true, + arch: true, +} as const); + const buildMacOSSchema = baseSchema .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, { message: 'Either projectPath or workspacePath is required.', @@ -89,12 +97,16 @@ export async function buildMacOSLogic( export default { 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( - buildMacOSSchema as z.ZodType, - buildMacOSLogic, - getDefaultCommandExecutor, - ), + description: 'Builds a macOS app.', + schema: publicSchemaObject.shape, + handler: createSessionAwareTool({ + internalSchema: buildMacOSSchema as unknown as z.ZodType, + logicFunction: buildMacOSLogic, + getExecutor: getDefaultCommandExecutor, + requirements: [ + { allOf: ['scheme'], message: 'scheme is required' }, + { oneOf: ['projectPath', 'workspacePath'], message: 'Provide a project or workspace' }, + ], + exclusivePairs: [['projectPath', 'workspacePath']], + }), }; diff --git a/src/mcp/tools/macos/build_run_macos.ts b/src/mcp/tools/macos/build_run_macos.ts index 19d5f2f0..adc9e9d3 100644 --- a/src/mcp/tools/macos/build_run_macos.ts +++ b/src/mcp/tools/macos/build_run_macos.ts @@ -12,7 +12,7 @@ import { executeXcodeBuildCommand } from '../../../utils/build/index.ts'; import { ToolResponse, XcodePlatform } from '../../../types/common.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; +import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath @@ -38,6 +38,14 @@ const baseSchemaObject = z.object({ const baseSchema = z.preprocess(nullifyEmptyStrings, baseSchemaObject); +const publicSchemaObject = baseSchemaObject.omit({ + projectPath: true, + workspacePath: true, + scheme: true, + configuration: true, + arch: true, +} as const); + const buildRunMacOSSchema = baseSchema .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, { message: 'Either projectPath or workspacePath is required.', @@ -207,20 +215,16 @@ export async function buildRunMacOSLogic( export default { 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 - handler: createTypedTool( - buildRunMacOSSchema as z.ZodType, - (params: BuildRunMacOSParams, executor) => - buildRunMacOSLogic( - { - ...params, - configuration: params.configuration ?? 'Debug', - preferXcodebuild: params.preferXcodebuild ?? false, - }, - executor, - ), - getDefaultCommandExecutor, - ), + description: 'Builds and runs a macOS app.', + schema: publicSchemaObject.shape, + handler: createSessionAwareTool({ + internalSchema: buildRunMacOSSchema as unknown as z.ZodType, + logicFunction: buildRunMacOSLogic, + getExecutor: getDefaultCommandExecutor, + requirements: [ + { allOf: ['scheme'], message: 'scheme is required' }, + { oneOf: ['projectPath', 'workspacePath'], message: 'Provide a project or workspace' }, + ], + exclusivePairs: [['projectPath', 'workspacePath']], + }), }; diff --git a/src/mcp/tools/macos/get_mac_app_path.ts b/src/mcp/tools/macos/get_mac_app_path.ts index 9e32d3ea..9f9ca8c4 100644 --- a/src/mcp/tools/macos/get_mac_app_path.ts +++ b/src/mcp/tools/macos/get_mac_app_path.ts @@ -10,7 +10,7 @@ import { ToolResponse } from '../../../types/common.ts'; import { log } from '../../../utils/logging/index.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; +import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath, sharing common options @@ -33,6 +33,14 @@ const baseSchemaObject = z.object({ const baseSchema = z.preprocess(nullifyEmptyStrings, baseSchemaObject); +const publicSchemaObject = baseSchemaObject.omit({ + projectPath: true, + workspacePath: true, + scheme: true, + configuration: true, + arch: true, +} as const); + const getMacosAppPathSchema = baseSchema .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, { message: 'Either projectPath or workspacePath is required.', @@ -179,12 +187,16 @@ export async function get_mac_app_pathLogic( export default { name: 'get_mac_app_path', - description: - "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( - getMacosAppPathSchema as z.ZodType, - get_mac_app_pathLogic, - getDefaultCommandExecutor, - ), + description: 'Retrieves the built macOS app bundle path.', + schema: publicSchemaObject.shape, + handler: createSessionAwareTool({ + internalSchema: getMacosAppPathSchema as unknown as z.ZodType, + logicFunction: get_mac_app_pathLogic, + getExecutor: getDefaultCommandExecutor, + requirements: [ + { allOf: ['scheme'], message: 'scheme is required' }, + { oneOf: ['projectPath', 'workspacePath'], message: 'Provide a project or workspace' }, + ], + exclusivePairs: [['projectPath', 'workspacePath']], + }), }; diff --git a/src/mcp/tools/macos/test_macos.ts b/src/mcp/tools/macos/test_macos.ts index 4949a131..5938d404 100644 --- a/src/mcp/tools/macos/test_macos.ts +++ b/src/mcp/tools/macos/test_macos.ts @@ -21,7 +21,7 @@ import { getDefaultCommandExecutor, getDefaultFileSystemExecutor, } from '../../../utils/execution/index.ts'; -import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; +import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath @@ -49,6 +49,13 @@ const baseSchemaObject = z.object({ const baseSchema = z.preprocess(nullifyEmptyStrings, baseSchemaObject); +const publicSchemaObject = baseSchemaObject.omit({ + projectPath: true, + workspacePath: true, + scheme: true, + configuration: true, +} as const); + const testMacosSchema = baseSchema .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, { message: 'Either projectPath or workspacePath is required.', @@ -318,14 +325,17 @@ export async function testMacosLogic( export default { name: 'test_macos', - description: - 'Runs tests for a macOS project or workspace using xcodebuild test and parses xcresult output. Provide exactly one of projectPath or workspacePath. IMPORTANT: Requires scheme. Example: test_macos({ projectPath: "/path/to/MyProject.xcodeproj", scheme: "MyScheme" })', - schema: baseSchemaObject.shape, // MCP SDK compatibility - handler: createTypedTool( - testMacosSchema as z.ZodType, - (params: TestMacosParams) => { - return testMacosLogic(params, getDefaultCommandExecutor(), getDefaultFileSystemExecutor()); - }, - getDefaultCommandExecutor, - ), + description: 'Runs tests for a macOS target.', + schema: publicSchemaObject.shape, + handler: createSessionAwareTool({ + internalSchema: testMacosSchema as unknown as z.ZodType, + logicFunction: (params, executor) => + testMacosLogic(params, executor, getDefaultFileSystemExecutor()), + getExecutor: getDefaultCommandExecutor, + requirements: [ + { allOf: ['scheme'], message: 'scheme is required' }, + { oneOf: ['projectPath', 'workspacePath'], message: 'Provide a project or workspace' }, + ], + exclusivePairs: [['projectPath', 'workspacePath']], + }), };