Skip to main content
Glama
andreahaku
by andreahaku

flow.run

Execute sequential tool calls to automate iOS development workflows for Expo/React Native applications, including simulator control, server management, and testing.

Instructions

Execute a sequence of tool calls (macro flow)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
stepsYesSteps to execute in sequence.
stopOnErrorNoStop flow on first error.

Implementation Reference

  • Core handler function that executes a sequence of FlowSteps using a provided ToolExecutor, with error handling, logging, and optional screenshots on failure.
    export async function runFlow( steps: FlowStep[], executor: ToolExecutor, options: { stopOnError?: boolean; screenshotOnError?: boolean; } = {} ): Promise<FlowResult> { const { stopOnError = true, screenshotOnError = true } = options; const startTime = Date.now(); const results: StepResult[] = []; const evidence: string[] = []; logger.info("expo", `Starting flow with ${steps.length} steps`); for (let i = 0; i < steps.length; i++) { const step = steps[i]; const stepNumber = i + 1; const stepStart = Date.now(); logger.info("expo", `Step ${stepNumber}/${steps.length}: ${step.tool}`, { description: step.description, }); try { const { success, result, error } = await executor( step.tool, step.input as Record<string, unknown> ); const stepResult: StepResult = { step: stepNumber, tool: step.tool, success, elapsedMs: Date.now() - stepStart, result, error, }; results.push(stepResult); if (!success) { logger.error("expo", `Step ${stepNumber} failed: ${error}`); if (screenshotOnError) { try { const screenshot = await takeScreenshot(`flow-error-step-${stepNumber}`); evidence.push(screenshot.path); } catch { logger.warn("expo", "Failed to capture error screenshot"); } } if (stopOnError) { return { success: false, totalSteps: steps.length, completedSteps: stepNumber - 1, failedStep: stepNumber, results, totalElapsedMs: Date.now() - startTime, evidence: evidence.length > 0 ? evidence : undefined, }; } } else { logger.info("expo", `Step ${stepNumber} completed in ${stepResult.elapsedMs}ms`); } } catch (err) { const stepResult: StepResult = { step: stepNumber, tool: step.tool, success: false, elapsedMs: Date.now() - stepStart, error: err instanceof Error ? err.message : "Unknown error", }; results.push(stepResult); if (screenshotOnError) { try { const screenshot = await takeScreenshot(`flow-error-step-${stepNumber}`); evidence.push(screenshot.path); } catch { // Ignore screenshot errors } } if (stopOnError) { return { success: false, totalSteps: steps.length, completedSteps: stepNumber - 1, failedStep: stepNumber, results, totalElapsedMs: Date.now() - startTime, evidence: evidence.length > 0 ? evidence : undefined, }; } } } const allSuccess = results.every((r) => r.success); logger.info("expo", `Flow completed: ${allSuccess ? "SUCCESS" : "FAILED"}`, { totalSteps: steps.length, completedSteps: results.filter((r) => r.success).length, totalElapsedMs: Date.now() - startTime, }); return { success: allSuccess, totalSteps: steps.length, completedSteps: results.filter((r) => r.success).length, failedStep: allSuccess ? undefined : results.findIndex((r) => !r.success) + 1, results, totalElapsedMs: Date.now() - startTime, evidence: evidence.length > 0 ? evidence : undefined, }; }
  • MCP server.tool registration for the 'flow.run' tool, delegating to runFlow with a custom toolExecutor.
    server.tool( "flow.run", "Execute a sequence of tool calls (macro flow)", FlowRunInputSchema.shape, async (args) => { try { const result = await runFlow(args.steps, toolExecutor, { stopOnError: args.stopOnError, }); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], isError: !result.success, }; } catch (error) { return handleToolError(error); } } );
  • Zod schema defining the input parameters for the flow.run tool, including steps array and stopOnError option.
    export const FlowRunInputSchema = z.object({ steps: z.array(FlowStepSchema).describe("Steps to execute in sequence."), stopOnError: z.boolean().optional().default(true).describe("Stop flow on first error."), });
  • Custom ToolExecutor implementation used by flow.run to execute individual UI and simulator tools via Detox actions.
    const toolExecutor: ToolExecutor = async (toolName, input) => { // This is a simplified executor - in production you might want to // route through the actual MCP tool handlers try { switch (toolName) { case "ui.tap": const tapSnippet = generateTapSnippet({ selector: input.selector as { by: "id" | "text" | "label"; value: string }, x: input.x as number | undefined, y: input.y as number | undefined, }); const tapResult = await runDetoxAction({ actionName: `tap:${(input.selector as { value: string }).value}`, actionSnippet: tapSnippet, }); return { success: tapResult.success, result: tapResult, error: tapResult.error?.message }; case "ui.type": const typeSnippet = generateTypeSnippet({ selector: input.selector as { by: "id" | "text" | "label"; value: string }, text: input.text as string, replace: input.replace as boolean | undefined, }); const typeResult = await runDetoxAction({ actionName: `type:${(input.selector as { value: string }).value}`, actionSnippet: typeSnippet, }); return { success: typeResult.success, result: typeResult, error: typeResult.error?.message }; case "ui.wait_for": const waitSnippet = generateWaitForSnippet({ selector: input.selector as { by: "id" | "text" | "label"; value: string }, visible: input.visible as boolean | undefined, timeout: input.timeout as number | undefined, }); const waitResult = await runDetoxAction({ actionName: `waitFor:${(input.selector as { value: string }).value}`, actionSnippet: waitSnippet, }); return { success: waitResult.success, result: waitResult, error: waitResult.error?.message }; case "simulator.screenshot": const screenshot = await takeScreenshot(input.name as string | undefined); return { success: true, result: screenshot }; default: return { success: false, error: `Unknown tool: ${toolName}` }; } } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error", }; } };

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/andreahaku/expo_ios_development_mcp'

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