From e827aa935abaf68abf5e1f27eafe1d2dd0763f5f Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Mon, 15 Dec 2025 13:48:48 +0000 Subject: [PATCH 1/6] Add unit tests to macOS app example --- .../macOS/MCPTest.xcodeproj/project.pbxproj | 128 +++++++++++++++++- .../xcshareddata/xcschemes/MCPTest.xcscheme | 13 ++ .../macOS/MCPTestTests/MCPTestTests.swift | 16 +++ 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 example_projects/macOS/MCPTestTests/MCPTestTests.swift diff --git a/example_projects/macOS/MCPTest.xcodeproj/project.pbxproj b/example_projects/macOS/MCPTest.xcodeproj/project.pbxproj index ef052345..48863fab 100644 --- a/example_projects/macOS/MCPTest.xcodeproj/project.pbxproj +++ b/example_projects/macOS/MCPTest.xcodeproj/project.pbxproj @@ -6,8 +6,19 @@ objectVersion = 77; objects = { +/* Begin PBXContainerItemProxy section */ + 8BCB4E2B2EF00E2600D60AD2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8BA9F8182D62A17D00C22D5D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8BA9F81F2D62A17D00C22D5D; + remoteInfo = MCPTest; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 8BA9F8202D62A17D00C22D5D /* MCPTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MCPTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8BCB4E272EF00E2600D60AD2 /* MCPTestTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MCPTestTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -16,6 +27,11 @@ path = MCPTest; sourceTree = ""; }; + 8BCB4E282EF00E2600D60AD2 /* MCPTestTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = MCPTestTests; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -26,6 +42,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8BCB4E242EF00E2600D60AD2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -33,6 +56,7 @@ isa = PBXGroup; children = ( 8BA9F8222D62A17D00C22D5D /* MCPTest */, + 8BCB4E282EF00E2600D60AD2 /* MCPTestTests */, 8BA9F8212D62A17D00C22D5D /* Products */, ); sourceTree = ""; @@ -41,6 +65,7 @@ isa = PBXGroup; children = ( 8BA9F8202D62A17D00C22D5D /* MCPTest.app */, + 8BCB4E272EF00E2600D60AD2 /* MCPTestTests.xctest */, ); name = Products; sourceTree = ""; @@ -70,6 +95,29 @@ productReference = 8BA9F8202D62A17D00C22D5D /* MCPTest.app */; productType = "com.apple.product-type.application"; }; + 8BCB4E262EF00E2600D60AD2 /* MCPTestTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8BCB4E2F2EF00E2600D60AD2 /* Build configuration list for PBXNativeTarget "MCPTestTests" */; + buildPhases = ( + 8BCB4E232EF00E2600D60AD2 /* Sources */, + 8BCB4E242EF00E2600D60AD2 /* Frameworks */, + 8BCB4E252EF00E2600D60AD2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 8BCB4E2C2EF00E2600D60AD2 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 8BCB4E282EF00E2600D60AD2 /* MCPTestTests */, + ); + name = MCPTestTests; + packageProductDependencies = ( + ); + productName = MCPTestTests; + productReference = 8BCB4E272EF00E2600D60AD2 /* MCPTestTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -77,12 +125,16 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1620; + LastSwiftUpdateCheck = 2600; LastUpgradeCheck = 1620; TargetAttributes = { 8BA9F81F2D62A17D00C22D5D = { CreatedOnToolsVersion = 16.2; }; + 8BCB4E262EF00E2600D60AD2 = { + CreatedOnToolsVersion = 26.0; + TestTargetID = 8BA9F81F2D62A17D00C22D5D; + }; }; }; buildConfigurationList = 8BA9F81B2D62A17D00C22D5D /* Build configuration list for PBXProject "MCPTest" */; @@ -100,6 +152,7 @@ projectRoot = ""; targets = ( 8BA9F81F2D62A17D00C22D5D /* MCPTest */, + 8BCB4E262EF00E2600D60AD2 /* MCPTestTests */, ); }; /* End PBXProject section */ @@ -112,6 +165,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8BCB4E252EF00E2600D60AD2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -122,8 +182,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8BCB4E232EF00E2600D60AD2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 8BCB4E2C2EF00E2600D60AD2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8BA9F81F2D62A17D00C22D5D /* MCPTest */; + targetProxy = 8BCB4E2B2EF00E2600D60AD2 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 8BA9F8432D62A18100C22D5D /* Debug */ = { isa = XCBuildConfiguration; @@ -297,6 +372,48 @@ }; name = Release; }; + 8BCB4E2D2EF00E2600D60AD2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = BR6WD3M6ZD; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cameroncooke.test.MCPTestTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MCPTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MCPTest"; + }; + name = Debug; + }; + 8BCB4E2E2EF00E2600D60AD2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = BR6WD3M6ZD; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cameroncooke.test.MCPTestTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MCPTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MCPTest"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -318,6 +435,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 8BCB4E2F2EF00E2600D60AD2 /* Build configuration list for PBXNativeTarget "MCPTestTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8BCB4E2D2EF00E2600D60AD2 /* Debug */, + 8BCB4E2E2EF00E2600D60AD2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 8BA9F8182D62A17D00C22D5D /* Project object */; diff --git a/example_projects/macOS/MCPTest.xcodeproj/xcshareddata/xcschemes/MCPTest.xcscheme b/example_projects/macOS/MCPTest.xcodeproj/xcshareddata/xcschemes/MCPTest.xcscheme index 63087a7a..364cf9ef 100644 --- a/example_projects/macOS/MCPTest.xcodeproj/xcshareddata/xcschemes/MCPTest.xcscheme +++ b/example_projects/macOS/MCPTest.xcodeproj/xcshareddata/xcschemes/MCPTest.xcscheme @@ -29,6 +29,19 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" shouldAutocreateTestPlan = "YES"> + + + + + + Date: Mon, 15 Dec 2025 14:14:50 +0000 Subject: [PATCH 2/6] Add session-aware opt-out --- .../session-aware-tool-factory.test.ts | 20 +++++++ src/utils/environment.ts | 12 ++++ src/utils/typed-tool-factory.ts | 58 ++++++++++++++----- 3 files changed, 75 insertions(+), 15 deletions(-) diff --git a/src/utils/__tests__/session-aware-tool-factory.test.ts b/src/utils/__tests__/session-aware-tool-factory.test.ts index 92911b74..115d88b6 100644 --- a/src/utils/__tests__/session-aware-tool-factory.test.ts +++ b/src/utils/__tests__/session-aware-tool-factory.test.ts @@ -98,6 +98,26 @@ describe('createSessionAwareTool', () => { expect(result.content[0].text).toContain('Provide a project or workspace'); }); + it('uses opt-out messaging when session defaults schema is disabled', async () => { + const original = process.env.XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS; + process.env.XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS = 'true'; + + try { + const result = await handler({ projectPath: '/p.xcodeproj', simulatorId: 'SIM-1' }); + expect(result.isError).toBe(true); + const text = result.content[0].text; + expect(text).toContain('Missing required parameters'); + expect(text).toContain('scheme is required'); + expect(text).not.toContain('session defaults'); + } finally { + if (original === undefined) { + delete process.env.XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS; + } else { + process.env.XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS = original; + } + } + }); + it('should surface Zod validation errors with tip when invalid', async () => { const badHandler = createSessionAwareTool({ internalSchema, diff --git a/src/utils/environment.ts b/src/utils/environment.ts index cdfdd7fd..fac49e9a 100644 --- a/src/utils/environment.ts +++ b/src/utils/environment.ts @@ -67,6 +67,18 @@ export function getDefaultEnvironmentDetector(): EnvironmentDetector { return defaultEnvironmentDetector; } +/** + * Global opt-out for session defaults in MCP tool schemas. + * When enabled, tools re-expose all parameters instead of hiding session-managed fields. + */ +export function isSessionDefaultsSchemaOptOutEnabled(): boolean { + const raw = process.env.XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS; + if (!raw) return false; + + const normalized = raw.trim().toLowerCase(); + return ['1', 'true', 'yes', 'on'].includes(normalized); +} + /** * Normalizes a set of user-provided environment variables by ensuring they are * prefixed with TEST_RUNNER_. Variables already prefixed are preserved. diff --git a/src/utils/typed-tool-factory.ts b/src/utils/typed-tool-factory.ts index 048c1f77..995547b6 100644 --- a/src/utils/typed-tool-factory.ts +++ b/src/utils/typed-tool-factory.ts @@ -14,6 +14,7 @@ import { ToolResponse } from '../types/common.ts'; import type { CommandExecutor } from './execution/index.ts'; import { createErrorResponse } from './responses/index.ts'; import { sessionStore, type SessionDefaults } from './session-store.ts'; +import { isSessionDefaultsSchemaOptOutEnabled } from './environment.ts'; /** * Creates a type-safe tool handler that validates parameters at runtime @@ -71,6 +72,27 @@ function missingFromMerged( return keys.filter((k) => merged[k] == null); } +function formatRequirementError(opts: { + message: string; + setHint?: string; + optOutEnabled: boolean; +}) { + const title = opts.optOutEnabled + ? 'Missing required parameters' + : 'Missing required session defaults'; + const body = opts.optOutEnabled + ? opts.message + : [opts.message, opts.setHint].filter(Boolean).join('\n'); + return { title, body }; +} + +export function getSessionAwareToolSchemaShape< + TSession extends z.ZodRawShape, + TLegacy extends z.ZodRawShape, +>(opts: { sessionAware: z.ZodObject; legacy: z.ZodObject }): z.ZodRawShape { + return isSessionDefaultsSchemaOptOutEnabled() ? opts.legacy.shape : opts.sessionAware.shape; +} + export function createSessionAwareTool(opts: { internalSchema: z.ZodType; logicFunction: (params: TParams, executor: CommandExecutor) => Promise; @@ -132,13 +154,15 @@ export function createSessionAwareTool(opts: { if ('allOf' in req) { const missing = missingFromMerged(req.allOf, merged); if (missing.length > 0) { - return createErrorResponse( - 'Missing required session defaults', - `${req.message ?? `Required: ${req.allOf.join(', ')}`}\n` + - `Set with: session-set-defaults { ${missing - .map((k) => `"${k}": "..."`) - .join(', ')} }`, - ); + const setHint = `Set with: session-set-defaults { ${missing + .map((k) => `"${k}": "..."`) + .join(', ')} }`; + const { title, body } = formatRequirementError({ + message: req.message ?? `Required: ${req.allOf.join(', ')}`, + setHint, + optOutEnabled: isSessionDefaultsSchemaOptOutEnabled(), + }); + return createErrorResponse(title, body); } } else if ('oneOf' in req) { const satisfied = req.oneOf.some((k) => merged[k] != null); @@ -147,10 +171,12 @@ export function createSessionAwareTool(opts: { const setHints = req.oneOf .map((k) => `session-set-defaults { "${k}": "..." }`) .join(' OR '); - return createErrorResponse( - 'Missing required session defaults', - `${req.message ?? `Provide one of: ${options}`}\nSet with: ${setHints}`, - ); + const { title, body } = formatRequirementError({ + message: req.message ?? `Provide one of: ${options}`, + setHint: `Set with: ${setHints}`, + optOutEnabled: isSessionDefaultsSchemaOptOutEnabled(), + }); + return createErrorResponse(title, body); } } } @@ -164,10 +190,12 @@ export function createSessionAwareTool(opts: { return `${path}: ${e.message}`; }); - return createErrorResponse( - 'Parameter validation failed', - `Invalid parameters:\n${errorMessages.join('\n')}\nTip: set session defaults via session-set-defaults`, - ); + const tip = isSessionDefaultsSchemaOptOutEnabled() + ? '' + : '\nTip: set session defaults via session-set-defaults'; + const details = `Invalid parameters:\n${errorMessages.join('\n')}${tip}`; + + return createErrorResponse('Parameter validation failed', details); } throw error; } From 62241d30cb7343e17d66f86c2a946e659db1828f Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Mon, 15 Dec 2025 14:18:33 +0000 Subject: [PATCH 3/6] Update tools to support session-aware opt-out mode --- src/mcp/tools/device/build_device.ts | 22 +++++++++++------ src/mcp/tools/device/get_device_app_path.ts | 22 +++++++++++------ src/mcp/tools/device/install_app_device.ts | 12 ++++++++-- src/mcp/tools/device/launch_app_device.ts | 12 ++++++++-- src/mcp/tools/device/stop_app_device.ts | 12 ++++++++-- src/mcp/tools/device/test_device.ts | 24 ++++++++++++------- src/mcp/tools/logging/start_device_log_cap.ts | 12 ++++++++-- src/mcp/tools/logging/start_sim_log_cap.ts | 10 ++++++-- src/mcp/tools/macos/build_macos.ts | 10 ++++++-- src/mcp/tools/macos/build_run_macos.ts | 10 ++++++-- src/mcp/tools/macos/get_mac_app_path.ts | 10 ++++++-- src/mcp/tools/macos/test_macos.ts | 10 ++++++-- .../tools/project-discovery/list_schemes.ts | 10 ++++++-- .../project-discovery/show_build_settings.ts | 10 ++++++-- .../tools/simulator-management/erase_sims.ts | 10 ++++++-- .../reset_sim_location.ts | 10 ++++++-- .../set_sim_appearance.ts | 10 ++++++-- .../simulator-management/set_sim_location.ts | 10 ++++++-- .../simulator-management/sim_statusbar.ts | 10 ++++++-- src/mcp/tools/simulator/boot_sim.ts | 10 ++++++-- src/mcp/tools/simulator/build_run_sim.ts | 10 ++++++-- src/mcp/tools/simulator/build_sim.ts | 10 ++++++-- src/mcp/tools/simulator/get_sim_app_path.ts | 10 ++++++-- src/mcp/tools/simulator/install_app_sim.ts | 10 ++++++-- .../tools/simulator/launch_app_logs_sim.ts | 10 ++++++-- src/mcp/tools/simulator/launch_app_sim.ts | 10 ++++++-- src/mcp/tools/simulator/record_sim_video.ts | 10 ++++++-- src/mcp/tools/simulator/stop_app_sim.ts | 10 ++++++-- src/mcp/tools/simulator/test_sim.ts | 10 ++++++-- src/mcp/tools/ui-testing/button.ts | 10 ++++++-- src/mcp/tools/ui-testing/describe_ui.ts | 10 ++++++-- src/mcp/tools/ui-testing/gesture.ts | 10 ++++++-- src/mcp/tools/ui-testing/key_press.ts | 10 ++++++-- src/mcp/tools/ui-testing/key_sequence.ts | 10 ++++++-- src/mcp/tools/ui-testing/long_press.ts | 10 ++++++-- src/mcp/tools/ui-testing/screenshot.ts | 10 ++++++-- src/mcp/tools/ui-testing/swipe.ts | 10 ++++++-- src/mcp/tools/ui-testing/tap.ts | 17 +++++++++---- src/mcp/tools/ui-testing/touch.ts | 10 ++++++-- src/mcp/tools/ui-testing/type_text.ts | 10 ++++++-- src/mcp/tools/utilities/clean.ts | 10 ++++++-- src/utils/typed-tool-factory.ts | 2 +- 42 files changed, 363 insertions(+), 102 deletions(-) diff --git a/src/mcp/tools/device/build_device.ts b/src/mcp/tools/device/build_device.ts index 40a3f510..cbb799db 100644 --- a/src/mcp/tools/device/build_device.ts +++ b/src/mcp/tools/device/build_device.ts @@ -10,7 +10,10 @@ import { ToolResponse, XcodePlatform } from '../../../types/common.ts'; import { executeXcodeBuildCommand } from '../../../utils/build/index.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath @@ -36,6 +39,13 @@ const buildDeviceSchema = baseSchema export type BuildDeviceParams = z.infer; +const publicSchemaObject = baseSchemaObject.omit({ + projectPath: true, + workspacePath: true, + scheme: true, + configuration: true, +} as const); + /** * Business logic for building device project or workspace. * Exported for direct testing and reuse. @@ -64,12 +74,10 @@ export async function buildDeviceLogic( export default { name: 'build_device', description: 'Builds an app for a connected device.', - schema: baseSchemaObject.omit({ - projectPath: true, - workspacePath: true, - scheme: true, - configuration: true, - } as const).shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: buildDeviceSchema as unknown as z.ZodType, logicFunction: buildDeviceLogic, diff --git a/src/mcp/tools/device/get_device_app_path.ts b/src/mcp/tools/device/get_device_app_path.ts index 77ab9647..6e1f9f21 100644 --- a/src/mcp/tools/device/get_device_app_path.ts +++ b/src/mcp/tools/device/get_device_app_path.ts @@ -11,7 +11,10 @@ import { log } from '../../../utils/logging/index.ts'; import { createTextResponse } from '../../../utils/responses/index.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath, sharing common options @@ -43,6 +46,13 @@ const getDeviceAppPathSchema = baseSchema // Use z.infer for type safety type GetDeviceAppPathParams = z.infer; +const publicSchemaObject = baseSchemaObject.omit({ + projectPath: true, + workspacePath: true, + scheme: true, + configuration: true, +} as const); + export async function get_device_app_pathLogic( params: GetDeviceAppPathParams, executor: CommandExecutor, @@ -147,12 +157,10 @@ export async function get_device_app_pathLogic( export default { name: 'get_device_app_path', description: 'Retrieves the built app path for a connected device.', - schema: baseSchemaObject.omit({ - projectPath: true, - workspacePath: true, - scheme: true, - configuration: true, - } as const).shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: getDeviceAppPathSchema as unknown as z.ZodType, logicFunction: get_device_app_pathLogic, diff --git a/src/mcp/tools/device/install_app_device.ts b/src/mcp/tools/device/install_app_device.ts index 16a2913d..96f8d0fe 100644 --- a/src/mcp/tools/device/install_app_device.ts +++ b/src/mcp/tools/device/install_app_device.ts @@ -10,7 +10,10 @@ 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 { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const installAppDeviceSchema = z.object({ @@ -23,6 +26,8 @@ const installAppDeviceSchema = z.object({ .describe('Path to the .app bundle to install (full path to the .app directory)'), }); +const publicSchemaObject = installAppDeviceSchema.omit({ deviceId: true } as const); + // Use z.infer for type safety type InstallAppDeviceParams = z.infer; @@ -83,7 +88,10 @@ export async function install_app_deviceLogic( export default { name: 'install_app_device', description: 'Installs an app on a connected device.', - schema: installAppDeviceSchema.omit({ deviceId: true } as const).shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: installAppDeviceSchema, + }), handler: createSessionAwareTool({ internalSchema: installAppDeviceSchema as unknown as z.ZodType, logicFunction: install_app_deviceLogic, diff --git a/src/mcp/tools/device/launch_app_device.ts b/src/mcp/tools/device/launch_app_device.ts index bebe0dc2..cea6219e 100644 --- a/src/mcp/tools/device/launch_app_device.ts +++ b/src/mcp/tools/device/launch_app_device.ts @@ -10,7 +10,10 @@ 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 { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { promises as fs } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; @@ -32,6 +35,8 @@ const launchAppDeviceSchema = z.object({ .describe('Bundle identifier of the app to launch (e.g., "com.example.MyApp")'), }); +const publicSchemaObject = launchAppDeviceSchema.omit({ deviceId: true } as const); + // Use z.infer for type safety type LaunchAppDeviceParams = z.infer; @@ -142,7 +147,10 @@ export async function launch_app_deviceLogic( export default { name: 'launch_app_device', description: 'Launches an app on a connected device.', - schema: launchAppDeviceSchema.omit({ deviceId: true } as const).shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: launchAppDeviceSchema, + }), handler: createSessionAwareTool({ internalSchema: launchAppDeviceSchema as unknown as z.ZodType, logicFunction: launch_app_deviceLogic, diff --git a/src/mcp/tools/device/stop_app_device.ts b/src/mcp/tools/device/stop_app_device.ts index 93789707..bc5f4565 100644 --- a/src/mcp/tools/device/stop_app_device.ts +++ b/src/mcp/tools/device/stop_app_device.ts @@ -10,7 +10,10 @@ 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 { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const stopAppDeviceSchema = z.object({ @@ -21,6 +24,8 @@ const stopAppDeviceSchema = z.object({ // Use z.infer for type safety type StopAppDeviceParams = z.infer; +const publicSchemaObject = stopAppDeviceSchema.omit({ deviceId: true } as const); + export async function stop_app_deviceLogic( params: StopAppDeviceParams, executor: CommandExecutor, @@ -85,7 +90,10 @@ export async function stop_app_deviceLogic( export default { name: 'stop_app_device', description: 'Stops a running app on a connected device.', - schema: stopAppDeviceSchema.omit({ deviceId: true } as const).shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: stopAppDeviceSchema, + }), handler: createSessionAwareTool({ internalSchema: stopAppDeviceSchema as unknown as z.ZodType, logicFunction: stop_app_deviceLogic, diff --git a/src/mcp/tools/device/test_device.ts b/src/mcp/tools/device/test_device.ts index 80ac57a2..15dbb8df 100644 --- a/src/mcp/tools/device/test_device.ts +++ b/src/mcp/tools/device/test_device.ts @@ -21,7 +21,10 @@ import { getDefaultCommandExecutor, getDefaultFileSystemExecutor, } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath @@ -58,6 +61,14 @@ const testDeviceSchema = baseSchema export type TestDeviceParams = z.infer; +const publicSchemaObject = baseSchemaObject.omit({ + projectPath: true, + workspacePath: true, + scheme: true, + deviceId: true, + configuration: true, +} as const); + /** * Type definition for test summary structure from xcresulttool * (JavaScript implementation - no actual interface, this is just documentation) @@ -276,13 +287,10 @@ export async function testDeviceLogic( export default { name: 'test_device', description: 'Runs tests on a physical Apple device.', - schema: baseSchemaObject.omit({ - projectPath: true, - workspacePath: true, - scheme: true, - deviceId: true, - configuration: true, - } as const).shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: testDeviceSchema as unknown as z.ZodType, logicFunction: (params: TestDeviceParams, executor: CommandExecutor) => diff --git a/src/mcp/tools/logging/start_device_log_cap.ts b/src/mcp/tools/logging/start_device_log_cap.ts index 49bfc7ec..8420720d 100644 --- a/src/mcp/tools/logging/start_device_log_cap.ts +++ b/src/mcp/tools/logging/start_device_log_cap.ts @@ -14,7 +14,10 @@ import { log } from '../../../utils/logging/index.ts'; import type { CommandExecutor, FileSystemExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; import { ToolResponse } from '../../../types/common.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; /** * Log file retention policy for device logs: @@ -632,6 +635,8 @@ const startDeviceLogCapSchema = z.object({ bundleId: z.string().describe('Bundle identifier of the app to launch and capture logs for.'), }); +const publicSchemaObject = startDeviceLogCapSchema.omit({ deviceId: true } as const); + // Use z.infer for type safety type StartDeviceLogCapParams = z.infer; @@ -679,7 +684,10 @@ export async function start_device_log_capLogic( export default { name: 'start_device_log_cap', description: 'Starts log capture on a connected device.', - schema: startDeviceLogCapSchema.omit({ deviceId: true } as const).shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: startDeviceLogCapSchema, + }), handler: createSessionAwareTool({ internalSchema: startDeviceLogCapSchema as unknown as z.ZodType, logicFunction: start_device_log_capLogic, diff --git a/src/mcp/tools/logging/start_sim_log_cap.ts b/src/mcp/tools/logging/start_sim_log_cap.ts index cc7b415d..e23ad14d 100644 --- a/src/mcp/tools/logging/start_sim_log_cap.ts +++ b/src/mcp/tools/logging/start_sim_log_cap.ts @@ -8,7 +8,10 @@ import { z } from 'zod'; import { startLogCapture } from '../../../utils/log-capture/index.ts'; import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/command.ts'; import { ToolResponse, createTextContent } from '../../../types/common.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const startSimLogCapSchema = z.object({ @@ -61,7 +64,10 @@ export default { name: 'start_sim_log_cap', description: 'Starts capturing logs from a specified simulator. Returns a session ID. By default, captures only structured logs.', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: startSimLogCapSchema, + }), handler: createSessionAwareTool({ internalSchema: startSimLogCapSchema as unknown as z.ZodType, logicFunction: start_sim_log_capLogic, diff --git a/src/mcp/tools/macos/build_macos.ts b/src/mcp/tools/macos/build_macos.ts index 4d4f6681..80d41bd7 100644 --- a/src/mcp/tools/macos/build_macos.ts +++ b/src/mcp/tools/macos/build_macos.ts @@ -11,7 +11,10 @@ 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 { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Types for dependency injection @@ -98,7 +101,10 @@ export async function buildMacOSLogic( export default { name: 'build_macos', description: 'Builds a macOS app.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: buildMacOSSchema as unknown as z.ZodType, logicFunction: buildMacOSLogic, diff --git a/src/mcp/tools/macos/build_run_macos.ts b/src/mcp/tools/macos/build_run_macos.ts index adc9e9d3..78249bae 100644 --- a/src/mcp/tools/macos/build_run_macos.ts +++ b/src/mcp/tools/macos/build_run_macos.ts @@ -12,7 +12,10 @@ 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 { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath @@ -216,7 +219,10 @@ export async function buildRunMacOSLogic( export default { name: 'build_run_macos', description: 'Builds and runs a macOS app.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: buildRunMacOSSchema as unknown as z.ZodType, logicFunction: buildRunMacOSLogic, diff --git a/src/mcp/tools/macos/get_mac_app_path.ts b/src/mcp/tools/macos/get_mac_app_path.ts index 9f9ca8c4..a14221ec 100644 --- a/src/mcp/tools/macos/get_mac_app_path.ts +++ b/src/mcp/tools/macos/get_mac_app_path.ts @@ -10,7 +10,10 @@ 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 { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath, sharing common options @@ -188,7 +191,10 @@ export async function get_mac_app_pathLogic( export default { name: 'get_mac_app_path', description: 'Retrieves the built macOS app bundle path.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: getMacosAppPathSchema as unknown as z.ZodType, logicFunction: get_mac_app_pathLogic, diff --git a/src/mcp/tools/macos/test_macos.ts b/src/mcp/tools/macos/test_macos.ts index 5938d404..393aa3ce 100644 --- a/src/mcp/tools/macos/test_macos.ts +++ b/src/mcp/tools/macos/test_macos.ts @@ -21,7 +21,10 @@ import { getDefaultCommandExecutor, getDefaultFileSystemExecutor, } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath @@ -326,7 +329,10 @@ export async function testMacosLogic( export default { name: 'test_macos', description: 'Runs tests for a macOS target.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: testMacosSchema as unknown as z.ZodType, logicFunction: (params, executor) => diff --git a/src/mcp/tools/project-discovery/list_schemes.ts b/src/mcp/tools/project-discovery/list_schemes.ts index 82064ffc..7ef25d4f 100644 --- a/src/mcp/tools/project-discovery/list_schemes.ts +++ b/src/mcp/tools/project-discovery/list_schemes.ts @@ -11,7 +11,10 @@ import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; import { createTextResponse } from '../../../utils/responses/index.ts'; import { ToolResponse } from '../../../types/common.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath @@ -117,7 +120,10 @@ const publicSchemaObject = baseSchemaObject.omit({ export default { name: 'list_schemes', description: 'Lists schemes for a project or workspace.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: listSchemesSchema as unknown as z.ZodType, logicFunction: listSchemesLogic, diff --git a/src/mcp/tools/project-discovery/show_build_settings.ts b/src/mcp/tools/project-discovery/show_build_settings.ts index e2a5b9db..fd6a25b3 100644 --- a/src/mcp/tools/project-discovery/show_build_settings.ts +++ b/src/mcp/tools/project-discovery/show_build_settings.ts @@ -11,7 +11,10 @@ import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; import { createTextResponse } from '../../../utils/responses/index.ts'; import { ToolResponse } from '../../../types/common.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath @@ -111,7 +114,10 @@ const publicSchemaObject = baseSchemaObject.omit({ export default { name: 'show_build_settings', description: 'Shows xcodebuild build settings.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: showBuildSettingsSchema as unknown as z.ZodType, logicFunction: showBuildSettingsLogic, diff --git a/src/mcp/tools/simulator-management/erase_sims.ts b/src/mcp/tools/simulator-management/erase_sims.ts index feb61e19..afe9437f 100644 --- a/src/mcp/tools/simulator-management/erase_sims.ts +++ b/src/mcp/tools/simulator-management/erase_sims.ts @@ -2,7 +2,10 @@ import { z } from 'zod'; import { ToolResponse } from '../../../types/common.ts'; import { log } from '../../../utils/logging/index.ts'; import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; const eraseSimsBaseSchema = z .object({ @@ -83,7 +86,10 @@ const publicSchemaObject = eraseSimsSchema.omit({ simulatorId: true } as const). export default { name: 'erase_sims', description: 'Erases a simulator by UDID.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: eraseSimsSchema, + }), handler: createSessionAwareTool({ internalSchema: eraseSimsSchema as unknown as z.ZodType, logicFunction: erase_simsLogic, diff --git a/src/mcp/tools/simulator-management/reset_sim_location.ts b/src/mcp/tools/simulator-management/reset_sim_location.ts index e08e303d..8f146d25 100644 --- a/src/mcp/tools/simulator-management/reset_sim_location.ts +++ b/src/mcp/tools/simulator-management/reset_sim_location.ts @@ -2,7 +2,10 @@ import { z } from 'zod'; import { ToolResponse } from '../../../types/common.ts'; import { log } from '../../../utils/logging/index.ts'; import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const resetSimulatorLocationSchema = z.object({ @@ -92,7 +95,10 @@ const publicSchemaObject = resetSimulatorLocationSchema export default { name: 'reset_sim_location', description: "Resets the simulator's location to default.", - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: resetSimulatorLocationSchema, + }), handler: createSessionAwareTool({ internalSchema: resetSimulatorLocationSchema as unknown as z.ZodType, diff --git a/src/mcp/tools/simulator-management/set_sim_appearance.ts b/src/mcp/tools/simulator-management/set_sim_appearance.ts index 156ae871..b97e9f59 100644 --- a/src/mcp/tools/simulator-management/set_sim_appearance.ts +++ b/src/mcp/tools/simulator-management/set_sim_appearance.ts @@ -2,7 +2,10 @@ import { z } from 'zod'; import { ToolResponse } from '../../../types/common.ts'; import { log } from '../../../utils/logging/index.ts'; import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const setSimAppearanceSchema = z.object({ @@ -92,7 +95,10 @@ const publicSchemaObject = setSimAppearanceSchema.omit({ simulatorId: true } as export default { name: 'set_sim_appearance', description: 'Sets the appearance mode (dark/light) of an iOS simulator.', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: setSimAppearanceSchema, + }), handler: createSessionAwareTool({ internalSchema: setSimAppearanceSchema as unknown as z.ZodType, logicFunction: set_sim_appearanceLogic, diff --git a/src/mcp/tools/simulator-management/set_sim_location.ts b/src/mcp/tools/simulator-management/set_sim_location.ts index 1121231e..732222fe 100644 --- a/src/mcp/tools/simulator-management/set_sim_location.ts +++ b/src/mcp/tools/simulator-management/set_sim_location.ts @@ -2,7 +2,10 @@ import { z } from 'zod'; import { ToolResponse } from '../../../types/common.ts'; import { log } from '../../../utils/logging/index.ts'; import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const setSimulatorLocationSchema = z.object({ @@ -120,7 +123,10 @@ const publicSchemaObject = setSimulatorLocationSchema.omit({ simulatorId: true } export default { name: 'set_sim_location', description: 'Sets a custom GPS location for the simulator.', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: setSimulatorLocationSchema, + }), handler: createSessionAwareTool({ internalSchema: setSimulatorLocationSchema as unknown as z.ZodType, logicFunction: set_sim_locationLogic, diff --git a/src/mcp/tools/simulator-management/sim_statusbar.ts b/src/mcp/tools/simulator-management/sim_statusbar.ts index cb271cbd..45e9c119 100644 --- a/src/mcp/tools/simulator-management/sim_statusbar.ts +++ b/src/mcp/tools/simulator-management/sim_statusbar.ts @@ -2,7 +2,10 @@ import { z } from 'zod'; import { ToolResponse } from '../../../types/common.ts'; import { log } from '../../../utils/logging/index.ts'; import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const simStatusbarSchema = z.object({ @@ -94,7 +97,10 @@ export default { name: 'sim_statusbar', description: 'Sets the data network indicator in the iOS simulator status bar. Use "clear" to reset all overrides, or specify a network type (hide, wifi, 3g, 4g, lte, lte-a, lte+, 5g, 5g+, 5g-uwb, 5g-uc).', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: simStatusbarSchema, + }), // MCP SDK compatibility handler: createSessionAwareTool({ internalSchema: simStatusbarSchema as unknown as z.ZodType, logicFunction: sim_statusbarLogic, diff --git a/src/mcp/tools/simulator/boot_sim.ts b/src/mcp/tools/simulator/boot_sim.ts index e8acd9c2..970f19fb 100644 --- a/src/mcp/tools/simulator/boot_sim.ts +++ b/src/mcp/tools/simulator/boot_sim.ts @@ -3,7 +3,10 @@ import { ToolResponse } from '../../../types/common.ts'; import { log } from '../../../utils/logging/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; const bootSimSchemaObject = z.object({ simulatorId: z.string().describe('UUID of the simulator to use (obtained from list_sims)'), @@ -68,7 +71,10 @@ Next steps: export default { name: 'boot_sim', description: 'Boots an iOS simulator.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: bootSimSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: bootSimSchemaObject, logicFunction: boot_simLogic, diff --git a/src/mcp/tools/simulator/build_run_sim.ts b/src/mcp/tools/simulator/build_run_sim.ts index c1e876e5..d4721c80 100644 --- a/src/mcp/tools/simulator/build_run_sim.ts +++ b/src/mcp/tools/simulator/build_run_sim.ts @@ -10,7 +10,10 @@ import { z } from 'zod'; import { ToolResponse, SharedBuildParams, XcodePlatform } from '../../../types/common.ts'; import { log } from '../../../utils/logging/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { createTextResponse } from '../../../utils/responses/index.ts'; import { executeXcodeBuildCommand } from '../../../utils/build/index.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; @@ -503,7 +506,10 @@ const publicSchemaObject = baseSchemaObject.omit({ export default { name: 'build_run_sim', description: 'Builds and runs an app on an iOS simulator.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: buildRunSimulatorSchema as unknown as z.ZodType, logicFunction: build_run_simLogic, diff --git a/src/mcp/tools/simulator/build_sim.ts b/src/mcp/tools/simulator/build_sim.ts index 53414e19..6d357d2e 100644 --- a/src/mcp/tools/simulator/build_sim.ts +++ b/src/mcp/tools/simulator/build_sim.ts @@ -12,7 +12,10 @@ 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 { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; // Unified schema: XOR between projectPath and workspacePath, and XOR between simulatorId and simulatorName @@ -150,7 +153,10 @@ const publicSchemaObject = baseSchemaObject.omit({ export default { name: 'build_sim', description: 'Builds an app for an iOS simulator.', - schema: publicSchemaObject.shape, // MCP SDK compatibility (public inputs only) + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), // MCP SDK compatibility (public inputs only) handler: createSessionAwareTool({ internalSchema: buildSimulatorSchema as unknown as z.ZodType, logicFunction: build_simLogic, diff --git a/src/mcp/tools/simulator/get_sim_app_path.ts b/src/mcp/tools/simulator/get_sim_app_path.ts index 2f7b591e..8787eae6 100644 --- a/src/mcp/tools/simulator/get_sim_app_path.ts +++ b/src/mcp/tools/simulator/get_sim_app_path.ts @@ -12,7 +12,10 @@ import { createTextResponse } from '../../../utils/responses/index.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; import { ToolResponse } from '../../../types/common.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; const XcodePlatform = { @@ -303,7 +306,10 @@ const publicSchemaObject = baseGetSimulatorAppPathSchema.omit({ export default { name: 'get_sim_app_path', description: 'Retrieves the built app path for an iOS simulator.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseGetSimulatorAppPathSchema, + }), handler: createSessionAwareTool({ internalSchema: getSimulatorAppPathSchema as unknown as z.ZodType, logicFunction: get_sim_app_pathLogic, diff --git a/src/mcp/tools/simulator/install_app_sim.ts b/src/mcp/tools/simulator/install_app_sim.ts index bcc03fee..f511913b 100644 --- a/src/mcp/tools/simulator/install_app_sim.ts +++ b/src/mcp/tools/simulator/install_app_sim.ts @@ -4,7 +4,10 @@ import { log } from '../../../utils/logging/index.ts'; import { validateFileExists } from '../../../utils/validation/index.ts'; import type { CommandExecutor, FileSystemExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; const installAppSimSchemaObject = z.object({ simulatorId: z.string().describe('UUID of the simulator to use (obtained from list_sims)'), @@ -96,7 +99,10 @@ export async function install_app_simLogic( export default { name: 'install_app_sim', description: 'Installs an app in an iOS simulator.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: installAppSimSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: installAppSimSchemaObject, logicFunction: install_app_simLogic, diff --git a/src/mcp/tools/simulator/launch_app_logs_sim.ts b/src/mcp/tools/simulator/launch_app_logs_sim.ts index a3be0641..88823fbe 100644 --- a/src/mcp/tools/simulator/launch_app_logs_sim.ts +++ b/src/mcp/tools/simulator/launch_app_logs_sim.ts @@ -4,7 +4,10 @@ import { log } from '../../../utils/logging/index.ts'; import { startLogCapture } from '../../../utils/log-capture/index.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; export type LogCaptureFunction = ( params: { @@ -67,7 +70,10 @@ export async function launch_app_logs_simLogic( export default { name: 'launch_app_logs_sim', description: 'Launches an app in an iOS simulator and captures its logs.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: launchAppLogsSimSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: launchAppLogsSimSchemaObject, logicFunction: launch_app_logs_simLogic, diff --git a/src/mcp/tools/simulator/launch_app_sim.ts b/src/mcp/tools/simulator/launch_app_sim.ts index 3937ae64..caa7d1c9 100644 --- a/src/mcp/tools/simulator/launch_app_sim.ts +++ b/src/mcp/tools/simulator/launch_app_sim.ts @@ -4,7 +4,10 @@ import { log } from '../../../utils/logging/index.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; const baseSchemaObject = z.object({ simulatorId: z @@ -200,7 +203,10 @@ const publicSchemaObject = baseSchemaObject export default { name: 'launch_app_sim', description: 'Launches an app in an iOS simulator.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: launchAppSimSchema as unknown as z.ZodType, logicFunction: launch_app_simLogic, diff --git a/src/mcp/tools/simulator/record_sim_video.ts b/src/mcp/tools/simulator/record_sim_video.ts index 8632dcf1..d4152bab 100644 --- a/src/mcp/tools/simulator/record_sim_video.ts +++ b/src/mcp/tools/simulator/record_sim_video.ts @@ -15,7 +15,10 @@ import { startSimulatorVideoCapture, stopSimulatorVideoCapture, } from '../../../utils/video-capture/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import { dirname } from 'path'; // Base schema object (used for MCP schema exposure) @@ -223,7 +226,10 @@ const publicSchemaObject = recordSimVideoSchemaObject.omit({ simulatorId: true } export default { name: 'record_sim_video', description: 'Starts or stops video capture for an iOS simulator.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: recordSimVideoSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: recordSimVideoSchema as unknown as z.ZodType, logicFunction: record_sim_videoLogic, diff --git a/src/mcp/tools/simulator/stop_app_sim.ts b/src/mcp/tools/simulator/stop_app_sim.ts index 30a07b95..7321281e 100644 --- a/src/mcp/tools/simulator/stop_app_sim.ts +++ b/src/mcp/tools/simulator/stop_app_sim.ts @@ -4,7 +4,10 @@ import { log } from '../../../utils/logging/index.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; const baseSchemaObject = z.object({ simulatorId: z @@ -154,7 +157,10 @@ const publicSchemaObject = baseSchemaObject export default { name: 'stop_app_sim', description: 'Stops an app running in an iOS simulator.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: stopAppSimSchema as unknown as z.ZodType, logicFunction: stop_app_simLogic, diff --git a/src/mcp/tools/simulator/test_sim.ts b/src/mcp/tools/simulator/test_sim.ts index b482570a..c6061c30 100644 --- a/src/mcp/tools/simulator/test_sim.ts +++ b/src/mcp/tools/simulator/test_sim.ts @@ -14,7 +14,10 @@ import { ToolResponse } from '../../../types/common.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define base schema object with all fields const baseSchemaObject = z.object({ @@ -128,7 +131,10 @@ const publicSchemaObject = baseSchemaObject.omit({ export default { name: 'test_sim', description: 'Runs tests on an iOS simulator.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: testSimulatorSchema as unknown as z.ZodType, logicFunction: test_simLogic, diff --git a/src/mcp/tools/ui-testing/button.ts b/src/mcp/tools/ui-testing/button.ts index b9bdbf32..a06787d7 100644 --- a/src/mcp/tools/ui-testing/button.ts +++ b/src/mcp/tools/ui-testing/button.ts @@ -10,7 +10,10 @@ import { getBundledAxeEnvironment, } from '../../../utils/axe-helpers.ts'; import { DependencyError, AxeError, SystemError } from '../../../utils/errors.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const buttonSchema = z.object({ @@ -79,7 +82,10 @@ export default { name: 'button', description: 'Press hardware button on iOS simulator. Supported buttons: apple-pay, home, lock, side-button, siri', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: buttonSchema, + }), handler: createSessionAwareTool({ internalSchema: buttonSchema as unknown as z.ZodType, logicFunction: (params: ButtonParams, executor: CommandExecutor) => diff --git a/src/mcp/tools/ui-testing/describe_ui.ts b/src/mcp/tools/ui-testing/describe_ui.ts index feb0f1f8..7587a40b 100644 --- a/src/mcp/tools/ui-testing/describe_ui.ts +++ b/src/mcp/tools/ui-testing/describe_ui.ts @@ -10,7 +10,10 @@ import { getAxePath, getBundledAxeEnvironment, } from '../../../utils/axe-helpers.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const describeUiSchema = z.object({ @@ -112,7 +115,10 @@ export default { name: 'describe_ui', description: 'Gets entire view hierarchy with precise frame coordinates (x, y, width, height) for all visible elements. Use this before UI interactions or after layout changes - do NOT guess coordinates from screenshots. Returns JSON tree with frame data for accurate automation.', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: describeUiSchema, + }), handler: createSessionAwareTool({ internalSchema: describeUiSchema as unknown as z.ZodType, logicFunction: (params: DescribeUiParams, executor: CommandExecutor) => diff --git a/src/mcp/tools/ui-testing/gesture.ts b/src/mcp/tools/ui-testing/gesture.ts index 5cb1f1d2..dc205368 100644 --- a/src/mcp/tools/ui-testing/gesture.ts +++ b/src/mcp/tools/ui-testing/gesture.ts @@ -22,7 +22,10 @@ import { getAxePath, getBundledAxeEnvironment, } from '../../../utils/axe/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const gestureSchema = z.object({ @@ -156,7 +159,10 @@ export default { name: 'gesture', description: 'Perform gesture on iOS simulator using preset gestures: scroll-up, scroll-down, scroll-left, scroll-right, swipe-from-left-edge, swipe-from-right-edge, swipe-from-top-edge, swipe-from-bottom-edge', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: gestureSchema, + }), handler: createSessionAwareTool({ internalSchema: gestureSchema as unknown as z.ZodType, logicFunction: (params: GestureParams, executor: CommandExecutor) => diff --git a/src/mcp/tools/ui-testing/key_press.ts b/src/mcp/tools/ui-testing/key_press.ts index 363dd511..d9ebbe02 100644 --- a/src/mcp/tools/ui-testing/key_press.ts +++ b/src/mcp/tools/ui-testing/key_press.ts @@ -15,7 +15,10 @@ import { getAxePath, getBundledAxeEnvironment, } from '../../../utils/axe/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const keyPressSchema = z.object({ @@ -84,7 +87,10 @@ export default { name: 'key_press', description: 'Press a single key by keycode on the simulator. Common keycodes: 40=Return, 42=Backspace, 43=Tab, 44=Space, 58-67=F1-F10.', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: keyPressSchema, + }), handler: createSessionAwareTool({ internalSchema: keyPressSchema as unknown as z.ZodType, logicFunction: (params: KeyPressParams, executor: CommandExecutor) => diff --git a/src/mcp/tools/ui-testing/key_sequence.ts b/src/mcp/tools/ui-testing/key_sequence.ts index 13da624f..8ca95989 100644 --- a/src/mcp/tools/ui-testing/key_sequence.ts +++ b/src/mcp/tools/ui-testing/key_sequence.ts @@ -21,7 +21,10 @@ import { getAxePath, getBundledAxeEnvironment, } from '../../../utils/axe/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const keySequenceSchema = z.object({ @@ -92,7 +95,10 @@ const publicSchemaObject = keySequenceSchema.omit({ simulatorId: true } as const export default { name: 'key_sequence', description: 'Press key sequence using HID keycodes on iOS simulator with configurable delay', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: keySequenceSchema, + }), handler: createSessionAwareTool({ internalSchema: keySequenceSchema as unknown as z.ZodType, logicFunction: (params: KeySequenceParams, executor: CommandExecutor) => diff --git a/src/mcp/tools/ui-testing/long_press.ts b/src/mcp/tools/ui-testing/long_press.ts index 335324c9..9c47e931 100644 --- a/src/mcp/tools/ui-testing/long_press.ts +++ b/src/mcp/tools/ui-testing/long_press.ts @@ -22,7 +22,10 @@ import { getAxePath, getBundledAxeEnvironment, } from '../../../utils/axe/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const longPressSchema = z.object({ @@ -112,7 +115,10 @@ export default { name: 'long_press', description: "Long press at specific coordinates for given duration (ms). Use describe_ui for precise coordinates (don't guess from screenshots).", - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: longPressSchema, + }), handler: createSessionAwareTool({ internalSchema: longPressSchema as unknown as z.ZodType, logicFunction: (params: LongPressParams, executor: CommandExecutor) => diff --git a/src/mcp/tools/ui-testing/screenshot.ts b/src/mcp/tools/ui-testing/screenshot.ts index f31689c8..7758f8dd 100644 --- a/src/mcp/tools/ui-testing/screenshot.ts +++ b/src/mcp/tools/ui-testing/screenshot.ts @@ -13,7 +13,10 @@ import { getDefaultFileSystemExecutor, getDefaultCommandExecutor, } from '../../../utils/execution/index.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; const LOG_PREFIX = '[Screenshot]'; @@ -143,7 +146,10 @@ export default { name: 'screenshot', description: "Captures screenshot for visual verification. For UI coordinates, use describe_ui instead (don't determine coordinates from screenshots).", - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: screenshotSchema, + }), handler: createSessionAwareTool({ internalSchema: screenshotSchema as unknown as z.ZodType, logicFunction: (params: ScreenshotParams, executor: CommandExecutor) => { diff --git a/src/mcp/tools/ui-testing/swipe.ts b/src/mcp/tools/ui-testing/swipe.ts index 7344d627..0434c0ac 100644 --- a/src/mcp/tools/ui-testing/swipe.ts +++ b/src/mcp/tools/ui-testing/swipe.ts @@ -16,7 +16,10 @@ import { getAxePath, getBundledAxeEnvironment, } from '../../../utils/axe-helpers.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const swipeSchema = z.object({ @@ -123,7 +126,10 @@ export default { name: 'swipe', description: "Swipe from one point to another. Use describe_ui for precise coordinates (don't guess from screenshots). Supports configurable timing.", - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: swipeSchema, + }), handler: createSessionAwareTool({ internalSchema: swipeSchema as unknown as z.ZodType, logicFunction: (params: SwipeParams, executor: CommandExecutor) => diff --git a/src/mcp/tools/ui-testing/tap.ts b/src/mcp/tools/ui-testing/tap.ts index ac008442..693aebfb 100644 --- a/src/mcp/tools/ui-testing/tap.ts +++ b/src/mcp/tools/ui-testing/tap.ts @@ -10,7 +10,10 @@ import { getBundledAxeEnvironment, } from '../../../utils/axe-helpers.ts'; import { DependencyError, AxeError, SystemError } from '../../../utils/errors.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; export interface AxeHelpers { getAxePath: () => string | null; @@ -86,8 +89,9 @@ export async function tapLogic( } return createTextResponse(message); - } catch (error) { - log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); + log('error', `${LOG_PREFIX}/${toolName}: Failed - ${errorMessage}`); if (error instanceof DependencyError) { return axeHelpers.createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { @@ -111,7 +115,10 @@ export default { name: 'tap', description: "Tap at specific coordinates. Use describe_ui to get precise element coordinates (don't guess from screenshots). Supports optional timing delays.", - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: tapSchema, + }), handler: createSessionAwareTool({ internalSchema: tapSchema as unknown as z.ZodType, logicFunction: (params: TapParams, executor: CommandExecutor) => @@ -169,7 +176,7 @@ async function executeAxeCommand( } // Function now returns void - the calling code creates its own response - } catch (error) { + } catch (error: unknown) { if (error instanceof Error) { if (error instanceof AxeError) { throw error; diff --git a/src/mcp/tools/ui-testing/touch.ts b/src/mcp/tools/ui-testing/touch.ts index 2560e0ba..cf6834ce 100644 --- a/src/mcp/tools/ui-testing/touch.ts +++ b/src/mcp/tools/ui-testing/touch.ts @@ -17,7 +17,10 @@ import { getBundledAxeEnvironment, } from '../../../utils/axe-helpers.ts'; import { ToolResponse } from '../../../types/common.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const touchSchema = z.object({ @@ -113,7 +116,10 @@ export default { name: 'touch', description: "Perform touch down/up events at specific coordinates. Use describe_ui for precise coordinates (don't guess from screenshots).", - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: touchSchema, + }), handler: createSessionAwareTool({ internalSchema: touchSchema as unknown as z.ZodType, logicFunction: (params: TouchParams, executor: CommandExecutor) => touchLogic(params, executor), diff --git a/src/mcp/tools/ui-testing/type_text.ts b/src/mcp/tools/ui-testing/type_text.ts index f4ea6213..4a2f4f1e 100644 --- a/src/mcp/tools/ui-testing/type_text.ts +++ b/src/mcp/tools/ui-testing/type_text.ts @@ -17,7 +17,10 @@ import { getAxePath, getBundledAxeEnvironment, } from '../../../utils/axe-helpers.ts'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; const LOG_PREFIX = '[AXe]'; @@ -85,7 +88,10 @@ export default { name: 'type_text', description: 'Type text (supports US keyboard characters). Use describe_ui to find text field, tap to focus, then type.', - schema: publicSchemaObject.shape, // MCP SDK compatibility + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: typeTextSchema, + }), handler: createSessionAwareTool({ internalSchema: typeTextSchema as unknown as z.ZodType, logicFunction: (params: TypeTextParams, executor: CommandExecutor) => diff --git a/src/mcp/tools/utilities/clean.ts b/src/mcp/tools/utilities/clean.ts index 062319a4..f59b288b 100644 --- a/src/mcp/tools/utilities/clean.ts +++ b/src/mcp/tools/utilities/clean.ts @@ -6,7 +6,10 @@ */ import { z } from 'zod'; -import { createSessionAwareTool } from '../../../utils/typed-tool-factory.ts'; +import { + createSessionAwareTool, + getSessionAwareToolSchemaShape, +} from '../../../utils/typed-tool-factory.ts'; import type { CommandExecutor } from '../../../utils/execution/index.ts'; import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts'; import { executeXcodeBuildCommand } from '../../../utils/build/index.ts'; @@ -156,7 +159,10 @@ const publicSchemaObject = baseSchemaObject.omit({ export default { name: 'clean', description: 'Cleans build products with xcodebuild.', - schema: publicSchemaObject.shape, + schema: getSessionAwareToolSchemaShape({ + sessionAware: publicSchemaObject, + legacy: baseSchemaObject, + }), handler: createSessionAwareTool({ internalSchema: cleanSchema as unknown as z.ZodType, logicFunction: cleanLogic, diff --git a/src/utils/typed-tool-factory.ts b/src/utils/typed-tool-factory.ts index 995547b6..18ca54a9 100644 --- a/src/utils/typed-tool-factory.ts +++ b/src/utils/typed-tool-factory.ts @@ -76,7 +76,7 @@ function formatRequirementError(opts: { message: string; setHint?: string; optOutEnabled: boolean; -}) { +}): { title: string; body: string } { const title = opts.optOutEnabled ? 'Missing required parameters' : 'Missing required session defaults'; From f97c8660edd4a999460ade43810bbdeadff13641 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Mon, 15 Dec 2025 14:34:47 +0000 Subject: [PATCH 4/6] Improve session-defaults schema desc --- src/mcp/tools/session-management/session_set_defaults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/tools/session-management/session_set_defaults.ts b/src/mcp/tools/session-management/session_set_defaults.ts index 5b9d6c86..220a8886 100644 --- a/src/mcp/tools/session-management/session_set_defaults.ts +++ b/src/mcp/tools/session-management/session_set_defaults.ts @@ -51,7 +51,7 @@ export async function sessionSetDefaultsLogic(params: Params): Promise Date: Mon, 15 Dec 2025 14:46:41 +0000 Subject: [PATCH 5/6] Update README --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27f36a29..a3902658 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ A Model Context Protocol (MCP) server that provides Xcode-related tools for inte - [MCP Resources](#mcp-resources) - [Getting started](#getting-started) - [Prerequisites](#prerequisites) - - [Configure your MCP client](#configure-your-mcp-client) - [One click install](#one-click-install) - [General installation](#general-installation) - [Specific client installation instructions](#specific-client-installation-instructions) @@ -33,6 +32,7 @@ A Model Context Protocol (MCP) server that provides Xcode-related tools for inte - [Usage Example](#usage-example) - [Client Compatibility](#client-compatibility) - [Selective Workflow Loading (Static Mode)](#selective-workflow-loading-static-mode) +- [Session-aware opt-out](#session-aware-opt-out) - [Code Signing for Device Deployment](#code-signing-for-device-deployment) - [Troubleshooting](#troubleshooting) - [Doctor Tool](#doctor-tool) @@ -291,6 +291,24 @@ For clients that don't support MCP Sampling but still want to reduce context win > [!NOTE] > The `XCODEBUILDMCP_ENABLED_WORKFLOWS` setting only works in Static Mode. If `XCODEBUILDMCP_DYNAMIC_TOOLS=true` is set, the selective workflow setting will be ignored. +## Session-aware opt-out + +By default, XcodeBuildMCP uses a session-aware mode: the LLM (or client) sets shared defaults once (simulator, device, project/workspace, scheme, etc.), and all tools reuse them—similar to choosing a scheme and simulator in Xcode’s UI so you don’t repeat them on every action. This cuts context bloat not just in each call payload, but also in the tool schemas themselves (those parameters don’t have to be described on every tool). + +If you prefer the older, explicit style where each tool requires its own parameters, set `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS=true`. This restores the legacy schemas with per-call parameters while still honoring any session defaults you choose to set. + +Example MCP client configuration: +```json +"XcodeBuildMCP": { + ... + "env": { + "XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS": "true" + } +} +``` + +Leave this unset for the streamlined session-aware experience; enable it to force explicit parameters on each tool call. + ## Code Signing for Device Deployment For device deployment features to work, code signing must be properly configured in Xcode **before** using XcodeBuildMCP device tools: From 2086d3c797ab0e07a9f3da20bd45dfccd7b81b9c Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Mon, 15 Dec 2025 14:52:42 +0000 Subject: [PATCH 6/6] Fix failing test --- .../session-management/__tests__/session_set_defaults.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/tools/session-management/__tests__/session_set_defaults.test.ts b/src/mcp/tools/session-management/__tests__/session_set_defaults.test.ts index 215d638b..df9ef581 100644 --- a/src/mcp/tools/session-management/__tests__/session_set_defaults.test.ts +++ b/src/mcp/tools/session-management/__tests__/session_set_defaults.test.ts @@ -14,7 +14,7 @@ describe('session-set-defaults tool', () => { it('should have correct description', () => { expect(plugin.description).toBe( - 'Set the session defaults needed by many tools. Most tools require one or more session defaults to be set before they can be used. Agents should set the relevant defaults at the beginning of a session.', + 'Set the session defaults needed by many tools. Most tools require one or more session defaults to be set before they can be used. Agents should set all relevant defaults up front in a single call (e.g., project/workspace, scheme, simulator or device ID, useLatestOS) to avoid iterative prompts; only set the keys your workflow needs.', ); });