@zenrows/mcp 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.d.ts CHANGED
@@ -1,2 +1,24 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export interface ScrapeParams {
3
+ url: string;
4
+ js_render?: boolean | null;
5
+ premium_proxy?: boolean | null;
6
+ proxy_country?: string | null;
7
+ response_type?: "markdown" | "plaintext" | "pdf" | "html" | null;
8
+ autoparse?: boolean | null;
9
+ css_extractor?: string | null;
10
+ wait_for?: string | null;
11
+ wait?: number | null;
12
+ js_instructions?: string | null;
13
+ outputs?: string | null;
14
+ screenshot?: boolean | null;
15
+ screenshot_fullpage?: boolean | null;
16
+ screenshot_selector?: string | null;
17
+ }
18
+ /**
19
+ * Builds the Fetch query string. Uses Adaptive Stealth Mode (mode=auto) unless the
20
+ * agent or the env forces js_render / premium_proxy, which the API won't combine
21
+ * with mode=auto. Mirrors the CLI's defaultMode "auto".
22
+ */
23
+ export declare function buildScrapeParams(apiKey: string, params: ScrapeParams): URLSearchParams;
2
24
  export declare function createServer(apiKey: string, clientName?: string): McpServer;
package/dist/server.js CHANGED
@@ -13,6 +13,54 @@ const ZENROWS_API_URL = "https://api.zenrows.com/v1/";
13
13
  const DEFAULT_JS_RENDER = process.env.ZENROWS_JS_RENDER === "true";
14
14
  const DEFAULT_PREMIUM_PROXY = process.env.ZENROWS_PREMIUM_PROXY === "true";
15
15
  const DEFAULT_RESPONSE_TYPE = process.env.ZENROWS_RESPONSE_TYPE ?? "markdown";
