Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
cache: npm
- run: npm ci
- run: npm test
- run: node --experimental-test-module-mocks --test test/adapters.test.mjs test/redaction.test.mjs test/remove.test.mjs test/host-profiles.test.mjs test/host-origin.test.mjs test/prepare-page.test.mjs test/load.test.mjs
- run: node --experimental-test-module-mocks --test test/adapters.test.mjs test/redaction.test.mjs test/remove.test.mjs test/host-profiles.test.mjs test/host-origin.test.mjs test/prepare-page.test.mjs test/load.test.mjs test/linux-chrome.test.mjs
- if: runner.os == 'macOS'
run: swift build --package-path helper/macos -c release
- run: npm audit --omit=dev
35 changes: 21 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ Network event observations are redacted before they cross the extension boundary
| --- | --- | --- |
| macOS | LaunchAgent | Swift helper (Accessibility) |
| Windows 11 | logon Scheduled Task | PowerShell 7 helper (UI Automation) |
| Linux (systemd) | systemd user service | not automated yet — use `chrome://extensions` |
| Linux (systemd) | systemd user service | Chrome Ops' own development Chrome (DevTools pipe) |

Claude Code, Codex, Cursor, and Grok Build are registered the same way on every OS.

On Linux, Chrome Ops runs a separate **development Chrome** with its own profile instead of driving your everyday Chrome. Wayland does not let other programs press Chrome's buttons, so Chrome Ops starts that Chrome itself and uses Chrome's DevTools pipe for Load unpacked, reload, errors and remove. The Bridge lives in that Chrome, so every Chrome Ops tool works on its tabs.

## Quick start

Requirements: Chrome with Developer mode, Node.js 22+, and Git. macOS also needs the Swift toolchain (`swift`) and Accessibility permission for the app that runs Chrome Ops. Windows also needs PowerShell 7+ (`pwsh`). Linux needs a systemd user session.
Requirements: Chrome, Node.js 22+, and Git. macOS also needs the Swift toolchain (`swift`) and Accessibility permission for the app that runs Chrome Ops. Windows also needs PowerShell 7+ (`pwsh`). Linux needs a graphical systemd user session and Google Chrome or Chromium.

```sh
git clone https://github.com/kitepon/chrome-ops-mcp.git
Expand All @@ -34,29 +36,33 @@ npm run setup

`npm run setup` installs dependencies, builds, and starts the persistent **Chrome Ops Host** as a per-user service for this OS. It is safe to repeat. When an unpacked Bridge is already connected, it also updates the Bridge from this checkout and waits for the new worker to reconnect.

Open `chrome://extensions`, enable Developer mode, choose **Load unpacked**, and select this repository's `extension/` directory. Load it in one Chrome profile only; if several Bridge profiles connect, Chrome Ops reports the ambiguity instead of guessing. Then register the harnesses you use:
On macOS and Windows, open `chrome://extensions`, enable Developer mode, choose **Load unpacked**, and select this repository's `extension/` directory. Load it in one Chrome profile only; if several Bridge profiles connect, Chrome Ops reports the ambiguity instead of guessing.

On Linux, `npm run setup` starts the development Chrome and loads the Bridge into it; there is nothing to load by hand. Do not load the Bridge into another Chrome on Linux. Close the development Chrome whenever you like; reopen it from **Chrome Ops Chrome** in the application menu, or let a developer operation start it. Its profile is `${XDG_DATA_HOME:-~/.local/share}/chrome-ops/chrome-profile`.

Then register the harnesses you use:

```sh
npm run register # every installed harness
npm run register:claude # or one of: claude, codex, cursor, grok
npm run doctor
```

