Skip to main content
Glama
axe.ts31.7 kB
/** * UI Automation Tools - Interact with iOS Simulator UI via AXe * * This module provides tools to control and inspect the iOS Simulator UI * using AXe command-line utility. * It assumes AXe is installed and available in the system PATH. */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { ToolResponse } from '../types/common.js'; import { log } from '../utils/logger.js'; import { createTextResponse, validateRequiredParam } from '../utils/validation.js'; import { DependencyError, AxeError, SystemError, createErrorResponse } from '../utils/errors.js'; import { executeCommand } from '../utils/command.js'; import { createAxeNotAvailableResponse } from '../utils/axe-setup.js'; import { areAxeToolsAvailable } from '../utils/axe-setup.js'; const AXE_COMMAND = 'axe'; const LOG_PREFIX = '[AXe]'; // Session tracking for describe_ui warnings interface DescribeUISession { timestamp: number; simulatorUuid: string; } const describeUITimestamps = new Map<string, DescribeUISession>(); const DESCRIBE_UI_WARNING_TIMEOUT = 60000; // 60 seconds function recordDescribeUICall(simulatorUuid: string): void { describeUITimestamps.set(simulatorUuid, { timestamp: Date.now(), simulatorUuid, }); } function getCoordinateWarning(simulatorUuid: string): string | null { const session = describeUITimestamps.get(simulatorUuid); if (!session) { return 'Warning: describe_ui has not been called yet. Consider using describe_ui for precise coordinates instead of guessing from screenshots.'; } const timeSinceDescribe = Date.now() - session.timestamp; if (timeSinceDescribe > DESCRIBE_UI_WARNING_TIMEOUT) { const secondsAgo = Math.round(timeSinceDescribe / 1000); return `Warning: describe_ui was last called ${secondsAgo} seconds ago. Consider refreshing UI coordinates with describe_ui instead of using potentially stale coordinates.`; } return null; } async function executeAxeCommand( commandArgs: string[], simulatorUuid: string, commandName: string, ): Promise<string> { // Add --udid parameter to all commands const fullArgs = [...commandArgs, '--udid', simulatorUuid]; // Construct the full command array with AXE_COMMAND as the first element const fullCommand = [AXE_COMMAND, ...fullArgs]; try { const result = await executeCommand(fullCommand, `${LOG_PREFIX}: ${commandName}`, false); if (!result.success) { throw new AxeError( `axe command '${commandName}' failed.`, commandName, result.error || result.output, simulatorUuid, ); } // Check for stderr output in successful commands if (result.error) { log( 'warn', `${LOG_PREFIX}: Command '${commandName}' produced stderr output but exited successfully. Output: ${result.error}`, ); } return result.output.trim(); } catch (error) { if (error instanceof Error) { if (error instanceof AxeError) { throw error; } // Otherwise wrap it in a SystemError throw new SystemError(`Failed to execute axe command: ${error.message}`, error); } // For any other type of error throw new SystemError(`Failed to execute axe command: ${String(error)}`); } } // --- Registration Function --- /** * Registers all axe-related tools with the dispatcher. * @param server The McpServer instance. */ export function registerAxeTools(server: McpServer): void { // Check if axe is available and log a warning if not if (!areAxeToolsAvailable()) { log('warning', `${LOG_PREFIX}: failed to register axe tools as axe is not available`); return; } // 1. describe_ui server.tool( 'describe_ui', '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.', { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), }, async (params): Promise<ToolResponse> => { const toolName = 'describe_ui'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const { simulatorUuid } = params; const commandArgs = ['describe-ui']; log('info', `${LOG_PREFIX}/${toolName}: Starting for ${simulatorUuid}`); try { const responseText = await executeAxeCommand(commandArgs, simulatorUuid, 'describe-ui'); // Record the describe_ui call for warning system recordDescribeUICall(simulatorUuid); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); return { content: [ { type: 'text', text: 'Accessibility hierarchy retrieved successfully:\n```json\n' + responseText + '\n```', }, { type: 'text', text: `Next Steps: - Use frame coordinates for tap/swipe (center: x+width/2, y+height/2) - Re-run describe_ui after layout changes - Screenshots are for visual verification only`, }, ], }; } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to get accessibility hierarchy: ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); // 2. tap server.tool( 'tap', "Tap at specific coordinates. Use describe_ui to get precise element coordinates (don't guess from screenshots). Supports optional timing delays.", { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), x: z.number().int('X coordinate must be an integer'), y: z.number().int('Y coordinate must be an integer'), preDelay: z.number().min(0, 'Pre-delay must be non-negative').optional(), postDelay: z.number().min(0, 'Post-delay must be non-negative').optional(), }, async (params): Promise<ToolResponse> => { const toolName = 'tap'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const xValidation = validateRequiredParam('x', params.x); if (!xValidation.isValid) return xValidation.errorResponse!; const yValidation = validateRequiredParam('y', params.y); if (!yValidation.isValid) return yValidation.errorResponse!; const { simulatorUuid, x, y, preDelay, postDelay } = params; const commandArgs = ['tap', '-x', String(x), '-y', String(y)]; if (preDelay !== undefined) { commandArgs.push('--pre-delay', String(preDelay)); } if (postDelay !== undefined) { commandArgs.push('--post-delay', String(postDelay)); } log('info', `${LOG_PREFIX}/${toolName}: Starting for (${x}, ${y}) on ${simulatorUuid}`); try { await executeAxeCommand(commandArgs, simulatorUuid, 'tap'); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); const warning = getCoordinateWarning(simulatorUuid); const message = `Tap at (${x}, ${y}) simulated successfully.`; if (warning) { return createTextResponse(`${message}\n\n${warning}`); } return createTextResponse(message); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to simulate tap at (${x}, ${y}): ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); // 3. long_press server.tool( 'long_press', "Long press at specific coordinates for given duration (ms). Use describe_ui for precise coordinates (don't guess from screenshots).", { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), x: z.number().int('X coordinate must be an integer'), y: z.number().int('Y coordinate must be an integer'), duration: z.number().positive('Duration must be a positive number (ms)'), }, async (params): Promise<ToolResponse> => { const toolName = 'long_press'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const xValidation = validateRequiredParam('x', params.x); if (!xValidation.isValid) return xValidation.errorResponse!; const yValidation = validateRequiredParam('y', params.y); if (!yValidation.isValid) return yValidation.errorResponse!; const durationValidation = validateRequiredParam('duration', params.duration); if (!durationValidation.isValid) return durationValidation.errorResponse!; const { simulatorUuid, x, y, duration } = params; // AXe uses touch command with --down, --up, and --delay for long press const delayInSeconds = duration / 1000; // Convert ms to seconds const commandArgs = [ 'touch', '-x', String(x), '-y', String(y), '--down', '--up', '--delay', String(delayInSeconds), ]; log( 'info', `${LOG_PREFIX}/${toolName}: Starting for (${x}, ${y}), ${duration}ms on ${simulatorUuid}`, ); try { await executeAxeCommand(commandArgs, simulatorUuid, 'touch'); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); const warning = getCoordinateWarning(simulatorUuid); const message = `Long press at (${x}, ${y}) for ${duration}ms simulated successfully.`; if (warning) { return createTextResponse(`${message}\n\n${warning}`); } return createTextResponse(message); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to simulate long press at (${x}, ${y}): ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); // 4. swipe server.tool( 'swipe', "Swipe from one point to another. Use describe_ui for precise coordinates (don't guess from screenshots). Supports configurable timing.", { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), x1: z.number().int('Start X coordinate must be an integer'), y1: z.number().int('Start Y coordinate must be an integer'), x2: z.number().int('End X coordinate must be an integer'), y2: z.number().int('End Y coordinate must be an integer'), duration: z.number().min(0, 'Duration must be non-negative').optional(), delta: z.number().min(0, 'Delta must be non-negative').optional(), preDelay: z.number().min(0, 'Pre-delay must be non-negative').optional(), postDelay: z.number().min(0, 'Post-delay must be non-negative').optional(), }, async (params): Promise<ToolResponse> => { const toolName = 'swipe'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const x1Validation = validateRequiredParam('x1', params.x1); if (!x1Validation.isValid) return x1Validation.errorResponse!; const y1Validation = validateRequiredParam('y1', params.y1); if (!y1Validation.isValid) return y1Validation.errorResponse!; const x2Validation = validateRequiredParam('x2', params.x2); if (!x2Validation.isValid) return x2Validation.errorResponse!; const y2Validation = validateRequiredParam('y2', params.y2); if (!y2Validation.isValid) return y2Validation.errorResponse!; const { simulatorUuid, x1, y1, x2, y2, duration, delta, preDelay, postDelay } = params; const commandArgs = [ 'swipe', '--start-x', String(x1), '--start-y', String(y1), '--end-x', String(x2), '--end-y', String(y2), ]; if (duration !== undefined) { commandArgs.push('--duration', String(duration)); } if (delta !== undefined) { commandArgs.push('--delta', String(delta)); } if (preDelay !== undefined) { commandArgs.push('--pre-delay', String(preDelay)); } if (postDelay !== undefined) { commandArgs.push('--post-delay', String(postDelay)); } const optionsText = duration ? ` duration=${duration}s` : ''; log( 'info', `${LOG_PREFIX}/${toolName}: Starting swipe (${x1},${y1})->(${x2},${y2})${optionsText} on ${simulatorUuid}`, ); try { await executeAxeCommand(commandArgs, simulatorUuid, 'swipe'); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); const warning = getCoordinateWarning(simulatorUuid); const message = `Swipe from (${x1}, ${y1}) to (${x2}, ${y2})${optionsText} simulated successfully.`; if (warning) { return createTextResponse(`${message}\n\n${warning}`); } return createTextResponse(message); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to simulate swipe: ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); // 5. type_text server.tool( 'type_text', 'Type text (supports US keyboard characters). Use describe_ui to find text field, tap to focus, then type.', { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), text: z.string().min(1, 'Text cannot be empty'), }, async (params): Promise<ToolResponse> => { const toolName = 'type_text'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const textValidation = validateRequiredParam('text', params.text); if (!textValidation.isValid) return textValidation.errorResponse!; const { simulatorUuid, text } = params; const commandArgs = ['type', text]; log( 'info', `${LOG_PREFIX}/${toolName}: Starting type "${text.substring(0, 20)}..." on ${simulatorUuid}`, ); try { await executeAxeCommand(commandArgs, simulatorUuid, 'type'); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); return createTextResponse(`Text typing simulated successfully.`); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to simulate text typing: ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); // 6. key_press server.tool( 'key_press', 'Press a single key by keycode on the simulator. Common keycodes: 40=Return, 42=Backspace, 43=Tab, 44=Space, 58-67=F1-F10.', { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), keyCode: z.number().int('HID keycode to press (0-255)').min(0).max(255), duration: z.number().min(0, 'Duration must be non-negative').optional(), }, async (params): Promise<ToolResponse> => { const toolName = 'key_press'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const keyCodeValidation = validateRequiredParam('keyCode', params.keyCode); if (!keyCodeValidation.isValid) return keyCodeValidation.errorResponse!; const { simulatorUuid, keyCode, duration } = params; const commandArgs = ['key', String(keyCode)]; if (duration !== undefined) { commandArgs.push('--duration', String(duration)); } log('info', `${LOG_PREFIX}/${toolName}: Starting key press ${keyCode} on ${simulatorUuid}`); try { await executeAxeCommand(commandArgs, simulatorUuid, 'key'); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); return createTextResponse(`Key press (code: ${keyCode}) simulated successfully.`); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to simulate key press (code: ${keyCode}): ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); // 7. button server.tool( 'button', 'Press a hardware button on the simulator. Available buttons: apple-pay, home, lock, side-button, siri.', { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), buttonType: z.enum(['apple-pay', 'home', 'lock', 'side-button', 'siri']), duration: z.number().min(0, 'Duration must be non-negative').optional(), }, async (params): Promise<ToolResponse> => { const toolName = 'button'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const buttonTypeValidation = validateRequiredParam('buttonType', params.buttonType); if (!buttonTypeValidation.isValid) return buttonTypeValidation.errorResponse!; const { simulatorUuid, buttonType, duration } = params; const commandArgs = ['button', buttonType]; if (duration !== undefined) { commandArgs.push('--duration', String(duration)); } log( 'info', `${LOG_PREFIX}/${toolName}: Starting ${buttonType} button press on ${simulatorUuid}`, ); try { await executeAxeCommand(commandArgs, simulatorUuid, 'button'); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); return createTextResponse(`Hardware button '${buttonType}' pressed successfully.`); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to press button '${buttonType}': ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); // 8. key_sequence server.tool( 'key_sequence', 'Press a sequence of keys by their keycodes on the simulator. Each key will be pressed and released before the next key.', { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), keyCodes: z.array(z.number().int().min(0).max(255)).min(1, 'At least one keycode required'), delay: z.number().min(0, 'Delay must be non-negative').optional(), }, async (params): Promise<ToolResponse> => { const toolName = 'key_sequence'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const keyCodesValidation = validateRequiredParam('keyCodes', params.keyCodes); if (!keyCodesValidation.isValid) return keyCodesValidation.errorResponse!; const { simulatorUuid, keyCodes, delay } = params; const commandArgs = ['key-sequence', '--keycodes', keyCodes.join(',')]; if (delay !== undefined) { commandArgs.push('--delay', String(delay)); } log( 'info', `${LOG_PREFIX}/${toolName}: Starting key sequence [${keyCodes.join(',')}] on ${simulatorUuid}`, ); try { await executeAxeCommand(commandArgs, simulatorUuid, 'key-sequence'); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); return createTextResponse(`Key sequence [${keyCodes.join(',')}] executed successfully.`); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to execute key sequence: ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); // 9. touch server.tool( 'touch', "Perform touch down/up events at specific coordinates. Use describe_ui for precise coordinates (don't guess from screenshots).", { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), x: z.number().int('X coordinate must be an integer'), y: z.number().int('Y coordinate must be an integer'), down: z.boolean().optional(), up: z.boolean().optional(), delay: z.number().min(0, 'Delay must be non-negative').optional(), }, async (params): Promise<ToolResponse> => { const toolName = 'touch'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const xValidation = validateRequiredParam('x', params.x); if (!xValidation.isValid) return xValidation.errorResponse!; const yValidation = validateRequiredParam('y', params.y); if (!yValidation.isValid) return yValidation.errorResponse!; const { simulatorUuid, x, y, down, up, delay } = params; // Validate that at least one of down or up is specified if (!down && !up) { return createErrorResponse( 'At least one of "down" or "up" must be true', undefined, 'ValidationError', ); } const commandArgs = ['touch', '-x', String(x), '-y', String(y)]; if (down) { commandArgs.push('--down'); } if (up) { commandArgs.push('--up'); } if (delay !== undefined) { commandArgs.push('--delay', String(delay)); } const actionText = down && up ? 'touch down+up' : down ? 'touch down' : 'touch up'; log( 'info', `${LOG_PREFIX}/${toolName}: Starting ${actionText} at (${x}, ${y}) on ${simulatorUuid}`, ); try { await executeAxeCommand(commandArgs, simulatorUuid, 'touch'); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); const warning = getCoordinateWarning(simulatorUuid); const message = `Touch event (${actionText}) at (${x}, ${y}) executed successfully.`; if (warning) { return createTextResponse(`${message}\n\n${warning}`); } return createTextResponse(message); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to execute touch event: ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); // 10. gesture server.tool( 'gesture', 'Perform preset gesture patterns on the simulator. Available presets: scroll-up, scroll-down, scroll-left, scroll-right, swipe-from-left-edge, swipe-from-right-edge, swipe-from-top-edge, swipe-from-bottom-edge.', { simulatorUuid: z.string().uuid('Invalid Simulator UUID format'), preset: z.enum([ 'scroll-up', 'scroll-down', 'scroll-left', 'scroll-right', 'swipe-from-left-edge', 'swipe-from-right-edge', 'swipe-from-top-edge', 'swipe-from-bottom-edge', ]), screenWidth: z.number().int().min(1).optional(), screenHeight: z.number().int().min(1).optional(), duration: z.number().min(0, 'Duration must be non-negative').optional(), delta: z.number().min(0, 'Delta must be non-negative').optional(), preDelay: z.number().min(0, 'Pre-delay must be non-negative').optional(), postDelay: z.number().min(0, 'Post-delay must be non-negative').optional(), }, async (params): Promise<ToolResponse> => { const toolName = 'gesture'; const simUuidValidation = validateRequiredParam('simulatorUuid', params.simulatorUuid); if (!simUuidValidation.isValid) return simUuidValidation.errorResponse!; const presetValidation = validateRequiredParam('preset', params.preset); if (!presetValidation.isValid) return presetValidation.errorResponse!; const { simulatorUuid, preset, screenWidth, screenHeight, duration, delta, preDelay, postDelay, } = params; const commandArgs = ['gesture', preset]; if (screenWidth !== undefined) { commandArgs.push('--screen-width', String(screenWidth)); } if (screenHeight !== undefined) { commandArgs.push('--screen-height', String(screenHeight)); } if (duration !== undefined) { commandArgs.push('--duration', String(duration)); } if (delta !== undefined) { commandArgs.push('--delta', String(delta)); } if (preDelay !== undefined) { commandArgs.push('--pre-delay', String(preDelay)); } if (postDelay !== undefined) { commandArgs.push('--post-delay', String(postDelay)); } log('info', `${LOG_PREFIX}/${toolName}: Starting gesture '${preset}' on ${simulatorUuid}`); try { await executeAxeCommand(commandArgs, simulatorUuid, 'gesture'); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorUuid}`); return createTextResponse(`Gesture '${preset}' executed successfully.`); } catch (error) { log('error', `${LOG_PREFIX}/${toolName}: Failed - ${error}`); if (error instanceof DependencyError) { return createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to execute gesture '${preset}': ${error.message}`, error.axeOutput, error.name, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, error.name, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, undefined, 'UnexpectedError', ); } }, ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SampsonKY/XcodeBuildMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server