scrapeunblocker-mcp 0.2.0 → 0.2.2

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 (2) hide show
  1. package/dist/index.js +61 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
  import { z } from "zod";
7
- import { ScrapeUnblockerClient } from "scrapeunblocker";
8
- var VERSION = "0.2.0";
7
+ import { APIError, ScrapeUnblockerClient } from "scrapeunblocker";
8
+ var VERSION = "0.2.2";
9
9
  var DEFAULT_BASE_URL = "https://api.scrapeunblocker.com";
10
10
  var API_KEY_HEADER = "x-scrapeunblocker-key";
11
11
  function resolveApiKey() {
@@ -28,6 +28,46 @@ function errorText(err) {
28
28
  if (err instanceof Error) return err.message;
29
29
  return String(err);
30
30
  }
31
+ var TARGET_GONE = /* @__PURE__ */ new Set([404, 410]);
32
+ function targetGoneStatus(status, originStatus) {
33
+ const origin = Number(originStatus);
34
+ if (TARGET_GONE.has(origin)) return origin;
35
+ return TARGET_GONE.has(status) ? status : null;
36
+ }
37
+ function targetGoneText(url, code, page) {
38
+ const reason = code === 410 ? "Gone" : "Not Found";
39
+ const head = `The target page does not exist: ${url} answered HTTP ${code} (${reason}). This is the website's own answer, not a block or an API failure. The call was billed, and retrying returns the same result - check the URL instead.`;
40
+ return page.trim() ? `${head}
41
+
42
+ The target's own ${code} page follows:
43
+
44
+ ${page}` : `${head}
45
+
46
+ The target sent no page body.`;
47
+ }
48
+ function isNoDataExtracted(status, body) {
49
+ if (status !== 422 || !body) return false;
50
+ try {
51
+ return JSON.parse(body)?.error === "no_data_extracted";
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+ function noDataExtractedText(url) {
57
+ return `No structured data could be extracted from ${url}: the page loaded, but nothing on it matched a structured shape. This call was not billed. To get the page itself, call fetch_html for the same URL.`;
58
+ }
59
+ function errorResult(url, err) {
60
+ if (err instanceof APIError) {
61
+ const gone = targetGoneStatus(err.statusCode);
62
+ if (gone !== null) {
63
+ return { content: [{ type: "text", text: targetGoneText(url, gone, err.body ?? "") }] };
64
+ }
65
+ if (isNoDataExtracted(err.statusCode, err.body)) {
66
+ return { content: [{ type: "text", text: noDataExtractedText(url) }] };
67
+ }
68
+ }
69
+ return { content: [{ type: "text", text: errorText(err) }], isError: true };
70
+ }
31
71
  async function getPageSourceRaw(params) {
32
72
  const apiKey = resolveApiKey();
33
73
  if (!apiKey) throw new Error(MISSING_KEY_MESSAGE);
@@ -46,7 +86,12 @@ async function getPageSourceRaw(params) {
46
86
  }
47
87
  });
48
88
  const text = await response.text();
49
- return { status: response.status, ok: response.ok, text };
89
+ return {
90
+ status: response.status,
91
+ ok: response.ok,
92
+ text,
93
+ originStatus: response.headers.get("x-origin-status")
94
+ };
50
95
  }
51
96
  var server = new McpServer({
52
97
  name: "scrapeunblocker",
@@ -159,6 +204,10 @@ server.registerTool(
159
204
  time_sleep: args.sleep_seconds,
160
205
  steps: JSON.stringify(args.steps)
161
206
  });
207
+ const gone = targetGoneStatus(res.status, res.originStatus);
208
+ if (gone !== null) {
209
+ return { content: [{ type: "text", text: targetGoneText(args.url, gone, res.text) }] };
210
+ }
162
211
  if (res.ok) {
163
212
  return { content: [{ type: "text", text: res.text }] };
164
213
  }
@@ -192,7 +241,7 @@ Fix the selector or step (use list_elements to inspect the page) and retry. The
192
241
  isError: true
193
242
  };
194
243
  } catch (err) {
195
- return { content: [{ type: "text", text: errorText(err) }], isError: true };
244
+ return errorResult(args.url, err);
196
245
  }
197
246
  }
198
247
  );
@@ -213,7 +262,8 @@ server.registerTool(
213
262
  list_elements: true,
214
263
  proxy_country: args.proxy_country
215
264
  });
216
- if (!res.ok) {
265
+ const gone = targetGoneStatus(res.status, res.originStatus);
266
+ if (!res.ok && gone === null) {
217
267
  return {
218
268
  content: [
219
269
  {
@@ -229,9 +279,12 @@ server.registerTool(
229
279
  out = JSON.stringify(JSON.parse(res.text), null, 2);
230
280
  } catch {
231
281
  }
282
+ if (gone !== null) {
283
+ return { content: [{ type: "text", text: targetGoneText(args.url, gone, out) }] };
284
+ }
232
285
  return { content: [{ type: "text", text: out }] };
233
286
  } catch (err) {
234
- return { content: [{ type: "text", text: errorText(err) }], isError: true };
287
+ return errorResult(args.url, err);
235
288
  }
236
289
  }
237
290
  );
@@ -239,7 +292,7 @@ server.registerTool(
239
292
  "fetch_parsed",
240
293
  {
241
294
  title: "Fetch AI-parsed page data",
242
- description: "Fetch a web page through ScrapeUnblocker and return AI-parsed structured JSON instead of raw HTML (e.g. product details, article content). Best for extracting fields from product, listing or article pages without writing your own HTML parsing.",
295
+ description: "Fetch a web page through ScrapeUnblocker and return AI-parsed structured JSON instead of raw HTML (e.g. product details, article content). Best for extracting fields from product, listing or article pages without writing your own HTML parsing. If the page holds no structured data, the result says so (that call is not billed) - use fetch_html for the page itself.",
243
296
  inputSchema: {
244
297
  url: z.string().url().describe("The absolute URL to fetch and parse."),
245
298
  proxy_country: z.string().length(2).optional().describe("Optional ISO country code to route through, e.g. 'US'."),
@@ -258,7 +311,7 @@ server.registerTool(
258
311
  content: [{ type: "text", text: JSON.stringify(parsed, null, 2) }]
259
312
  };
260
313
  } catch (err) {
261
- return { content: [{ type: "text", text: errorText(err) }], isError: true };
314
+ return errorResult(args.url, err);
262
315
  }
263
316
  }
264
317
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrapeunblocker-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "mcpName": "com.scrapeunblocker/scrapeunblocker-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for ScrapeUnblocker - lets Claude fetch any web page's HTML (or AI-parsed JSON) through the ScrapeUnblocker anti-bot API, using your own API key.",
6
6
  "keywords": [