mcp-scraper 0.2.18 → 0.2.20

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 (71) hide show
  1. package/README.md +17 -11
  2. package/dist/bin/api-server.cjs +1020 -348
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/browser-agent-stdio-server.cjs +314 -55
  6. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
  7. package/dist/bin/browser-agent-stdio-server.js +2 -2
  8. package/dist/bin/mcp-scraper-cli.cjs +321 -167
  9. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-cli.js +3 -2
  11. package/dist/bin/mcp-scraper-cli.js.map +1 -1
  12. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +1230 -528
  13. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
  14. package/dist/bin/mcp-scraper-combined-stdio-server.js +19 -3
  15. package/dist/bin/mcp-scraper-combined-stdio-server.js.map +1 -1
  16. package/dist/bin/mcp-scraper-install.cjs +8 -6
  17. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  18. package/dist/bin/mcp-scraper-install.js +5 -92
  19. package/dist/bin/mcp-scraper-install.js.map +1 -1
  20. package/dist/bin/mcp-stdio-server.cjs +726 -387
  21. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  22. package/dist/bin/mcp-stdio-server.js +3 -2
  23. package/dist/bin/mcp-stdio-server.js.map +1 -1
  24. package/dist/bin/paa-harvest.cjs +5 -3
  25. package/dist/bin/paa-harvest.cjs.map +1 -1
  26. package/dist/bin/paa-harvest.js +2 -1
  27. package/dist/bin/paa-harvest.js.map +1 -1
  28. package/dist/{chunk-TL7YTFLH.js → chunk-DBQDG7EH.js} +99 -27
  29. package/dist/chunk-DBQDG7EH.js.map +1 -0
  30. package/dist/{chunk-F44RBOJ5.js → chunk-H2R232HK.js} +216 -108
  31. package/dist/chunk-H2R232HK.js.map +1 -0
  32. package/dist/{chunk-P5PLQU3H.js → chunk-L4OWOUGR.js} +315 -56
  33. package/dist/chunk-L4OWOUGR.js.map +1 -0
  34. package/dist/chunk-LFATOGDF.js +111 -0
  35. package/dist/chunk-LFATOGDF.js.map +1 -0
  36. package/dist/chunk-MLNCKQ2B.js +96 -0
  37. package/dist/chunk-MLNCKQ2B.js.map +1 -0
  38. package/dist/{chunk-RJMCASQH.js → chunk-O5J7E4VX.js} +394 -57
  39. package/dist/chunk-O5J7E4VX.js.map +1 -0
  40. package/dist/{chunk-BSYPATSM.js → chunk-SXTXMFEQ.js} +2 -2
  41. package/dist/{chunk-CQTAKXBN.js → chunk-XGUDTDZ2.js} +7 -104
  42. package/dist/chunk-XGUDTDZ2.js.map +1 -0
  43. package/dist/chunk-XKUDVN2E.js +7 -0
  44. package/dist/chunk-XKUDVN2E.js.map +1 -0
  45. package/dist/{db-P5X6UQ3E.js → db-BE4JVB3V.js} +8 -2
  46. package/dist/index.cjs +5 -3
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +2 -2
  49. package/dist/index.d.ts +2 -2
  50. package/dist/index.js +2 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/{server-VVY5K44O.js → server-7NE45K2A.js} +211 -37
  53. package/dist/server-7NE45K2A.js.map +1 -0
  54. package/dist/{worker-OZSWIS3F.js → worker-MIYG2B2I.js} +5 -4
  55. package/dist/{worker-OZSWIS3F.js.map → worker-MIYG2B2I.js.map} +1 -1
  56. package/docs/adr/0001-in-page-graphql-interception-for-anti-bot-scraping.md +2 -2
  57. package/docs/mcp-tool-craft-lint.generated.md +51 -0
  58. package/docs/mcp-tool-manifest.generated.json +1406 -0
  59. package/docs/specs/local-competitive-audit-spec.md +1 -2
  60. package/docs/specs/mcp-tool-definition-quality-audit-spec.md +1602 -0
  61. package/package.json +4 -1
  62. package/dist/chunk-CQTAKXBN.js.map +0 -1
  63. package/dist/chunk-F44RBOJ5.js.map +0 -1
  64. package/dist/chunk-M5SAUO4K.js +0 -7
  65. package/dist/chunk-M5SAUO4K.js.map +0 -1
  66. package/dist/chunk-P5PLQU3H.js.map +0 -1
  67. package/dist/chunk-RJMCASQH.js.map +0 -1
  68. package/dist/chunk-TL7YTFLH.js.map +0 -1
  69. package/dist/server-VVY5K44O.js.map +0 -1
  70. /package/dist/{chunk-BSYPATSM.js.map → chunk-SXTXMFEQ.js.map} +0 -0
  71. /package/dist/{db-P5X6UQ3E.js.map → db-BE4JVB3V.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PACKAGE_VERSION
3
- } from "./chunk-M5SAUO4K.js";
3
+ } from "./chunk-XKUDVN2E.js";
4
4
 
5
5
  // src/mcp/browser-agent-mcp-server.ts
6
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -10,6 +10,18 @@ import { join as join2 } from "path";
10
10
 
11
11
  // src/mcp/browser-agent-tool-schemas.ts
12
12
  import { z } from "zod";
