Drive real Chrome, Android, macOS, Windows — and other Claude sessions — from Claude Code
Five MCP servers. Four of them let an agent tap, type, and reason through your actual apps — the browser you're logged into, the device in your hand, the desktop in front of you. The fifth lets one Claude session drive others. No headless sandbox, no fragile selectors, no install script.
chrome-mcp
Attaches over CDP to your running Chrome. Auto-launches it the first time you send a command. macOS, Linux, Windows. The agent sees your logged-in tabs — Gmail, internal dashboards, whatever.
Paste into ~/.claude.json and restart Claude Code:
{
"mcpServers": {
"chrome": {
"command": "node",
"args": [
"-e",
"(async()=>{const o=await import(\"node:fs\"),{homedir:s}=await import(\"node:os\"),{join:e}=await import(\"node:path\"),{pathToFileURL:a}=await import(\"node:url\"),i=process.env.CHROME_MCP_ENDPOINT||\"https://chrome-mcp.actuallyroy.com\",c=process.env.CHROME_MCP_CACHE_DIR||e(s(),\".chrome-mcp\"),r=e(c,\"loader.mjs\");if(!o.existsSync(r)||process.env.CHROME_MCP_REFRESH_LOADER){o.mkdirSync(c,{recursive:!0});const t=await fetch(i+\"/loader.mjs\");if(!t.ok)throw new Error(\"loader download failed: \"+t.status);o.writeFileSync(r,Buffer.from(await t.arrayBuffer()))}await import(a(r).href)})().catch(o=>{console.error(\"[chrome-mcp]\",o.message||o),process.exit(1)});"
]
}
}
}sha256: 9115a7a110ef3bf48917074ff6ce35e2295cf6b8ef939b69fa1646adf2957b1c
bundle · manifest · loader.mjs
android-mcp
Drives a real Android device or emulator via UIAutomator2. Same locator ergonomics as the Chrome version, but against the native view hierarchy (text, resource-id, content-desc, or XPath — no pixel matching). Requires adb on PATH. APK installs itself on first use.
Paste into ~/.claude.json and restart Claude Code:
{
"mcpServers": {
"android": {
"command": "node",
"args": [
"-e",
"(async()=>{const o=await import(\"node:fs\"),{homedir:s}=await import(\"node:os\"),{join:a}=await import(\"node:path\"),{pathToFileURL:i}=await import(\"node:url\"),c=process.env.ANDROID_MCP_ENDPOINT||\"https://chrome-mcp.actuallyroy.com\",e=process.env.ANDROID_MCP_CACHE_DIR||a(s(),\".android-mcp\"),r=a(e,\"loader.mjs\");if(!o.existsSync(r)||process.env.ANDROID_MCP_REFRESH_LOADER){o.mkdirSync(e,{recursive:!0});const t=await fetch(c+\"/android/loader.mjs\");if(!t.ok)throw new Error(\"android loader download failed: \"+t.status);o.writeFileSync(r,Buffer.from(await t.arrayBuffer()))}await import(i(r).href)})().catch(o=>{console.error(\"[android-mcp]\",o.message||o),process.exit(1)});"
]
}
}
}sha256: 6a0d84a62cc72b5981cbbc4509c5abd883ba601ffbbce9b0dbf3a2df308fdc0c
bundle · manifest · loader.mjs · u2 APK
macos-mcp
Drives any macOS desktop app — AppKit, Catalyst, SwiftUI, Electron — through the OS Accessibility tree. Same locator ergonomics (text, role, AXIdentifier). Posts real CGEvent mouse + keyboard input and grabs PNGs via ScreenCaptureKit. Bundled Swift sidecar binary; macOS 14+; arm64 (Apple Silicon) only for now.
One-time TCC permission setup: grant Accessibility (required for AX inspection + input) and Screen Recording (required for screenshots) when the OS prompts. Call open_permissions_settings from the agent to jump to the right pane.
Paste into ~/.claude.json and restart Claude Code:
{
"mcpServers": {
"macos": {
"command": "node",
"args": [
"-e",
"(async()=>{const o=await import(\"node:fs\"),{homedir:c}=await import(\"node:os\"),{join:t}=await import(\"node:path\"),{pathToFileURL:e}=await import(\"node:url\"),i=process.env.MACOS_MCP_ENDPOINT||\"https://chrome-mcp.actuallyroy.com\",a=process.env.MACOS_MCP_CACHE_DIR||t(c(),\".macos-mcp\"),r=t(a,\"loader.mjs\");if(!o.existsSync(r)||process.env.MACOS_MCP_REFRESH_LOADER){o.mkdirSync(a,{recursive:!0});const s=await fetch(i+\"/macos/loader.mjs\");if(!s.ok)throw new Error(\"macos loader download failed: \"+s.status);o.writeFileSync(r,Buffer.from(await s.arrayBuffer()))}await import(e(r).href)})().catch(o=>{console.error(\"[macos-mcp]\",o.message||o),process.exit(1)});"
]
}
}
}sha256: 02bab7f70ccd8019a6421ba9c07172b0b72246f6b0e00a1d4417361f4e0c0d21
bundle · manifest · loader.mjs · Swift helper
windows-mcp
Drives any Windows desktop app — Win32, WPF, WinForms, WinUI 3, UWP, Edge / Chrome / Electron (with ARIA) — through the UI Automation tree. Same locator ergonomics (text, role, AutomationId). Synthesizes SendInput mouse + keyboard, grabs PNG screenshots via PrintWindow, runs OCR through built-in Windows.Media.Ocr for apps with no UIA tree (custom canvases, games). Bundled C# helper exe; Windows 10 1903+ (x64); requires .NET 8 Desktop Runtime (Microsoft-signed, ~55 MB install — windows-mcp prints a clear hint on first run if it's missing).
Paste into ~/.claude.json and restart Claude Code:
{
"mcpServers": {
"windows": {
"command": "node",
"args": [
"-e",
"(async()=>{const o=await import(\"node:fs\"),{homedir:a}=await import(\"node:os\"),{join:t}=await import(\"node:path\"),{pathToFileURL:i}=await import(\"node:url\"),c=process.env.WINDOWS_MCP_ENDPOINT||\"https://chrome-mcp.actuallyroy.com\",e=process.env.WINDOWS_MCP_CACHE_DIR||t(a(),\".windows-mcp\"),r=t(e,\"loader.mjs\");if(!o.existsSync(r)||process.env.WINDOWS_MCP_REFRESH_LOADER){o.mkdirSync(e,{recursive:!0});const s=await fetch(c+\"/windows/loader.mjs\");if(!s.ok)throw new Error(\"windows loader download failed: \"+s.status);o.writeFileSync(r,Buffer.from(await s.arrayBuffer()))}await import(i(r).href)})().catch(o=>{console.error(\"[windows-mcp]\",o.message||o),process.exit(1)});"
]
}
}
}sha256: 0918ef1b118d27a5bd58a20616fd1c8cc978bd3eb0e0ffff5d7c89e1ad1c63c7
bundle · manifest · loader.mjs · C# helper exe
orch-mcp
A different beast. Instead of driving an app, it lets one Claude Code session drive others — a master session that can adopt or spin up worker sessions, send them prompts, fork its own context into a new worker, and run them in background while you keep working. Each worker is a real persistent Claude session on disk; the master just shells out to claude --resume under the hood.
Workers are launched with Bash denied — they can analyze, plan, read files, and edit, but all shell execution stays in the master so you see it in one terminal. Send tasks in foreground (block-and-return) or background (fire, poll worker_resultlater). Fan out two workers in parallel and rejoin when both finish.
Paste into ~/.claude.json and restart Claude Code:
{
"mcpServers": {
"orch": {
"command": "node",
"args": [
"-e",
"(async()=>{const o=await import(\"node:fs\"),{homedir:s}=await import(\"node:os\"),{join:c}=await import(\"node:path\"),{pathToFileURL:a}=await import(\"node:url\"),i=process.env.ORCH_MCP_ENDPOINT||\"https://chrome-mcp.actuallyroy.com\",e=process.env.ORCH_MCP_CACHE_DIR||c(s(),\".orch-mcp\"),r=c(e,\"loader.mjs\");if(!o.existsSync(r)||process.env.ORCH_MCP_REFRESH_LOADER){o.mkdirSync(e,{recursive:!0});const t=await fetch(i+\"/orch/loader.mjs\");if(!t.ok)throw new Error(\"orch loader download failed: \"+t.status);o.writeFileSync(r,Buffer.from(await t.arrayBuffer()))}await import(a(r).href)})().catch(o=>{console.error(\"[orch-mcp]\",o.message||o),process.exit(1)});"
]
}
}
}sha256: 9608857ba1fdf437f60e064b90d6de7520744eefad10bf61749e112ec0672aa9
bundle · manifest · loader.mjs
Requires the claude CLI on $PATH. Override with ORCH_MCP_CLAUDE_BIN.
Why not Playwright / Maestro / Appium?
Those are great for CI suites that run on clean sandboxes. They're frustrating for agent-driven work — screenshots on every step, flaky text matching, no live pause, no good way to see a React Native LogBox or an auto-dismissed toast. These MCPs are designed around that loop: one cheap outline call describes the page; refs are stable across calls; dev overlays get auto-dismissed before your next click; failed order? A toast popped up for 800 ms and we captured it.
How updates work
The bootstrap in the config block is a 600-char Node snippet. First run it downloads a ~4 KB loader to ~/.chrome-mcp/ or ~/.android-mcp/. The loader fetches the latest bundle on every launch, verifies its SHA-256, and runs it. Offline? It falls back to the cached bundle. No npm publish step, no manual upgrade.
CHROME_MCP_PIN_VERSION/ANDROID_MCP_PIN_VERSION— pin a versionCHROME_MCP_SKIP_UPDATE=1/ANDROID_MCP_SKIP_UPDATE=1— use cached bundle, skip network*_ENDPOINT— self-host the bundles on your own domain*_REFRESH_LOADER=1— force re-download ofloader.mjs
What's in chrome-mcp
click / fill / fill_form / select_option— locate by visible text, form label, accessibility ref, or CSS selector (escape hatch)outline— compact page snapshot with stable refs that persist across callsget_toasts/wait_for_toast— captures sonner / role=alert messages even when they auto-dismissget_console/get_network— ring-buffered logs and fetch/XHR trafficpause/resume— in-page "Resume" overlay that blocks the agent until you click itinject_script— persists across navigations (debug helpers, test hooks)start_recording / stop_recording / run_script / assert— capture a flow live, replay it as a testlaunch_chrome— spawns Chrome with a dedicated debug profile; also runs automatically on first tool call
What's in android-mcp
click / fill / long_press / swipe / scroll— locators: text, content-desc, resource-id, xpath, ref, class, or raw UiSelectoroutline— real Android view hierarchy with stable refs (no OCR, no pixel diffing)describe— full element info (attrs, rect, ancestors)launch_app / stop_app / install_app / clear_app_data / current_apppress_key— HOME, BACK, APP_SWITCH, ENTER, VOLUME_UP… or raw keycodesscroll { until_text }— scrolls until target is on screenget_logcat— filtered ring buffer (by tag, level, or substring)dismiss_dev_overlay— handles React Native LogBox + Android ANR dialogs. Runs automatically before every interactive tool.adb_shell— escape hatchstart_recording / stop_recording / run_script / assert— same flow pattern as chrome-mcpwait_for_stable— polls the view tree until two consecutive snapshots match
What's in orch-mcp
worker_list/worker_list_available— registered workers, plus any unregistered Claude sessions in this project (with recent prompt + assistant snippets so you can pick the right one to adopt)worker_register— adopt an existing session by id under a friendly nameworker_create— spin up a fresh worker with an initial briefing promptworker_fork— snapshot the master's transcript into a new session id so the fork starts with full context and diverges. Optional source override viafrom_session_id.worker_send { background: true }— fire-and-poll: master keeps working, retrieve the answer later withworker_resultworker_result/worker_tasks/worker_cancel— poll, inspect, kill background tasksworker_compact— run/compactagainst a worker that's gotten longworker_status/worker_remove— inspect transcript size, last activity; unregister mappings (the underlying session file is preserved on disk)
Honest caveats
- Both are local-only. No cloud farm, no parallel execution — one agent, one browser, one device.
- iOS isn't in android-mcp yet. WebDriverAgent + Xcode provisioning is a separate dance.
- React Native controlled inputs can reject raw keystrokes. Numeric fields with custom
onChangeTextfilters are the main casualty. There's a fallback viaadb input textthat works for most fields. - The bundle is auto-downloaded. SHA-256 verified, cached locally. If you're uncomfortable with that, pin a version and review the bundle yourself.
- orch-mcp workers can't run Bash by design. If a worker concludes it needs shell, it says so and the master has to run the command itself. Keeps all shell visible in one terminal.
- worker_fork is a transcript-file snapshot. If the source session writes mid-fork, the new session's last line can be truncated — Claude tolerates this on resume but it's worth knowing.