Skip to content

actuallyroy/chrome-mcp

Repository files navigation

chrome-mcp + android-mcp + macos-mcp + windows-mcp

Four MCP servers that drive your real tooling from Claude Code — Chrome (via CDP), Android devices (via UIAutomator2), macOS desktop apps (via AX + ScreenCaptureKit), and Windows desktop apps (via UI Automation + SendInput). Same architecture, same distribution: semantic locators, live pause/inject, flow record/replay, one-line install.

Distributed via chrome-mcp.actuallyroy.com. The site hosts both bundled servers + tiny zero-dep loaders that handle install, updates, and SHA-256 tamper detection.

For users

One step. Paste into ~/.claude.json (or .mcp.json in your project) and restart Claude Code. The exact block is on the landing page with the correct bootstrap inlined. In shape:

{
  "mcpServers": {
    "chrome": {
      "command": "node",
      "args": ["-e", "<short bootstrap that fetches loader.mjs on first run>"]
    }
  }
}

Requires Node ≥18.

What happens:

  1. First launch: bootstrap downloads ~/.chrome-mcp/loader.mjs, loader fetches the latest bundle, verifies its SHA-256, caches it, executes it.
  2. First tool call: MCP tries to connect to localhost:9222 — not there? It spawns Chrome with --remote-debugging-port=9222 on a dedicated profile at ~/ChromeMCP-Profile. A Chrome window pops up.
  3. You sign into whatever sites once. The profile persists.
  4. Subsequent runs: Chrome is already up, bundle is cached, tools just work.

The dedicated profile coexists with your normal Chrome — we never touch your main profile.

Power-user install (skips the bootstrap, writes the loader directly):

curl -fsSL https://chrome-mcp.actuallyroy.com/install.sh | sh      # macOS / Linux
irm https://chrome-mcp.actuallyroy.com/install.ps1 | iex           # Windows

This gives you ~/.chrome-mcp/bin/chrome-mcp as a persistent binary you can reference directly in .mcp.json instead of the node -e form. Only reason to prefer this: faster cold start (skips the bootstrap's fetch check).

Updates

The loader checks the endpoint on every launch, downloads a newer bundle if one exists, verifies its SHA-256, and uses the cached copy if the network is unreachable. Controls (env vars):

  • CHROME_MCP_PIN_VERSION=0.2.0 — pin to a specific version, skip update checks
  • CHROME_MCP_SKIP_UPDATE=1 — use cached bundle, skip network
  • CHROME_MCP_ENDPOINT=https://… — override the origin (for self-hosting)
  • CHROME_MCP_CACHE_DIR=/path — override the cache location

Tools

Locator-taking tools accept { ref, text, label, selector } — refs come from outline and stay stable across calls.

  • Inspection: outline, describe, screenshot, snapshot, get_text, get_html, get_url, get_title, get_attribute
  • Interaction: click, fill, fill_form, select_option, press, type, hover, scroll
  • Capture: get_toasts, wait_for_toast, get_console, get_network
  • Navigation: navigate, go_back, go_forward, reload, wait_for_navigation, wait_for_selector
  • Tabs: list_tabs, select_tab, new_tab, close_tab
  • Debug: pause, resume, inject_script, evaluate
  • Flows: start_recording, stop_recording, recording_status, run_script, assert
  • Cookies: get_cookies, set_cookies

See examples/demo.flow.json for a sample run_script flow.


For contributors

Repo layout

.
├── app/                       # Next.js landing page + /api/version
├── loader/
│   ├── loader.mjs             # chrome-mcp loader (zero-dep)
│   ├── bootstrap.js           # chrome inline bootstrap for .mcp.json
│   ├── android-loader.mjs     # android-mcp loader (zero-dep)
│   └── android-bootstrap.js   # android inline bootstrap
├── installer/                 # install.sh / install.ps1 (chrome power-user)
├── mcp-server/                # chrome-mcp TypeScript source
│   ├── src/
│   └── package.json
├── android-mcp/               # android-mcp TypeScript source
│   ├── src/
│   │   ├── adb.ts             # adb subprocess wrapper
│   │   ├── devices.ts
│   │   ├── uiautomator2.ts    # JSON-RPC client + APK install
│   │   ├── outline.ts         # view-hierarchy → text outline + stable refs
│   │   ├── locators.ts        # text/desc/id/xpath/ref resolvers
│   │   ├── logcat.ts          # ring-buffered logcat capture
│   │   ├── tools.ts
│   │   ├── recorder.ts        # copied from mcp-server/
│   │   └── index.ts
│   └── package.json
├── macos-mcp/                 # macos-mcp Node + Swift sidecar
│   ├── src/
│   ├── swift-helper/          # AX + ScreenCaptureKit Swift sidecar
│   ├── scripts/build-helper.sh
│   └── package.json
├── windows-mcp/               # windows-mcp Node + C# sidecar
│   ├── src/
│   ├── csharp-helper/         # UI Automation + SendInput .NET 8 sidecar
│   ├── scripts/build-helper.ps1
│   └── package.json
├── scripts/
│   ├── build-mcp.mjs          # builds chrome bundle + manifest
│   ├── build-android-mcp.mjs  # builds android bundle + manifest + fetches UIAutomator2 APKs
│   ├── build-macos-mcp.mjs    # builds macos bundle + manifest + Swift helper
│   ├── build-windows-mcp.mjs  # builds windows bundle + manifest + C# helper
│   ├── launch-chrome.sh
│   └── launch-chrome.ps1
├── examples/
└── public/                    # (gitignored; populated on build)
    ├── bundle/v<version>.mjs             # chrome
    ├── bundle/manifest.json
    ├── loader.mjs                        # chrome loader
    ├── bootstrap.min.js
    ├── android/bundle/v<version>.mjs
    ├── android/bundle/manifest.json
    ├── android/loader.mjs
    ├── android/bootstrap.min.js
    ├── android/vendor/uiautomator2-server.apk
    ├── android/vendor/uiautomator2-server-test.apk
    ├── macos/bundle/v<version>.mjs
    ├── macos/bundle/manifest.json
    ├── macos/loader.mjs
    ├── macos/bootstrap.min.js
    ├── macos/vendor/macos-mcp-helper
    ├── windows/bundle/v<version>.mjs
    ├── windows/bundle/manifest.json
    ├── windows/loader.mjs
    ├── windows/bootstrap.min.js
    └── windows/vendor/windows-mcp-helper.exe

android-mcp quick reference

Users must have adb on PATH or set ANDROID_MCP_ADB / $ANDROID_SDK_ROOT. Devices listed by adb devices are detected automatically; multiple devices require select_device first.

On first tool call, the MCP:

  1. Ensures exactly one device is active (or throws).
  2. Checks that io.appium.uiautomator2.server + its test APK are installed; if not, downloads from the Vercel endpoint and adb install -rs them.
  3. adb forward tcp:6790 tcp:6790.
  4. Starts the UIAutomator2 test runner (am instrument …), polls /wd/hub/status until ready.
  5. Creates a session, caches the id for the process lifetime.

Set ANDROID_MCP_APK_LOCAL=/abs/path/to.apk to use a hand-placed APK instead of downloading.

Known issue: "UiAutomation not connected"

On some emulator AVD configurations (especially Play Store images, or anything that blocks AccessibilityService by policy), the Appium UIAutomator2 server reports IllegalStateException: UiAutomation not connected, UiAutomation@…[id=-1, ...] on session creation. The APK installs, the HTTP server starts on :6790, but the AccessibilityService handshake never completes. Workarounds:

  • Use a Google APIs (non-Play Store) system image for the AVD; accessibility service hookup is permissive on those.
  • Or: enable the server as an accessibility service explicitly:
    adb shell settings put secure enabled_accessibility_services io.appium.uiautomator2.server.test/androidx.test.runner.AndroidJUnitRunner
    adb shell settings put secure accessibility_enabled 1
  • Or: swap the driver to openatx/uiautomator2-server — its init path doesn't require AccessibilityService and is generally more emulator-friendly. This requires changing the APK references in android-mcp/src/uiautomator2.ts (see issue #34 in project notes).

Everything up to the session handshake (ADB, APK install, instrumentation, HTTP ready) is validated; this is purely an Android-side accessibility-policy issue on certain devices.

Build

npm install             # root deps (Next.js + esbuild)
npm run build           # runs build:mcp (tsc + esbuild + manifest), then next build
npm run dev             # dev server only; run `npm run build:mcp` first

Release a new version

  1. Bump mcp-server/package.jsonversion.
  2. Commit + push. Vercel rebuilds: the manifest now points to v<new>.mjs.
  3. Existing installs auto-update on their next launch (unless they've set CHROME_MCP_PIN_VERSION or CHROME_MCP_SKIP_UPDATE).

Develop the MCP server locally

cd mcp-server
npm install
npm run build

For iterating without going through Vercel, point a local install at a local server:

# Terminal 1: serve public/ on port 18888
npx http-server ./public -p 18888

# Terminal 2: run Claude Code with
CHROME_MCP_ENDPOINT=http://127.0.0.1:18888 ~/.chrome-mcp/bin/chrome-mcp

License

chrome-mcp and android-mcp are released under the GNU General Public License v3.0 (see LICENSE).

GPLv3 is a copyleft license. In short:

  • ✅ Use it freely — personally, internally, for clients, in CI, anywhere. There is no usage restriction.
  • ✅ Modify it freely.
  • ⚠️ If you redistribute it (binaries, forks, repackaged installers, hosted services that ship the code to users), the redistributed work must also be GPLv3 and you must make the corresponding source available to recipients.
  • ⚠️ Code that links into / extends the MCP servers (custom tools compiled in, forks of the loader/bundle) inherits GPLv3.
  • ❌ You can't sublicense it under MIT/Apache/proprietary.

Bundled third-party deps (puppeteer-core, @modelcontextprotocol/sdk, pngjs, the Appium UIAutomator2 server APK) are Apache-2.0 / MIT and are GPLv3-compatible.

If you're unsure whether your use case requires source disclosure, the practical rule of thumb is: internal use never does; redistribution does.

About

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors