Skip to main content
Glama

flutter_run

Start a Flutter development session with hot reload enabled to run and test mobile applications on target devices during development.

Instructions

Start a Flutter development session (hot reload enabled)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cwdYesWorking directory (Flutter project root)
deviceIdNoTarget device ID
targetNoTarget dart file (e.g., lib/main.dart)
flavorNoBuild flavor
debugPortNoDebug port number

Implementation Reference

  • The core handler function that executes the flutter_run tool. It validates inputs, verifies the Flutter project, checks for existing sessions, constructs flutter run command arguments, spawns the child process, tracks the session in runningFlutterSessions map, and returns session details including PID and status.
    handler: async (args: any) => { const validation = FlutterRunSchema.safeParse(args); if (!validation.success) { throw new Error(`Invalid request: ${validation.error.message}`); } const { cwd, deviceId, target, flavor, debugPort } = validation.data; // Validate that it's a Flutter project await validateFlutterProject(cwd); // Check if there's already a running session for this project const existingSession = Array.from(runningFlutterSessions.entries()).find( ([_, session]) => session.projectPath === cwd ); if (existingSession) { return { success: true, data: { sessionId: existingSession[0], pid: existingSession[1].pid, status: 'already_running', projectPath: cwd, deviceId: existingSession[1].deviceId, message: 'Flutter run session is already active for this project', }, }; } const flutter_args = ['run']; if (deviceId) { flutter_args.push('-d', deviceId); } if (target) { // Validate target path (must be .dart file) if (!target.endsWith('.dart')) { throw new Error(`Target must be a .dart file. Invalid target: ${target}`); } flutter_args.push('--target', target); } if (flavor) { flutter_args.push('--flavor', flavor); } if (debugPort) { flutter_args.push('--debug-port', debugPort.toString()); } // Start flutter run in background const flutterProcess = spawn('flutter', flutter_args, { cwd, detached: true, stdio: ['ignore', 'pipe', 'pipe'], }); const sessionId = `flutter_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Track the process runningFlutterSessions.set(sessionId, { pid: flutterProcess.pid!, deviceId: deviceId || 'default', projectPath: cwd, }); // Handle process exit flutterProcess.on('exit', () => { runningFlutterSessions.delete(sessionId); }); // Unref to allow the parent process to exit flutterProcess.unref(); return { success: true, data: { sessionId, pid: flutterProcess.pid, status: 'started', projectPath: cwd, deviceId: deviceId || 'default', target: target || 'lib/main.dart', flavor, debugPort, }, }; }
  • Zod validation schema defining the input parameters for the flutter_run tool: cwd (required string), optional deviceId, target, flavor (strings), and debugPort (number 1024-65535). Used in the handler for input validation.
    const FlutterRunSchema = z.object({ cwd: z.string().min(1), deviceId: z.string().optional(), target: z.string().optional(), flavor: z.string().optional(), debugPort: z.number().min(1024).max(65535).optional(), });
  • Registration of the flutter_run tool within the createFlutterTools factory function. Defines name, description, inputSchema (JSON schema version), and references the handler function. This map is later used in server registration.
    * Zod validation schema for flutter_build tool. * Validates Flutter build parameters for multiple platforms. * * @type {z.ZodObject} * @property {string} cwd - Working directory (Flutter project root) * @property {string} target - Build target (apk, appbundle, ipa, ios, android, web, etc.) * @property {string} buildMode - Build mode (debug, profile, release) * @property {string} [flavor] - Build flavor */ const FlutterBuildSchema = z.object({ cwd: z.string().min(1), target: z.enum(['apk', 'appbundle', 'ipa', 'ios', 'android', 'web', 'windows', 'macos', 'linux']), buildMode: z.enum(['debug', 'profile', 'release']).default('debug'), flavor: z.string().optional(), }); /** * Zod validation schema for flutter_test tool. * Validates Flutter test execution parameters. * * @type {z.ZodObject} * @property {string} cwd - Working directory (Flutter project root) * @property {string} [testFile] - Specific test file to run * @property {boolean} coverage - Enable test coverage (default: false) */ const FlutterTestSchema = z.object({ cwd: z.string().min(1), testFile: z.string().optional(), coverage: z.boolean().default(false), }); /** * Zod validation schema for flutter_clean tool. * * @type {z.ZodObject} * @property {string} cwd - Working directory (Flutter project root) */ const FlutterCleanSchema = z.object({ cwd: z.string().min(1), }); /** * Zod validation schema for flutter_pub_get tool. * * @type {z.ZodObject} * @property {string} cwd - Working directory (Flutter project root) */ const FlutterPubGetSchema = z.object({ cwd: z.string().min(1), }); /** * Zod validation schema for flutter_screenshot tool. * * @type {z.ZodObject} * @property {string} [deviceId] - Target device ID * @property {string} [outputPath] - Output PNG file path */ const FlutterScreenshotSchema = z.object({ deviceId: z.string().optional(), outputPath: z.string().optional(), }); /** * Zod validation schema for flutter_stop_session tool. * * @type {z.ZodObject} * @property {string} sessionId - Flutter session ID to stop */ const FlutterStopSessionSchema = z.object({ sessionId: z.string().min(1), }); /** * Validates that a directory is a valid Flutter project. * Checks for pubspec.yaml file and flutter section. * * @param {string} cwd - Directory path to validate * @returns {Promise<void>} * @throws {Error} If pubspec.yaml not found or flutter section missing * * @example * ```typescript * await validateFlutterProject('/path/to/flutter/project'); * ``` */ const validateFlutterProject = async (cwd: string): Promise<void> => { const pubspecPath = path.join(cwd, 'pubspec.yaml'); try { await fs.access(pubspecPath); const pubspecContent = await fs.readFile(pubspecPath, 'utf8'); if (!pubspecContent.includes('flutter:')) { throw new Error(`Directory does not appear to be a Flutter project. No flutter section found in ${pubspecPath}`); } } catch { throw new Error(`pubspec.yaml not found. Flutter project must contain pubspec.yaml at: ${pubspecPath}`); } }; /** * Creates and configures all Flutter tools for the MCP Mobile Server. * * This factory function initializes and returns a complete set of Flutter development tools, * providing comprehensive Flutter SDK integration for AI agents via the MCP protocol. * * **Tools Created:** * - `flutter_doctor`: System diagnostics and environment validation * - `flutter_version`: Get Flutter SDK version information * - `flutter_list_devices`: List connected devices and emulators * - `flutter_list_emulators`: List available emulators * - `flutter_launch_emulator`: Launch a Flutter emulator * - `flutter_run`: Start development session with hot reload * - `flutter_stop_session`: Stop running development session * - `flutter_list_sessions`: List active development sessions * - `flutter_build`: Build app for target platform * - `flutter_test`: Run tests with optional coverage * - `flutter_clean`: Clean build cache * - `flutter_pub_get`: Install project dependencies * - `flutter_screenshot`: Capture screenshot from device * * **Features:** * - Session management with hot reload support * - Multi-platform build support (Android, iOS, Web, Desktop) * - Comprehensive error handling and validation * - Process lifecycle tracking * - Automatic fallback for machine-readable output * * @param {Map<string, number>} globalProcessMap - Global map tracking all active processes * @returns {Map<string, any>} Map of tool names to tool configurations * * @example * ```typescript * const globalProcesses = new Map(); * const tools = createFlutterTools(globalProcesses); * * // Use flutter_doctor * const doctor = tools.get('flutter_doctor'); * const result = await doctor.handler({}); * console.log(result.data.machineOutput); * * // Start development session * const run = tools.get('flutter_run'); * const session = await run.handler({ * cwd: '/path/to/project', * deviceId: 'emulator-5554' * }); * console.log(session.data.sessionId); * ``` * * @throws {Error} If Flutter SDK is not installed or not in PATH * @throws {Error} If validation schemas fail for any tool parameters * * @see {@link https://docs.flutter.dev/reference/flutter-cli|Flutter CLI Reference} */ export function createFlutterTools(globalProcessMap: Map<string, number>): Map<string, any> { // Initialize local sessions tracking if not provided runningFlutterSessions = new Map(); const tools = new Map<string, any>(); // Flutter Doctor - System diagnostics tools.set('flutter_doctor', { name: 'flutter_doctor', description: 'Run Flutter doctor to check development environment setup', inputSchema: { type: 'object', properties: {}, required: [] }, handler: async () => { const result = await processExecutor.execute('flutter', ['doctor', '--machine']); if (result.exitCode !== 0) { // Try fallback without --machine flag const fallbackResult = await processExecutor.execute('flutter', ['doctor', '-v']); return { success: true, data: { status: 'completed_with_issues', machineOutput: null, humanOutput: fallbackResult.stdout, issues: fallbackResult.stderr, rawExitCode: result.exitCode, }, }; } // Parse machine output if available let parsedOutput = null; try { parsedOutput = JSON.parse(result.stdout); } catch { // If JSON parsing fails, provide raw output parsedOutput = null; } return { success: true, data: { status: 'completed', machineOutput: parsedOutput, rawOutput: result.stdout, issues: result.stderr, exitCode: result.exitCode, }, }; } }); // Flutter Version - Get version information tools.set('flutter_version', { name: 'flutter_version', description: 'Get Flutter SDK version information', inputSchema: { type: 'object', properties: {}, required: [] }, handler: async () => { const result = await processExecutor.execute('flutter', ['--version', '--machine']); if (result.exitCode !== 0) { // Try fallback without --machine flag const fallbackResult = await processExecutor.execute('flutter', ['--version']); return { success: true, data: { machineOutput: null, humanOutput: fallbackResult.stdout, rawExitCode: result.exitCode, }, }; } // Parse machine output if available let parsedOutput = null; try { parsedOutput = JSON.parse(result.stdout); } catch { // Fallback to human readable const humanResult = await processExecutor.execute('flutter', ['--version']); return { success: true, data: { machineOutput: null, humanOutput: humanResult.stdout, rawExitCode: result.exitCode, }, }; } return { success: true, data: { machineOutput: parsedOutput, rawOutput: result.stdout, exitCode: result.exitCode, }, }; } }); // Flutter Devices - List connected devices tools.set('flutter_list_devices', { name: 'flutter_list_devices', description: 'List connected devices and emulators available for Flutter development', inputSchema: { type: 'object', properties: {}, required: [] }, handler: async () => { const result = await processExecutor.execute('flutter', ['devices', '--machine']); if (result.exitCode !== 0) { throw new Error(`Failed to list Flutter devices: ${result.stderr}`); } // Parse JSON output let devices = []; try { devices = JSON.parse(result.stdout); } catch (parseError) { throw new Error(`Failed to parse devices output: ${parseError}`); } // Enhance device info with running session status const enhancedDevices = devices.map((device: any) => ({ ...device, hasRunningSession: Array.from(runningFlutterSessions.values()).some( session => session.deviceId === device.id ), })); return { success: true, data: { devices: enhancedDevices, totalCount: enhancedDevices.length, connectedCount: enhancedDevices.filter((d: any) => d.isDevice).length, simulatorCount: enhancedDevices.filter((d: any) => !d.isDevice).length, runningSessionsCount: runningFlutterSessions.size, }, }; } }); // Flutter Emulators - List available emulators tools.set('flutter_list_emulators', { name: 'flutter_list_emulators', description: 'List available emulators for Flutter development', inputSchema: { type: 'object', properties: {}, required: [] }, handler: async () => { const result = await processExecutor.execute('flutter', ['emulators']); if (result.exitCode !== 0) { throw new Error(`Failed to list Flutter emulators: ${result.stderr}`); } // Parse text output (emulators command doesn't support --machine flag) const emulators = result.stdout .split('\n') .filter((line: string) => line.trim() && !line.includes('No emulators available')) .map((line: string, index: number) => ({ id: `emulator_${index}`, name: line.trim(), available: true })); return { success: true, data: { emulators, totalCount: emulators.length, }, }; } }); // Flutter Emulators - Launch emulator tools.set('flutter_launch_emulator', { name: 'flutter_launch_emulator', description: 'Launch a Flutter emulator', inputSchema: { type: 'object', properties: { emulatorId: { type: 'string', minLength: 1, description: 'Emulator ID to launch' } }, required: ['emulatorId'] }, handler: async (args: any) => { const validation = FlutterEmulatorLaunchSchema.safeParse(args); if (!validation.success) { throw new Error(`Invalid request: ${validation.error.message}`); } const { emulatorId } = validation.data; // Validate emulator ID format (alphanumeric, underscores, dots, dashes) if (!/^[a-zA-Z0-9._-]+$/.test(emulatorId)) { throw new Error(`Invalid emulator ID format. Emulator ID can only contain alphanumeric characters, dots, underscores, and dashes: ${emulatorId}`); } const result = await processExecutor.execute( 'flutter', ['emulators', '--launch', emulatorId], { timeout: 180000 } // 3 minutes timeout for emulator launch ); if (result.exitCode !== 0) { throw new Error(`Failed to launch emulator: ${result.stderr || result.stdout}`); } return { success: true, data: { emulatorId, status: 'launched', output: result.stdout, }, }; } }); // Flutter Run - Start development session
  • src/server.ts:43-71 (registration)
    Global MCP server registration in registerTools(). Calls createFlutterTools to get flutter tools including flutter_run, combines with other tool maps, and registers only those in TOOL_REGISTRY to the server's tools map used by MCP handlers.
    async function registerTools() { // Create tool handlers with shared process map const androidTools = createAndroidTools(globalProcessMap); const iosTools = createIOSTools(globalProcessMap); const flutterTools = createFlutterTools(globalProcessMap); const superTools = createSuperTools(globalProcessMap); const setupTools = createSetupTools(globalProcessMap); // Combine all tool sources const allAvailableTools = new Map<string, any>([ ...androidTools.entries(), ...iosTools.entries(), ...flutterTools.entries(), ...superTools.entries(), ...setupTools.entries() ]); // Only register tools that are in TOOL_REGISTRY for (const toolName of Object.keys(TOOL_REGISTRY)) { if (allAvailableTools.has(toolName)) { tools.set(toolName, allAvailableTools.get(toolName)); } else if (toolName === 'health_check') { // health_check is handled separately below continue; } else { // Tool is in registry but not implemented - log warning console.warn(`Warning: Tool '${toolName}' is in TOOL_REGISTRY but not implemented`); } }
  • Exported Zod schema for flutter_run inputs from types index, used potentially across the codebase for type inference and validation.
    export const FlutterRunSchema = z.object({ cwd: z.string(), deviceId: z.string().optional(), target: z.string().optional(), flavor: z.string().optional(), debugPort: z.number().optional(), });

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/cristianoaredes/mcp-mobile-server'

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