mcp-scraper 0.3.6 → 0.3.7

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.
Files changed (44) hide show
  1. package/README.md +18 -26
  2. package/dist/bin/api-server.cjs +158 -321
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +1 -1
  5. package/dist/bin/browser-agent-stdio-server.cjs +39 -1750
  6. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
  7. package/dist/bin/browser-agent-stdio-server.js +2 -4
  8. package/dist/bin/browser-agent-stdio-server.js.map +1 -1
  9. package/dist/bin/mcp-scraper-cli.cjs +47 -328
  10. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  11. package/dist/bin/mcp-scraper-cli.js +7 -91
  12. package/dist/bin/mcp-scraper-cli.js.map +1 -1
  13. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +78 -1796
  14. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
  15. package/dist/bin/mcp-scraper-combined-stdio-server.js +4 -6
  16. package/dist/bin/mcp-scraper-combined-stdio-server.js.map +1 -1
  17. package/dist/bin/mcp-scraper-install.cjs +4 -4
  18. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  19. package/dist/bin/mcp-scraper-install.js +2 -2
  20. package/dist/bin/mcp-stdio-server.cjs +18 -25
  21. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  22. package/dist/bin/mcp-stdio-server.js +2 -2
  23. package/dist/chunk-2NEVRPK3.js +7 -0
  24. package/dist/chunk-2NEVRPK3.js.map +1 -0
  25. package/dist/{chunk-ROZEDHAA.js → chunk-2O7RAK6E.js} +19 -26
  26. package/dist/chunk-2O7RAK6E.js.map +1 -0
  27. package/dist/{chunk-RVYHP7QN.js → chunk-ELI6NDEG.js} +4 -4
  28. package/dist/{chunk-RVYHP7QN.js.map → chunk-ELI6NDEG.js.map} +1 -1
  29. package/dist/{chunk-TK4D2BZ5.js → chunk-IWNLHCKA.js} +13 -694
  30. package/dist/chunk-IWNLHCKA.js.map +1 -0
  31. package/dist/{server-YSKBD2VX.js → server-HRVD52CX.js} +836 -114
  32. package/dist/server-HRVD52CX.js.map +1 -0
  33. package/docs/mcp-tool-craft-lint.generated.md +2 -5
  34. package/docs/mcp-tool-manifest.generated.json +9 -101
  35. package/package.json +1 -1
  36. package/dist/chunk-DUEW4EOO.js +0 -214
  37. package/dist/chunk-DUEW4EOO.js.map +0 -1
  38. package/dist/chunk-IHXHQL53.js +0 -7
  39. package/dist/chunk-IHXHQL53.js.map +0 -1
  40. package/dist/chunk-NXRWFOEZ.js +0 -816
  41. package/dist/chunk-NXRWFOEZ.js.map +0 -1
  42. package/dist/chunk-ROZEDHAA.js.map +0 -1
  43. package/dist/chunk-TK4D2BZ5.js.map +0 -1
  44. package/dist/server-YSKBD2VX.js.map +0 -1
@@ -1,21 +1,10 @@
1
- import {
2
- runFanoutCapture
3
- } from "./chunk-NXRWFOEZ.js";
4
1
  import {
5
2
  browserServiceProfileName,
6
3
  browserServiceProfileSaveChanges
7
4
  } from "./chunk-WN7PBKMV.js";
8
- import {
9
- defaultChromeExecutablePath,
10
- importChromeProfile,
11
- listLocalChromeProfiles,
12
- loadImportedBrowserProfile,
13
- localBrowserModeEnabled,
14
- syncImportedChromeProfile
15
- } from "./chunk-DUEW4EOO.js";
16
5
  import {
17
6
  PACKAGE_VERSION
18
- } from "./chunk-IHXHQL53.js";
7
+ } from "./chunk-2NEVRPK3.js";
19
8
 
20
9
  // src/mcp/browser-agent-mcp-server.ts
21
10
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -23,374 +12,6 @@ import { mkdirSync, writeFileSync } from "fs";
23
12
  import { homedir } from "os";
24
13
  import { join as join2 } from "path";
25
14
 
