ghost-in-the-droid
OfficialServer Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| IOS_BUNDLE_ID | No | Default iOS app/browser bundle, e.g. com.google.chrome.ios or com.apple.mobilesafari | |
| DEFAULT_DEVICE | No | ADB serial (auto-detected if empty) | |
| IOS_APPIUM_URL | No | Appium server URL | http://127.0.0.1:4723 |
| OPENAI_API_KEY | No | LLM features (Skill Creator, Agent Chat) | |
| GITD_ENABLE_IOS | No | Enable iOS support (set to '1' to enable) | |
| IOS_DEVICE_UDID | No | iPhone or simulator UDID; devices are addressed as ios:<udid> | |
| IOS_DEVICES_JSON | No | Per-device iOS config for multiple phones/simulators, WDA ports, bundle IDs, and MJPEG ports | |
| ANTHROPIC_API_KEY | No | Alternative LLM provider | |
| OPENROUTER_API_KEY | No | OpenRouter LLM provider | |
| IOS_MJPEG_SERVER_PORT | No | WDA MJPEG stream port; use one unique port per iOS device |
Capabilities
Features and capabilities supported by this server
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| list_devicesA | List connected Android ADB devices and configured iOS Appium devices. Call this first to get the device serial you need for other tools. |
| screenshotA | Take a screenshot of the device screen. Returns a base64-encoded JPEG. Use this to SEE what's on screen before deciding what to tap. Routes through the shared compressed screenshot path (half-resolution JPEG, cross-platform) instead of a raw full-res PNG: a raw PNG base64 string overflows the MCP tool-result token cap on content-heavy screens, so the client falls back to text/OCR and never sees the pixels. The downscale cuts the payload ~4-8x so most screens stay under the cap. (The lasting fix is returning an MCP image-content block; tracked as feature #8.) |
| get_elementsB | Get all UI elements on the current screen as a JSON array. Each element has: idx, text, content_desc, resource_id, class, bounds, center, clickable, scrollable. Use element idx with tap_element(). Call this to understand the screen layout before acting. |
| tapB | Tap at exact pixel coordinates (x, y) on the device screen. |
| tap_elementA | Tap a UI element by its index from get_elements(). Call get_elements() first to see what's on screen and get element indices. |
| swipeA | Swipe from (x1,y1) to (x2,y2). Use for scrolling, pulling down notifications, etc. Common patterns: scroll down = swipe(dev, 540, 1400, 540, 600) scroll up = swipe(dev, 540, 600, 540, 1400) |
| type_textA | Type text into the currently focused input field. Tap an input field first to focus it. Spaces are supported. Non-ASCII input is transliterated to the closest ASCII (adb input text is ASCII-only); for full-fidelity emoji/CJK use type_unicode() instead. |
| type_unicodeA | Type unicode text into the focused field. Android uses ADBKeyboard when configured; iOS uses WDA text entry. Use type_text() for plain ASCII. |
| press_backC | Press Back on Android or the best available iOS browser/navigation back action. |
| press_homeA | Press the platform Home button. Returns to the home screen. |
| press_keyA | Send a platform key event. Android accepts KEYCODE_* names, with the KEYCODE_ prefix added automatically. iOS supports WDA-backed HOME, ENTER/RETURN, and BACK/ESCAPE. |
| launch_appA | Launch an app by Android package name or iOS bundle id. Use search_apps() to find it. Args: device: ADB serial or ios:. package: App package name or iOS bundle id, e.g. "com.android.chrome" or "com.google.chrome.ios". fresh: If True, force-stop the app first (cold start, clears in-memory state — back stack, unsaved drafts, login flow position, etc.). If False (default), reuses any existing background instance (warm start — resumes wherever the user left off). Use fresh=True for benchmarks, fresh start of a flow, or when the current app state would interfere with the task. |
| force_stopA | Force-stop an Android package or terminate an iOS bundle id. |
| app_stateA | Check whether an Android package or iOS bundle id is installed, running, or foreground. |
| open_cameraA | Open the platform camera app in a specific mode. Android uses launcher/UI automation; iOS uses the Camera bundle and WDA UI controls. No package or bundle id is required. Args: device: ADB serial or ios:. mode: One of: "photo" — rear camera, photo mode (default) "video" — rear camera, video/record mode "selfie" — front camera, photo mode "selfie_video" — front camera, video mode timer_s: Self-timer delay in seconds. Supported: 0 (off), 2, 3, 5, 10. Uses UI automation — snaps to the closest value the device supports (ASUS: 3s/10s, Samsung: 2s/5s/10s). 0 = no timer (default). |
| speak_textA | Make the phone speak text aloud using its built-in TTS engine. Works whether the agent runs on the phone or on a PC — the call always goes through the Ghost portal app running on the device. Args: device: ADB serial. This tool is Android-only. text: Text to speak. rate: Speech rate multiplier (0.5 = slow, 1.0 = normal, 1.5 = fast). |
| search_appsA | Search installed apps by name. Case-insensitive. Returns Android packages or iOS bundle ids. Example: search_apps('tiktok') → [{"name": "TikTok", "package": "com.zhiliaoapp.musically"}] |
| list_appsB | List installed apps with human-readable names and package names or bundle ids. iOS is limited to configured/common bundle ids verified through Appium. |
| list_packagesA | List raw Android package names or iOS bundle ids. Prefer list_apps() for display names. |
| long_pressB | Long press at coordinates. Use for context menus, drag initiation, etc. |
| get_phone_stateA | Get current app, activity, keyboard state, and focused element. Quick way to check what app/screen the device is on without parsing full elements. |
| device_healthC | Run a comprehensive device health check. iOS includes Appium/WDA status, active session details, and recovery steps. |
| fix_device_healthC | Apply a recovery action returned by device_health.recommended_fix. |
| get_screen_treeA | Get an LLM-friendly indented UI hierarchy of the current screen. Each node shows: [idx] ClassName "label" [clickable] [x1,y1][x2,y2]. Use this to understand screen layout and pick which element to tap. Much more readable than raw XML — prefer this over get_elements() for planning. |
| get_screen_xmlA | Get the raw normalized UI XML dump from the device. Android returns uiautomator XML; iOS returns normalized Appium/WDA XML. Use get_screen_tree() instead for a readable summary. Use this only when you need exact attribute values or the full hierarchy. |
| screenshot_annotatedA | Take a screenshot with numbered element labels overlaid on interactive elements. The numbers correspond to element indices from get_elements(). Use this when you want to SEE the screen with elements visually labelled. Returns base64-encoded PNG. |
| screenshot_croppedA | Take a screenshot of a specific region of the screen. Coordinates are in device pixels. Use this to zoom in on a specific area (e.g., a form field, a notification, a chart). Returns base64-encoded JPEG. |
| start_screen_recordingB | Start recording the device screen. iOS uses WDA MJPEG captured through ffmpeg. Android uses adb screenrecord. |
| stop_screen_recordingC | Stop a running device screen recording and save the MP4. |
| screen_recording_statusB | Return active screen recording status for a device. |
| get_stream_infoB | Return effective stream metadata without opening the stream. iOS reports WDA MJPEG URL/settings, screenshot fallback, and unsupported Portal/WebRTC actions. Android reports Portal/H264/screencap metadata. |
| ocr_screenA | OCR the entire device screen using RapidOCR. Returns all visible text with positions. Use this when UI elements are rendered as images/canvas (e.g., analytics dashboards, games, WebViews) where get_elements() returns no text. Returns JSON array of {text, conf, x, y, w, h} sorted top-to-bottom. |
| ocr_regionA | OCR a specific region of the screen. Coordinates in device pixels. More accurate than full-screen OCR for targeted text extraction. Returns JSON array of {text, conf, x, y, w, h} relative to the crop region. |
| classify_screenA | Classify the current screen: what app, what type of screen (home, search, profile, settings, dialog, error, loading), keyboard state. No LLM needed — uses XML heuristics. Use this for quick state checks before deciding what action to take. |
| toggle_overlayA | Toggle the numbered element overlay on the device screen. When on, interactive elements get visible numbered labels that match get_elements() indices. Useful for visual debugging or when sending screenshots to a vision model. |
| clipboard_getC | Get the current clipboard text from the device. |
| clipboard_setA | Set clipboard text on the device. Use with press_key(PASTE) to paste into fields. |
| paste_textA | Set clipboard text and immediately paste it into the currently focused field. Equivalent to clipboard_set + press_key(PASTE) in one call. Tap the target input field first to focus it, then call this. |
| get_notificationsA | Get active notifications. Returns JSON array of {package, title, text}. |
| open_notificationsC | Pull down the notification shade or iOS Notification Center. |
| clear_notificationsA | Dismiss visible notifications when the platform exposes a clear control. |
| web_searchA | Open a web search in whatever browser is on the device. Faster than: launch Chrome → tap address bar → type → submit. Useful when the user asks "search for X" or you need to look up info that's not on the current screen. Picks the first installed browser from a fallback chain (Chrome → Firefox → Samsung Internet → Edge → Brave → Opera → Vivaldi → DuckDuckGo Browser → system default), so it works even if Chrome is missing. Args: device: ADB serial or ios:. query: Free-text search terms (don't pre-escape — handled here). engine: "google" (default), "ddg" / "duckduckgo", "bing", or "brave". bundle_id: Optional iOS browser bundle id override, e.g. com.google.chrome.ios. |
| open_urlA | Open a URL in the platform browser. On iOS this uses Appium/WDA and defaults to the configured browser bundle id, usually com.google.chrome.ios or com.apple.mobilesafari. |
| browser_backC | Navigate back in the current browser/app context. |
| get_current_urlC | Get the current browser URL when the platform exposes it. |
| wait_for_textC | Wait until text appears on screen and return visible text context. |
| extract_visible_textA | Extract visible text from the current screen with browser chrome filtered by default. |
| extract_articlesC | Extract likely visible article/headline candidates from the current browser page. |
| read_newsC | Open a news page and return structured headlines plus article snippets. This is the iOS Chrome/WebDriver smoke workflow exposed as a single tool. |
| launch_intentA | Launch a full Android intent. More powerful than launch_app(). Examples: Open a URL: action="android.intent.action.VIEW" data="https://google.com" Open Settings: package="com.android.settings" Share text: action="android.intent.action.SEND" extras='{"android.intent.extra.TEXT": "hello"}' |
| find_on_screenA | Find specific text on the screen and return its location. Searches XML elements first (fast), falls back to OCR if not found. Use this to check if a button, label, or message is visible. Returns JSON with {text, x, y, w, h, method} or null if not found. |
| list_skillsA | List all installed mobile automation skills with their actions, workflows, and platform support. Use this to discover what high-level automations are available. Prefer using run_workflow() over raw tap/swipe when a skill exists for the task. |
| run_workflowA | Run an installed skill workflow on the device. Call list_skills() first to see available skills and workflows. Examples: run_workflow("SERIAL", "tiktok", "upload_video", '{"video_path": "/tmp/video.mp4"}') run_workflow("SERIAL", "send_gmail_email", "recorded", '{"subject": "Hello", "body": "Test"}') params is a JSON string of keyword arguments for the workflow. |
| run_actionA | Run a single skill action on the device. Call list_skills() first to see available actions. Examples: run_action("SERIAL", "tiktok", "open_app", '{}') run_action("SERIAL", "tiktok", "type_and_search", '{"query": "cats"}') |
| run_flowA | Run an ordered batch of tool calls server-side in ONE round-trip.
Example: run_flow("SERIAL", '[{"tool":"launch_app","args":{"package":"com.android.settings"}}, {"tool":"find_on_screen","args":{"text":"Wi-Fi"}}, {"tool":"tap","args":{"x":540,"y":300}}]') Security: fail-closed allow-list — only vetted read/UI tools may run inside
a flow. Anything else (raw |
| list_crashesA | List recent app crashes and ANRs (from the logcat crash buffer, no root). Android-only: iOS crash logs need syslog/CrashReporter access, not exposed yet. Optionally filter by package (substring match on the crashing process). Returns JSON: {count, crashes: [{type, timestamp, process, summary}]}. Use get_crash() to pull a full stack for the most recent one. |
| get_crashA | Return the full stack trace of the MOST RECENT crash (from the crash buffer). Android-only: iOS crash logs need syslog/CrashReporter access, not exposed yet. Optionally filter by package. Pair with list_crashes() to see what's there. |
| waitB | Pause execution for a fixed number of seconds. |
| explore_appA | Explore an app's UI autonomously using BFS. Launches the app, taps every interactive element, builds a state graph. Returns JSON with discovered screens, elements, and transitions. Use this to understand an unfamiliar app before writing automation for it. |
| create_skillA | Create a new reusable skill from a JSON list of recorded steps. steps is a JSON array like: [ {"action": "launch", "package": "com.example.app", "description": "Open app"}, {"action": "tap", "x": 540, "y": 1200, "description": "Tap button"}, {"action": "type", "text": "hello", "description": "Type greeting"}, {"action": "wait", "seconds": 2, "description": "Wait for load"} ] Supported actions: launch, tap (x,y or element_idx), type, swipe, back, home, wait. For iOS skills, pass platforms="ios" and either app_package or ios_bundle_id as the bundle id. Optional elements_ios/elements_android are JSON selector maps written to elements_ios.yaml/elements.yaml. After creating, use run_workflow(dev, name, "recorded", params) to replay it. |
| draft_skillA | Distil the device's most recent chat conversation into draft replayable steps for a HARD skill. Returns the captured steps (with correct coords/args), a guessed app_package, and a summary. Nothing is written — review/prune/rename the steps, then call save_skill(kind="hard", steps=...). |
| save_skillA | Save the device's current chat conversation as a reusable skill. kind="hard" replays concrete actions — pass revised |
| lookup_leadA | Get the full fact sheet for one influencer lead by handle. Use this when you need to know everything about an influencer to draft a personalised reply or decide next-step outreach: their follower count, engagement, bio, niche, what hashtag we found them on, when we DMed them, which account sent the DM, their latest reply, and unread state. Args: handle: TikTok username, with or without @ (e.g. 'creatorhandle' or '@creatorhandle') |
| list_unread_leadsA | List every influencer with an unread reply in the inbox, sorted by recency. Returns one row per unread conversation with the handle, unread count, last message preview, and timestamp. Useful for daily prioritisation: 'which leads should I respond to right now?' |
| crm_lookup_contactA | Get the stored fact sheet for one local CRM contact by handle. Read-only. Returns the contact's profile fields, contact status/history, and the latest conversation state (last message, unread count, timestamps). Args: handle: Contact handle, with or without @. |
| crm_list_unread_messagesA | List local CRM contacts with unread messages, sorted by recency. Read-only. Returns one row per unread conversation with the handle, unread count, last message preview, and timestamp. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/ghost-in-the-droid/android-agent'
If you have feedback or need assistance with the MCP directory API, please join our Discord server