@zenrows/mcp 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -163,8 +163,13 @@ Examples:
163
163
  }
164
164
  let response;
165
165
  try {
166
+ const clientName = server.server.getClientVersion()?.name;
166
167
  response = await fetch(`${ZENROWS_API_URL}?${searchParams}`, {
167
- headers: { "User-Agent": `zenrows/mcp ${pkg.version}` },
168
+ headers: {
169
+ "User-Agent": `zenrows/mcp ${pkg.version}`,
170
+ ...(clientName ? { "x-mcp-client-name": clientName } : {}),
171
+ "x-mcp-tool": "scrape",
172
+ },
168
173
  });
169
174
  }
170
175
  catch (err) {
@@ -261,6 +266,6 @@ Examples:
261
266
  ],
262
267
  }));
263
268
  const BROWSER_URL = process.env.ZENROWS_BROWSER_URL ?? "https://mcp.zenrows.com";
264
- registerBrowserTools(server, apiKey, BROWSER_URL);
269
+ registerBrowserTools(server, apiKey, BROWSER_URL, server);
265
270
  return server;
266
271
  }
@@ -3,5 +3,5 @@ export interface BrowserFetchResult {
3
3
  status: number;
4
4
  data: unknown;
5
5
  }
6
- export declare function browserFetch(method: string, path: string, apiKey: string, browserUrl: string, body?: unknown): Promise<BrowserFetchResult>;
6
+ export declare function browserFetch(method: string, path: string, apiKey: string, browserUrl: string, body?: unknown, clientName?: string, toolName?: string): Promise<BrowserFetchResult>;
7
7
  export declare function browserError(result: BrowserFetchResult): string;
@@ -1,9 +1,15 @@
1
1
  const REQUEST_TIMEOUT_MS = 35_000;