16
+ /**
17
+ * Builds the Fetch query string. Uses Adaptive Stealth Mode (mode=auto) unless the
18
+ * agent or the env forces js_render / premium_proxy, which the API won't combine
19
+ * with mode=auto. Mirrors the CLI's defaultMode "auto".
20
+ */
21
+ export function buildScrapeParams(apiKey, params) {
22
+ const searchParams = new URLSearchParams({
23
+ apikey: apiKey,
24
+ url: params.url,
25
+ });
26
+ const isScreenshot = !!(params.screenshot || params.screenshot_fullpage || params.screenshot_selector);
27
+ const jsRender = !!params.js_render || DEFAULT_JS_RENDER;
28
+ const premiumProxy = !!params.premium_proxy || DEFAULT_PREMIUM_PROXY;
29
+ if (!jsRender && !premiumProxy) {
30
+ searchParams.set("mode", "auto");
31
+ }
32
+ else {
33
+ if (jsRender || isScreenshot)
34
+ searchParams.set("js_render", "true");
35
+ if (premiumProxy)
36
+ searchParams.set("premium_proxy", "true");
37
+ }
38
+ if (params.proxy_country)
39
+ searchParams.set("proxy_country", params.proxy_country.toUpperCase());
40
+ if (params.autoparse)
41
+ searchParams.set("autoparse", "true");
42
+ if (params.css_extractor)
43
+ searchParams.set("css_extractor", params.css_extractor);
44
+ if (params.wait_for)
45
+ searchParams.set("wait_for", params.wait_for);
46
+ if (params.wait != null)
47
+ searchParams.set("wait", String(params.wait));
48
+ if (params.js_instructions)
49
+ searchParams.set("js_instructions", params.js_instructions);
50
+ if (params.outputs)
51
+ searchParams.set("outputs", params.outputs);
52
+ if (isScreenshot)
53
+ searchParams.set("screenshot", "true");
54
+ if (params.screenshot_fullpage)
55
+ searchParams.set("screenshot_fullpage", "true");
56
+ if (params.screenshot_selector)
57
+ searchParams.set("screenshot_selector", params.screenshot_selector);
58
+ const effectiveType = params.response_type ?? DEFAULT_RESPONSE_TYPE;
59
+ if (!params.autoparse && !params.css_extractor && !params.outputs && !isScreenshot && effectiveType !== "html") {
60
+ searchParams.set("response_type", effectiveType);
61
+ }
62
+ return searchParams;
63
+ }
16
64
  export function createServer(apiKey, clientName) {
17
65
  const server = new McpServer({
18
66
  name: "zenrows",
@@ -34,35 +82,38 @@ Use for full-page content (markdown/HTML/PDF/screenshot). For structured JSON
34
82
  fields (products, articles, listings), prefer the extract tool when it fits —
35
83
  it returns parsed fields instead of a full page body.
36
84
 
37
- When to enable options:
38
- - js_render: page uses React/Vue/Angular, loads content dynamically, or content
39
- appears missing on the first attempt
40
- - premium_proxy: site returns 403/blocked errors even with js_render enabled
41
- - wait_for: specific content loads after initial render (requires js_render)
85
+ By default the request uses Adaptive Stealth Mode: Zenrows picks JS rendering
86
+ and premium proxies per page, escalates only when the site blocks, and charges
87
+ only for the configuration that succeeds. Pass just the URL for protected,
88
+ dynamic, or blocked pages; do not turn on js_render or premium_proxy to get past
89
+ a block.
90
+
91
+ Set js_render or premium_proxy only to force a fixed configuration. Doing so
92
+ switches off Adaptive Stealth Mode, and every request is billed at that
93
+ configuration's cost (premium_proxy with js_render is 25x a basic request).
42
94
 
43
95
  Examples:
44
- Basic: { url: "https://example.com" }
45
- Dynamic: { url: "https://spa.com", js_render: true }
46
- Protected:{ url: "https://protected.com", js_render: true, premium_proxy: true }`,
96
+ Default: { url: "https://example.com" }
97
+ Geo: { url: "https://example.com", proxy_country: "US" }
98
+ Forced: { url: "https://spa.com", js_render: true }`,
47
99
  inputSchema: {
48
100
  url: z.string().url().describe("The webpage URL to scrape"),
49
101
  js_render: z
50
102
  .boolean()
51
103
  .nullish()
52
- .default(false)
53
- .describe("Enable JavaScript rendering via headless browser. Required for SPAs " +
54
- "(React, Vue, Angular) and pages that load content dynamically."),
104
+ .describe("Force JavaScript rendering on every request. Overrides Adaptive Stealth Mode, " +
105
+ "which already renders JavaScript when a page needs it. Leave unset unless you need a fixed configuration."),
55
106
  premium_proxy: z
56
107
  .boolean()
57
108
  .nullish()
58
- .default(false)
59
- .describe("Use premium residential proxies to bypass anti-bot protection. " +
60
- "Required for heavily protected sites. Implies higher credit cost."),
109
+ .describe("Force premium residential proxies on every request (10x credit cost). Overrides " +
110
+ "Adaptive Stealth Mode, which already escalates to premium proxies when a site blocks. " +
111
+ "Leave unset unless you need a fixed configuration."),
61
112
  proxy_country: z
62
113
  .string()
63
114
  .nullish()
64
115
  .describe("Country for geo-targeted scraping. ISO 3166-1 alpha-2 code (e.g. 'US', 'GB', 'DE'). " +
65
- "Requires premium_proxy=true."),
116
+ "Works in Adaptive Stealth Mode; if you set js_render without premium_proxy, it requires premium_proxy=true."),
66
117
  response_type: z
67
118
  .enum(["markdown", "plaintext", "pdf", "html"])
68
119
  .nullish()
@@ -87,7 +138,7 @@ Examples:
87
138
  .string()
88
139
  .nullish()
89
140
  .describe("CSS selector to wait for before capturing. Use when key content loads " +
90
- "after the initial page render. Requires js_render=true."),
141
+ "after the initial page render. Works in Adaptive Stealth Mode or with js_render=true."),
91
142
  wait: z
92
143
  .number()
93
144
  .int()
@@ -95,11 +146,11 @@ Examples:
95
146
  .max(30000)
96
147
  .nullish()
97
148
  .describe("Milliseconds to wait after page load before capturing content. " +
98
- "Max 30000 (30s). Requires js_render=true."),
149
+ "Max 30000 (30s). Works in Adaptive Stealth Mode or with js_render=true."),
99
150
  js_instructions: z
100
151
  .string()
101
152
  .nullish()
102
- .describe("JSON array of browser interactions to run before scraping. Requires js_render=true. " +
153
+ .describe("JSON array of browser interactions to run before scraping. Works in Adaptive Stealth Mode or with js_render=true. " +
103
154
  'Example: [{"click":"#load-more"},{"wait":1000},{"wait_for":".results"}]'),
104
155
  outputs: z
105
156
  .string()
@@ -124,45 +175,7 @@ Examples:
124
175
  'Example: ".product-card". Returns an image instead of text content.'),
125
176
  },