26
- // src/services/browser-agent/local-browser-agent-service.ts
27
- import { randomUUID } from "crypto";
28
- import { existsSync } from "fs";
29
- import { basename } from "path";
30
- import { chromium } from "playwright-extra";
31
- import StealthPlugin from "puppeteer-extra-plugin-stealth";
32
- chromium.use(StealthPlugin());
33
- function directProfileDir() {
34
- return process.env.MCP_SCRAPER_BROWSER_PROFILE_DIR?.trim() || void 0;
35
- }
36
- function configuredProfileName(profile) {
37
- return profile?.trim() || process.env.MCP_SCRAPER_BROWSER_PROFILE?.trim() || (directProfileDir() ? basename(directProfileDir()) : "default");
38
- }
39
- function launchExecutablePath(profile) {
40
- const configured = process.env.MCP_SCRAPER_BROWSER_EXECUTABLE?.trim() || profile.browserExecutablePath || defaultChromeExecutablePath();
41
- return configured && existsSync(configured) ? configured : void 0;
42
- }
43
- function ignoreDefaultArgsFor(executablePath) {
44
- if (!executablePath) return void 0;
45
- if (process.platform !== "darwin") return void 0;
46
- if (!/Google Chrome\.app\/Contents\/MacOS\/Google Chrome$/.test(executablePath)) return void 0;
47
- return ["--password-store=basic", "--use-mock-keychain"];
48
- }
49
- function normalizeKeyCombo(value) {
50
- return value.split("+").map((part) => {
51
- const trimmed = part.trim();
52
- const lower = trimmed.toLowerCase();
53
- if (lower === "ctrl" || lower === "control") return "Control";
54
- if (lower === "cmd" || lower === "command" || lower === "meta") return "Meta";
55
- if (lower === "option" || lower === "alt") return "Alt";
56
- if (lower === "return") return "Enter";
57
- if (lower === "esc") return "Escape";
58
- if (trimmed.length === 1) return trimmed.toUpperCase();
59
- return trimmed;
60
- }).join("+");
61
- }
62
- function sessionSummary(session) {
63
- return {
64
- session_id: session.sessionId,
65
- status: session.status,
66
- label: session.label,
67
- profile: session.profileName,
68
- user_data_dir: session.userDataDir,
69
- created_at: session.createdAt,
70
- last_action_at: session.lastActionAt,
71
- closed_at: session.closedAt,
72
- watch_url: `local://browser-session/${session.sessionId}`,
73
- url: session.status === "open" ? session.page.url() : null
74
- };
75
- }
76
- var LocalBrowserAgentService = class {
77
- contexts = /* @__PURE__ */ new Map();
78
- sessions = /* @__PURE__ */ new Map();
79
- async open(input) {
80
- const profile = await this.resolveProfile(input.profile);
81
- const contextState = await this.ensureContext(profile);
82
- const pages = contextState.context.pages();
83
- const page = pages.length === 1 && !this.hasOpenSessionForContext(contextState.key) ? pages[0] : await contextState.context.newPage();
84
- const now = (/* @__PURE__ */ new Date()).toISOString();
85
- const sessionId = `lbs_${randomUUID().replace(/-/g, "").slice(0, 20)}`;
86
- const session = {
87
- sessionId,
88
- label: input.label ?? null,
89
- page,
90
- contextKey: contextState.key,
91
- profileName: profile.name,
92
- userDataDir: profile.userDataDir,
93
- createdAt: now,
94
- lastActionAt: now,
95
- closedAt: null,
96
- status: "open"
97
- };
98
- const timeoutSeconds = input.timeout_seconds ?? 600;
99
- if (timeoutSeconds > 0) {
100
- session.timeout = setTimeout(() => {
101
- void this.close(sessionId);
102
- }, timeoutSeconds * 1e3);
103
- session.timeout.unref();
104
- }
105
- this.sessions.set(sessionId, session);
106
- if (input.url) await this.goto(sessionId, input.url);
107
- return {
108
- session_id: sessionId,
109
- watch_url: `local://browser-session/${sessionId}`,
110
- live_view_url: null,
111
- profile: profile.name,
112
- user_data_dir: profile.userDataDir,
113
- url: input.url ?? null,
114
- mode: "local"
115
- };
116
- }
117
- async screenshot(sessionId) {
118
- const session = this.requireOpenSession(sessionId);
119
- await this.waitForPage(session.page);
120
- const image = await session.page.screenshot({ type: "png", fullPage: false });
121
- const page = await this.read(sessionId);
122
- return {
123
- ...page,
124
- image_base64: image.toString("base64"),
125
- mime_type: "image/png"
126
- };
127
- }
128
- async read(sessionId) {
129
- const session = this.requireOpenSession(sessionId);
130
- await this.waitForPage(session.page);
131
- this.touch(session);
132
- const title = await session.page.title().catch(() => "");
133
- const snapshot = await session.page.evaluate(() => {
134
- const cssEscape = (value) => {
135
- const escapeFn = globalThis.CSS?.escape;
136
- return escapeFn ? escapeFn(value) : value.replace(/["\\#.:>+~[\]()]/g, "\\$&");
137
- };
138
- const textFor = (element) => {
139
- const html = element;
140
- return (element.getAttribute("aria-label") || element.getAttribute("title") || html.placeholder || html.value || html.innerText || element.textContent || "").replace(/\s+/g, " ").trim();
141
- };
142
- const selectorFor = (element) => {
143
- const id = element.id;
144
- if (id) return `#${cssEscape(id)}`;
145
- const name = element.getAttribute("name");
146
- const tag = element.tagName.toLowerCase();
147
- if (name) return `${tag}[name="${name.replace(/"/g, '\\"')}"]`;
148
- const parts = [];
149
- let current = element;
150
- while (current && current !== document.body && parts.length < 4) {
151
- const currentTag = current.tagName.toLowerCase();
152
- const parent = current.parentElement;
153
- if (!parent) {
154
- parts.unshift(currentTag);
155
- break;
156
- }
157
- const children = Array.from(parent.children);
158
- const siblings = children.filter((child) => child.tagName === current.tagName);
159
- const index = siblings.indexOf(current) + 1;
160
- parts.unshift(siblings.length > 1 ? `${currentTag}:nth-of-type(${index})` : currentTag);
161
- current = parent;
162
- }
163
- return parts.join(" > ") || tag;
164
- };
165
- const candidates = Array.from(document.querySelectorAll(
166
- 'a, button, input, textarea, select, summary, [role], [contenteditable="true"], [tabindex]:not([tabindex="-1"])'
167
- ));
168
- const elements = candidates.map((element, index) => {
169
- const rect = element.getBoundingClientRect();
170
- const style = window.getComputedStyle(element);
171
- const visible = rect.width > 0 && rect.height > 0 && style.display !== "none" && style.visibility !== "hidden" && Number(style.opacity || "1") > 0;
172
- if (!visible) return null;
173
- const label = textFor(element);
174
- return {
175
- index,
176
- tag: element.tagName.toLowerCase(),
177
- role: element.getAttribute("role") || null,
178
- text: label.slice(0, 180),
179
- selector: selectorFor(element),
180
- x: Math.round(rect.left + rect.width / 2),
181
- y: Math.round(rect.top + rect.height / 2),
182
- left: Math.round(rect.left),
183
- top: Math.round(rect.top),
184
- width: Math.round(rect.width),
185
- height: Math.round(rect.height)
186
- };
187
- }).filter(Boolean).slice(0, 120);
188
- return {
189
- text: (document.body?.innerText || "").replace(/\n{3,}/g, "\n\n").slice(0, 3e4),
190
- elements,
191
- viewport: {
192
- width: window.innerWidth,
193
- height: window.innerHeight,
194
- devicePixelRatio: window.devicePixelRatio,
195
- scrollX: window.scrollX,
196
- scrollY: window.scrollY
197
- }
198
- };
199
- });
200
- return {
201
- url: session.page.url(),
202
- title,
203
- text: snapshot.text,
204
- elements: snapshot.elements,
205
- viewport: snapshot.viewport
206
- };
207
- }
208
- async locate(sessionId, targets) {
209
- const session = this.requireOpenSession(sessionId);
210
- this.touch(session);
211
- const located = [];
212
- for (const target of targets) {
213
- try {
214
- const locator = target.selector ? session.page.locator(target.selector) : session.page.getByText(target.text ?? "", { exact: target.match === "exact" });
215
- const selected = locator.nth(target.index ?? 0);
216
- await selected.waitFor({ state: "visible", timeout: 3e3 }).catch(() => void 0);
217
- const box = await selected.boundingBox();
218
- const text = await selected.textContent({ timeout: 1e3 }).catch(() => null);
219
- located.push({
220
- input: target,
221
- found: Boolean(box),
222
- element: box ? {
223
- left: Math.round(box.x),
224
- top: Math.round(box.y),
225
- width: Math.round(box.width),
226
- height: Math.round(box.height),
227
- x: Math.round(box.x + box.width / 2),
228
- y: Math.round(box.y + box.height / 2),
229
- text: (text ?? "").replace(/\s+/g, " ").trim().slice(0, 220)
230
- } : null
231
- });
232
- } catch (err) {
233
- located.push({
234
- input: target,
235
- found: false,
236
- element: null,
237
- error: err instanceof Error ? err.message : String(err)
238
- });
239
- }
240
- }
241
- const read = await this.read(sessionId);
242
- return {
243
- url: read.url,
244
- title: read.title,
245
- viewport: read.viewport,
246
- replay: null,
247
- targets: located
248
- };
249
- }
250
- async goto(sessionId, url) {
251
- const session = this.requireOpenSession(sessionId);
252
- this.touch(session);
253
- const response = await session.page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 }).catch((err) => {
254
- throw new Error(err instanceof Error ? err.message : String(err));
255
- });
256
- await this.waitForPage(session.page);
257
- return {
258
- ok: true,
259
- url: session.page.url(),
260
- status: response?.status() ?? null,
261
- mode: "local"
262
- };
263
- }
264
- async click(sessionId, input) {
265
- const session = this.requireOpenSession(sessionId);
266
- this.touch(session);
267
- await session.page.mouse.click(input.x, input.y, {
268
- button: input.button ?? "left",
269
- clickCount: input.num_clicks ?? 1
270
- });
271
- await this.waitForPage(session.page);
272
- return { ok: true, mode: "local", url: session.page.url() };
273
- }
274
- async type(sessionId, input) {
275
- const session = this.requireOpenSession(sessionId);
276
- this.touch(session);
277
- await session.page.keyboard.type(input.text, { delay: input.delay });
278
- return { ok: true, mode: "local", url: session.page.url() };
279
- }
280
- async scroll(sessionId, input) {
281
- const session = this.requireOpenSession(sessionId);
282
- this.touch(session);
283
- if (typeof input.x === "number" && typeof input.y === "number") {
284
- await session.page.mouse.move(input.x, input.y);
285
- }
286
- await session.page.mouse.wheel((input.delta_x ?? 0) * 120, (input.delta_y ?? 5) * 120);
287
- await session.page.waitForTimeout(150).catch(() => void 0);
288
- return { ok: true, mode: "local", url: session.page.url() };
289
- }
290
- async press(sessionId, input) {
291
- const session = this.requireOpenSession(sessionId);
292
- this.touch(session);
293
- for (const key of input.keys) {
294
- await session.page.keyboard.press(normalizeKeyCombo(key));
295
- }
296
- await this.waitForPage(session.page);
297
- return { ok: true, mode: "local", url: session.page.url() };
298
- }
299
- async close(sessionId) {
300
- const session = this.sessions.get(sessionId);
301
- if (!session || session.status === "closed") {
302
- return { ok: true, closed: true, already_closed: true, mode: "local" };
303
- }
304
- if (session.timeout) clearTimeout(session.timeout);
305
- session.status = "closed";
306
- session.closedAt = (/* @__PURE__ */ new Date()).toISOString();
307
- await session.page.close().catch(() => void 0);
308
- await this.closeContextIfUnused(session.contextKey);
309
- return { ok: true, closed: true, mode: "local" };
310
- }
311
- list(includeClosed = false) {
312
- return Array.from(this.sessions.values()).filter((session) => includeClosed || session.status === "open").map(sessionSummary);
313
- }
314
- async captureFanout(sessionId, input) {
315
- const session = this.requireOpenSession(sessionId);
316
- this.touch(session);
317
- return runFanoutCapture(session.page, input);
318
- }
319
- async screenshotOnly(sessionId) {
320
- const session = this.requireOpenSession(sessionId);
321
- await this.waitForPage(session.page);
322
- const img = await session.page.screenshot({ type: "png", fullPage: false });
323
- return img.toString("base64");
324
- }
325
- async resolveProfile(profile) {
326
- const directDir = directProfileDir();
327
- const requestedName = configuredProfileName(profile);
328
- if (directDir && (!profile || requestedName === process.env.MCP_SCRAPER_BROWSER_PROFILE?.trim())) {
329
- return {
330
- name: requestedName,
331
- userDataDir: directDir,
332
- browserExecutablePath: process.env.MCP_SCRAPER_BROWSER_EXECUTABLE?.trim() || defaultChromeExecutablePath() || null,
333
- manifest: null
334
- };
335
- }
336
- const manifest = await loadImportedBrowserProfile(requestedName);
337
- return {
338
- name: manifest.name,
339
- userDataDir: manifest.userDataDir,
340
- browserExecutablePath: manifest.browserExecutablePath,
341
- manifest
342
- };
343
- }
344
- async ensureContext(profile) {
345
- const existing = this.contexts.get(profile.userDataDir);
346
- if (existing) return existing;
347
- const executablePath = launchExecutablePath(profile);
348
- const context = await chromium.launchPersistentContext(profile.userDataDir, {
349
- headless: false,
350
- viewport: { width: 1440, height: 900 },
351
- acceptDownloads: true,
352
- ...executablePath ? { executablePath } : {},
353
- ...ignoreDefaultArgsFor(executablePath) ? { ignoreDefaultArgs: ignoreDefaultArgsFor(executablePath) } : {}
354
- });
355
- const state = { key: profile.userDataDir, context, profile };
356
- context.on("close", () => {
357
- this.contexts.delete(profile.userDataDir);
358
- const closedAt = (/* @__PURE__ */ new Date()).toISOString();
359
- for (const session of this.sessions.values()) {
360
- if (session.contextKey === profile.userDataDir && session.status === "open") {
361
- session.status = "closed";
362
- session.closedAt = closedAt;
363
- if (session.timeout) clearTimeout(session.timeout);
364
- }
365
- }
366
- });
367
- this.contexts.set(profile.userDataDir, state);
368
- return state;
369
- }
370
- requireOpenSession(sessionId) {
371
- const session = this.sessions.get(sessionId);
372
- if (!session || session.status !== "open") throw new Error(`Local browser session not found or closed: ${sessionId}`);
373
- return session;
374
- }
375
- touch(session) {
376
- session.lastActionAt = (/* @__PURE__ */ new Date()).toISOString();
377
- }
378
- hasOpenSessionForContext(contextKey) {
379
- return Array.from(this.sessions.values()).some((session) => session.contextKey === contextKey && session.status === "open");
380
- }
381
- async closeContextIfUnused(contextKey) {
382
- if (this.hasOpenSessionForContext(contextKey)) return;
383
- const state = this.contexts.get(contextKey);
384
- if (!state) return;
385
- this.contexts.delete(contextKey);
386
- await state.context.close().catch(() => void 0);
387
- }
388
- async waitForPage(page) {
389
- await page.waitForLoadState("domcontentloaded", { timeout: 5e3 }).catch(() => void 0);
390
- await page.waitForTimeout(200).catch(() => void 0);
391
- }
392
- };
393
-
394
15
  // src/mcp/browser-agent-tool-schemas.ts
395
16
  import { z } from "zod";
396
17
  var NullableString = z.string().nullable();
@@ -412,32 +33,13 @@ var BrowserOpenInputSchema = {
412
33
  save_profile_changes: z.boolean().optional().describe("Persist cookies and local storage back to the named profile when the session is closed. Use this for profile setup or intentional auth refreshes; avoid parallel sessions writing to the same profile."),
413
34
  timeout_seconds: z.number().int().min(60).max(259200).optional().describe("How long the direct no-proxy browser session may live before auto-termination. Defaults to 600. The browser idles into a zero-cost standby between actions, so a longer timeout is cheap.")
414
35
  };
415
- var BrowserProfileListInputSchema = {
416
- email: z.string().optional().describe("Optional Chrome account email or displayed profile name to filter for, for example seo@example.com."),
417
- user_data_dir: z.string().optional().describe("Optional Chrome user data directory. Defaults to the current OS Chrome profile root.")
418
- };
419
36
  var BrowserProfileOnboardInputSchema = {
420
37
  email: z.string().optional().describe("Optional account email used to derive a stable MCP Scraper profile name. This does not import cookies or browser state."),
421
38
  profile: z.string().optional().describe("Optional saved MCP Scraper hosted browser profile name to create or load. If omitted, email is used to derive one."),
422
- user_data_dir: z.string().optional().describe("Optional Chrome metadata directory for deriving a profile name only. This is not a local browser setup path."),
423
39
  url: z.string().url().optional().describe("Setup URL to open after creating the direct no-proxy browser. Defaults to https://google.com/ so the user can click Sign in."),
424
40
  label: z.string().optional().describe("Optional human label for this setup session."),
425
41
  timeout_seconds: z.number().int().min(60).max(259200).optional().describe("How long the direct no-proxy setup session may live before auto-termination. Defaults to 600.")
426
42
  };
427
- var BrowserProfileImportInputSchema = {
428
- email: z.string().optional().describe("Optional Chrome account email or displayed profile name to select from Chrome Local State."),
429
- name: z.string().optional().describe("Name for the managed MCP Scraper browser profile. Defaults to a slug derived from the selected Chrome account."),
430
- chrome_profile_directory: z.string().optional().describe("Optional Chrome profile directory to import, for example Default or Profile 1."),
431
- user_data_dir: z.string().optional().describe("Optional source Chrome user data directory. Defaults to the current OS Chrome profile root."),
432
- output_dir: z.string().optional().describe("Optional managed profile base directory. Defaults to ~/.mcp-scraper/browser-profiles."),
433
- browser_executable_path: z.string().optional().describe("Optional Chrome executable path for local browser launches. Defaults to the installed Google Chrome path when found."),
434
- overwrite: z.boolean().default(false).describe("Overwrite an existing managed profile clone with the same name.")
435
- };
436
- var BrowserProfileSyncInputSchema = {
437
- name: z.string().describe("Managed MCP Scraper browser profile name to refresh from its recorded source Chrome profile."),
438
- output_dir: z.string().optional().describe("Optional managed profile base directory. Defaults to ~/.mcp-scraper/browser-profiles."),
439
- browser_executable_path: z.string().optional().describe("Optional Chrome executable path to store in the refreshed profile manifest.")
440
- };
441
43
  var BrowserSessionInputSchema = {
442
44
  session_id: z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself.")
443
45
  };
@@ -606,14 +208,6 @@ var BrowserOpenOutputSchema = {
606
208
  hint: z.string(),
607
209
  raw: BrowserRawObject.optional()
608
210
  };
609
- var BrowserProfileListOutputSchema = {
610
- ok: z.boolean(),
611
- tool: z.literal("browser_profile_list"),
612
- session_id: z.null(),
613
- profiles: z.array(BrowserRawObject),
614
- count: z.number().int().min(0),
615
- hint: z.string()
616
- };
617
211
  var BrowserProfileOnboardOutputSchema = {
618
212
  ok: z.boolean(),
619
213
  tool: z.literal("browser_profile_onboard"),
@@ -622,27 +216,10 @@ var BrowserProfileOnboardOutputSchema = {
622
216
  live_view_url: NullableString,
623
217
  profile: z.string(),
624
218
  setup_url: z.string(),
625
- chrome_profile: BrowserRawObject.nullable(),
219
+ account_email: NullableString,
626
220
  next_steps: z.array(z.string()),
627
221
  raw: BrowserRawObject.optional()
628
222
  };
629
- var BrowserProfileImportOutputSchema = {
630
- ok: z.boolean(),
631
- tool: z.literal("browser_profile_import"),
632
- session_id: z.null(),
633
- profile: BrowserRawObject,
634
- source_profile: BrowserRawObject,
635
- next_steps: z.array(z.string()),
636
- hint: z.string()
637
- };
638
- var BrowserProfileSyncOutputSchema = {
639
- ok: z.boolean(),
640
- tool: z.literal("browser_profile_sync"),
641
- session_id: z.null(),
642
- profile: BrowserRawObject,
643
- source_profile: BrowserRawObject,
644
- hint: z.string()
645
- };
646
223
  var BrowserScreenshotOutputSchema = {
647
224
  ...BrowserBaseOutput,
648
225
  tool: z.literal("browser_screenshot"),
@@ -1049,6 +626,11 @@ function replayFilePath(sessionId, replayId, filename) {
1049
626
  const name = requested ? safeFilePart(requested).replace(/\.mp4$/i, "") : `${stamp}-${safeFilePart(sessionId)}-${safeFilePart(replayId)}`;
1050
627
  return join2(outputBaseDir(), "browser-replays", `${name}.mp4`);
1051
628
  }
629
+ function savedProfileNameFromEmail(email) {
630
+ const value = email?.trim().toLowerCase();
631
+ if (!value) return null;
632
+ return value.replace(/[^a-z0-9._-]+/g, "-").replace(/[._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || null;
633
+ }
1052
634
  function annotatedReplayFilePath(sessionId, replayId, filename) {
1053
635
  const requested = filename?.trim();
1054
636
  if (requested) return replayFilePath(sessionId, replayId, requested);
@@ -1083,7 +665,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1083
665
  const baseUrl = opts.baseUrl.replace(/\/$/, "");
1084
666
  const consoleBase = (opts.consoleBaseUrl ?? opts.baseUrl).replace(/\/$/, "");
1085
667
  const timeoutMs = opts.timeoutMs ?? 9e4;
1086
- const localBrowser = new LocalBrowserAgentService();
1087
668
  async function req(method, path, body) {
1088
669
  try {
1089
670
  const res = await fetch(`${baseUrl}${path}`, {
@@ -1135,131 +716,20 @@ function registerBrowserAgentMcpTools(server, opts) {
1135
716
  idempotentHint: false,
1136
717
  openWorldHint: true
1137
718
  });
1138
- function selectChromeProfile(matches, email) {
1139
- const wanted = email?.trim().toLowerCase();
1140
- if (!wanted) return matches[0] ?? null;
1141
- return matches.find((profile) => profile.email.toLowerCase() === wanted) ?? matches[0] ?? null;
1142
- }
1143
- function localReplayUnsupported(tool, sessionId = null, replayId = null) {
1144
- return errorResult(tool, {
1145
- error: "Local browser mode does not support replay recording yet. Use hosted mode for browser_replay_* tools.",
1146
- mode: "local"
1147
- }, sessionId, replayId);
1148
- }
1149
- server.registerTool(
1150
- "browser_profile_list",
1151
- {
1152
- title: "Developer: List Local Chrome Profiles",
1153
- description: "Developer/local-machine helper only. List local Chrome profile metadata visible to this MCP server: account email, Chrome profile directory, local path, and a suggested managed browser profile name. This reads Chrome Local State only; it does not read cookies, passwords, browsing history, or copy local browser state. Do not use this for customer AI visibility setup or reconnects; use browser_profile_onboard at Google and provide the hosted watch_url instead.",
1154
- inputSchema: BrowserProfileListInputSchema,
1155
- outputSchema: BrowserProfileListOutputSchema,
1156
- annotations: annotations("Developer: List Local Chrome Profiles", true)
1157
- },
1158
- async (input) => {
1159
- try {
1160
- const profiles = await listLocalChromeProfiles(input.user_data_dir, input.email);
1161
- return structuredResult({
1162
- ok: true,
1163
- tool: "browser_profile_list",
1164
- session_id: null,
1165
- profiles,
1166
- count: profiles.length,
1167
- hint: "For customer auth setup, use browser_profile_onboard so the user signs in through the hosted watch_url. Local import is only for developer/local-machine mode."
1168
- });
1169
- } catch (err) {
1170
- return errorResult("browser_profile_list", { error: err instanceof Error ? err.message : String(err) });
1171
- }
1172
- }
1173
- );
1174
- server.registerTool(
1175
- "browser_profile_import",
1176
- {
1177
- title: "Developer: Import Local Chrome Profile",
1178
- description: "Developer/local-machine helper only. Clone one local Chrome profile into MCP Scraper managed storage for local browser mode. This copies browser state files such as cookies, history, local storage, session storage, and password database metadata into ~/.mcp-scraper/browser-profiles by default; it skips cache and lock files and never prints cookie or password values. Do not use this for customer AI visibility setup or reconnects; use browser_profile_onboard at Google and provide the hosted watch_url instead.",
1179
- inputSchema: BrowserProfileImportInputSchema,
1180
- outputSchema: BrowserProfileImportOutputSchema,
1181
- annotations: { title: "Developer: Import Local Chrome Profile", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
1182
- },
1183
- async (input) => {
1184
- try {
1185
- const result = await importChromeProfile({
1186
- email: input.email,
1187
- name: input.name,
1188
- chromeProfileDirectory: input.chrome_profile_directory,
1189
- sourceUserDataDir: input.user_data_dir,
1190
- outputDir: input.output_dir,
1191
- overwrite: input.overwrite,
1192
- browserExecutablePath: input.browser_executable_path
1193
- });
1194
- return structuredResult({
1195
- ok: true,
1196
- tool: "browser_profile_import",
1197
- session_id: null,
1198
- profile: result.profile,
1199
- source_profile: result.sourceProfile,
1200
- next_steps: [
1201
- `Set MCP_SCRAPER_BROWSER_MODE=local in the MCP server config.`,
1202
- `Set MCP_SCRAPER_BROWSER_PROFILE=${result.profile.name}.`,
1203
- "Restart the MCP client so it starts the local browser mode process.",
1204
- "Call browser_open with the same MCP server; it will open a local Chrome window using the cloned profile."
1205
- ],
1206
- hint: "Developer-only local clone. For customer auth setup, use browser_profile_onboard at Google, provide the hosted watch_url, then browser_close to save the hosted profile."
1207
- });
1208
- } catch (err) {
1209
- return errorResult("browser_profile_import", { error: err instanceof Error ? err.message : String(err) });
1210
- }
1211
- }
1212
- );
1213
- server.registerTool(
1214
- "browser_profile_sync",
1215
- {
1216
- title: "Developer: Sync Local Chrome Profile",
1217
- description: "Developer/local-machine helper only. Refresh an existing MCP Scraper managed local browser profile from the source Chrome profile recorded in its manifest. This overwrites the managed clone, copies browser state files again, skips cache and lock files, and never prints cookie or password values. Do not use this for customer AI visibility setup or reconnects; use browser_profile_onboard at Google and provide the hosted watch_url instead.",
1218
- inputSchema: BrowserProfileSyncInputSchema,
1219
- outputSchema: BrowserProfileSyncOutputSchema,
1220
- annotations: { title: "Developer: Sync Local Chrome Profile", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
1221
- },
1222
- async (input) => {
1223
- try {
1224
- const result = await syncImportedChromeProfile(input.name, {
1225
- outputDir: input.output_dir,
1226
- browserExecutablePath: input.browser_executable_path
1227
- });
1228
- return structuredResult({
1229
- ok: true,
1230
- tool: "browser_profile_sync",
1231
- session_id: null,
1232
- profile: result.profile,
1233
- source_profile: result.sourceProfile,
1234
- hint: `Developer-only local clone refreshed for ${result.profile.name}. Customer reconnects should use browser_profile_onboard with the same hosted profile name.`
1235
- });
1236
- } catch (err) {
1237
- return errorResult("browser_profile_sync", { error: err instanceof Error ? err.message : String(err) });
1238
- }
1239
- }
1240
- );
1241
719
  server.registerTool(
1242
720
  "browser_profile_onboard",
1243
721
  {
1244
722
  title: "Connect Hosted Browser Profile",
1245
- description: "Create or load a saved MCP Scraper hosted browser profile and open a direct no-proxy setup browser with profile saving enabled. This is the customer auth setup and reconnect flow: open Google, give the user the watch_url, have them click Sign in and complete login, then call browser_close to save cookies and local storage. If a saved profile later shows login controls, run this same setup flow again. Do not use local Chrome import for this customer setup.",
723
+ description: "Create or load a saved MCP Scraper hosted browser profile and open a direct no-proxy setup browser with profile saving enabled. This is the customer auth setup and reconnect flow: open Google, give the user the watch_url, have them click Sign in and complete login, then call browser_close to save cookies and storage. If a saved profile later shows login controls, run this same setup flow again.",
1246
724
  inputSchema: BrowserProfileOnboardInputSchema,
1247
725
  outputSchema: BrowserProfileOnboardOutputSchema,
1248
726
  annotations: annotations("Connect Hosted Browser Profile")
1249
727
  },
1250
728
  async (input) => {
1251
- let chromeProfile = null;
1252
- if (input.email) {
1253
- try {
1254
- chromeProfile = selectChromeProfile(await listLocalChromeProfiles(input.user_data_dir, input.email), input.email);
1255
- } catch (err) {
1256
- return errorResult("browser_profile_onboard", { error: err instanceof Error ? err.message : String(err) });
1257
- }
1258
- }
1259
- const profile = input.profile?.trim() || chromeProfile?.recommendedKernelProfileName || browserServiceProfileName();
729
+ const profile = input.profile?.trim() || savedProfileNameFromEmail(input.email) || browserServiceProfileName();
1260
730
  if (!profile) {
1261
731
  return errorResult("browser_profile_onboard", {
1262
- error: "profile is required when no email match or BROWSER_AGENT_PROFILE_NAME is available"
732
+ error: "profile or email is required when BROWSER_AGENT_PROFILE_NAME is not available"
1263
733
  });
1264
734
  }
1265
735
  const setupUrl = input.url ?? "https://google.com/";
@@ -1282,10 +752,10 @@ function registerBrowserAgentMcpTools(server, opts) {
1282
752
  live_view_url: session.live_view_url ?? null,
1283
753
  profile,
1284
754
  setup_url: setupUrl,
1285
- chrome_profile: chromeProfile,
755
+ account_email: input.email?.trim() || null,
1286
756
  next_steps: [
1287
757
  "Open the watch_url and let the user click Sign in on Google.",
1288
- "After the user confirms login is complete, call browser_close with this session_id to persist cookies and local storage to the saved profile.",
758
+ "After the user confirms login is complete, call browser_close with this session_id to persist cookies and storage to the saved profile.",
1289
759
  "If the saved profile later shows login controls or disconnects, repeat browser_profile_onboard and close the setup session again.",
1290
760
  "Use BROWSER_AGENT_PROFILE_NAME or browser_open.profile with this profile name for future authenticated sessions."
1291
761
  ],
@@ -1303,23 +773,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1303
773
  annotations: annotations("Open Browser Session")
1304
774
  },
1305
775
  async (input) => {
1306
- if (localBrowserModeEnabled()) {
1307
- try {
1308
- const session2 = await localBrowser.open(input);
1309
- return structuredResult({
1310
- ok: true,
1311
- tool: "browser_open",
1312
- session_id: session2.session_id,
1313
- watch_url: session2.watch_url,
1314
- live_view_url: null,
1315
- url: input.url ?? null,
1316
- hint: "Local Chrome is open on this machine. Call browser_screenshot to see the page and click by x,y coordinates.",
1317
- raw: session2
1318
- });
1319
- } catch (err) {
1320
- return errorResult("browser_open", { error: err instanceof Error ? err.message : String(err), mode: "local" });
1321
- }
1322
- }
1323
776
  const profile = input.profile ?? browserServiceProfileName();
1324
777
  const saveProfileChanges = input.save_profile_changes ?? browserServiceProfileSaveChanges();
1325
778
  const open = await req("POST", "/agent/sessions", {
@@ -1356,27 +809,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1356
809
  annotations: annotations("See Page", true)
1357
810
  },
1358
811
  async (input) => {
1359
- if (localBrowserModeEnabled()) {
1360
- try {
1361
- const res2 = await localBrowser.screenshot(input.session_id);
1362
- const content2 = [];
1363
- if (res2.image_base64) content2.push({ type: "image", data: res2.image_base64, mimeType: res2.mime_type });
1364
- const structured2 = {
1365
- ok: true,
1366
- tool: "browser_screenshot",
1367
- session_id: input.session_id,
1368
- url: res2.url ?? null,
1369
- title: res2.title ?? null,
1370
- text: res2.text,
1371
- elements: res2.elements,
1372
- screenshot: { mime_type: res2.mime_type, inline: true }
1373
- };
1374
- content2.push({ type: "text", text: JSON.stringify(structured2) });
1375
- return { content: content2, structuredContent: structured2 };
1376
- } catch (err) {
1377
- return errorResult("browser_screenshot", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1378
- }
1379
- }
1380
812
  const res = await req("POST", `/agent/sessions/${input.session_id}/screenshot`);
1381
813
  if (!res.ok) return errorResult("browser_screenshot", res.data, input.session_id);
1382
814
  const { image_base64, mime_type, url, title, elements, text } = res.data;
@@ -1409,23 +841,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1409
841
  annotations: annotations("Read Page", true)
1410
842
  },
1411
843
  async (input) => {
1412
- if (localBrowserModeEnabled()) {
1413
- try {
1414
- const res2 = await localBrowser.read(input.session_id);
1415
- return structuredResult({
1416
- ok: true,
1417
- tool: "browser_read",
1418
- session_id: input.session_id,
1419
- url: res2.url ?? null,
1420
- title: res2.title ?? null,
1421
- text: res2.text,
1422
- elements: res2.elements,
1423
- raw: res2
1424
- });
1425
- } catch (err) {
1426
- return errorResult("browser_read", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1427
- }
1428
- }
1429
844
  const res = await req("POST", `/agent/sessions/${input.session_id}/read`);
1430
845
  if (!res.ok) return errorResult("browser_read", res.data, input.session_id);
1431
846
  return structuredResult({
@@ -1450,24 +865,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1450
865
  annotations: annotations("Locate DOM Targets", true)
1451
866
  },
1452
867
  async (input) => {
1453
- if (localBrowserModeEnabled()) {
1454
- try {
1455
- const res2 = await localBrowser.locate(input.session_id, input.targets);
1456
- return structuredResult({
1457
- ok: true,
1458
- tool: "browser_locate",
1459
- session_id: input.session_id,
1460
- url: res2.url ?? null,
1461
- title: res2.title ?? null,
1462
- viewport: res2.viewport ?? null,
1463
- replay: null,
1464
- targets: Array.isArray(res2.targets) ? res2.targets : [],
1465
- raw: res2
1466
- });
1467
- } catch (err) {
1468
- return errorResult("browser_locate", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1469
- }
1470
- }
1471
868
  const res = await req("POST", `/agent/sessions/${input.session_id}/locate`, { targets: input.targets });
1472
869
  if (!res.ok) return errorResult("browser_locate", res.data, input.session_id);
1473
870
  return structuredResult({
@@ -1493,14 +890,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1493
890
  annotations: annotations("Navigate To URL")
1494
891
  },
1495
892
  async (input) => {
1496
- if (localBrowserModeEnabled()) {
1497
- try {
1498
- const res2 = await localBrowser.goto(input.session_id, input.url);
1499
- return actionResult("browser_goto", input.session_id, true, res2, "browser_screenshot");
1500
- } catch (err) {
1501
- return errorResult("browser_goto", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1502
- }
1503
- }
1504
893
  const res = await req("POST", `/agent/sessions/${input.session_id}/goto`, { url: input.url });
1505
894
  return actionResult("browser_goto", input.session_id, res.ok, res.data, "browser_screenshot");
1506
895
  }
@@ -1515,14 +904,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1515
904
  annotations: annotations("Click")
1516
905
  },
1517
906
  async (input) => {
1518
- if (localBrowserModeEnabled()) {
1519
- try {
1520
- const res2 = await localBrowser.click(input.session_id, input);
1521
- return actionResult("browser_click", input.session_id, true, res2, "browser_screenshot");
1522
- } catch (err) {
1523
- return errorResult("browser_click", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1524
- }
1525
- }
1526
907
  const res = await req("POST", `/agent/sessions/${input.session_id}/click`, {
1527
908
  x: input.x,
1528
909
  y: input.y,
@@ -1542,14 +923,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1542
923
  annotations: annotations("Type Text")
1543
924
  },
1544
925
  async (input) => {
1545
- if (localBrowserModeEnabled()) {
1546
- try {
1547
- const res2 = await localBrowser.type(input.session_id, input);
1548
- return actionResult("browser_type", input.session_id, true, res2, "browser_screenshot");
1549
- } catch (err) {
1550
- return errorResult("browser_type", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1551
- }
1552
- }
1553
926
  const res = await req("POST", `/agent/sessions/${input.session_id}/type`, { text: input.text, delay: input.delay });
1554
927
  return actionResult("browser_type", input.session_id, res.ok, res.data, "browser_screenshot");
1555
928
  }
@@ -1564,14 +937,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1564
937
  annotations: annotations("Scroll")
1565
938
  },
1566
939
  async (input) => {
1567
- if (localBrowserModeEnabled()) {
1568
- try {
1569
- const res2 = await localBrowser.scroll(input.session_id, input);
1570
- return actionResult("browser_scroll", input.session_id, true, res2, "browser_screenshot");
1571
- } catch (err) {
1572
- return errorResult("browser_scroll", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1573
- }
1574
- }
1575
940
  const res = await req("POST", `/agent/sessions/${input.session_id}/scroll`, {
1576
941
  delta_y: input.delta_y,
1577
942
  delta_x: input.delta_x,
@@ -1591,14 +956,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1591
956
  annotations: annotations("Press Keys")
1592
957
  },
1593
958
  async (input) => {
1594
- if (localBrowserModeEnabled()) {
1595
- try {
1596
- const res2 = await localBrowser.press(input.session_id, input);
1597
- return actionResult("browser_press", input.session_id, true, res2, "browser_screenshot");
1598
- } catch (err) {
1599
- return errorResult("browser_press", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1600
- }
1601
- }
1602
959
  const res = await req("POST", `/agent/sessions/${input.session_id}/press`, { keys: input.keys });
1603
960
  return actionResult("browser_press", input.session_id, res.ok, res.data, "browser_screenshot");
1604
961
  }
@@ -1613,7 +970,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1613
970
  annotations: annotations("Start Recording")
1614
971
  },
1615
972
  async (input) => {
1616
- if (localBrowserModeEnabled()) return localReplayUnsupported("browser_replay_start", input.session_id);
1617
973
  const res = await req("POST", `/agent/sessions/${input.session_id}/replay/start`);
1618
974
  if (!res.ok) return errorResult("browser_replay_start", res.data, input.session_id);
1619
975
  return structuredResult({
@@ -1637,7 +993,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1637
993
  annotations: annotations("Stop Recording")
1638
994
  },
1639
995
  async (input) => {
1640
- if (localBrowserModeEnabled()) return localReplayUnsupported("browser_replay_stop", input.session_id, input.replay_id);
1641
996
  const res = await req("POST", `/agent/sessions/${input.session_id}/replay/stop`, { replay_id: input.replay_id });
1642
997
  if (!res.ok) return errorResult("browser_replay_stop", res.data, input.session_id, input.replay_id);
1643
998
  return structuredResult({
@@ -1661,7 +1016,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1661
1016
  annotations: annotations("List Replay Videos", true)
1662
1017
  },
1663
1018
  async (input) => {
1664
- if (localBrowserModeEnabled()) return localReplayUnsupported("browser_list_replays", input.session_id);
1665
1019
  const res = await req("GET", `/agent/sessions/${input.session_id}/replays`);
1666
1020
  if (!res.ok) return errorResult("browser_list_replays", res.data, input.session_id);
1667
1021
  const replays = Array.isArray(res.data?.replays) ? res.data.replays : [];
@@ -1684,7 +1038,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1684
1038
  annotations: annotations("Download Replay MP4")
1685
1039
  },
1686
1040
  async (input) => {
1687
- if (localBrowserModeEnabled()) return localReplayUnsupported("browser_replay_download", input.session_id, input.replay_id);
1688
1041
  const res = await downloadReplay(input.session_id, input.replay_id, input.filename);
1689
1042
  if (!res.ok) return errorResult("browser_replay_download", res.data, input.session_id, input.replay_id);
1690
1043
  return structuredResult({
@@ -1709,7 +1062,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1709
1062
  annotations: annotations("Mark Replay Annotation", true)
1710
1063
  },
1711
1064
  async (input) => {
1712
- if (localBrowserModeEnabled()) return localReplayUnsupported("browser_replay_mark", input.session_id);
1713
1065
  const res = await req("POST", `/agent/sessions/${input.session_id}/locate`, { targets: [input.target] });
1714
1066
  if (!res.ok) return errorResult("browser_replay_mark", res.data, input.session_id);
1715
1067
  const target = res.data?.targets?.[0];
@@ -1760,7 +1112,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1760
1112
  annotations: annotations("Annotate Replay MP4")
1761
1113
  },
1762
1114
  async (input) => {
1763
- if (localBrowserModeEnabled()) return localReplayUnsupported("browser_replay_annotate", input.session_id, input.replay_id);
1764
1115
  const sourceName = input.filename ? `${input.filename}-source` : void 0;
1765
1116
  const downloaded = await downloadReplay(input.session_id, input.replay_id, sourceName);
1766
1117
  if (!downloaded.ok) return errorResult("browser_replay_annotate", downloaded.data, input.session_id, input.replay_id);
@@ -1803,20 +1154,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1803
1154
  annotations: { title: "Close Browser Session", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
1804
1155
  },
1805
1156
  async (input) => {
1806
- if (localBrowserModeEnabled()) {
1807
- try {
1808
- const res2 = await localBrowser.close(input.session_id);
1809
- return structuredResult({
1810
- ok: true,
1811
- tool: "browser_close",
1812
- session_id: input.session_id,
1813
- closed: true,
1814
- raw: res2
1815
- });
1816
- } catch (err) {
1817
- return errorResult("browser_close", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1818
- }
1819
- }
1820
1157
  const res = await req("DELETE", `/agent/sessions/${input.session_id}`);
1821
1158
  if (!res.ok) return errorResult("browser_close", res.data, input.session_id);
1822
1159
  return structuredResult({
@@ -1838,16 +1175,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1838
1175
  annotations: annotations("List Browser Sessions", true)
1839
1176
  },
1840
1177
  async (input) => {
1841
- if (localBrowserModeEnabled()) {
1842
- const sessions2 = localBrowser.list(input.include_closed);
1843
- return structuredResult({
1844
- ok: true,
1845
- tool: "browser_list_sessions",
1846
- session_id: null,
1847
- sessions: sessions2,
1848
- count: sessions2.length
1849
- });
1850
- }
1851
1178
  const res = await req("GET", `/agent/sessions${input.include_closed ? "?all=1" : ""}`);
1852
1179
  if (!res.ok) return errorResult("browser_list_sessions", res.data);
1853
1180
  const sessions = (res.data.sessions ?? []).map((s) => ({ ...s, watch_url: `${consoleBase}/console/${s.session_id}` }));
@@ -1889,14 +1216,6 @@ function registerBrowserAgentMcpTools(server, opts) {
1889
1216
  exports: exports ?? null,
1890
1217
  ...result.debug ? { debug: result.debug } : {}
1891
1218
  });
1892
- if (localBrowserModeEnabled()) {
1893
- try {
1894
- const { result, exports } = await localBrowser.captureFanout(input.session_id, input);
1895
- return emit(result, exports);
1896
- } catch (err) {
1897
- return errorResult("browser_capture_fanout", { error: err instanceof Error ? err.message : String(err), mode: "local" }, input.session_id);
1898
- }
1899
- }
1900
1219
  const res = await req("POST", `/agent/sessions/${input.session_id}/capture-fanout`, {
1901
1220
  prompt: input.prompt,
1902
1221
  wait_ms: input.wait_ms,
@@ -1914,4 +1233,4 @@ export {
1914
1233
  buildBrowserAgentMcpServer,
1915
1234
  registerBrowserAgentMcpTools
1916
1235
  };
1917
- //# sourceMappingURL=chunk-TK4D2BZ5.js.map
1236
+ //# sourceMappingURL=chunk-IWNLHCKA.js.map