@mehmoodqureshi/chrome-mcp 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -20
- package/SETUP.md +37 -25
- package/dist/src/cli.js +26 -9
- package/dist/src/config.js +4 -2
- package/dist/src/extension-install.d.ts +42 -0
- package/dist/src/extension-install.js +86 -0
- package/extension-dist/manifest.json +1 -1
- package/extension-dist/options.html +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,29 +108,55 @@ explicitly (and is never written to disk).
|
|
|
108
108
|
|
|
109
109
|
**2. Load the extension** — **required**; the server can drive nothing without it.
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
The extension ships prebuilt inside the npm package, and every time the server
|
|
112
|
+
boots it copies it to a plain folder right under your home directory:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
~/chrome-mcp-extension (macOS / Linux)
|
|
116
|
+
%USERPROFILE%\chrome-mcp-extension (Windows)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
So after step 1 has started the server once (restart your client, or `/mcp` in
|
|
120
|
+
Claude Code), the folder is already there. To create it without a client, or
|
|
121
|
+
to print the exact path:
|
|
113
122
|
|
|
114
123
|
```bash
|
|
115
|
-
|
|
116
|
-
chrome-mcp --extension-path # prints the absolute path of extension-dist
|
|
124
|
+
npx -y @mehmoodqureshi/chrome-mcp --extension-path
|
|
117
125
|
```
|
|
118
126
|
|
|
119
127
|
Then `chrome://extensions` → enable **Developer mode** → **Load unpacked** →
|
|
120
|
-
|
|
121
|
-
|
|
128
|
+
pick `chrome-mcp-extension` in your home folder. After upgrading the package the
|
|
129
|
+
server refreshes the files on its next boot; click **Reload** on
|
|
130
|
+
`chrome://extensions` to pick them up. `CHROME_MCP_EXTENSION_DIR` moves the
|
|
131
|
+
folder somewhere else. (Working from a git clone instead? Run
|
|
132
|
+
`npm install && npm run build:ext` first — `extension-dist/` is gitignored, and
|
|
133
|
+
the server mirrors it to the same home folder.)
|
|
122
134
|
|
|
123
135
|
**3. Pair it — usually nothing to do.** Every time the server boots it writes
|
|
124
136
|
`pairing.json` (mode 0600, never shipped in the tarball) into the very
|
|
125
|
-
`extension
|
|
126
|
-
own folder on startup and pairs itself, so the toolbar badge turns
|
|
127
|
-
token to paste. Load the extension before the server has ever
|
|
128
|
-
every 30 seconds and pairs as soon as the file appears.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
`chrome-mcp-extension` folder you just loaded. The extension reads that file
|
|
138
|
+
from its own folder on startup and pairs itself, so the toolbar badge turns
|
|
139
|
+
green with no token to paste. Load the extension before the server has ever
|
|
140
|
+
run? It re-checks every 30 seconds and pairs as soon as the file appears.
|
|
141
|
+
|
|
142
|
+
**Where to see the badge:** it sits on the extension's icon in Chrome's
|
|
143
|
+
toolbar, not on the `chrome://extensions` page. Chrome hides new extensions
|
|
144
|
+
behind the puzzle-piece button at the right of the address bar, so click that,
|
|
145
|
+
find **Chrome MCP Bridge**, and click the pin next to it once; the icon then
|
|
146
|
+
stays in the toolbar. Hover it for the status in words.
|
|
147
|
+
|
|
148
|
+
| Badge | Meaning |
|
|
149
|
+
|---|---|
|
|
150
|
+
| green dot | paired and connected |
|
|
151
|
+
| yellow dots | connecting |
|
|
152
|
+
| grey circle | not paired yet (no server has run, or no pairing file) |
|
|
153
|
+
| red exclamation mark | token rejected; the server rotated it, re-pairs by itself in a moment |
|
|
154
|
+
|
|
155
|
+
Manual fallback (a copied folder, a read-only home): run
|
|
156
|
+
`npx -y @mehmoodqureshi/chrome-mcp --print-pairing`, open the extension's
|
|
157
|
+
**Options** page, and paste the `port` + `token` from
|
|
158
|
+
`~/.chrome-mcp/handshake.json`. Values saved there take precedence over the
|
|
159
|
+
bundled file.
|
|
134
160
|
|
|
135
161
|
### Running more than one session
|
|
136
162
|
|
|
@@ -178,7 +204,7 @@ start. Wrap it in `cmd /c`:
|
|
|
178
204
|
|
|
179
205
|
Or from Claude Code: `claude mcp add chrome-mcp -- cmd /c npx -y @mehmoodqureshi/chrome-mcp --allow-domain example.com`
|
|
180
206
|
|
|
181
|
-
Everything else is the same — load
|
|
207
|
+
Everything else is the same — load `%USERPROFILE%\chrome-mcp-extension` and pair
|
|
182
208
|
as above.
|
|
183
209
|
|
|
184
210
|
The tools cover tabs, navigation, interaction (`click`/`type`/`press`/`hover`/
|
|
@@ -423,10 +449,13 @@ RUN_EXT_SMOKE=1 node --test dist/test/extension-smoke.test.js # live, headed
|
|
|
423
449
|
## The extension
|
|
424
450
|
|
|
425
451
|
`extension/` builds (esbuild) to `extension-dist/`, loaded via
|
|
426
|
-
`chrome://extensions` → **Load unpacked** → select
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
452
|
+
`chrome://extensions` → **Load unpacked** → select `~/chrome-mcp-extension`, the
|
|
453
|
+
mirror the server refreshes from `extension-dist/` on every boot (loading
|
|
454
|
+
`extension-dist/` directly also works). It pairs itself from the `pairing.json`
|
|
455
|
+
the server writes into that folder; the **Options** page paste of `port` +
|
|
456
|
+
`token` from `~/.chrome-mcp/handshake.json` (run
|
|
457
|
+
`npx -y @mehmoodqureshi/chrome-mcp --print-pairing` to get the path) is only
|
|
458
|
+
the fallback.
|
|
430
459
|
|
|
431
460
|
> **Reads/interaction use `chrome.scripting`/`chrome.tabs`** — no "is being
|
|
432
461
|
> debugged" banner, CSP-safe reads (isolated world), testable under Playwright.
|
package/SETUP.md
CHANGED
|
@@ -33,25 +33,22 @@ npm --version
|
|
|
33
33
|
|
|
34
34
|
If Node is below 18, stop and tell the human to upgrade before anything else.
|
|
35
35
|
|
|
36
|
-
## Step 2 — Install the
|
|
36
|
+
## Step 2 — Install the extension folder
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
No global install is needed. This one command fetches the package, copies the
|
|
39
|
+
extension to a plain folder directly under the home directory, and prints its
|
|
40
|
+
path:
|
|
39
41
|
|
|
40
42
|
```
|
|
41
|
-
|
|
42
|
-
chrome-mcp --version
|
|
43
|
+
npx -y @mehmoodqureshi/chrome-mcp --extension-path
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
It prints the absolute path of the bundled `extension-dist` folder. Verify with
|
|
52
|
-
`ls` (or `dir`) that the folder holds `manifest.json` and `background.js`.
|
|
53
|
-
Record the absolute path; the human needs it in step 4. The install is small
|
|
54
|
-
and downloads no browser: the server drives the Chrome the human already has.
|
|
46
|
+
It prints `~/chrome-mcp-extension` (on Windows `%USERPROFILE%\chrome-mcp-extension`).
|
|
47
|
+
Verify with `ls` (or `dir`) that the folder holds `manifest.json` and
|
|
48
|
+
`background.js`. Record the path; the human needs it in step 4. The install is
|
|
49
|
+
small and downloads no browser: the server drives the Chrome the human already
|
|
50
|
+
has. The server refreshes this folder on every boot, so upgrades need no
|
|
51
|
+
re-copy, only a Reload on `chrome://extensions`.
|
|
55
52
|
|
|
56
53
|
## Step 3 — Wire the server into this MCP client
|
|
57
54
|
|
|
@@ -108,7 +105,8 @@ You cannot do this step yourself. Give the human these exact instructions:
|
|
|
108
105
|
|
|
109
106
|
1. Open `chrome://extensions` in Chrome.
|
|
110
107
|
2. Turn on **Developer mode** (top right).
|
|
111
|
-
3. Click **Load unpacked** and choose the `extension
|
|
108
|
+
3. Click **Load unpacked** and choose the `chrome-mcp-extension` folder in
|
|
109
|
+
their home directory (the path from step 2).
|
|
112
110
|
4. Confirm an extension named **Chrome MCP Bridge** now appears in the list.
|
|
113
111
|
|
|
114
112
|
Wait for the human to confirm before continuing.
|
|
@@ -116,7 +114,7 @@ Wait for the human to confirm before continuing.
|
|
|
116
114
|
## Step 5 — Pairing (automatic; verify it)
|
|
117
115
|
|
|
118
116
|
Pairing needs no paste. Every time the server boots it writes `pairing.json`
|
|
119
|
-
(mode 0600) into the same `extension
|
|
117
|
+
(mode 0600) into the same `chrome-mcp-extension` folder the human loaded in step 4.
|
|
120
118
|
The extension reads that file from its own folder and pairs itself. So make
|
|
121
119
|
sure a server has booted at least once since step 2, in either of these ways:
|
|
122
120
|
|
|
@@ -124,19 +122,33 @@ sure a server has booted at least once since step 2, in either of these ways:
|
|
|
124
122
|
- start one yourself in pairing mode and leave it running in the background:
|
|
125
123
|
|
|
126
124
|
```
|
|
127
|
-
chrome-mcp --print-pairing --persist-token
|
|
125
|
+
npx -y @mehmoodqureshi/chrome-mcp --print-pairing --persist-token
|
|
128
126
|
```
|
|
129
127
|
|
|
130
128
|
Verify the file exists (do not print it):
|
|
131
129
|
|
|
132
130
|
```
|
|
133
|
-
ls "$(chrome-mcp --extension-path)/pairing.json"
|
|
131
|
+
ls "$(npx -y @mehmoodqureshi/chrome-mcp --extension-path)/pairing.json"
|
|
134
132
|
```
|
|
135
133
|
|
|
136
|
-
Then
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
Then tell the human where to look. The status badge is on the extension's
|
|
135
|
+
icon in Chrome's toolbar, not on the `chrome://extensions` page. Chrome hides
|
|
136
|
+
new extensions behind the puzzle-piece button at the right of the address bar,
|
|
137
|
+
so give them these exact steps:
|
|
138
|
+
|
|
139
|
+
1. Click the puzzle-piece button at the right of the address bar.
|
|
140
|
+
2. Find **Chrome MCP Bridge** in the list and click the pin icon next to it.
|
|
141
|
+
The extension icon now stays in the toolbar.
|
|
142
|
+
3. Look at the small badge on that icon. Hovering it shows the status in words.
|
|
143
|
+
|
|
144
|
+
Badge meanings: a green dot means paired and connected. Yellow dots mean
|
|
145
|
+
connecting. A grey circle means not paired yet, usually because no server has
|
|
146
|
+
run since the extension was loaded. A red exclamation mark means the token was
|
|
147
|
+
rejected, which the extension fixes by itself within a few seconds by re-reading
|
|
148
|
+
the pairing file.
|
|
149
|
+
|
|
150
|
+
A green dot can take up to 30 seconds if the extension was loaded before the
|
|
151
|
+
server first ran. Wait for the human to confirm the green dot.
|
|
140
152
|
|
|
141
153
|
**Manual fallback**, only if the badge stays grey after a minute (a copied
|
|
142
154
|
extension folder or a read-only global install): read the port without
|
|
@@ -178,10 +190,10 @@ not stick. Re-check the port and token before anything else.
|
|
|
178
190
|
Summarize for the human:
|
|
179
191
|
|
|
180
192
|
- Node and package versions installed.
|
|
181
|
-
- The absolute `extension
|
|
193
|
+
- The absolute `chrome-mcp-extension` path they loaded.
|
|
182
194
|
- Which MCP client was configured, at which scope, with which domains and gates.
|
|
183
|
-
- Whether
|
|
184
|
-
each verified
|
|
195
|
+
- Whether the badge went green, and whether `tabs_list`, an allowed
|
|
196
|
+
navigation, and a refused navigation each verified.
|
|
185
197
|
- Anything still open on their side, such as restarting the client.
|
|
186
198
|
|
|
187
199
|
For any failure, name the exact symptom, what you tried, and the matching
|
package/dist/src/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ const datadir_1 = require("./bridge/datadir");
|
|
|
21
21
|
const workspace_1 = require("./bridge/workspace");
|
|
22
22
|
const auth_1 = require("./bridge/auth");
|
|
23
23
|
const server_2 = require("./mcp/server");
|
|
24
|
+
const extension_install_1 = require("./extension-install");
|
|
24
25
|
/** Hard deadline for clean shutdown before we force-exit (a stuck socket must not hang us). */
|
|
25
26
|
const SHUTDOWN_DEADLINE_MS = 3000;
|
|
26
27
|
/**
|
|
@@ -52,12 +53,23 @@ function version() {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
56
|
+
* Mirror the bundled extension into the visible install folder
|
|
57
|
+
* (`~/chrome-mcp-extension`) and report where Chrome should load it from. On a
|
|
58
|
+
* read-only home the bundled folder is returned instead; both are loadable.
|
|
58
59
|
*/
|
|
59
|
-
function
|
|
60
|
-
|
|
60
|
+
function installExtension() {
|
|
61
|
+
const r = (0, extension_install_1.syncExtension)();
|
|
62
|
+
if (!r.ok) {
|
|
63
|
+
(0, server_2.logDebug)(`extension not mirrored to the home folder (${r.error}); using the bundled copy at ${r.dir}`);
|
|
64
|
+
return r.dir;
|
|
65
|
+
}
|
|
66
|
+
if (r.created) {
|
|
67
|
+
(0, server_2.logErr)(`extension installed at ${r.dir} — chrome://extensions -> Load unpacked -> pick that folder`);
|
|
68
|
+
}
|
|
69
|
+
else if (r.copied.length > 0) {
|
|
70
|
+
(0, server_2.logErr)(`extension files updated at ${r.dir} — click Reload on chrome://extensions to pick up the new version`);
|
|
71
|
+
}
|
|
72
|
+
return r.dir;
|
|
61
73
|
}
|
|
62
74
|
/** Render a byte count as a short human string (1.2 MB, 904 KB, …). */
|
|
63
75
|
function humanBytes(n) {
|
|
@@ -163,7 +175,7 @@ async function main() {
|
|
|
163
175
|
return;
|
|
164
176
|
}
|
|
165
177
|
if (cfg.showExtensionPath) {
|
|
166
|
-
process.stdout.write(`${
|
|
178
|
+
process.stdout.write(`${installExtension()}\n`);
|
|
167
179
|
return;
|
|
168
180
|
}
|
|
169
181
|
const dataDir = (0, datadir_1.ensureDataDir)(cfg.dataDir);
|
|
@@ -194,13 +206,18 @@ async function main() {
|
|
|
194
206
|
// Drop the same port + token into the bundled extension folder so a Load
|
|
195
207
|
// unpacked from there pairs itself. Best-effort: a read-only install just
|
|
196
208
|
// falls back to the Options-page paste.
|
|
197
|
-
const
|
|
209
|
+
const extDir = installExtension();
|
|
210
|
+
const bundled = (0, auth_1.writeBundledPairing)(extDir, { port, token });
|
|
198
211
|
if (bundled) {
|
|
199
|
-
(0, server_2.logErr)(`auto-pairing file written to ${bundled} — Load unpacked from
|
|
212
|
+
(0, server_2.logErr)(`auto-pairing file written to ${bundled} — Load unpacked from ${extDir} needs no token paste`);
|
|
200
213
|
}
|
|
201
214
|
else {
|
|
202
|
-
(0, server_2.logDebug)(`auto-pairing file not written (extension folder missing or read-only at ${
|
|
215
|
+
(0, server_2.logDebug)(`auto-pairing file not written (extension folder missing or read-only at ${extDir})`);
|
|
203
216
|
}
|
|
217
|
+
// Anyone who loaded the extension straight from the package folder (0.8.0
|
|
218
|
+
// docs) keeps pairing too.
|
|
219
|
+
if (extDir !== (0, extension_install_1.bundledExtensionDir)())
|
|
220
|
+
(0, auth_1.writeBundledPairing)((0, extension_install_1.bundledExtensionDir)(), { port, token });
|
|
204
221
|
if (process.env.CHROME_MCP_TOKEN) {
|
|
205
222
|
(0, server_2.logErr)('token: pinned from CHROME_MCP_TOKEN (stable; pair once, never again).');
|
|
206
223
|
}
|
package/dist/src/config.js
CHANGED
|
@@ -274,8 +274,10 @@ Connection:
|
|
|
274
274
|
land in profiles/<profile>/tasks/<task>/.
|
|
275
275
|
--print-pairing Write the handshake, print its path, and keep the bridge
|
|
276
276
|
up until Ctrl-C (manual pairing; never serves MCP)
|
|
277
|
-
--extension-path
|
|
278
|
-
|
|
277
|
+
--extension-path Install the extension to ~/chrome-mcp-extension (or
|
|
278
|
+
CHROME_MCP_EXTENSION_DIR) and print that path — the folder
|
|
279
|
+
to pick in chrome://extensions -> Load unpacked. The server
|
|
280
|
+
also refreshes it on every boot.
|
|
279
281
|
--persist-token Reuse a stable on-disk token across restarts so the
|
|
280
282
|
extension never has to re-pair (default: fresh per boot).
|
|
281
283
|
CHROME_MCP_TOKEN env, if set, pins the token explicitly.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/extension-install.ts — put the extension somewhere a human can find.
|
|
3
|
+
*
|
|
4
|
+
* The extension ships inside the npm package, which is the wrong place to send
|
|
5
|
+
* someone with a "Load unpacked" file dialog: a global install buries it five
|
|
6
|
+
* folders deep under `npm root -g`, and an npx run hides it in a cache. So on
|
|
7
|
+
* every boot (and on `--extension-path`) the server mirrors the bundled
|
|
8
|
+
* `extension-dist/` into a plain, visible folder directly under the home
|
|
9
|
+
* directory — `~/chrome-mcp-extension` — and that is the folder the docs, the
|
|
10
|
+
* pairing file, and `--extension-path` all point at.
|
|
11
|
+
*
|
|
12
|
+
* The copy is a one-way mirror of top-level regular files, written only when
|
|
13
|
+
* content differs, so an unchanged boot touches nothing. It never deletes: the
|
|
14
|
+
* target may hold `pairing.json` (ours) and, if the operator pointed
|
|
15
|
+
* CHROME_MCP_EXTENSION_DIR somewhere of their own, files that are not ours to
|
|
16
|
+
* remove. It never throws — a read-only home just falls back to the bundled
|
|
17
|
+
* folder, which still works for Load unpacked.
|
|
18
|
+
*/
|
|
19
|
+
/** Folder name under the home directory. */
|
|
20
|
+
export declare const EXTENSION_DIR_NAME = "chrome-mcp-extension";
|
|
21
|
+
/** The auto-pairing file the server writes; never copied, never overwritten by the mirror. */
|
|
22
|
+
export declare const PAIRING_FILE = "pairing.json";
|
|
23
|
+
/** `dist/src/extension-install.js` -> `<pkg>/extension-dist`, which the tarball ships. */
|
|
24
|
+
export declare function bundledExtensionDir(): string;
|
|
25
|
+
/** Where the extension is mirrored for Load unpacked. `CHROME_MCP_EXTENSION_DIR` overrides. */
|
|
26
|
+
export declare function extensionInstallDir(): string;
|
|
27
|
+
export interface SyncResult {
|
|
28
|
+
/** The folder that now holds the extension (the target on success, the source on failure). */
|
|
29
|
+
dir: string;
|
|
30
|
+
/** Files written this run (new or changed). Empty means the target was already current. */
|
|
31
|
+
copied: string[];
|
|
32
|
+
/** True when the target folder did not exist before this run (first install). */
|
|
33
|
+
created: boolean;
|
|
34
|
+
ok: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Mirror the bundled extension into the install dir. Returns the folder to
|
|
39
|
+
* point Chrome at either way: the target when the mirror succeeded, the source
|
|
40
|
+
* when it did not (so callers always have a loadable folder).
|
|
41
|
+
*/
|
|
42
|
+
export declare function syncExtension(from?: string, to?: string): SyncResult;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* src/extension-install.ts — put the extension somewhere a human can find.
|
|
4
|
+
*
|
|
5
|
+
* The extension ships inside the npm package, which is the wrong place to send
|
|
6
|
+
* someone with a "Load unpacked" file dialog: a global install buries it five
|
|
7
|
+
* folders deep under `npm root -g`, and an npx run hides it in a cache. So on
|
|
8
|
+
* every boot (and on `--extension-path`) the server mirrors the bundled
|
|
9
|
+
* `extension-dist/` into a plain, visible folder directly under the home
|
|
10
|
+
* directory — `~/chrome-mcp-extension` — and that is the folder the docs, the
|
|
11
|
+
* pairing file, and `--extension-path` all point at.
|
|
12
|
+
*
|
|
13
|
+
* The copy is a one-way mirror of top-level regular files, written only when
|
|
14
|
+
* content differs, so an unchanged boot touches nothing. It never deletes: the
|
|
15
|
+
* target may hold `pairing.json` (ours) and, if the operator pointed
|
|
16
|
+
* CHROME_MCP_EXTENSION_DIR somewhere of their own, files that are not ours to
|
|
17
|
+
* remove. It never throws — a read-only home just falls back to the bundled
|
|
18
|
+
* folder, which still works for Load unpacked.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.PAIRING_FILE = exports.EXTENSION_DIR_NAME = void 0;
|
|
22
|
+
exports.bundledExtensionDir = bundledExtensionDir;
|
|
23
|
+
exports.extensionInstallDir = extensionInstallDir;
|
|
24
|
+
exports.syncExtension = syncExtension;
|
|
25
|
+
const node_os_1 = require("node:os");
|
|
26
|
+
const node_path_1 = require("node:path");
|
|
27
|
+
const node_fs_1 = require("node:fs");
|
|
28
|
+
/** Folder name under the home directory. */
|
|
29
|
+
exports.EXTENSION_DIR_NAME = 'chrome-mcp-extension';
|
|
30
|
+
/** The auto-pairing file the server writes; never copied, never overwritten by the mirror. */
|
|
31
|
+
exports.PAIRING_FILE = 'pairing.json';
|
|
32
|
+
/** `dist/src/extension-install.js` -> `<pkg>/extension-dist`, which the tarball ships. */
|
|
33
|
+
function bundledExtensionDir() {
|
|
34
|
+
return (0, node_path_1.resolve)(__dirname, '..', '..', 'extension-dist');
|
|
35
|
+
}
|
|
36
|
+
/** Where the extension is mirrored for Load unpacked. `CHROME_MCP_EXTENSION_DIR` overrides. */
|
|
37
|
+
function extensionInstallDir() {
|
|
38
|
+
const override = process.env.CHROME_MCP_EXTENSION_DIR;
|
|
39
|
+
return override && override.trim() ? (0, node_path_1.resolve)(override.trim()) : (0, node_path_1.join)((0, node_os_1.homedir)(), exports.EXTENSION_DIR_NAME);
|
|
40
|
+
}
|
|
41
|
+
function sameBytes(a, b) {
|
|
42
|
+
return a.length === b.length && a.equals(b);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Mirror the bundled extension into the install dir. Returns the folder to
|
|
46
|
+
* point Chrome at either way: the target when the mirror succeeded, the source
|
|
47
|
+
* when it did not (so callers always have a loadable folder).
|
|
48
|
+
*/
|
|
49
|
+
function syncExtension(from = bundledExtensionDir(), to = extensionInstallDir()) {
|
|
50
|
+
const copied = [];
|
|
51
|
+
let created = false;
|
|
52
|
+
try {
|
|
53
|
+
const entries = (0, node_fs_1.readdirSync)(from, { withFileTypes: true }).filter((e) => e.isFile() && e.name !== exports.PAIRING_FILE);
|
|
54
|
+
if (!entries.some((e) => e.name === 'manifest.json')) {
|
|
55
|
+
return { dir: from, copied, created, ok: false, error: `no manifest.json in ${from}` };
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
(0, node_fs_1.statSync)(to);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
created = true;
|
|
62
|
+
}
|
|
63
|
+
(0, node_fs_1.mkdirSync)(to, { recursive: true });
|
|
64
|
+
for (const e of entries) {
|
|
65
|
+
const src = (0, node_fs_1.readFileSync)((0, node_path_1.join)(from, e.name));
|
|
66
|
+
const dst = (0, node_path_1.join)(to, e.name);
|
|
67
|
+
let current = null;
|
|
68
|
+
try {
|
|
69
|
+
if ((0, node_fs_1.statSync)(dst).isFile())
|
|
70
|
+
current = (0, node_fs_1.readFileSync)(dst);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* absent */
|
|
74
|
+
}
|
|
75
|
+
if (current && sameBytes(current, src))
|
|
76
|
+
continue;
|
|
77
|
+
(0, node_fs_1.writeFileSync)(dst, src);
|
|
78
|
+
copied.push(e.name);
|
|
79
|
+
}
|
|
80
|
+
return { dir: to, copied, created, ok: true };
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
return { dir: from, copied, created, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=extension-install.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Chrome MCP Bridge",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"description": "Lets a local chrome-mcp server drive this browser. Pair it with the server's handshake token.",
|
|
6
6
|
"minimum_chrome_version": "116",
|
|
7
7
|
"background": {
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
<h1>Pair with chrome-mcp</h1>
|
|
19
19
|
<p class="hint" id="source">Checking pairing source…</p>
|
|
20
20
|
<p class="hint">
|
|
21
|
-
Manual pairing: run <code>npx chrome-mcp --print-pairing</code>,
|
|
22
|
-
printed <code>handshake.json</code>, and paste its <code>port</code>
|
|
23
|
-
<code>token</code> below.
|
|
21
|
+
Manual pairing: run <code>npx -y @mehmoodqureshi/chrome-mcp --print-pairing</code>,
|
|
22
|
+
open the printed <code>handshake.json</code>, and paste its <code>port</code>
|
|
23
|
+
and <code>token</code> below.
|
|
24
24
|
</p>
|
|
25
25
|
<label for="port">Port</label>
|
|
26
26
|
<input id="port" type="number" inputmode="numeric" placeholder="38017" />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mehmoodqureshi/chrome-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Drive your real Chrome browser over MCP — real logins, real cookies. A stdio MCP server (CLI) plus an MV3 extension, driving Chrome via chrome.scripting/chrome.tabs. Multi-tab batch automation, accessibility snapshots, deny-all security by default.",
|
|
5
5
|
"author": "Mehmood Ur Rehman Qureshi",
|
|
6
6
|
"license": "MIT",
|