Skip to main content
Glama

tap

Tap at specified coordinates on Xcode simulators using exact UI element positions obtained via describe_ui. Configure optional timing delays for precise interactions.

Instructions

Tap at specific coordinates. Use describe_ui to get precise element coordinates (don't guess from screenshots). Supports optional timing delays.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
postDelayNo
preDelayNo
simulatorUuidYes
xYes
yYes

Implementation Reference

  • Core handler function that constructs the axe 'tap' command with coordinates and optional delays, executes it on the simulator, handles responses, warnings about UI coordinates, and all error cases.
    export async function tapLogic( params: TapParams, executor: CommandExecutor, axeHelpers: AxeHelpers = { getAxePath, getBundledAxeEnvironment, createAxeNotAvailableResponse, }, ): Promise<ToolResponse> { const toolName = 'tap'; const { simulatorId, 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 ${simulatorId}`); try { await executeAxeCommand(commandArgs, simulatorId, 'tap', executor, axeHelpers); log('info', `${LOG_PREFIX}/${toolName}: Success for ${simulatorId}`); const warning = getCoordinateWarning(simulatorId); 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 axeHelpers.createAxeNotAvailableResponse(); } else if (error instanceof AxeError) { return createErrorResponse( `Failed to simulate tap at (${x}, ${y}): ${error.message}`, error.axeOutput, ); } else if (error instanceof SystemError) { return createErrorResponse( `System error executing axe: ${error.message}`, error.originalError?.stack, ); } return createErrorResponse( `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`, ); }
  • Zod input schema validating simulatorId (UUID), integer coordinates x/y, and optional non-negative pre/post delays.
    const tapSchema = z.object({ simulatorId: 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(), });
  • Default export defining the tool object with name 'tap', description, public schema (without simulatorId), and session-aware handler wrapper. This object is loaded by the plugin registry and registered dynamically.
    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 handler: createSessionAwareTool<TapParams>({ internalSchema: tapSchema as unknown as z.ZodType<TapParams>, logicFunction: (params: TapParams, executor: CommandExecutor) => tapLogic(params, executor, { getAxePath, getBundledAxeEnvironment, createAxeNotAvailableResponse, }), getExecutor: getDefaultCommandExecutor, requirements: [{ allOf: ['simulatorId'], message: 'simulatorId is required' }], }), };
  • Internal helper that executes the axe command binary with udid, handles bundled axe env, checks success, logs warnings, and throws typed errors.
    async function executeAxeCommand( commandArgs: string[], simulatorId: string, commandName: string, executor: CommandExecutor = getDefaultCommandExecutor(), axeHelpers: AxeHelpers = { getAxePath, getBundledAxeEnvironment, createAxeNotAvailableResponse }, ): Promise<void> { // Get the appropriate axe binary path const axeBinary = axeHelpers.getAxePath(); if (!axeBinary) { throw new DependencyError('AXe binary not found'); } // Add --udid parameter to all commands const fullArgs = [...commandArgs, '--udid', simulatorId]; // Construct the full command array with the axe binary as the first element const fullCommand = [axeBinary, ...fullArgs]; try { // Determine environment variables for bundled AXe const axeEnv = axeBinary !== 'axe' ? axeHelpers.getBundledAxeEnvironment() : undefined; const result = await executor(fullCommand, `${LOG_PREFIX}: ${commandName}`, false, axeEnv); if (!result.success) { throw new AxeError( `axe command '${commandName}' failed.`, commandName, result.error ?? result.output, simulatorId, ); } // 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}`, ); } // Function now returns void - the calling code creates its own response } 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)}`); } }

Other Tools

Related Tools

  • @cameroncooke/XcodeBuildMCP
  • @cameroncooke/XcodeBuildMCP
  • @joshuayoes/ios-simulator-mcp
  • @vs4vijay/espresso-mcp
  • @joshuayoes/ios-simulator-mcp
  • @cameroncooke/XcodeBuildMCP

Latest Blog Posts

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/cameroncooke/XcodeBuildMCP'

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