126
177
  }, async (params) => {
127
- const searchParams = new URLSearchParams({
128
- apikey: apiKey,
129
- url: params.url,
130
- });
131
- if (params.js_render ||
132
- DEFAULT_JS_RENDER ||
133
- params.screenshot ||
134
- params.screenshot_fullpage ||
135
- params.screenshot_selector)
136
- searchParams.set("js_render", "true");
137
- if (params.premium_proxy || DEFAULT_PREMIUM_PROXY)
138
- searchParams.set("premium_proxy", "true");
139
- if (params.proxy_country)
140
- searchParams.set("proxy_country", params.proxy_country.toUpperCase());
141
- if (params.autoparse)
142
- searchParams.set("autoparse", "true");
143
- if (params.css_extractor)
144
- searchParams.set("css_extractor", params.css_extractor);
145
- if (params.wait_for)
146
- searchParams.set("wait_for", params.wait_for);
147
- if (params.wait != null)
148
- searchParams.set("wait", String(params.wait));
149
- if (params.js_instructions)
150
- searchParams.set("js_instructions", params.js_instructions);
151
- if (params.outputs)
152
- searchParams.set("outputs", params.outputs);
153
- if (params.screenshot || params.screenshot_fullpage || params.screenshot_selector)
154
- searchParams.set("screenshot", "true");
155
- if (params.screenshot_fullpage)
156
- searchParams.set("screenshot_fullpage", "true");
157
- if (params.screenshot_selector)
158
- searchParams.set("screenshot_selector", params.screenshot_selector);
159
- // response_type is mutually exclusive with autoparse, css_extractor, outputs, and screenshot params.
160
- // 'html' is the Zenrows default (no param); all other values are passed through.
161
- const isScreenshot = params.screenshot || params.screenshot_fullpage || params.screenshot_selector;
162
- const effectiveType = params.response_type ?? DEFAULT_RESPONSE_TYPE;
163
- if (!params.autoparse && !params.css_extractor && !params.outputs && !isScreenshot && effectiveType !== "html") {
164
- searchParams.set("response_type", effectiveType);
165
- }
178
+ const searchParams = buildScrapeParams(apiKey, params);
166
179
  let response;
167
180
  try {
168
181
  response = await fetch(`${ZENROWS_API_URL}?${searchParams}`, {
@@ -265,7 +278,7 @@ Examples:
265
278
  role: "user",
266
279
  content: {
267
280
  type: "text",
268
- text: `Scrape ${url} using the Zenrows MCP scrape tool with js_render set to true. The page requires JavaScript execution to load its content. Return the full rendered content in markdown format.`,
281
+ text: `Scrape ${url} using the Zenrows MCP scrape tool. Pass only the URL: Adaptive Stealth Mode renders JavaScript when the page needs it. Return the full rendered content in markdown format.`,
269
282
  },
270
283
  },
271
284
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenrows/mcp",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Zenrows MCP server — Fetch, Extract, Batch, and Browser Sessions for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {