mcp-appium-visual
Version:
MCP Server for Appium mobile automation with visual recovery
1,403 lines • 135 kB
JavaScript
import { z } from "zod";
import { AppiumHelper } from "../lib/appium/appiumHelper.js";
import * as fs from "fs/promises";
// Shared Appium instance for reuse across tool calls
let appiumHelper = null;
// Global configuration that can be set from server config
let globalAppiumUrl;
/**
* Get the Appium helper, validating the session if it exists
* This is a utility function to centralize session validation and recovery
*
* @returns The existing and validated appiumHelper or null if not initialized
*/
async function getValidAppiumHelper() {
if (!appiumHelper) {
return null;
}
try {
// Validate the session and attempt recovery if needed
const isSessionValid = await appiumHelper.validateSession();
if (!isSessionValid) {
console.error("Appium session validation failed and could not be recovered automatically");
return null;
}
return appiumHelper;
}
catch (error) {
console.error("Error validating Appium session:", error instanceof Error ? error.message : String(error));
return null;
}
}
/**
* Register mobile automation tools with the MCP server
*/
export function registerMobileTools(server, config) {
// Store the appium URL from config for use in tools
if (config?.appiumUrl) {
globalAppiumUrl = config.appiumUrl;
}
// Tool: Initialize Appium driver
server.tool("initialize-appium", "Initialize an Appium driver session for mobile automation", {
platformName: z
.enum(["Android", "iOS"])
.describe("The mobile platform to automate"),
deviceName: z.string().describe("The name of the device to target"),
udid: z
.string()
.optional()
.describe("Device unique identifier (required for real devices)"),
app: z
.string()
.optional()
.describe("Path to the app to install (optional)"),
appPackage: z.string().optional().describe("App package name (Android)"),
appActivity: z
.string()
.optional()
.describe("App activity name to launch (Android)"),
bundleId: z.string().optional().describe("Bundle identifier (iOS)"),
automationName: z
.enum(["UiAutomator2", "XCUITest"])
.optional()
.describe("Automation engine to use"),
noReset: z
.boolean()
.optional()
.describe("Preserve app state between sessions"),
fullReset: z
.boolean()
.optional()
.describe("Perform a full reset (uninstall app before starting)"),
appiumUrl: z.string().optional().describe("URL of the Appium server"),
screenshotDir: z
.string()
.optional()
.describe("Directory to save screenshots"),
}, async (params) => {
try {
// If there's an existing session, try to close it first
if (appiumHelper) {
try {
await appiumHelper.closeDriver();
}
catch (error) {
console.warn("Error closing existing Appium session:", error instanceof Error ? error.message : String(error));
}
}
// Create capabilities object from parameters
const capabilities = {
platformName: params.platformName,
deviceName: params.deviceName,
};
// Add optional capabilities
if (params.udid)
capabilities.udid = params.udid;
if (params.app)
capabilities.app = params.app;
if (params.appPackage)
capabilities.appPackage = params.appPackage;
if (params.appActivity)
capabilities.appActivity = params.appActivity;
if (params.bundleId)
capabilities.bundleId = params.bundleId;
if (params.automationName)
capabilities.automationName = params.automationName;
if (params.noReset !== undefined)
capabilities.noReset = params.noReset;
if (params.fullReset !== undefined)
capabilities.fullReset = params.fullReset;
// Set default automation based on platform if not specified
if (!capabilities.automationName) {
capabilities.automationName =
params.platformName === "Android" ? "UiAutomator2" : "XCUITest";
}
// Create and initialize Appium helper
appiumHelper = new AppiumHelper(params.screenshotDir || "./screenshots");
// Use appiumUrl from params, or fall back to global config
const appiumUrl = params.appiumUrl || globalAppiumUrl;
await appiumHelper.initializeDriver(capabilities, appiumUrl);
return {
content: [
{
type: "text",
text: `Successfully initialized Appium session for ${params.platformName} device: ${params.deviceName}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error initializing Appium session: ${error.message}`,
},
],
};
}
});
// Tool: Close Appium driver session
server.tool("close-appium", "Close the current Appium driver session", {}, async () => {
try {
if (!appiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session to close.",
},
],
};
}
await appiumHelper.closeDriver();
appiumHelper = null;
return {
content: [
{
type: "text",
text: "Successfully closed Appium session.",
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error closing Appium session: ${error.message}`,
},
],
};
}
});
// Tool: Take screenshot using Appium
server.tool("appium-screenshot", "Take a screenshot using Appium", {
name: z.string().describe("Base name for the screenshot file"),
}, async ({ name }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const screenshotPath = await validAppiumHelper.takeScreenshot(name);
return {
content: [
{
type: "text",
text: `Screenshot saved to: ${screenshotPath}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error taking screenshot: ${error.message}`,
},
],
};
}
});
// Tool: Tap on element
server.tool("tap-element", "Tap on a UI element identified by a selector", {
selector: z.string().describe("Element selector (e.g., xpath, id)"),
strategy: z
.string()
.optional()
.describe("Selector strategy: xpath, id, accessibility id, class name (default: xpath)"),
}, async ({ selector, strategy }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
console.log(`MCP server: Attempting to tap element with selector "${selector}" using strategy "${strategy || "xpath"}"`);
// Direct Approach: Find the element and click it directly
try {
const element = await validAppiumHelper.findElement(selector, strategy || "xpath");
console.log("MCP server: Element found, attempting direct click");
await element.waitForClickable({ timeout: 5000 });
await element.click();
console.log("MCP server: Direct element click successful");
return {
content: [
{
type: "text",
text: `Successfully tapped on element: ${selector}`,
},
],
};
}
catch (clickError) {
console.log(`MCP server: Direct click failed: ${clickError instanceof Error
? clickError.message
: String(clickError)}`);
// Fallback 1: Try using AppiumHelper.tapElement which has its own implementation
try {
console.log("MCP server: Attempting tap using AppiumHelper.tapElement");
const success = await validAppiumHelper.tapElement(selector, strategy || "xpath");
if (success) {
console.log("MCP server: AppiumHelper.tapElement successful");
return {
content: [
{
type: "text",
text: `Successfully tapped on element: ${selector} using tapElement method`,
},
],
};
}
}
catch (tapError) {
console.log(`MCP server: tapElement method failed: ${tapError instanceof Error ? tapError.message : String(tapError)}`);
}
// Fallback 2: Try using touchAction directly with coordinates
try {
console.log("MCP server: Attempting touchAction as final fallback");
const element = await validAppiumHelper.findElement(selector, strategy || "xpath");
const location = await element.getLocation();
const size = await element.getSize();
// Click in the center of the element
const x = location.x + size.width / 2;
const y = location.y + size.height / 2;
console.log(`MCP server: Using touchAction at coordinates (${x}, ${y})`);
await validAppiumHelper
.getDriver()
.touchAction([{ action: "press", x, y }, { action: "release" }]);
console.log("MCP server: TouchAction successful");
return {
content: [
{
type: "text",
text: `Successfully tapped on element: ${selector} (using touch coordinates)`,
},
],
};
}
catch (touchError) {
console.log(`MCP server: TouchAction failed: ${touchError instanceof Error
? touchError.message
: String(touchError)}`);
throw touchError;
}
}
}
catch (error) {
console.log(`MCP server: All tap attempts failed: ${error?.message}`);
return {
content: [
{
type: "text",
text: `Error tapping element: ${error.message}`,
},
],
};
}
});
// Tool: Send keys to element
server.tool("send-keys", "Send text input to a UI element", {
selector: z.string().describe("Element selector (e.g., xpath, id)"),
text: z.string().describe("Text to input"),
strategy: z
.string()
.optional()
.describe("Selector strategy: xpath, id, accessibility id, class name (default: xpath)"),
}, async ({ selector, text, strategy }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const success = await validAppiumHelper.sendKeys(selector, text, strategy || "xpath");
if (success) {
return {
content: [
{
type: "text",
text: `Successfully sent text to element: ${selector}`,
},
],
};
}
else {
return {
content: [
{
type: "text",
text: `Failed to send text to element: ${selector}. Element might not be visible or present.`,
},
],
};
}
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error sending text to element: ${error.message}`,
},
],
};
}
});
// Tool: Get page source (UI XML)
server.tool("get-page-source", "Get the XML representation of the current UI", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const source = await validAppiumHelper.getPageSource();
return {
content: [
{
type: "text",
text: `UI Source XML:\n${source}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error retrieving page source: ${error.message}`,
},
],
};
}
});
// Tool: Swipe on screen
server.tool("swipe", "Perform a swipe gesture on the screen", {
startX: z.number().describe("Starting X coordinate"),
startY: z.number().describe("Starting Y coordinate"),
endX: z.number().describe("Ending X coordinate"),
endY: z.number().describe("Ending Y coordinate"),
duration: z
.number()
.optional()
.describe("Duration of the swipe in milliseconds (default: 800)"),
}, async ({ startX, startY, endX, endY, duration }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const success = await validAppiumHelper.swipe(startX, startY, endX, endY, duration || 800);
if (success) {
return {
content: [
{
type: "text",
text: `Successfully performed swipe from (${startX},${startY}) to (${endX},${endY})`,
},
],
};
}
else {
return {
content: [
{
type: "text",
text: `Failed to perform swipe gesture`,
},
],
};
}
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error performing swipe: ${error.message}`,
},
],
};
}
});
// Tool: Wait for element
server.tool("wait-for-element", "Wait for an element to be visible on screen", {
selector: z.string().describe("Element selector (e.g., xpath, id)"),
strategy: z
.string()
.optional()
.describe("Selector strategy: xpath, id, accessibility id, class name (default: xpath)"),
timeoutMs: z
.number()
.optional()
.describe("Timeout in milliseconds (default: 10000)"),
}, async ({ selector, strategy, timeoutMs }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const success = await validAppiumHelper.waitForElement(selector, strategy || "xpath", timeoutMs || 10000);
if (success) {
return {
content: [
{
type: "text",
text: `Element ${selector} is now visible`,
},
],
};
}
else {
return {
content: [
{
type: "text",
text: `Timed out waiting for element: ${selector}`,
},
],
};
}
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error waiting for element: ${error.message}`,
},
],
};
}
});
// Tool: Long press on element
server.tool("long-press", "Perform a long press gesture on an element", {
selector: z.string().describe("Element selector (e.g., xpath, id)"),
duration: z
.number()
.optional()
.describe("Duration of the long press in milliseconds (default: 1000)"),
strategy: z
.string()
.optional()
.describe("Selector strategy: xpath, id, accessibility id, class name (default: xpath)"),
}, async ({ selector, duration, strategy }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const success = await validAppiumHelper.longPress(selector, duration || 1000, strategy || "xpath");
if (success) {
return {
content: [
{
type: "text",
text: `Successfully performed long press on element: ${selector}`,
},
],
};
}
else {
return {
content: [
{
type: "text",
text: `Failed to perform long press on element: ${selector}`,
},
],
};
}
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error performing long press: ${error.message}`,
},
],
};
}
});
// Tool: Scroll to element
server.tool("scroll-to-element", "Scroll until an element becomes visible", {
selector: z
.string()
.describe("Element selector to scroll to (e.g., xpath)"),
direction: z
.enum(["up", "down", "left", "right"])
.optional()
.describe("Direction to scroll (default: down)"),
strategy: z
.string()
.optional()
.describe("Selector strategy: xpath, id, accessibility id, class name (default: xpath)"),
maxScrolls: z
.number()
.optional()
.describe("Maximum number of scroll attempts (default: 10)"),
}, async ({ selector, direction, strategy, maxScrolls }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const success = await validAppiumHelper.scrollToElement(selector, direction || "down", strategy || "xpath", maxScrolls || 10);
if (success) {
return {
content: [
{
type: "text",
text: `Successfully scrolled to element: ${selector}`,
},
],
};
}
else {
return {
content: [
{
type: "text",
text: `Failed to find element: ${selector} after scrolling`,
},
],
};
}
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error scrolling to element: ${error.message}`,
},
],
};
}
});
// Tool: Get device orientation
server.tool("get-orientation", "Get the current device orientation", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const orientation = await validAppiumHelper.getOrientation();
return {
content: [
{
type: "text",
text: `Current device orientation: ${orientation}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error getting device orientation: ${error.message}`,
},
],
};
}
});
// Tool: Set device orientation
server.tool("set-orientation", "Set the device orientation", {
orientation: z
.enum(["PORTRAIT", "LANDSCAPE"])
.describe("Desired orientation: PORTRAIT or LANDSCAPE"),
}, async ({ orientation }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.setOrientation(orientation);
return {
content: [
{
type: "text",
text: `Successfully set device orientation to: ${orientation}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error setting device orientation: ${error.message}`,
},
],
};
}
});
// Tool: Hide keyboard
server.tool("hide-keyboard", "Hide the keyboard if it's currently visible", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.hideKeyboard();
return {
content: [
{
type: "text",
text: "Keyboard hidden successfully",
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error hiding keyboard: ${error.message}`,
},
],
};
}
});
// Tool: Get current app package
server.tool("get-current-package", "Get the current active app package name", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const packageName = await validAppiumHelper.getCurrentPackage();
return {
content: [
{
type: "text",
text: `Current app package: ${packageName}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error getting current package: ${error.message}`,
},
],
};
}
});
// Tool: Get current activity (Android only)
server.tool("get-current-activity", "Get the current Android activity name", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const activity = await validAppiumHelper.getCurrentActivity();
return {
content: [
{
type: "text",
text: `Current activity: ${activity}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error getting current activity: ${error.message}`,
},
],
};
}
});
// Tool: Launch app
server.tool("launch-appium-app", "Launch the app associated with the current Appium session", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.launchApp();
return {
content: [
{
type: "text",
text: "App launched successfully",
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error launching app: ${error.message}`,
},
],
};
}
});
// Tool: Close app
server.tool("close-app", "Close the app associated with the current Appium session", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.closeApp();
return {
content: [
{
type: "text",
text: "App closed successfully",
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error closing app: ${error.message}`,
},
],
};
}
});
// Tool: Reset app
server.tool("reset-app", "Reset the app (terminate and relaunch) associated with the current Appium session", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.resetApp();
return {
content: [
{
type: "text",
text: "App reset successfully",
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error resetting app: ${error.message}`,
},
],
};
}
});
// Tool: Get device time
server.tool("get-device-time", "Get the current device time", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const time = await validAppiumHelper.getDeviceTime();
return {
content: [
{
type: "text",
text: `Current device time: ${time}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error getting device time: ${error.message}`,
},
],
};
}
});
// Tool: Lock device
server.tool("lock-device", "Lock the device screen", {
durationSec: z
.number()
.optional()
.describe("Duration in seconds to lock the device for"),
}, async ({ durationSec }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.lockDevice(durationSec);
return {
content: [
{
type: "text",
text: durationSec
? `Device locked for ${durationSec} seconds`
: "Device locked",
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error locking device: ${error.message}`,
},
],
};
}
});
// Tool: Check if device is locked
server.tool("is-device-locked", "Check if the device is currently locked", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const isLocked = await validAppiumHelper.isDeviceLocked();
return {
content: [
{
type: "text",
text: isLocked ? "Device is locked" : "Device is unlocked",
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error checking device lock state: ${error.message}`,
},
],
};
}
});
// Tool: Unlock device
server.tool("unlock-device", "Unlock the device screen", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.unlockDevice();
return {
content: [
{
type: "text",
text: "Device unlocked successfully",
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error unlocking device: ${error.message}`,
},
],
};
}
});
// Tool: Press key code (Android only)
server.tool("press-key-code", "Press an Android key code", {
keycode: z.number().describe("Android keycode to press"),
}, async ({ keycode }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.pressKeyCode(keycode);
return {
content: [
{
type: "text",
text: `Successfully pressed key code: ${keycode}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error pressing key code: ${error.message}`,
},
],
};
}
});
// Tool: Open notifications (Android only)
server.tool("open-notifications", "Open the notifications panel (Android only)", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.openNotifications();
return {
content: [
{
type: "text",
text: "Notifications panel opened successfully",
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error opening notifications: ${error.message}`,
},
],
};
}
});
// Tool: Get available contexts
server.tool("get-contexts", "Get all available contexts (NATIVE_APP, WEBVIEW, etc.)", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const contexts = await validAppiumHelper.getContexts();
return {
content: [
{
type: "text",
text: `Available contexts: ${contexts.join(", ")}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error getting contexts: ${error.message}`,
},
],
};
}
});
// Tool: Switch context
server.tool("switch-context", "Switch between contexts (e.g., NATIVE_APP, WEBVIEW)", {
context: z.string().describe("Context to switch to"),
}, async ({ context }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.switchContext(context);
return {
content: [
{
type: "text",
text: `Successfully switched to context: ${context}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error switching context: ${error.message}`,
},
],
};
}
});
// Tool: Get current context
server.tool("get-current-context", "Get the current context", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const context = await validAppiumHelper.getCurrentContext();
return {
content: [
{
type: "text",
text: `Current context: ${context}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error getting current context: ${error.message}`,
},
],
};
}
});
// Tool: Pull file from device
server.tool("pull-file", "Pull a file from the device", {
path: z.string().describe("Path to the file on the device"),
}, async ({ path }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const fileContent = await validAppiumHelper.pullFile(path);
return {
content: [
{
type: "text",
text: `Successfully pulled file from ${path}. Content length: ${fileContent.length} bytes.`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error pulling file: ${error.message}`,
},
],
};
}
});
// Tool: Push file to device
server.tool("push-file", "Push a file to the device", {
path: z.string().describe("Path on the device to write the file"),
data: z.string().describe("Base64-encoded file content"),
}, async ({ path, data }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
await validAppiumHelper.pushFile(path, data);
return {
content: [
{
type: "text",
text: `Successfully pushed file to ${path}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error pushing file: ${error.message}`,
},
],
};
}
});
// Tool: Get battery info
server.tool("get-battery-info", "Get the device battery information", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const batteryInfo = await validAppiumHelper.getBatteryInfo();
return {
content: [
{
type: "text",
text: `Battery level: ${batteryInfo.level * 100}%, State: ${batteryInfo.state} (0: unknown, 1: charging, 2: discharging, 3: not charging, 4: full)`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error getting battery info: ${error.message}`,
},
],
};
}
});
// Tool: Check if element exists
server.tool("element-exists", "Check if an element exists on the current page", {
selector: z.string().describe("Element selector (e.g., xpath, id)"),
strategy: z
.string()
.optional()
.describe("Selector strategy: xpath, id, accessibility id, class name (default: xpath)"),
}, async ({ selector, strategy }) => {
try {
const validAppiumHelper = await getValidAppiumHelper();
if (!validAppiumHelper) {
return {
content: [
{
type: "text",
text: "No active Appium session. Initialize one first with initialize-appium.",
},
],
};
}
const exists = await validAppiumHelper.elementExists(selector, strategy || "xpath");
return {
content: [
{
type: "text",
text: exists
? `Element exists: ${selector}`
: `Element does not exist: ${selector}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error checking if element exists: ${error.message}`,
},
],
};
}
});
// Tool: List iOS Simulators
server.tool("list-ios-simulators", "Get list of available iOS simulators", {}, async () => {
try {
const validAppiumHelper = await getValidAppiumH