mcpscraper-sdk 0.52.0 → 0.53.1

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/index.js CHANGED
@@ -40,7 +40,29 @@ var PUBLIC_ERROR_CODES = /* @__PURE__ */ new Set([
40
40
  "site_export_not_found",
41
41
  "site_export_format_unavailable",
42
42
  "site_export_read_failed",
43
- "site_export_image_not_found"
43
+ "site_export_image_not_found",
44
+ "directory_job_not_found",
45
+ "idempotency_in_progress",
46
+ "idempotency_conflict",
47
+ "image_url_invalid",
48
+ "image_url_not_https",
49
+ "image_host_unresolvable",
50
+ "image_host_blocked",
51
+ "image_redirect_invalid",
52
+ "image_source_unavailable",
53
+ "image_too_large",
54
+ "image_redirect_limit",
55
+ "image_base64_invalid",
56
+ "image_source_missing",
57
+ "image_source_ambiguous",
58
+ "image_source_type_invalid",
59
+ "image_artifact_not_found",
60
+ "image_page_too_large",
61
+ "page_image_not_found",
62
+ "page_image_candidates_unusable",
63
+ "image_format_unsupported",
64
+ "image_storage_unconfigured",
65
+ "featured_image_unhostable"
44
66
  ]);
45
67
  var PUBLIC_ERROR_FIELDS = /* @__PURE__ */ new Set([
46
68
  "error",
@@ -71,13 +93,51 @@ var PUBLIC_DETAIL_FIELDS = /* @__PURE__ */ new Set([
71
93
  "slots_per_pack",
72
94
  "pack_price_usd",
73
95
  "balance_credits",
74
- "required_credits"
96
+ "required_credits",
97
+ "retry_guidance",
98
+ "http_status",
99
+ "job_id"
75
100
  ]);
101
+ function boundedString(value, limit) {
102
+ return typeof value === "string" && value.length > 0 && value.length <= limit;
103
+ }
104
+ function publicRecoveryFields(body) {
105
+ if (!body || typeof body !== "object" || Array.isArray(body)) return {};
106
+ const row = body;
107
+ const safe = {};
108
+ if (boundedString(row.idempotencyKey, 512)) safe.idempotencyKey = row.idempotencyKey;
109
+ if (typeof row.idempotencyFingerprint === "string" && /^[a-f0-9]{16}$/.test(row.idempotencyFingerprint)) safe.idempotencyFingerprint = row.idempotencyFingerprint;
110
+ const operation = row.operation;
111
+ if (operation && typeof operation === "object" && !Array.isArray(operation)) {
112
+ const op = operation;
113
+ if ((op.runId === null || boundedString(op.runId, 200)) && boundedString(op.idempotencyKey, 512) && boundedString(op.state, 100)) {
114
+ safe.operation = { runId: op.runId, idempotencyKey: op.idempotencyKey, state: op.state };
115
+ }
116
+ }
117
+ const recovery = row.recovery;
118
+ if (recovery && typeof recovery === "object" && !Array.isArray(recovery)) {
119
+ const receipt = recovery;
120
+ if (typeof receipt.jobId === "string" && /^[a-zA-Z0-9-]{1,100}$/.test(receipt.jobId) && typeof receipt.statusTool === "string" && /^[a-z][a-z0-9_]{0,99}$/.test(receipt.statusTool) && receipt.executionState === "unknown" && ["held", "unconfirmed"].includes(String(receipt.holdState))) {
121
+ safe.recovery = { jobId: receipt.jobId, statusTool: receipt.statusTool, executionState: receipt.executionState, holdState: receipt.holdState };
122
+ }
123
+ }
124
+ if (typeof row.job_id === "string" && /^[a-zA-Z0-9-]{1,100}$/.test(row.job_id)) safe.job_id = row.job_id;
125
+ if (typeof row.status_tool === "string" && /^[a-z][a-z0-9_]{0,99}$/.test(row.status_tool)) safe.status_tool = row.status_tool;
126
+ if (row.executionState === "unknown") safe.executionState = row.executionState;
127
+ if (row.hold_state === "held" || row.hold_state === "unconfirmed") safe.hold_state = row.hold_state;
128
+ if (typeof row.runId === "string" && /^[a-zA-Z0-9_-]{1,200}$/.test(row.runId)) {
129
+ safe.runId = row.runId;
130
+ if (["queued", "running", "interrupted", "complete", "partial", "failed", "cancelled"].includes(String(row.runStatus))) safe.runStatus = row.runStatus;
131
+ if (["unreserved", "held", "settling", "settled", "refunded", "pending_reconciliation"].includes(String(row.billingState))) safe.billingState = row.billingState;
132
+ if (row.statusPath === `/maps/place/runs/${row.runId}`) safe.statusPath = row.statusPath;
133
+ }
134
+ return safe;
135
+ }
76
136
  function recognizedPublicDetails(value) {
77
137
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
78
138
  const details = Object.fromEntries(
79
139
  Object.entries(value).filter(
80
- ([key, child]) => PUBLIC_DETAIL_FIELDS.has(key) && (child === null || ["string", "number", "boolean"].includes(typeof child))
140
+ ([key, child]) => PUBLIC_DETAIL_FIELDS.has(key) && (child === null || typeof child === "boolean" || typeof child === "number" && Number.isFinite(child) || typeof child === "string" && child.length <= (key === "job_id" ? 200 : 500))
81
141
  )
82
142
  );
83
143
  return Object.keys(details).length > 0 ? details : void 0;
@@ -87,10 +147,13 @@ function recognizedPublicErrorBody(body) {
87
147
  const record = body;
88
148
  const code = readString(record, "error_code");
89
149
  if (!code || !PUBLIC_ERROR_CODES.has(code)) return void 0;
90
- const safe = Object.fromEntries(Object.entries(record).filter(([key]) => PUBLIC_ERROR_FIELDS.has(key)));
150
+ const safe = Object.fromEntries(Object.entries(record).filter(([key, value]) => PUBLIC_ERROR_FIELDS.has(key) && (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) || typeof value === "string" && value.length <= 1e3)));
151
+ Object.assign(safe, publicRecoveryFields(record));
91
152
  const details = recognizedPublicDetails(record.details);
92
153
  if (details) safe.details = details;
93
154
  else delete safe.details;
155
+ const upgrade = recognizedPublicDetails(record.upgrade);
156
+ if (upgrade) safe.upgrade = upgrade;
94
157
  return safe;
95
158
  }
96
159
  function publicErrorBody(status, body) {
@@ -103,20 +166,23 @@ function publicErrorBody(status, body) {
103
166
  error_type: "service_unavailable",
104
167
  retryable: true,
105
168
  status,
106
- message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE
169
+ message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,
170
+ ...publicRecoveryFields(body)
107
171
  };
108
172
  }
109
173
  var ScraperApiError = class extends Error {
110
174
  status;
111
175
  code;
112
176
  body;
113
- constructor(status, body) {
177
+ requestId;
178
+ constructor(status, body, details = {}) {
114
179
  const safeBody = publicErrorBody(status, body);
115
180
  super(readString(safeBody, "message") ?? `mcpscraper.dev API request failed with status ${status}`);
116
181
  this.name = "ScraperApiError";
117
182
  this.status = status;
118
183
  this.code = readString(safeBody, "error_code") ?? readString(safeBody, "error");
119
184
  this.body = safeBody;
185
+ this.requestId = details.requestId;
120
186
  }
121
187
  isInsufficientBalance() {
122
188
  return this.code === "insufficient_balance";
@@ -153,22 +219,35 @@ import {
153
219
  VaultsNamespace,
154
220
  VideoNamespace as MemoryVideoNamespace,
155
221
  WebhooksNamespace,
156
- McpToolsClient
222
+ McpToolsClient,
223
+ clientAttributionHeaders,
224
+ notifyResponse,
225
+ responseMetadata
157
226
  } from "mcpscraper-memory-sdk";
158
227
  var Requester = class {
159
- constructor(apiKey, baseUrl, fetchImpl, timeoutMs) {
228
+ constructor(apiKey, baseUrl, fetchImpl, timeoutMs, metadataOptions = {}) {
160
229
  this.apiKey = apiKey;
161
230
  this.baseUrl = baseUrl;
162
231
  this.fetchImpl = fetchImpl;
163
232
  this.timeoutMs = timeoutMs;
233
+ this.metadataOptions = metadataOptions;
164
234
  }
165
235
  apiKey;
166
236
  baseUrl;
167
237
  fetchImpl;
168
238
  timeoutMs;
239
+ metadataOptions;
240
+ headers(extra = {}) {
241
+ return { ...clientAttributionHeaders(this.metadataOptions.clientAttribution), ...extra, "x-api-key": this.apiKey };
242
+ }
243
+ received(res) {
244
+ notifyResponse(this.metadataOptions.onResponse, responseMetadata(res));
245
+ }
169
246
  async request(url, init) {
170
247
  try {
171
- return await this.fetchImpl(url, { ...init, signal: AbortSignal.timeout(this.timeoutMs) });
248
+ const response = await this.fetchImpl(url, { ...init, signal: AbortSignal.timeout(this.timeoutMs) });
249
+ this.received(response);
250
+ return response;
172
251
  } catch (error) {
173
252
  if (error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError")) {
174
253
  throw new ScraperApiError(408, {
@@ -193,14 +272,14 @@ var Requester = class {
193
272
  try {
194
273
  res = await this.fetchImpl(`${this.baseUrl}${path}`, {
195
274
  method,
196
- headers: {
197
- "x-api-key": this.apiKey,
275
+ headers: this.headers({
198
276
  ...body !== void 0 ? { "content-type": "application/json" } : {},
199
277
  ...extraHeaders
200
- },
278
+ }),
201
279
  body: body !== void 0 ? JSON.stringify(body) : void 0,
202
280
  signal
203
281
  });
282
+ this.received(res);
204
283
  } catch (error) {
205
284
  if (error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError")) {
206
285
  if (options.signal?.aborted && !timeoutSignal.aborted) throw error;
@@ -215,14 +294,14 @@ var Requester = class {
215
294
  throw error;
216
295
  }
217
296
  const data = await res.json().catch(() => void 0);
218
- if (!res.ok) throw new ScraperApiError(res.status, data);
297
+ if (!res.ok) throw new ScraperApiError(res.status, data, { requestId: responseMetadata(res).requestId });
219
298
  return { data, headers: res.headers };
220
299
  }
221
300
  async callRaw(method, path) {
222
- const res = await this.request(`${this.baseUrl}${path}`, { method, headers: { "x-api-key": this.apiKey } });
301
+ const res = await this.request(`${this.baseUrl}${path}`, { method, headers: this.headers() });
223
302
  if (!res.ok) {
224
303
  const data = await res.json().catch(() => void 0);
225
- throw new ScraperApiError(res.status, data);
304
+ throw new ScraperApiError(res.status, data, { requestId: responseMetadata(res).requestId });
226
305
  }
227
306
  return res.arrayBuffer();
228
307
  }
@@ -304,8 +383,14 @@ var RedditNamespace = class {
304
383
  this.r = r;
305
384
  }
306
385
  r;
307
- thread(params) {
308
- return this.r.call("POST", "/reddit/thread", params);
386
+ thread(params, options = {}) {
387
+ return this.r.call(
388
+ "POST",
389
+ "/reddit/thread",
390
+ params,
391
+ options.idempotencyKey === void 0 ? {} : { "Idempotency-Key": options.idempotencyKey },
392
+ options
393
+ );
309
394
  }
310
395
  };
311
396
  var VideoNamespace = class {
@@ -408,7 +493,8 @@ var SerpIntelligenceNamespace = class {
408
493
  );
409
494
  return {
410
495
  data: result.data,
411
- idempotencyKey: result.headers.get("Idempotency-Key")
496
+ idempotencyKey: result.headers.get("Idempotency-Key"),
497
+ ...result.headers.get("x-mcp-request-id") ? { requestId: result.headers.get("x-mcp-request-id") } : {}
412
498
  };
413
499
  }
414
500
  pageSnapshots(params) {
@@ -548,7 +634,7 @@ var ScraperClient = class {
548
634
  constructor(options) {
549
635
  const requestTimeoutMs = options.requestTimeoutMs ?? 59e4;
550
636
  if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs <= 0) throw new TypeError("requestTimeoutMs must be a positive number");
551
- this.r = new Requester(options.apiKey, options.baseUrl ?? "https://mcpscraper.dev", options.fetch ?? globalThis.fetch, requestTimeoutMs);
637
+ this.r = new Requester(options.apiKey, options.baseUrl ?? "https://mcpscraper.dev", options.fetch ?? globalThis.fetch, requestTimeoutMs, options);
552
638
  this.youtube = new YoutubeNamespace(this.r);
553
639
  this.screenshot = new ScreenshotNamespace(this.r);
554
640
  this.facebook = new FacebookNamespace(this.r);
@@ -566,17 +652,19 @@ var ScraperClient = class {
566
652
  apiKey: options.apiKey,
567
653
  baseUrl: options.baseUrl ?? "https://mcpscraper.dev",
568
654
  fetch: options.fetch,
569
- requestTimeoutMs
655
+ requestTimeoutMs,
656
+ clientAttribution: options.clientAttribution,
657
+ onResponse: options.onResponse
570
658
  });
571
659
  }
572
660
  async callMemoryTool(toolName, args) {
573
- const result = await this.r.call("POST", "/memory/mcp-call", {
661
+ const { data: result, headers } = await this.r.callWithReceipt("POST", "/memory/mcp-call", {
574
662
  toolName,
575
663
  args: args ?? {}
576
664
  });
577
665
  if (result && typeof result === "object" && result.ok === false) {
578
666
  const failure = result;
579
- throw new ScraperApiError(200, { message: failure.error, ...failure });
667
+ throw new ScraperApiError(200, { message: failure.error, ...failure }, { requestId: headers.get("x-mcp-request-id") ?? void 0 });
580
668
  }
581
669
  return result;
582
670
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcpscraper-sdk",
3
- "version": "0.52.0",
4
- "description": "Official Node.js client for all 364 mcpscraper.dev MCP tools and the REST API",
3
+ "version": "0.53.1",
4
+ "description": "Official Node.js client for all 367 mcpscraper.dev MCP tools and the REST API",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
@@ -32,7 +32,7 @@
32
32
  "seo-api"
33
33
  ],
34
34
  "dependencies": {
35
- "mcpscraper-memory-sdk": "^0.51.0"
35
+ "mcpscraper-memory-sdk": "^0.52.1"
36
36
  },
37
37
  "devDependencies": {
38
38
  "openapi-typescript": "^7.13.0"