mcp-browser-extension 0.9.32 → 0.9.34
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 +2 -2
- package/SETUP.md +1 -1
- package/dist/src/mcp/person-wait.d.ts +31 -0
- package/dist/src/mcp/person-wait.js +74 -0
- package/dist/src/mcp/server.js +4 -0
- package/dist/src/mcp/tools.js +19 -10
- package/docs/CLIENT-APPROVAL.md +6 -0
- package/extension-dist/background.js +15 -2
- package/extension-dist/manifest.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -630,7 +630,7 @@ PDF is megabytes of base64 no model can read.
|
|
|
630
630
|
### Paying less per turn — `--tools`
|
|
631
631
|
|
|
632
632
|
Every MCP server you connect costs context before you ask it anything: the host
|
|
633
|
-
sends the whole tool catalog to the model on **every** turn. MCP Browser Extension's
|
|
633
|
+
sends the whole tool catalog to the model on **every** turn. MCP Browser Extension's 43
|
|
634
634
|
tools are 28 KB of JSON Schema, about 7.1k tokens, on each one.
|
|
635
635
|
|
|
636
636
|
Most runs need a handful of them. `--tools` advertises only those:
|
|
@@ -652,7 +652,7 @@ for a run that was never going to print a PDF or upload a file.
|
|
|
652
652
|
`--unsafe-enable-eval`.
|
|
653
653
|
- An unknown name fails at startup and prints the catalog, so a typo can never
|
|
654
654
|
quietly drop `click` from the surface.
|
|
655
|
-
- `mcp-browser-extension --help` prints the full catalog of
|
|
655
|
+
- `mcp-browser-extension --help` prints the full catalog of 43 names to pick from.
|
|
656
656
|
|
|
657
657
|
## Status
|
|
658
658
|
|
package/SETUP.md
CHANGED
|
@@ -20,7 +20,7 @@ inside Chrome's own UI (loading the extension, pasting the pairing token).
|
|
|
20
20
|
catch-all pattern. Only enable `--unsafe-enable-eval` if the human asks by name.
|
|
21
21
|
- Never print or log the pairing token. It lives in a 0600 file; show the human
|
|
22
22
|
its path, not its contents.
|
|
23
|
-
- Requires Node
|
|
23
|
+
- Requires Node 20 or newer (`node --version`) and Chrome 116 or newer.
|
|
24
24
|
- On Windows, MCP hosts must launch the server through `cmd /c npx ...`. See
|
|
25
25
|
step 3.
|
|
26
26
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/mcp/person-wait.ts — hold a tool call open while the PERSON approves the
|
|
3
|
+
* app or allows a site in Chrome, then continue on its own.
|
|
4
|
+
*
|
|
5
|
+
* Without this the agent gets an error, tells the person, and has to invent its
|
|
6
|
+
* own polling (shell sleeps, "I'll check again in a minute"). Waiting here means
|
|
7
|
+
* one tool call simply completes once the person clicks Approve/Allow.
|
|
8
|
+
*/
|
|
9
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
+
export interface PersonWaitRequest {
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
progressToken?: string | number;
|
|
13
|
+
sendNotification?: (n: {
|
|
14
|
+
method: 'notifications/progress';
|
|
15
|
+
params: {
|
|
16
|
+
progressToken: string | number;
|
|
17
|
+
progress: number;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
}) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export declare function setPersonWaitForTesting(poll: number, wait: number): void;
|
|
23
|
+
/** The person-only step a result is blocked on, or undefined when it is not. */
|
|
24
|
+
export declare function personStep(result: CallToolResult): string | undefined;
|
|
25
|
+
export declare const STILL_WAITING: string;
|
|
26
|
+
/**
|
|
27
|
+
* Run `attempt` until it is no longer blocked on the person, the wait expires,
|
|
28
|
+
* or the call is cancelled. Only results that ran nothing (approval / site
|
|
29
|
+
* gate refusals) are retried.
|
|
30
|
+
*/
|
|
31
|
+
export declare function waitForPerson(attempt: () => Promise<CallToolResult>, request: PersonWaitRequest): Promise<CallToolResult>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* src/mcp/person-wait.ts — hold a tool call open while the PERSON approves the
|
|
4
|
+
* app or allows a site in Chrome, then continue on its own.
|
|
5
|
+
*
|
|
6
|
+
* Without this the agent gets an error, tells the person, and has to invent its
|
|
7
|
+
* own polling (shell sleeps, "I'll check again in a minute"). Waiting here means
|
|
8
|
+
* one tool call simply completes once the person clicks Approve/Allow.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.STILL_WAITING = void 0;
|
|
12
|
+
exports.setPersonWaitForTesting = setPersonWaitForTesting;
|
|
13
|
+
exports.personStep = personStep;
|
|
14
|
+
exports.waitForPerson = waitForPerson;
|
|
15
|
+
let pollMs = 1_500;
|
|
16
|
+
// Stay under the 60 s default tool timeout some MCP clients enforce.
|
|
17
|
+
let waitMs = 45_000;
|
|
18
|
+
function setPersonWaitForTesting(poll, wait) {
|
|
19
|
+
pollMs = Math.max(1, poll);
|
|
20
|
+
waitMs = Math.max(1, wait);
|
|
21
|
+
}
|
|
22
|
+
const APPROVAL = /Please approve "/;
|
|
23
|
+
const SITE = /^\[POLICY_DENIED\] Please allow access to /;
|
|
24
|
+
/** The person-only step a result is blocked on, or undefined when it is not. */
|
|
25
|
+
function personStep(result) {
|
|
26
|
+
if (!result.isError)
|
|
27
|
+
return undefined;
|
|
28
|
+
const first = result.content[0];
|
|
29
|
+
const text = first?.type === 'text' ? first.text : '';
|
|
30
|
+
return APPROVAL.test(text) || SITE.test(text) ? text : undefined;
|
|
31
|
+
}
|
|
32
|
+
exports.STILL_WAITING = 'Still waiting for the person. Call the same tool again to keep waiting; it continues on its own once they approve. ' +
|
|
33
|
+
'Do not use shell sleeps or ask them to confirm in chat.';
|
|
34
|
+
/**
|
|
35
|
+
* Run `attempt` until it is no longer blocked on the person, the wait expires,
|
|
36
|
+
* or the call is cancelled. Only results that ran nothing (approval / site
|
|
37
|
+
* gate refusals) are retried.
|
|
38
|
+
*/
|
|
39
|
+
async function waitForPerson(attempt, request) {
|
|
40
|
+
let result = await attempt();
|
|
41
|
+
let step = personStep(result);
|
|
42
|
+
if (!step)
|
|
43
|
+
return result;
|
|
44
|
+
const deadline = Date.now() + waitMs;
|
|
45
|
+
let progress = 0;
|
|
46
|
+
const report = async (message) => {
|
|
47
|
+
if (request.progressToken === undefined || !request.sendNotification || request.signal?.aborted)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
await request.sendNotification({ method: 'notifications/progress', params: { progressToken: request.progressToken, progress: ++progress, message } });
|
|
51
|
+
}
|
|
52
|
+
catch { /* Progress is optional; the tool result still carries the outcome. */ }
|
|
53
|
+
};
|
|
54
|
+
await report(`${step} Waiting for this automatically.`);
|
|
55
|
+
while (Date.now() < deadline && !request.signal?.aborted) {
|
|
56
|
+
await new Promise((resolve) => {
|
|
57
|
+
const timer = setTimeout(done, Math.min(pollMs, Math.max(1, deadline - Date.now())));
|
|
58
|
+
function done() { clearTimeout(timer); request.signal?.removeEventListener('abort', done); resolve(); }
|
|
59
|
+
request.signal?.addEventListener('abort', done, { once: true });
|
|
60
|
+
});
|
|
61
|
+
if (request.signal?.aborted)
|
|
62
|
+
break;
|
|
63
|
+
result = await attempt();
|
|
64
|
+
const next = personStep(result);
|
|
65
|
+
if (!next)
|
|
66
|
+
return result;
|
|
67
|
+
// Approving the app can uncover the site gate; say what is awaited now.
|
|
68
|
+
if (next !== step)
|
|
69
|
+
await report(`${next} Waiting for this automatically.`);
|
|
70
|
+
step = next;
|
|
71
|
+
}
|
|
72
|
+
return { ...result, content: [...result.content, { type: 'text', text: exports.STILL_WAITING }] };
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=person-wait.js.map
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -38,6 +38,8 @@ const SERVER_VERSION = '0.1.0';
|
|
|
38
38
|
exports.SERVER_INSTRUCTIONS = "MCP Browser Extension drives the user's real, signed-in Chrome. " +
|
|
39
39
|
'On the first browser tool call, enter your email in the MCP input prompt and open the sign-in link sent to you, then click Verify this device on the website. If it returns ACCESS_INPUT_REQUIRED, the local verification form opens automatically in the browser; also show its URL as a backup so the user can enter their email. Do not open the backup URL again or add another chat email prompt while that form is active. Do not fill it or read their email for them. The tool waits for verification, reports the result, and continues automatically on success. If it returns ACCESS_PENDING, call chrome_status again to keep waiting; do not ask for a chat reply or another email. Only ask the user to click Connect if the result says Chrome is not paired. ' +
|
|
40
40
|
'When Chrome approval is required, show the app name and key fingerprint and ask the person to review App connections in the extension popup or Settings. Approval lasts eight hours. Never approve a client yourself; YOLO and email-verification bypass do not bypass Chrome approval. ' +
|
|
41
|
+
'Approval and site refusals wait automatically: the tool call stays open (about 45 seconds) and continues on its own as soon as the person clicks Approve or Allow. Tell the person once. If it returns "Still waiting", call the same tool again; never use shell sleeps, timers or "I will check again later" messages, and never ask them to confirm in chat. ' +
|
|
42
|
+
'Chrome blocks every extension from Chrome Web Store pages (chromewebstore.google.com and chrome.google.com/webstore, including the developer dashboard): they fail with TARGET_GONE "The extensions gallery cannot be scripted". Do not ask the person to allow those sites; it cannot help. ' +
|
|
41
43
|
'Start with chrome_status. If it reports no paired browser (or any tool fails with NO_BACKEND), do not retry: ' +
|
|
42
44
|
'give the user the steps in its `setup` field, which name what went wrong and how to fix it. ' +
|
|
43
45
|
'If a tool fails with POLICY_DENIED because a site is not allowed, ask the user to allow it from the extension\'s ' +
|
|
@@ -46,6 +48,8 @@ exports.SERVER_INSTRUCTIONS = "MCP Browser Extension drives the user's real, sig
|
|
|
46
48
|
"To act on the user's current page, pass its tabId from tabs_list.";
|
|
47
49
|
const LOCAL_BYPASS_INSTRUCTIONS = 'Email verification is disabled by the local operator for this server. Only trusted local MCP clients should connect; the browser-domain allowlist and other capability safety gates remain active. ' +
|
|
48
50
|
'When Chrome approval is required, show the app name and key fingerprint and ask the person to review App connections in the extension popup or Settings. Approval lasts eight hours. Never approve a client yourself; YOLO and email-verification bypass do not bypass Chrome approval. ' +
|
|
51
|
+
'Approval and site refusals wait automatically: the tool call stays open (about 45 seconds) and continues on its own as soon as the person clicks Approve or Allow. Tell the person once. If it returns "Still waiting", call the same tool again; never use shell sleeps, timers or "I will check again later" messages, and never ask them to confirm in chat. ' +
|
|
52
|
+
'Chrome blocks every extension from Chrome Web Store pages (chromewebstore.google.com and chrome.google.com/webstore, including the developer dashboard): they fail with TARGET_GONE "The extensions gallery cannot be scripted". Do not ask the person to allow those sites; it cannot help. ' +
|
|
49
53
|
'Start with chrome_status. If it reports no paired browser (or any tool fails with NO_BACKEND), give the user the steps in its `setup` field. ' +
|
|
50
54
|
'If a tool fails with POLICY_DENIED because a site is not allowed, relay its friendly one-line approval steps and wait for the person to allow it from the extension\'s toolbar icon. Never approve it yourself or enable all-site access. ' +
|
|
51
55
|
"Work in your own tab: tab_new opens one in the background. To act on the user's current page, pass its tabId from tabs_list.";
|
package/dist/src/mcp/tools.js
CHANGED
|
@@ -35,6 +35,7 @@ const policy_2 = require("../../shared/policy");
|
|
|
35
35
|
const envelopes_1 = require("./envelopes");
|
|
36
36
|
const limits_1 = require("./limits");
|
|
37
37
|
const batch_1 = require("./batch");
|
|
38
|
+
const person_wait_1 = require("./person-wait");
|
|
38
39
|
const helpers_1 = require("./helpers");
|
|
39
40
|
const redact_1 = require("./redact");
|
|
40
41
|
const locate_1 = require("./locate");
|
|
@@ -1544,17 +1545,25 @@ function registerTools(server, policy, accessGuard = access_1.requireAccess) {
|
|
|
1544
1545
|
});
|
|
1545
1546
|
if (accessError)
|
|
1546
1547
|
return accessError;
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1548
|
+
// Approval and site refusals run nothing, so the call waits for the
|
|
1549
|
+
// person here and continues once they click Approve or Allow.
|
|
1550
|
+
const result = await (0, person_wait_1.waitForPerson)(async () => {
|
|
1551
|
+
if (profileBridge?.hasActiveExtension()) {
|
|
1552
|
+
const active = (0, workspace_1.peekActiveWorkspace)()?.profile ?? 'default';
|
|
1553
|
+
const profile = profileBridge.hasConnection(active) ? active : profileBridge.pairedProfiles()[0]?.name;
|
|
1554
|
+
try {
|
|
1555
|
+
await profileBridge.sendCommand('ping_probe', { requireClientApproval: true }, { profile });
|
|
1556
|
+
}
|
|
1557
|
+
catch (err) {
|
|
1558
|
+
return (0, envelopes_1.errorResult)(err instanceof Error ? err.message : 'Chrome client approval is required.');
|
|
1559
|
+
}
|
|
1552
1560
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1561
|
+
return dispatchToolCall(d.name, args);
|
|
1562
|
+
}, {
|
|
1563
|
+
signal: extra.signal,
|
|
1564
|
+
progressToken: extra._meta?.progressToken,
|
|
1565
|
+
sendNotification: extra.sendNotification,
|
|
1566
|
+
});
|
|
1558
1567
|
if (verifiedNow)
|
|
1559
1568
|
result.content.unshift({ type: 'text', text: 'Email verified. MCP Browser Extension access is active.' });
|
|
1560
1569
|
return result;
|
package/docs/CLIENT-APPROVAL.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Client approval
|
|
2
2
|
|
|
3
|
+
Related security steps: [Step 1: App and Website Approval](SECURITY-STEP-1.md),
|
|
4
|
+
[Step 2: Optional Browser Permissions](SECURITY-STEP-2.md),
|
|
5
|
+
[Step 3: Per-Action Approval Decision](SECURITY-STEP-3.md),
|
|
6
|
+
[Step 4: Activity and Stop Access](SECURITY-STEP-4.md), and
|
|
7
|
+
[Step 5: Download and Bridge Hardening](SECURITY-STEP-5.md).
|
|
8
|
+
|
|
3
9
|
The extension requires a person to approve each new MCP app session in its Chrome
|
|
4
10
|
popup or Settings before it can read or act on browser pages. This is independent of
|
|
5
11
|
email verification, transport pairing, domain restrictions and client permission
|
|
@@ -3683,7 +3683,9 @@
|
|
|
3683
3683
|
void operation.then((changed) => reply({
|
|
3684
3684
|
ok: changed || !msg.allow,
|
|
3685
3685
|
...!changed && msg.allow ? { error: "No access was added. You can allow it whenever you are ready." } : {}
|
|
3686
|
-
}), () => reply({ ok: false, error: "Could not change this permission. Please try again." }))
|
|
3686
|
+
}), () => reply({ ok: false, error: "Could not change this permission. Please try again." })).finally(() => {
|
|
3687
|
+
if (msg.allow) void reopenPopup(sender);
|
|
3688
|
+
});
|
|
3687
3689
|
return true;
|
|
3688
3690
|
}
|
|
3689
3691
|
if (["access_stop", "access_resume", "activity_clear"].includes(msg?.type)) {
|
|
@@ -3761,7 +3763,9 @@
|
|
|
3761
3763
|
ws.send(frame);
|
|
3762
3764
|
if (currentPolicy) await onAllowlist(currentPolicy);
|
|
3763
3765
|
reply({ ok: true });
|
|
3764
|
-
})().catch(() => reply({ ok: false, error: "Could not update this website permission. Please try again." }))
|
|
3766
|
+
})().catch(() => reply({ ok: false, error: "Could not update this website permission. Please try again." })).finally(() => {
|
|
3767
|
+
if (msg.type === "site_access" && msg.allow) void reopenPopup(sender);
|
|
3768
|
+
});
|
|
3765
3769
|
return true;
|
|
3766
3770
|
}
|
|
3767
3771
|
const fromOwnPage = sender.id === chrome.runtime.id && sender.url?.startsWith(chrome.runtime.getURL(""));
|
|
@@ -3791,6 +3795,15 @@
|
|
|
3791
3795
|
return true;
|
|
3792
3796
|
}
|
|
3793
3797
|
});
|
|
3798
|
+
async function reopenPopup(sender) {
|
|
3799
|
+
if (sender.url !== chrome.runtime.getURL("popup.html")) return;
|
|
3800
|
+
try {
|
|
3801
|
+
const open = await chrome.runtime.getContexts({ contextTypes: [chrome.runtime.ContextType.POPUP] });
|
|
3802
|
+
if (open.length) return;
|
|
3803
|
+
await chrome.action.openPopup();
|
|
3804
|
+
} catch {
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3794
3807
|
async function healFromHelper() {
|
|
3795
3808
|
try {
|
|
3796
3809
|
if (!await chrome.permissions.contains({ permissions: ["nativeMessaging"] })) return false;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "MCP Browser Extension – AI agent in your logged-in Chrome",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.34",
|
|
5
5
|
"description": "Lets your AI agent use the Chrome you are signed into, through a local MCP server. Deny-all by default.",
|
|
6
6
|
"homepage_url": "https://mcp-browser-extension.com",
|
|
7
7
|
"minimum_chrome_version": "116",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-browser-extension",
|
|
3
3
|
"mcpName": "io.github.Mehmoodqureshi/mcp-browser-extension",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.34",
|
|
5
5
|
"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.",
|
|
6
6
|
"author": "Mehmood Ur Rehman Qureshi",
|
|
7
7
|
"license": "MIT",
|
|
@@ -78,6 +78,6 @@
|
|
|
78
78
|
"typescript": "^5.7.2"
|
|
79
79
|
},
|
|
80
80
|
"engines": {
|
|
81
|
-
"node": ">=
|
|
81
|
+
"node": ">=20.0.0"
|
|
82
82
|
}
|
|
83
83
|
}
|