Registration backs up the harness configuration before a change and refuses to replace a different `chrome-ops` entry. Restart or reload the harness if it does not discover the new server immediately. `npm run doctor` reports the Host service, the Bridge connection, developer-operation readiness, and the registration state of each harness. `npm run uninstall` stops and removes the Host service; it leaves the Bridge extension and harness registrations in place.
Registration backs up the harness configuration before a change and refuses to replace a different `chrome-ops` entry. Restart or reload the harness if it does not discover the new server immediately. `npm run doctor` reports the Host service, the Bridge connection, developer-operation readiness, and the registration state of each harness. `npm run uninstall` stops and removes the Host service (and on Linux the development Chrome service and launcher); it leaves the Bridge extension, the Linux development Chrome profile, and harness registrations in place.

Registrations point at the absolute path of the current Node executable and of `dist/index.js` in this checkout. Moving the checkout or changing Node requires registering again.

Logs: macOS writes the Host log to `~/Library/Logs/ChromeOps/`; Linux uses `journalctl --user -u chrome-ops-host`. Backups of harness configuration go to `~/Library/Application Support/ChromeOps/backups` (macOS), `%LOCALAPPDATA%\ChromeOps\backups` (Windows), or `${XDG_STATE_HOME:-~/.local/state}/chrome-ops/backups` (Linux).
Logs: macOS writes the Host log to `~/Library/Logs/ChromeOps/`; Linux uses `journalctl --user -u chrome-ops-host` and `journalctl --user -u chrome-ops-chrome`. Backups of harness configuration go to `~/Library/Application Support/ChromeOps/backups` (macOS), `%LOCALAPPDATA%\ChromeOps\backups` (Windows), or `${XDG_STATE_HOME:-~/.local/state}/chrome-ops/backups` (Linux).

## Code layout

Shared code, OS adaptation, and harness adaptation live in separate files.

| Area | Shared | OS adaptation | Harness adaptation |
| --- | --- | --- | --- |
| MCP server | `src/index.ts`, `src/bridge.ts`, `src/host.ts`, `src/helper.ts` | `src/os/{macos,windows,linux}.ts` | — |
| MCP server | `src/index.ts`, `src/bridge.ts`, `src/host.ts`, `src/helper.ts` | `src/os/{macos,windows,linux}.ts`, `src/os/linux-*.ts` | — |
| Setup | `scripts/chrome-ops.mjs`, `scripts/lib/` | `scripts/os/{macos,windows,linux}.mjs` | `scripts/harness/{claude,codex,cursor,grok}.mjs` |
| Native helper | `helper/contract.ts` | `helper/macos/`, `helper/windows/` | — |
| Native helper | `helper/contract.ts` | `helper/macos/`, `helper/windows/`, `src/os/linux-helper.ts` | — |

See `docs/ARCHITECTURE.md` for the contracts between them.

Expand Down Expand Up @@ -86,16 +92,17 @@ Chrome 116+ keeps extension service workers alive when WebSocket traffic is acti
- macOS helper: Load unpacked, exact-ID reload, Errors extraction, remove + postcondition verification on a disposable fixture
- macOS LaunchAgent: setup, repeat setup, restart, uninstall, and reinstall with stdio MCP reconnection
- v0.3 on macOS 27, Windows 11, and Ubuntu 26.04: `npm run setup`, `npm run register` for Claude Code, Codex, Cursor, and Grok Build, `npm run doctor`, and each harness's own MCP check. Load/reload/errors/remove passed on macOS and Windows with a disposable fixture.
- v0.4 on Ubuntu 26.04 (GNOME Wayland): the development Chrome started by `npm run setup`, and load/reload/errors/remove through MCP and from Claude Code, Codex, Cursor, and Grok Build. CI runs the Linux helper against headless Chrome.

## Known boundary

Chrome's public `chrome.management` extension API can inspect installed extensions but does not expose arbitrary unpacked-directory loading or another extension's developer reload action. Chrome Ops uses deliberately narrow PowerShell 7 + Windows UI Automation and Swift + macOS Accessibility helpers for those developer-mode operations. The helpers are not generic shell/UI automation APIs.
Chrome's public `chrome.management` extension API can inspect installed extensions but does not expose arbitrary unpacked-directory loading or another extension's developer reload action. Chrome Ops uses deliberately narrow PowerShell 7 + Windows UI Automation and Swift + macOS Accessibility helpers for those developer-mode operations. On Linux it uses the DevTools pipe of the development Chrome it started itself. The helpers are not generic shell/UI automation APIs.

