leapfrog-mcp 0.6.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +48 -2
- package/dist/tiles-coordinator.d.ts +6 -0
- package/dist/tiles-coordinator.js +17 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,6 +62,47 @@ const LEAP_HUD = process.env.LEAP_HUD === "true";
|
|
|
62
62
|
const LEAP_AUTO_CONSENT = process.env.LEAP_AUTO_CONSENT !== "false"; // default ON
|
|
63
63
|
const LEAP_TRACE = process.env.LEAP_TRACE === "true";
|
|
64
64
|
const LEAP_RECORD = process.env.LEAP_RECORD === "true";
|
|
65
|
+
const LEAP_AD_BLOCK = process.env.LEAP_AD_BLOCK !== "false"; // default ON
|
|
66
|
+
// ── Ad/Tracker Blocking ──────────────────────────────────────────────────
|
|
67
|
+
const AD_BLOCK_DOMAINS = new Set([
|
|
68
|
+
"doubleclick.net", "googlesyndication.com", "googleadservices.com",
|
|
69
|
+
"google-analytics.com", "googletagmanager.com", "googletagservices.com",
|
|
70
|
+
"adservice.google.com", "pagead2.googlesyndication.com",
|
|
71
|
+
"facebook.net", "connect.facebook.net", "fbcdn.net",
|
|
72
|
+
"amazon-adsystem.com", "ads-api.twitter.com",
|
|
73
|
+
"ads.yahoo.com", "analytics.yahoo.com",
|
|
74
|
+
"scorecardresearch.com", "quantserve.com", "outbrain.com",
|
|
75
|
+
"taboola.com", "criteo.com", "criteo.net",
|
|
76
|
+
"moatads.com", "adsrvr.org", "adnxs.com", "rubiconproject.com",
|
|
77
|
+
"pubmatic.com", "openx.net", "casalemedia.com",
|
|
78
|
+
"chartbeat.com", "hotjar.com", "mixpanel.com", "segment.io",
|
|
79
|
+
"newrelic.com", "nr-data.net", "optimizely.com",
|
|
80
|
+
"demdex.net", "omtrdc.net", "2o7.net",
|
|
81
|
+
"tealiumiq.com", "tags.tiqcdn.com",
|
|
82
|
+
]);
|
|
83
|
+
function shouldBlockUrl(url) {
|
|
84
|
+
try {
|
|
85
|
+
const hostname = new URL(url).hostname;
|
|
86
|
+
for (const domain of AD_BLOCK_DOMAINS) {
|
|
87
|
+
if (hostname === domain || hostname.endsWith("." + domain))
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function attachAdBlocker(context) {
|
|
95
|
+
if (!LEAP_AD_BLOCK)
|
|
96
|
+
return;
|
|
97
|
+
context.route("**/*", (route) => {
|
|
98
|
+
if (shouldBlockUrl(route.request().url())) {
|
|
99
|
+
route.abort("blockedbyclient").catch(() => { });
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
route.fallback().catch(() => { });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
65
106
|
const sessions = new SessionManager({
|
|
66
107
|
maxSessions: MAX_SESSIONS,
|
|
67
108
|
idleTimeoutMs: IDLE_TIMEOUT_MS,
|
|
@@ -88,6 +129,9 @@ if (LEAP_TILE && LEAP_TILE !== "false") {
|
|
|
88
129
|
const defaultW = LEAP_SCREEN_WIDTH > 0 ? LEAP_SCREEN_WIDTH : detectedScreen?.width ?? 1920;
|
|
89
130
|
const defaultH = LEAP_SCREEN_HEIGHT > 0 ? LEAP_SCREEN_HEIGHT : detectedScreen?.height ?? 1080;
|
|
90
131
|
tilesCoord = new TilesCoordinator(defaultW, defaultH);
|
|
132
|
+
// Purge ALL slots not owned by this process — handles zombie PIDs
|
|
133
|
+
// from /mcp reconnects where old node process lingers alive.
|
|
134
|
+
tilesCoord.purgeOtherPids().catch(() => { });
|
|
91
135
|
// File watcher only needed for multi-terminal mode (multiple Leapfrog instances).
|
|
92
136
|
// In single-instance mode, the watcher causes spurious reflows that fight
|
|
93
137
|
// with external monitor positioning. Only enable when explicitly requested.
|
|
@@ -325,6 +369,7 @@ server.registerTool("session_create", {
|
|
|
325
369
|
}
|
|
326
370
|
// Always inject intervention detection (lightweight MutationObserver)
|
|
327
371
|
await session.context.addInitScript(getDetectionInitScript());
|
|
372
|
+
attachAdBlocker(session.context);
|
|
328
373
|
// Start tracing if enabled
|
|
329
374
|
if (LEAP_TRACE) {
|
|
330
375
|
await session.context.tracing.start({ screenshots: true, snapshots: true });
|
|
@@ -382,6 +427,7 @@ server.registerTool("session_create_batch", {
|
|
|
382
427
|
if (LEAP_AUTO_CONSENT)
|
|
383
428
|
await session.context.addInitScript(getConsentDismissScript());
|
|
384
429
|
await session.context.addInitScript(getDetectionInitScript());
|
|
430
|
+
attachAdBlocker(session.context);
|
|
385
431
|
if (LEAP_TRACE)
|
|
386
432
|
await session.context.tracing.start({ screenshots: true, snapshots: true });
|
|
387
433
|
// Claim tile slot (without triggering reflow yet — watcher handles it)
|
|
@@ -418,7 +464,7 @@ server.registerTool("session_create_batch", {
|
|
|
418
464
|
return;
|
|
419
465
|
}
|
|
420
466
|
await session.page.goto(spec.url, {
|
|
421
|
-
waitUntil: spec.waitUntil || "
|
|
467
|
+
waitUntil: spec.waitUntil || "domcontentloaded",
|
|
422
468
|
timeout: 30000,
|
|
423
469
|
});
|
|
424
470
|
results.push({ id: session.id, url: spec.url });
|
|
@@ -627,7 +673,7 @@ server.registerTool("navigate", {
|
|
|
627
673
|
url: z.string().describe("Full URL including https://"),
|
|
628
674
|
waitUntil: z
|
|
629
675
|
.enum(["load", "domcontentloaded", "networkidle"])
|
|
630
|
-
.default("
|
|
676
|
+
.default("domcontentloaded")
|
|
631
677
|
.describe("Wait strategy. Use networkidle for SPAs."),
|
|
632
678
|
autoRetry: z
|
|
633
679
|
.boolean()
|
|
@@ -87,6 +87,12 @@ export declare class TilesCoordinator {
|
|
|
87
87
|
* @returns Session IDs of the reaped (dead) slots.
|
|
88
88
|
*/
|
|
89
89
|
reapDeadSlots(): Promise<string[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Purge all slots NOT owned by the current process.pid.
|
|
92
|
+
* Called on startup to clear ghost slots from previous instances
|
|
93
|
+
* that may still be alive (e.g., after /mcp reconnect).
|
|
94
|
+
*/
|
|
95
|
+
purgeOtherPids(): Promise<number>;
|
|
90
96
|
/**
|
|
91
97
|
* Watch `tiles.json` for changes made by other instances.
|
|
92
98
|
* The callback fires with the new state whenever the file changes.
|
|
@@ -283,6 +283,23 @@ export class TilesCoordinator {
|
|
|
283
283
|
return deadIds;
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Purge all slots NOT owned by the current process.pid.
|
|
288
|
+
* Called on startup to clear ghost slots from previous instances
|
|
289
|
+
* that may still be alive (e.g., after /mcp reconnect).
|
|
290
|
+
*/
|
|
291
|
+
async purgeOtherPids() {
|
|
292
|
+
return withLock(async () => {
|
|
293
|
+
const state = readState(this.screenWidth, this.screenHeight);
|
|
294
|
+
const before = state.slots.length;
|
|
295
|
+
state.slots = state.slots.filter((s) => s.instancePid === process.pid);
|
|
296
|
+
if (state.slots.length !== before) {
|
|
297
|
+
recalculatePositions(state);
|
|
298
|
+
writeState(state);
|
|
299
|
+
}
|
|
300
|
+
return before - state.slots.length;
|
|
301
|
+
});
|
|
302
|
+
}
|
|
286
303
|
/**
|
|
287
304
|
* Watch `tiles.json` for changes made by other instances.
|
|
288
305
|
* The callback fires with the new state whenever the file changes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leapfrog-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Multi-session browser MCP for AI agents — 36 tools, stealth, persistent auth, code-first scripts, API sniffer, agent intelligence",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|