@hasna/browser 0.0.3 → 0.0.4
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/dist/cli/index.js +246 -62
- package/dist/db/sessions.d.ts.map +1 -1
- package/dist/index.js +32 -8
- package/dist/lib/annotate.d.ts.map +1 -1
- package/dist/lib/screenshot-v4.test.d.ts +2 -0
- package/dist/lib/screenshot-v4.test.d.ts.map +1 -0
- package/dist/lib/screenshot.d.ts.map +1 -1
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/mcp/index.js +240 -54
- package/dist/mcp/v4.test.d.ts +2 -0
- package/dist/mcp/v4.test.d.ts.map +1 -0
- package/dist/server/index.js +32 -8
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2121,6 +2121,12 @@ var init_types = __esm(() => {
|
|
|
2121
2121
|
});
|
|
2122
2122
|
|
|
2123
2123
|
// src/db/schema.ts
|
|
2124
|
+
var exports_schema = {};
|
|
2125
|
+
__export(exports_schema, {
|
|
2126
|
+
resetDatabase: () => resetDatabase,
|
|
2127
|
+
getDatabase: () => getDatabase,
|
|
2128
|
+
getDataDir: () => getDataDir
|
|
2129
|
+
});
|
|
2124
2130
|
import { Database } from "bun:sqlite";
|
|
2125
2131
|
import { join } from "path";
|
|
2126
2132
|
import { mkdirSync } from "fs";
|
|
@@ -2146,6 +2152,15 @@ function getDatabase(path) {
|
|
|
2146
2152
|
runMigrations(_db);
|
|
2147
2153
|
return _db;
|
|
2148
2154
|
}
|
|
2155
|
+
function resetDatabase() {
|
|
2156
|
+
if (_db) {
|
|
2157
|
+
try {
|
|
2158
|
+
_db.close();
|
|
2159
|
+
} catch {}
|
|
2160
|
+
}
|
|
2161
|
+
_db = null;
|
|
2162
|
+
_dbPath = null;
|
|
2163
|
+
}
|
|
2149
2164
|
function runMigrations(db) {
|
|
2150
2165
|
db.exec(`
|
|
2151
2166
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
@@ -2313,7 +2328,14 @@ import { randomUUID } from "crypto";
|
|
|
2313
2328
|
function createSession(data) {
|
|
2314
2329
|
const db = getDatabase();
|
|
2315
2330
|
const id = randomUUID();
|
|
2316
|
-
|
|
2331
|
+
let name = data.name ?? null;
|
|
2332
|
+
if (name) {
|
|
2333
|
+
const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
|
|
2334
|
+
if (existing) {
|
|
2335
|
+
name = `${name}-${id.slice(0, 6)}`;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
|
|
2317
2339
|
return getSession(id);
|
|
2318
2340
|
}
|
|
2319
2341
|
function getSessionByName(name) {
|
|
@@ -2876,12 +2898,19 @@ async function createSession2(opts = {}) {
|
|
|
2876
2898
|
userAgent: opts.userAgent
|
|
2877
2899
|
});
|
|
2878
2900
|
}
|
|
2901
|
+
let sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
2902
|
+
try {
|
|
2903
|
+
return new URL(opts.startUrl).hostname;
|
|
2904
|
+
} catch {
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
})() : undefined);
|
|
2879
2908
|
const session = createSession({
|
|
2880
2909
|
engine: resolvedEngine,
|
|
2881
2910
|
projectId: opts.projectId,
|
|
2882
2911
|
agentId: opts.agentId,
|
|
2883
2912
|
startUrl: opts.startUrl,
|
|
2884
|
-
name:
|
|
2913
|
+
name: sessionName
|
|
2885
2914
|
});
|
|
2886
2915
|
if (opts.stealth) {
|
|
2887
2916
|
try {
|
|
@@ -2978,12 +3007,28 @@ var init_session = __esm(() => {
|
|
|
2978
3007
|
});
|
|
2979
3008
|
|
|
2980
3009
|
// src/lib/snapshot.ts
|
|
3010
|
+
var exports_snapshot = {};
|
|
3011
|
+
__export(exports_snapshot, {
|
|
3012
|
+
takeSnapshot: () => takeSnapshot,
|
|
3013
|
+
setLastSnapshot: () => setLastSnapshot,
|
|
3014
|
+
hasRefs: () => hasRefs,
|
|
3015
|
+
getSessionRefs: () => getSessionRefs,
|
|
3016
|
+
getRefLocator: () => getRefLocator,
|
|
3017
|
+
getRefInfo: () => getRefInfo,
|
|
3018
|
+
getLastSnapshot: () => getLastSnapshot,
|
|
3019
|
+
diffSnapshots: () => diffSnapshots,
|
|
3020
|
+
clearSessionRefs: () => clearSessionRefs,
|
|
3021
|
+
clearLastSnapshot: () => clearLastSnapshot
|
|
3022
|
+
});
|
|
2981
3023
|
function getLastSnapshot(sessionId) {
|
|
2982
3024
|
return lastSnapshots.get(sessionId) ?? null;
|
|
2983
3025
|
}
|
|
2984
3026
|
function setLastSnapshot(sessionId, snapshot) {
|
|
2985
3027
|
lastSnapshots.set(sessionId, snapshot);
|
|
2986
3028
|
}
|
|
3029
|
+
function clearLastSnapshot(sessionId) {
|
|
3030
|
+
lastSnapshots.delete(sessionId);
|
|
3031
|
+
}
|
|
2987
3032
|
async function takeSnapshot(page, sessionId) {
|
|
2988
3033
|
let ariaTree;
|
|
2989
3034
|
try {
|
|
@@ -3086,6 +3131,21 @@ function getRefLocator(page, sessionId, ref) {
|
|
|
3086
3131
|
throw new Error(`Ref ${ref} not found. Available refs: ${[...refMap.keys()].slice(0, 20).join(", ")}`);
|
|
3087
3132
|
return page.getByRole(entry.role, { name: entry.name }).first();
|
|
3088
3133
|
}
|
|
3134
|
+
function getRefInfo(sessionId, ref) {
|
|
3135
|
+
const refMap = sessionRefMaps.get(sessionId);
|
|
3136
|
+
if (!refMap)
|
|
3137
|
+
return null;
|
|
3138
|
+
return refMap.get(ref) ?? null;
|
|
3139
|
+
}
|
|
3140
|
+
function getSessionRefs(sessionId) {
|
|
3141
|
+
return sessionRefMaps.get(sessionId) ?? null;
|
|
3142
|
+
}
|
|
3143
|
+
function clearSessionRefs(sessionId) {
|
|
3144
|
+
sessionRefMaps.delete(sessionId);
|
|
3145
|
+
}
|
|
3146
|
+
function hasRefs(sessionId) {
|
|
3147
|
+
return sessionRefMaps.has(sessionId) && (sessionRefMaps.get(sessionId)?.size ?? 0) > 0;
|
|
3148
|
+
}
|
|
3089
3149
|
function refKey(info) {
|
|
3090
3150
|
return `${info.role}::${info.name}`;
|
|
3091
3151
|
}
|
|
@@ -10166,11 +10226,20 @@ async function takeScreenshot(page, opts) {
|
|
|
10166
10226
|
}
|
|
10167
10227
|
const originalSizeBytes = rawBuffer.length;
|
|
10168
10228
|
let finalBuffer;
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10229
|
+
let compressed = true;
|
|
10230
|
+
let fallback = false;
|
|
10231
|
+
try {
|
|
10232
|
+
if (compress && format !== "png") {
|
|
10233
|
+
finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
|
|
10234
|
+
} else if (compress && format === "png") {
|
|
10235
|
+
finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
|
|
10236
|
+
} else {
|
|
10237
|
+
finalBuffer = rawBuffer;
|
|
10238
|
+
compressed = false;
|
|
10239
|
+
}
|
|
10240
|
+
} catch (sharpErr) {
|
|
10241
|
+
fallback = true;
|
|
10242
|
+
compressed = false;
|
|
10174
10243
|
finalBuffer = rawBuffer;
|
|
10175
10244
|
}
|
|
10176
10245
|
const compressedSizeBytes = finalBuffer.length;
|
|
@@ -10198,7 +10267,8 @@ async function takeScreenshot(page, opts) {
|
|
|
10198
10267
|
compressed_size_bytes: compressedSizeBytes,
|
|
10199
10268
|
compression_ratio: compressionRatio,
|
|
10200
10269
|
thumbnail_path: thumbnailPath,
|
|
10201
|
-
thumbnail_base64: thumbnailBase64
|
|
10270
|
+
thumbnail_base64: thumbnailBase64,
|
|
10271
|
+
...fallback ? { fallback: true, compressed: false } : {}
|
|
10202
10272
|
};
|
|
10203
10273
|
if (opts?.track !== false) {
|
|
10204
10274
|
try {
|
|
@@ -15298,7 +15368,8 @@ async function annotateScreenshot(page, sessionId) {
|
|
|
15298
15368
|
const annotations = [];
|
|
15299
15369
|
const labelToRef = {};
|
|
15300
15370
|
let labelCounter = 1;
|
|
15301
|
-
|
|
15371
|
+
const refsToAnnotate = Object.entries(snapshot.refs).slice(0, MAX_ANNOTATIONS);
|
|
15372
|
+
for (const [ref, info] of refsToAnnotate) {
|
|
15302
15373
|
try {
|
|
15303
15374
|
const locator = page.getByRole(info.role, { name: info.name }).first();
|
|
15304
15375
|
const box = await locator.boundingBox();
|
|
@@ -15337,7 +15408,7 @@ async function annotateScreenshot(page, sessionId) {
|
|
|
15337
15408
|
const annotatedBuffer = await import_sharp3.default(rawBuffer).composite([{ input: Buffer.from(svg), top: 0, left: 0 }]).webp({ quality: 85 }).toBuffer();
|
|
15338
15409
|
return { buffer: annotatedBuffer, annotations, labelToRef };
|
|
15339
15410
|
}
|
|
15340
|
-
var import_sharp3;
|
|
15411
|
+
var import_sharp3, MAX_ANNOTATIONS = 40;
|
|
15341
15412
|
var init_annotate = __esm(() => {
|
|
15342
15413
|
init_snapshot();
|
|
15343
15414
|
import_sharp3 = __toESM(require_lib(), 1);
|
|
@@ -15347,6 +15418,8 @@ var init_annotate = __esm(() => {
|
|
|
15347
15418
|
var exports_mcp = {};
|
|
15348
15419
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15349
15420
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15421
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
15422
|
+
import { join as join7 } from "path";
|
|
15350
15423
|
function json(data) {
|
|
15351
15424
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
15352
15425
|
}
|
|
@@ -15358,7 +15431,7 @@ function err(e) {
|
|
|
15358
15431
|
isError: true
|
|
15359
15432
|
};
|
|
15360
15433
|
}
|
|
15361
|
-
var networkLogCleanup, consoleCaptureCleanup, harCaptures, server, activeWatchHandles, transport;
|
|
15434
|
+
var _pkg, networkLogCleanup, consoleCaptureCleanup, harCaptures, server, activeWatchHandles, _startupToolCount, transport;
|
|
15362
15435
|
var init_mcp = __esm(async () => {
|
|
15363
15436
|
init_zod();
|
|
15364
15437
|
init_session();
|
|
@@ -15382,6 +15455,7 @@ var init_mcp = __esm(async () => {
|
|
|
15382
15455
|
init_dialogs();
|
|
15383
15456
|
init_profiles();
|
|
15384
15457
|
init_types();
|
|
15458
|
+
_pkg = JSON.parse(readFileSync3(join7(import.meta.dir, "../../package.json"), "utf8"));
|
|
15385
15459
|
networkLogCleanup = new Map;
|
|
15386
15460
|
consoleCaptureCleanup = new Map;
|
|
15387
15461
|
harCaptures = new Map;
|
|
@@ -15436,13 +15510,50 @@ var init_mcp = __esm(async () => {
|
|
|
15436
15510
|
return err(e);
|
|
15437
15511
|
}
|
|
15438
15512
|
});
|
|
15439
|
-
server.tool("browser_navigate", "Navigate to a URL.
|
|
15513
|
+
server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto-names session, returns compact refs + thumbnail.", {
|
|
15514
|
+
session_id: exports_external.string(),
|
|
15515
|
+
url: exports_external.string(),
|
|
15516
|
+
timeout: exports_external.number().optional().default(30000),
|
|
15517
|
+
auto_snapshot: exports_external.boolean().optional().default(true),
|
|
15518
|
+
auto_thumbnail: exports_external.boolean().optional().default(true)
|
|
15519
|
+
}, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
|
|
15440
15520
|
try {
|
|
15441
15521
|
const page = getSessionPage(session_id);
|
|
15442
15522
|
await navigate(page, url, timeout);
|
|
15443
15523
|
const title = await getTitle(page);
|
|
15444
15524
|
const current_url = await getUrl(page);
|
|
15445
|
-
const
|
|
15525
|
+
const redirected = current_url !== url && current_url !== url + "/" && url !== current_url.replace(/\/$/, "");
|
|
15526
|
+
let redirect_type;
|
|
15527
|
+
if (redirected) {
|
|
15528
|
+
try {
|
|
15529
|
+
const reqHost = new URL(url).hostname;
|
|
15530
|
+
const resHost = new URL(current_url).hostname;
|
|
15531
|
+
const reqPath = new URL(url).pathname;
|
|
15532
|
+
const resPath = new URL(current_url).pathname;
|
|
15533
|
+
if (reqHost !== resHost)
|
|
15534
|
+
redirect_type = "canonical";
|
|
15535
|
+
else if (resPath.match(/\/[a-z]{2}-[a-z]{2}\//))
|
|
15536
|
+
redirect_type = "geo";
|
|
15537
|
+
else if (current_url.includes("login") || current_url.includes("signin"))
|
|
15538
|
+
redirect_type = "auth";
|
|
15539
|
+
else
|
|
15540
|
+
redirect_type = "unknown";
|
|
15541
|
+
} catch {}
|
|
15542
|
+
}
|
|
15543
|
+
try {
|
|
15544
|
+
const session = getSession2(session_id);
|
|
15545
|
+
if (!session.name) {
|
|
15546
|
+
const hostname = new URL(current_url).hostname;
|
|
15547
|
+
renameSession2(session_id, hostname);
|
|
15548
|
+
}
|
|
15549
|
+
} catch {}
|
|
15550
|
+
const result = {
|
|
15551
|
+
url,
|
|
15552
|
+
title,
|
|
15553
|
+
current_url,
|
|
15554
|
+
redirected,
|
|
15555
|
+
...redirect_type ? { redirect_type } : {}
|
|
15556
|
+
};
|
|
15446
15557
|
if (auto_thumbnail) {
|
|
15447
15558
|
try {
|
|
15448
15559
|
const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
|
|
@@ -15452,7 +15563,9 @@ var init_mcp = __esm(async () => {
|
|
|
15452
15563
|
if (auto_snapshot) {
|
|
15453
15564
|
try {
|
|
15454
15565
|
const snap = await takeSnapshot(page, session_id);
|
|
15455
|
-
|
|
15566
|
+
setLastSnapshot(session_id, snap);
|
|
15567
|
+
const refEntries = Object.entries(snap.refs).slice(0, 30);
|
|
15568
|
+
result.snapshot_refs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 50)} [${ref}]`).join(", ");
|
|
15456
15569
|
result.interactive_count = snap.interactive_count;
|
|
15457
15570
|
result.has_errors = getConsoleLog(session_id, "error").length > 0;
|
|
15458
15571
|
} catch {}
|
|
@@ -15558,7 +15671,7 @@ var init_mcp = __esm(async () => {
|
|
|
15558
15671
|
return err(e);
|
|
15559
15672
|
}
|
|
15560
15673
|
});
|
|
15561
|
-
server.tool("
|
|
15674
|
+
server.tool("browser_toggle", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
|
|
15562
15675
|
try {
|
|
15563
15676
|
const page = getSessionPage(session_id);
|
|
15564
15677
|
if (ref) {
|
|
@@ -15649,12 +15762,33 @@ var init_mcp = __esm(async () => {
|
|
|
15649
15762
|
return err(e);
|
|
15650
15763
|
}
|
|
15651
15764
|
});
|
|
15652
|
-
server.tool("browser_snapshot", "Get
|
|
15765
|
+
server.tool("browser_snapshot", "Get accessibility snapshot with element refs (@e0, @e1...). Use compact=true (default) for token-efficient output. Use refs in browser_click, browser_type, etc.", {
|
|
15766
|
+
session_id: exports_external.string(),
|
|
15767
|
+
compact: exports_external.boolean().optional().default(true),
|
|
15768
|
+
max_refs: exports_external.number().optional().default(50),
|
|
15769
|
+
full_tree: exports_external.boolean().optional().default(false)
|
|
15770
|
+
}, async ({ session_id, compact, max_refs, full_tree }) => {
|
|
15653
15771
|
try {
|
|
15654
15772
|
const page = getSessionPage(session_id);
|
|
15655
15773
|
const result = await takeSnapshot(page, session_id);
|
|
15656
15774
|
setLastSnapshot(session_id, result);
|
|
15657
|
-
|
|
15775
|
+
const refEntries = Object.entries(result.refs).slice(0, max_refs);
|
|
15776
|
+
const limitedRefs = Object.fromEntries(refEntries);
|
|
15777
|
+
const truncated = Object.keys(result.refs).length > max_refs;
|
|
15778
|
+
if (compact && !full_tree) {
|
|
15779
|
+
const compactRefs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 60)} [${ref}]${info.checked !== undefined ? ` checked=${info.checked}` : ""}${!info.enabled ? " disabled" : ""}`).join(`
|
|
15780
|
+
`);
|
|
15781
|
+
return json({
|
|
15782
|
+
snapshot_compact: compactRefs,
|
|
15783
|
+
interactive_count: result.interactive_count,
|
|
15784
|
+
shown_count: refEntries.length,
|
|
15785
|
+
truncated,
|
|
15786
|
+
refs: limitedRefs
|
|
15787
|
+
});
|
|
15788
|
+
}
|
|
15789
|
+
const tree = full_tree ? result.tree : result.tree.slice(0, 5000) + (result.tree.length > 5000 ? `
|
|
15790
|
+
... (truncated \u2014 use full_tree=true for complete)` : "");
|
|
15791
|
+
return json({ snapshot: tree, refs: limitedRefs, interactive_count: result.interactive_count, truncated });
|
|
15658
15792
|
} catch (e) {
|
|
15659
15793
|
return err(e);
|
|
15660
15794
|
}
|
|
@@ -16136,40 +16270,6 @@ var init_mcp = __esm(async () => {
|
|
|
16136
16270
|
return err(e);
|
|
16137
16271
|
}
|
|
16138
16272
|
});
|
|
16139
|
-
server.tool("browser_page_check", "One-call page summary: page info + console errors + performance metrics + thumbnail + accessibility snapshot preview. Replaces 4-5 separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
16140
|
-
try {
|
|
16141
|
-
const page = getSessionPage(session_id);
|
|
16142
|
-
const info = await getPageInfo(page);
|
|
16143
|
-
const errors2 = getConsoleLog(session_id, "error");
|
|
16144
|
-
info.has_console_errors = errors2.length > 0;
|
|
16145
|
-
let perf = {};
|
|
16146
|
-
try {
|
|
16147
|
-
perf = await getPerformanceMetrics(page);
|
|
16148
|
-
} catch {}
|
|
16149
|
-
let thumbnail_base64 = "";
|
|
16150
|
-
try {
|
|
16151
|
-
const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
|
|
16152
|
-
thumbnail_base64 = ss.base64;
|
|
16153
|
-
} catch {}
|
|
16154
|
-
let snapshot_preview = "";
|
|
16155
|
-
let interactive_count = 0;
|
|
16156
|
-
try {
|
|
16157
|
-
const snap = await takeSnapshot(page, session_id);
|
|
16158
|
-
snapshot_preview = snap.tree.slice(0, 2000);
|
|
16159
|
-
interactive_count = snap.interactive_count;
|
|
16160
|
-
} catch {}
|
|
16161
|
-
return json({
|
|
16162
|
-
...info,
|
|
16163
|
-
error_count: errors2.length,
|
|
16164
|
-
performance: perf,
|
|
16165
|
-
thumbnail_base64: thumbnail_base64.length > 50000 ? "" : thumbnail_base64,
|
|
16166
|
-
snapshot_preview,
|
|
16167
|
-
interactive_count
|
|
16168
|
-
});
|
|
16169
|
-
} catch (e) {
|
|
16170
|
-
return err(e);
|
|
16171
|
-
}
|
|
16172
|
-
});
|
|
16173
16273
|
server.tool("browser_gallery_list", "List screenshot gallery entries with optional filters", {
|
|
16174
16274
|
project_id: exports_external.string().optional(),
|
|
16175
16275
|
session_id: exports_external.string().optional(),
|
|
@@ -16494,7 +16594,7 @@ var init_mcp = __esm(async () => {
|
|
|
16494
16594
|
{ tool: "browser_hover", description: "Hover over an element" },
|
|
16495
16595
|
{ tool: "browser_scroll", description: "Scroll the page" },
|
|
16496
16596
|
{ tool: "browser_select", description: "Select a dropdown option" },
|
|
16497
|
-
{ tool: "
|
|
16597
|
+
{ tool: "browser_toggle", description: "Check/uncheck a checkbox" },
|
|
16498
16598
|
{ tool: "browser_upload", description: "Upload a file to an input" },
|
|
16499
16599
|
{ tool: "browser_press_key", description: "Press a keyboard key" },
|
|
16500
16600
|
{ tool: "browser_wait", description: "Wait for a selector to appear" },
|
|
@@ -16514,9 +16614,10 @@ var init_mcp = __esm(async () => {
|
|
|
16514
16614
|
{ tool: "browser_evaluate", description: "Execute JavaScript in page context" }
|
|
16515
16615
|
],
|
|
16516
16616
|
Capture: [
|
|
16517
|
-
{ tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP)" },
|
|
16617
|
+
{ tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP, annotate=true for labels)" },
|
|
16518
16618
|
{ tool: "browser_pdf", description: "Generate a PDF of the page" },
|
|
16519
|
-
{ tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" }
|
|
16619
|
+
{ tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" },
|
|
16620
|
+
{ tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" }
|
|
16520
16621
|
],
|
|
16521
16622
|
Storage: [
|
|
16522
16623
|
{ tool: "browser_cookies_get", description: "Get cookies" },
|
|
@@ -16595,7 +16696,8 @@ var init_mcp = __esm(async () => {
|
|
|
16595
16696
|
{ tool: "browser_tab_close", description: "Close a tab by index" }
|
|
16596
16697
|
],
|
|
16597
16698
|
Meta: [
|
|
16598
|
-
{ tool: "
|
|
16699
|
+
{ tool: "browser_check", description: "RECOMMENDED: One-call page summary with diagnostics" },
|
|
16700
|
+
{ tool: "browser_version", description: "Show running binary version and tool count" },
|
|
16599
16701
|
{ tool: "browser_help", description: "Show this help (all tools)" },
|
|
16600
16702
|
{ tool: "browser_snapshot_diff", description: "Diff current snapshot vs previous" },
|
|
16601
16703
|
{ tool: "browser_watch_start", description: "Watch page for DOM changes" },
|
|
@@ -16609,6 +16711,88 @@ var init_mcp = __esm(async () => {
|
|
|
16609
16711
|
return err(e);
|
|
16610
16712
|
}
|
|
16611
16713
|
});
|
|
16714
|
+
server.tool("browser_version", "Get the running browser MCP version, tool count, and environment info. Use this to verify which binary is active.", {}, async () => {
|
|
16715
|
+
try {
|
|
16716
|
+
const { getDataDir: getDataDir4 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
16717
|
+
const toolCount = Object.keys(server._registeredTools ?? {}).length;
|
|
16718
|
+
return json({
|
|
16719
|
+
version: _pkg.version,
|
|
16720
|
+
mcp_tools_count: toolCount,
|
|
16721
|
+
bun_version: Bun.version,
|
|
16722
|
+
data_dir: getDataDir4(),
|
|
16723
|
+
node_env: process.env["NODE_ENV"] ?? "production"
|
|
16724
|
+
});
|
|
16725
|
+
} catch (e) {
|
|
16726
|
+
return err(e);
|
|
16727
|
+
}
|
|
16728
|
+
});
|
|
16729
|
+
server.tool("browser_scroll_to_element", "Scroll an element into view (by ref or selector) then optionally take a screenshot of it. Replaces scroll + wait + screenshot pattern.", {
|
|
16730
|
+
session_id: exports_external.string(),
|
|
16731
|
+
selector: exports_external.string().optional(),
|
|
16732
|
+
ref: exports_external.string().optional(),
|
|
16733
|
+
screenshot: exports_external.boolean().optional().default(true),
|
|
16734
|
+
wait_ms: exports_external.number().optional().default(200)
|
|
16735
|
+
}, async ({ session_id, selector, ref, screenshot: doScreenshot, wait_ms }) => {
|
|
16736
|
+
try {
|
|
16737
|
+
const page = getSessionPage(session_id);
|
|
16738
|
+
let locator;
|
|
16739
|
+
if (ref) {
|
|
16740
|
+
const { getRefLocator: getRefLocator2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
|
|
16741
|
+
locator = getRefLocator2(page, session_id, ref);
|
|
16742
|
+
} else if (selector) {
|
|
16743
|
+
locator = page.locator(selector).first();
|
|
16744
|
+
} else {
|
|
16745
|
+
return err(new Error("Either ref or selector is required"));
|
|
16746
|
+
}
|
|
16747
|
+
await locator.scrollIntoViewIfNeeded();
|
|
16748
|
+
await new Promise((r) => setTimeout(r, wait_ms));
|
|
16749
|
+
const result = { scrolled: ref ?? selector };
|
|
16750
|
+
if (doScreenshot) {
|
|
16751
|
+
try {
|
|
16752
|
+
const ss = await takeScreenshot(page, { selector, track: false });
|
|
16753
|
+
ss.url = page.url();
|
|
16754
|
+
if (ss.base64.length > 50000) {
|
|
16755
|
+
ss.base64_truncated = true;
|
|
16756
|
+
ss.base64 = ss.thumbnail_base64 ?? "";
|
|
16757
|
+
}
|
|
16758
|
+
result.screenshot = ss;
|
|
16759
|
+
} catch {}
|
|
16760
|
+
}
|
|
16761
|
+
return json(result);
|
|
16762
|
+
} catch (e) {
|
|
16763
|
+
return err(e);
|
|
16764
|
+
}
|
|
16765
|
+
});
|
|
16766
|
+
server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u2014 url, title, errors, performance, thumbnail, refs. Replaces 4+ separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
16767
|
+
try {
|
|
16768
|
+
const page = getSessionPage(session_id);
|
|
16769
|
+
const info = await getPageInfo(page);
|
|
16770
|
+
const errors2 = getConsoleLog(session_id, "error");
|
|
16771
|
+
info.has_console_errors = errors2.length > 0;
|
|
16772
|
+
let perf = {};
|
|
16773
|
+
try {
|
|
16774
|
+
perf = await getPerformanceMetrics(page);
|
|
16775
|
+
} catch {}
|
|
16776
|
+
let thumbnail_base64 = "";
|
|
16777
|
+
try {
|
|
16778
|
+
const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
|
|
16779
|
+
thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
|
|
16780
|
+
} catch {}
|
|
16781
|
+
let snapshot_refs = "";
|
|
16782
|
+
let interactive_count = 0;
|
|
16783
|
+
try {
|
|
16784
|
+
const snap = await takeSnapshot(page, session_id);
|
|
16785
|
+
setLastSnapshot(session_id, snap);
|
|
16786
|
+
interactive_count = snap.interactive_count;
|
|
16787
|
+
snapshot_refs = Object.entries(snap.refs).slice(0, 30).map(([ref, i]) => `${i.role}:${i.name.slice(0, 50)} [${ref}]`).join(", ");
|
|
16788
|
+
} catch {}
|
|
16789
|
+
return json({ ...info, error_count: errors2.length, performance: perf, thumbnail_base64, snapshot_refs, interactive_count });
|
|
16790
|
+
} catch (e) {
|
|
16791
|
+
return err(e);
|
|
16792
|
+
}
|
|
16793
|
+
});
|
|
16794
|
+
_startupToolCount = Object.keys(server._registeredTools ?? {}).length;
|
|
16795
|
+
console.error(`@hasna/browser v${_pkg.version} \u2014 ${_startupToolCount} tools | data: ${(await Promise.resolve().then(() => (init_schema(), exports_schema))).getDataDir()}`);
|
|
16612
16796
|
transport = new StdioServerTransport;
|
|
16613
16797
|
await server.connect(transport);
|
|
16614
16798
|
});
|
|
@@ -16651,7 +16835,7 @@ var init_snapshots = __esm(() => {
|
|
|
16651
16835
|
|
|
16652
16836
|
// src/server/index.ts
|
|
16653
16837
|
var exports_server = {};
|
|
16654
|
-
import { join as
|
|
16838
|
+
import { join as join8 } from "path";
|
|
16655
16839
|
import { existsSync as existsSync4 } from "fs";
|
|
16656
16840
|
function ok(data, status = 200) {
|
|
16657
16841
|
return new Response(JSON.stringify(data), {
|
|
@@ -16945,13 +17129,13 @@ var init_server = __esm(() => {
|
|
|
16945
17129
|
const id = path.split("/")[3];
|
|
16946
17130
|
return ok({ deleted: deleteDownload(id) });
|
|
16947
17131
|
}
|
|
16948
|
-
const dashboardDist =
|
|
17132
|
+
const dashboardDist = join8(import.meta.dir, "../../dashboard/dist");
|
|
16949
17133
|
if (existsSync4(dashboardDist)) {
|
|
16950
|
-
const filePath = path === "/" ?
|
|
17134
|
+
const filePath = path === "/" ? join8(dashboardDist, "index.html") : join8(dashboardDist, path);
|
|
16951
17135
|
if (existsSync4(filePath)) {
|
|
16952
17136
|
return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
|
|
16953
17137
|
}
|
|
16954
|
-
return new Response(Bun.file(
|
|
17138
|
+
return new Response(Bun.file(join8(dashboardDist, "index.html")), { headers: CORS_HEADERS });
|
|
16955
17139
|
}
|
|
16956
17140
|
if (path === "/" || path === "") {
|
|
16957
17141
|
return new Response("@hasna/browser REST API running. Dashboard not built.", {
|
|
@@ -16993,10 +17177,10 @@ init_projects();
|
|
|
16993
17177
|
init_recorder();
|
|
16994
17178
|
init_recordings();
|
|
16995
17179
|
init_lightpanda();
|
|
16996
|
-
import { readFileSync as
|
|
16997
|
-
import { join as
|
|
17180
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
17181
|
+
import { join as join9 } from "path";
|
|
16998
17182
|
import chalk from "chalk";
|
|
16999
|
-
var pkg = JSON.parse(
|
|
17183
|
+
var pkg = JSON.parse(readFileSync4(join9(import.meta.dir, "../../package.json"), "utf8"));
|
|
17000
17184
|
var program2 = new Command;
|
|
17001
17185
|
program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
|
|
17002
17186
|
program2.command("navigate <url>").description("Navigate to a URL and optionally take a screenshot").option("--engine <engine>", "Browser engine: playwright|cdp|lightpanda|auto", "auto").option("--screenshot", "Take a screenshot after navigation").option("--extract", "Extract page text after navigation").option("--headless", "Run in headless mode (default: true)", true).action(async (url, opts) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/db/sessions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG/E,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/db/sessions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG/E,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAiB9D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAG7D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAI/D;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAK9C;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,aAAa,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,EAAE,CAQ/F;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAO9E;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAG9C"}
|
package/dist/index.js
CHANGED
|
@@ -6827,7 +6827,14 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
6827
6827
|
function createSession(data) {
|
|
6828
6828
|
const db = getDatabase();
|
|
6829
6829
|
const id = randomUUID3();
|
|
6830
|
-
|
|
6830
|
+
let name = data.name ?? null;
|
|
6831
|
+
if (name) {
|
|
6832
|
+
const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
|
|
6833
|
+
if (existing) {
|
|
6834
|
+
name = `${name}-${id.slice(0, 6)}`;
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6837
|
+
db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
|
|
6831
6838
|
return getSession(id);
|
|
6832
6839
|
}
|
|
6833
6840
|
function getSessionByName(name) {
|
|
@@ -7728,12 +7735,19 @@ async function createSession2(opts = {}) {
|
|
|
7728
7735
|
userAgent: opts.userAgent
|
|
7729
7736
|
});
|
|
7730
7737
|
}
|
|
7738
|
+
let sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
7739
|
+
try {
|
|
7740
|
+
return new URL(opts.startUrl).hostname;
|
|
7741
|
+
} catch {
|
|
7742
|
+
return;
|
|
7743
|
+
}
|
|
7744
|
+
})() : undefined);
|
|
7731
7745
|
const session = createSession({
|
|
7732
7746
|
engine: resolvedEngine,
|
|
7733
7747
|
projectId: opts.projectId,
|
|
7734
7748
|
agentId: opts.agentId,
|
|
7735
7749
|
startUrl: opts.startUrl,
|
|
7736
|
-
name:
|
|
7750
|
+
name: sessionName
|
|
7737
7751
|
});
|
|
7738
7752
|
if (opts.stealth) {
|
|
7739
7753
|
try {
|
|
@@ -8496,11 +8510,20 @@ async function takeScreenshot(page, opts) {
|
|
|
8496
8510
|
}
|
|
8497
8511
|
const originalSizeBytes = rawBuffer.length;
|
|
8498
8512
|
let finalBuffer;
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8513
|
+
let compressed = true;
|
|
8514
|
+
let fallback = false;
|
|
8515
|
+
try {
|
|
8516
|
+
if (compress && format !== "png") {
|
|
8517
|
+
finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
|
|
8518
|
+
} else if (compress && format === "png") {
|
|
8519
|
+
finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
|
|
8520
|
+
} else {
|
|
8521
|
+
finalBuffer = rawBuffer;
|
|
8522
|
+
compressed = false;
|
|
8523
|
+
}
|
|
8524
|
+
} catch (sharpErr) {
|
|
8525
|
+
fallback = true;
|
|
8526
|
+
compressed = false;
|
|
8504
8527
|
finalBuffer = rawBuffer;
|
|
8505
8528
|
}
|
|
8506
8529
|
const compressedSizeBytes = finalBuffer.length;
|
|
@@ -8528,7 +8551,8 @@ async function takeScreenshot(page, opts) {
|
|
|
8528
8551
|
compressed_size_bytes: compressedSizeBytes,
|
|
8529
8552
|
compression_ratio: compressionRatio,
|
|
8530
8553
|
thumbnail_path: thumbnailPath,
|
|
8531
|
-
thumbnail_base64: thumbnailBase64
|
|
8554
|
+
thumbnail_base64: thumbnailBase64,
|
|
8555
|
+
...fallback ? { fallback: true, compressed: false } : {}
|
|
8532
8556
|
};
|
|
8533
8557
|
if (opts?.track !== false) {
|
|
8534
8558
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"annotate.d.ts","sourceRoot":"","sources":["../../src/lib/annotate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;
|
|
1
|
+
{"version":3,"file":"annotate.d.ts","sourceRoot":"","sources":["../../src/lib/annotate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAID,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,yBAAyB,CAAC,CAwEpC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot-v4.test.d.ts","sourceRoot":"","sources":["../../src/lib/screenshot-v4.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/lib/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAgDpG,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,iBAAiB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrF,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/lib/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAgDpG,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,iBAAiB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrF,OAAO,CAAC,gBAAgB,CAAC,CAyH3B;AAID,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,UAAU,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,OAAO,CAAC,SAAS,CAAC,CA6BpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAW,MAAM,mBAAmB,CAAC;AAyB3D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,aAAa,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAW,MAAM,mBAAmB,CAAC;AAyB3D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,aAAa,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAiE3F;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAWtD;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAI5D;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAIjE;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAIlE;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBtE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,aAAa,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,EAAE,CAE/F;AAED,wBAAgB,iBAAiB,IAAI,OAAO,EAAE,CAE7C;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAItD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,kBAE5C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,WAErD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGxF"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -30,6 +30,12 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
30
30
|
var __require = import.meta.require;
|
|
31
31
|
|
|
32
32
|
// src/db/schema.ts
|
|
33
|
+
var exports_schema = {};
|
|
34
|
+
__export(exports_schema, {
|
|
35
|
+
resetDatabase: () => resetDatabase,
|
|
36
|
+
getDatabase: () => getDatabase,
|
|
37
|
+
getDataDir: () => getDataDir
|
|
38
|
+
});
|
|
33
39
|
import { Database } from "bun:sqlite";
|
|
34
40
|
import { join } from "path";
|
|
35
41
|
import { mkdirSync } from "fs";
|
|
@@ -55,6 +61,15 @@ function getDatabase(path) {
|
|
|
55
61
|
runMigrations(_db);
|
|
56
62
|
return _db;
|
|
57
63
|
}
|
|
64
|
+
function resetDatabase() {
|
|
65
|
+
if (_db) {
|
|
66
|
+
try {
|
|
67
|
+
_db.close();
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
_db = null;
|
|
71
|
+
_dbPath = null;
|
|
72
|
+
}
|
|
58
73
|
function runMigrations(db) {
|
|
59
74
|
db.exec(`
|
|
60
75
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
@@ -252,12 +267,28 @@ var init_console_log = __esm(() => {
|
|
|
252
267
|
});
|
|
253
268
|
|
|
254
269
|
// src/lib/snapshot.ts
|
|
270
|
+
var exports_snapshot = {};
|
|
271
|
+
__export(exports_snapshot, {
|
|
272
|
+
takeSnapshot: () => takeSnapshot,
|
|
273
|
+
setLastSnapshot: () => setLastSnapshot,
|
|
274
|
+
hasRefs: () => hasRefs,
|
|
275
|
+
getSessionRefs: () => getSessionRefs,
|
|
276
|
+
getRefLocator: () => getRefLocator,
|
|
277
|
+
getRefInfo: () => getRefInfo,
|
|
278
|
+
getLastSnapshot: () => getLastSnapshot,
|
|
279
|
+
diffSnapshots: () => diffSnapshots,
|
|
280
|
+
clearSessionRefs: () => clearSessionRefs,
|
|
281
|
+
clearLastSnapshot: () => clearLastSnapshot
|
|
282
|
+
});
|
|
255
283
|
function getLastSnapshot(sessionId) {
|
|
256
284
|
return lastSnapshots.get(sessionId) ?? null;
|
|
257
285
|
}
|
|
258
286
|
function setLastSnapshot(sessionId, snapshot) {
|
|
259
287
|
lastSnapshots.set(sessionId, snapshot);
|
|
260
288
|
}
|
|
289
|
+
function clearLastSnapshot(sessionId) {
|
|
290
|
+
lastSnapshots.delete(sessionId);
|
|
291
|
+
}
|
|
261
292
|
async function takeSnapshot(page, sessionId) {
|
|
262
293
|
let ariaTree;
|
|
263
294
|
try {
|
|
@@ -360,6 +391,21 @@ function getRefLocator(page, sessionId, ref) {
|
|
|
360
391
|
throw new Error(`Ref ${ref} not found. Available refs: ${[...refMap.keys()].slice(0, 20).join(", ")}`);
|
|
361
392
|
return page.getByRole(entry.role, { name: entry.name }).first();
|
|
362
393
|
}
|
|
394
|
+
function getRefInfo(sessionId, ref) {
|
|
395
|
+
const refMap = sessionRefMaps.get(sessionId);
|
|
396
|
+
if (!refMap)
|
|
397
|
+
return null;
|
|
398
|
+
return refMap.get(ref) ?? null;
|
|
399
|
+
}
|
|
400
|
+
function getSessionRefs(sessionId) {
|
|
401
|
+
return sessionRefMaps.get(sessionId) ?? null;
|
|
402
|
+
}
|
|
403
|
+
function clearSessionRefs(sessionId) {
|
|
404
|
+
sessionRefMaps.delete(sessionId);
|
|
405
|
+
}
|
|
406
|
+
function hasRefs(sessionId) {
|
|
407
|
+
return sessionRefMaps.has(sessionId) && (sessionRefMaps.get(sessionId)?.size ?? 0) > 0;
|
|
408
|
+
}
|
|
363
409
|
function refKey(info) {
|
|
364
410
|
return `${info.role}::${info.name}`;
|
|
365
411
|
}
|
|
@@ -6833,7 +6879,8 @@ async function annotateScreenshot(page, sessionId) {
|
|
|
6833
6879
|
const annotations = [];
|
|
6834
6880
|
const labelToRef = {};
|
|
6835
6881
|
let labelCounter = 1;
|
|
6836
|
-
|
|
6882
|
+
const refsToAnnotate = Object.entries(snapshot.refs).slice(0, MAX_ANNOTATIONS);
|
|
6883
|
+
for (const [ref, info] of refsToAnnotate) {
|
|
6837
6884
|
try {
|
|
6838
6885
|
const locator = page.getByRole(info.role, { name: info.name }).first();
|
|
6839
6886
|
const box = await locator.boundingBox();
|
|
@@ -6872,7 +6919,7 @@ async function annotateScreenshot(page, sessionId) {
|
|
|
6872
6919
|
const annotatedBuffer = await import_sharp3.default(rawBuffer).composite([{ input: Buffer.from(svg), top: 0, left: 0 }]).webp({ quality: 85 }).toBuffer();
|
|
6873
6920
|
return { buffer: annotatedBuffer, annotations, labelToRef };
|
|
6874
6921
|
}
|
|
6875
|
-
var import_sharp3;
|
|
6922
|
+
var import_sharp3, MAX_ANNOTATIONS = 40;
|
|
6876
6923
|
var init_annotate = __esm(() => {
|
|
6877
6924
|
init_snapshot();
|
|
6878
6925
|
import_sharp3 = __toESM(require_lib(), 1);
|
|
@@ -10855,6 +10902,10 @@ var coerce = {
|
|
|
10855
10902
|
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
|
10856
10903
|
};
|
|
10857
10904
|
var NEVER = INVALID;
|
|
10905
|
+
// src/mcp/index.ts
|
|
10906
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
10907
|
+
import { join as join7 } from "path";
|
|
10908
|
+
|
|
10858
10909
|
// src/types/index.ts
|
|
10859
10910
|
class BrowserError extends Error {
|
|
10860
10911
|
code;
|
|
@@ -10922,7 +10973,14 @@ import { randomUUID } from "crypto";
|
|
|
10922
10973
|
function createSession(data) {
|
|
10923
10974
|
const db = getDatabase();
|
|
10924
10975
|
const id = randomUUID();
|
|
10925
|
-
|
|
10976
|
+
let name = data.name ?? null;
|
|
10977
|
+
if (name) {
|
|
10978
|
+
const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
|
|
10979
|
+
if (existing) {
|
|
10980
|
+
name = `${name}-${id.slice(0, 6)}`;
|
|
10981
|
+
}
|
|
10982
|
+
}
|
|
10983
|
+
db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
|
|
10926
10984
|
return getSession(id);
|
|
10927
10985
|
}
|
|
10928
10986
|
function getSessionByName(name) {
|
|
@@ -11424,12 +11482,19 @@ async function createSession2(opts = {}) {
|
|
|
11424
11482
|
userAgent: opts.userAgent
|
|
11425
11483
|
});
|
|
11426
11484
|
}
|
|
11485
|
+
let sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
11486
|
+
try {
|
|
11487
|
+
return new URL(opts.startUrl).hostname;
|
|
11488
|
+
} catch {
|
|
11489
|
+
return;
|
|
11490
|
+
}
|
|
11491
|
+
})() : undefined);
|
|
11427
11492
|
const session = createSession({
|
|
11428
11493
|
engine: resolvedEngine,
|
|
11429
11494
|
projectId: opts.projectId,
|
|
11430
11495
|
agentId: opts.agentId,
|
|
11431
11496
|
startUrl: opts.startUrl,
|
|
11432
|
-
name:
|
|
11497
|
+
name: sessionName
|
|
11433
11498
|
});
|
|
11434
11499
|
if (opts.stealth) {
|
|
11435
11500
|
try {
|
|
@@ -12116,11 +12181,20 @@ async function takeScreenshot(page, opts) {
|
|
|
12116
12181
|
}
|
|
12117
12182
|
const originalSizeBytes = rawBuffer.length;
|
|
12118
12183
|
let finalBuffer;
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12184
|
+
let compressed = true;
|
|
12185
|
+
let fallback = false;
|
|
12186
|
+
try {
|
|
12187
|
+
if (compress && format !== "png") {
|
|
12188
|
+
finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
|
|
12189
|
+
} else if (compress && format === "png") {
|
|
12190
|
+
finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
|
|
12191
|
+
} else {
|
|
12192
|
+
finalBuffer = rawBuffer;
|
|
12193
|
+
compressed = false;
|
|
12194
|
+
}
|
|
12195
|
+
} catch (sharpErr) {
|
|
12196
|
+
fallback = true;
|
|
12197
|
+
compressed = false;
|
|
12124
12198
|
finalBuffer = rawBuffer;
|
|
12125
12199
|
}
|
|
12126
12200
|
const compressedSizeBytes = finalBuffer.length;
|
|
@@ -12148,7 +12222,8 @@ async function takeScreenshot(page, opts) {
|
|
|
12148
12222
|
compressed_size_bytes: compressedSizeBytes,
|
|
12149
12223
|
compression_ratio: compressionRatio,
|
|
12150
12224
|
thumbnail_path: thumbnailPath,
|
|
12151
|
-
thumbnail_base64: thumbnailBase64
|
|
12225
|
+
thumbnail_base64: thumbnailBase64,
|
|
12226
|
+
...fallback ? { fallback: true, compressed: false } : {}
|
|
12152
12227
|
};
|
|
12153
12228
|
if (opts?.track !== false) {
|
|
12154
12229
|
try {
|
|
@@ -13149,6 +13224,7 @@ function deleteProfile(name) {
|
|
|
13149
13224
|
}
|
|
13150
13225
|
|
|
13151
13226
|
// src/mcp/index.ts
|
|
13227
|
+
var _pkg = JSON.parse(readFileSync3(join7(import.meta.dir, "../../package.json"), "utf8"));
|
|
13152
13228
|
var networkLogCleanup = new Map;
|
|
13153
13229
|
var consoleCaptureCleanup = new Map;
|
|
13154
13230
|
var harCaptures = new Map;
|
|
@@ -13214,13 +13290,50 @@ server.tool("browser_session_close", "Close a browser session", { session_id: ex
|
|
|
13214
13290
|
return err(e);
|
|
13215
13291
|
}
|
|
13216
13292
|
});
|
|
13217
|
-
server.tool("browser_navigate", "Navigate to a URL.
|
|
13293
|
+
server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto-names session, returns compact refs + thumbnail.", {
|
|
13294
|
+
session_id: exports_external.string(),
|
|
13295
|
+
url: exports_external.string(),
|
|
13296
|
+
timeout: exports_external.number().optional().default(30000),
|
|
13297
|
+
auto_snapshot: exports_external.boolean().optional().default(true),
|
|
13298
|
+
auto_thumbnail: exports_external.boolean().optional().default(true)
|
|
13299
|
+
}, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
|
|
13218
13300
|
try {
|
|
13219
13301
|
const page = getSessionPage(session_id);
|
|
13220
13302
|
await navigate(page, url, timeout);
|
|
13221
13303
|
const title = await getTitle(page);
|
|
13222
13304
|
const current_url = await getUrl(page);
|
|
13223
|
-
const
|
|
13305
|
+
const redirected = current_url !== url && current_url !== url + "/" && url !== current_url.replace(/\/$/, "");
|
|
13306
|
+
let redirect_type;
|
|
13307
|
+
if (redirected) {
|
|
13308
|
+
try {
|
|
13309
|
+
const reqHost = new URL(url).hostname;
|
|
13310
|
+
const resHost = new URL(current_url).hostname;
|
|
13311
|
+
const reqPath = new URL(url).pathname;
|
|
13312
|
+
const resPath = new URL(current_url).pathname;
|
|
13313
|
+
if (reqHost !== resHost)
|
|
13314
|
+
redirect_type = "canonical";
|
|
13315
|
+
else if (resPath.match(/\/[a-z]{2}-[a-z]{2}\//))
|
|
13316
|
+
redirect_type = "geo";
|
|
13317
|
+
else if (current_url.includes("login") || current_url.includes("signin"))
|
|
13318
|
+
redirect_type = "auth";
|
|
13319
|
+
else
|
|
13320
|
+
redirect_type = "unknown";
|
|
13321
|
+
} catch {}
|
|
13322
|
+
}
|
|
13323
|
+
try {
|
|
13324
|
+
const session = getSession2(session_id);
|
|
13325
|
+
if (!session.name) {
|
|
13326
|
+
const hostname = new URL(current_url).hostname;
|
|
13327
|
+
renameSession2(session_id, hostname);
|
|
13328
|
+
}
|
|
13329
|
+
} catch {}
|
|
13330
|
+
const result = {
|
|
13331
|
+
url,
|
|
13332
|
+
title,
|
|
13333
|
+
current_url,
|
|
13334
|
+
redirected,
|
|
13335
|
+
...redirect_type ? { redirect_type } : {}
|
|
13336
|
+
};
|
|
13224
13337
|
if (auto_thumbnail) {
|
|
13225
13338
|
try {
|
|
13226
13339
|
const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
|
|
@@ -13230,7 +13343,9 @@ server.tool("browser_navigate", "Navigate to a URL. Returns title + thumbnail +
|
|
|
13230
13343
|
if (auto_snapshot) {
|
|
13231
13344
|
try {
|
|
13232
13345
|
const snap = await takeSnapshot(page, session_id);
|
|
13233
|
-
|
|
13346
|
+
setLastSnapshot(session_id, snap);
|
|
13347
|
+
const refEntries = Object.entries(snap.refs).slice(0, 30);
|
|
13348
|
+
result.snapshot_refs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 50)} [${ref}]`).join(", ");
|
|
13234
13349
|
result.interactive_count = snap.interactive_count;
|
|
13235
13350
|
result.has_errors = getConsoleLog(session_id, "error").length > 0;
|
|
13236
13351
|
} catch {}
|
|
@@ -13336,7 +13451,7 @@ server.tool("browser_select", "Select a dropdown option by ref or selector", { s
|
|
|
13336
13451
|
return err(e);
|
|
13337
13452
|
}
|
|
13338
13453
|
});
|
|
13339
|
-
server.tool("
|
|
13454
|
+
server.tool("browser_toggle", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
|
|
13340
13455
|
try {
|
|
13341
13456
|
const page = getSessionPage(session_id);
|
|
13342
13457
|
if (ref) {
|
|
@@ -13427,12 +13542,33 @@ server.tool("browser_find", "Find elements matching a selector and return their
|
|
|
13427
13542
|
return err(e);
|
|
13428
13543
|
}
|
|
13429
13544
|
});
|
|
13430
|
-
server.tool("browser_snapshot", "Get
|
|
13545
|
+
server.tool("browser_snapshot", "Get accessibility snapshot with element refs (@e0, @e1...). Use compact=true (default) for token-efficient output. Use refs in browser_click, browser_type, etc.", {
|
|
13546
|
+
session_id: exports_external.string(),
|
|
13547
|
+
compact: exports_external.boolean().optional().default(true),
|
|
13548
|
+
max_refs: exports_external.number().optional().default(50),
|
|
13549
|
+
full_tree: exports_external.boolean().optional().default(false)
|
|
13550
|
+
}, async ({ session_id, compact, max_refs, full_tree }) => {
|
|
13431
13551
|
try {
|
|
13432
13552
|
const page = getSessionPage(session_id);
|
|
13433
13553
|
const result = await takeSnapshot(page, session_id);
|
|
13434
13554
|
setLastSnapshot(session_id, result);
|
|
13435
|
-
|
|
13555
|
+
const refEntries = Object.entries(result.refs).slice(0, max_refs);
|
|
13556
|
+
const limitedRefs = Object.fromEntries(refEntries);
|
|
13557
|
+
const truncated = Object.keys(result.refs).length > max_refs;
|
|
13558
|
+
if (compact && !full_tree) {
|
|
13559
|
+
const compactRefs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 60)} [${ref}]${info.checked !== undefined ? ` checked=${info.checked}` : ""}${!info.enabled ? " disabled" : ""}`).join(`
|
|
13560
|
+
`);
|
|
13561
|
+
return json({
|
|
13562
|
+
snapshot_compact: compactRefs,
|
|
13563
|
+
interactive_count: result.interactive_count,
|
|
13564
|
+
shown_count: refEntries.length,
|
|
13565
|
+
truncated,
|
|
13566
|
+
refs: limitedRefs
|
|
13567
|
+
});
|
|
13568
|
+
}
|
|
13569
|
+
const tree = full_tree ? result.tree : result.tree.slice(0, 5000) + (result.tree.length > 5000 ? `
|
|
13570
|
+
... (truncated \u2014 use full_tree=true for complete)` : "");
|
|
13571
|
+
return json({ snapshot: tree, refs: limitedRefs, interactive_count: result.interactive_count, truncated });
|
|
13436
13572
|
} catch (e) {
|
|
13437
13573
|
return err(e);
|
|
13438
13574
|
}
|
|
@@ -13914,40 +14050,6 @@ server.tool("browser_watch_stop", "Stop a DOM change watcher", { watch_id: expor
|
|
|
13914
14050
|
return err(e);
|
|
13915
14051
|
}
|
|
13916
14052
|
});
|
|
13917
|
-
server.tool("browser_page_check", "One-call page summary: page info + console errors + performance metrics + thumbnail + accessibility snapshot preview. Replaces 4-5 separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
13918
|
-
try {
|
|
13919
|
-
const page = getSessionPage(session_id);
|
|
13920
|
-
const info = await getPageInfo(page);
|
|
13921
|
-
const errors2 = getConsoleLog(session_id, "error");
|
|
13922
|
-
info.has_console_errors = errors2.length > 0;
|
|
13923
|
-
let perf = {};
|
|
13924
|
-
try {
|
|
13925
|
-
perf = await getPerformanceMetrics(page);
|
|
13926
|
-
} catch {}
|
|
13927
|
-
let thumbnail_base64 = "";
|
|
13928
|
-
try {
|
|
13929
|
-
const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
|
|
13930
|
-
thumbnail_base64 = ss.base64;
|
|
13931
|
-
} catch {}
|
|
13932
|
-
let snapshot_preview = "";
|
|
13933
|
-
let interactive_count = 0;
|
|
13934
|
-
try {
|
|
13935
|
-
const snap = await takeSnapshot(page, session_id);
|
|
13936
|
-
snapshot_preview = snap.tree.slice(0, 2000);
|
|
13937
|
-
interactive_count = snap.interactive_count;
|
|
13938
|
-
} catch {}
|
|
13939
|
-
return json({
|
|
13940
|
-
...info,
|
|
13941
|
-
error_count: errors2.length,
|
|
13942
|
-
performance: perf,
|
|
13943
|
-
thumbnail_base64: thumbnail_base64.length > 50000 ? "" : thumbnail_base64,
|
|
13944
|
-
snapshot_preview,
|
|
13945
|
-
interactive_count
|
|
13946
|
-
});
|
|
13947
|
-
} catch (e) {
|
|
13948
|
-
return err(e);
|
|
13949
|
-
}
|
|
13950
|
-
});
|
|
13951
14053
|
server.tool("browser_gallery_list", "List screenshot gallery entries with optional filters", {
|
|
13952
14054
|
project_id: exports_external.string().optional(),
|
|
13953
14055
|
session_id: exports_external.string().optional(),
|
|
@@ -14272,7 +14374,7 @@ server.tool("browser_help", "Show all available browser tools grouped by categor
|
|
|
14272
14374
|
{ tool: "browser_hover", description: "Hover over an element" },
|
|
14273
14375
|
{ tool: "browser_scroll", description: "Scroll the page" },
|
|
14274
14376
|
{ tool: "browser_select", description: "Select a dropdown option" },
|
|
14275
|
-
{ tool: "
|
|
14377
|
+
{ tool: "browser_toggle", description: "Check/uncheck a checkbox" },
|
|
14276
14378
|
{ tool: "browser_upload", description: "Upload a file to an input" },
|
|
14277
14379
|
{ tool: "browser_press_key", description: "Press a keyboard key" },
|
|
14278
14380
|
{ tool: "browser_wait", description: "Wait for a selector to appear" },
|
|
@@ -14292,9 +14394,10 @@ server.tool("browser_help", "Show all available browser tools grouped by categor
|
|
|
14292
14394
|
{ tool: "browser_evaluate", description: "Execute JavaScript in page context" }
|
|
14293
14395
|
],
|
|
14294
14396
|
Capture: [
|
|
14295
|
-
{ tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP)" },
|
|
14397
|
+
{ tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP, annotate=true for labels)" },
|
|
14296
14398
|
{ tool: "browser_pdf", description: "Generate a PDF of the page" },
|
|
14297
|
-
{ tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" }
|
|
14399
|
+
{ tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" },
|
|
14400
|
+
{ tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" }
|
|
14298
14401
|
],
|
|
14299
14402
|
Storage: [
|
|
14300
14403
|
{ tool: "browser_cookies_get", description: "Get cookies" },
|
|
@@ -14373,7 +14476,8 @@ server.tool("browser_help", "Show all available browser tools grouped by categor
|
|
|
14373
14476
|
{ tool: "browser_tab_close", description: "Close a tab by index" }
|
|
14374
14477
|
],
|
|
14375
14478
|
Meta: [
|
|
14376
|
-
{ tool: "
|
|
14479
|
+
{ tool: "browser_check", description: "RECOMMENDED: One-call page summary with diagnostics" },
|
|
14480
|
+
{ tool: "browser_version", description: "Show running binary version and tool count" },
|
|
14377
14481
|
{ tool: "browser_help", description: "Show this help (all tools)" },
|
|
14378
14482
|
{ tool: "browser_snapshot_diff", description: "Diff current snapshot vs previous" },
|
|
14379
14483
|
{ tool: "browser_watch_start", description: "Watch page for DOM changes" },
|
|
@@ -14387,5 +14491,87 @@ server.tool("browser_help", "Show all available browser tools grouped by categor
|
|
|
14387
14491
|
return err(e);
|
|
14388
14492
|
}
|
|
14389
14493
|
});
|
|
14494
|
+
server.tool("browser_version", "Get the running browser MCP version, tool count, and environment info. Use this to verify which binary is active.", {}, async () => {
|
|
14495
|
+
try {
|
|
14496
|
+
const { getDataDir: getDataDir4 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
14497
|
+
const toolCount = Object.keys(server._registeredTools ?? {}).length;
|
|
14498
|
+
return json({
|
|
14499
|
+
version: _pkg.version,
|
|
14500
|
+
mcp_tools_count: toolCount,
|
|
14501
|
+
bun_version: Bun.version,
|
|
14502
|
+
data_dir: getDataDir4(),
|
|
14503
|
+
node_env: process.env["NODE_ENV"] ?? "production"
|
|
14504
|
+
});
|
|
14505
|
+
} catch (e) {
|
|
14506
|
+
return err(e);
|
|
14507
|
+
}
|
|
14508
|
+
});
|
|
14509
|
+
server.tool("browser_scroll_to_element", "Scroll an element into view (by ref or selector) then optionally take a screenshot of it. Replaces scroll + wait + screenshot pattern.", {
|
|
14510
|
+
session_id: exports_external.string(),
|
|
14511
|
+
selector: exports_external.string().optional(),
|
|
14512
|
+
ref: exports_external.string().optional(),
|
|
14513
|
+
screenshot: exports_external.boolean().optional().default(true),
|
|
14514
|
+
wait_ms: exports_external.number().optional().default(200)
|
|
14515
|
+
}, async ({ session_id, selector, ref, screenshot: doScreenshot, wait_ms }) => {
|
|
14516
|
+
try {
|
|
14517
|
+
const page = getSessionPage(session_id);
|
|
14518
|
+
let locator;
|
|
14519
|
+
if (ref) {
|
|
14520
|
+
const { getRefLocator: getRefLocator2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
|
|
14521
|
+
locator = getRefLocator2(page, session_id, ref);
|
|
14522
|
+
} else if (selector) {
|
|
14523
|
+
locator = page.locator(selector).first();
|
|
14524
|
+
} else {
|
|
14525
|
+
return err(new Error("Either ref or selector is required"));
|
|
14526
|
+
}
|
|
14527
|
+
await locator.scrollIntoViewIfNeeded();
|
|
14528
|
+
await new Promise((r) => setTimeout(r, wait_ms));
|
|
14529
|
+
const result = { scrolled: ref ?? selector };
|
|
14530
|
+
if (doScreenshot) {
|
|
14531
|
+
try {
|
|
14532
|
+
const ss = await takeScreenshot(page, { selector, track: false });
|
|
14533
|
+
ss.url = page.url();
|
|
14534
|
+
if (ss.base64.length > 50000) {
|
|
14535
|
+
ss.base64_truncated = true;
|
|
14536
|
+
ss.base64 = ss.thumbnail_base64 ?? "";
|
|
14537
|
+
}
|
|
14538
|
+
result.screenshot = ss;
|
|
14539
|
+
} catch {}
|
|
14540
|
+
}
|
|
14541
|
+
return json(result);
|
|
14542
|
+
} catch (e) {
|
|
14543
|
+
return err(e);
|
|
14544
|
+
}
|
|
14545
|
+
});
|
|
14546
|
+
server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u2014 url, title, errors, performance, thumbnail, refs. Replaces 4+ separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
14547
|
+
try {
|
|
14548
|
+
const page = getSessionPage(session_id);
|
|
14549
|
+
const info = await getPageInfo(page);
|
|
14550
|
+
const errors2 = getConsoleLog(session_id, "error");
|
|
14551
|
+
info.has_console_errors = errors2.length > 0;
|
|
14552
|
+
let perf = {};
|
|
14553
|
+
try {
|
|
14554
|
+
perf = await getPerformanceMetrics(page);
|
|
14555
|
+
} catch {}
|
|
14556
|
+
let thumbnail_base64 = "";
|
|
14557
|
+
try {
|
|
14558
|
+
const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
|
|
14559
|
+
thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
|
|
14560
|
+
} catch {}
|
|
14561
|
+
let snapshot_refs = "";
|
|
14562
|
+
let interactive_count = 0;
|
|
14563
|
+
try {
|
|
14564
|
+
const snap = await takeSnapshot(page, session_id);
|
|
14565
|
+
setLastSnapshot(session_id, snap);
|
|
14566
|
+
interactive_count = snap.interactive_count;
|
|
14567
|
+
snapshot_refs = Object.entries(snap.refs).slice(0, 30).map(([ref, i]) => `${i.role}:${i.name.slice(0, 50)} [${ref}]`).join(", ");
|
|
14568
|
+
} catch {}
|
|
14569
|
+
return json({ ...info, error_count: errors2.length, performance: perf, thumbnail_base64, snapshot_refs, interactive_count });
|
|
14570
|
+
} catch (e) {
|
|
14571
|
+
return err(e);
|
|
14572
|
+
}
|
|
14573
|
+
});
|
|
14574
|
+
var _startupToolCount = Object.keys(server._registeredTools ?? {}).length;
|
|
14575
|
+
console.error(`@hasna/browser v${_pkg.version} \u2014 ${_startupToolCount} tools | data: ${(await Promise.resolve().then(() => (init_schema(), exports_schema))).getDataDir()}`);
|
|
14390
14576
|
var transport = new StdioServerTransport;
|
|
14391
14577
|
await server.connect(transport);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v4.test.d.ts","sourceRoot":"","sources":["../../src/mcp/v4.test.ts"],"names":[],"mappings":""}
|
package/dist/server/index.js
CHANGED
|
@@ -6881,7 +6881,14 @@ import { randomUUID } from "crypto";
|
|
|
6881
6881
|
function createSession(data) {
|
|
6882
6882
|
const db = getDatabase();
|
|
6883
6883
|
const id = randomUUID();
|
|
6884
|
-
|
|
6884
|
+
let name = data.name ?? null;
|
|
6885
|
+
if (name) {
|
|
6886
|
+
const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
|
|
6887
|
+
if (existing) {
|
|
6888
|
+
name = `${name}-${id.slice(0, 6)}`;
|
|
6889
|
+
}
|
|
6890
|
+
}
|
|
6891
|
+
db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
|
|
6885
6892
|
return getSession(id);
|
|
6886
6893
|
}
|
|
6887
6894
|
function getSession(id) {
|
|
@@ -7361,12 +7368,19 @@ async function createSession2(opts = {}) {
|
|
|
7361
7368
|
userAgent: opts.userAgent
|
|
7362
7369
|
});
|
|
7363
7370
|
}
|
|
7371
|
+
let sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
7372
|
+
try {
|
|
7373
|
+
return new URL(opts.startUrl).hostname;
|
|
7374
|
+
} catch {
|
|
7375
|
+
return;
|
|
7376
|
+
}
|
|
7377
|
+
})() : undefined);
|
|
7364
7378
|
const session = createSession({
|
|
7365
7379
|
engine: resolvedEngine,
|
|
7366
7380
|
projectId: opts.projectId,
|
|
7367
7381
|
agentId: opts.agentId,
|
|
7368
7382
|
startUrl: opts.startUrl,
|
|
7369
|
-
name:
|
|
7383
|
+
name: sessionName
|
|
7370
7384
|
});
|
|
7371
7385
|
if (opts.stealth) {
|
|
7372
7386
|
try {
|
|
@@ -7744,11 +7758,20 @@ async function takeScreenshot(page, opts) {
|
|
|
7744
7758
|
}
|
|
7745
7759
|
const originalSizeBytes = rawBuffer.length;
|
|
7746
7760
|
let finalBuffer;
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7761
|
+
let compressed = true;
|
|
7762
|
+
let fallback = false;
|
|
7763
|
+
try {
|
|
7764
|
+
if (compress && format !== "png") {
|
|
7765
|
+
finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 82, maxWidth);
|
|
7766
|
+
} else if (compress && format === "png") {
|
|
7767
|
+
finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
|
|
7768
|
+
} else {
|
|
7769
|
+
finalBuffer = rawBuffer;
|
|
7770
|
+
compressed = false;
|
|
7771
|
+
}
|
|
7772
|
+
} catch (sharpErr) {
|
|
7773
|
+
fallback = true;
|
|
7774
|
+
compressed = false;
|
|
7752
7775
|
finalBuffer = rawBuffer;
|
|
7753
7776
|
}
|
|
7754
7777
|
const compressedSizeBytes = finalBuffer.length;
|
|
@@ -7776,7 +7799,8 @@ async function takeScreenshot(page, opts) {
|
|
|
7776
7799
|
compressed_size_bytes: compressedSizeBytes,
|
|
7777
7800
|
compression_ratio: compressionRatio,
|
|
7778
7801
|
thumbnail_path: thumbnailPath,
|
|
7779
|
-
thumbnail_base64: thumbnailBase64
|
|
7802
|
+
thumbnail_base64: thumbnailBase64,
|
|
7803
|
+
...fallback ? { fallback: true, compressed: false } : {}
|
|
7780
7804
|
};
|
|
7781
7805
|
if (opts?.track !== false) {
|
|
7782
7806
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/browser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "General-purpose browser agent toolkit — Playwright, Chrome DevTools Protocol, Lightpanda with auto engine selection. CLI + MCP + REST + SDK.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|