From de741bc374be231a708c2f7cdeaa6144ea11e941 Mon Sep 17 00:00:00 2001 From: Kamal Fariz Mahyuddin Date: Fri, 6 Feb 2026 10:05:53 -0800 Subject: [PATCH 1/2] Add env parameter to XcodeBuildMCP launch tools for passing environment variables --- .../__tests__/launch_app_device.test.ts | 63 ++++++++++++- src/mcp/tools/device/launch_app_device.ts | 39 +++++--- .../__tests__/launch_app_logs_sim.test.ts | 64 ++++++++++++- .../__tests__/launch_app_sim.test.ts | 89 ++++++++++++++++++- .../tools/simulator/launch_app_logs_sim.ts | 14 +++ src/mcp/tools/simulator/launch_app_sim.ts | 16 +++- src/utils/__tests__/log_capture.test.ts | 48 ++++++++++ src/utils/__tests__/simctl-child-env.test.ts | 34 +++++++ src/utils/environment.ts | 18 ++++ src/utils/log_capture.ts | 6 +- 10 files changed, 373 insertions(+), 18 deletions(-) create mode 100644 src/utils/__tests__/simctl-child-env.test.ts diff --git a/src/mcp/tools/device/__tests__/launch_app_device.test.ts b/src/mcp/tools/device/__tests__/launch_app_device.test.ts index 958e1546..a0787fb9 100644 --- a/src/mcp/tools/device/__tests__/launch_app_device.test.ts +++ b/src/mcp/tools/device/__tests__/launch_app_device.test.ts @@ -30,7 +30,7 @@ describe('launch_app_device plugin (device-shared)', () => { const schemaObj = z.strictObject(schema); expect(schemaObj.safeParse({}).success).toBe(true); expect(schemaObj.safeParse({ bundleId: 'com.example.app' }).success).toBe(false); - expect(Object.keys(schema)).toEqual([]); + expect(Object.keys(schema).sort()).toEqual(['env']); }); it('should validate schema with invalid inputs', () => { @@ -134,6 +134,67 @@ describe('launch_app_device plugin (device-shared)', () => { 'com.apple.mobilesafari', ]); }); + + it('should append --environment-variables flags before bundleId when env is provided', async () => { + const calls: any[] = []; + const mockExecutor = createMockExecutor({ + success: true, + output: 'App launched successfully', + process: { pid: 12345 }, + }); + + const trackingExecutor = async (command: string[]) => { + calls.push({ command }); + return mockExecutor(command); + }; + + await launch_app_deviceLogic( + { + deviceId: 'test-device-123', + bundleId: 'com.example.app', + env: { STAGING_ENABLED: '1', DEBUG: 'true' }, + }, + trackingExecutor, + createMockFileSystemExecutor(), + ); + + expect(calls).toHaveLength(1); + const cmd = calls[0].command; + // bundleId should be the last element + expect(cmd[cmd.length - 1]).toBe('com.example.app'); + // --environment-variables flags should appear before bundleId + const envIdx1 = cmd.indexOf('--environment-variables'); + expect(envIdx1).toBeGreaterThan(-1); + expect(cmd[envIdx1 + 1]).toBe('STAGING_ENABLED=1'); + const envIdx2 = cmd.indexOf('--environment-variables', envIdx1 + 1); + expect(envIdx2).toBeGreaterThan(-1); + expect(cmd[envIdx2 + 1]).toBe('DEBUG=true'); + }); + + it('should not include --environment-variables when env is not provided', async () => { + const calls: any[] = []; + const mockExecutor = createMockExecutor({ + success: true, + output: 'App launched successfully', + process: { pid: 12345 }, + }); + + const trackingExecutor = async (command: string[]) => { + calls.push({ command }); + return mockExecutor(command); + }; + + await launch_app_deviceLogic( + { + deviceId: 'test-device-123', + bundleId: 'com.example.app', + }, + trackingExecutor, + createMockFileSystemExecutor(), + ); + + expect(calls[0].command).not.toContain('--environment-variables'); + }); }); describe('Success Path Tests', () => { diff --git a/src/mcp/tools/device/launch_app_device.ts b/src/mcp/tools/device/launch_app_device.ts index 987629d4..dce7e1f1 100644 --- a/src/mcp/tools/device/launch_app_device.ts +++ b/src/mcp/tools/device/launch_app_device.ts @@ -32,6 +32,10 @@ type LaunchDataResponse = { const launchAppDeviceSchema = z.object({ deviceId: z.string().describe('UDID of the device (obtained from list_devices)'), bundleId: z.string(), + env: z + .record(z.string(), z.string()) + .optional() + .describe('Environment variables to pass to the launched app (as KEY=VALUE pairs)'), }); const publicSchemaObject = launchAppDeviceSchema.omit({ @@ -55,20 +59,29 @@ export async function launch_app_deviceLogic( // Use JSON output to capture process ID const tempJsonPath = join(fileSystem.tmpdir(), `launch-${Date.now()}.json`); + const command = [ + 'xcrun', + 'devicectl', + 'device', + 'process', + 'launch', + '--device', + deviceId, + '--json-output', + tempJsonPath, + '--terminate-existing', + ]; + + if (params.env) { + for (const [key, value] of Object.entries(params.env)) { + command.push('--environment-variables', `${key}=${value}`); + } + } + + command.push(bundleId); + const result = await executor( - [ - 'xcrun', - 'devicectl', - 'device', - 'process', - 'launch', - '--device', - deviceId, - '--json-output', - tempJsonPath, - '--terminate-existing', - bundleId, - ], + command, 'Launch app on device', false, // useShell undefined, // env diff --git a/src/mcp/tools/simulator/__tests__/launch_app_logs_sim.test.ts b/src/mcp/tools/simulator/__tests__/launch_app_logs_sim.test.ts index c67ccfcc..b37f26f2 100644 --- a/src/mcp/tools/simulator/__tests__/launch_app_logs_sim.test.ts +++ b/src/mcp/tools/simulator/__tests__/launch_app_logs_sim.test.ts @@ -28,7 +28,7 @@ describe('launch_app_logs_sim tool', () => { expect(schemaObj.safeParse({ bundleId: 'com.example.app' }).success).toBe(true); expect(schemaObj.safeParse({ bundleId: 42 }).success).toBe(true); - expect(Object.keys(schema).sort()).toEqual(['args']); + expect(Object.keys(schema).sort()).toEqual(['args', 'env']); const withSimId = schemaObj.safeParse({ simulatorId: 'abc123', @@ -140,6 +140,68 @@ describe('launch_app_logs_sim tool', () => { }); }); + it('should pass env vars through to log capture function', async () => { + let capturedParams: unknown = null; + const logCaptureStub: LogCaptureFunction = async (params) => { + capturedParams = params; + return { + sessionId: 'test-session-789', + logFilePath: '/tmp/xcodemcp_sim_log_test-session-789.log', + processes: [], + error: undefined, + }; + }; + + const mockExecutor = createMockExecutor({ success: true, output: '' }); + + await launch_app_logs_simLogic( + { + simulatorId: 'test-uuid-123', + bundleId: 'com.example.testapp', + env: { STAGING_ENABLED: '1' }, + }, + mockExecutor, + logCaptureStub, + ); + + expect(capturedParams).toEqual({ + simulatorUuid: 'test-uuid-123', + bundleId: 'com.example.testapp', + captureConsole: true, + env: { STAGING_ENABLED: '1' }, + }); + }); + + it('should not include env in capture params when env is undefined', async () => { + let capturedParams: unknown = null; + const logCaptureStub: LogCaptureFunction = async (params) => { + capturedParams = params; + return { + sessionId: 'test-session-101', + logFilePath: '/tmp/xcodemcp_sim_log_test-session-101.log', + processes: [], + error: undefined, + }; + }; + + const mockExecutor = createMockExecutor({ success: true, output: '' }); + + await launch_app_logs_simLogic( + { + simulatorId: 'test-uuid-123', + bundleId: 'com.example.testapp', + }, + mockExecutor, + logCaptureStub, + ); + + expect(capturedParams).toEqual({ + simulatorUuid: 'test-uuid-123', + bundleId: 'com.example.testapp', + captureConsole: true, + }); + }); + it('should surface log capture failure', async () => { const logCaptureStub: LogCaptureFunction = async () => ({ sessionId: '', diff --git a/src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts b/src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts index 67162fcc..f79c395a 100644 --- a/src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts +++ b/src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts @@ -24,7 +24,7 @@ describe('launch_app_sim tool', () => { expect(schemaObj.safeParse({ bundleId: 'com.example.testapp' }).success).toBe(false); expect(schemaObj.safeParse({ bundleId: 123 }).success).toBe(false); - expect(Object.keys(schema).sort()).toEqual(['args']); + expect(Object.keys(schema).sort()).toEqual(['args', 'env']); const withSimDefaults = schemaObj.safeParse({ simulatorId: 'sim-default', @@ -346,5 +346,92 @@ describe('launch_app_sim tool', () => { ], }); }); + + it('should pass env vars with SIMCTL_CHILD_ prefix to executor opts', async () => { + let callCount = 0; + const capturedOpts: (Record | undefined)[] = []; + + const sequencedExecutor = async ( + command: string[], + _logPrefix?: string, + _useShell?: boolean, + opts?: { env?: Record }, + ) => { + callCount++; + capturedOpts.push(opts); + if (callCount === 1) { + return { + success: true, + output: '/path/to/app/container', + error: '', + process: {} as any, + }; + } + return { + success: true, + output: 'App launched successfully', + error: '', + process: {} as any, + }; + }; + + await launch_app_simLogic( + { + simulatorId: 'test-uuid-123', + bundleId: 'com.example.testapp', + env: { STAGING_ENABLED: '1', DEBUG: 'true' }, + }, + sequencedExecutor, + ); + + // First call is get_app_container (no env), second is launch (with env) + expect(capturedOpts[1]).toEqual({ + env: { + SIMCTL_CHILD_STAGING_ENABLED: '1', + SIMCTL_CHILD_DEBUG: 'true', + }, + }); + }); + + it('should not pass env opts when env is undefined', async () => { + let callCount = 0; + const capturedOpts: (Record | undefined)[] = []; + + const sequencedExecutor = async ( + command: string[], + _logPrefix?: string, + _useShell?: boolean, + opts?: { env?: Record }, + ) => { + callCount++; + capturedOpts.push(opts); + if (callCount === 1) { + return { + success: true, + output: '/path/to/app/container', + error: '', + process: {} as any, + }; + } + return { + success: true, + output: 'App launched successfully', + error: '', + process: {} as any, + }; + }; + + await launch_app_simLogic( + { + simulatorId: 'test-uuid-123', + bundleId: 'com.example.testapp', + }, + sequencedExecutor, + ); + + // Launch call opts should be undefined when no env provided + expect(capturedOpts[1]).toBeUndefined(); + }); + }); }); diff --git a/src/mcp/tools/simulator/launch_app_logs_sim.ts b/src/mcp/tools/simulator/launch_app_logs_sim.ts index be5732aa..47d599f1 100644 --- a/src/mcp/tools/simulator/launch_app_logs_sim.ts +++ b/src/mcp/tools/simulator/launch_app_logs_sim.ts @@ -16,6 +16,7 @@ export type LogCaptureFunction = ( bundleId: string; captureConsole?: boolean; args?: string[]; + env?: Record; }, executor: CommandExecutor, ) => Promise<{ sessionId: string; logFilePath: string; processes: unknown[]; error?: string }>; @@ -35,6 +36,12 @@ const baseSchemaObject = z.object({ ), bundleId: z.string().describe('Bundle identifier of the app to launch'), args: z.array(z.string()).optional().describe('Optional arguments to pass to the app'), + env: z + .record(z.string(), z.string()) + .optional() + .describe( + 'Environment variables to pass to the launched app (SIMCTL_CHILD_ prefix added automatically)', + ), }); // Internal schema requires simulatorId (factory resolves simulatorName → simulatorId) @@ -43,6 +50,12 @@ const internalSchemaObject = z.object({ simulatorName: z.string().optional(), bundleId: z.string(), args: z.array(z.string()).optional(), + env: z + .record(z.string(), z.string()) + .optional() + .describe( + 'Environment variables to pass to the launched app (SIMCTL_CHILD_ prefix added automatically)', + ), }); type LaunchAppLogsSimParams = z.infer; @@ -67,6 +80,7 @@ export async function launch_app_logs_simLogic( bundleId: params.bundleId, captureConsole: true, ...(params.args && params.args.length > 0 ? { args: params.args } : {}), + ...(params.env ? { env: params.env } : {}), } as const; const { sessionId, error } = await logCaptureFunction(captureParams, executor); diff --git a/src/mcp/tools/simulator/launch_app_sim.ts b/src/mcp/tools/simulator/launch_app_sim.ts index d24af0e5..0207e2c4 100644 --- a/src/mcp/tools/simulator/launch_app_sim.ts +++ b/src/mcp/tools/simulator/launch_app_sim.ts @@ -7,6 +7,7 @@ import { createSessionAwareTool, getSessionAwareToolSchemaShape, } from '../../../utils/typed-tool-factory.ts'; +import { normalizeSimctlChildEnv } from '../../../utils/environment.ts'; const baseSchemaObject = z.object({ simulatorId: z @@ -23,6 +24,12 @@ const baseSchemaObject = z.object({ ), bundleId: z.string().describe('Bundle identifier of the app to launch'), args: z.array(z.string()).optional().describe('Optional arguments to pass to the app'), + env: z + .record(z.string(), z.string()) + .optional() + .describe( + 'Environment variables to pass to the launched app (SIMCTL_CHILD_ prefix added automatically)', + ), }); // Internal schema requires simulatorId (factory resolves simulatorName → simulatorId) @@ -31,6 +38,12 @@ const internalSchemaObject = z.object({ simulatorName: z.string().optional(), bundleId: z.string(), args: z.array(z.string()).optional(), + env: z + .record(z.string(), z.string()) + .optional() + .describe( + 'Environment variables to pass to the launched app (SIMCTL_CHILD_ prefix added automatically)', + ), }); export type LaunchAppSimParams = z.infer; @@ -90,7 +103,8 @@ export async function launch_app_simLogic( command.push(...params.args); } - const result = await executor(command, 'Launch App in Simulator', false, undefined); + const execOpts = params.env ? { env: normalizeSimctlChildEnv(params.env) } : undefined; + const result = await executor(command, 'Launch App in Simulator', false, execOpts); if (!result.success) { return { diff --git a/src/utils/__tests__/log_capture.test.ts b/src/utils/__tests__/log_capture.test.ts index a95c8466..88761e05 100644 --- a/src/utils/__tests__/log_capture.test.ts +++ b/src/utils/__tests__/log_capture.test.ts @@ -263,6 +263,54 @@ describe('startLogCapture', () => { expect(callHistory[1].useShell).toBe(false); expect(callHistory[1].detached).toBe(true); }); + + it('passes SIMCTL_CHILD_-prefixed env to console launch executor when env is provided', async () => { + const callHistory: CallHistoryEntry[] = []; + const executor = createMockExecutorWithCalls(callHistory); + const fileSystem = createInMemoryFileSystemExecutor(); + + const result = await startLogCapture( + { + simulatorUuid: 'sim-uuid', + bundleId: 'com.example.app', + captureConsole: true, + env: { STAGING_ENABLED: '1', DEBUG: 'true' }, + }, + executor, + fileSystem, + ); + + expect(result.error).toBeUndefined(); + expect(callHistory).toHaveLength(2); + // Console launch call should have prefixed env + expect(callHistory[0].opts).toEqual({ + env: { + SIMCTL_CHILD_STAGING_ENABLED: '1', + SIMCTL_CHILD_DEBUG: 'true', + }, + }); + // OS log stream call should not have env + expect(callHistory[1].opts).toBeUndefined(); + }); + + it('does not pass env opts to executor when env is not provided', async () => { + const callHistory: CallHistoryEntry[] = []; + const executor = createMockExecutorWithCalls(callHistory); + const fileSystem = createInMemoryFileSystemExecutor(); + + const result = await startLogCapture( + { + simulatorUuid: 'sim-uuid', + bundleId: 'com.example.app', + captureConsole: true, + }, + executor, + fileSystem, + ); + + expect(result.error).toBeUndefined(); + expect(callHistory[0].opts).toBeUndefined(); + }); }); describe('stopLogCapture', () => { diff --git a/src/utils/__tests__/simctl-child-env.test.ts b/src/utils/__tests__/simctl-child-env.test.ts new file mode 100644 index 00000000..aa657a8c --- /dev/null +++ b/src/utils/__tests__/simctl-child-env.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import { normalizeSimctlChildEnv } from '../environment.ts'; + +describe('normalizeSimctlChildEnv', () => { + it('should prefix unprefixed keys with SIMCTL_CHILD_', () => { + const result = normalizeSimctlChildEnv({ FOO: '1', BAR: '2' }); + expect(result).toEqual({ SIMCTL_CHILD_FOO: '1', SIMCTL_CHILD_BAR: '2' }); + }); + + it('should preserve already-prefixed keys', () => { + const result = normalizeSimctlChildEnv({ SIMCTL_CHILD_FOO: '1' }); + expect(result).toEqual({ SIMCTL_CHILD_FOO: '1' }); + }); + + it('should handle a mix of prefixed and unprefixed keys', () => { + const result = normalizeSimctlChildEnv({ FOO: '1', SIMCTL_CHILD_BAR: '2' }); + expect(result).toEqual({ SIMCTL_CHILD_FOO: '1', SIMCTL_CHILD_BAR: '2' }); + }); + + it('should filter null and undefined values', () => { + const input = { FOO: '1', BAR: null, BAZ: undefined } as unknown as Record; + const result = normalizeSimctlChildEnv(input); + expect(result).toEqual({ SIMCTL_CHILD_FOO: '1' }); + }); + + it('should return empty object for empty input', () => { + expect(normalizeSimctlChildEnv({})).toEqual({}); + }); + + it('should return empty object for null/undefined input', () => { + expect(normalizeSimctlChildEnv(null as unknown as Record)).toEqual({}); + expect(normalizeSimctlChildEnv(undefined as unknown as Record)).toEqual({}); + }); +}); diff --git a/src/utils/environment.ts b/src/utils/environment.ts index f85021f0..82aef678 100644 --- a/src/utils/environment.ts +++ b/src/utils/environment.ts @@ -98,3 +98,21 @@ export function normalizeTestRunnerEnv(vars: Record): Record { SIMCTL_CHILD_FOO: '1', SIMCTL_CHILD_BAR: '2' } + */ +export function normalizeSimctlChildEnv(vars: Record): Record { + const normalized: Record = {}; + for (const [key, value] of Object.entries(vars ?? {})) { + if (value == null) continue; + const prefixedKey = key.startsWith('SIMCTL_CHILD_') ? key : `SIMCTL_CHILD_${key}`; + normalized[prefixedKey] = value; + } + return normalized; +} diff --git a/src/utils/log_capture.ts b/src/utils/log_capture.ts index 3cf3d585..3e32c58d 100644 --- a/src/utils/log_capture.ts +++ b/src/utils/log_capture.ts @@ -6,6 +6,7 @@ import { v4 as uuidv4 } from 'uuid'; import { log } from '../utils/logger.ts'; import type { CommandExecutor } from './command.ts'; import { getDefaultCommandExecutor, getDefaultFileSystemExecutor } from './command.ts'; +import { normalizeSimctlChildEnv } from './environment.ts'; import type { FileSystemExecutor } from './FileSystemExecutor.ts'; import { acquireDaemonActivity } from '../daemon/activity-registry.ts'; @@ -71,6 +72,7 @@ export async function startLogCapture( bundleId: string; captureConsole?: boolean; args?: string[]; + env?: Record; subsystemFilter?: SubsystemFilter; }, executor: CommandExecutor = getDefaultCommandExecutor(), @@ -84,6 +86,7 @@ export async function startLogCapture( bundleId, captureConsole = false, args = [], + env, subsystemFilter = 'app', } = params; const logSessionId = uuidv4(); @@ -143,11 +146,12 @@ export async function startLogCapture( launchCommand.push(...args); } + const launchOpts = env ? { env: normalizeSimctlChildEnv(env) } : undefined; const stdoutLogResult = await executor( launchCommand, 'Console Log Capture', false, // useShell - undefined, // env + launchOpts, // env true, // detached - don't wait for this streaming process to complete ); From 976543038f9050d5ebfbc4d2e0441912eca0f47f Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Sat, 7 Feb 2026 23:33:24 +0000 Subject: [PATCH 2/2] Fix env handling in launch tools and tests --- .../device/__tests__/launch_app_device.test.ts | 16 ++++++++-------- src/mcp/tools/device/launch_app_device.ts | 13 +++++-------- .../__tests__/launch_app_logs_sim.test.ts | 11 +++++------ .../simulator/__tests__/launch_app_sim.test.ts | 1 - src/mcp/tools/simulator/launch_app_logs_sim.ts | 2 +- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/mcp/tools/device/__tests__/launch_app_device.test.ts b/src/mcp/tools/device/__tests__/launch_app_device.test.ts index a0787fb9..c22e190a 100644 --- a/src/mcp/tools/device/__tests__/launch_app_device.test.ts +++ b/src/mcp/tools/device/__tests__/launch_app_device.test.ts @@ -135,7 +135,7 @@ describe('launch_app_device plugin (device-shared)', () => { ]); }); - it('should append --environment-variables flags before bundleId when env is provided', async () => { + it('should append a JSON --environment-variables payload before bundleId when env is provided', async () => { const calls: any[] = []; const mockExecutor = createMockExecutor({ success: true, @@ -162,13 +162,13 @@ describe('launch_app_device plugin (device-shared)', () => { const cmd = calls[0].command; // bundleId should be the last element expect(cmd[cmd.length - 1]).toBe('com.example.app'); - // --environment-variables flags should appear before bundleId - const envIdx1 = cmd.indexOf('--environment-variables'); - expect(envIdx1).toBeGreaterThan(-1); - expect(cmd[envIdx1 + 1]).toBe('STAGING_ENABLED=1'); - const envIdx2 = cmd.indexOf('--environment-variables', envIdx1 + 1); - expect(envIdx2).toBeGreaterThan(-1); - expect(cmd[envIdx2 + 1]).toBe('DEBUG=true'); + // --environment-variables should be provided exactly once as JSON + const envFlagIndices = cmd + .map((part: string, index: number) => (part === '--environment-variables' ? index : -1)) + .filter((index: number) => index >= 0); + expect(envFlagIndices).toHaveLength(1); + const envIdx = envFlagIndices[0]; + expect(JSON.parse(cmd[envIdx + 1])).toEqual({ STAGING_ENABLED: '1', DEBUG: 'true' }); }); it('should not include --environment-variables when env is not provided', async () => { diff --git a/src/mcp/tools/device/launch_app_device.ts b/src/mcp/tools/device/launch_app_device.ts index dce7e1f1..7d03d489 100644 --- a/src/mcp/tools/device/launch_app_device.ts +++ b/src/mcp/tools/device/launch_app_device.ts @@ -35,7 +35,7 @@ const launchAppDeviceSchema = z.object({ env: z .record(z.string(), z.string()) .optional() - .describe('Environment variables to pass to the launched app (as KEY=VALUE pairs)'), + .describe('Environment variables to pass to the launched app (as key-value dictionary)'), }); const publicSchemaObject = launchAppDeviceSchema.omit({ @@ -72,10 +72,8 @@ export async function launch_app_deviceLogic( '--terminate-existing', ]; - if (params.env) { - for (const [key, value] of Object.entries(params.env)) { - command.push('--environment-variables', `${key}=${value}`); - } + if (params.env && Object.keys(params.env).length > 0) { + command.push('--environment-variables', JSON.stringify(params.env)); } command.push(bundleId); @@ -121,11 +119,10 @@ export async function launch_app_deviceLogic( const launchData = parsedData as LaunchDataResponse; processId = launchData.result?.process?.processIdentifier; } - - // Clean up temp file - await fileSystem.rm(tempJsonPath, { force: true }).catch(() => {}); } catch (error) { log('warn', `Failed to parse launch JSON output: ${error}`); + } finally { + await fileSystem.rm(tempJsonPath, { force: true }).catch(() => {}); } let responseText = `✅ App launched successfully\n\n${result.output}`; diff --git a/src/mcp/tools/simulator/__tests__/launch_app_logs_sim.test.ts b/src/mcp/tools/simulator/__tests__/launch_app_logs_sim.test.ts index b37f26f2..1c2e4b29 100644 --- a/src/mcp/tools/simulator/__tests__/launch_app_logs_sim.test.ts +++ b/src/mcp/tools/simulator/__tests__/launch_app_logs_sim.test.ts @@ -21,20 +21,19 @@ describe('launch_app_logs_sim tool', () => { describe('Export Field Validation (Literal)', () => { it('should expose only non-session fields in public schema', () => { - const schemaObj = z.object(schema); + const schemaObj = z.strictObject(schema); expect(schemaObj.safeParse({}).success).toBe(true); expect(schemaObj.safeParse({ args: ['--debug'] }).success).toBe(true); - expect(schemaObj.safeParse({ bundleId: 'com.example.app' }).success).toBe(true); - expect(schemaObj.safeParse({ bundleId: 42 }).success).toBe(true); + expect(schemaObj.safeParse({ bundleId: 'com.example.app' }).success).toBe(false); + expect(schemaObj.safeParse({ bundleId: 42 }).success).toBe(false); expect(Object.keys(schema).sort()).toEqual(['args', 'env']); const withSimId = schemaObj.safeParse({ simulatorId: 'abc123', }); - expect(withSimId.success).toBe(true); - expect('simulatorId' in (withSimId.data as Record)).toBe(false); + expect(withSimId.success).toBe(false); }); }); @@ -225,7 +224,7 @@ describe('launch_app_logs_sim tool', () => { content: [ { type: 'text', - text: 'App was launched but log capture failed: Failed to start log capture', + text: 'Failed to launch app with log capture: Failed to start log capture', }, ], isError: true, diff --git a/src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts b/src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts index f79c395a..957235b6 100644 --- a/src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts +++ b/src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts @@ -432,6 +432,5 @@ describe('launch_app_sim tool', () => { // Launch call opts should be undefined when no env provided expect(capturedOpts[1]).toBeUndefined(); }); - }); }); diff --git a/src/mcp/tools/simulator/launch_app_logs_sim.ts b/src/mcp/tools/simulator/launch_app_logs_sim.ts index 47d599f1..ec6dea53 100644 --- a/src/mcp/tools/simulator/launch_app_logs_sim.ts +++ b/src/mcp/tools/simulator/launch_app_logs_sim.ts @@ -86,7 +86,7 @@ export async function launch_app_logs_simLogic( const { sessionId, error } = await logCaptureFunction(captureParams, executor); if (error) { return { - content: [createTextContent(`App was launched but log capture failed: ${error}`)], + content: [createTextContent(`Failed to launch app with log capture: ${error}`)], isError: true, }; }