### v0.3 alpha limitations
### v0.4 alpha limitations

- Chrome Developer mode must already be enabled for unpacked-extension operations.
- Developer-management UI automation is currently validated against Japanese and English Chrome labels; other UI languages are not yet guaranteed.
- The Chrome Ops extension itself must be loaded manually once during initial setup. Later `npm run setup` runs update an already connected unpacked Bridge.
- Linux does not automate Load unpacked, reload, errors, or remove yet. Chrome's extension-management page is not exposed through AT-SPI in the tested GNOME Wayland session; everything else works on Linux.
- Each unpacked-extension developer operation prepares its own management tab in the connected Bridge profile. Load verifies the new development extension through Chrome's management API before returning `verifiedLoaded: true`. Reload reports UI submission; check an observable version or behavior change. Remove verifies absence through Chrome's management API.
- On macOS and Windows, Chrome Developer mode must already be enabled for unpacked-extension operations. The Linux development Chrome turns it on itself.
- Developer-management UI automation on macOS and Windows is currently validated against Japanese and English Chrome labels; other UI languages are not yet guaranteed.
- On macOS and Windows, the Chrome Ops extension itself must be loaded manually once during initial setup. Later `npm run setup` runs update an already connected unpacked Bridge.
- On Linux the developer operations and every other tool act on the development Chrome, not on your everyday Chrome.
- On macOS and Windows, each unpacked-extension developer operation prepares its own management tab in the connected Bridge profile. Load verifies the new development extension through Chrome's management API before returning `verifiedLoaded: true`. Reload reports UI submission; check an observable version or behavior change. Remove verifies absence through Chrome's management API.
- Each OS was verified on one machine and Chrome installation.
18 changes: 15 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Network secrets are redacted before crossing from the extension to the MCP proce

Initial `Load unpacked` is a helper operation because Chrome's public extension APIs do not expose arbitrary unpacked-directory loading.

On every OS with a helper, the Bridge first prepares a fixed management tab in its own Chrome profile. The helper matches that exact tab before using developer controls. The installer has one internal, fixed Bridge-update action for an already connected unpacked Bridge; it is not an MCP tool and does not accept arbitrary URLs or input commands.
On macOS and Windows, the Bridge first prepares a fixed management tab in its own Chrome profile. The helper matches that exact tab before using developer controls. The installer has one internal, fixed Bridge-update action for an already connected unpacked Bridge; it is not an MCP tool and does not accept arbitrary URLs or input commands.

## Code layout: shared, OS, harness

Expand All @@ -41,12 +41,14 @@ Shared code, OS adaptation, and harness adaptation are separated at the file lev
- OS: `os/macos.ts`, `os/windows.ts`, `os/linux.ts`, selected by `os/index.ts`. Contract in `os/types.ts`:
- `unsupportedReason` — `null` when the OS has a developer-operation helper.
- `reportsLoadedId` — whether the helper reads the new id after Load unpacked. Otherwise the server takes the one new development extension.
- `usesManagementTab` — whether the Bridge must prepare a chrome://extensions tab before the helper runs (macOS, Windows).
- `helper(operation, value, token)` — the helper command line.
- `startBrowser` — starts the Chrome that carries the Bridge when Chrome Ops owns it (Linux); `null` elsewhere.

### Setup (`scripts/`)

