iOS Simulator Workflows
conorluddy/xclaude-plugin
Manages iOS Simulator devices and apps through the execute_simulator_command MCP tool instead of raw simctl: boot, create and delete devices, install and launch apps, screenshots and diagnostics.
A skill your agent uses when interacting with Android devices via scrcpy-mcp tools (tap, swipe, inputtext, keyevent, uidump, uifindelement, appstart, etc.).
$ npx skills add JuanCF/scrcpy-mcp --skill scrcpy-gotchas -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install JuanCF/scrcpy-mcp scrcpy-gotchas --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/JuanCF/scrcpy-mcp.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.opencode/skills/scrcpy-gotchas .claude/skills/scrcpy-gotchas && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "scrcpy-gotchas" agent skill from https://github.com/JuanCF/scrcpy-mcp/tree/main/.opencode/skills/scrcpy-gotchas into .claude/skills/scrcpy-gotchas/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scrcpy-gotchas", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/JuanCF/scrcpy-mcp/tree/main/.opencode/skills/scrcpy-gotchasType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add JuanCF/scrcpy-mcp --skill scrcpy-gotchas -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install JuanCF/scrcpy-mcp scrcpy-gotchas --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JuanCF/scrcpy-mcp.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.opencode/skills/scrcpy-gotchas .agents/skills/scrcpy-gotchas && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "scrcpy-gotchas" agent skill from https://github.com/JuanCF/scrcpy-mcp/tree/main/.opencode/skills/scrcpy-gotchas into .agents/skills/scrcpy-gotchas/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scrcpy-gotchas", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add JuanCF/scrcpy-mcp --skill scrcpy-gotchas -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install JuanCF/scrcpy-mcp scrcpy-gotchas --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JuanCF/scrcpy-mcp.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.opencode/skills/scrcpy-gotchas .cursor/skills/scrcpy-gotchas && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "scrcpy-gotchas" agent skill from https://github.com/JuanCF/scrcpy-mcp/tree/main/.opencode/skills/scrcpy-gotchas into .cursor/skills/scrcpy-gotchas/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scrcpy-gotchas", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/JuanCF/scrcpy-mcp.git --path .opencode/skills/scrcpy-gotchas--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add JuanCF/scrcpy-mcp --skill scrcpy-gotchas -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install JuanCF/scrcpy-mcp scrcpy-gotchas --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JuanCF/scrcpy-mcp.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.opencode/skills/scrcpy-gotchas .gemini/skills/scrcpy-gotchas && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "scrcpy-gotchas" agent skill from https://github.com/JuanCF/scrcpy-mcp/tree/main/.opencode/skills/scrcpy-gotchas into .gemini/skills/scrcpy-gotchas/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scrcpy-gotchas", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install JuanCF/scrcpy-mcp scrcpy-gotchasInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add JuanCF/scrcpy-mcp --skill scrcpy-gotchas -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/JuanCF/scrcpy-mcp.git skills-src && mkdir -p .github/skills && cp -r skills-src/.opencode/skills/scrcpy-gotchas .github/skills/scrcpy-gotchas && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "scrcpy-gotchas" agent skill from https://github.com/JuanCF/scrcpy-mcp/tree/main/.opencode/skills/scrcpy-gotchas into .github/skills/scrcpy-gotchas/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scrcpy-gotchas", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add JuanCF/scrcpy-mcp --skill scrcpy-gotchas -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install JuanCF/scrcpy-mcp scrcpy-gotchas --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JuanCF/scrcpy-mcp.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.opencode/skills/scrcpy-gotchas .opencode/skills/scrcpy-gotchas && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "scrcpy-gotchas" agent skill from https://github.com/JuanCF/scrcpy-mcp/tree/main/.opencode/skills/scrcpy-gotchas into .opencode/skills/scrcpy-gotchas/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "scrcpy-gotchas", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
scrcpy-gotchasA skill your agent uses when interacting with Android devices via scrcpy-mcp tools (tap, swipe, inputtext, keyevent, uidump, uifindelement, appstart, etc.).
Scrcpy Gotchas is an agent skill from JuanCF/scrcpy-mcp. Use when interacting with Android devices via scrcpy-mcp tools (tap, swipe, inputtext, keyevent, uidump, uifindelement, appstart, etc.). Documents common pitfalls and lessons learned.
Its SKILL.md is about 5.8k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Mobile, covering MCP servers and Mobile testing and debugging. It works with Android and Model Context Protocol. The repository describes itself as: MCP server for Android device control via ADB and scrcpy — gives AI agents vision and control over Android devices. The licence is MIT.
3 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 5f93d15. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
adbFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Scrcpy Gotchas loads about 5.8k tokens when it runs. Until then it costs about 51 tokens; SKILL.md has 2,402 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from JuanCF/scrcpy-mcp at commit 5f93d15, republished under its MIT licence (© JuanCF). 2,402 words, ~5,781 tokens.
.claude/skills/scrcpy-gotchas/SKILL.md (or your agent's skills folder).scrcpy_tap, scrcpy_swipe, scrcpy_long_press, and scrcpy_scroll take device-native coordinates — the same space that scrcpy_ui_dump / scrcpy_ui_find_element report in their bounds and tapX/tapY, and the same space adb shell input tap uses. So the reliable workflow needs no scaling math:
// Find the element (returns native tapX/tapY), then tap them straight through
const el = scrcpy_ui_find_element({ text: "<label>" }) // → { tapX, tapY }
scrcpy_tap({ x: el.tapX, y: el.tapY })The input tools scale native → video-frame coordinates internally before sending the touch to the scrcpy server, so you never compute a scale factor yourself.
scrcpy_screenshot returns a downscaled image (e.g. 576×1024 at maxSize=1024), not the native frame. Tapping a pixel coordinate read off the screenshot lands in the wrong place. Always source coordinates from ui_dump / ui_find_element (native), never from the screenshot.
Why internal scaling is required: the scrcpy server's touch protocol is not resolution-independent — the touch message's screen size must exactly match the encoder's video-frame size, or the server silently drops the event. The tools handle this conversion; you just supply native coords.
The four search criteria have different matching behaviors:
| Criterion | Match type | Case sensitivity |
|---|---|---|
text | Partial substring (includes) | Case-insensitive |
contentDesc | Partial substring (includes) | Case-insensitive |
resourceId | Exact equality (===) | Case-sensitive |
className | Exact equality (===) | Case-sensitive |
All criteria are combined with AND logic — an element must satisfy every supplied criterion.
// text: case-insensitive substring — "bater" matches "Batería"
scrcpy_ui_find_element({ text: "bater" }) // ✓
// resourceId: exact match only — must be full string
scrcpy_ui_find_element({ resourceId: "android:id/title" }) // must match exactly
// className: full qualified name — "Button" won't match "android.widget.Button"
scrcpy_ui_find_element({ className: "android.widget.Button" }) // full name requiredtext and contentDesc matching lowercases both sides and uses String.includes(). However, Unicode normalization differences between the XML dump (from uiautomator) and your query string can cause mismatches. Searching for an accented word (e.g. "Batería") may return 0 even though the element displays that exact text, while a partial ASCII-only substring (e.g. "bater") will find it.
Rule of thumb: Search with lowercase ASCII-only substrings when possible. If that fails, use ui_dump and check the raw XML for the actual byte sequence.
For tappable cards — search results, feed rows, media thumbnails — the visible label is often rendered by a child view that the clickable wrapper ViewGroup does not expose as text. A text: query then returns 0 matches (or only matches an unrelated element like the search bar).
The label instead lives in the wrapper's contentDesc, which is usually richer (e.g. it bundles secondary metadata into one string) and easier to disambiguate between similar items:
// May return 0 — the result card has no `text`
scrcpy_ui_find_element({ text: "<title>" })
// Matches the clickable wrapper's contentDesc, which carries the label
scrcpy_ui_find_element({ contentDesc: "<title>" })
// → { contentDesc: "...", tapX, tapY, clickable: true }Rule of thumb: if text: finds nothing for something you can clearly see on screen, retry with contentDesc: (or fall back to scrcpy_ui_dump and grep the XML) before assuming it isn't there.
uiautomator dump wraps attribute values in double quotes (") by default. But when a value itself contains double quotes (common in X/Twitter content-desc fields), it switches to single quotes ('). The parseUiNodes() parser only handles double-quote delimiters:
HTML attr regex: content-desc="([^"]*)"
↑ only matches "..."So content-desc='tweet with "quoted" text' → contentDesc: "" (empty string — parser misses it entirely).
Impact: ui_find_element won't find tweets/items whose contentDesc contains double quotes. The element is still in the dump but its contentDesc is silently empty.
Workaround: for text that reliably exists, use text: queries instead. For X timeline tweets, search by partial lowercase ASCII snippets that avoid quoted substrings.
// This tweet: "a company just fired 90% of their dev team because claude code..."
// Won't match — contentDesc parsing fails due to embedded double quotes
scrcpy_ui_find_element({ contentDesc: "claude" }) // → 0 results
// But this promoted tweet has no embedded quotes → contentDesc parses fine
scrcpy_ui_find_element({ contentDesc: "runpod" }) // → foundADB's input text (used by scrcpy_input_text) crashes on non-ASCII characters like ¡, ¿, ñ, accented vowels (é, á, etc.), and !. The command throws a NullPointerException and silently fails.
Workaround: Use scrcpy_clipboard_set with paste: true instead — it handles any Unicode text:
// BAD — crashes with special characters
scrcpy_input_text({ text: "¡Hola, mamá!" })
// GOOD — clipboard handles all characters
scrcpy_clipboard_set({ text: "¡Hola, mamá!", paste: true })Critical: paste: true requires an active scrcpy session. Without one, the clipboard is set but the paste action is silently skipped (message: "Paste action not performed"). Always scrcpy_start_session before sending non-ASCII text.
!) causes partial text lossEven though scrcpy_input_text returns success, using ! in the string can cause partial text loss — leading characters may be dropped. For example, "Hello world!" may type as "llo world!" (the ! causes the ADB input text command to partially fail). The clipboard workaround above avoids this completely.
Start a scrcpy session proactively when doing multi-step interactions. It unlocks:
scrcpy_clipboard_set with paste: true)Without a session, clipboard paste silently degrades and every screenshot is a slow ADB call.
Input (tap, swipe) and screenshots continue to work even when the screen is turned off via scrcpy_screen_off. Screenshots taken while screen is off will show a black/dark frame. Use scrcpy_screen_on to wake the device when needed.
scrcpy_clipboard_get can fail even with an active scrcpy session, especially on Android 10 devices. If it returns an error, the clipboard content is still likely not critical — rely on direct state checks via ui_dump instead.
scrcpy_scroll does NOT work reliably on RecyclerView or ScrollView containers. Many Android apps (Settings, Chrome, launchers, messaging apps) use RecyclerView for scrollable lists. scrcpy_scroll may return success but produce no visible movement.
Use scrcpy_swipe instead for scrolling:
// BAD — may have no effect on RecyclerView
scrcpy_scroll({ x: 720, y: 1500, dx: 0, dy: 1 })
// GOOD — swipe gestures reliably scroll all scrollable views
scrcpy_swipe({ x1: 720, y1: 1800, x2: 720, y2: 800, duration: 500 })For a swipe-down (content scrolls up): y1 > y2. For a swipe-up (content scrolls down): y1 < y2. Duration 300-500ms is a good default for natural scrolling.
Watch for momentum side effects: Long swipes can carry momentum that collapses toolbars, scrolls beyond intended position, or triggers overscroll actions. For precise scrolling, use shorter swipe distances (e.g., 200-400px delta).
When sending messages in WhatsApp (and likely other messaging apps), scrcpy_tap on the send button can silently miss — the hit target is small and easy to land just outside. Prefer scrcpy_key_event with ENTER instead, which is more reliable.
These keycodes have been confirmed working via scrcpy_key_event:
| Keycode | Effect |
|---|---|
HOME (3) | Go to home screen |
BACK (4) | Navigate back |
ENTER (66) | Submit/send in input fields |
APP_SWITCH (187) | Open recent apps |
VOLUME_UP (24) | Increase volume |
VOLUME_DOWN (25) | Decrease volume |
MENU (82) | Open menu (context-dependent) |
Use scrcpy_key_event with the string name (e.g., "HOME") or numeric keycode.
scrcpy_app_start brings an app to the foreground but reuses its existing task, preserving scroll position, UI state, and back stack. This is efficient for quick app switching but can be surprising:
// Opens Settings but preserves scroll position from previous session
scrcpy_app_start({ packageName: "com.android.settings" })To force a clean launch, prefix the package name with +:
scrcpy_app_start({ packageName: "+com.android.settings" })This force-stops the app before launching, clearing its state.
Use only scrcpy_* tools when controlling an Android device. Do not reach for browser tools (browser_*, chrome-devtools_*) in an Android context. Common mistake: using browser_wait instead of just proceeding to the next action.
Visible text labels (in text, contentDesc, hint) are locale-dependent. What reads "Chats" in English is "Chats" in Spanish, "Discussions" in French, etc. Hardcoding text strings makes the skill fragile across devices.
Always prefer locale-independent selectors first:
| Selector | Locale-safe? | Example |
|---|---|---|
resourceId | Yes | com.whatsapp:id/entry |
className | Yes | android.widget.Switch |
text | No | "Mensaje" (placeholder, varies) |
contentDesc | No | "Nuevo chat" (varies) |
Workflow for text-based targeting:
scrcpy_ui_dump to discover the actual label on the deviceresourceId + className when possibleIn this document, example labels are shown as generic patterns like <app-name> or with English/Spanish examples marked (locale-dependent). Treat them as illustrations of the type of text to expect, not exact strings to copy.
com.twitter.androidandroid:id/list) inside nested ViewPager + ScrollView. Scroll with scrcpy_swipe.contentDesc — the row's text field is always empty. The contentDesc bundles author, handle, tweet body, engagement stats, and timestamp into one long string.BACK to return to X.android.widget.FrameLayout elements with contentDesc labels (locale-dependent). Active tab has selected="true". Discover actual labels via ui_dump.contentDesc on the parent LinearLayout; active tab has selected="true".BACK restores them.resourceId="com.twitter.android:id/banner_text". Tapping it scrolls to top.HorizontalScrollView sub-tab bar (tab_layout). Tab labels are locale-dependent; discover via ui_dump. Trending topic titles use text (not contentDesc).resourceId="com.twitter.android:id/tweet_ad_badge_top_right" with a short text label (locale-dependent, e.g. "Ad" / "Anuncio").com.android.chromeui_dump when pages have proper accessibility markup (semantic HTML, ARIA). Plain pages like google.com may not expose content.android.webkit.WebView) is scrollable — use scrcpy_swipe on the web content area (below the toolbar, y > 280).com.android.chrome:id/url_bar. Tap it, paste a URL via clipboard_set, and press ENTER to navigate.bounds="[0,0][0,0]" or zero-dimension bounds. Elements scrolled past the top show bounds="[x,top][x,top]" (height=0). Filter these out: only tap elements with positive height.BACK to dismiss them.ui_find_element with targeted criteria is far more efficient than parsing the full ui_dump response.APP_SWITCH (keycode 187) opens the recents overview with scrollable app previews.BACK in recents returns to the previously focused app.HOME then app_start is a reliable alternative when recents navigation gets confusing.com.android.settingsBACK returns one level.android.widget.Switch with checkable="true", checked="true"|"false". The text shows on/off state (locale-dependent, e.g. "On"/"Off"). The parent row also has checkable="true".contentDesc on the clickable row: <ssid>,<status>,<signal>,<security>. The title and summary are separate text fields.ProgressBar with resource ID com.android.settings:id/progress_bar_animation and a scanning message (locale-dependent).com.android.settings:id/settings_button_no_background.scrcpy_screen_record_start with duration (seconds) auto-stops after that duration. The video is written to the specified remotePath on the device.scrcpy_screen_record_stop reports "No recording in progress" — this is expected. The file is already saved.scrcpy_file_pull with pullToHost: true to retrieve the recording if you still need it.duration parameter, recording continues indefinitely until screen_record_stop is called.// Start a 10-second recording
scrcpy_screen_record_start({ duration: 10, remotePath: "/sdcard/demo.mp4" })
// Pull it after it auto-stops
scrcpy_file_pull({ remotePath: "/sdcard/demo.mp4", localPath: "/tmp/demo.mp4" })scrcpy_file_push copies a file from the host to the device. Both paths must be absolute.scrcpy_file_pull copies from the device to the host. Both paths must be absolute.scrcpy_file_list verifies file presence and shows metadata: permissions, owner, group, size, date, isDirectory.scrcpy_file_push({ localPath: "/tmp/data.txt", remotePath: "/sdcard/data.txt" })
scrcpy_file_list({ path: "/sdcard/" }) // verify it's there
scrcpy_file_pull({ remotePath: "/sdcard/data.txt", localPath: "/tmp/copy.txt" })scrcpy_rotate_device toggles between portrait and landscape. There's no separate portrait/landscape command — each call flips orientation.scrcpy_ui_dump shows rotation="1" (landscape) or rotation="0" (portrait) on the root <hierarchy> node.[0,0][1440,2560] → landscape [0,0][2392,1440]. Height is reduced by status bar area.tapX/tapY from a landscape dump are valid for taps in landscape — no manual coordinate transformation needed.scrcpy_shell_exec runs arbitrary shell commands on the device via adb shell.&& or ;.getprop, settings get, dumpsys, pm list, wm, df, top, ps.// Device properties
scrcpy_shell_exec({ command: "getprop ro.build.version.release && getprop ro.product.model" })
// → "10\nNokia 8 Sirocco"
// Screen info
scrcpy_shell_exec({ command: "wm size && wm density" })
// → "Physical size: 1440x2560\nPhysical density: 560"
// Battery details
scrcpy_shell_exec({ command: "dumpsys battery | grep level" })
// → " level: 100"
// Installed third-party packages
scrcpy_shell_exec({ command: "pm list packages -3" })
// → "package:com.whatsapp\npackage:com.twitter.android\n..."scrcpy_start_video_stream starts an HTTP MJPEG stream at http://127.0.0.1:7183 and opens an ffplay viewer window on the host.scrcpy_stop_video_stream to clean up.maxSize parameter (default 1024). Use maxFps to cap frame rate (default 30).scrcpy_start_video_stream() // opens ffplay window at http://127.0.0.1:7183
// Optional: custom port and resolution
scrcpy_start_video_stream({ port: 8080, maxSize: 720, maxFps: 15 })
scrcpy_stop_video_stream() // close the stream and ffplay windowandroid.widget.ScrollView with com.android.launcher3:id/workspace. Horizontal swipes navigate between home screen pages.EditText at com.android.launcher3:id/fallback_search_view with a placeholder hint (locale-dependent)androidx.recyclerview.widget.RecyclerView (com.android.launcher3:id/apps_list_view) with app icons as android.widget.TextView elementscom.android.launcher3:id/prediction_row) showing 5 suggested apps at the toptext and contentDesc set to the same app name. Off-screen icons show bounds="[0,0][0,0]".LauncherAppWidgetHostView nodes with descriptive contentDesc (e.g. "At a Glance" / "De un vistazo", "Digital Clock" / "Reloj digital").scrcpy_expand_settings opens the Quick Settings panel (requires active scrcpy session). scrcpy_collapse_panels closes it.android.widget.Switch widgets with checkable="true" and checked="true"/"false". Use checked to determine state; text shows locale-dependent on/off labels.contentDesc. Example: WiFi tile → "Wi-Fi,<signal-strength>,<ssid>".android.widget.LinearLayout with clickable="true" instead of Switch.androidx.viewpager.widget.ViewPager (quick_settings_panel). A page indicator shows the current page (contentDesc like "Page 1 of 2"). Swipe horizontally on the ViewPager to change pages.android.widget.SeekBar at com.android.systemui:id/slider.android:id/edit), user switch, settings gear (opens Settings app).contentDesc (e.g., "Flashlight" for English, "Linterna" for Spanish — discover via ui_dump first) and tap its center coordinates.// Toggle the flashlight tile (find by contentDesc substring)
const tile = scrcpy_ui_find_element({ contentDesc: "flashlight" }) // try English first
if (!tile.count) tile = scrcpy_ui_find_element({ contentDesc: "linterna" }) // fall back
scrcpy_tap({ x: tile.elements[0].tapX, y: tile.elements[0].tapY })
// Verify: tile.text changed, checked flippedcom.android.systemui. Quick settings, notifications, and status bar are not part of regular app windows — they overlay the existing content.com.whatsapp+ to force-stop before launch: +com.whatsappandroid.widget.FrameLayout elements): chats, updates, communities, calls. Active tab has selected="true". Tab labels are locale-dependent — discover via ui_dump.android:id/list with com.whatsapp:id/contact_row_container rows. Contact names are in com.whatsapp:id/conversations_row_contact_name (text field).com.whatsapp:id/conversation_contact_name, last-seen at com.whatsapp:id/conversation_contact_status.com.whatsapp:id/entry (android.widget.EditText) — tap to focus, then use scrcpy_clipboard_set with paste: true (requires active scrcpy session) for any text with non-ASCII characters. See Non-ASCII text input above.scrcpy_key_event with ENTER instead of tapping the send button.<name>" (locale-dependent pattern).© JuanCF, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .opencode/skills/scrcpy-gotchas of JuanCF/scrcpy-mcp.
Open the folder on GitHubat commit 5f93d15
Scrcpy Gotchas next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Scrcpy Gotchas this skillJuanCF/scrcpy-mcp | 112 | — | ~5.8k | Automated safety check: Pass | MIT | |
| iOS Simulator Workflowsconorluddy/xclaude-plugin | 183 | — | ~3.3k | Automated safety check: Pass | MIT | |
| Sentry Crash Analysishyvanmielenpelit/GnollHack | 161 | — | ~222 | Automated safety check: Pass | Custom licence | |
| Carnetjfarcand/mirroir-mcp | 245 | — | ~2.8k | Automated safety check: Pass | Apache-2.0 | |
| Magicnet Device DebuggingLIghtJUNction/MagicNet | 185 | — | ~2.9k | Automated safety check: Notes | MIT | |
| Register Limitationjfarcand/mirroir-mcp | 245 | — | ~1.4k | Automated safety check: Pass | Apache-2.0 |
conorluddy/xclaude-plugin
Manages iOS Simulator devices and apps through the execute_simulator_command MCP tool instead of raw simctl: boot, create and delete devices, install and launch apps, screenshots and diagnostics.
hyvanmielenpelit/GnollHack
Systematic methodology for diagnosing GnollHack crash reports from the Sentry MCP server.
jfarcand/mirroir-mcp
Work the private register (mirroir-carnet) from a session — claim an issue before touching it so peers see who holds it and which session, release or close it when done, file new issues in the one…
LIghtJUNction/MagicNet
Debug and verify MagicNet on a real Android root device. An agent skill from LIghtJUNction/MagicNet.
jfarcand/mirroir-mcp
Register a known gap in the limitation register — file the issue in the private tracker, write the LIMITATION(registren) marker, or ledger a dark-launched feature.
appclawhq/AppClaw
Generate YAML flow files for AppClaw mobile automation. An agent skill from appclawhq/AppClaw.
Works with
Categories
A skill your agent uses when interacting with Android devices via scrcpy-mcp tools (tap, swipe, inputtext, keyevent, uidump, uifindelement, appstart, etc.). Scrcpy Gotchas is an agent skill from JuanCF/scrcpy-mcp.).
Scrcpy Gotchas fits situations like: interacting with Android devices via scrcpy-mcp tools (tap; tasks that involve MCP servers; tasks that involve Mobile testing and debugging.
Run `npx skills add JuanCF/scrcpy-mcp --skill scrcpy-gotchas -a claude-code`. Or copy the skill folder (.opencode/skills/scrcpy-gotchas in JuanCF/scrcpy-mcp) into .claude/skills/scrcpy-gotchas in your project. Claude Code loads it when a task matches its description.
Run `npx skills add JuanCF/scrcpy-mcp --skill scrcpy-gotchas -a codex`. Or copy the skill folder (.opencode/skills/scrcpy-gotchas in JuanCF/scrcpy-mcp) into .agents/skills/scrcpy-gotchas in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add JuanCF/scrcpy-mcp --skill scrcpy-gotchas -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/scrcpy-gotchas, .gemini/skills/scrcpy-gotchas, .github/skills/scrcpy-gotchas and .opencode/skills/scrcpy-gotchas in your project.
Going by SKILL.md and its folder, Scrcpy Gotchas needs the command-line tools its instructions call (adb).
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Scrcpy Gotchas is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.8k tokens (SKILL.md is roughly 23k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Scrcpy Gotchas: iOS Simulator Workflows (conorluddy/xclaude-plugin, 183 stars), Sentry Crash Analysis (hyvanmielenpelit/GnollHack, 161 stars), Carnet (jfarcand/mirroir-mcp, 245 stars) and Magicnet Device Debugging (LIghtJUNction/MagicNet, 185 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
JuanCF (a GitHub user) maintains it in JuanCF/scrcpy-mcp, which has 112 GitHub stars. The repository was last updated on September 23, 2026.
Source: JuanCF/scrcpy-mcp on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.