2
- export async function browserFetch(method, path, apiKey, browserUrl, body) {
2
+ export async function browserFetch(method, path, apiKey, browserUrl, body, clientName, toolName) {
3
3
  const url = `${browserUrl.replace(/\/$/, "")}${path}`;
4
+ const headers = {
5
+ Authorization: `Bearer ${apiKey}`,
6
+ "Content-Type": "application/json",
7
+ ...(clientName ? { "x-mcp-client-name": clientName } : {}),
8
+ ...(toolName ? { "x-mcp-tool": toolName } : {}),
9
+ };
4
10
  const init = {
5
11
  method,
6
- headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
12
+ headers,
7
13
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
8
14
  };
9
15
  if (body !== undefined) {
@@ -12,7 +18,8 @@ export async function browserFetch(method, path, apiKey, browserUrl, body) {
12
18
  const response = await fetch(url, init);
13
19
  let data;
14
20
  const contentType = response.headers.get("content-type") ?? "";
15
- data = contentType.includes("application/json") ? await response.json() : await response.text();
21
+ const text = await response.text();
22
+ data = contentType.includes("application/json") && text ? JSON.parse(text) : text;
16
23
  return { ok: response.ok, status: response.status, data };
17
24
  }
18
25
  export function browserError(result) {
@@ -1,2 +1,2 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- export declare function registerBrowserTools(server: McpServer, apiKey: string, browserUrl: string): void;
2
+ export declare function registerBrowserTools(server: McpServer, apiKey: string, browserUrl: string, mcpServer: McpServer): void;
@@ -10,8 +10,8 @@ function json(data) {
10
10
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
11
11
  }
12
12
  const sessionId = z.string().describe("Session ID returned by browser_navigate");
13
- export function registerBrowserTools(server, apiKey, browserUrl) {
14
- const fetch = (method, path, body) => browserFetch(method, path, apiKey, browserUrl, body);
13
+ export function registerBrowserTools(server, apiKey, browserUrl, mcpServer) {
14
+ const tfetch = (toolName) => (method, path, body) => browserFetch(method, path, apiKey, browserUrl, body, mcpServer.server.getClientVersion()?.name, toolName);
15
15
  // ─── Session ─────────────────────────────────────────────────────────────
16
16
  server.registerTool("browser_navigate", {
17
17
  annotations: { title: "Open Browser & Navigate", readOnlyHint: false, destructiveHint: false },
@@ -35,6 +35,7 @@ When to use options:
35
35
  proxy_region: z.string().optional().describe("World region code for geo-targeted proxy (eu=Europe, na=North America, ap=Asia Pacific, sa=South America, af=Africa, me=Middle East)"),
36
36
  },
37
37
  }, async (params) => {
38
+ const fetch = tfetch("browser_navigate");
38
39
  let result;
39
40
  try {
40
41
  result = await fetch("POST", "/browser/sessions", {
@@ -69,6 +70,7 @@ When to use options:
69
70
  description: "Close a browser session and free all associated resources. Always call this when done to release the browser slot.",
70
71
  inputSchema: { session_id: sessionId },
71
72
  }, async ({ session_id }) => {
73
+ const fetch = tfetch("browser_close");
72
74
  try {
73
75
  const result = await fetch("DELETE", `/browser/sessions/${session_id}`);
74
76
  if (!result.ok)
@@ -85,6 +87,7 @@ When to use options:
85
87
  description: "Navigate back to the previous page in the browser history.",
86
88
  inputSchema: { session_id: sessionId },
87
89
  }, async ({ session_id }) => {
90
+ const fetch = tfetch("browser_go_back");
88
91
  try {
89
92
  const result = await fetch("POST", `/browser/sessions/${session_id}/go_back`);
90
93
  if (!result.ok)
@@ -100,6 +103,7 @@ When to use options:
100
103
  description: "Navigate forward to the next page in the browser history.",
101
104
  inputSchema: { session_id: sessionId },
102
105
  }, async ({ session_id }) => {
106
+ const fetch = tfetch("browser_go_forward");
103
107
  try {
104
108
  const result = await fetch("POST", `/browser/sessions/${session_id}/go_forward`);
105
109
  if (!result.ok)
@@ -115,6 +119,7 @@ When to use options:
115
119
  description: "Reload the current page.",
116
120
  inputSchema: { session_id: sessionId },
117
121
  }, async ({ session_id }) => {
122
+ const fetch = tfetch("browser_reload");
118
123
  try {
119
124
  const result = await fetch("POST", `/browser/sessions/${session_id}/reload`);
120
125
  if (!result.ok)
@@ -134,6 +139,7 @@ When to use options:
134
139
  selector: z.string().describe("CSS selector of the element to click"),
135
140
  },
136
141
  }, async ({ session_id, selector }) => {
142
+ const fetch = tfetch("browser_click");
137
143
  try {
138
144
  const result = await fetch("POST", `/browser/sessions/${session_id}/click`, { selector });
139
145
  if (!result.ok)
@@ -152,6 +158,7 @@ When to use options:
152
158
  selector: z.string().describe("CSS selector of the element to hover over"),
153
159
  },
154
160
  }, async ({ session_id, selector }) => {
161
+ const fetch = tfetch("browser_hover");
155
162
  try {
156
163
  const result = await fetch("POST", `/browser/sessions/${session_id}/hover`, { selector });
157
164
  if (!result.ok)
@@ -172,6 +179,7 @@ When to use options:
172
179
  clear_first: z.boolean().optional().describe("Clear existing content before typing (default false)"),
173
180
  },
174
181
  }, async ({ session_id, selector, text, clear_first }) => {
182
+ const fetch = tfetch("browser_type");
175
183
  try {
176
184
  const result = await fetch("POST", `/browser/sessions/${session_id}/type`, { selector, text, clear_first });
177
185
  if (!result.ok)
@@ -191,6 +199,7 @@ When to use options:
191
199
  value: z.string().describe("Value to fill in"),
192
200
  },
193
201
  }, async ({ session_id, selector, value }) => {
202
+ const fetch = tfetch("browser_fill");
194
203
  try {
195
204
  const result = await fetch("POST", `/browser/sessions/${session_id}/fill`, { selector, value });
196
205
  if (!result.ok)
@@ -210,6 +219,7 @@ When to use options:
210
219
  value: z.string().describe("Value of the option to select"),
211
220
  },
212
221
  }, async ({ session_id, selector, value }) => {
222
+ const fetch = tfetch("browser_select_option");
213
223
  try {
214
224
  const result = await fetch("POST", `/browser/sessions/${session_id}/select`, { selector, value });
215
225
  if (!result.ok)
@@ -228,6 +238,7 @@ When to use options:
228
238
  selector: z.string().describe("CSS selector of the checkbox or radio element"),
229
239
  },
230
240
  }, async ({ session_id, selector }) => {
241
+ const fetch = tfetch("browser_check");
231
242
  try {
232
243
  const result = await fetch("POST", `/browser/sessions/${session_id}/check`, { selector });
233
244
  if (!result.ok)
@@ -246,6 +257,7 @@ When to use options:
246
257
  selector: z.string().describe("CSS selector of the checkbox element"),
247
258
  },
248
259
  }, async ({ session_id, selector }) => {
260
+ const fetch = tfetch("browser_uncheck");
249
261
  try {
250
262
  const result = await fetch("POST", `/browser/sessions/${session_id}/uncheck`, { selector });
251
263
  if (!result.ok)
@@ -264,6 +276,7 @@ When to use options:
264
276
  selector: z.string().describe("CSS selector of the element to focus"),
265
277
  },
266
278
  }, async ({ session_id, selector }) => {
279
+ const fetch = tfetch("browser_focus");
267
280
  try {
268
281
  const result = await fetch("POST", `/browser/sessions/${session_id}/focus`, { selector });
269
282
  if (!result.ok)
@@ -282,6 +295,7 @@ When to use options:
282
295
  key: z.string().describe('Key to press (e.g. "Enter", "Tab", "Escape", "ArrowDown", "Control+a")'),
283
296
  },
284
297
  }, async ({ session_id, key }) => {
298
+ const fetch = tfetch("browser_press_key");
285
299
  try {
286
300
  const result = await fetch("POST", `/browser/sessions/${session_id}/press_key`, { key });
287
301
  if (!result.ok)
@@ -301,6 +315,7 @@ When to use options:
301
315
  distance: z.number().int().positive().optional().describe("Pixels to scroll (default 500)"),
302
316
  },
303
317
  }, async ({ session_id, direction, distance }) => {
318
+ const fetch = tfetch("browser_scroll");
304
319
  try {
305
320
  const result = await fetch("POST", `/browser/sessions/${session_id}/scroll`, { direction, distance });
306
321
  if (!result.ok)
@@ -320,6 +335,7 @@ When to use options:
320
335
  target_selector: z.string().describe("CSS selector of the drop target"),
321
336
  },
322
337
  }, async ({ session_id, source_selector, target_selector }) => {
338
+ const fetch = tfetch("browser_drag");
323
339
  try {
324
340
  const result = await fetch("POST", `/browser/sessions/${session_id}/drag`, {
325
341
  source_selector,
@@ -346,6 +362,7 @@ The tree shows all interactive elements (buttons, inputs, links) with their role
346
362
  labels, and states — everything needed to drive browser interactions.`,
347
363
  inputSchema: { session_id: sessionId },
348
364
  }, async ({ session_id }) => {
365
+ const fetch = tfetch("browser_get_accessibility_tree");
349
366
  try {
350
367
  const result = await fetch("GET", `/browser/sessions/${session_id}/accessibility_tree`);
351
368
  if (!result.ok)
@@ -362,6 +379,7 @@ labels, and states — everything needed to drive browser interactions.`,
362
379
  description: "Get the current URL of the browser session.",
363
380
  inputSchema: { session_id: sessionId },
364
381
  }, async ({ session_id }) => {
382
+ const fetch = tfetch("browser_get_url");
365
383
  try {
366
384
  const result = await fetch("GET", `/browser/sessions/${session_id}/url`);
367
385
  if (!result.ok)
@@ -377,6 +395,7 @@ labels, and states — everything needed to drive browser interactions.`,
377
395
  description: "Get the title of the current page.",
378
396
  inputSchema: { session_id: sessionId },
379
397
  }, async ({ session_id }) => {
398
+ const fetch = tfetch("browser_get_title");
380
399
  try {
381
400
  const result = await fetch("GET", `/browser/sessions/${session_id}/title`);
382
401
  if (!result.ok)
@@ -395,6 +414,7 @@ labels, and states — everything needed to drive browser interactions.`,
395
414
  selector: z.string().optional().describe("CSS selector of the element (omit for full page body text)"),
396
415
  },
397
416
  }, async ({ session_id, selector }) => {
417
+ const fetch = tfetch("browser_get_text");
398
418
  try {
399
419
  const result = await fetch("POST", `/browser/sessions/${session_id}/get_text`, { selector });
400
420
  if (!result.ok)
@@ -415,6 +435,7 @@ labels, and states — everything needed to drive browser interactions.`,
415
435
  attribute: z.string().describe("Attribute name to retrieve (e.g. 'href', 'src', 'data-id')"),
416
436
  },
417
437
  }, async ({ session_id, selector, attribute }) => {
438
+ const fetch = tfetch("browser_get_attribute");
418
439
  try {
419
440
  const result = await fetch("POST", `/browser/sessions/${session_id}/get_attribute`, { selector, attribute });
420
441
  if (!result.ok)
@@ -433,6 +454,7 @@ labels, and states — everything needed to drive browser interactions.`,
433
454
  selector: z.string().optional().describe("CSS selector of the element (omit for full page HTML)"),
434
455
  },
435
456
  }, async ({ session_id, selector }) => {
457
+ const fetch = tfetch("browser_get_html");
436
458
  try {
437
459
  const result = await fetch("POST", `/browser/sessions/${session_id}/get_html`, { selector });
438
460
  if (!result.ok)
@@ -452,6 +474,7 @@ labels, and states — everything needed to drive browser interactions.`,
452
474
  selector: z.string().describe("CSS selector to query"),
453
475
  },
454
476
  }, async ({ session_id, selector }) => {
477
+ const fetch = tfetch("browser_query_selector_all");
455
478
  try {
456
479
  const result = await fetch("POST", `/browser/sessions/${session_id}/query_selector_all`, { selector });
457
480
  if (!result.ok)
@@ -475,6 +498,7 @@ labels, and states — everything needed to drive browser interactions.`,
475
498
  .describe("CSS selector to capture only a specific element (overrides full_page)"),
476
499
  },
477
500
  }, async ({ session_id, full_page, selector }) => {
501
+ const fetch = tfetch("browser_screenshot");
478
502
  try {
479
503
  const result = await fetch("POST", `/browser/sessions/${session_id}/screenshot`, { full_page, selector });
480
504
  if (!result.ok)
@@ -504,6 +528,7 @@ labels, and states — everything needed to drive browser interactions.`,
504
528
  scale: z.number().min(0.1).max(2).optional().describe("Page scale factor (default 1)"),
505
529
  },
506
530
  }, async ({ session_id, print_background, landscape, scale }) => {
531
+ const fetch = tfetch("browser_generate_pdf");
507
532
  try {
508
533
  const result = await fetch("POST", `/browser/sessions/${session_id}/generate_pdf`, { print_background, landscape, scale });
509
534
  if (!result.ok)
@@ -536,6 +561,7 @@ labels, and states — everything needed to drive browser interactions.`,
536
561
  visible: z.boolean().optional().describe("Also require the element to be visible, not just present in the DOM (default false)"),
537
562
  },
538
563
  }, async ({ session_id, selector, visible }) => {
564
+ const fetch = tfetch("browser_wait_for_selector");
539
565
  try {
540
566
  const result = await fetch("POST", `/browser/sessions/${session_id}/wait_for_selector`, { selector, visible });
541
567
  if (!result.ok)
@@ -554,6 +580,7 @@ labels, and states — everything needed to drive browser interactions.`,
554
580
  timeout_ms: z.number().int().min(1000).max(60000).optional().describe("How long to wait in milliseconds (default 30000, max 60000)"),
555
581
  },
556
582
  }, async ({ session_id, timeout_ms }) => {
583
+ const fetch = tfetch("browser_wait_for_navigation");
557
584
  try {
558
585
  const result = await fetch("POST", `/browser/sessions/${session_id}/wait_for_navigation`, timeout_ms ? { timeout_ms } : undefined);
559
586
  if (!result.ok)
@@ -572,6 +599,7 @@ labels, and states — everything needed to drive browser interactions.`,
572
599
  ms: z.number().int().min(0).max(30000).describe("Milliseconds to wait (max 30000)"),
573
600
  },
574
601
  }, async ({ session_id, ms }) => {
602
+ const fetch = tfetch("browser_wait");
575
603
  try {
576
604
  const result = await fetch("POST", `/browser/sessions/${session_id}/wait`, { ms });
577
605
  if (!result.ok)
@@ -591,6 +619,7 @@ labels, and states — everything needed to drive browser interactions.`,
591
619
  script: z.string().describe("JavaScript expression or function body to evaluate in the page context"),
592
620
  },
593
621
  }, async ({ session_id, script }) => {
622
+ const fetch = tfetch("browser_evaluate");
594
623
  try {
595
624
  const result = await fetch("POST", `/browser/sessions/${session_id}/evaluate`, { script });
596
625
  if (!result.ok)
@@ -607,6 +636,7 @@ labels, and states — everything needed to drive browser interactions.`,
607
636
  description: "Get all cookies for the current page.",
608
637
  inputSchema: { session_id: sessionId },
609
638
  }, async ({ session_id }) => {
639
+ const fetch = tfetch("browser_get_cookies");
610
640
  try {
611
641
  const result = await fetch("GET", `/browser/sessions/${session_id}/cookies`);
612
642
  if (!result.ok)
@@ -635,6 +665,7 @@ labels, and states — everything needed to drive browser interactions.`,
635
665
  .describe("Array of cookie objects to set"),
636
666
  },
637
667
  }, async ({ session_id, cookies }) => {
668
+ const fetch = tfetch("browser_set_cookies");
638
669
  try {
639
670
  // Remap http_only → httpOnly to match Chrome DevTools Protocol / Rod's JSON field names.
640
671
  const mapped = cookies.map(({ http_only, ...rest }) => ({ ...rest, httpOnly: http_only }));
@@ -652,6 +683,7 @@ labels, and states — everything needed to drive browser interactions.`,
652
683
  description: "Clear all cookies for the current browser session.",
653
684
  inputSchema: { session_id: sessionId },
654
685
  }, async ({ session_id }) => {
686
+ const fetch = tfetch("browser_clear_cookies");
655
687
  try {
656
688
  const result = await fetch("DELETE", `/browser/sessions/${session_id}/cookies`);
657
689
  if (!result.ok)
@@ -673,6 +705,7 @@ labels, and states — everything needed to drive browser interactions.`,
673
705
  value: z.string().optional().describe("Value to store (required for set)"),
674
706
  },
675
707
  }, async ({ session_id, action, key, value }) => {
708
+ const fetch = tfetch("browser_local_storage");
676
709
  try {
677
710
  const result = await fetch("POST", `/browser/sessions/${session_id}/local_storage`, { action, key, value });
678
711
  if (!result.ok)
@@ -692,6 +725,7 @@ labels, and states — everything needed to drive browser interactions.`,
692
725
  url: z.string().url().optional().describe("URL to open in the new tab (opens blank tab if omitted)"),
693
726
  },
694
727
  }, async ({ session_id, url }) => {
728
+ const fetch = tfetch("browser_new_tab");
695
729
  try {
696
730
  const result = await fetch("POST", `/browser/sessions/${session_id}/new_tab`, { url });
697
731
  if (!result.ok)
@@ -710,6 +744,7 @@ labels, and states — everything needed to drive browser interactions.`,
710
744
  tab_id: z.string().describe("Tab ID returned by browser_new_tab"),
711
745
  },
712
746
  }, async ({ session_id, tab_id }) => {
747
+ const fetch = tfetch("browser_switch_tab");
713
748
  try {
714
749
  const result = await fetch("POST", `/browser/sessions/${session_id}/switch_tab`, { tab_id });
715
750
  if (!result.ok)
@@ -750,7 +785,7 @@ labels, and states — everything needed to drive browser interactions.`,
750
785
  z.object({ type: z.literal("wait"), ms: z.number().int().min(0).max(30000) }),
751
786
  z.object({ type: z.literal("evaluate"), script: z.string() }),
752
787
  ]);
753
- async function runAction(action, session_id) {
788
+ async function runAction(action, session_id, fetch) {
754
789
  const sid = session_id;
755
790
  switch (action.type) {
756
791
  case "navigate": {
@@ -938,11 +973,12 @@ For proper image rendering, use browser_screenshot directly.`,
938
973
  stop_on_error: z.boolean().optional().describe("Stop executing on the first failed action (default true)"),
939
974
  },
940
975
  }, async ({ session_id, actions, stop_on_error = true }) => {
976
+ const fetch = tfetch("browser_batch");
941
977
  const results = [];
942
978
  for (let i = 0; i < actions.length; i++) {
943
979
  const action = actions[i];
944
980
  try {
945
- const result = await runAction(action, session_id);
981
+ const result = await runAction(action, session_id, fetch);
946
982
  results.push({ step: i, type: action.type, result });
947
983
  }
948
984
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenrows/mcp",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "ZenRows MCP server — Universal Scraper API for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {