@fusengine/browser-mcp 0.1.18 → 0.1.19
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/dist/lib/diff-regions.d.ts +20 -0
- package/dist/lib/diff-regions.d.ts.map +1 -0
- package/dist/lib/diff-regions.js +62 -0
- package/dist/lib/diff-regions.js.map +1 -0
- package/dist/lib/fs.d.ts +2 -0
- package/dist/lib/fs.d.ts.map +1 -1
- package/dist/lib/fs.js +5 -0
- package/dist/lib/fs.js.map +1 -1
- package/dist/lib/pixel-diff.d.ts +13 -0
- package/dist/lib/pixel-diff.d.ts.map +1 -0
- package/dist/lib/pixel-diff.js +50 -0
- package/dist/lib/pixel-diff.js.map +1 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +2 -0
- package/dist/server/server.js.map +1 -1
- package/dist/server/tools/visual-diff.d.ts +5 -0
- package/dist/server/tools/visual-diff.d.ts.map +1 -0
- package/dist/server/tools/visual-diff.js +54 -0
- package/dist/server/tools/visual-diff.js.map +1 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ Supported: `FUSE_ENGINE`, `FUSE_CHANNEL`, `FUSE_CDP_ENDPOINT`, `FUSE_EXECUTABLE_
|
|
|
132
132
|
`FUSE_HEADLESS`, `FUSE_COUNTRY`, `FUSE_CURRENCY`, `FUSE_USER_DATA_DIR`,
|
|
133
133
|
`FUSE_STORAGE_STATE`, `FUSE_OUTPUT_DIR`.
|
|
134
134
|
|
|
135
|
-
### Tools (
|
|
135
|
+
### Tools (27)
|
|
136
136
|
|
|
137
137
|
| Group | Tools |
|
|
138
138
|
| --- | --- |
|
|
@@ -144,7 +144,7 @@ Supported: `FUSE_ENGINE`, `FUSE_CHANNEL`, `FUSE_CDP_ENDPOINT`, `FUSE_EXECUTABLE_
|
|
|
144
144
|
| **Agentic** | `browser_snapshot` (indexed refs), `browser_act` (by ref + page diff), `browser_run` (multi-step plan), `browser_collect` (exhaust a virtualized/infinite list) |
|
|
145
145
|
| **Extract** | `browser_extract` (text/prices/hotels/challenges), `browser_extract_schema` (typed, by CSS selectors) |
|
|
146
146
|
| **SERP** | `browser_serp_batch` — multi-query Google search in one session, per-query organic results + domain rank |
|
|
147
|
-
| **Vision** | `browser_screenshot` (page, single element by `ref`, or responsive set via `viewport`/`viewports`) |
|
|
147
|
+
| **Vision** | `browser_screenshot` (page, single element by `ref`, or responsive set via `viewport`/`viewports`), `browser_visual_diff` (pixel diff vs baseline + changed-region boxes) |
|
|
148
148
|
|
|
149
149
|
Key agentic patterns:
|
|
150
150
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group a pixelmatch diff mask into changed-region bounding boxes. Quantizes
|
|
3
|
+
* diff pixels onto a coarse grid (cellSize) then flood-fills adjacent dirty
|
|
4
|
+
* cells into rectangles — O(cells), no per-pixel recursion. Pure & testable.
|
|
5
|
+
* @module lib/diff-regions
|
|
6
|
+
*/
|
|
7
|
+
/** A rectangular zone (in pixels) that changed between two images. */
|
|
8
|
+
export interface DiffRegion {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Bounding boxes of changed clusters from an RGBA diff `mask` (pixelmatch marks
|
|
16
|
+
* changed pixels red). A grid cell is dirty when it holds >= `minPerCell` diff
|
|
17
|
+
* pixels; adjacent dirty cells (4-connected) merge into one region.
|
|
18
|
+
*/
|
|
19
|
+
export declare function computeDiffRegions(mask: Uint8Array, width: number, height: number, cellSize?: number, minPerCell?: number): DiffRegion[];
|
|
20
|
+
//# sourceMappingURL=diff-regions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-regions.d.ts","sourceRoot":"","sources":["../../src/lib/diff-regions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,SAAK,EACb,UAAU,SAAI,GACb,UAAU,EAAE,CAgDd"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group a pixelmatch diff mask into changed-region bounding boxes. Quantizes
|
|
3
|
+
* diff pixels onto a coarse grid (cellSize) then flood-fills adjacent dirty
|
|
4
|
+
* cells into rectangles — O(cells), no per-pixel recursion. Pure & testable.
|
|
5
|
+
* @module lib/diff-regions
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Bounding boxes of changed clusters from an RGBA diff `mask` (pixelmatch marks
|
|
9
|
+
* changed pixels red). A grid cell is dirty when it holds >= `minPerCell` diff
|
|
10
|
+
* pixels; adjacent dirty cells (4-connected) merge into one region.
|
|
11
|
+
*/
|
|
12
|
+
export function computeDiffRegions(mask, width, height, cellSize = 16, minPerCell = 2) {
|
|
13
|
+
const cols = Math.ceil(width / cellSize);
|
|
14
|
+
const rows = Math.ceil(height / cellSize);
|
|
15
|
+
const dirty = new Uint32Array(cols * rows);
|
|
16
|
+
for (let y = 0; y < height; y++) {
|
|
17
|
+
for (let x = 0; x < width; x++) {
|
|
18
|
+
const p = (y * width + x) * 4;
|
|
19
|
+
if (mask[p] && mask[p + 1] === 0 && mask[p + 2] === 0) {
|
|
20
|
+
const ci = Math.floor(y / cellSize) * cols + Math.floor(x / cellSize);
|
|
21
|
+
dirty[ci] = (dirty[ci] ?? 0) + 1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const flagged = dirty.map((n) => (n >= minPerCell ? 1 : 0));
|
|
26
|
+
const seen = new Uint8Array(cols * rows);
|
|
27
|
+
const regions = [];
|
|
28
|
+
for (let cy = 0; cy < rows; cy++) {
|
|
29
|
+
for (let cx = 0; cx < cols; cx++) {
|
|
30
|
+
if (!flagged[cy * cols + cx] || seen[cy * cols + cx])
|
|
31
|
+
continue;
|
|
32
|
+
let minX = cx;
|
|
33
|
+
let maxX = cx;
|
|
34
|
+
let minY = cy;
|
|
35
|
+
let maxY = cy;
|
|
36
|
+
const stack = [[cx, cy]];
|
|
37
|
+
seen[cy * cols + cx] = 1;
|
|
38
|
+
while (stack.length) {
|
|
39
|
+
const [x, y] = stack.pop();
|
|
40
|
+
minX = Math.min(minX, x);
|
|
41
|
+
maxX = Math.max(maxX, x);
|
|
42
|
+
minY = Math.min(minY, y);
|
|
43
|
+
maxY = Math.max(maxY, y);
|
|
44
|
+
for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) {
|
|
45
|
+
const i = ny * cols + nx;
|
|
46
|
+
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows && flagged[i] && !seen[i]) {
|
|
47
|
+
seen[i] = 1;
|
|
48
|
+
stack.push([nx, ny]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
regions.push({
|
|
53
|
+
x: minX * cellSize,
|
|
54
|
+
y: minY * cellSize,
|
|
55
|
+
width: Math.min((maxX + 1) * cellSize, width) - minX * cellSize,
|
|
56
|
+
height: Math.min((maxY + 1) * cellSize, height) - minY * cellSize,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return regions;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=diff-regions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-regions.js","sourceRoot":"","sources":["../../src/lib/diff-regions.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/fs.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export declare function ensureDir(dir: string): void;
|
|
|
4
4
|
export declare function writeFileEnsured(filePath: string, content: string): void;
|
|
5
5
|
/** Sérialise en JSON indenté et écrit le fichier. */
|
|
6
6
|
export declare function writeJson(filePath: string, value: unknown): void;
|
|
7
|
+
/** Écrit des octets bruts (PNG, etc.) en garantissant le dossier parent. */
|
|
8
|
+
export declare function writeFileBytes(filePath: string, data: Uint8Array): void;
|
|
7
9
|
/** Lit et parse un JSON ; renvoie `fallback` en cas d'erreur/absence. */
|
|
8
10
|
export declare function readJsonSafe<T>(filePath: string, fallback: T): T;
|
|
9
11
|
/** SHA-1 hexadécimal d'une chaîne (signatures DOM, run ids — usage non cryptographique). */
|
package/dist/lib/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/lib/fs.ts"],"names":[],"mappings":"AAQA,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAGxE;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAEhE;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAMhE;AAED,4FAA4F;AAC5F,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1C"}
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/lib/fs.ts"],"names":[],"mappings":"AAQA,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAGxE;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAEhE;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAGvE;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAMhE;AAED,4FAA4F;AAC5F,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1C"}
|
package/dist/lib/fs.js
CHANGED
|
@@ -18,6 +18,11 @@ export function writeFileEnsured(filePath, content) {
|
|
|
18
18
|
export function writeJson(filePath, value) {
|
|
19
19
|
writeFileEnsured(filePath, JSON.stringify(value, null, 2));
|
|
20
20
|
}
|
|
21
|
+
/** Écrit des octets bruts (PNG, etc.) en garantissant le dossier parent. */
|
|
22
|
+
export function writeFileBytes(filePath, data) {
|
|
23
|
+
ensureDir(dirname(filePath));
|
|
24
|
+
writeFileSync(filePath, data);
|
|
25
|
+
}
|
|
21
26
|
/** Lit et parse un JSON ; renvoie `fallback` en cas d'erreur/absence. */
|
|
22
27
|
export function readJsonSafe(filePath, fallback) {
|
|
23
28
|
try {
|
package/dist/lib/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/lib/fs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,8CAA8C;AAC9C,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,OAAe;IAChE,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7B,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,KAAc;IACxD,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAI,QAAgB,EAAE,QAAW;IAC3D,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAM,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,IAAI,CAAC,KAAa;IAChC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC"}
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/lib/fs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,8CAA8C;AAC9C,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,OAAe;IAChE,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7B,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,KAAc;IACxD,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,IAAgB;IAC/D,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7B,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAI,QAAgB,EAAE,QAAW;IAC3D,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAM,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,IAAI,CAAC,KAAa;IAChC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DiffRegion } from "./diff-regions.js";
|
|
2
|
+
/** Result of comparing two PNG buffers. */
|
|
3
|
+
export interface PixelDiff {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
diffPixels: number;
|
|
7
|
+
diffRatio: number;
|
|
8
|
+
regions: DiffRegion[];
|
|
9
|
+
diffPng: Uint8Array;
|
|
10
|
+
}
|
|
11
|
+
/** Compare two PNG buffers; `threshold` is pixelmatch's color tolerance (0..1). */
|
|
12
|
+
export declare function diffPng(baseline: Uint8Array, current: Uint8Array, threshold?: number): PixelDiff;
|
|
13
|
+
//# sourceMappingURL=pixel-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pixel-diff.d.ts","sourceRoot":"","sources":["../../src/lib/pixel-diff.ts"],"names":[],"mappings":"AAQA,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AA+BxE,2CAA2C;AAC3C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,mFAAmF;AACnF,wBAAgB,OAAO,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,SAAM,GAAG,SAAS,CAkB7F"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PNG pixel diff via fast-png (decode/encode) + pixelmatch — pure JS, no native
|
|
3
|
+
* build. Returns diff stats, changed-region boxes and the highlighted diff PNG.
|
|
4
|
+
* Throws `dimension_mismatch` when sizes differ (no silent resize).
|
|
5
|
+
* @module lib/pixel-diff
|
|
6
|
+
*/
|
|
7
|
+
import { decode, encode } from "fast-png";
|
|
8
|
+
import pixelmatch from "pixelmatch";
|
|
9
|
+
import { computeDiffRegions } from "./diff-regions.js";
|
|
10
|
+
/**
|
|
11
|
+
* Normalize any channel/depth layout to packed 8-bit RGBA (alpha defaults to
|
|
12
|
+
* 255). 16-bit samples are scaled down to 8-bit so pixelmatch sees byte values.
|
|
13
|
+
*/
|
|
14
|
+
function toRgba(img) {
|
|
15
|
+
const { width, height, data, channels, depth } = img;
|
|
16
|
+
const scale = (v) => (depth === 16 ? v >> 8 : v);
|
|
17
|
+
if (channels === 4 && depth === 8)
|
|
18
|
+
return data;
|
|
19
|
+
const out = new Uint8Array(width * height * 4);
|
|
20
|
+
for (let i = 0; i < width * height; i++) {
|
|
21
|
+
const g = scale(data[i * channels] ?? 0);
|
|
22
|
+
out[i * 4] = channels >= 3 ? scale(data[i * channels] ?? 0) : g;
|
|
23
|
+
out[i * 4 + 1] = channels >= 3 ? scale(data[i * channels + 1] ?? 0) : g;
|
|
24
|
+
out[i * 4 + 2] = channels >= 3 ? scale(data[i * channels + 2] ?? 0) : g;
|
|
25
|
+
out[i * 4 + 3] =
|
|
26
|
+
channels === 2 ? scale(data[i * channels + 1] ?? 255) : channels === 4 ? scale(data[i * channels + 3] ?? 255) : 255;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/** Compare two PNG buffers; `threshold` is pixelmatch's color tolerance (0..1). */
|
|
31
|
+
export function diffPng(baseline, current, threshold = 0.1) {
|
|
32
|
+
const a = decode(baseline);
|
|
33
|
+
const b = decode(current);
|
|
34
|
+
if (a.width !== b.width || a.height !== b.height) {
|
|
35
|
+
throw new Error(`dimension_mismatch: ${a.width}x${a.height} vs ${b.width}x${b.height}`);
|
|
36
|
+
}
|
|
37
|
+
const { width, height } = a;
|
|
38
|
+
const out = new Uint8Array(width * height * 4);
|
|
39
|
+
// Playwright PNGs are often RGB (no alpha); normalize both to packed RGBA.
|
|
40
|
+
const diffPixels = pixelmatch(toRgba(a), toRgba(b), out, width, height, { threshold, includeAA: false });
|
|
41
|
+
return {
|
|
42
|
+
width,
|
|
43
|
+
height,
|
|
44
|
+
diffPixels,
|
|
45
|
+
diffRatio: width * height ? diffPixels / (width * height) : 0,
|
|
46
|
+
regions: computeDiffRegions(out, width, height),
|
|
47
|
+
diffPng: encode({ width, height, data: out }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=pixel-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pixel-diff.js","sourceRoot":"","sources":["../../src/lib/pixel-diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAkBvD,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,IAAI,WAAW,CAoB1C"}
|
package/dist/server/server.js
CHANGED
|
@@ -19,6 +19,7 @@ import { registerRunTool } from "./tools/run.js";
|
|
|
19
19
|
import { registerScreenshotTool } from "./tools/screenshot.js";
|
|
20
20
|
import { registerSessionTools } from "./tools/session.js";
|
|
21
21
|
import { registerSnapshotTools } from "./tools/snapshot.js";
|
|
22
|
+
import { registerVisualDiffTool } from "./tools/visual-diff.js";
|
|
22
23
|
import { registerWaitTool } from "./tools/wait.js";
|
|
23
24
|
/** Create the fuse-browser MCP server with every tool and resource wired. */
|
|
24
25
|
export function createServer() {
|
|
@@ -38,6 +39,7 @@ export function createServer() {
|
|
|
38
39
|
registerExtractTool(server, sessions);
|
|
39
40
|
registerExtractSchemaTool(server, sessions);
|
|
40
41
|
registerScreenshotTool(server, sessions);
|
|
42
|
+
registerVisualDiffTool(server, sessions);
|
|
41
43
|
registerResources(server);
|
|
42
44
|
return { server, sessions };
|
|
43
45
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAQnD,6EAA6E;AAC7E,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;IACtC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACxC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,yBAAyB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5C,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAQnD,6EAA6E;AAC7E,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;IACtC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACxC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,yBAAyB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5C,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzC,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { SessionManager } from "../../session/manager.js";
|
|
3
|
+
/** Register `browser_visual_diff`. */
|
|
4
|
+
export declare function registerVisualDiffTool(server: McpServer, sessions: SessionManager): void;
|
|
5
|
+
//# sourceMappingURL=visual-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-diff.d.ts","sourceRoot":"","sources":["../../../src/server/tools/visual-diff.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAI/D,sCAAsC;AACtC,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAwCxF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `browser_visual_diff` tool: pixel-compare the live page against a baseline PNG
|
|
3
|
+
* (created on first run), or two explicit PNG paths. Returns diff stats +
|
|
4
|
+
* changed-region boxes; writes the highlighted diff PNG next to the baseline.
|
|
5
|
+
* @module server/tools/visual-diff
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { writeFileBytes } from "../../lib/fs.js";
|
|
10
|
+
import { diffPng } from "../../lib/pixel-diff.js";
|
|
11
|
+
import { errorResult, jsonResult } from "../result.js";
|
|
12
|
+
import { withSession } from "./with-session.js";
|
|
13
|
+
/** Register `browser_visual_diff`. */
|
|
14
|
+
export function registerVisualDiffTool(server, sessions) {
|
|
15
|
+
server.registerTool("browser_visual_diff", {
|
|
16
|
+
title: "Visual diff",
|
|
17
|
+
description: "Pixel-compare the live page against a `baseline` PNG (created on first call, then diffed on later calls), or two explicit PNGs (`a` + `b`). Returns diffPixels/diffRatio and changed-region boxes, and writes a highlighted diff PNG. Fails if image sizes differ. For visual regression / page-change monitoring.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
sessionId: z.string().optional(),
|
|
20
|
+
baseline: z.string().optional(),
|
|
21
|
+
a: z.string().optional(),
|
|
22
|
+
b: z.string().optional(),
|
|
23
|
+
fullPage: z.boolean().optional(),
|
|
24
|
+
threshold: z.number().optional(),
|
|
25
|
+
},
|
|
26
|
+
}, async (args) => {
|
|
27
|
+
const x = args;
|
|
28
|
+
const threshold = typeof x.threshold === "number" ? x.threshold : 0.1;
|
|
29
|
+
if (typeof x.a === "string" && typeof x.b === "string") {
|
|
30
|
+
const d = diffPng(readFileSync(x.a), readFileSync(x.b), threshold);
|
|
31
|
+
writeFileBytes(`${x.b}.diff.png`, d.diffPng);
|
|
32
|
+
return jsonResult({ ...stats(d), diffImage: `${x.b}.diff.png` });
|
|
33
|
+
}
|
|
34
|
+
if (typeof x.sessionId !== "string" || typeof x.baseline !== "string") {
|
|
35
|
+
return errorResult("browser_visual_diff needs either `a`+`b` paths, or `sessionId`+`baseline`");
|
|
36
|
+
}
|
|
37
|
+
const baseline = x.baseline;
|
|
38
|
+
return withSession(sessions, x.sessionId, async (s) => {
|
|
39
|
+
const shot = await s.page.screenshot({ fullPage: x.fullPage === true });
|
|
40
|
+
if (!existsSync(baseline)) {
|
|
41
|
+
writeFileBytes(baseline, shot);
|
|
42
|
+
return jsonResult({ baselineCreated: true, baseline });
|
|
43
|
+
}
|
|
44
|
+
const d = diffPng(readFileSync(baseline), shot, threshold);
|
|
45
|
+
writeFileBytes(`${baseline}.diff.png`, d.diffPng);
|
|
46
|
+
return jsonResult({ ...stats(d), baseline, diffImage: `${baseline}.diff.png` });
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Serializable diff stats (without the raw PNG bytes). */
|
|
51
|
+
function stats(d) {
|
|
52
|
+
return { width: d.width, height: d.height, diffPixels: d.diffPixels, diffRatio: d.diffRatio, regions: d.regions };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=visual-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-diff.js","sourceRoot":"","sources":["../../../src/server/tools/visual-diff.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusengine/browser-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "MCP server + CLI giving AI agents a real, stealth browser (Patchright/Playwright) — per-country identity, self-healing actions, snapshots, multi-step plans, structured extraction, CDP attach.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Fusengine",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build": "tsc -p tsconfig.json",
|
|
55
55
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
56
56
|
"test": "bun test tests/unit",
|
|
57
|
-
"test:integration": "node --test --import tsx tests/integration/mcp.test.ts tests/integration/probe.test.ts tests/integration/snapshot.test.ts tests/integration/snapshot-frames.test.ts tests/integration/collect.test.ts tests/integration/selectors.test.ts tests/integration/run.test.ts tests/integration/extract-schema.test.ts",
|
|
57
|
+
"test:integration": "node --test --import tsx tests/integration/mcp.test.ts tests/integration/probe.test.ts tests/integration/snapshot.test.ts tests/integration/snapshot-frames.test.ts tests/integration/collect.test.ts tests/integration/selectors.test.ts tests/integration/visual-diff.test.ts tests/integration/run.test.ts tests/integration/extract-schema.test.ts",
|
|
58
58
|
"browsers": "patchright install chromium",
|
|
59
59
|
"mcp": "node --import tsx src/bin/mcp.ts",
|
|
60
60
|
"cli": "node --import tsx src/bin/cli.ts"
|
|
@@ -65,9 +65,11 @@
|
|
|
65
65
|
],
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
68
|
+
"fast-png": "^8.0.0",
|
|
68
69
|
"impit": "^0.14.1",
|
|
69
70
|
"linkedom": "^0.18.12",
|
|
70
71
|
"patchright": "^1.60.1",
|
|
72
|
+
"pixelmatch": "^7.2.0",
|
|
71
73
|
"playwright": "^1.60.0",
|
|
72
74
|
"zod": "^4.4.3"
|
|
73
75
|
},
|