mcpscraper-sdk 0.51.0 → 0.53.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/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 {
@@ -326,7 +411,8 @@ var MapsNamespace = class {
326
411
  }
327
412
  r;
328
413
  search(params, options = {}) {
329
- return this.r.call("POST", "/maps/search", params, {}, options);
414
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : {};
415
+ return this.r.call("POST", "/maps/search", params, headers, options);
330
416
  }
331
417
  place(params, options = {}) {
332
418
  const headers = {
@@ -372,8 +458,17 @@ var DirectoryNamespace = class {
372
458
  this.r = r;
373
459
  }
374
460
  r;
375
- run(params) {
376
- return this.r.call("POST", "/directory/run", params);
461
+ run(params, options = {}) {
462
+ return this.r.call(
463
+ "POST",
464
+ "/directory/run",
465
+ params,
466
+ options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : {},
467
+ options
468
+ );
469
+ }
470
+ status(jobId, options = {}) {
471
+ return this.r.call("GET", `/directory/jobs/${encodeURIComponent(jobId)}`, void 0, {}, options);
377
472
  }
378
473
  };
379
474
  var SerpIntelligenceNamespace = class {
@@ -398,7 +493,8 @@ var SerpIntelligenceNamespace = class {
398
493
  );
399
494
  return {
400
495
  data: result.data,
401
- 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") } : {}
402
498
  };
403
499
  }
404
500
  pageSnapshots(params) {
@@ -538,7 +634,7 @@ var ScraperClient = class {
538
634
  constructor(options) {
539
635
  const requestTimeoutMs = options.requestTimeoutMs ?? 59e4;
540
636
  if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs <= 0) throw new TypeError("requestTimeoutMs must be a positive number");
541
- 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);
542
638
  this.youtube = new YoutubeNamespace(this.r);
543
639
  this.screenshot = new ScreenshotNamespace(this.r);
544
640
  this.facebook = new FacebookNamespace(this.r);
@@ -556,17 +652,19 @@ var ScraperClient = class {
556
652
  apiKey: options.apiKey,
557
653
  baseUrl: options.baseUrl ?? "https://mcpscraper.dev",
558
654
  fetch: options.fetch,
559
- requestTimeoutMs
655
+ requestTimeoutMs,
656
+ clientAttribution: options.clientAttribution,
657
+ onResponse: options.onResponse
560
658
  });
561
659
  }
562
660
  async callMemoryTool(toolName, args) {
563
- const result = await this.r.call("POST", "/memory/mcp-call", {
661
+ const { data: result, headers } = await this.r.callWithReceipt("POST", "/memory/mcp-call", {
564
662
  toolName,
565
663
  args: args ?? {}
566
664
  });
567
665
  if (result && typeof result === "object" && result.ok === false) {
568
666
  const failure = result;
569
- 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 });
570
668
  }
571
669
  return result;
572
670
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcpscraper-sdk",
3
- "version": "0.51.0",
4
- "description": "Official Node.js client for all 364 mcpscraper.dev MCP tools and the REST API",
3
+ "version": "0.53.0",
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",
@@ -19,7 +19,7 @@
19
19
  "README.md"
20
20
  ],
21
21
  "scripts": {
22
- "generate": "openapi-typescript ../../contracts/scraper.openapi.yaml -o src/schema.ts",
22
+ "generate": "openapi-typescript ../../contracts/scraper.openapi.generated.json -o src/schema.ts",
23
23
  "prepack": "npm run build",
24
24
  "build": "tsup src/index.ts --format esm,cjs --dts",
25
25
  "typecheck": "tsc --noEmit",
@@ -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.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "openapi-typescript": "^7.13.0"