- Shared: `chrome-ops.mjs` (entry: `setup`, `uninstall`, `doctor`, `register`), `lib/host-client.mjs`, `lib/bridge-update.mjs`, `lib/registration.mjs`, `lib/process.mjs`.
- OS: `os/macos.mjs` (LaunchAgent, Swift build), `os/windows.mjs` (Scheduled Task, npm-shim resolution), `os/linux.mjs` (systemd user service). Each exports:
- OS: `os/macos.mjs` (LaunchAgent, Swift build), `os/windows.mjs` (Scheduled Task, npm-shim resolution), `os/linux.mjs` (systemd user services for the Host and the development Chrome, and its launcher). Each exports:
- `name`, `stateDir` (backups), `exec(program, args)` (runs a harness CLI), `prepare()`,
- `installService()` → `{ changed, service }` (idempotent, rolls back on failure), `uninstallService()`, `serviceStatus()`,
- `developerOperations()` (doctor), `nativeBridgeUpdate(id, activeTabHashes)` or `null`.
Expand All @@ -56,4 +58,14 @@ Shared code, OS adaptation, and harness adaptation are separated at the file lev

### Native helpers (`helper/`)

`helper/contract.ts` is shared. `helper/macos/` (Swift, Accessibility) and `helper/windows/` (PowerShell 7, UI Automation) implement it. Linux has no helper yet.
`helper/contract.ts` is shared. `helper/macos/` (Swift, Accessibility) and `helper/windows/` (PowerShell 7, UI Automation) implement it by driving the user's Chrome.

### Linux development Chrome

Wayland does not let another program press Chrome's buttons, and Chrome only exposes its pages to AT-SPI when started with `--force-renderer-accessibility`. So on Linux Chrome Ops does not drive the user's Chrome. Instead:

- `src/os/linux-chrome.ts` runs as the `chrome-ops-chrome` systemd user service. It starts Chrome with a dedicated profile, `--remote-debugging-pipe` and `--enable-unsafe-extension-debugging`, turns on developer mode, and loads the Bridge from this checkout (loading again on every start keeps it current). No debugging port is opened.
- It listens on `$XDG_RUNTIME_DIR/chrome-ops/chrome.sock` (user-only). `src/os/linux-helper.ts` is the helper command: it sends one `{operation, value}` request there, starting the service first if needed, and prints the same result JSON as the other helpers.
- Load uses `Extensions.loadUnpacked`, remove uses `Extensions.uninstall` (unpacked only). Reload and errors open chrome://extensions in a background tab and call `chrome.developerPrivate.reload` / `getExtensionInfo`, the same source as the Errors view. Values are an absolute directory or an exact extension id; the expressions are fixed.
- The service is not started at login. `npm run setup`, a developer operation, or the **Chrome Ops Chrome** launcher starts it; closing the last window stops it.
- `src/os/linux-paths.ts` holds the profile, socket and Chrome locations shared by the service, the helper and setup.
2 changes: 1 addition & 1 deletion helper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Native helper boundary for Chrome developer-mode operations unavailable through

The helper is intentionally **not** a shell, terminal, generic UI automation server, or arbitrary filesystem API. See `docs/ARCHITECTURE.md` for the allowlist.

Windows uses PowerShell 7 and UI Automation. macOS uses Swift and Accessibility APIs. Both implement the same four-operation contract. On macOS, `npm run setup` builds the release helper used by the MCP server. Linux has no helper yet. The MCP server picks the helper through `src/os/`.
Windows uses PowerShell 7 and UI Automation. macOS uses Swift and Accessibility APIs. Both implement the same four-operation contract. On macOS, `npm run setup` builds the release helper used by the MCP server. On Linux, `src/os/linux-helper.ts` hands the same four operations to the development Chrome that Chrome Ops runs (`src/os/linux-chrome.ts`), which performs them through its DevTools pipe. The MCP server picks the helper through `src/os/`.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chrome-ops-mcp",
"version": "0.3.0-alpha.0",
"version": "0.4.0-alpha.0",
"description": "MCP server for Chrome DevTools, extension management, and browser settings via a local Chrome extension.",
"type": "module",
"main": "dist/index.js",
Expand Down
Loading
Loading