13
+ var NullableString = z.string().nullable();
14
+ var BrowserRawObject = z.record(z.unknown());
15
+ var BrowserElementOutput = z.record(z.unknown());
16
+ var BrowserBaseOutput = {
17
+ ok: z.boolean().describe("Whether the browser-agent action succeeded."),
18
+ tool: z.string().describe("Browser Agent MCP tool that produced this response."),
19
+ session_id: NullableString.describe("Browser session id when the response is scoped to a session.")
20
+ };
21
+ var BrowserReplayBaseOutput = {
22
+ ...BrowserBaseOutput,
23
+ replay_id: NullableString.describe("Replay id when the response is scoped to a replay.")
24
+ };
13
25
  var BrowserOpenInputSchema = {
14
26
  label: z.string().optional().describe("Optional human label for this session, shown in the watch console."),
15
27
  url: z.string().url().optional().describe("Optional URL to navigate to immediately after opening."),
@@ -17,7 +29,7 @@ var BrowserOpenInputSchema = {
17
29
  timeout_seconds: z.number().int().min(60).max(259200).optional().describe("How long the 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.")
18
30
  };
19
31
  var BrowserSessionInputSchema = {
20
- session_id: z.string().describe("The session id returned by browser_open.")
32
+ 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.")
21
33
  };
22
34
  var BrowserLocateTargetSchema = z.object({
23
35
  name: z.string().optional().describe("Optional label for this target, echoed in the result."),
@@ -29,43 +41,43 @@ var BrowserLocateTargetSchema = z.object({
29
41
  message: "target requires selector or text"
30
42
  });
31
43
  var BrowserLocateInputSchema = {
32
- session_id: z.string().describe("The session id returned by browser_open."),
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."),
33
45
  targets: z.array(BrowserLocateTargetSchema).min(1).max(20).describe("DOM targets to locate in the current viewport. Use selectors for exact elements, or text for visible text ranges.")
34
46
  };
35
47
  var BrowserGotoInputSchema = {
36
- session_id: z.string().describe("The session id returned by browser_open."),
48
+ 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."),
37
49
  url: z.string().url().describe("URL to navigate the browser to.")
38
50
  };
39
51
  var BrowserClickInputSchema = {
40
- session_id: z.string().describe("The session id returned by browser_open."),
41
- x: z.number().describe("X coordinate to click, in screenshot pixels. Use the x of an element from the latest screenshot."),
42
- y: z.number().describe("Y coordinate to click, in screenshot pixels."),
52
+ 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."),
53
+ x: z.number().describe("X coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
54
+ y: z.number().describe("Y coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
43
55
  button: z.enum(["left", "right", "middle"]).default("left").describe("Mouse button."),
44
56
  num_clicks: z.number().int().min(1).max(3).optional().describe("Number of clicks, e.g. 2 for double-click.")
45
57
  };
46
58
  var BrowserTypeInputSchema = {
47
- session_id: z.string().describe("The session id returned by browser_open."),
59
+ 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."),
48
60
  text: z.string().describe("Text to type at the current focus. Click a field first to focus it."),
49
61
  delay: z.number().int().min(0).max(500).optional().describe("Optional per-keystroke delay in ms for human-like typing.")
50
62
  };
51
63
  var BrowserScrollInputSchema = {
52
- session_id: z.string().describe("The session id returned by browser_open."),
64
+ 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."),
53
65
  delta_y: z.number().default(5).describe("Vertical scroll in wheel units. Positive scrolls down, negative up."),
54
66
  delta_x: z.number().default(0).describe("Horizontal scroll in wheel units."),
55
67
  x: z.number().optional().describe("X position to scroll at. Defaults to screen center."),
56
68
  y: z.number().optional().describe("Y position to scroll at. Defaults to screen center.")
57
69
  };
58
70
  var BrowserPressInputSchema = {
59
- session_id: z.string().describe("The session id returned by browser_open."),
71
+ 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."),
60
72
  keys: z.array(z.string()).min(1).describe('Keys or combinations to press, e.g. ["Return"], ["Ctrl+a"], ["Ctrl+Shift+Tab"].')
61
73
  };
62
74
  var BrowserReplayStopInputSchema = {
63
- session_id: z.string().describe("The session id returned by browser_open."),
64
- replay_id: z.string().describe("The replay id returned by browser_replay_start.")
75
+ 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."),
76
+ replay_id: z.string().describe("The replay id returned by browser_replay_start or browser_list_replays. Use only a returned replay_id; do not construct one yourself.")
65
77
  };
66
78
  var BrowserReplayDownloadInputSchema = {
67
- session_id: z.string().describe("The session id returned by browser_open."),
68
- replay_id: z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
79
+ 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."),
80
+ replay_id: z.string().describe("The replay id returned by browser_replay_start or browser_list_replays. Use only a returned replay_id; do not construct one yourself."),
69
81
  filename: z.string().optional().describe("Optional local MP4 filename. Defaults to a timestamped replay filename.")
70
82
  };
71
83
  var BrowserReplayAnnotationSchema = z.object({
@@ -87,7 +99,7 @@ var BrowserReplayAnnotationSchema = z.object({
87
99
  thickness: z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5.")
88
100
  });
89
101
  var BrowserReplayMarkInputSchema = {
90
- session_id: z.string().describe("The session id returned by browser_open. A replay must already be recording."),
102
+ 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. A replay must already be recording."),
91
103
  target: BrowserLocateTargetSchema.describe("The exact DOM element or text range to mark in the current viewport."),
92
104
  type: z.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate. Labels are included on the returned annotation when label is provided."),
93
105
  label: z.string().max(120).optional().describe("Optional callout text to render near the target."),
@@ -98,8 +110,8 @@ var BrowserReplayMarkInputSchema = {
98
110
  duration_seconds: z.number().min(0.5).max(30).default(4).describe("How long the generated annotation should remain visible.")
99
111
  };
100
112
  var BrowserReplayAnnotateInputSchema = {
101
- session_id: z.string().describe("The session id returned by browser_open."),
102
- replay_id: z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
113
+ 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."),
114
+ replay_id: z.string().describe("The replay id returned by browser_replay_start or browser_list_replays. Use only a returned replay_id; do not construct one yourself."),
103
115
  annotations: z.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations to render on the replay. Prefer annotations returned by browser_replay_mark; otherwise use exact DOM bounds from browser_locate/browser_screenshot/browser_read."),
104
116
  filename: z.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped annotated replay filename."),
105
117
  source_width: z.number().positive().optional().describe("Width of the screenshot coordinate space used for annotations. Defaults to the replay video width."),
@@ -110,6 +122,113 @@ var BrowserReplayAnnotateInputSchema = {
110
122
  var BrowserListInputSchema = {
111
123
  include_closed: z.boolean().default(false).describe("Include closed sessions in the list.")
112
124
  };
125
+ var BrowserOpenOutputSchema = {
126
+ ok: z.boolean(),
127
+ tool: z.literal("browser_open"),
128
+ session_id: z.string().describe("Session id returned by browser_open. Use only this exact value in later browser_* calls; do not construct one yourself."),
129
+ watch_url: z.string().describe("Human watch/takeover URL for this browser session."),
130
+ live_view_url: NullableString.describe("Raw live-view URL from the browser service when available."),
131
+ url: NullableString.describe("Initial URL requested by the caller, when provided."),
132
+ hint: z.string(),
133
+ raw: BrowserRawObject.optional()
134
+ };
135
+ var BrowserScreenshotOutputSchema = {
136
+ ...BrowserBaseOutput,
137
+ tool: z.literal("browser_screenshot"),
138
+ url: NullableString,
139
+ title: NullableString,
140
+ text: z.string(),
141
+ elements: z.array(BrowserElementOutput),
142
+ screenshot: z.object({
143
+ mime_type: z.string(),
144
+ inline: z.boolean()
145
+ }).nullable()
146
+ };
147
+ var BrowserReadOutputSchema = {
148
+ ...BrowserBaseOutput,
149
+ tool: z.literal("browser_read"),
150
+ url: NullableString,
151
+ title: NullableString,
152
+ text: z.string(),
153
+ elements: z.array(BrowserElementOutput),
154
+ raw: BrowserRawObject.optional()
155
+ };
156
+ var BrowserLocateOutputSchema = {
157
+ ...BrowserBaseOutput,
158
+ tool: z.literal("browser_locate"),
159
+ url: NullableString,
160
+ title: NullableString,
161
+ viewport: BrowserRawObject.nullable(),
162
+ replay: BrowserRawObject.nullable(),
163
+ targets: z.array(BrowserRawObject),
164
+ raw: BrowserRawObject.optional()
165
+ };
166
+ var BrowserActionOutputSchema = {
167
+ ...BrowserBaseOutput,
168
+ result: BrowserRawObject.describe("Provider action result. Check ok and follow with browser_screenshot/browser_read when page state matters."),
169
+ nextRecommendedTool: z.string().nullable()
170
+ };
171
+ var BrowserReplayStartOutputSchema = {
172
+ ...BrowserReplayBaseOutput,
173
+ tool: z.literal("browser_replay_start"),
174
+ view_url: NullableString,
175
+ download_url: NullableString,
176
+ raw: BrowserRawObject.optional()
177
+ };
178
+ var BrowserReplayStopOutputSchema = {
179
+ ...BrowserReplayBaseOutput,
180
+ tool: z.literal("browser_replay_stop"),
181
+ view_url: NullableString,
182
+ download_url: NullableString,
183
+ raw: BrowserRawObject.optional()
184
+ };
185
+ var BrowserListReplaysOutputSchema = {
186
+ ...BrowserBaseOutput,
187
+ tool: z.literal("browser_list_replays"),
188
+ replays: z.array(BrowserRawObject),
189
+ count: z.number().int().min(0)
190
+ };
191
+ var BrowserReplayDownloadOutputSchema = {
192
+ ...BrowserReplayBaseOutput,
193
+ tool: z.literal("browser_replay_download"),
194
+ file_path: NullableString,
195
+ bytes: z.number().int().min(0).nullable(),
196
+ mime_type: NullableString,
197
+ download_url: NullableString
198
+ };
199
+ var BrowserReplayMarkOutputSchema = {
200
+ ...BrowserReplayBaseOutput,
201
+ tool: z.literal("browser_replay_mark"),
202
+ annotation: BrowserRawObject,
203
+ source_width: z.number().nullable(),
204
+ source_height: z.number().nullable(),
205
+ target: BrowserRawObject.nullable(),
206
+ hint: z.string()
207
+ };
208
+ var BrowserReplayAnnotateOutputSchema = {
209
+ ...BrowserReplayBaseOutput,
210
+ tool: z.literal("browser_replay_annotate"),
211
+ source_file_path: NullableString,
212
+ annotated_file_path: NullableString,
213
+ bytes: z.number().int().min(0).nullable(),
214
+ width: z.number().int().min(0).nullable(),
215
+ height: z.number().int().min(0).nullable(),
216
+ annotation_count: z.number().int().min(0).nullable(),
217
+ mime_type: NullableString
218
+ };
219
+ var BrowserCloseOutputSchema = {
220
+ ...BrowserBaseOutput,
221
+ tool: z.literal("browser_close"),
222
+ closed: z.boolean(),
223
+ raw: BrowserRawObject.optional()
224
+ };
225
+ var BrowserListSessionsOutputSchema = {
226
+ ok: z.boolean(),
227
+ tool: z.literal("browser_list_sessions"),
228
+ session_id: z.null(),
229
+ sessions: z.array(BrowserRawObject),
230
+ count: z.number().int().min(0)
231
+ };
113
232
 
114
233
  // src/mcp/replay-annotator.ts
115
234
  import { execFile } from "child_process";
@@ -372,8 +491,40 @@ async function annotateReplayVideo(inputFilePath, outputFilePath, options) {
372
491
  }
373
492
 
374
493
  // src/mcp/browser-agent-mcp-server.ts
375
- function textResult(value, isError = false) {
376
- return { content: [{ type: "text", text: JSON.stringify(value) }], isError };
494
+ function structuredResult(value, isError = false) {
495
+ return {
496
+ content: [{ type: "text", text: JSON.stringify(value) }],
497
+ structuredContent: value,
498
+ isError
499
+ };
500
+ }
501
+ function errorMessage(value) {
502
+ if (value && typeof value === "object") {
503
+ const data = value;
504
+ if (typeof data.error === "string") return data.error;
505
+ if (typeof data.message === "string") return data.message;
506
+ }
507
+ return typeof value === "string" ? value : "Browser Agent request failed";
508
+ }
509
+ function errorResult(tool, value, sessionId = null, replayId = null) {
510
+ return structuredResult({
511
+ ok: false,
512
+ tool,
513
+ session_id: sessionId,
514
+ ...replayId !== null ? { replay_id: replayId } : {},
515
+ error: errorMessage(value),
516
+ raw: value && typeof value === "object" ? value : { value }
517
+ }, true);
518
+ }
519
+ function actionResult(tool, sessionId, ok, data, nextRecommendedTool = "browser_screenshot") {
520
+ if (!ok) return errorResult(tool, data, sessionId);
521
+ return structuredResult({
522
+ ok: true,
523
+ tool,
524
+ session_id: sessionId,
525
+ result: data && typeof data === "object" ? data : { value: data },
526
+ nextRecommendedTool
527
+ });
377
528
  }
378
529
  function outputBaseDir() {
379
530
  return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join2(homedir(), "Downloads", "mcp-scraper");
@@ -478,6 +629,7 @@ function registerBrowserAgentMcpTools(server, opts) {
478
629
  title: "Open Browser Session",
479
630
  description: "Open a fresh cloud browser you can drive. Returns a session_id used by all other browser_* tools, and a watch_url where a human can watch live or take over. Anti-bot stealth and automatic CAPTCHA/Cloudflare solving are on by default: if a Cloudflare or CAPTCHA challenge appears, do NOT click it \u2014 wait a few seconds and call browser_screenshot again; it is solved automatically. Billing: metered per second of active browser work at ~4 credits per minute; idle and standby time are free. Call browser_close when done to stop the meter. After opening, call browser_screenshot to see the page.",
480
631
  inputSchema: BrowserOpenInputSchema,
632
+ outputSchema: BrowserOpenOutputSchema,
481
633
  annotations: annotations("Open Browser Session")
482
634
  },
483
635
  async (input) => {
@@ -486,16 +638,20 @@ function registerBrowserAgentMcpTools(server, opts) {
486
638
  profile: input.profile,
487
639
  timeout_seconds: input.timeout_seconds
488
640
  });
489
- if (!open.ok) return textResult(open.data, true);
641
+ if (!open.ok) return errorResult("browser_open", open.data);
490
642
  const session = open.data;
491
643
  if (input.url) {
492
644
  await req("POST", `/agent/sessions/${session.session_id}/goto`, { url: input.url });
493
645
  }
494
- return textResult({
646
+ return structuredResult({
647
+ ok: true,
648
+ tool: "browser_open",
495
649
  session_id: session.session_id,
496
650
  watch_url: `${consoleBase}/console/${session.session_id}`,
497
651
  live_view_url: session.live_view_url ?? null,
498
- hint: "Call browser_screenshot to see the page. Click by the x,y of an element from the snapshot."
652
+ url: input.url ?? null,
653
+ hint: "Call browser_screenshot to see the page. Click by the x,y of an element from the snapshot.",
654
+ raw: session
499
655
  });
500
656
  }
501
657
  );
@@ -505,19 +661,30 @@ function registerBrowserAgentMcpTools(server, opts) {
505
661
  title: "See Page (Screenshot + Elements)",
506
662
  description: "Capture what the browser currently shows. Returns a screenshot image PLUS a text snapshot listing interactive elements with their center x,y coordinates, the page url and title, and visible text. This is your primary way to perceive the page. Click elements by their listed x,y. If a Cloudflare/CAPTCHA challenge is visible, wait and screenshot again rather than clicking it.",
507
663
  inputSchema: BrowserSessionInputSchema,
664
+ outputSchema: BrowserScreenshotOutputSchema,
508
665
  annotations: annotations("See Page", true)
509
666
  },
510
667
  async (input) => {
511
668
  const res = await req("POST", `/agent/sessions/${input.session_id}/screenshot`);
512
- if (!res.ok) return textResult(res.data, true);
669
+ if (!res.ok) return errorResult("browser_screenshot", res.data, input.session_id);
513
670
  const { image_base64, mime_type, url, title, elements, text } = res.data;
514
671
  const content = [];
515
672
  if (image_base64) content.push({ type: "image", data: image_base64, mimeType: mime_type ?? "image/png" });
673
+ const structured = {
674
+ ok: true,
675
+ tool: "browser_screenshot",
676
+ session_id: input.session_id,
677
+ url: url ?? null,
678
+ title: title ?? null,
679
+ text: typeof text === "string" ? text : "",
680
+ elements: Array.isArray(elements) ? elements : [],
681
+ screenshot: image_base64 ? { mime_type: mime_type ?? "image/png", inline: true } : null
682
+ };
516
683
  content.push({
517
684
  type: "text",
518
- text: JSON.stringify({ url, title, elements, text })
685
+ text: JSON.stringify(structured)
519
686
  });
520
- return { content };
687
+ return { content, structuredContent: structured };
521
688
  }
522
689
  );
523
690
  server.registerTool(
@@ -526,11 +693,22 @@ function registerBrowserAgentMcpTools(server, opts) {
526
693
  title: "Read Page Text + Elements",
527
694
  description: "Return the page url, title, visible text, and the list of interactive elements (with x,y) without an image. Cheaper than browser_screenshot when you only need to read content or find a target element to click.",
528
695
  inputSchema: BrowserSessionInputSchema,
696
+ outputSchema: BrowserReadOutputSchema,
529
697
  annotations: annotations("Read Page", true)
530
698
  },
531
699
  async (input) => {
532
700
  const res = await req("POST", `/agent/sessions/${input.session_id}/read`);
533
- return textResult(res.data, !res.ok);
701
+ if (!res.ok) return errorResult("browser_read", res.data, input.session_id);
702
+ return structuredResult({
703
+ ok: true,
704
+ tool: "browser_read",
705
+ session_id: input.session_id,
706
+ url: res.data?.url ?? null,
707
+ title: res.data?.title ?? null,
708
+ text: typeof res.data?.text === "string" ? res.data.text : "",
709
+ elements: Array.isArray(res.data?.elements) ? res.data.elements : [],
710
+ raw: res.data
711
+ });
534
712
  }
535
713
  );
536
714
  server.registerTool(
@@ -539,32 +717,46 @@ function registerBrowserAgentMcpTools(server, opts) {
539
717
  title: "Locate DOM Targets",
540
718
  description: "Locate exact visible DOM elements or text ranges in the current browser viewport and return left/top/width/height bounds in screenshot pixels. Use this before drawing annotations or when a callout must literally circle, box, underline, or point to a real element. Prefer CSS selectors for exact UI elements; use text when selector is unknown. When a replay is actively recording, the result includes replay_elapsed_seconds for timing.",
541
719
  inputSchema: BrowserLocateInputSchema,
720
+ outputSchema: BrowserLocateOutputSchema,
542
721
  annotations: annotations("Locate DOM Targets", true)
543
722
  },
544
723
  async (input) => {
545
724
  const res = await req("POST", `/agent/sessions/${input.session_id}/locate`, { targets: input.targets });
546
- return textResult(res.data, !res.ok);
725
+ if (!res.ok) return errorResult("browser_locate", res.data, input.session_id);
726
+ return structuredResult({
727
+ ok: true,
728
+ tool: "browser_locate",
729
+ session_id: input.session_id,
730
+ url: res.data?.url ?? null,
731
+ title: res.data?.title ?? null,
732
+ viewport: res.data?.viewport ?? null,
733
+ replay: res.data?.replay ?? null,
734
+ targets: Array.isArray(res.data?.targets) ? res.data.targets : [],
735
+ raw: res.data
736
+ });
547
737
  }
548
738
  );
549
739
  server.registerTool(
550
740
  "browser_goto",
551
741
  {
552
742
  title: "Navigate To URL",
553
- description: "Navigate the browser to a URL. Follow with browser_screenshot to see the result.",
743
+ description: "Navigate an existing browser session to a URL when the user asks you to go to another page or continue browsing in the same session. Use browser_open first if no session exists. Follow with browser_screenshot to see redirects, login walls, CAPTCHA/Cloudflare state, or the loaded page.",
554
744
  inputSchema: BrowserGotoInputSchema,
745
+ outputSchema: BrowserActionOutputSchema,
555
746
  annotations: annotations("Navigate To URL")
556
747
  },
557
748
  async (input) => {
558
749
  const res = await req("POST", `/agent/sessions/${input.session_id}/goto`, { url: input.url });
559
- return textResult(res.data, !res.ok);
750
+ return actionResult("browser_goto", input.session_id, res.ok, res.data, "browser_screenshot");
560
751
  }
561
752
  );
562
753
  server.registerTool(
563
754
  "browser_click",
564
755
  {
565
756
  title: "Click",
566
- description: "Click at x,y (screenshot pixel coordinates). Use the x,y of a target element from the latest browser_screenshot or browser_read.",
757
+ description: "Click a visible page target using screenshot pixel coordinates. Use this when the user asks you to press a button, open a menu, choose a result, or activate a visible UI element. Use x/y only from the latest browser_screenshot, browser_read, or browser_locate result; do not guess coordinates. Follow with browser_screenshot when the click may change the page.",
567
758
  inputSchema: BrowserClickInputSchema,
759
+ outputSchema: BrowserActionOutputSchema,
568
760
  annotations: annotations("Click")
569
761
  },
570
762
  async (input) => {
@@ -574,28 +766,30 @@ function registerBrowserAgentMcpTools(server, opts) {
574
766
  button: input.button,
575
767
  num_clicks: input.num_clicks
576
768
  });
577
- return textResult(res.data, !res.ok);
769
+ return actionResult("browser_click", input.session_id, res.ok, res.data, "browser_screenshot");
578
770
  }
579
771
  );
580
772
  server.registerTool(
581
773
  "browser_type",
582
774
  {
583
775
  title: "Type Text",
584
- description: 'Type text at the current focus. Click an input field first to focus it. Use browser_press with ["Return"] to submit.',
776
+ description: 'Type text into the currently focused browser field. Use this when the user asks you to fill a search box, form field, login field, or editable text area. Click or Tab to the field first if focus is uncertain. Use browser_press with ["Return"] to submit, and browser_screenshot afterward when page state matters.',
585
777
  inputSchema: BrowserTypeInputSchema,
778
+ outputSchema: BrowserActionOutputSchema,
586
779
  annotations: annotations("Type Text")
587
780
  },
588
781
  async (input) => {
589
782
  const res = await req("POST", `/agent/sessions/${input.session_id}/type`, { text: input.text, delay: input.delay });
590
- return textResult(res.data, !res.ok);
783
+ return actionResult("browser_type", input.session_id, res.ok, res.data, "browser_screenshot");
591
784
  }
592
785
  );
593
786
  server.registerTool(
594
787
  "browser_scroll",
595
788
  {
596
789
  title: "Scroll",
597
- description: "Scroll the page. Positive delta_y scrolls down. Follow with browser_screenshot to see newly revealed content.",
790
+ description: "Scroll the page to reveal more content before reading, clicking, or locating elements. Positive delta_y scrolls down; negative delta_y scrolls up. Follow with browser_screenshot or browser_read to inspect newly revealed content.",
598
791
  inputSchema: BrowserScrollInputSchema,
792
+ outputSchema: BrowserActionOutputSchema,
599
793
  annotations: annotations("Scroll")
600
794
  },
601
795
  async (input) => {
@@ -605,20 +799,21 @@ function registerBrowserAgentMcpTools(server, opts) {
605
799
  x: input.x,
606
800
  y: input.y
607
801
  });
608
- return textResult(res.data, !res.ok);
802
+ return actionResult("browser_scroll", input.session_id, res.ok, res.data, "browser_screenshot");
609
803
  }
610
804
  );
611
805
  server.registerTool(
612
806
  "browser_press",
613
807
  {
614
808
  title: "Press Keys",
615
- description: 'Press keys or combinations, e.g. ["Return"] to submit, ["Ctrl+a"] to select all, ["Ctrl+Shift+Tab"] to switch tabs.',
809
+ description: 'Press keyboard keys or combinations in the active browser session. Use this for submit, Escape, Tab navigation, select-all, or keyboard shortcuts. Examples: ["Return"], ["Escape"], ["Tab"], ["Ctrl+a"], ["Ctrl+Shift+Tab"]. Use browser_type for text entry and browser_screenshot after keypresses that may change the page.',
616
810
  inputSchema: BrowserPressInputSchema,
811
+ outputSchema: BrowserActionOutputSchema,
617
812
  annotations: annotations("Press Keys")
618
813
  },
619
814
  async (input) => {
620
815
  const res = await req("POST", `/agent/sessions/${input.session_id}/press`, { keys: input.keys });
621
- return textResult(res.data, !res.ok);
816
+ return actionResult("browser_press", input.session_id, res.ok, res.data, "browser_screenshot");
622
817
  }
623
818
  );
624
819
  server.registerTool(
@@ -627,11 +822,21 @@ function registerBrowserAgentMcpTools(server, opts) {
627
822
  title: "Start Recording",
628
823
  description: "Start recording an MP4 replay of the session. Returns replay_id, view_url when available, and a download_url. Use to capture a task for later review; stop with browser_replay_stop.",
629
824
  inputSchema: BrowserSessionInputSchema,
825
+ outputSchema: BrowserReplayStartOutputSchema,
630
826
  annotations: annotations("Start Recording")
631
827
  },
632
828
  async (input) => {
633
829
  const res = await req("POST", `/agent/sessions/${input.session_id}/replay/start`);
634
- return textResult(res.data, !res.ok);
830
+ if (!res.ok) return errorResult("browser_replay_start", res.data, input.session_id);
831
+ return structuredResult({
832
+ ok: true,
833
+ tool: "browser_replay_start",
834
+ session_id: input.session_id,
835
+ replay_id: res.data?.replay_id ?? res.data?.replayId ?? null,
836
+ view_url: res.data?.view_url ?? res.data?.viewUrl ?? null,
837
+ download_url: res.data?.download_url ?? res.data?.downloadUrl ?? null,
838
+ raw: res.data
839
+ });
635
840
  }
636
841
  );
637
842
  server.registerTool(
@@ -640,11 +845,21 @@ function registerBrowserAgentMcpTools(server, opts) {
640
845
  title: "Stop Recording",
641
846
  description: "Stop a replay recording and expose the final view_url and download_url. Use browser_replay_download to save the MP4 locally.",
642
847
  inputSchema: BrowserReplayStopInputSchema,
848
+ outputSchema: BrowserReplayStopOutputSchema,
643
849
  annotations: annotations("Stop Recording")
644
850
  },
645
851
  async (input) => {
646
852
  const res = await req("POST", `/agent/sessions/${input.session_id}/replay/stop`, { replay_id: input.replay_id });
647
- return textResult(res.data, !res.ok);
853
+ if (!res.ok) return errorResult("browser_replay_stop", res.data, input.session_id, input.replay_id);
854
+ return structuredResult({
855
+ ok: true,
856
+ tool: "browser_replay_stop",
857
+ session_id: input.session_id,
858
+ replay_id: res.data?.replay_id ?? res.data?.replayId ?? input.replay_id,
859
+ view_url: res.data?.view_url ?? res.data?.viewUrl ?? null,
860
+ download_url: res.data?.download_url ?? res.data?.downloadUrl ?? null,
861
+ raw: res.data
862
+ });
648
863
  }
649
864
  );
650
865
  server.registerTool(
@@ -653,11 +868,20 @@ function registerBrowserAgentMcpTools(server, opts) {
653
868
  title: "List Replay Videos",
654
869
  description: "List replay recordings for a browser session, including final view_url and authenticated download_url values when available.",
655
870
  inputSchema: BrowserSessionInputSchema,
871
+ outputSchema: BrowserListReplaysOutputSchema,
656
872
  annotations: annotations("List Replay Videos", true)
657
873
  },
658
874
  async (input) => {
659
875
  const res = await req("GET", `/agent/sessions/${input.session_id}/replays`);
660
- return textResult(res.data, !res.ok);
876
+ if (!res.ok) return errorResult("browser_list_replays", res.data, input.session_id);
877
+ const replays = Array.isArray(res.data?.replays) ? res.data.replays : [];
878
+ return structuredResult({
879
+ ok: true,
880
+ tool: "browser_list_replays",
881
+ session_id: input.session_id,
882
+ replays,
883
+ count: replays.length
884
+ });
661
885
  }
662
886
  );
663
887
  server.registerTool(
@@ -666,11 +890,22 @@ function registerBrowserAgentMcpTools(server, opts) {
666
890
  title: "Download Replay MP4",
667
891
  description: "Download a replay recording through MCP Scraper and save the MP4 locally under MCP_SCRAPER_OUTPUT_DIR/browser-replays. Use after browser_replay_stop or browser_list_replays.",
668
892
  inputSchema: BrowserReplayDownloadInputSchema,
669
- annotations: annotations("Download Replay MP4", true)
893
+ outputSchema: BrowserReplayDownloadOutputSchema,
894
+ annotations: annotations("Download Replay MP4")
670
895
  },
671
896
  async (input) => {
672
897
  const res = await downloadReplay(input.session_id, input.replay_id, input.filename);
673
- return textResult(res.data, !res.ok);
898
+ if (!res.ok) return errorResult("browser_replay_download", res.data, input.session_id, input.replay_id);
899
+ return structuredResult({
900
+ ok: true,
901
+ tool: "browser_replay_download",
902
+ session_id: input.session_id,
903
+ replay_id: input.replay_id,
904
+ file_path: res.data?.file_path ?? null,
905
+ bytes: typeof res.data?.bytes === "number" ? res.data.bytes : null,
906
+ mime_type: res.data?.mime_type ?? null,
907
+ download_url: res.data?.download_url ?? null
908
+ });
674
909
  }
675
910
  );
676
911
  server.registerTool(
@@ -679,19 +914,20 @@ function registerBrowserAgentMcpTools(server, opts) {
679
914
  title: "Mark Replay Annotation",
680
915
  description: "While a replay is actively recording, locate one exact DOM target and return a ready-to-use annotation object with DOM bounds and replay-relative timing. Use this instead of guessing start_seconds or drawing rough rectangles. Workflow: start browser_replay_start, navigate until the target is visible and stable, call browser_replay_mark for each callout, then stop the replay and pass the returned annotations to browser_replay_annotate.",
681
916
  inputSchema: BrowserReplayMarkInputSchema,
917
+ outputSchema: BrowserReplayMarkOutputSchema,
682
918
  annotations: annotations("Mark Replay Annotation", true)
683
919
  },
684
920
  async (input) => {
685
921
  const res = await req("POST", `/agent/sessions/${input.session_id}/locate`, { targets: [input.target] });
686
- if (!res.ok) return textResult(res.data, true);
922
+ if (!res.ok) return errorResult("browser_replay_mark", res.data, input.session_id);
687
923
  const target = res.data?.targets?.[0];
688
924
  const element = target?.element;
689
925
  const elapsed = res.data?.replay?.replay_elapsed_seconds;
690
926
  if (!target?.found || !element) {
691
- return textResult({ error: target?.error ?? "target not found in current viewport", target }, true);
927
+ return errorResult("browser_replay_mark", { error: target?.error ?? "target not found in current viewport", target }, input.session_id);
692
928
  }
693
929
  if (!finiteNumber2(elapsed)) {
694
- return textResult({ error: "no active replay clock found; call browser_replay_start before browser_replay_mark" }, true);
930
+ return errorResult("browser_replay_mark", { error: "no active replay clock found; call browser_replay_start before browser_replay_mark" }, input.session_id);
695
931
  }
696
932
  const padded = expandElementBounds(element, res.data?.viewport, input.padding ?? 8);
697
933
  const start = Math.max(0, elapsed + (input.start_offset_seconds ?? -0.25));
@@ -708,7 +944,11 @@ function registerBrowserAgentMcpTools(server, opts) {
708
944
  ...input.color ? { color: input.color } : {},
709
945
  ...input.thickness ? { thickness: input.thickness } : {}
710
946
  };
711
- return textResult({
947
+ return structuredResult({
948
+ ok: true,
949
+ tool: "browser_replay_mark",
950
+ session_id: input.session_id,
951
+ replay_id: res.data?.replay?.replay_id ?? res.data?.replay?.replayId ?? null,
712
952
  annotation,
713
953
  source_width: padded.sourceWidth,
714
954
  source_height: padded.sourceHeight,
@@ -724,12 +964,13 @@ function registerBrowserAgentMcpTools(server, opts) {
724
964
  title: "Annotate Replay MP4",
725
965
  description: "Download a browser replay MP4, render visual annotations over it, and save a new annotated MP4 locally. Use this after browser_replay_stop when the user wants proof videos with circles, boxes, arrows, underlines, or labels. For accurate timing and placement, prefer annotations returned by browser_replay_mark while the replay is recording; otherwise use exact left/top/width/height bounds from browser_locate. If the replay video size differs from the screenshot coordinate space, pass source_width and source_height.",
726
966
  inputSchema: BrowserReplayAnnotateInputSchema,
727
- annotations: annotations("Annotate Replay MP4", true)
967
+ outputSchema: BrowserReplayAnnotateOutputSchema,
968
+ annotations: annotations("Annotate Replay MP4")
728
969
  },
729
970
  async (input) => {
730
971
  const sourceName = input.filename ? `${input.filename}-source` : void 0;
731
972
  const downloaded = await downloadReplay(input.session_id, input.replay_id, sourceName);
732
- if (!downloaded.ok) return textResult(downloaded.data, true);
973
+ if (!downloaded.ok) return errorResult("browser_replay_annotate", downloaded.data, input.session_id, input.replay_id);
733
974
  try {
734
975
  const sourcePath = String(downloaded.data.file_path);
735
976
  const outputPath = annotatedReplayFilePath(input.session_id, input.replay_id, input.filename);
@@ -741,7 +982,10 @@ function registerBrowserAgentMcpTools(server, opts) {
741
982
  sourceLeftOffset: input.source_left_offset,
742
983
  sourceTopOffset: input.source_top_offset
743
984
  });
744
- return textResult({
985
+ return structuredResult({
986
+ ok: true,
987
+ tool: "browser_replay_annotate",
988
+ session_id: input.session_id,
745
989
  replay_id: input.replay_id,
746
990
  source_file_path: sourcePath,
747
991
  annotated_file_path: result.filePath,
@@ -752,7 +996,7 @@ function registerBrowserAgentMcpTools(server, opts) {
752
996
  mime_type: "video/mp4"
753
997
  });
754
998
  } catch (err) {
755
- return textResult({ error: err instanceof Error ? err.message : String(err) }, true);
999
+ return errorResult("browser_replay_annotate", { error: err instanceof Error ? err.message : String(err) }, input.session_id, input.replay_id);
756
1000
  }
757
1001
  }
758
1002
  );
@@ -760,28 +1004,43 @@ function registerBrowserAgentMcpTools(server, opts) {
760
1004
  "browser_close",
761
1005
  {
762
1006
  title: "Close Browser Session",
763
- description: "Close and release the browser session when the task is done.",
1007
+ description: "Close and release a browser session when the task is done, when the user asks to stop the browser, or when active browser billing should end. Use browser_list_sessions first if you need to recover the session_id.",
764
1008
  inputSchema: BrowserSessionInputSchema,
1009
+ outputSchema: BrowserCloseOutputSchema,
765
1010
  annotations: { title: "Close Browser Session", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
766
1011
  },
767
1012
  async (input) => {
768
1013
  const res = await req("DELETE", `/agent/sessions/${input.session_id}`);
769
- return textResult(res.data, !res.ok);
1014
+ if (!res.ok) return errorResult("browser_close", res.data, input.session_id);
1015
+ return structuredResult({
1016
+ ok: true,
1017
+ tool: "browser_close",
1018
+ session_id: input.session_id,
1019
+ closed: true,
1020
+ raw: res.data
1021
+ });
770
1022
  }
771
1023
  );
772
1024
  server.registerTool(
773
1025
  "browser_list_sessions",
774
1026
  {
775
1027
  title: "List Browser Sessions",
776
- description: "List your browser sessions and their status, with a watch_url for each.",
1028
+ description: "List browser sessions and their status, with a watch_url for each. Use this to recover a session_id, find an active browser, or decide which session to close. Use browser_open to create a new session.",
777
1029
  inputSchema: BrowserListInputSchema,
1030
+ outputSchema: BrowserListSessionsOutputSchema,
778
1031
  annotations: annotations("List Browser Sessions", true)
779
1032
  },
780
1033
  async (input) => {
781
1034
  const res = await req("GET", `/agent/sessions${input.include_closed ? "?all=1" : ""}`);
782
- if (!res.ok) return textResult(res.data, true);
1035
+ if (!res.ok) return errorResult("browser_list_sessions", res.data);
783
1036
  const sessions = (res.data.sessions ?? []).map((s) => ({ ...s, watch_url: `${consoleBase}/console/${s.session_id}` }));
784
- return textResult({ sessions });
1037
+ return structuredResult({
1038
+ ok: true,
1039
+ tool: "browser_list_sessions",
1040
+ session_id: null,
1041
+ sessions,
1042
+ count: sessions.length
1043
+ });
785
1044
  }
786
1045
  );
787
1046
  }
@@ -790,4 +1049,4 @@ export {
790
1049
  buildBrowserAgentMcpServer,
791
1050
  registerBrowserAgentMcpTools
792
1051
  };
793
- //# sourceMappingURL=chunk-P5PLQU3H.js.map
1052
+ //# sourceMappingURL=chunk-L4OWOUGR.js.map