gwagent
Provides tools for using Gmail through the Android app UI, including searching messages, reading threads, sending and replying with confirmation safeguards, and chaining structured Gmail operations.
Provides tools for managing Google Calendar events via the Android Calendar app and Android calendar content provider, including listing, creating, and updating events.
Provides tools for interacting with Google Drive through the Android app UI, including listing and searching files, downloading files and attachments, and reading text from supported documents such as PDFs.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@gwagent check my unread Gmail and tell me what needs a reply"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
gwagent
Let an AI agent use Gmail, Google Calendar and Google Drive on an Android phone or emulator, through adb, the way a person would: by driving the apps' own UI (Gmail, Drive, Calendar app screens) and, where Android allows it, Android's calendar content provider.
It is meant for a Google Workspace account that can only be used from the phone (no API access): the agent gets about 150 structured operations, each returning JSON, instead of raw taps.
One registry, two front ends: every operation is a Python function declared with
@op; the CLI (gwagent ...) and the MCP server (gwagent mcp) are generated from it.Structured results: rows with indexes, events with ids, files with paths, so an agent can chain calls (search -> read -> reply, list -> download, events -> update one occurrence).
Safety built in: destructive or outward actions need
confirm=true; index-based targets are checked against the list they came from; a Gmail send is never repeated blind.Fast path: a small on-phone server (a
.dexrun withapp_process, not an installed app) makes UI dumps take ~50 ms instead of 4-10 s and lets any Unicode text be typed.
Documents:
File | For |
An AI agent that receives this system: workflows, ids, errors, etiquette | |
Every operation with its parameters (generated from the registry) | |
Moving the system to another machine or phone; | |
Rules for adding or changing operations |
Requirements
macOS or Linux host with Python >= 3.12 and uv.
Android platform-tools (
adb) onPATH, or pointed to withGWAGENT_ADB.An Android device reachable by adb: the Android emulator with a Google Play system image (developed on Android 15 / API 35, x86_64), or a real phone with USB debugging enabled. The phone's UI language must be English (ops match English labels).
A Google (Workspace) account signed in on the phone, with Gmail, Calendar and Drive installed. A Workspace account usually forces a screen lock: gwagent unlocks it with a PIN (see below).
Optional:
pdftotext(poppler) on the host, fordrive.read_texton PDFs.To rebuild the on-phone server only: a JDK and Android build-tools (see
dumpserver/build.sh).
Related MCP server: DroidPilot
Install
git clone <this repo> gwagent && cd gwagent
uv sync # creates .venv with gwagent and its dependencies
uv run gwagent device.status --pretty # check that the phone is reachableuv run gwagent ... works from the checkout. To have a gwagent command everywhere:
uv tool install --editable ..
On a new machine or phone, follow PORTING.md and run scripts/bootstrap.sh.
Configuration (environment variables)
All configuration comes from the environment; nothing personal is stored in the repository.
Variable | Default | Meaning |
|
| adb serial of the phone to drive |
|
| adb binary |
| - | Screen-lock PIN (prefer the PIN file) |
|
| File holding the PIN (mode 600); never print or commit it |
|
| Where downloads, attachments and screenshots go on the host |
|
| Host state: last Gmail list, last Drive location, last Gmail action (for undo) |
|
| Time zone for calendar output and for times given without an offset |
| primary calendar | Default calendar id for |
|
| UI lock file (see UI lock) |
|
| Seconds to wait for the UI lock before failing with |
| unset |
|
| unset |
|
|
| Keep the screen awake during long ops; |
|
| Wait for an offline phone and repeat retry-safe ops once; |
|
| Max seconds to wait for the phone to come back |
| unset | Operator hook (shell command) run once when the phone stays unreachable for 90 s after |
|
| Max seconds the |
|
|
|
|
| Seconds after which an idle on-phone server exits |
|
| Seconds to wait for the on-phone server to start |
|
| Seconds of silence before the server is considered stuck and restarted |
| unset |
|
|
| Android SDK used by |
Test-only variables: GWAGENT_LIVE=1 / GWAGENT_OUTWARD=1 (same as --live / --outward),
GWAGENT_REGEN_MANIFEST=1 and GWAGENT_REGEN_OPS_DOC=1 (regenerate golden files),
GWAGENT_PRIVACY_TERMS (extra comma-separated terms for the fixture privacy scan).
GWAGENT_DUMP_NONCE is set by gwagent itself when it starts the on-phone server.
CLI
Output is always one JSON object on stdout; the exit code is 0 on success and an error-specific code otherwise (see docs/OPERATIONS.md#error-codes).
uv run gwagent list # every operation (name, summary, ui, confirm, params)
uv run gwagent list --app gmail --names # just the names of one app's ops
uv run gwagent describe gmail.search # one op's full description and JSON schema
uv run gwagent gmail.search --query "is:unread" --limit 5
uv run gwagent gmail search --query "is:unread" # app and action as two words
uv run gwagent call gmail.read '{"index": 0, "expect_subject": "Weekly report"}'
uv run gwagent gmail.send --to alice@example.com --subject Hi --body "Ciao" --confirm
uv run gwagent calendar.events --period week --pretty
uv run gwagent mcp # MCP server on stdioFlags: --name value, --name=value, bare --flag (true), --no-flag (false); a repeated flag
becomes a list (--to a@example.com --to b@example.com, or --to a@example.com,b@example.com, or
a JSON list). --args-json '{...}' merges a JSON object; --pretty indents the output.
Result envelope:
{"ok": true, "op": "gmail.search", "result": {"rows": [...]}, "elapsed_s": 6.1}
{"ok": false, "op": "gmail.reply", "error": "not_found", "message": "...", "elapsed_s": 3.2}MCP server
gwagent mcp runs a stdio MCP server exposing every op as a tool; the tool name is the op
name with . replaced by _ (gmail.search -> gmail_search). Tool results are the same
JSON envelope as the CLI.
Claude Code:
claude mcp add gwagent -- uv --directory /path/to/gwagent run gwagent mcpAny MCP client (Claude Desktop claude_desktop_config.json, a project .mcp.json, ...):
{
"mcpServers": {
"gwagent": {
"command": "uv",
"args": ["--directory", "/path/to/gwagent", "run", "gwagent", "mcp"],
"env": {
"GWAGENT_SERIAL": "emulator-5554",
"GWAGENT_PIN_FILE": "/Users/you/.android-ufficio-pin"
}
}
}
}Give the agent AGENT_GUIDE.md (e.g. reference it from the client's project
instructions). Ops can take tens of seconds; set the client's tool timeout to at least 5 minutes
(MCP_TOOL_TIMEOUT=300000 for Claude Code), since a UI op may also wait for the UI lock.
How it works
src/gwagent/
adb.py, device.py adb wrapper; Device: unlock, tap/swipe/type, dump -> Screen, wait_for, recover
ui.py Screen/Node/Selector: parsing and querying UI dumps
fastdump.py client of the on-phone server (assets/gwagent-dump.dex)
lock.py the inter-process UI lock
registry.py @op, Context, call() -> {"ok": ...} envelope, offline recovery
cli.py, mcp_server.py generated front ends
ops/ gmail*.py, calendar.py (provider), calendar_ui.py (app), drive*.py, device.py
dumpserver/ Java source of the on-phone server; build.sh -> src/gwagent/assets/gwagent-dump.dexFast path: the on-phone server
Device.dump/tap/swipe/key/type_text go through a persistent server on the phone that holds
one UiAutomation connection. It is a .dex pushed to /data/local/tmp and started with
app_process (shell user); it is not an APK and nothing is installed. A UI dump takes
~20-60 ms instead of 4-10 s, a tap a few ms instead of ~1 s, and type_text sets text through
accessibility, so any Unicode works in every field. If the server is unavailable, everything
falls back to uiautomator dump and adb shell input automatically. It exits after 45 s idle
and is restarted on demand (also after an emulator restart).
Transport and trust:
The server listens on an abstract unix socket (not a TCP port) with a random name
gwagent-dump-<16 hex>chosen at each start; the host reaches it throughadb forward tcp:<port> localabstract:<name>.Server checks the client: only peers with uid 2000 (shell, which is what adbd forwards as) or 0 are served (
getPeerCredentials); other apps on the phone are refused.Client checks the server: the abstract namespace has no permissions, so an app could bind a name and pose as the server. Each start gets a random nonce (passed to the server in its environment); the name and nonce are kept in
/data/local/tmp/gwagent-dump.token(mode 600, shell only). The client accepts the server only ifversionanswers"<PROTO> <nonce>".The protocol is versioned (
fastdump.PROTO=DumpServer.PROTO, currently 5); a running server with another version is replaced. Run one gwagent version per phone.
Safety model
confirm: ops that send, delete, share or are otherwise outward-facing or irreversible are marked
requires_confirm; withoutconfirm=truethey fail withconfirmation_requiredand do nothing. Some ops need it only in some cases (calendar.create/calendar.updateon events with guests, since Google e-mails them).Guards against acting on the wrong item: the last Gmail list (folder/query and each row's sender, subject, date) is remembered on the host. An op given a bare
indexre-opens that list and checks the row is still the same conversation; moved rows are found again, vanished ones fail withnot_foundbefore anything is touched.expect_subject(Gmail read/reply/forward/ attachments/...) andmatch(bulk actions) add explicit checks. Drive ops resolve names in the remembered location, not in whatever the screen shows.drive.empty_trashrequiresonly_if_all_matchorall=true.Recipients are verified on the compose screen before Send: any unexpected or missing address keeps the mail as a draft and raises an error.
Send is never re-sent blind: when Gmail's reaction to Send is unclear, the mail is looked up in Outbox / Sent; if that cannot tell, the op fails with
send_state_unknownand the caller must check the Sent folder before any retry. Ops withconfirmare never repeated automatically, not even after the phone went offline.Calendar out of office (
calendar.out_of_office) only declines new invitations by default (decline="only_new"); meetings already accepted are declined (and their organizers notified) only withdecline="new_and_existing".UI lock: only one program drives the phone at a time (see below).
Live tests only create objects whose name or subject contains
AGT-TEST, send mail only to the phone's own account, add no guests to test events, and delete what they create. Tests that change account settings skip when the user has that setting in use and restore it afterwards.Privacy: the account is a real office account. No mail, calendar or Drive content, no screenshots or UI dumps of the phone, and no account address may be sent to cloud LLMs or committed; test fixtures are scrubbed (
tests/unit/test_fixture_privacy.pyscans them). The PIN is never printed, logged or committed. Run the agent that uses gwagent on a model the data owner has approved for this content.No APKs: nothing is installed on the phone. The only on-phone code is the dump server
.dexrun withapp_process. (device.install_adb_keyboardexists for other phones only.)
UI lock
Every ui=true op holds an exclusive flock on /tmp/gwagent-ui.lock (override
GWAGENT_LOCK_FILE) for its whole duration; it waits up to GWAGENT_LOCK_TIMEOUT seconds
(default 300), then fails with busy (exit 7). ui=false ops (calendar provider, file
transfers) do not take it. Other programs that touch the phone's UI must take the same lock;
the exact contract and a wrapper for shell commands are in
docs/OPERATIONS.md#ui-lock-contract.
Tests
Level | Command | Needs |
Unit |
| nothing (FakeAdb + scrubbed fixtures), ~seconds |
Live |
| the phone; creates and deletes |
Live + outward |
| the phone; also sends mail to the account itself and changes settings (restored) |
Quality |
| no new broad |
The whole suite (uv run pytest --live --outward) takes several hours. Never run two live
sessions at once: there is only one phone. One module at a time:
uv run pytest tests/live/test_gmail_read_live.py --live --outward -q.
Golden files checked by unit tests: tests/fixtures/mcp_manifest.json (the MCP tool list,
regenerate with GWAGENT_REGEN_MANIFEST=1 uv run pytest tests/unit/test_mcp_manifest.py -q)
and docs/OPERATIONS.md (regenerate with uv run python scripts/gen_ops_doc.py).
Limitations
Speed: ops drive the real UI; typical ops take ~5-35 s, long lists and uploads minutes. Host performance matters most: on macOS the emulator must run as an interactive process (see PORTING.md).
English UI only; one phone, one account, one gwagent version per phone.
Google Docs/Sheets/Slides cannot be created or edited (their apps are not installed and installing apps is not allowed):
drive.create_docfails withapp_missing. Native Google files can only be read; a Google Sheet is read through Drive's export, which is a PDF (table structure lost). The Drive app has no "Manage versions".drive.read_textwithmethod="viewer"only sees the pages the viewer rendered (usually the first); the default download method needspdftotextfor PDFs.Drive search filters use search operators (
type:,owner:,after:,before:,is:starred); the app's filter chips are not used.drive.set_role/drive.unsharecould only be live-tested on their guards (single-account phone).Gmail dates are parsed from screen text (English formats, phone time zone):
date_isonext to the raw value. Gmail bulk actions beyond one screen needall=truewith a query or folder.gmail.undorecalls a sent mail only while Gmail still shows the Undo snackbar (seconds).Gemini in Gmail depends on the account's licence (
gemini_unavailableotherwise).Calendar: working locations, Out of office and Focus time are not in the provider (they are set, read and deleted through the app). Avoid rapid successive edits of one event (an edit made while the previous one uploads can be overwritten by the server copy; check
pending_sync). A recurring series that never synced has no instances until it syncs.Google Tasks are only reachable through the Calendar app UI.
Attribution
Parts of the on-phone server and a few host helpers are adapted from google/artemis, Copyright 2026 Google LLC, licensed under the Apache License 2.0. Adapted code carries a comment saying so; the list of adapted files is in NOTICE and the license text in LICENSES/Apache-2.0.txt. Artemis itself is not used or run.
License
Apache License 2.0: see LICENSE. Third-party attributions are in NOTICE.
Available Tools
156 toolsapp_intentC
Start an activity with an arbitrary intent (am start).
Inputs: data, mime, action, extras, +2 more.
app_launchB
Bring an app to the foreground.
Returns the focused window (package, activity, window) plus state: front (the app has
the focus), covered (the app is in front under a dialog of another package, e.g. a
permission prompt), other (another app is in front) or unknown.
Inputs: package (required), restart, activity.
app_stopB
Force-stop an app.
Inputs: package (required).
calendar_add_meetA
Add a Google Meet video conference to one of your events (the provider cannot create one).
Guests are not e-mailed about the change ("Don't send" is chosen if asked). Returns the Meet link once the provider has it.
Inputs: event_id (required), scope, start, timeout.
calendar_attendee_addA
Invite people to an event (Google e-mails them). Returns the attendee list.
Requires confirm=true (outward-facing or destructive).
Inputs: emails (required), event_id (required), confirm, optional.
calendar_attendee_removeA
Remove guests from an event (Google may e-mail them a cancellation). Returns what is left.
Requires confirm=true (outward-facing or destructive).
Inputs: emails (required), event_id (required), confirm.
calendar_calendarsA
List the calendars on the phone (id, name, account, colour, access level, visible, sync).
Use the id (or the name) as the calendar parameter of other calendar ops.
primary marks the account's own calendar (default target of calendar.create).
Inputs: visible_only, writable_only.
calendar_createB
Create an event (synced to Google Calendar). Returns the new event (see calendar.get).
Inputs: start (required), title (required), end, color, +11 more.
calendar_deleteA
Delete an event (the whole series for recurring events, or one occurrence). Synced to Google.
An event you organise that has guests: Google cancels it for everybody and e-mails them.
An invitation from someone else: it is removed from your calendar only; Google records it as declined (the organizer can see that). To decline explicitly, use calendar.respond.
With
instance_start, only that occurrence of a recurring event is canceled (the series stays; guests of a series you organise are told about that occurrence).
Requires confirm=true (outward-facing or destructive).
Inputs: event_id (required), confirm, instance_start.
calendar_delete_specialA
Delete a working location, Out of office or Focus time entry of one day in the Calendar app.
These entries are not in the calendar provider (calendar.delete cannot see them). For a working location only that day is removed (your weekly setting stays for other days; the day then has no working location). A multi-day Out of office is deleted as a whole.
Requires confirm=true (outward-facing or destructive).
Inputs: date (required), entry (required), title, confirm.
calendar_eventsA
List event occurrences in a time range (recurring events are expanded into instances).
Range: period, or start + (end | days). With nothing given: from today 00:00 for 7 days.
Covers every calendar synced to the phone, including calendars hidden in the app (unless
include_hidden=false). Calendars with sync off have no events on the phone: with
include_unsynced=true they are synced for the duration of the call (slow: about a minute)
and their sync is turned off again afterwards.
Inputs: end, days, limit, query, +9 more.
calendar_focus_timeA
Create a Focus time block in the Calendar app (colleagues see it; meetings can be auto-declined).
Not in the calendar provider: read back from the app, removed with calendar.delete_special.
Requires confirm=true (outward-facing or destructive).
Inputs: start (required), end, title, confirm, +4 more.
calendar_free_busyA
Busy blocks (merged) and the free gaps between them in a time range.
Counts events marked busy (and tentative, unless disabled), skipping canceled events and
events you declined. All-day events are ignored unless include_all_day. Every calendar
synced to the phone counts (hidden ones too, unless include_hidden=false); calendars with
sync off are listed in unsynced_calendars, or synced for the call with include_unsynced.
Inputs: end, days, start, period, +7 more.
calendar_getA
Get one event (whole series for recurring events) with attendees and reminders.
Inputs: event_id (required).
calendar_meet_linkB
The Google Meet link of an event, if it has one (provider only, no UI).
Inputs: event_id (required).
calendar_nextB
The next upcoming (or currently running) event occurrences, soonest first.
Inputs: days, count, calendar, include_hidden, +3 more.
calendar_openA
Show a date in the Calendar app (optionally switching view) and read what is visible.
Returns the view, the toolbar title and the visible items (or, in month view, the number of items of each day). Use calendar.view_screen to read whole days in detail.
Inputs: date, view.
calendar_open_eventA
Open an event in the Calendar app by provider id and read its screen.
Returns what the app shows: when, recurrence, Google Meet link and dial-in, location, notifications, calendar, guests with their answers, notes and your RSVP state (rsvp.available, rsvp.response = yes/no/maybe/null). The event must be synced with Google.
Inputs: event_id (required), start, stay_open.
calendar_out_of_officeA
Create an Out of office block in the Calendar app (colleagues see it; meetings can be auto-declined).
Google shows it to people who look at your calendar and, with auto_decline, declines new
meeting invitations in that time, sending message to the organizers. Meetings you already
accepted are declined too only when asked explicitly with decline='new_and_existing' (it
notifies their organizers); the default is 'only_new'. Out of office is not in the calendar
provider: it is read back from the app and removed with calendar.delete_special.
Requires confirm=true (outward-facing or destructive).
Inputs: start (required), end, title, all_day, +4 more.
calendar_reminder_addB
Add a reminder to an event. Returns the event's reminders.
Inputs: minutes (required), event_id (required), method.
calendar_reminder_removeA
Remove reminders from an event (by minutes, by reminder_id, or all). Returns what is left.
Inputs: event_id (required), all, minutes, reminder_id.
calendar_remove_meetA
Remove the Google Meet conference from one of your events (the link stops working).
Requires confirm=true (outward-facing or destructive).
Inputs: event_id (required), scope, start, confirm, +1 more.
calendar_respondA
Answer an invitation: set your own attendee status (the organizer is notified by Google).
Requires confirm=true (outward-facing or destructive).
Inputs: status (required), event_id (required), confirm, instance_start.
calendar_rsvpA
Answer an invitation with the Calendar app's Yes / No / Maybe buttons (organizer is notified).
Unlike calendar.respond (provider), this goes through Google's servers at once and can answer a single occurrence of a recurring event (scope 'this'). The result is checked on the provider after the app syncs. Read the current answer first with calendar.open_event.
Requires confirm=true (outward-facing or destructive).
Inputs: event_id (required), response (required), scope, start, +2 more.
calendar_searchA
Search events (series, not occurrences) by text in title, description or location.
Searches all calendars, including hidden ones, newest first. For recurring events the
start/end are those of the first occurrence; use calendar.events with query to get the
concrete occurrences in a range.
Inputs: query (required), end, limit, start, +2 more.
calendar_set_calendarA
Show/hide a calendar in the Calendar app, or turn its sync to the phone on/off.
Hidden calendars are still synced (calendar.events includes them). A calendar with sync off has no events on the phone; turning sync on downloads them (run calendar.sync_background). These are phone settings of your calendar list, not changes to the events.
Inputs: calendar (required), sync, visible.
calendar_set_working_locationA
Set your working location (Home, Office or any place) for one day or a range of days.
Colleagues see it in Calendar/Gmail/Chat. It applies to these days only (your weekly working-location settings stay); to undo, set the previous place again or use calendar.delete_special (the day then has no working location). Read with calendar.working_location.
Requires confirm=true (outward-facing or destructive).
Inputs: date (required), place (required), confirm, end_date.
calendar_syncA
Force a Calendar sync with Google and wait for it to finish.
New server events then show in the provider (calendar.events) and local changes are uploaded. method auto (default): Android's sync manager first (fast, no screen), the app's Refresh if no sync started; background: sync manager only (same as calendar.sync_background); ui: the app's Refresh (drawer). The sync start is verified on Android's sync manager.
Inputs: wait, method, timeout.
calendar_sync_backgroundA
Sync the calendar with Google without the screen (Android sync manager, requestsync).
Pushes local changes (create/update/delete/respond) and pulls server changes; works with the phone locked and the screen off. calendar.sync does the same and falls back to the app.
Inputs: wait, timeout.
calendar_task_completeA
Mark a Google Task as completed (or not completed again with undo=true).
Inputs: title (required), list, undo, occurrence.
calendar_task_createC
Create a Google Task (shown in Calendar on its date).
Inputs: title (required), date, list, time, +2 more.
calendar_task_deleteA
Delete a Google Task.
Requires confirm=true (outward-facing or destructive).
Inputs: title (required), list, confirm, completed, +1 more.
calendar_task_list_createB
Create a new Google Tasks list (it becomes the list shown).
Inputs: list (required).
calendar_task_list_deleteA
Delete a Google Tasks list and all its tasks (the default list cannot be deleted).
Requires confirm=true (outward-facing or destructive).
Inputs: list (required), confirm.
calendar_task_listsC
The Google Tasks lists (names usable as list in the other task ops).
Args:
Inputs: no named parameters.
calendar_tasksA
List Google Tasks of one list as shown in the Calendar app (tasks are not in the provider).
Each task: title, due (as shown, e.g. 'Tomorrow, 9:15 AM'), completed, starred, notes, subtask (true for a subtask, listed right after its parent). Tasks are addressed by title (+ occurrence when several share it) in the other task ops.
Inputs: list, include_completed.
calendar_task_updateA
Edit a Google Task: rename, change/remove its date and time, notes, subtasks, star, list.
Returns the task as it is now (title, list, notes, due, subtasks, starred) plus
occurrence, so the new identity (new_title + occurrence) can be used in later calls.
Inputs: title (required), date, list, time, +8 more.
calendar_updateA
Change fields of an event (whole series for recurring events, or one occurrence). Omitted fields are kept.
Changing only start keeps the duration; only end/duration_minutes keeps the start.
Pass an empty string to clear location/description; rrule='none' turns a series into a
single event (its first occurrence). With instance_start only that occurrence of a
recurring event changes (e.g. move tomorrow's stand-up): the result is the occurrence's own
event (recurrence_exception_of = the series id).
Guests: changing title, time, location, description or recurrence of an event you organise that has guests makes Google e-mail them the update, so it needs confirm=true. Invitations from someone else cannot be changed (only your availability/colour, and reminders with calendar.reminder_*): answer them with calendar.respond / calendar.rsvp.
Avoid rapid successive edits of the same event: an edit made while the sync adapter is
uploading the previous one can be overwritten by the server copy (check pending_sync).
Inputs: event_id (required), end, color, rrule, +10 more.
calendar_view_screenA
Read what the Calendar app shows for one or more days, as structured rows (schedule view).
Shows things the provider lacks: working locations, tasks, the RSVP shown for each event. Rows: kind (event / working_location / task / out_of_office / focus_time), title, all_day, time, start, end (HH:MM), response (Not responded / No / Maybe..., absent when you accepted or organise it), location, calendar (absent for your own calendar), color, and event_id/instance_id matched from the provider (use them with calendar.open_event / calendar.rsvp). If the provider has events the app does not show yet, the app is refreshed and read again; those still missing are listed in not_shown_event_ids.
Inputs: date, days, include, match_ids.
calendar_working_locationB
Your working location (Office, Home, ...) for each day, as shown by the Calendar app.
Working locations are not in the provider. Days without one are reported with place null.
Inputs: date, days.
device_accountsB
Accounts registered on the phone (name and type).
Inputs: no named parameters.
device_install_adb_keyboardA
Install ADBKeyboard (not needed: the on-phone server types any Unicode; the office phone forbids APK installs).
Requires confirm=true (outward-facing or destructive).
Inputs: apk_path (required), confirm.
device_lsB
List files in a phone directory.
Inputs: remote_dir.
device_pullA
Copy a file from the phone to this computer (default: the gwagent downloads dir).
Inputs: remote_path (required), local_path.
device_pushB
Copy a file from this computer to the phone (default: the phone's Download folder).
Inputs: local_path (required), remote_path.
device_rmA
Delete a file from the phone's shared storage (/sdcard only).
Requires confirm=true (outward-facing or destructive).
Inputs: remote_path (required), confirm.
device_shellA
Run a raw adb shell command on the phone (escape hatch for anything not covered).
Inputs: command (required), timeout.
device_sleepA
Turn the screen off (re-locks the phone). Use when done, to save CPU on the host.
Inputs: home.
device_statusB
Phone status: adb state, Android version, screen on/locked, foreground app, app versions.
Inputs: no named parameters.
device_unlockA
Wake the screen and unlock it with the configured PIN (every UI op does this automatically).
Inputs: no named parameters.
drive_cdA
Enter a folder: by path relative to the current location ("A/B/C", ".." = parent, leading "/" = from My Drive) or by index from the last list. Returns the folder's items.
Inputs: path, index, limit.
drive_copyC
Make a copy of a file ("Make a copy"); the copy is named "Copy of ".
Drive is left in the destination folder, where the copy is checked.
Inputs: name, path, index, occurrence, +1 more.
drive_create_docA
Create a new Google Docs/Sheets/Slides file (Create new > Google Docs...).
Drive hands the creation to the Docs/Sheets/Slides app: when that app is not installed
(as on this phone) Drive opens the Play Store instead and this op fails with
app_missing without creating anything.
Inputs: kind, path.
drive_delete_foreverA
Permanently delete an item that is in the Trash (cannot be undone).
Requires confirm=true (outward-facing or destructive).
Inputs: name, index, confirm, occurrence.
drive_downloadA
Download a file with Drive's "Download" (Google Docs/Sheets/Slides are exported by the app, e.g. a Google Doc as PDF) into the phone's Download folder, then copy it to this computer.
Inputs: name, path, index, timeout, +3 more.
drive_empty_trashA
Permanently delete everything in the Trash ("Empty trash"; cannot be undone).
Give only_if_all_match or all=true (invalid_argument otherwise, nothing touched): the Trash can hold items the user trashed by hand, so emptying it unconditionally must be explicit.
Requires confirm=true (outward-facing or destructive).
Inputs: all, confirm, only_if_all_match.
drive_infoA
Details and activity of an item ('View information'): type, size, location, created, modified (by whom), sharing summary, recent activity.
Inputs: name, path, index, occurrence, +1 more.
drive_listA
List the items of the current Drive location (folder, section or search results).
Scrolls and de-duplicates; switches grid view to list view (richer info). Indexes are usable by drive.cd / drive.info / drive.download / drive.read_text.
Inputs: path, limit, start, section.
drive_locationB
Where Drive is now: screen kind, title, sort order, view mode and remembered folder path.
Inputs: no named parameters.
drive_mkdirB
Create a new folder in the current location (or in path).
Inputs: name (required), path.
drive_moveB
Move a file or folder to another folder of My Drive.
Inputs: destination (required), name, path, index, +1 more.
drive_offlineB
Make a file available offline on the phone (enable=true) or remove offline access.
Inputs: name, path, index, enable, +1 more.
drive_openC
Open a Drive section and list its first items.
Inputs: limit, section.
drive_read_textA
Read the text of a file (PDF pages, document, slides, sheets, plain text).
'download' downloads/exports the file and extracts its text on this computer (nothing is left on the phone); 'viewer' opens it in Drive's viewer and reads the text it exposes; 'auto' tries download, then the viewer.
Inputs: name, path, index, method, +3 more.
drive_refreshA
Pull-to-refresh the current Drive list (re-sync from the server) and list it again.
Inputs: limit.
drive_renameC
Rename a file or folder.
Inputs: new_name (required), name, path, index, +1 more.
drive_restoreA
Restore an item from the Trash to its original folder.
Inputs: name, index, occurrence.
drive_searchB
Search Drive (file names and content) and return the matching items, optionally filtered by type, owner and modification date.
Inputs: type, limit, owner, query, +4 more.
drive_set_roleA
Change the role of a person who already has access to a file or folder (Manage access).
Requires confirm=true (outward-facing or destructive).
Inputs: role (required), email (required), name, path, +3 more.
drive_sharingA
Read an item's sharing (changes nothing): people with access (name, email, role), general access ('Restricted'...) and the item's link and file id.
Inputs: name, path, index, copy_link, +1 more.
drive_sortA
Change the sort order of the current list (Drive remembers it per view).
Inputs: by, reverse.
drive_starC
Add a file or folder to Starred.
Inputs: name, path, index, occurrence.
drive_storageB
Drive storage used, and optionally the files using the most storage.
Inputs: limit.
drive_trashA
Move a file or folder to the Trash (restorable with drive.restore for 30 days).
Requires confirm=true (outward-facing or destructive).
Inputs: name, path, index, confirm, +1 more.
drive_unstarB
Remove a file or folder from Starred.
Inputs: name, path, index, occurrence.
drive_upB
Go to the parent folder (or leave a section/search back to the main screen).
Inputs: limit.
drive_uploadA
Upload a file from this computer into a Drive folder (Create new > Upload).
The file is copied to the phone, picked in the system file picker, uploaded under
title into the folder, then the phone copy is removed. Waits until the new item shows
in the folder and the upload bar no longer reports progress.
Inputs: local_path (required), path, title, timeout.
drive_upload_filesB
Upload several files from this computer into one Drive folder (one after the other).
Inputs: local_paths (required), path, timeout.
drive_upload_folderA
Upload a folder from this computer: creates a Drive folder and uploads its files and sub-folders into it (recursively; hidden files are skipped).
Inputs: local_dir (required), name, path, timeout, +1 more.
drive_viewA
Switch the current Drive list between list and grid view.
Inputs: mode.
gmail_archiveB
Archive conversations (remove them from the Inbox; they stay in All mail).
Inputs: all, index, match, query, +3 more.
gmail_attachmentsC
List the attachments of a conversation (name, type, size, message index).
Inputs: index, query, folder, stay_open, +2 more.
gmail_backA
Close the open conversation (or dialog/full view) and return to the mail list.
Inputs: to_inbox.
gmail_block_senderA
Block the sender of a message: their future mail goes to Spam (message menu > Block "sender").
Gmail blocks at once (no dialog); gmail.unblock_sender reverts it. Not offered for your own messages.
Requires confirm=true (outward-facing or destructive).
Inputs: index, query, folder, confirm, +4 more.
gmail_cancel_scheduledA
Cancel a scheduled send: the message is not sent and becomes a draft again (see gmail.drafts).
Inputs: index, query, expect_subject.
gmail_compose_stateA
Describe the compose window currently open (mode, recipients, subject, body, attachments).
Inputs: no named parameters.
gmail_create_labelB
Create a new Gmail label (drawer -> Create label).
Inputs: name (required).
gmail_delete_draftA
Delete (discard) a draft: index from gmail.drafts, optionally with the same query.
Requires confirm=true (outward-facing or destructive).
Inputs: index, query, confirm, expect_subject.
gmail_delete_foreverA
Permanently delete conversations that are in the Trash or Spam (cannot be undone).
Only conversations listed in the Trash or Spam folder (default: Trash), or in a search restricted to them ('in:trash ...' / 'in:spam ...'), can be targeted. Some Gmail builds (e.g. Workspace accounts) offer no per-conversation 'Delete forever'; the op then fails with not_found and gmail.empty is the only way to purge.
Requires confirm=true (outward-facing or destructive).
Inputs: all, index, match, query, +4 more.
gmail_delete_labelA
Delete a user label (Gmail settings -> Manage labels). Conversations keep existing without it.
Requires confirm=true (outward-facing or destructive).
Inputs: name (required), confirm.
gmail_discardA
Close the compose window currently open WITHOUT sending it (its text is thrown away).
Requires confirm=true (outward-facing or destructive).
Inputs: confirm.
gmail_download_attachmentA
Download attachment(s) of a conversation to the phone's Download folder and copy them to this computer.
Choose the attachment with name (case-insensitive substring) or attachment_index (from
gmail.attachments), or all=true; with none of them the conversation must have exactly one
attachment.
Inputs: all, name, index, query, +7 more.
gmail_draftA
Create a new draft (not sent): recipients, subject, Unicode body, attachments.
Inputs: cc, to, bcc, body, +5 more.
gmail_draftsB
List drafts (Drafts folder, or drafts matching a Gmail query). Indexes feed gmail.send_draft.
Inputs: limit, query.
gmail_edit_draftA
Change an existing draft (recipients, subject, body, attachments) and save it again.
The draft keeps its thread (reply drafts stay replies). Fields left out are unchanged. With append=false (default) given recipients REPLACE the field and body replaces the whole editable body; with append=true recipients are added and body is added at the end.
Inputs: cc, to, bcc, body, +7 more.
gmail_emptyA
Empty the Trash or the Spam folder: EVERY conversation in it is permanently deleted.
Returns how many conversations Gmail said it would delete ('count').
Requires confirm=true (outward-facing or destructive).
Inputs: folder, confirm.
gmail_foldersA
List Gmail folders (inbox categories, system folders) and user labels with unread counts.
Counts are what the drawer shows: unread conversations for most folders, the total for Drafts/Outbox/Scheduled, and 'N new' for inbox categories.
Inputs: no named parameters.
gmail_forwardA
Forward the last message of a conversation, or message_index (with its attachments), to new recipients.
Send is never repeated blind: when Gmail's reaction to Send is unclear the mail is looked up in Outbox / Sent; if that cannot tell, the op fails with error 'send_state_unknown' and nothing is re-sent (check gmail.list folder='sent' before any retry). A result may carry verified_in='outbox' | 'sent' when the send was confirmed that way.
Requires confirm=true (outward-facing or destructive).
Inputs: cc, to, bcc, body, +9 more.
gmail_geminiA
Ask Gemini in Gmail and return its answer text.
Without index/query/folder Gemini is asked from the mail list (questions about the mailbox, e.g. 'unread mail from Alice this week'); with them the conversation is opened first and the question is about that conversation. Raises gemini_unavailable when the account has no Gemini in Gmail.
Inputs: prompt (required), index, query, folder, +2 more.
gmail_importantC
Mark conversations as important.
Inputs: all, index, match, query, +3 more.
gmail_labelB
Apply and/or remove labels on conversations ('Label as' dialog; 'Inbox' is a label too).
The result lists the labels offered by the dialog ('labels').
Inputs: add, all, index, match, +5 more.
gmail_labelsA
List the user labels (and system folders) available to gmail.label / gmail.move.
Inputs: no named parameters.
gmail_listA
List conversations of the current list (folder, label or search results) or of a given folder.
Rows have an 'index' (0 = top of the list) usable with gmail.read / gmail.reply / gmail.archive ... The list and its rows are remembered: a follow-up op given only an index re-opens this list if the phone shows another one, and refuses (not_found) when the row at that index is no longer the same conversation (it is looked up nearby if it just moved).
Inputs: limit, start, folder.
gmail_mark_readB
Mark conversations as read (changed=false if they already were).
Inputs: all, index, match, query, +3 more.
gmail_mark_unreadB
Mark conversations as unread (an open conversation is closed, as in the app).
Inputs: all, index, match, query, +3 more.
gmail_message_actionsA
List the actions a message's own menu offers (Reply all, Forward, Block "x", Report spam ...).
Read-only: tells whether gmail.block_sender / gmail.unblock_sender / gmail.unsubscribe / gmail.report_phishing apply to this message.
Inputs: index, query, folder, stay_open, +3 more.
gmail_moveA
Move conversations to another folder: an inbox category (Primary, Promotions...), a label, or the Inbox.
'Move to' is offered on folder lists (and, as 'Move to Inbox', for trashed/archived search results); it is not available on ordinary search results - use gmail.label there.
Inputs: to (required), all, index, match, +4 more.
gmail_muteA
Mute conversations: they are archived and later replies skip the Inbox (find them with 'is:muted').
Inputs: all, index, match, query, +3 more.
gmail_not_importantC
Mark conversations as not important.
Inputs: all, index, match, query, +3 more.
gmail_not_spamB
Report conversations as not spam: they go back to the Inbox (default list: the Spam folder).
Inputs: all, index, match, query, +3 more.
gmail_openB
Open Gmail on a folder or label (Inbox, Starred, Sent, Drafts, All mail, Spam, Trash, a user label...).
Inputs: folder.
gmail_readA
Read a whole conversation: subject, labels, and every message (from, to, cc, date, full body, attachments).
Opens row index of the current list (or of query search results / folder); with no
index, reads the conversation already open. Collapsed messages are expanded and the whole
thread is scrolled. Opening an unread conversation marks it read unless keep_unread=true.
Inputs: full, index, query, folder, +4 more.
gmail_refreshA
Sync mail now: pull-to-refresh on the current list (goes to the list first).
Inputs: no named parameters.
gmail_rename_labelA
Rename a user label (Gmail settings -> Manage labels -> label -> Name). Conversations keep it.
Inputs: name (required), new_name (required).
gmail_replyA
Reply to the sender of the last message of a conversation (or of message_index).
Send is never repeated blind: when Gmail's reaction to Send is unclear the mail is looked up in Outbox / Sent; if that cannot tell, the op fails with error 'send_state_unknown' and nothing is re-sent (check gmail.list folder='sent' before any retry). A result may carry verified_in='outbox' | 'sent' when the send was confirmed that way.
Requires confirm=true (outward-facing or destructive).
Inputs: cc, to, bcc, body, +9 more.
gmail_reply_allA
Reply to everyone on the last message of a conversation (or of message_index).
Send is never repeated blind: when Gmail's reaction to Send is unclear the mail is looked up in Outbox / Sent; if that cannot tell, the op fails with error 'send_state_unknown' and nothing is re-sent (check gmail.list folder='sent' before any retry). A result may carry verified_in='outbox' | 'sent' when the send was confirmed that way.
Requires confirm=true (outward-facing or destructive).
Inputs: cc, to, bcc, body, +9 more.
gmail_report_phishingA
Report a message as phishing to Google (irreversible), when Gmail offers it for that message.
Not every message has the entry: the op then fails with not_found and lists the actions the message menu offers (see gmail.message_actions); use gmail.spam to report spam instead.
Requires confirm=true (outward-facing or destructive).
Inputs: index, query, folder, confirm, +3 more.
gmail_restoreA
Bring conversations back to the Inbox from the Trash, Spam or the archive (All mail / search).
Uses 'Move to Inbox' / 'Move to' -> Inbox when offered, else adds the Inbox label.
Inputs: all, index, match, query, +3 more.
gmail_save_attachment_to_driveA
Save an attachment of a conversation into Google Drive (the tile's 'Save to Drive').
Choose the attachment with name (case-insensitive substring) or attachment_index (from
gmail.attachments); with neither, the conversation must have exactly one attachment.
Inputs: name, path, index, query, +6 more.
gmail_scheduledA
List mail scheduled to be sent later (the 'Scheduled' folder). Indexes feed gmail.cancel_scheduled.
Inputs: limit, query.
gmail_scrollA
Scroll the current Gmail list or open conversation; 'top' returns to the top of the list.
On a list, the rows now visible are returned with their absolute 'index' (usable by gmail.read / gmail.reply / gmail.archive...) when the list is the one last returned by gmail.list / gmail.search / gmail.scroll; 'indexed' tells whether they could be numbered (direction=top always can). On an open conversation the visible messages are returned.
Inputs: times, direction.
gmail_searchA
Search mail with Gmail query syntax and return the result rows.
Examples: 'from:alice has:attachment', 'is:unread label:work', 'subject:report after:2026/01/01'. The results stay on screen: gmail.read / gmail.list without arguments work on them.
Inputs: query (required), limit.
gmail_sendA
Send a new e-mail (any Unicode text) with optional Cc, Bcc, attachments, From alias, or schedule it.
The account's signature is added after the body by Gmail (signature=false removes it). Recipients are verified on the compose screen before sending; on any mismatch nothing is sent. Scheduled mail waits in the 'Scheduled' folder (gmail.scheduled, gmail.cancel_scheduled).
Send is never repeated blind: when Gmail's reaction to Send is unclear the mail is looked up in Outbox / Sent; if that cannot tell, the op fails with error 'send_state_unknown' and nothing is re-sent (check gmail.list folder='sent' before any retry). A result may carry verified_in='outbox' | 'sent' when the send was confirmed that way; a scheduled send counts only once Gmail confirms it or it is found in 'Scheduled' (verified_in='scheduled').
Gmail offers 'Undo' for about 7 s after 'Sent' (undo_available tells whether it was still on screen when the op returned; gmail.undo uses it). undo=true taps that Undo within this op, the moment Gmail shows it: the result then has undone='send' and saved_as_draft=true only when the mail came back as a draft and is not in Outbox / Sent; undone=false (with undo_error) means it was sent anyway.
Requires confirm=true (outward-facing or destructive).
Inputs: to (required), cc, bcc, body, +8 more.
gmail_send_draftA
Send an existing draft (index from gmail.drafts, optionally with the same query).
Send is never repeated blind: when Gmail's reaction to Send is unclear the mail is looked up in Outbox / Sent; if that cannot tell, the op fails with error 'send_state_unknown' and nothing is re-sent (check gmail.list folder='sent' before any retry). A result may carry verified_in='outbox' | 'sent' when the send was confirmed that way; a scheduled send counts only once Gmail confirms it or it is found in 'Scheduled' (verified_in='scheduled').
Requires confirm=true (outward-facing or destructive).
Inputs: index, query, confirm, schedule, +1 more.
gmail_set_signatureA
Set the mobile signature added to mail sent from the phone ('' removes it).
Inputs: text (required).
gmail_set_vacationA
Turn the vacation responder (out-of-office auto-reply) on or off and set its dates and text.
While on, Gmail answers incoming mail automatically (outward-facing: needs confirm). Fields left out keep their current value. The result is the state read back from the screen.
Requires confirm=true (outward-facing or destructive).
Inputs: enabled (required), confirm, message, subject, +4 more.
gmail_signatureB
Read the mobile signature Gmail adds to mail sent from the phone ('' = none: the web signature is used).
Inputs: no named parameters.
gmail_snoozeA
Snooze conversations until a preset moment or a chosen date and time (gmail.unsnooze reverts).
Gmail offers only the presets that make sense now (e.g. no 'later_today' in the evening); an unavailable preset fails with not_found listing the available ones.
Inputs: all, date, time, index, +6 more.
gmail_spamA
Report conversations as spam (moved to Spam; the report is sent to Google).
Requires confirm=true (outward-facing or destructive).
Inputs: all, index, match, query, +4 more.
gmail_starC
Star conversations.
Inputs: all, index, match, query, +3 more.
gmail_summarizeB
Summarize a conversation with Gemini in Gmail ('Summarize this email').
Raises gemini_unavailable when the account has no Gemini in Gmail.
Inputs: index, query, folder, timeout, +1 more.
gmail_trashA
Delete conversations (move them to the Trash; gmail.restore or gmail.undo bring them back).
Requires confirm=true (outward-facing or destructive).
Inputs: all, index, match, query, +4 more.
gmail_unblock_senderB
Unblock the sender of a message (banner 'Unblock sender' or message menu > Unblock).
A sender that is not blocked is left alone (was_blocked=false).
Inputs: index, query, folder, stay_open, +3 more.
gmail_undoA
Undo the last Gmail action: tap 'Undo' on its snackbar if still shown, else revert it.
After gmail.send / reply / forward / send_draft it recalls the mail while Gmail still shows 'Undo' (about 7 s after 'Sent'; the send result's undo_available says whether it still did): undone='send' only when the message is back in Drafts and not in Outbox / Sent. Later it fails (not_found, the mail was sent). To be sure to recall a send, use gmail.send undo=true.
Reverting runs the opposite action on the same conversations (found again by subject): archive/trash/spam -> back to the Inbox, star <-> unstar, read <-> unread, label add <-> remove, mute -> unmute, snooze -> unsnooze, move -> move back (when it was done from a folder list).
Inputs: max_age_s.
gmail_unmuteA
Unmute conversations (e.g. query='is:muted'); they stay archived (use gmail.restore for the Inbox).
Inputs: all, index, match, query, +3 more.
gmail_unread_countB
Number of unread conversations in a folder or label, as shown in the Gmail drawer.
Inputs: folder.
gmail_unsnoozeA
Unsnooze conversations: they return to the Inbox now (default list: the Snoozed folder).
Inputs: all, index, match, query, +3 more.
gmail_unstarC
Remove the star from conversations.
Inputs: all, index, match, query, +3 more.
gmail_unsubscribeA
Unsubscribe from a mailing list / newsletter with Gmail's own Unsubscribe (outward, irreversible).
Gmail offers it only for messages with a recognised unsubscribe header; otherwise the op fails with not_found and lists the available actions.
Requires confirm=true (outward-facing or destructive).
Inputs: index, query, folder, confirm, +3 more.
gmail_vacationA
Read the vacation responder (out-of-office auto-reply): on/off, first/last day, subject, message.
Inputs: no named parameters.
meta_list_opsA
List every available operation with its parameters (JSON schema).
Inputs: app.
ui_double_tapA
Double-tap by coordinates or on the first element matching a selector (e.g. zoom in an image or PDF preview). Without the on-phone server a slow phone may see two single taps.
Inputs: x, y, rid, desc, +5 more.
ui_dumpB
Describe the current screen: foreground app and visible elements (text, desc, id, bounds, flags).
Inputs: raw, all_nodes.
ui_findC
Find elements on screen matching a selector (optionally wait up to timeout seconds).
Inputs: rid, desc, text, timeout, +3 more.
ui_global_actionA
Perform a system action like the navigation buttons or the status bar do.
lock_screen turns the screen off and locks the phone (the next UI op unlocks it); take_screenshot saves an image in the phone's gallery (prefer ui.screenshot); power_dialog needs the on-phone server.
Inputs: action (required).
ui_keyA
Press a key: BACK, HOME, ENTER, APP_SWITCH, DEL, TAB, DPAD_DOWN, MENU, SEARCH, or any KEYCODE_*.
Inputs: key (required).
ui_screenshotB
Save a PNG screenshot of the phone screen on this computer (look at it to see the UI).
Inputs: local_path.
ui_scrollA
Scroll the main content ('down' shows what is below, 'up' goes back toward the top).
Inputs: times, amount, direction.
ui_swipeC
Raw swipe gesture between two points.
Inputs: x1 (required), x2 (required), y1 (required), y2 (required), +1 more.
ui_tapB
Tap by coordinates, or tap the first element matching a selector (waits for it).
Inputs: x, y, rid, desc, +6 more.
ui_typeB
Type text into the focused field.
Inputs: text (required), clear, enter.
ui_waitB
Wait until an element appears (or disappears with gone=true).
Inputs: rid, desc, gone, text, +4 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 156 tools
Tools are grouped by domain and mostly distinct, but many near-neighbors require close reading, such as calendar_respond vs calendar_rsvp, calendar_sync vs calendar_sync_background, gmail_move vs gmail_label, and gmail_delete_forever vs gmail_empty. Descriptions clarify most boundaries, but the large surface creates real misselection risk.
All names use snake_case with a clear domain prefix (calendar_, drive_, gmail_, ui_, device_, app_), giving predictable grouping. The pattern is not strictly verb_noun throughout—several names are noun/state-oriented such as drive_location, gmail_compose_state, and calendar_calendars—but consistency is mostly maintained.
At 156 tools, this is far beyond the well-scoped 3-15 range and exceeds the 50-tool threshold for extreme mismatch. Even though the server spans multiple app domains, the surface is too large to navigate efficiently as a coherent tool set.
Coverage across Gmail, Calendar, Drive, UI automation, and device control is extensive, including CRUD, sharing, labels, drafts, sync, tasks, and escape hatches. No obvious dead ends are apparent; unsupported edge cases can generally be reached through device_shell.
Maintenance
Related MCP Connectors
Give your AI agent a real cloud Android phone: push APKs, drive it, get screenshots and logs.
Cloud Android phones for AI agents: create a phone, read the screen, tap, type, install APKs, park.
Let AI agents control Android and iOS devices on Mobilerun Cloud: screen, input, apps, workflows.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables full Android control from any AI agent via 7 MCP tools, including screen capture, touch interaction, app management, and system control.43 PyPIMIT
- AlicenseAqualityDmaintenanceDrive an Android phone over ADB and expose it to AI agents via MCP, enabling natural language-driven interaction for native app testing.13MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to control Android devices via ADB, providing tools for screen capture, tap/swipe/input, app management, and diagnostics. Works locally over USB or WiFi and integrates with MCP clients like Claude and Cursor.1MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to see and operate a real Android phone over adb by fusing live screenshots with UI-tree data, supporting look, tap, swipe, type, and screenshot actions through any MCP client.4MIT