Skip to main content
Glama

Mobile Next MCP

ios.ts6.28 kB
import { execFileSync } from "child_process"; import { Socket } from "net"; import { WebDriverAgent } from "./webdriver-agent"; import { ActionableError, Button, InstalledApp, Robot, ScreenSize, SwipeDirection, ScreenElement, Orientation } from "./robot"; const WDA_PORT = 8100; const IOS_TUNNEL_PORT = 60105; interface ListCommandOutput { deviceList: string[]; } interface VersionCommandOutput { version: string; } interface InfoCommandOutput { DeviceClass: string; DeviceName: string; ProductName: string; ProductType: string; ProductVersion: string; PhoneNumber: string; TimeZone: string; } export interface IosDevice { deviceId: string; deviceName: string; } const getGoIosPath = (): string => { if (process.env.GO_IOS_PATH) { return process.env.GO_IOS_PATH; } // fallback to go-ios in PATH via `npm install -g go-ios` return "ios"; }; export class IosRobot implements Robot { public constructor(private deviceId: string) { } private isListeningOnPort(port: number): Promise<boolean> { return new Promise((resolve, reject) => { const client = new Socket(); client.connect(port, "localhost", () => { client.destroy(); resolve(true); }); client.on("error", (err: any) => { resolve(false); }); }); } private async isTunnelRunning(): Promise<boolean> { return await this.isListeningOnPort(IOS_TUNNEL_PORT); } private async isWdaForwardRunning(): Promise<boolean> { return await this.isListeningOnPort(WDA_PORT); } private async assertTunnelRunning(): Promise<void> { if (await this.isTunnelRequired()) { if (!(await this.isTunnelRunning())) { throw new ActionableError("iOS tunnel is not running, please see https://github.com/mobile-next/mobile-mcp/wiki/"); } } } private async wda(): Promise<WebDriverAgent> { await this.assertTunnelRunning(); if (!(await this.isWdaForwardRunning())) { throw new ActionableError("Port forwarding to WebDriverAgent is not running (tunnel okay), please see https://github.com/mobile-next/mobile-mcp/wiki/"); } const wda = new WebDriverAgent("localhost", WDA_PORT); if (!(await wda.isRunning())) { throw new ActionableError("WebDriverAgent is not running on device (tunnel okay, port forwarding okay), please see https://github.com/mobile-next/mobile-mcp/wiki/"); } return wda; } private async ios(...args: string[]): Promise<string> { return execFileSync(getGoIosPath(), ["--udid", this.deviceId, ...args], {}).toString(); } public async getIosVersion(): Promise<string> { const output = await this.ios("info"); const json = JSON.parse(output); return json.ProductVersion; } private async isTunnelRequired(): Promise<boolean> { const version = await this.getIosVersion(); const args = version.split("."); return parseInt(args[0], 10) >= 17; } public async getScreenSize(): Promise<ScreenSize> { const wda = await this.wda(); return await wda.getScreenSize(); } public async swipe(direction: SwipeDirection): Promise<void> { const wda = await this.wda(); await wda.swipe(direction); } public async swipeFromCoordinate(x: number, y: number, direction: SwipeDirection, distance?: number): Promise<void> { const wda = await this.wda(); await wda.swipeFromCoordinate(x, y, direction, distance); } public async listApps(): Promise<InstalledApp[]> { await this.assertTunnelRunning(); const output = await this.ios("apps", "--all", "--list"); return output .split("\n") .map(line => { const [packageName, appName] = line.split(" "); return { packageName, appName, }; }); } public async launchApp(packageName: string): Promise<void> { await this.assertTunnelRunning(); await this.ios("launch", packageName); } public async terminateApp(packageName: string): Promise<void> { await this.assertTunnelRunning(); await this.ios("kill", packageName); } public async openUrl(url: string): Promise<void> { const wda = await this.wda(); await wda.openUrl(url); } public async sendKeys(text: string): Promise<void> { const wda = await this.wda(); await wda.sendKeys(text); } public async pressButton(button: Button): Promise<void> { const wda = await this.wda(); await wda.pressButton(button); } public async tap(x: number, y: number): Promise<void> { const wda = await this.wda(); await wda.tap(x, y); } public async getElementsOnScreen(): Promise<ScreenElement[]> { const wda = await this.wda(); return await wda.getElementsOnScreen(); } public async getScreenshot(): Promise<Buffer> { const wda = await this.wda(); return await wda.getScreenshot(); /* alternative: await this.assertTunnelRunning(); const tmpFilename = path.join(tmpdir(), `screenshot-${randomBytes(8).toString("hex")}.png`); await this.ios("screenshot", "--output", tmpFilename); const buffer = readFileSync(tmpFilename); unlinkSync(tmpFilename); return buffer; */ } public async setOrientation(orientation: Orientation): Promise<void> { const wda = await this.wda(); await wda.setOrientation(orientation); } public async getOrientation(): Promise<Orientation> { const wda = await this.wda(); return await wda.getOrientation(); } } export class IosManager { public async isGoIosInstalled(): Promise<boolean> { try { const output = execFileSync(getGoIosPath(), ["version"], { stdio: ["pipe", "pipe", "ignore"] }).toString(); const json: VersionCommandOutput = JSON.parse(output); return json.version !== undefined && (json.version.startsWith("v") || json.version === "local-build"); } catch (error) { return false; } } public getDeviceName(deviceId: string): string { const output = execFileSync(getGoIosPath(), ["info", "--udid", deviceId]).toString(); const json: InfoCommandOutput = JSON.parse(output); return json.DeviceName; } public listDevices(): IosDevice[] { if (!this.isGoIosInstalled()) { console.error("go-ios is not installed, no physical iOS devices can be detected"); return []; } const output = execFileSync(getGoIosPath(), ["list"]).toString(); const json: ListCommandOutput = JSON.parse(output); const devices = json.deviceList.map(device => ({ deviceId: device, deviceName: this.getDeviceName(device), })); return devices; } }

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/EmpathySlainLovers/MCP'

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