Skip to main content
Glama

record_video

Capture iOS Simulator screen recordings using simctl. Specify output path, codec, display settings, and mask handling for video files.

Instructions

Records a video of the iOS Simulator using simctl directly

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
output_pathNoOptional output path. If not provided, a default name will be used. The file will be saved in the directory specified by `IOS_SIMULATOR_MCP_DEFAULT_OUTPUT_DIR` or in `~/Downloads` if the environment variable is not set.
codecNoSpecifies the codec type: "h264" or "hevc". Default is "hevc".
displayNoDisplay to capture: "internal" or "external". Default depends on device type.
maskNoFor non-rectangular displays, handle the mask by policy: "ignored", "alpha", or "black".
forceNoForce the output file to be written to, even if the file already exists.

Implementation Reference

  • Handler function that starts video recording on the booted iOS simulator using xcrun simctl io booted recordVideo. It handles output path, codec, display, mask, and force options, spawns the process, waits for start confirmation, and instructs to use stop_recording to end.
    async ({ output_path, codec, display, mask, force }) => { try { const defaultFileName = `simulator_recording_${Date.now()}.mp4`; const outputFile = ensureAbsolutePath(output_path ?? defaultFileName); // Start the recording process const recordingProcess = spawn("xcrun", [ "simctl", "io", "booted", "recordVideo", ...(codec ? [`--codec=${codec}`] : []), ...(display ? [`--display=${display}`] : []), ...(mask ? [`--mask=${mask}`] : []), ...(force ? ["--force"] : []), // When passing user-provided values to a command, it's crucial to use `--` // to separate the command's options from positional arguments. // This prevents the shell from misinterpreting the arguments as options. "--", outputFile, ]); // Wait for recording to start await new Promise((resolve, reject) => { let errorOutput = ""; recordingProcess.stderr.on("data", (data) => { const message = data.toString(); if (message.includes("Recording started")) { resolve(true); } else { errorOutput += message; } }); // Set timeout for start verification setTimeout(() => { if (recordingProcess.killed) { reject(new Error("Recording process terminated unexpectedly")); } else { resolve(true); } }, 3000); }); return { isError: false, content: [ { type: "text", text: `Recording started. The video will be saved to: ${outputFile}\nTo stop recording, use the stop_recording command.`, }, ], }; } catch (error) { return { isError: true, content: [ { type: "text", text: errorWithTroubleshooting( `Error starting recording: ${toError(error).message}` ), }, ], }; } } );
  • Input schema for the record_video tool using Zod validators for output_path (optional string), codec (h264/hevc), display (internal/external), mask (ignored/alpha/black), and force (boolean).
    { output_path: z .string() .max(1024) .optional() .describe( `Optional output path. If not provided, a default name will be used. The file will be saved in the directory specified by \`IOS_SIMULATOR_MCP_DEFAULT_OUTPUT_DIR\` or in \`~/Downloads\` if the environment variable is not set.` ), codec: z .enum(["h264", "hevc"]) .optional() .describe( 'Specifies the codec type: "h264" or "hevc". Default is "hevc".' ), display: z .enum(["internal", "external"]) .optional() .describe( 'Display to capture: "internal" or "external". Default depends on device type.' ), mask: z .enum(["ignored", "alpha", "black"]) .optional() .describe( 'For non-rectangular displays, handle the mask by policy: "ignored", "alpha", or "black".' ), force: z .boolean() .optional() .describe( "Force the output file to be written to, even if the file already exists." ), },
  • src/index.ts:729-835 (registration)
    Registration of the record_video tool on the MCP server, conditional on not being filtered, including name, description, input schema, and handler function.
    if (!isToolFiltered("record_video")) { server.tool( "record_video", "Records a video of the iOS Simulator using simctl directly", { output_path: z .string() .max(1024) .optional() .describe( `Optional output path. If not provided, a default name will be used. The file will be saved in the directory specified by \`IOS_SIMULATOR_MCP_DEFAULT_OUTPUT_DIR\` or in \`~/Downloads\` if the environment variable is not set.` ), codec: z .enum(["h264", "hevc"]) .optional() .describe( 'Specifies the codec type: "h264" or "hevc". Default is "hevc".' ), display: z .enum(["internal", "external"]) .optional() .describe( 'Display to capture: "internal" or "external". Default depends on device type.' ), mask: z .enum(["ignored", "alpha", "black"]) .optional() .describe( 'For non-rectangular displays, handle the mask by policy: "ignored", "alpha", or "black".' ), force: z .boolean() .optional() .describe( "Force the output file to be written to, even if the file already exists." ), }, async ({ output_path, codec, display, mask, force }) => { try { const defaultFileName = `simulator_recording_${Date.now()}.mp4`; const outputFile = ensureAbsolutePath(output_path ?? defaultFileName); // Start the recording process const recordingProcess = spawn("xcrun", [ "simctl", "io", "booted", "recordVideo", ...(codec ? [`--codec=${codec}`] : []), ...(display ? [`--display=${display}`] : []), ...(mask ? [`--mask=${mask}`] : []), ...(force ? ["--force"] : []), // When passing user-provided values to a command, it's crucial to use `--` // to separate the command's options from positional arguments. // This prevents the shell from misinterpreting the arguments as options. "--", outputFile, ]); // Wait for recording to start await new Promise((resolve, reject) => { let errorOutput = ""; recordingProcess.stderr.on("data", (data) => { const message = data.toString(); if (message.includes("Recording started")) { resolve(true); } else { errorOutput += message; } }); // Set timeout for start verification setTimeout(() => { if (recordingProcess.killed) { reject(new Error("Recording process terminated unexpectedly")); } else { resolve(true); } }, 3000); }); return { isError: false, content: [ { type: "text", text: `Recording started. The video will be saved to: ${outputFile}\nTo stop recording, use the stop_recording command.`, }, ], }; } catch (error) { return { isError: true, content: [ { type: "text", text: errorWithTroubleshooting( `Error starting recording: ${toError(error).message}` ), }, ], }; } } ); }
  • Companion tool 'stop_recording' that sends SIGINT to processes matching 'simctl.*recordVideo' to stop the video recording started by record_video.
    if (!isToolFiltered("stop_recording")) { server.tool( "stop_recording", "Stops the simulator video recording using killall", {}, async () => { try { await run("pkill", ["-SIGINT", "-f", "simctl.*recordVideo"]); // Wait a moment for the video to finalize await new Promise((resolve) => setTimeout(resolve, 1000)); return { isError: false, content: [ { type: "text", text: "Recording stopped successfully.", }, ], }; } catch (error) { return { isError: true, content: [ { type: "text", text: errorWithTroubleshooting( `Error stopping recording: ${toError(error).message}` ), }, ], }; } } ); }
  • Helper function used by record_video (and screenshot) to resolve relative output paths to absolute paths, supporting ~ expansion and defaulting to ~/Downloads or env var directory.
    function ensureAbsolutePath(filePath: string): string { if (path.isAbsolute(filePath)) { return filePath; } // Handle ~/something paths in the provided filePath if (filePath.startsWith("~/")) { return path.join(os.homedir(), filePath.slice(2)); } // Determine the default directory from env var or fallback to ~/Downloads let defaultDir = path.join(os.homedir(), "Downloads"); const customDefaultDir = process.env.IOS_SIMULATOR_MCP_DEFAULT_OUTPUT_DIR; if (customDefaultDir) { // also expand tilde for the custom directory path if (customDefaultDir.startsWith("~/")) { defaultDir = path.join(os.homedir(), customDefaultDir.slice(2)); } else { defaultDir = customDefaultDir; } } // Join the relative filePath with the resolved default directory return path.join(defaultDir, filePath); }

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/joshuayoes/ios-simulator-mcp'

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