mcp-scraper 0.15.0 → 0.16.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.
@@ -37,7 +37,7 @@ import {
37
37
  renewConnectedDataArtifactDownload,
38
38
  sanitizeAttempts,
39
39
  sanitizeHarvestResult
40
- } from "./chunk-ETJBTYZX.js";
40
+ } from "./chunk-YIV4IKFG.js";
41
41
  import {
42
42
  auditImages,
43
43
  buildLinkReport,
@@ -73,7 +73,7 @@ import {
73
73
  RawMapsOverviewSchema,
74
74
  RawMapsReviewStatsSchema
75
75
  } from "./chunk-XGIPATLV.js";
76
- import "./chunk-TK2S2M7G.js";
76
+ import "./chunk-HQYIP5X3.js";
77
77
  import {
78
78
  completeExtractJob,
79
79
  countSuccessfulPages,
@@ -20356,6 +20356,10 @@ async function collectConnectedDataExport(args) {
20356
20356
  recordBytes += pageBytes;
20357
20357
  cursor = page.nextCursor ?? void 0;
20358
20358
  continuationCursor = page.nextCursor ?? null;
20359
+ if (page.complete === false && typeof beforePageCursor === "string" && page.nextCursor === beforePageCursor) {
20360
+ warnings.add("The provider could not advance this page after bounded retries. Its records and warnings were included once; retry this exact continuation later.");
20361
+ break;
20362
+ }
20359
20363
  if (page.complete || page.nextCursor === null || page.nextCursor === void 0) {
20360
20364
  complete = true;
20361
20365
  continuationCursor = null;
@@ -20629,6 +20633,7 @@ async function dbUsage(identity, plan) {
20629
20633
  }
20630
20634
 
20631
20635
  // src/api/nango-control.ts
20636
+ import { createHash as createHash5 } from "crypto";
20632
20637
  var DEFAULT_NANGO_CONTROL_URL = "https://mcp-scraper-scheduler.vercel.app";
20633
20638
  var DISABLED_NANGO_TOOLS = {
20634
20639
  "google-mail": /* @__PURE__ */ new Set(["list-filters"]),
@@ -20707,10 +20712,14 @@ function connectionSyncSelectionError(connections) {
20707
20712
  }
20708
20713
  var NangoControlError = class extends Error {
20709
20714
  status;
20710
- constructor(message, status = 502) {
20715
+ code;
20716
+ retryable;
20717
+ constructor(message, status = 502, code = status === 503 ? "connection_transport_unavailable" : "connection_control_failed", retryable = status === 429 || status >= 500) {
20711
20718
  super(message);
20712
20719
  this.name = "NangoControlError";
20713
20720
  this.status = status;
20721
+ this.code = code;
20722
+ this.retryable = retryable;
20714
20723
  }
20715
20724
  };
20716
20725
  var ScheduleConnectionValidationError = class extends Error {
@@ -20775,6 +20784,63 @@ function arrayFromPayload(value, keys) {
20775
20784
  }
20776
20785
  return [];
20777
20786
  }
20787
+ var SAFE_CONTROL_ERROR_CODES = /* @__PURE__ */ new Set([
20788
+ "invalid_request",
20789
+ "tool_not_allowed",
20790
+ "invalid_schema",
20791
+ "connection_not_found",
20792
+ "connection_inactive",
20793
+ "actions_disabled",
20794
+ "action_not_allowed",
20795
+ "upstream_rate_limited",
20796
+ "tool_discovery_failed",
20797
+ "live_tool_missing",
20798
+ "connection_transport_unavailable",
20799
+ "connection_control_failed"
20800
+ ]);
20801
+ var CONTROL_ERROR_CODE_ALIASES = /* @__PURE__ */ new Map([
20802
+ ["connection_not_active", "connection_inactive"],
20803
+ ["actions_not_enabled", "actions_disabled"],
20804
+ ["action_tool_not_allowed", "action_not_allowed"],
20805
+ ["no_action_for_provider", "action_not_allowed"],
20806
+ ["tool_not_available", "live_tool_missing"]
20807
+ ]);
20808
+ function controlErrorStatus(status) {
20809
+ if (status === 400 || status === 403 || status === 404 || status === 409 || status === 429 || status === 502 || status === 503) {
20810
+ return status;
20811
+ }
20812
+ return status >= 500 ? 503 : 502;
20813
+ }
20814
+ function defaultControlErrorCode(status) {
20815
+ if (status === 400) return "invalid_request";
20816
+ if (status === 403) return "actions_disabled";
20817
+ if (status === 404) return "connection_not_found";
20818
+ if (status === 409) return "connection_inactive";
20819
+ if (status === 429) return "upstream_rate_limited";
20820
+ if (status === 503) return "connection_transport_unavailable";
20821
+ return "connection_control_failed";
20822
+ }
20823
+ function defaultControlErrorMessage(status) {
20824
+ if (status === 400) return "The connection request was invalid.";
20825
+ if (status === 403) return "Actions are disabled for this service connection.";
20826
+ if (status === 404) return "The service connection was not found.";
20827
+ if (status === 409) return "The service connection is not currently usable.";
20828
+ if (status === 429) return "The connected service is temporarily rate limited.";
20829
+ if (status === 503) return "The service connection transport is temporarily unavailable.";
20830
+ return "The service connection control request failed.";
20831
+ }
20832
+ function safeControlErrorPayload(body, responseStatus) {
20833
+ const status = controlErrorStatus(responseStatus);
20834
+ const unwrapped = unwrapData(body);
20835
+ const record = isRecord(unwrapped) ? unwrapped : isRecord(body) ? body : null;
20836
+ const candidateCode = record ? firstString(record, ["code", "errorCode", "error_code"], 100) : null;
20837
+ const normalizedCandidateCode = candidateCode ? CONTROL_ERROR_CODE_ALIASES.get(candidateCode) ?? candidateCode : null;
20838
+ const code = normalizedCandidateCode && SAFE_CONTROL_ERROR_CODES.has(normalizedCandidateCode) ? normalizedCandidateCode : defaultControlErrorCode(status);
20839
+ const candidateMessage = record ? firstString(record, ["error", "message"], 500) : null;
20840
+ const message = normalizedCandidateCode && SAFE_CONTROL_ERROR_CODES.has(normalizedCandidateCode) && candidateMessage ? candidateMessage.replace(/[\u0000-\u001f\u007f]/g, " ") : defaultControlErrorMessage(status);
20841
+ const retryable = record && typeof record.retryable === "boolean" ? record.retryable : status === 429 || status >= 500;
20842
+ return { status, code, message, retryable };
20843
+ }
20778
20844
  async function controlRequest(path5, init, timeoutMs = 2e4) {
20779
20845
  const response = await fetch(`${controlBaseUrl()}${path5}`, {
20780
20846
  ...init,
@@ -20786,7 +20852,13 @@ async function controlRequest(path5, init, timeoutMs = 2e4) {
20786
20852
  },
20787
20853
  signal: AbortSignal.timeout(timeoutMs)
20788
20854
  }).catch((err) => {
20789
- throw new NangoControlError(`Scheduled service connection control is unavailable: ${err instanceof Error ? err.message : "network error"}`);
20855
+ const cause = err instanceof Error && err.name === "TimeoutError" ? "request timed out" : "network error";
20856
+ throw new NangoControlError(
20857
+ `Scheduled service connection control is unavailable: ${cause}.`,
20858
+ 503,
20859
+ "connection_transport_unavailable",
20860
+ true
20861
+ );
20790
20862
  });
20791
20863
  const text = await response.text();
20792
20864
  let body = {};
@@ -20796,8 +20868,8 @@ async function controlRequest(path5, init, timeoutMs = 2e4) {
20796
20868
  body = {};
20797
20869
  }
20798
20870
  if (!response.ok) {
20799
- const upstream = isRecord(body) ? cleanString(body.error, 300) : null;
20800
- throw new NangoControlError(upstream || `Scheduled service connection control failed (${response.status}).`);
20871
+ const error = safeControlErrorPayload(body, response.status);
20872
+ throw new NangoControlError(error.message, error.status, error.code, error.retryable);
20801
20873
  }
20802
20874
  return body;
20803
20875
  }
@@ -20892,6 +20964,9 @@ async function getNangoConnections(identity) {
20892
20964
  actionsEnabled: row.actionsEnabled === true || row.actions_enabled === true,
20893
20965
  readTools: cleanTools(row.readTools ?? row.read_tools),
20894
20966
  actionTools: cleanTools(row.actionTools ?? row.action_tools ?? row.writeTools ?? row.write_tools),
20967
+ mcpEndpoint: null,
20968
+ schemaDiscovery: "compatibility_describe",
20969
+ toolRevision: firstString(row, ["toolRevision", "tool_revision"], 200),
20895
20970
  vaultName: firstString(row, ["vaultName", "vault_name"], 100),
20896
20971
  tableName: firstString(row, ["tableName", "table_name"], 100),
20897
20972
  createdAt: firstString(row, ["createdAt", "created_at"], 100),
@@ -21024,6 +21099,145 @@ async function callScheduleConnectionRead(identity, connectionId, tool, args) {
21024
21099
  const data = unwrapData(body);
21025
21100
  return isRecord(data) ? data.result ?? data : data;
21026
21101
  }
21102
+ function sanitizeToolSchema(value) {
21103
+ if (!isRecord(value) || value.type !== "object") return null;
21104
+ try {
21105
+ const serialized = JSON.stringify(value);
21106
+ if (Buffer.byteLength(serialized, "utf8") > 256 * 1024) return null;
21107
+ const cloned = JSON.parse(serialized);
21108
+ return isRecord(cloned) ? cloned : null;
21109
+ } catch {
21110
+ return null;
21111
+ }
21112
+ }
21113
+ function sanitizeToolAnnotations(value) {
21114
+ if (!isRecord(value)) return void 0;
21115
+ const annotations = {};
21116
+ const title = cleanString(value.title, 200);
21117
+ if (title) annotations.title = title;
21118
+ for (const key of ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"]) {
21119
+ if (typeof value[key] === "boolean") annotations[key] = value[key];
21120
+ }
21121
+ return Object.keys(annotations).length > 0 ? annotations : void 0;
21122
+ }
21123
+ function sanitizeToolIconSource(value) {
21124
+ const candidate = cleanString(value, 1e4);
21125
+ if (!candidate) return null;
21126
+ if (/^data:image\/(?:png|jpeg|webp|gif|svg\+xml);base64,[a-z0-9+/=]+$/i.test(candidate)) return candidate;
21127
+ return cleanHttpsUrl(candidate);
21128
+ }
21129
+ function sanitizeToolIcons(value) {
21130
+ if (!Array.isArray(value)) return void 0;
21131
+ const icons = [];
21132
+ for (const item of value.slice(0, 16)) {
21133
+ if (!isRecord(item)) continue;
21134
+ const src = sanitizeToolIconSource(item.src);
21135
+ if (!src) continue;
21136
+ const mimeType = cleanString(item.mimeType ?? item.mime_type, 100);
21137
+ const sizes = cleanStringArray(item.sizes, 16, 32);
21138
+ const theme = item.theme === "light" || item.theme === "dark" ? item.theme : void 0;
21139
+ icons.push({
21140
+ src,
21141
+ ...mimeType ? { mimeType } : {},
21142
+ ...sizes.length > 0 ? { sizes } : {},
21143
+ ...theme ? { theme } : {}
21144
+ });
21145
+ }
21146
+ return icons.length > 0 ? icons : void 0;
21147
+ }
21148
+ function canonicalJson(value) {
21149
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
21150
+ if (isRecord(value)) {
21151
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
21152
+ }
21153
+ return JSON.stringify(value);
21154
+ }
21155
+ function projectedToolSchemaHash(tool) {
21156
+ return createHash5("sha256").update(canonicalJson(tool)).digest("hex");
21157
+ }
21158
+ async function describeNangoTool(identity, connectionId, tool, fresh) {
21159
+ const body = await controlRequest("/api/internal/nango/connections/actions/describe", {
21160
+ method: "POST",
21161
+ body: JSON.stringify({
21162
+ identity,
21163
+ connectionId,
21164
+ tool,
21165
+ ...fresh === void 0 ? {} : { fresh }
21166
+ })
21167
+ });
21168
+ const data = unwrapData(body);
21169
+ const rawTool = isRecord(data) && isRecord(data.tool) ? data.tool : data;
21170
+ if (!isRecord(rawTool)) {
21171
+ throw new NangoControlError(
21172
+ "The connection service returned an invalid live tool description.",
21173
+ 502,
21174
+ "tool_discovery_failed",
21175
+ true
21176
+ );
21177
+ }
21178
+ let serializedBytes = Number.POSITIVE_INFINITY;
21179
+ try {
21180
+ serializedBytes = Buffer.byteLength(JSON.stringify(rawTool), "utf8");
21181
+ } catch {
21182
+ }
21183
+ const name = cleanString(rawTool.name, 200);
21184
+ const classification = cleanString(rawTool.classification, 20);
21185
+ const transport = cleanString(rawTool.transport, 20);
21186
+ const providerConfigKey = cleanString(rawTool.providerConfigKey ?? rawTool.provider_config_key, 200);
21187
+ const schemaSource = cleanString(rawTool.schemaSource ?? rawTool.schema_source, 50);
21188
+ const upstreamSchemaHash = cleanString(rawTool.schemaHash ?? rawTool.schema_hash, 200);
21189
+ const fetchedAt = cleanString(rawTool.fetchedAt ?? rawTool.fetched_at, 100);
21190
+ const inputSchema = sanitizeToolSchema(rawTool.inputSchema ?? rawTool.input_schema);
21191
+ const outputSchemaValue = rawTool.outputSchema ?? rawTool.output_schema;
21192
+ const outputSchema = outputSchemaValue === void 0 ? void 0 : sanitizeToolSchema(outputSchemaValue);
21193
+ const blockedReasonValue = rawTool.blockedReason ?? rawTool.blocked_reason;
21194
+ const blockedReason = blockedReasonValue === null || blockedReasonValue === void 0 ? null : blockedReasonValue === "actions_disabled" || blockedReasonValue === "inactive_connection" ? blockedReasonValue : void 0;
21195
+ if (serializedBytes > 256 * 1024 || !name || name !== tool || classification !== "read" && classification !== "action" || typeof rawTool.callable !== "boolean" || blockedReason === void 0 || transport !== "nango" && transport !== "remote_mcp" || !providerConfigKey || schemaSource !== "live_tools_list" || !upstreamSchemaHash || !/^[a-f0-9]{64}$/i.test(upstreamSchemaHash) || !fetchedAt || Number.isNaN(Date.parse(fetchedAt)) || !inputSchema || outputSchemaValue !== void 0 && !outputSchema) {
21196
+ throw new NangoControlError(
21197
+ "The connection service returned an invalid live tool description.",
21198
+ 502,
21199
+ "tool_discovery_failed",
21200
+ true
21201
+ );
21202
+ }
21203
+ const protocolVersion = rawTool.protocolVersion === null || rawTool.protocol_version === null ? null : cleanString(rawTool.protocolVersion ?? rawTool.protocol_version, 100);
21204
+ const executionValue = rawTool.execution;
21205
+ const taskSupport = isRecord(executionValue) && (executionValue.taskSupport === "forbidden" || executionValue.taskSupport === "optional" || executionValue.taskSupport === "required") ? executionValue.taskSupport : null;
21206
+ const title = cleanString(rawTool.title, 200);
21207
+ const description = cleanString(rawTool.description, 12e3);
21208
+ const annotations = sanitizeToolAnnotations(rawTool.annotations);
21209
+ const icons = sanitizeToolIcons(rawTool.icons);
21210
+ const execution = taskSupport ? { taskSupport } : void 0;
21211
+ const schemaHash = projectedToolSchemaHash({
21212
+ name,
21213
+ inputSchema,
21214
+ ...title ? { title } : {},
21215
+ ...description ? { description } : {},
21216
+ ...outputSchema ? { outputSchema } : {},
21217
+ ...annotations ? { annotations } : {},
21218
+ ...icons ? { icons } : {},
21219
+ ...execution ? { execution } : {}
21220
+ });
21221
+ return {
21222
+ name,
21223
+ title,
21224
+ description,
21225
+ classification,
21226
+ callable: rawTool.callable,
21227
+ blockedReason,
21228
+ transport,
21229
+ providerConfigKey,
21230
+ protocolVersion,
21231
+ schemaSource: "live_tools_list",
21232
+ inputSchema,
21233
+ ...outputSchema ? { outputSchema } : {},
21234
+ ...annotations ? { annotations } : {},
21235
+ ...icons ? { icons } : {},
21236
+ ...execution ? { execution } : {},
21237
+ schemaHash,
21238
+ fetchedAt
21239
+ };
21240
+ }
21027
21241
  async function callScheduleConnectionExportPage(identity, input) {
21028
21242
  const body = await controlRequest("/api/internal/nango/connections/export-page", {
21029
21243
  method: "POST",
@@ -21243,6 +21457,9 @@ async function getResendConnections(identity) {
21243
21457
  readTools: cleanTools2(row.readTools ?? row.read_tools),
21244
21458
  actionTools: cleanTools2(row.actionTools ?? row.action_tools ?? row.writeTools ?? row.write_tools),
21245
21459
  adminBlockedTools: cleanTools2(row.adminBlockedTools ?? row.admin_blocked_tools).length > 0 ? cleanTools2(row.adminBlockedTools ?? row.admin_blocked_tools) : [...RESEND_ADMIN_BLOCKED_TOOLS],
21460
+ mcpEndpoint: null,
21461
+ schemaDiscovery: "compatibility_describe",
21462
+ toolRevision: firstString2(row, ["toolRevision", "tool_revision"], 200),
21246
21463
  vaultName: firstString2(row, ["vaultName", "vault_name"], 100),
21247
21464
  tableName: firstString2(row, ["tableName", "table_name"], 100),
21248
21465
  createdAt: firstString2(row, ["createdAt", "created_at"], 100),
@@ -21985,7 +22202,15 @@ app.post("/schedule-checkout", auth2, async (c) => {
21985
22202
  });
21986
22203
  function scheduleConnectionError(c, err, fallback) {
21987
22204
  if (err instanceof ScheduleConnectionValidationError) return c.json({ ok: false, error: err.message }, 400);
21988
- if (err instanceof NangoControlError) return c.json({ ok: false, error: err.message }, err.status);
22205
+ if (err instanceof NangoControlError) {
22206
+ return c.json({
22207
+ ok: false,
22208
+ error: err.message,
22209
+ code: err.code,
22210
+ errorCode: err.code,
22211
+ retryable: err.retryable
22212
+ }, err.status);
22213
+ }
21989
22214
  if (err instanceof ResendControlError) return c.json({ ok: false, error: err.message }, err.status);
21990
22215
  console.error("[schedule-connections]", err instanceof Error ? err.message : String(err));
21991
22216
  return c.json({ ok: false, error: fallback }, 502);
@@ -22239,11 +22464,12 @@ app.post("/schedule-connections/actions/describe", auth2, async (c) => {
22239
22464
  const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
22240
22465
  const tool = providerConfigKeyFrom(body.tool);
22241
22466
  if (!connectionId || !tool) return c.json({ ok: false, error: "connectionId and tool are required." }, 400);
22467
+ if (body.fresh !== void 0 && typeof body.fresh !== "boolean") {
22468
+ return c.json({ ok: false, error: "fresh must be a boolean when provided." }, 400);
22469
+ }
22242
22470
  try {
22243
- if (!await isResendConnection(user.email, connectionId, providerConfigKey)) {
22244
- return c.json({ ok: false, error: "Typed tool descriptions are not available for this connection transport yet." }, 501);
22245
- }
22246
- return c.json({ ok: true, tool: await describeResendTool(user.email, connectionId, tool) });
22471
+ const description = await isResendConnection(user.email, connectionId, providerConfigKey) ? await describeResendTool(user.email, connectionId, tool) : await describeNangoTool(user.email, connectionId, tool, body.fresh);
22472
+ return c.json({ ok: true, tool: description });
22247
22473
  } catch (err) {
22248
22474
  return scheduleConnectionError(c, err, "Unable to describe this connection tool.");
22249
22475
  }
@@ -23562,4 +23788,4 @@ app.get("/blog/:slug/", (c) => {
23562
23788
  export {
23563
23789
  app
23564
23790
  };
23565
- //# sourceMappingURL=server-DDKXWYTO.js.map
23791
+ //# sourceMappingURL=server-QEXOVJPR.js.map