@mehmoodqureshi/chrome-mcp 0.9.2 → 0.9.3
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 +4 -2
- package/dist/shared/page-fns.js +208 -182
- package/dist/shared/screenshot.d.ts +14 -0
- package/dist/shared/screenshot.js +19 -4
- package/dist/shared/snapshot.d.ts +24 -2
- package/dist/shared/snapshot.js +117 -33
- package/dist/src/bridge/connection.d.ts +13 -0
- package/dist/src/bridge/connection.js +55 -1
- package/dist/src/bridge/server.d.ts +2 -0
- package/dist/src/bridge/server.js +5 -0
- package/dist/src/bridge/workspace.d.ts +1 -1
- package/dist/src/bridge/workspace.js +2 -2
- package/dist/src/executor/cdp-executor.d.ts +3 -2
- package/dist/src/executor/cdp-executor.js +21 -6
- package/dist/src/executor/extension-executor.d.ts +6 -2
- package/dist/src/executor/extension-executor.js +18 -5
- package/dist/src/executor/types.d.ts +25 -2
- package/dist/src/mcp/locate.d.ts +7 -4
- package/dist/src/mcp/locate.js +15 -8
- package/dist/src/mcp/tools.js +16 -9
- package/docs/BLUEPRINT.md +1 -1
- package/extension-dist/background.js +494 -263
- package/extension-dist/manifest.json +3 -2
- package/package.json +1 -1
package/dist/src/mcp/locate.js
CHANGED
|
@@ -10,10 +10,13 @@
|
|
|
10
10
|
* redeploy.
|
|
11
11
|
*
|
|
12
12
|
* A locator closes that: `{ role: 'button', name: 'Sign in' }` resolves through
|
|
13
|
-
* one snapshot
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* one snapshot and the caller never sees the tree. The page does the matching
|
|
14
|
+
* itself (`collectSnapshot` with a locator returns only the strongest-tier
|
|
15
|
+
* hits, and stamps refs on those alone), so what crosses the bridge is a
|
|
16
|
+
* handful of nodes rather than 400; this module re-scores them — same tiers:
|
|
17
|
+
* exact, then case-insensitive, then prefix, then contains — so an unambiguous
|
|
18
|
+
* name wins outright and an ambiguous one fails loudly with the candidates
|
|
19
|
+
* rather than silently clicking the first row.
|
|
17
20
|
*/
|
|
18
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
22
|
exports.hasLocator = hasLocator;
|
|
@@ -60,6 +63,7 @@ async function resolveLocator(ex, loc, opts = {}) {
|
|
|
60
63
|
tabId: opts.tabId,
|
|
61
64
|
interactiveOnly: false,
|
|
62
65
|
max: 400,
|
|
66
|
+
locator: want,
|
|
63
67
|
frameId: opts.frameId,
|
|
64
68
|
allFrames: opts.allFrames,
|
|
65
69
|
});
|
|
@@ -79,10 +83,13 @@ async function resolveLocator(ex, loc, opts = {}) {
|
|
|
79
83
|
}
|
|
80
84
|
const describe = (n) => `${n.role} "${n.name}"`;
|
|
81
85
|
if (winners.length === 0) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.
|
|
86
|
+
// A page that scored in place reports what it had of that role as `nearby`;
|
|
87
|
+
// a backend that returned the full tree leaves it to us.
|
|
88
|
+
const sample = snap.nearby ??
|
|
89
|
+
snap.nodes
|
|
90
|
+
.filter((n) => !want.role || norm(n.role) === norm(want.role))
|
|
91
|
+
.slice(0, 8)
|
|
92
|
+
.map(describe);
|
|
86
93
|
throw new validators_1.McpToolError(`no element matches ${JSON.stringify(want)}. ` +
|
|
87
94
|
(sample.length
|
|
88
95
|
? `Closest by role: ${sample.join(', ')}. `
|
package/dist/src/mcp/tools.js
CHANGED
|
@@ -91,7 +91,7 @@ exports.TOOL_DEFINITIONS = [
|
|
|
91
91
|
{ name: 'press', description: 'Press a key (with optional modifiers).', inputSchema: { key: zod_1.z.string(), modifiers: zod_1.z.array(zod_1.z.string()).optional(), tabId: tabIdField, failOnAuthWall: authWallField } },
|
|
92
92
|
{ name: 'hover', description: 'Hover over an element.', inputSchema: { ...TARGET_PROPS, ...LOCATOR_PROPS, ...FRAME_PROPS, tabId: tabIdField, snapshotAfter: snapshotAfterField } },
|
|
93
93
|
{ name: 'scroll', description: 'Scroll the page or to an element.', inputSchema: { ...TARGET_PROPS, ...FRAME_PROPS, x: zod_1.z.number().optional(), y: zod_1.z.number().optional(), deltaX: zod_1.z.number().optional(), deltaY: zod_1.z.number().optional(), tabId: tabIdField } },
|
|
94
|
-
{ name: 'screenshot', description: 'Capture a
|
|
94
|
+
{ name: 'screenshot', description: 'Capture a screenshot (page or element). Default is JPEG (quality 70) at CSS-pixel size, which is several times smaller than PNG and reads fine. Pass format:"png" for lossless, quality 1-100 for JPEG, scale 2 for device pixels on a Retina display or 0.5 to shrink.', inputSchema: { ...TARGET_PROPS, ...FRAME_PROPS, fullPage: zod_1.z.boolean().optional(), format: zod_1.z.enum(['jpeg', 'png']).optional(), quality: zod_1.z.number().optional(), scale: zod_1.z.number().optional(), tabId: tabIdField } },
|
|
95
95
|
{ name: 'get_text', description: 'Get visible text of the page or an element.', inputSchema: { ...TARGET_PROPS, ...FRAME_PROPS, tabId: tabIdField, maxBytes: maxBytesField } },
|
|
96
96
|
{ name: 'get_html', description: 'Get HTML of the page or an element. Output is capped (see maxBytes) and cut at a tag boundary; narrow it with `selector` rather than raising the cap when you can. Password field values are always blanked.', inputSchema: { ...TARGET_PROPS, ...FRAME_PROPS, outer: zod_1.z.boolean().optional(), tabId: tabIdField, maxBytes: maxBytesField } },
|
|
97
97
|
{ name: 'snapshot', description: 'Accessibility snapshot: interactive elements with refs to target by `ref` (more reliable than guessing CSS selectors). Pass diff:true to get only what changed since the last snapshot of this tab - far cheaper in a click/read loop. Password fields appear as secret:true with no value.', inputSchema: { interactiveOnly: zod_1.z.boolean().optional(), max: zod_1.z.number().optional(), diff: zod_1.z.boolean().describe('Return added/removed/changed elements since the previous snapshot of this tab instead of the whole tree').optional(), failOnAuthWall: authWallField, ...FRAME_PROPS, tabId: tabIdField } },
|
|
@@ -201,15 +201,15 @@ const GATE_CONTEXT = 'cannot resolve the target tab URL for the policy gate';
|
|
|
201
201
|
*
|
|
202
202
|
* Prefers a URL the backend already reported over asking again: the extension
|
|
203
203
|
* rides the tab's landing URL home on every result frame, which is what keeps a
|
|
204
|
-
* gated call to ONE round-trip instead of two.
|
|
205
|
-
* the
|
|
204
|
+
* gated call to ONE round-trip instead of two. The active-tab cache serves calls
|
|
205
|
+
* without a `tabId`; the per-tab cache (fed by results for that tab and by any
|
|
206
|
+
* `tabs_list`) serves explicitly-targeted ones, so a parallel batch over N tabs
|
|
207
|
+
* gates on the one listing that opened it rather than N more.
|
|
206
208
|
*/
|
|
207
209
|
async function gatedUrl(ex, tabId) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return known;
|
|
212
|
-
}
|
|
210
|
+
const known = tabId ? ex.cachedTabUrl?.(tabId) : ex.cachedActiveUrl?.();
|
|
211
|
+
if (known)
|
|
212
|
+
return known;
|
|
213
213
|
let tabs;
|
|
214
214
|
try {
|
|
215
215
|
tabs = await ex.tabsList();
|
|
@@ -547,13 +547,20 @@ exports.TOOL_HANDLERS = {
|
|
|
547
547
|
},
|
|
548
548
|
screenshot: async (a, ctx) => {
|
|
549
549
|
await gate(ctx, 'screenshot', { tabId: tabId(a) });
|
|
550
|
+
const format = (0, validators_1.optionalString)(a, 'format');
|
|
551
|
+
if (format !== undefined && format !== 'jpeg' && format !== 'png') {
|
|
552
|
+
throw new validators_1.McpToolError('"format" must be "jpeg" or "png"');
|
|
553
|
+
}
|
|
550
554
|
const shot = await ctx.ex.screenshot({
|
|
551
555
|
tabId: tabId(a),
|
|
552
556
|
...frameOpts(a),
|
|
553
557
|
fullPage: (0, validators_1.optionalBoolean)(a, 'fullPage'),
|
|
554
558
|
target: (0, validators_1.optionalTarget)(a),
|
|
559
|
+
format,
|
|
560
|
+
quality: (0, validators_1.optionalNumber)(a, 'quality', { min: 1, max: 100 }),
|
|
561
|
+
scale: (0, validators_1.optionalNumber)(a, 'scale', { min: 0.1, max: 4 }),
|
|
555
562
|
});
|
|
556
|
-
(0, workspace_1.saveScreenshot)(shot.dataBase64);
|
|
563
|
+
(0, workspace_1.saveScreenshot)(shot.dataBase64, shot.mimeType === 'image/jpeg' ? 'jpg' : 'png');
|
|
557
564
|
const caption = shot.truncated ? `(truncated; full height ${shot.fullHeight}px)` : undefined;
|
|
558
565
|
return (0, envelopes_1.imageResult)(shot.dataBase64, shot.mimeType, caption);
|
|
559
566
|
},
|
package/docs/BLUEPRINT.md
CHANGED
|
@@ -173,7 +173,7 @@ export interface EvalResult { ok: boolean; value?: unknown; type?: string; error
|
|
|
173
173
|
export interface WaitResult { matched: boolean; ref?: string; waitedMs: number; }
|
|
174
174
|
export interface ActionOk { ok: true; }
|
|
175
175
|
export interface ScreenshotResult {
|
|
176
|
-
dataBase64: string; mimeType: 'image/png';
|
|
176
|
+
dataBase64: string; mimeType: 'image/png' | 'image/jpeg'; // jpeg q70 @ CSS px by default; format/quality/scale opt-in
|
|
177
177
|
width: number; height: number; truncated: boolean; fullHeight?: number; // fullPage cap metadata
|
|
178
178
|
}
|
|
179
179
|
export interface DownloadResult { path: string; backend: BackendKind; bytes: number; mimeType?: string; suggestedName?: string; }
|