mcp-scraper 0.14.0 → 0.15.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.
@@ -37,7 +37,7 @@ import {
37
37
  renewConnectedDataArtifactDownload,
38
38
  sanitizeAttempts,
39
39
  sanitizeHarvestResult
40
- } from "./chunk-DXU327CY.js";
40
+ } from "./chunk-QPYYOTT2.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-HM7SDTUO.js";
76
+ import "./chunk-EZAQWOKI.js";
77
77
  import {
78
78
  completeExtractJob,
79
79
  countSuccessfulPages,
@@ -20193,6 +20193,20 @@ var CONNECTED_DATA_EXPORT_BUDGET_MS = Number(
20193
20193
  var CONNECTED_DATA_PAGE_START_HEADROOM_MS = Number(
20194
20194
  process.env.MCP_SCRAPER_CONNECTED_DATA_PAGE_START_HEADROOM_MS ?? 13e4
20195
20195
  );
20196
+ var CONNECTED_DATA_DATASETS = [
20197
+ "auto",
20198
+ "emails",
20199
+ "calendar_events",
20200
+ "zoom_recordings",
20201
+ "zoom_transcripts",
20202
+ "resend_data",
20203
+ "resend_emails",
20204
+ "resend_received_emails",
20205
+ "resend_logs",
20206
+ "resend_contacts",
20207
+ "resend_broadcasts",
20208
+ "resend_templates"
20209
+ ];
20196
20210
  var ConnectedDataExportValidationError = class extends Error {
20197
20211
  constructor(message) {
20198
20212
  super(message);
@@ -20226,7 +20240,7 @@ function resolveConnectedDataExportRequest(args) {
20226
20240
  ...args.cursor ? { cursor: args.cursor } : {}
20227
20241
  };
20228
20242
  }
20229
- const supported = ["emails", "calendar_events", "zoom_recordings", "zoom_transcripts"];
20243
+ const supported = CONNECTED_DATA_DATASETS.filter((dataset2) => dataset2 !== "auto");
20230
20244
  if (typeof continuation.cursor !== "string" || !continuation.cursor || typeof continuation.from !== "string" || !continuation.from || typeof continuation.to !== "string" || !continuation.to || !supported.includes(continuation.dataset)) {
20231
20245
  throw new ConnectedDataExportValidationError("continuation is missing its cursor, from, to, or dataset.");
20232
20246
  }
@@ -20258,6 +20272,7 @@ function previewRecord(value) {
20258
20272
  const preview = {};
20259
20273
  for (const key of [
20260
20274
  "recordType",
20275
+ "dataset",
20261
20276
  "id",
20262
20277
  "providerRecordId",
20263
20278
  "threadId",
@@ -20271,6 +20286,11 @@ function previewRecord(value) {
20271
20286
  "snippet",
20272
20287
  "topic",
20273
20288
  "startTime",
20289
+ "email",
20290
+ "name",
20291
+ "status",
20292
+ "createdAt",
20293
+ "updatedAt",
20274
20294
  "hasTranscript",
20275
20295
  "contentTruncated"
20276
20296
  ]) {
@@ -20336,6 +20356,10 @@ async function collectConnectedDataExport(args) {
20336
20356
  recordBytes += pageBytes;
20337
20357
  cursor = page.nextCursor ?? void 0;
20338
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
+ }
20339
20363
  if (page.complete || page.nextCursor === null || page.nextCursor === void 0) {
20340
20364
  complete = true;
20341
20365
  continuationCursor = null;
@@ -20632,6 +20656,20 @@ var CONNECTION_SYNC_REQUIRED_TOOLS = {
20632
20656
  "list-releases",
20633
20657
  "list-workflows",
20634
20658
  "list-workflow-runs"
20659
+ ],
20660
+ resend: [
20661
+ "list-emails",
20662
+ "get-email",
20663
+ "list-received-emails",
20664
+ "get-received-email",
20665
+ "list-logs",
20666
+ "get-log",
20667
+ "list-contacts",
20668
+ "get-contact",
20669
+ "list-broadcasts",
20670
+ "get-broadcast",
20671
+ "list-templates",
20672
+ "get-template"
20635
20673
  ]
20636
20674
  };
20637
20675
  function connectionSyncPolicyIssues(selections) {
@@ -21001,7 +21039,7 @@ async function callScheduleConnectionExportPage(identity, input) {
21001
21039
  }
21002
21040
  const providerConfigKey = cleanString(data.providerConfigKey, 128);
21003
21041
  const dataset = cleanString(data.dataset, 64);
21004
- if (!providerConfigKey || !["emails", "calendar_events", "zoom_recordings", "zoom_transcripts"].includes(dataset ?? "")) {
21042
+ if (!providerConfigKey || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
21005
21043
  throw new NangoControlError("Connected-data export returned invalid provider metadata.");
21006
21044
  }
21007
21045
  if (!Array.isArray(data.records)) {
@@ -21028,6 +21066,315 @@ async function callScheduleConnectionExportPage(identity, input) {
21028
21066
  };
21029
21067
  }
21030
21068
 
21069
+ // src/api/resend-control.ts
21070
+ var DEFAULT_CONNECTION_CONTROL_URL = "https://mcp-scraper-scheduler.vercel.app";
21071
+ var RESEND_PROVIDER_CONFIG_KEY = "resend";
21072
+ var RESEND_LOGO_URL = "https://cdn.resend.com/brand/resend-icon-black.svg";
21073
+ var RESEND_DOCS_URL = "https://resend.com/docs/mcp-server";
21074
+ var RESEND_ADMIN_BLOCKED_TOOLS = [
21075
+ "connect-to-editor",
21076
+ "create-webhook",
21077
+ "create-api-key",
21078
+ "list-api-keys",
21079
+ "remove-api-key",
21080
+ "list-oauth-grants",
21081
+ "revoke-oauth-grant"
21082
+ ];
21083
+ var RESEND_CONNECTION_SYNC_REQUIRED_TOOLS = [
21084
+ "list-emails",
21085
+ "get-email",
21086
+ "list-received-emails",
21087
+ "get-received-email",
21088
+ "list-logs",
21089
+ "get-log",
21090
+ "list-contacts",
21091
+ "get-contact",
21092
+ "list-broadcasts",
21093
+ "get-broadcast",
21094
+ "list-templates",
21095
+ "get-template"
21096
+ ];
21097
+ var ResendControlError = class extends Error {
21098
+ status;
21099
+ constructor(message, status = 502) {
21100
+ super(message);
21101
+ this.name = "ResendControlError";
21102
+ this.status = status;
21103
+ }
21104
+ };
21105
+ function controlBaseUrl2() {
21106
+ return (process.env.SCHEDULE_INTEGRATIONS_CONTROL_URL?.trim() || process.env.NANGO_CONTROL_URL?.trim() || DEFAULT_CONNECTION_CONTROL_URL).replace(/\/$/, "");
21107
+ }
21108
+ function controlSecret2() {
21109
+ const secret2 = process.env.SCHEDULE_INTEGRATIONS_SECRET?.trim();
21110
+ if (!secret2) throw new ResendControlError("Resend connections are not configured.", 503);
21111
+ return secret2;
21112
+ }
21113
+ function isRecord2(value) {
21114
+ return !!value && typeof value === "object" && !Array.isArray(value);
21115
+ }
21116
+ function unwrapData2(value) {
21117
+ return isRecord2(value) && isRecord2(value.data) ? value.data : value;
21118
+ }
21119
+ function cleanString2(value, max = 300) {
21120
+ if (typeof value !== "string") return null;
21121
+ const trimmed = value.trim();
21122
+ return trimmed ? trimmed.slice(0, max) : null;
21123
+ }
21124
+ function firstString2(record, keys, max = 300) {
21125
+ for (const key of keys) {
21126
+ const value = cleanString2(record[key], max);
21127
+ if (value) return value;
21128
+ }
21129
+ return null;
21130
+ }
21131
+ function cleanTools2(value) {
21132
+ if (!Array.isArray(value)) return [];
21133
+ return [...new Set(value.map((tool) => cleanString2(tool, 200)).filter((tool) => !!tool))].slice(0, 200);
21134
+ }
21135
+ function cleanStringArray2(value, maxItems = 50, maxLength = 500) {
21136
+ if (!Array.isArray(value)) return [];
21137
+ return [...new Set(value.map((item) => cleanString2(item, maxLength)).filter((item) => !!item))].slice(0, maxItems);
21138
+ }
21139
+ function cleanHttpsUrl2(value) {
21140
+ const candidate = cleanString2(value, 2e3);
21141
+ if (!candidate) return null;
21142
+ try {
21143
+ const url = new URL(candidate);
21144
+ return url.protocol === "https:" ? url.toString() : null;
21145
+ } catch {
21146
+ return null;
21147
+ }
21148
+ }
21149
+ function cleanAuthorizationUrl(value) {
21150
+ const candidate = cleanHttpsUrl2(value);
21151
+ if (!candidate) return null;
21152
+ const url = new URL(candidate);
21153
+ return url.hostname.toLowerCase() === "api.resend.com" && url.pathname === "/oauth/authorize" ? candidate : null;
21154
+ }
21155
+ function arrayFromPayload2(value, keys) {
21156
+ const unwrapped = unwrapData2(value);
21157
+ if (Array.isArray(unwrapped)) return unwrapped;
21158
+ if (!isRecord2(unwrapped)) return [];
21159
+ for (const key of keys) {
21160
+ if (Array.isArray(unwrapped[key])) return unwrapped[key];
21161
+ }
21162
+ return [];
21163
+ }
21164
+ async function controlRequest2(path5, init, timeoutMs = 2e4) {
21165
+ const response = await fetch(`${controlBaseUrl2()}${path5}`, {
21166
+ ...init,
21167
+ headers: {
21168
+ accept: "application/json",
21169
+ authorization: `Bearer ${controlSecret2()}`,
21170
+ ...init?.body ? { "content-type": "application/json" } : {},
21171
+ ...init?.headers ?? {}
21172
+ },
21173
+ signal: AbortSignal.timeout(timeoutMs)
21174
+ }).catch((err) => {
21175
+ throw new ResendControlError(`Resend connection control is unavailable: ${err instanceof Error ? err.message : "network error"}`);
21176
+ });
21177
+ const text = await response.text();
21178
+ let body = {};
21179
+ try {
21180
+ body = text ? JSON.parse(text) : {};
21181
+ } catch {
21182
+ body = {};
21183
+ }
21184
+ if (!response.ok) {
21185
+ const upstream = isRecord2(body) ? cleanString2(body.error, 300) : null;
21186
+ throw new ResendControlError(upstream || `Resend connection control failed (${response.status}).`);
21187
+ }
21188
+ return body;
21189
+ }
21190
+ async function getResendCatalog() {
21191
+ const body = await controlRequest2("/api/internal/resend/catalog");
21192
+ const rows = arrayFromPayload2(body, ["providers", "catalog", "integrations", "services"]);
21193
+ const result = [];
21194
+ for (const row of rows) {
21195
+ if (!isRecord2(row)) continue;
21196
+ const id = firstString2(row, ["providerConfigKey", "provider_config_key", "id"]);
21197
+ if (id !== RESEND_PROVIDER_CONFIG_KEY) continue;
21198
+ const safeDefaultAllowedTools = cleanTools2(
21199
+ row.safeDefaultAllowedTools ?? row.safe_default_allowed_tools ?? row.readTools ?? row.read_tools
21200
+ );
21201
+ const actionTools = cleanTools2(row.actionTools ?? row.action_tools ?? row.writeTools ?? row.write_tools);
21202
+ const adminBlockedTools = cleanTools2(row.adminBlockedTools ?? row.admin_blocked_tools);
21203
+ result.push({
21204
+ providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
21205
+ provider: RESEND_PROVIDER_CONFIG_KEY,
21206
+ label: firstString2(row, ["label", "displayName", "display_name", "name"]) || "Resend",
21207
+ description: firstString2(row, ["description"], 500) || "Connect Resend through its official remote MCP to read email operations and run explicitly enabled delivery workflows.",
21208
+ logoUrl: cleanHttpsUrl2(row.logoUrl ?? row.logo_url ?? row.logo) || RESEND_LOGO_URL,
21209
+ docsUrl: cleanHttpsUrl2(row.docsUrl ?? row.docs_url ?? row.docs) || RESEND_DOCS_URL,
21210
+ authMode: "REMOTE_MCP_OAUTH2",
21211
+ categories: ["Email", "Developer tools"],
21212
+ safeDefaultAllowedTools,
21213
+ actionTools,
21214
+ adminBlockedTools: adminBlockedTools.length > 0 ? adminBlockedTools : [...RESEND_ADMIN_BLOCKED_TOOLS],
21215
+ connectionSyncSupported: true,
21216
+ connectionSyncRequiredTools: [...RESEND_CONNECTION_SYNC_REQUIRED_TOOLS],
21217
+ platformSetupStatus: firstString2(row, ["platformSetupStatus", "platform_setup_status", "setupStatus", "setup_status"], 100) || "ready",
21218
+ appReviewStatus: null,
21219
+ appReviewNote: "Secret-returning webhook creation, API-key, OAuth-grant, and raw editor-session access is permanently blocked from agents and schedules.",
21220
+ transport: "remote_mcp"
21221
+ });
21222
+ }
21223
+ return result;
21224
+ }
21225
+ async function getResendConnections(identity) {
21226
+ const query = new URLSearchParams({ identity }).toString();
21227
+ const body = await controlRequest2(`/api/internal/resend/connections?${query}`);
21228
+ const rows = arrayFromPayload2(body, ["connections"]);
21229
+ const result = [];
21230
+ for (const row of rows) {
21231
+ if (!isRecord2(row)) continue;
21232
+ const connectionId = firstString2(row, ["connectionId", "connection_id", "id"]);
21233
+ const providerConfigKey = firstString2(row, ["providerConfigKey", "provider_config_key", "provider"]) || RESEND_PROVIDER_CONFIG_KEY;
21234
+ if (!connectionId || providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) continue;
21235
+ const rawStatus = (firstString2(row, ["status"], 64) || "needs_reauth").toLowerCase();
21236
+ const pending = ["pending", "pending_oauth", "authorizing", "authorization_pending"].includes(rawStatus);
21237
+ const active = ["active", "connected", "ready"].includes(rawStatus);
21238
+ const reconnectRequired = row.reconnectRequired === true || row.reconnect_required === true || !pending && !active;
21239
+ result.push({
21240
+ connectionId,
21241
+ providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
21242
+ provider: RESEND_PROVIDER_CONFIG_KEY,
21243
+ label: firstString2(row, ["label", "accountLabel", "account_label", "displayName", "display_name"]) || "Resend account",
21244
+ status: pending ? "pending_oauth" : reconnectRequired ? "needs_reauth" : "connected",
21245
+ reconnectRequired,
21246
+ actionsEnabled: row.actionsEnabled === true || row.actions_enabled === true,
21247
+ readTools: cleanTools2(row.readTools ?? row.read_tools),
21248
+ actionTools: cleanTools2(row.actionTools ?? row.action_tools ?? row.writeTools ?? row.write_tools),
21249
+ adminBlockedTools: cleanTools2(row.adminBlockedTools ?? row.admin_blocked_tools).length > 0 ? cleanTools2(row.adminBlockedTools ?? row.admin_blocked_tools) : [...RESEND_ADMIN_BLOCKED_TOOLS],
21250
+ vaultName: firstString2(row, ["vaultName", "vault_name"], 100),
21251
+ tableName: firstString2(row, ["tableName", "table_name"], 100),
21252
+ createdAt: firstString2(row, ["createdAt", "created_at"], 100),
21253
+ updatedAt: firstString2(row, ["updatedAt", "updated_at"], 100),
21254
+ transport: "remote_mcp"
21255
+ });
21256
+ }
21257
+ return result;
21258
+ }
21259
+ function sanitizeConnectSession2(body) {
21260
+ const data = unwrapData2(body);
21261
+ if (!isRecord2(data)) throw new ResendControlError("The Resend connection service returned an invalid connect session.");
21262
+ const connectLink = cleanAuthorizationUrl(data.authorizationUrl ?? data.authorization_url ?? data.connectLink ?? data.connect_link);
21263
+ if (!connectLink) throw new ResendControlError("The Resend connection service did not return a trusted authorization link.");
21264
+ return {
21265
+ connectLink,
21266
+ sessionToken: null,
21267
+ expiresAt: firstString2(data, ["expiresAt", "expires_at"], 100)
21268
+ };
21269
+ }
21270
+ async function createResendConnectSession(identity, redirectUri) {
21271
+ const body = await controlRequest2("/api/internal/resend/connect-session", {
21272
+ method: "POST",
21273
+ body: JSON.stringify({ identity, email: identity, redirectUri })
21274
+ });
21275
+ return sanitizeConnectSession2(body);
21276
+ }
21277
+ async function createResendReconnectSession(identity, connectionId, redirectUri) {
21278
+ const body = await controlRequest2("/api/internal/resend/reconnect-session", {
21279
+ method: "POST",
21280
+ body: JSON.stringify({ identity, email: identity, connectionId, redirectUri })
21281
+ });
21282
+ return sanitizeConnectSession2(body);
21283
+ }
21284
+ async function completeResendOAuthCallback(input) {
21285
+ await controlRequest2("/api/internal/resend/oauth/callback", {
21286
+ method: "POST",
21287
+ body: JSON.stringify(input)
21288
+ });
21289
+ }
21290
+ async function deleteResendConnection(identity, connectionId) {
21291
+ await controlRequest2("/api/internal/resend/connections", {
21292
+ method: "DELETE",
21293
+ body: JSON.stringify({ identity, connectionId })
21294
+ });
21295
+ }
21296
+ async function setResendActionsEnabled(identity, connectionId, enabled) {
21297
+ const body = await controlRequest2("/api/internal/resend/connections/actions", {
21298
+ method: "PUT",
21299
+ body: JSON.stringify({ identity, connectionId, enabled })
21300
+ });
21301
+ const data = unwrapData2(body);
21302
+ if (!isRecord2(data) || !isRecord2(data.connection)) return enabled;
21303
+ return data.connection.actionsEnabled === true || data.connection.actions_enabled === true;
21304
+ }
21305
+ async function callResendRead(identity, connectionId, tool, args) {
21306
+ const body = await controlRequest2("/api/internal/resend/read", {
21307
+ method: "POST",
21308
+ body: JSON.stringify({ identity, connectionId, tool, input: args ?? {} })
21309
+ });
21310
+ const data = unwrapData2(body);
21311
+ return isRecord2(data) ? data.result ?? data : data;
21312
+ }
21313
+ async function callResendAction(identity, connectionId, tool, input) {
21314
+ const body = await controlRequest2("/api/internal/resend/actions/call", {
21315
+ method: "POST",
21316
+ body: JSON.stringify({ identity, connectionId, tool, input })
21317
+ });
21318
+ const data = unwrapData2(body);
21319
+ return isRecord2(data) ? data.result ?? data : data;
21320
+ }
21321
+ async function describeResendTool(identity, connectionId, tool) {
21322
+ const body = await controlRequest2("/api/internal/resend/describe", {
21323
+ method: "POST",
21324
+ body: JSON.stringify({ identity, connectionId, tool })
21325
+ });
21326
+ const data = unwrapData2(body);
21327
+ const rawTool = isRecord2(data) && isRecord2(data.tool) ? data.tool : data;
21328
+ if (!isRecord2(rawTool)) throw new ResendControlError("Resend returned an invalid tool description.");
21329
+ const name = firstString2(rawTool, ["name"], 200);
21330
+ const classification = firstString2(rawTool, ["classification", "kind"], 20);
21331
+ const inputSchema = rawTool.inputSchema ?? rawTool.input_schema;
21332
+ if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord2(inputSchema)) {
21333
+ throw new ResendControlError("Resend returned an invalid tool description.");
21334
+ }
21335
+ return {
21336
+ name,
21337
+ title: firstString2(rawTool, ["title"], 300),
21338
+ description: firstString2(rawTool, ["description"], 2e3),
21339
+ inputSchema,
21340
+ classification
21341
+ };
21342
+ }
21343
+ async function callResendExportPage(identity, input) {
21344
+ const body = await controlRequest2("/api/internal/resend/export-page", {
21345
+ method: "POST",
21346
+ body: JSON.stringify({ identity, ...input })
21347
+ }, 9e4);
21348
+ const data = unwrapData2(body);
21349
+ if (!isRecord2(data) || data.ok !== true || data.providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) {
21350
+ throw new ResendControlError("Resend export returned an invalid page response.");
21351
+ }
21352
+ const dataset = cleanString2(data.dataset, 64);
21353
+ if (!dataset || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
21354
+ throw new ResendControlError("Resend export returned invalid dataset metadata.");
21355
+ }
21356
+ if (!Array.isArray(data.records)) throw new ResendControlError("Resend export returned invalid records.");
21357
+ if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
21358
+ throw new ResendControlError("Resend export returned an invalid continuation cursor.");
21359
+ }
21360
+ const rawCounts = isRecord2(data.counts) ? data.counts : {};
21361
+ const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
21362
+ return {
21363
+ providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
21364
+ dataset,
21365
+ records: data.records,
21366
+ nextCursor: typeof data.nextCursor === "string" ? data.nextCursor : null,
21367
+ complete: data.complete === true,
21368
+ counts: {
21369
+ listed: numberOrZero(rawCounts.listed),
21370
+ exported: numberOrZero(rawCounts.exported ?? rawCounts.returned),
21371
+ failed: numberOrZero(rawCounts.failed)
21372
+ },
21373
+ warnings: cleanStringArray2(data.warnings),
21374
+ untrustedContent: true
21375
+ };
21376
+ }
21377
+
21031
21378
  // src/api/server.ts
21032
21379
  var secureCookies2 = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
21033
21380
  var isProduction2 = secureCookies2;
@@ -21643,6 +21990,7 @@ app.post("/schedule-checkout", auth2, async (c) => {
21643
21990
  function scheduleConnectionError(c, err, fallback) {
21644
21991
  if (err instanceof ScheduleConnectionValidationError) return c.json({ ok: false, error: err.message }, 400);
21645
21992
  if (err instanceof NangoControlError) return c.json({ ok: false, error: err.message }, err.status);
21993
+ if (err instanceof ResendControlError) return c.json({ ok: false, error: err.message }, err.status);
21646
21994
  console.error("[schedule-connections]", err instanceof Error ? err.message : String(err));
21647
21995
  return c.json({ ok: false, error: fallback }, 502);
21648
21996
  }
@@ -21654,6 +22002,52 @@ function providerConfigKeyFrom(value) {
21654
22002
  function bindingsForSchedule(bindings, scheduleActionId) {
21655
22003
  return bindings.filter((binding) => binding.scheduleActionId === scheduleActionId);
21656
22004
  }
22005
+ function resendOAuthCallbackUrl() {
22006
+ return new URL("/oauth/resend/callback", appOrigin()).toString();
22007
+ }
22008
+ async function combinedConnectionCatalog() {
22009
+ const [nango, resend] = await Promise.allSettled([getNangoCatalog(), getResendCatalog()]);
22010
+ if (nango.status === "rejected") {
22011
+ console.warn("[schedule-connections] Nango catalog unavailable:", nango.reason instanceof Error ? nango.reason.message : "unknown error");
22012
+ }
22013
+ if (resend.status === "rejected") {
22014
+ console.warn("[schedule-connections] Resend catalog unavailable:", resend.reason instanceof Error ? resend.reason.message : "unknown error");
22015
+ }
22016
+ if (nango.status === "rejected" && resend.status === "rejected") throw nango.reason;
22017
+ return [
22018
+ ...nango.status === "fulfilled" ? nango.value : [],
22019
+ ...resend.status === "fulfilled" ? resend.value : []
22020
+ ];
22021
+ }
22022
+ async function combinedConnections(identity) {
22023
+ const [nango, resend] = await Promise.allSettled([getNangoConnections(identity), getResendConnections(identity)]);
22024
+ if (nango.status === "rejected") {
22025
+ console.warn("[schedule-connections] Nango connection listing unavailable:", nango.reason instanceof Error ? nango.reason.message : "unknown error");
22026
+ }
22027
+ if (resend.status === "rejected") {
22028
+ console.warn("[schedule-connections] Resend connection listing unavailable:", resend.reason instanceof Error ? resend.reason.message : "unknown error");
22029
+ }
22030
+ if (nango.status === "rejected" && resend.status === "rejected") throw nango.reason;
22031
+ return [
22032
+ ...nango.status === "fulfilled" ? nango.value.map((connection) => ({ ...connection, transport: "nango", adminBlockedTools: [] })) : [],
22033
+ ...resend.status === "fulfilled" ? resend.value : []
22034
+ ];
22035
+ }
22036
+ async function isResendConnection(identity, connectionId, providerHint) {
22037
+ if (providerHint === "resend") return true;
22038
+ if (providerHint) return false;
22039
+ try {
22040
+ return (await getResendConnections(identity)).some((connection) => connection.connectionId === connectionId);
22041
+ } catch (err) {
22042
+ console.warn("[schedule-connections] Resend connection resolution unavailable:", err instanceof Error ? err.message : "unknown error");
22043
+ return false;
22044
+ }
22045
+ }
22046
+ function renderResendOAuthResult(ok) {
22047
+ const title = ok ? "Resend connected" : "Resend connection not completed";
22048
+ const message = ok ? "Your Resend account is connected privately to MCP Scraper. You can close this tab." : "The connection could not be completed. Close this tab and try again from Integrations.";
22049
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>${title}</title><style>:root{color-scheme:light dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;background:#f5f2ee;color:#26211d;font-family:ui-sans-serif,system-ui,-apple-system,sans-serif}.card{width:min(100%,440px);padding:32px;border:1px solid #d8d0c8;border-radius:16px;background:#fff;box-shadow:0 18px 55px rgba(31,24,18,.1)}.mark{width:38px;height:38px;display:grid;place-items:center;border-radius:10px;background:#26211d;color:#fff;font-size:20px;font-weight:700}h1{margin:18px 0 8px;font-size:24px;line-height:1.15}p{margin:0;color:#71675f;line-height:1.55}a{display:inline-flex;margin-top:20px;color:#b44d36;font-weight:650;text-underline-offset:3px}@media(prefers-color-scheme:dark){body{background:#171411;color:#f4eee8}.card{background:#211d19;border-color:#3b342d}.mark{background:#f4eee8;color:#211d19}p{color:#b9aea5}}</style></head><body><main class="card"><div class="mark" aria-hidden="true">R</div><h1>${title}</h1><p>${message}</p><a href="/dashboard/integrations">Return to Integrations</a></main><script>if(window.opener){try{window.opener.postMessage({type:'mcp-scraper:resend-oauth',ok:${ok}},window.location.origin)}catch(e){}}setTimeout(function(){window.close()},1200)</script></body></html>`;
22050
+ }
21657
22051
  app.get("/schedule-status", auth2, async (c) => {
21658
22052
  try {
21659
22053
  const user = c.get("user");
@@ -21669,7 +22063,7 @@ app.get("/schedule-status", auth2, async (c) => {
21669
22063
  });
21670
22064
  app.get("/schedule-connection-catalog", auth2, async (c) => {
21671
22065
  try {
21672
- return c.json({ ok: true, catalog: await getNangoCatalog() });
22066
+ return c.json({ ok: true, catalog: await combinedConnectionCatalog() });
21673
22067
  } catch (err) {
21674
22068
  return scheduleConnectionError(c, err, "Unable to load the service connection catalog.");
21675
22069
  }
@@ -21677,7 +22071,7 @@ app.get("/schedule-connection-catalog", auth2, async (c) => {
21677
22071
  app.get("/schedule-connections", auth2, async (c) => {
21678
22072
  try {
21679
22073
  const user = c.get("user");
21680
- return c.json({ ok: true, connections: await getNangoConnections(user.email) });
22074
+ return c.json({ ok: true, connections: await combinedConnections(user.email) });
21681
22075
  } catch (err) {
21682
22076
  return scheduleConnectionError(c, err, "Unable to load service connections.");
21683
22077
  }
@@ -21688,7 +22082,7 @@ app.post("/schedule-connections/session", auth2, async (c) => {
21688
22082
  const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
21689
22083
  if (!providerConfigKey) return c.json({ ok: false, error: "providerConfigKey is required." }, 400);
21690
22084
  try {
21691
- const session = await createNangoConnectSession(user.email, providerConfigKey);
22085
+ const session = providerConfigKey === "resend" ? await createResendConnectSession(user.email, resendOAuthCallbackUrl()) : await createNangoConnectSession(user.email, providerConfigKey);
21692
22086
  return c.json({ ok: true, ...session });
21693
22087
  } catch (err) {
21694
22088
  return scheduleConnectionError(c, err, "Unable to start the service connection flow.");
@@ -21696,24 +22090,65 @@ app.post("/schedule-connections/session", auth2, async (c) => {
21696
22090
  });
21697
22091
  app.post("/schedule-connections/:id/reconnect-session", auth2, async (c) => {
21698
22092
  const user = c.get("user");
22093
+ const body = await c.req.json().catch(() => ({}));
22094
+ const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
21699
22095
  try {
21700
- const session = await createNangoReconnectSession(user.email, c.req.param("id"));
22096
+ const session = await isResendConnection(user.email, c.req.param("id"), providerConfigKey) ? await createResendReconnectSession(user.email, c.req.param("id"), resendOAuthCallbackUrl()) : await createNangoReconnectSession(user.email, c.req.param("id"));
21701
22097
  return c.json({ ok: true, ...session });
21702
22098
  } catch (err) {
21703
22099
  return scheduleConnectionError(c, err, "Unable to start the service reconnection flow.");
21704
22100
  }
21705
22101
  });
22102
+ app.delete("/schedule-connections/:id", auth2, async (c) => {
22103
+ const user = c.get("user");
22104
+ const body = await c.req.json().catch(() => ({}));
22105
+ const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
22106
+ try {
22107
+ if (!await isResendConnection(user.email, c.req.param("id"), providerConfigKey)) {
22108
+ return c.json({ ok: false, error: "Disconnect is not available for this connection type yet." }, 409);
22109
+ }
22110
+ await deleteResendConnection(user.email, c.req.param("id"));
22111
+ return c.json({ ok: true });
22112
+ } catch (err) {
22113
+ return scheduleConnectionError(c, err, "Unable to disconnect this service connection.");
22114
+ }
22115
+ });
21706
22116
  app.post("/schedule-connections/:id/enable-actions", auth2, async (c) => {
21707
22117
  const user = c.get("user");
21708
22118
  const body = await c.req.json().catch(() => ({}));
21709
22119
  if (typeof body.enabled !== "boolean") return c.json({ ok: false, error: "enabled must be a boolean." }, 400);
22120
+ const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
21710
22121
  try {
21711
- const actionsEnabled = await setScheduleConnectionActionsEnabled(user.email, c.req.param("id"), body.enabled);
22122
+ const actionsEnabled = await isResendConnection(user.email, c.req.param("id"), providerConfigKey) ? await setResendActionsEnabled(user.email, c.req.param("id"), body.enabled) : await setScheduleConnectionActionsEnabled(user.email, c.req.param("id"), body.enabled);
21712
22123
  return c.json({ ok: true, actionsEnabled });
21713
22124
  } catch (err) {
21714
22125
  return scheduleConnectionError(c, err, "Unable to update this connection's action setting.");
21715
22126
  }
21716
22127
  });
22128
+ app.get("/oauth/resend/callback", async (c) => {
22129
+ const code = c.req.query("code")?.trim();
22130
+ const state = c.req.query("state")?.trim();
22131
+ const providerError = c.req.query("error")?.trim();
22132
+ c.header("cache-control", "no-store");
22133
+ c.header("content-security-policy", "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; base-uri 'none'; frame-ancestors 'none'");
22134
+ c.header("referrer-policy", "no-referrer");
22135
+ c.header("x-content-type-options", "nosniff");
22136
+ if (!state || state.length > 4e3 || !code && !providerError || (code?.length ?? 0) > 4e3 || (providerError?.length ?? 0) > 200) {
22137
+ return c.html(renderResendOAuthResult(false), 400);
22138
+ }
22139
+ try {
22140
+ await completeResendOAuthCallback({
22141
+ ...code ? { code } : {},
22142
+ state,
22143
+ ...providerError ? { error: providerError } : {},
22144
+ redirectUri: resendOAuthCallbackUrl()
22145
+ });
22146
+ return c.html(renderResendOAuthResult(!providerError));
22147
+ } catch (err) {
22148
+ console.warn("[resend-oauth-callback]", err instanceof Error ? err.name : "unknown_error");
22149
+ return c.html(renderResendOAuthResult(false), 502);
22150
+ }
22151
+ });
21717
22152
  app.post("/schedule-connections/actions/slack/send-message", auth2, async (c) => {
21718
22153
  const user = c.get("user");
21719
22154
  const body = await c.req.json().catch(() => ({}));
@@ -21788,25 +22223,42 @@ app.post("/schedule-connections/actions/read", auth2, async (c) => {
21788
22223
  const user = c.get("user");
21789
22224
  const body = await c.req.json().catch(() => ({}));
21790
22225
  const connectionId = providerConfigKeyFrom(body.connectionId);
22226
+ const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
21791
22227
  const tool = providerConfigKeyFrom(body.tool);
21792
22228
  if (!connectionId || !tool) {
21793
22229
  return c.json({ ok: false, error: "connectionId and tool are required." }, 400);
21794
22230
  }
21795
22231
  const args = body.args && typeof body.args === "object" && !Array.isArray(body.args) ? body.args : {};
21796
22232
  try {
21797
- const result = await callScheduleConnectionRead(user.email, connectionId, tool, args);
22233
+ const result = await isResendConnection(user.email, connectionId, providerConfigKey) ? await callResendRead(user.email, connectionId, tool, args) : await callScheduleConnectionRead(user.email, connectionId, tool, args);
21798
22234
  return c.json({ ok: true, result });
21799
22235
  } catch (err) {
21800
22236
  return scheduleConnectionError(c, err, "Unable to read from this connection.");
21801
22237
  }
21802
22238
  });
22239
+ app.post("/schedule-connections/actions/describe", auth2, async (c) => {
22240
+ const user = c.get("user");
22241
+ const body = await c.req.json().catch(() => ({}));
22242
+ const connectionId = providerConfigKeyFrom(body.connectionId);
22243
+ const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
22244
+ const tool = providerConfigKeyFrom(body.tool);
22245
+ if (!connectionId || !tool) return c.json({ ok: false, error: "connectionId and tool are required." }, 400);
22246
+ try {
22247
+ if (!await isResendConnection(user.email, connectionId, providerConfigKey)) {
22248
+ return c.json({ ok: false, error: "Typed tool descriptions are not available for this connection transport yet." }, 501);
22249
+ }
22250
+ return c.json({ ok: true, tool: await describeResendTool(user.email, connectionId, tool) });
22251
+ } catch (err) {
22252
+ return scheduleConnectionError(c, err, "Unable to describe this connection tool.");
22253
+ }
22254
+ });
21803
22255
  app.post("/schedule-connections/actions/export", auth2, async (c) => {
21804
22256
  const user = c.get("user");
21805
22257
  const body = await c.req.json().catch(() => ({}));
21806
22258
  const connectionId = providerConfigKeyFrom(body.connectionId);
21807
22259
  if (!connectionId) return c.json({ ok: false, error: "connectionId is required." }, 400);
21808
22260
  const requestedDataset = typeof body.dataset === "string" ? body.dataset.trim() : "auto";
21809
- const datasets = ["auto", "emails", "calendar_events", "zoom_recordings", "zoom_transcripts"];
22261
+ const datasets = [...CONNECTED_DATA_DATASETS];
21810
22262
  if (!datasets.includes(requestedDataset)) {
21811
22263
  return c.json({ ok: false, error: `dataset must be one of: ${datasets.join(", ")}.` }, 400);
21812
22264
  }
@@ -21829,6 +22281,7 @@ app.post("/schedule-connections/actions/export", auth2, async (c) => {
21829
22281
  return c.json({ ok: false, error: "continuation must be the complete object returned by a prior export." }, 400);
21830
22282
  }
21831
22283
  try {
22284
+ const useResend = await isResendConnection(user.email, connectionId);
21832
22285
  const resolved = resolveConnectedDataExportRequest({
21833
22286
  requestedDataset,
21834
22287
  from: typeof body.from === "string" ? body.from : void 0,
@@ -21845,7 +22298,7 @@ app.post("/schedule-connections/actions/export", auth2, async (c) => {
21845
22298
  maxItems,
21846
22299
  forceArtifact: body.delivery === "artifact",
21847
22300
  ...resolved.cursor ? { cursor: resolved.cursor } : {},
21848
- fetchPage: (input) => callScheduleConnectionExportPage(user.email, input),
22301
+ fetchPage: (input) => useResend ? callResendExportPage(user.email, input) : callScheduleConnectionExportPage(user.email, input),
21849
22302
  writeArtifact: createConnectedDataArtifact
21850
22303
  });
21851
22304
  return c.json(result);
@@ -21880,12 +22333,13 @@ app.post("/schedule-connections/actions/call", auth2, async (c) => {
21880
22333
  const user = c.get("user");
21881
22334
  const body = await c.req.json().catch(() => ({}));
21882
22335
  const connectionId = providerConfigKeyFrom(body.connectionId);
22336
+ const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
21883
22337
  const tool = providerConfigKeyFrom(body.tool);
21884
22338
  if (!connectionId || !tool || !body.args || typeof body.args !== "object" || Array.isArray(body.args)) {
21885
22339
  return c.json({ ok: false, error: "connectionId, tool, and an args object are required." }, 400);
21886
22340
  }
21887
22341
  try {
21888
- const result = await callScheduleConnectionAction(
22342
+ const result = await isResendConnection(user.email, connectionId, providerConfigKey) ? await callResendAction(user.email, connectionId, tool, body.args) : await callScheduleConnectionAction(
21889
22343
  user.email,
21890
22344
  connectionId,
21891
22345
  body.args,
@@ -23112,4 +23566,4 @@ app.get("/blog/:slug/", (c) => {
23112
23566
  export {
23113
23567
  app
23114
23568
  };
23115
- //# sourceMappingURL=server-EYPXW2JG.js.map
23569
+ //# sourceMappingURL=server-FFNOTEQI.js.map