mcp-scraper 0.8.0 → 0.9.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.
@@ -27452,7 +27452,7 @@ var PACKAGE_VERSION;
27452
27452
  var init_version = __esm({
27453
27453
  "src/version.ts"() {
27454
27454
  "use strict";
27455
- PACKAGE_VERSION = "0.8.0";
27455
+ PACKAGE_VERSION = "0.9.0";
27456
27456
  }
27457
27457
  });
27458
27458
 
@@ -27625,7 +27625,7 @@ var init_output_schema_registry = __esm({
27625
27625
  });
27626
27626
 
27627
27627
  // src/mcp/mcp-tool-schemas.ts
27628
- var import_zod33, HarvestPaaInputSchema, ExtractUrlInputSchema, DiffPageInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, ArtifactPointerOutputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema;
27628
+ var import_zod33, HarvestPaaInputSchema, ExtractUrlInputSchema, DiffPageInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, ArtifactPointerOutputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema, ListServiceConnectionsInputSchema, ListServiceConnectionsOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema;
27629
27629
  var init_mcp_tool_schemas = __esm({
27630
27630
  "src/mcp/mcp-tool-schemas.ts"() {
27631
27631
  "use strict";
@@ -28650,6 +28650,52 @@ var init_mcp_tool_schemas = __esm({
28650
28650
  totalBytes: import_zod33.z.number().int().min(0),
28651
28651
  nextOffset: import_zod33.z.number().int().min(0).nullable()
28652
28652
  };
28653
+ ListServiceConnectionsInputSchema = {};
28654
+ ListServiceConnectionsOutputSchema = {
28655
+ connections: import_zod33.z.array(import_zod33.z.object({
28656
+ connectionId: import_zod33.z.string(),
28657
+ providerConfigKey: import_zod33.z.string(),
28658
+ label: import_zod33.z.string(),
28659
+ status: import_zod33.z.string(),
28660
+ actionsEnabled: import_zod33.z.boolean()
28661
+ }))
28662
+ };
28663
+ SlackSendMessageInputSchema = {
28664
+ connectionId: import_zod33.z.string().min(1).describe("A Slack connectionId from list_service_connections, with actionsEnabled true."),
28665
+ channel: import_zod33.z.string().min(1).describe(`Slack channel ID to send to, e.g. "C1234567890". Get this from the connection's own read tools, not guessed.`),
28666
+ text: import_zod33.z.string().min(1).max(4e3).describe("Message text to send.")
28667
+ };
28668
+ SlackSendMessageOutputSchema = {
28669
+ ok: import_zod33.z.boolean(),
28670
+ result: import_zod33.z.unknown().optional(),
28671
+ error: NullableString
28672
+ };
28673
+ GmailSendMessageInputSchema = {
28674
+ connectionId: import_zod33.z.string().min(1).describe("A Gmail connectionId from list_service_connections, with actionsEnabled true."),
28675
+ to: import_zod33.z.string().email().describe("Recipient email address."),
28676
+ subject: import_zod33.z.string().min(1).max(500).describe("Email subject line."),
28677
+ body: import_zod33.z.string().min(1).max(5e4).describe("Plain-text email body.")
28678
+ };
28679
+ GmailSendMessageOutputSchema = {
28680
+ ok: import_zod33.z.boolean(),
28681
+ result: import_zod33.z.unknown().optional(),
28682
+ error: NullableString
28683
+ };
28684
+ GoogleCalendarCreateEventInputSchema = {
28685
+ connectionId: import_zod33.z.string().min(1).describe("A Google Calendar connectionId from list_service_connections, with actionsEnabled true."),
28686
+ calendarId: import_zod33.z.string().min(1).default("primary").describe('Calendar to create the event in. Default "primary".'),
28687
+ summary: import_zod33.z.string().min(1).max(500).describe("Event title."),
28688
+ description: import_zod33.z.string().max(5e3).optional().describe("Event description."),
28689
+ location: import_zod33.z.string().max(500).optional().describe("Event location."),
28690
+ startDateTime: import_zod33.z.string().min(1).describe('Start time, ISO 8601, e.g. "2026-07-15T09:00:00-06:00".'),
28691
+ endDateTime: import_zod33.z.string().min(1).describe('End time, ISO 8601, e.g. "2026-07-15T10:00:00-06:00".'),
28692
+ timeZone: import_zod33.z.string().max(100).optional().describe('IANA timezone, e.g. "America/Denver". Applies to both start and end.')
28693
+ };
28694
+ GoogleCalendarCreateEventOutputSchema = {
28695
+ ok: import_zod33.z.boolean(),
28696
+ result: import_zod33.z.unknown().optional(),
28697
+ error: NullableString
28698
+ };
28653
28699
  }
28654
28700
  });
28655
28701
 
@@ -29329,6 +29375,34 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
29329
29375
  openWorldHint: false
29330
29376
  }
29331
29377
  }, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
29378
+ server.registerTool("list_service_connections", {
29379
+ title: "List Connected Services",
29380
+ description: "List the third-party service connections (Slack, Gmail, Google Calendar) the account has authorized, with each connection's id and whether it is enabled for live actions. Get a connectionId from here before calling slack_send_message, gmail_send_message, or google_calendar_create_event.",
29381
+ inputSchema: ListServiceConnectionsInputSchema,
29382
+ outputSchema: recordOutputSchema("list_service_connections", ListServiceConnectionsOutputSchema),
29383
+ annotations: { title: "List Connected Services", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
29384
+ }, async (input) => executor.listServiceConnections(input));
29385
+ server.registerTool("slack_send_message", {
29386
+ title: "Send Slack Message",
29387
+ description: "Send a message to a Slack channel through a connected, action-enabled Slack connection. Requires a connectionId from list_service_connections with actionsEnabled true; the person must have explicitly turned actions on for that connection.",
29388
+ inputSchema: SlackSendMessageInputSchema,
29389
+ outputSchema: recordOutputSchema("slack_send_message", SlackSendMessageOutputSchema),
29390
+ annotations: { title: "Send Slack Message", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
29391
+ }, async (input) => executor.slackSendMessage(input));
29392
+ server.registerTool("gmail_send_message", {
29393
+ title: "Send Gmail Message",
29394
+ description: "Send an email through a connected, action-enabled Gmail connection. Requires a connectionId from list_service_connections with actionsEnabled true; the person must have explicitly turned actions on for that connection.",
29395
+ inputSchema: GmailSendMessageInputSchema,
29396
+ outputSchema: recordOutputSchema("gmail_send_message", GmailSendMessageOutputSchema),
29397
+ annotations: { title: "Send Gmail Message", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
29398
+ }, async (input) => executor.gmailSendMessage(input));
29399
+ server.registerTool("google_calendar_create_event", {
29400
+ title: "Create Calendar Event",
29401
+ description: "Create an event on a connected, action-enabled Google Calendar connection. Requires a connectionId from list_service_connections with actionsEnabled true; the person must have explicitly turned actions on for that connection.",
29402
+ inputSchema: GoogleCalendarCreateEventInputSchema,
29403
+ outputSchema: recordOutputSchema("google_calendar_create_event", GoogleCalendarCreateEventOutputSchema),
29404
+ annotations: { title: "Create Calendar Event", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
29405
+ }, async (input) => executor.googleCalendarCreateEvent(input));
29332
29406
  }
29333
29407
  var import_mcp, import_node_fs9, import_node_path12, import_node_crypto10;
29334
29408
  var init_paa_mcp_server = __esm({
@@ -29637,6 +29711,18 @@ var init_http_mcp_tool_executor = __esm({
29637
29711
  creditsInfo(input) {
29638
29712
  return this.call("/billing/credits", input);
29639
29713
  }
29714
+ listServiceConnections(input) {
29715
+ return this.getJson("/schedule-connections");
29716
+ }
29717
+ slackSendMessage(input) {
29718
+ return this.call("/schedule-connections/actions/slack/send-message", input);
29719
+ }
29720
+ gmailSendMessage(input) {
29721
+ return this.call("/schedule-connections/actions/gmail/send-message", input);
29722
+ }
29723
+ googleCalendarCreateEvent(input) {
29724
+ return this.call("/schedule-connections/actions/google-calendar/create-event", input);
29725
+ }
29640
29726
  captureSerpSnapshot(input) {
29641
29727
  return this.call("/serp-intelligence/capture", input, this.serpIntelligenceTimeoutMs);
29642
29728
  }
@@ -38705,6 +38791,312 @@ var init_memory_db = __esm({
38705
38791
  }
38706
38792
  });
38707
38793
 
38794
+ // src/api/nango-control.ts
38795
+ function controlBaseUrl() {
38796
+ return (process.env.NANGO_CONTROL_URL?.trim() || DEFAULT_NANGO_CONTROL_URL).replace(/\/$/, "");
38797
+ }
38798
+ function controlSecret() {
38799
+ const secret2 = process.env.SCHEDULE_INTEGRATIONS_SECRET?.trim();
38800
+ if (!secret2) throw new NangoControlError("Scheduled service connections are not configured.", 503);
38801
+ return secret2;
38802
+ }
38803
+ function isRecord(value) {
38804
+ return !!value && typeof value === "object" && !Array.isArray(value);
38805
+ }
38806
+ function unwrapData(value) {
38807
+ if (isRecord(value) && isRecord(value.data)) return value.data;
38808
+ return value;
38809
+ }
38810
+ function cleanString(value, max = 300) {
38811
+ if (typeof value !== "string") return null;
38812
+ const trimmed = value.trim();
38813
+ return trimmed ? trimmed.slice(0, max) : null;
38814
+ }
38815
+ function firstString(record, keys, max = 300) {
38816
+ for (const key of keys) {
38817
+ const value = cleanString(record[key], max);
38818
+ if (value) return value;
38819
+ }
38820
+ return null;
38821
+ }
38822
+ function cleanTools(value) {
38823
+ if (!Array.isArray(value)) return [];
38824
+ const tools = value.map((tool) => cleanString(tool, 200)).filter((tool) => !!tool);
38825
+ return [...new Set(tools)].slice(0, 200);
38826
+ }
38827
+ function cleanStringArray(value, maxItems = 20, maxLength = 100) {
38828
+ if (!Array.isArray(value)) return [];
38829
+ const items = value.map((item) => cleanString(item, maxLength)).filter((item) => !!item);
38830
+ return [...new Set(items)].slice(0, maxItems);
38831
+ }
38832
+ function cleanHttpsUrl(value) {
38833
+ const candidate = cleanString(value, 2e3);
38834
+ if (!candidate) return null;
38835
+ try {
38836
+ const url = new URL(candidate);
38837
+ return url.protocol === "https:" ? url.toString() : null;
38838
+ } catch {
38839
+ return null;
38840
+ }
38841
+ }
38842
+ function arrayFromPayload(value, keys) {
38843
+ const unwrapped = unwrapData(value);
38844
+ if (Array.isArray(unwrapped)) return unwrapped;
38845
+ if (!isRecord(unwrapped)) return [];
38846
+ for (const key of keys) {
38847
+ if (Array.isArray(unwrapped[key])) return unwrapped[key];
38848
+ }
38849
+ return [];
38850
+ }
38851
+ async function controlRequest(path6, init) {
38852
+ const response = await fetch(`${controlBaseUrl()}${path6}`, {
38853
+ ...init,
38854
+ headers: {
38855
+ accept: "application/json",
38856
+ authorization: `Bearer ${controlSecret()}`,
38857
+ ...init?.body ? { "content-type": "application/json" } : {},
38858
+ ...init?.headers ?? {}
38859
+ },
38860
+ signal: AbortSignal.timeout(2e4)
38861
+ }).catch((err) => {
38862
+ throw new NangoControlError(`Scheduled service connection control is unavailable: ${err instanceof Error ? err.message : "network error"}`);
38863
+ });
38864
+ const text = await response.text();
38865
+ let body = {};
38866
+ try {
38867
+ body = text ? JSON.parse(text) : {};
38868
+ } catch {
38869
+ body = {};
38870
+ }
38871
+ if (!response.ok) {
38872
+ const upstream = isRecord(body) ? cleanString(body.error, 300) : null;
38873
+ throw new NangoControlError(upstream || `Scheduled service connection control failed (${response.status}).`);
38874
+ }
38875
+ return body;
38876
+ }
38877
+ function sanitizeScheduleConnectionSelections(value) {
38878
+ if (value == null) return [];
38879
+ if (!Array.isArray(value)) throw new ScheduleConnectionValidationError("connections must be an array.");
38880
+ if (value.length > 20) throw new ScheduleConnectionValidationError("A scheduled action can use at most 20 service connections.");
38881
+ const selections = [];
38882
+ const seen = /* @__PURE__ */ new Set();
38883
+ for (const item of value) {
38884
+ if (!isRecord(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
38885
+ const connectionId = firstString(item, ["connectionId", "connection_id"]);
38886
+ const providerConfigKey = firstString(item, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id"]);
38887
+ const allowedTools = cleanTools(item.allowedTools ?? item.allowed_tools ?? item.allowedCapabilities ?? item.allowed_capabilities);
38888
+ if (!connectionId || !providerConfigKey) throw new ScheduleConnectionValidationError("Each service connection needs a connectionId and providerConfigKey.");
38889
+ if (allowedTools.length === 0) throw new ScheduleConnectionValidationError("Each selected service needs at least one allowed tool.");
38890
+ const key = `${providerConfigKey}:${connectionId}`;
38891
+ if (seen.has(key)) continue;
38892
+ seen.add(key);
38893
+ selections.push({ connectionId, providerConfigKey, allowedTools });
38894
+ }
38895
+ return selections;
38896
+ }
38897
+ async function getNangoCatalog() {
38898
+ const body = await controlRequest("/api/internal/nango/catalog");
38899
+ const rows = arrayFromPayload(body, ["providers", "catalog", "integrations", "services"]);
38900
+ const result = [];
38901
+ for (const row of rows) {
38902
+ if (!isRecord(row)) continue;
38903
+ const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "id"]);
38904
+ if (!providerConfigKey) continue;
38905
+ const provider = firstString(row, ["provider"]);
38906
+ const label = firstString(row, ["label", "displayName", "display_name", "name"]) || providerConfigKey;
38907
+ const description = firstString(row, ["description"], 500);
38908
+ const logoUrl = cleanHttpsUrl(row.logoUrl ?? row.logo_url ?? row.logo);
38909
+ const docsUrl = cleanHttpsUrl(row.docsUrl ?? row.docs_url ?? row.docs);
38910
+ const authMode = firstString(row, ["authMode", "auth_mode"], 100);
38911
+ const categories = cleanStringArray(row.categories);
38912
+ const disabledTools = DISABLED_NANGO_TOOLS[providerConfigKey];
38913
+ const safeDefaultAllowedTools = cleanTools(
38914
+ row.safeDefaultAllowedTools ?? row.safe_default_allowed_tools ?? row.defaultAllowedTools ?? row.default_allowed_tools ?? row.allowedTools ?? row.allowed_tools
38915
+ ).filter((tool) => !disabledTools?.has(tool));
38916
+ result.push({
38917
+ providerConfigKey,
38918
+ provider,
38919
+ label,
38920
+ description,
38921
+ logoUrl,
38922
+ docsUrl,
38923
+ authMode,
38924
+ categories,
38925
+ safeDefaultAllowedTools
38926
+ });
38927
+ }
38928
+ return result;
38929
+ }
38930
+ async function getNangoConnections(identity) {
38931
+ const query = new URLSearchParams({ identity }).toString();
38932
+ const body = await controlRequest(`/api/internal/nango/connections?${query}`);
38933
+ const rows = arrayFromPayload(body, ["connections"]);
38934
+ const result = [];
38935
+ for (const row of rows) {
38936
+ if (!isRecord(row)) continue;
38937
+ const connectionId = firstString(row, ["connectionId", "connection_id", "id"]);
38938
+ const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
38939
+ if (!connectionId || !providerConfigKey) continue;
38940
+ const provider = firstString(row, ["provider"]);
38941
+ const label = firstString(row, ["label", "accountLabel", "account_label", "displayName", "display_name", "endUserEmail", "end_user_email"]) || providerConfigKey;
38942
+ const rawStatus = firstString(row, ["status"], 64) || "connected";
38943
+ const reconnectRequired = row.reconnectRequired === true || row.reconnect_required === true || rawStatus === "needs_reauth" || rawStatus === "reauth_required" || rawStatus === "invalid" || rawStatus === "error" || rawStatus === "revoked" || rawStatus === "disabled";
38944
+ result.push({
38945
+ connectionId,
38946
+ providerConfigKey,
38947
+ provider,
38948
+ label,
38949
+ status: reconnectRequired ? "needs_reauth" : "connected",
38950
+ reconnectRequired,
38951
+ actionsEnabled: row.actionsEnabled === true || row.actions_enabled === true,
38952
+ createdAt: firstString(row, ["createdAt", "created_at"], 100),
38953
+ updatedAt: firstString(row, ["updatedAt", "updated_at"], 100)
38954
+ });
38955
+ }
38956
+ return result;
38957
+ }
38958
+ function sanitizeConnectSession(body) {
38959
+ const data = unwrapData(body);
38960
+ if (!isRecord(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
38961
+ const connectLink = firstString(data, ["connectLink", "connect_link"], 2e3);
38962
+ if (!connectLink) throw new NangoControlError("The connection service did not return a connect link.");
38963
+ return {
38964
+ connectLink,
38965
+ sessionToken: firstString(data, ["sessionToken", "session_token", "token"], 2e3),
38966
+ expiresAt: firstString(data, ["expiresAt", "expires_at"], 100)
38967
+ };
38968
+ }
38969
+ async function createNangoConnectSession(identity, providerConfigKey) {
38970
+ const body = await controlRequest("/api/internal/nango/connect-session", {
38971
+ method: "POST",
38972
+ body: JSON.stringify({ identity, email: identity, provider: providerConfigKey })
38973
+ });
38974
+ return sanitizeConnectSession(body);
38975
+ }
38976
+ async function createNangoReconnectSession(identity, connectionId) {
38977
+ const body = await controlRequest("/api/internal/nango/reconnect-session", {
38978
+ method: "POST",
38979
+ body: JSON.stringify({ identity, connectionId, email: identity })
38980
+ });
38981
+ return sanitizeConnectSession(body);
38982
+ }
38983
+ function sanitizeBindingRows(body, defaultScheduleActionId) {
38984
+ const data = unwrapData(body);
38985
+ const flattened = [];
38986
+ if (isRecord(data) && isRecord(data.bindings) && !Array.isArray(data.bindings)) {
38987
+ for (const [scheduleActionId, value] of Object.entries(data.bindings)) {
38988
+ if (Array.isArray(value)) value.forEach((row) => flattened.push({ row, scheduleActionId }));
38989
+ }
38990
+ } else {
38991
+ const rows = arrayFromPayload(data, ["bindings", "connections"]);
38992
+ for (const row of rows) {
38993
+ if (isRecord(row) && Array.isArray(row.connections)) {
38994
+ const scheduleActionId = firstString(row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || defaultScheduleActionId;
38995
+ row.connections.forEach((connection) => flattened.push({ row: connection, scheduleActionId }));
38996
+ } else {
38997
+ flattened.push({ row, scheduleActionId: defaultScheduleActionId });
38998
+ }
38999
+ }
39000
+ }
39001
+ const result = [];
39002
+ for (const item of flattened) {
39003
+ if (!isRecord(item.row)) continue;
39004
+ const connectionId = firstString(item.row, ["connectionId", "connection_id"]);
39005
+ const providerConfigKey = firstString(item.row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
39006
+ if (!connectionId || !providerConfigKey) continue;
39007
+ result.push({
39008
+ scheduleActionId: firstString(item.row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || item.scheduleActionId,
39009
+ connectionId,
39010
+ providerConfigKey,
39011
+ allowedTools: cleanTools(item.row.allowedTools ?? item.row.allowed_tools ?? item.row.allowedCapabilities ?? item.row.allowed_capabilities),
39012
+ label: firstString(item.row, ["label", "accountLabel", "account_label", "displayName", "display_name"])
39013
+ });
39014
+ }
39015
+ return result;
39016
+ }
39017
+ async function getScheduleConnectionBindings(identity, scheduleActionId) {
39018
+ const query = new URLSearchParams({ identity, scheduleId: scheduleActionId });
39019
+ const body = await controlRequest(`/api/internal/nango/bindings?${query.toString()}`);
39020
+ return sanitizeBindingRows(body, scheduleActionId);
39021
+ }
39022
+ async function putScheduleConnectionBindings(identity, scheduleActionId, connections) {
39023
+ const body = await controlRequest("/api/internal/nango/bindings", {
39024
+ method: "PUT",
39025
+ body: JSON.stringify({
39026
+ identity,
39027
+ scheduleId: scheduleActionId,
39028
+ connections: connections.map((connection) => ({
39029
+ connectionId: connection.connectionId,
39030
+ allowedTools: connection.allowedTools
39031
+ }))
39032
+ })
39033
+ });
39034
+ const sanitized = sanitizeBindingRows(body, scheduleActionId);
39035
+ const expectedByConnectionId = new Map(connections.map((connection) => [
39036
+ connection.connectionId,
39037
+ {
39038
+ providerConfigKey: connection.providerConfigKey,
39039
+ allowedTools: [...connection.allowedTools].sort()
39040
+ }
39041
+ ]));
39042
+ const responseMatchesRequest = sanitized.length === connections.length && sanitized.every((binding) => {
39043
+ const expected = expectedByConnectionId.get(binding.connectionId);
39044
+ return !!expected && binding.scheduleActionId === scheduleActionId && binding.providerConfigKey === expected.providerConfigKey && [...binding.allowedTools].sort().join("\0") === expected.allowedTools.join("\0");
39045
+ });
39046
+ if (!responseMatchesRequest) {
39047
+ throw new NangoControlError("Scheduled service connection control returned an invalid binding response.");
39048
+ }
39049
+ return sanitized;
39050
+ }
39051
+ async function deleteScheduleConnectionBindings(identity, scheduleActionId) {
39052
+ await controlRequest("/api/internal/nango/bindings", {
39053
+ method: "DELETE",
39054
+ body: JSON.stringify({ identity, scheduleId: scheduleActionId })
39055
+ });
39056
+ }
39057
+ async function setScheduleConnectionActionsEnabled(identity, connectionId, enabled) {
39058
+ const body = await controlRequest("/api/internal/nango/connections/actions/enable", {
39059
+ method: "POST",
39060
+ body: JSON.stringify({ identity, connectionId, enabled })
39061
+ });
39062
+ const data = unwrapData(body);
39063
+ if (!isRecord(data) || !isRecord(data.connection)) return enabled;
39064
+ return data.connection.actionsEnabled === true;
39065
+ }
39066
+ async function callScheduleConnectionAction(identity, connectionId, input) {
39067
+ const body = await controlRequest("/api/internal/nango/connections/actions/call", {
39068
+ method: "POST",
39069
+ body: JSON.stringify({ identity, connectionId, input })
39070
+ });
39071
+ const data = unwrapData(body);
39072
+ return isRecord(data) ? data.result ?? data : data;
39073
+ }
39074
+ var DEFAULT_NANGO_CONTROL_URL, DISABLED_NANGO_TOOLS, NangoControlError, ScheduleConnectionValidationError;
39075
+ var init_nango_control = __esm({
39076
+ "src/api/nango-control.ts"() {
39077
+ "use strict";
39078
+ DEFAULT_NANGO_CONTROL_URL = "https://mcp-scraper-scheduler.vercel.app";
39079
+ DISABLED_NANGO_TOOLS = {
39080
+ "google-mail": /* @__PURE__ */ new Set(["list-filters"]),
39081
+ slack: /* @__PURE__ */ new Set(["search-files", "search-messages"])
39082
+ };
39083
+ NangoControlError = class extends Error {
39084
+ status;
39085
+ constructor(message, status = 502) {
39086
+ super(message);
39087
+ this.name = "NangoControlError";
39088
+ this.status = status;
39089
+ }
39090
+ };
39091
+ ScheduleConnectionValidationError = class extends Error {
39092
+ constructor(message) {
39093
+ super(message);
39094
+ this.name = "ScheduleConnectionValidationError";
39095
+ }
39096
+ };
39097
+ }
39098
+ });
39099
+
38708
39100
  // src/api/webhook.ts
38709
39101
  async function deliverWebhook(url, payload, retries = 3) {
38710
39102
  for (let attempt = 1; attempt <= retries; attempt++) {
@@ -38930,6 +39322,20 @@ function maybeProvisionInBackground(user, vaultCount) {
38930
39322
  }
38931
39323
  })();
38932
39324
  }
39325
+ function scheduleConnectionError(c, err, fallback) {
39326
+ if (err instanceof ScheduleConnectionValidationError) return c.json({ ok: false, error: err.message }, 400);
39327
+ if (err instanceof NangoControlError) return c.json({ ok: false, error: err.message }, err.status);
39328
+ console.error("[schedule-connections]", err instanceof Error ? err.message : String(err));
39329
+ return c.json({ ok: false, error: fallback }, 502);
39330
+ }
39331
+ function providerConfigKeyFrom(value) {
39332
+ if (typeof value !== "string") return null;
39333
+ const key = value.trim();
39334
+ return key && key.length <= 200 ? key : null;
39335
+ }
39336
+ function bindingsForSchedule(bindings, scheduleActionId) {
39337
+ return bindings.filter((binding) => binding.scheduleActionId === scheduleActionId);
39338
+ }
38933
39339
  function combineAbortSignals(signals) {
38934
39340
  const controller = new AbortController();
38935
39341
  const abortFrom = (signal) => {
@@ -39037,6 +39443,7 @@ var init_server = __esm({
39037
39443
  init_session();
39038
39444
  init_memory();
39039
39445
  init_memory_db();
39446
+ init_nango_control();
39040
39447
  init_concurrency_gates();
39041
39448
  secureCookies2 = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
39042
39449
  isProduction2 = secureCookies2;
@@ -39569,6 +39976,103 @@ var init_server = __esm({
39569
39976
  return c.json({ error: message }, 500);
39570
39977
  }
39571
39978
  });
39979
+ app.get("/schedule-connection-catalog", auth2, async (c) => {
39980
+ try {
39981
+ return c.json({ ok: true, catalog: await getNangoCatalog() });
39982
+ } catch (err) {
39983
+ return scheduleConnectionError(c, err, "Unable to load the service connection catalog.");
39984
+ }
39985
+ });
39986
+ app.get("/schedule-connections", auth2, async (c) => {
39987
+ try {
39988
+ const user = c.get("user");
39989
+ return c.json({ ok: true, connections: await getNangoConnections(user.email) });
39990
+ } catch (err) {
39991
+ return scheduleConnectionError(c, err, "Unable to load service connections.");
39992
+ }
39993
+ });
39994
+ app.post("/schedule-connections/session", auth2, async (c) => {
39995
+ const user = c.get("user");
39996
+ const body = await c.req.json().catch(() => ({}));
39997
+ const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
39998
+ if (!providerConfigKey) return c.json({ ok: false, error: "providerConfigKey is required." }, 400);
39999
+ try {
40000
+ const session = await createNangoConnectSession(user.email, providerConfigKey);
40001
+ return c.json({ ok: true, ...session });
40002
+ } catch (err) {
40003
+ return scheduleConnectionError(c, err, "Unable to start the service connection flow.");
40004
+ }
40005
+ });
40006
+ app.post("/schedule-connections/:id/reconnect-session", auth2, async (c) => {
40007
+ const user = c.get("user");
40008
+ try {
40009
+ const session = await createNangoReconnectSession(user.email, c.req.param("id"));
40010
+ return c.json({ ok: true, ...session });
40011
+ } catch (err) {
40012
+ return scheduleConnectionError(c, err, "Unable to start the service reconnection flow.");
40013
+ }
40014
+ });
40015
+ app.post("/schedule-connections/:id/enable-actions", auth2, async (c) => {
40016
+ const user = c.get("user");
40017
+ const body = await c.req.json().catch(() => ({}));
40018
+ if (typeof body.enabled !== "boolean") return c.json({ ok: false, error: "enabled must be a boolean." }, 400);
40019
+ try {
40020
+ const actionsEnabled = await setScheduleConnectionActionsEnabled(user.email, c.req.param("id"), body.enabled);
40021
+ return c.json({ ok: true, actionsEnabled });
40022
+ } catch (err) {
40023
+ return scheduleConnectionError(c, err, "Unable to update this connection's action setting.");
40024
+ }
40025
+ });
40026
+ app.post("/schedule-connections/actions/slack/send-message", auth2, async (c) => {
40027
+ const user = c.get("user");
40028
+ const body = await c.req.json().catch(() => ({}));
40029
+ const connectionId = providerConfigKeyFrom(body.connectionId);
40030
+ if (!connectionId || typeof body.channel !== "string" || typeof body.text !== "string") {
40031
+ return c.json({ ok: false, error: "connectionId, channel, and text are required." }, 400);
40032
+ }
40033
+ try {
40034
+ const result = await callScheduleConnectionAction(user.email, connectionId, { channel: body.channel, text: body.text });
40035
+ return c.json({ ok: true, result });
40036
+ } catch (err) {
40037
+ return scheduleConnectionError(c, err, "Unable to send the Slack message.");
40038
+ }
40039
+ });
40040
+ app.post("/schedule-connections/actions/gmail/send-message", auth2, async (c) => {
40041
+ const user = c.get("user");
40042
+ const body = await c.req.json().catch(() => ({}));
40043
+ const connectionId = providerConfigKeyFrom(body.connectionId);
40044
+ if (!connectionId || typeof body.to !== "string" || typeof body.subject !== "string" || typeof body.body !== "string") {
40045
+ return c.json({ ok: false, error: "connectionId, to, subject, and body are required." }, 400);
40046
+ }
40047
+ try {
40048
+ const result = await callScheduleConnectionAction(user.email, connectionId, { to: body.to, subject: body.subject, body: body.body });
40049
+ return c.json({ ok: true, result });
40050
+ } catch (err) {
40051
+ return scheduleConnectionError(c, err, "Unable to send the email.");
40052
+ }
40053
+ });
40054
+ app.post("/schedule-connections/actions/google-calendar/create-event", auth2, async (c) => {
40055
+ const user = c.get("user");
40056
+ const body = await c.req.json().catch(() => ({}));
40057
+ const connectionId = providerConfigKeyFrom(body.connectionId);
40058
+ if (!connectionId || typeof body.summary !== "string" || typeof body.startDateTime !== "string" || typeof body.endDateTime !== "string") {
40059
+ return c.json({ ok: false, error: "connectionId, summary, startDateTime, and endDateTime are required." }, 400);
40060
+ }
40061
+ const timeZone = typeof body.timeZone === "string" ? body.timeZone : void 0;
40062
+ try {
40063
+ const result = await callScheduleConnectionAction(user.email, connectionId, {
40064
+ calendarId: typeof body.calendarId === "string" ? body.calendarId : "primary",
40065
+ summary: body.summary,
40066
+ description: typeof body.description === "string" ? body.description : void 0,
40067
+ location: typeof body.location === "string" ? body.location : void 0,
40068
+ start: { dateTime: body.startDateTime, timeZone },
40069
+ end: { dateTime: body.endDateTime, timeZone }
40070
+ });
40071
+ return c.json({ ok: true, result });
40072
+ } catch (err) {
40073
+ return scheduleConnectionError(c, err, "Unable to create the calendar event.");
40074
+ }
40075
+ });
39572
40076
  app.post("/schedule-link", auth2, async (c) => {
39573
40077
  try {
39574
40078
  const user = c.get("user");
@@ -39587,15 +40091,58 @@ var init_server = __esm({
39587
40091
  const { key, error } = await getOrCreateUserMemoryKey(user);
39588
40092
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
39589
40093
  const r = await memoryCall("listScheduledActionsTool", {}, key);
39590
- return c.json(r);
40094
+ if (!r.ok || !Array.isArray(r.actions)) return c.json(r);
40095
+ try {
40096
+ const actions = await Promise.all(r.actions.map(async (action) => {
40097
+ const id = typeof action.id === "string" ? action.id : "";
40098
+ if (!id) return { ...action, connections: [] };
40099
+ const bindings = await getScheduleConnectionBindings(user.email, id);
40100
+ return { ...action, connections: bindingsForSchedule(bindings, id) };
40101
+ }));
40102
+ return c.json({
40103
+ ...r,
40104
+ actions
40105
+ });
40106
+ } catch (err) {
40107
+ console.warn("[schedule-actions] connection bindings unavailable:", err instanceof Error ? err.message : String(err));
40108
+ return c.json({
40109
+ ...r,
40110
+ actions: r.actions.map((action) => ({ ...action, connections: [] })),
40111
+ connectionBindingsUnavailable: true
40112
+ });
40113
+ }
39591
40114
  });
39592
40115
  app.post("/schedule-actions", auth2, async (c) => {
39593
40116
  const user = c.get("user");
39594
40117
  const { key, error } = await getOrCreateUserMemoryKey(user);
39595
40118
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
39596
40119
  const body = await c.req.json().catch(() => ({}));
39597
- const r = await memoryCall("createScheduledActionTool", body, key);
39598
- return c.json(r);
40120
+ const hasConnections = Object.prototype.hasOwnProperty.call(body, "connections");
40121
+ let connections;
40122
+ try {
40123
+ connections = sanitizeScheduleConnectionSelections(body.connections);
40124
+ } catch (err) {
40125
+ return scheduleConnectionError(c, err, "Invalid service connections.");
40126
+ }
40127
+ const actionBody = { ...body };
40128
+ delete actionBody.connections;
40129
+ const r = await memoryCall("createScheduledActionTool", actionBody, key);
40130
+ if (!r.ok || !hasConnections) return c.json(r);
40131
+ if (connections.length === 0) return c.json({ ...r, connections: [] });
40132
+ if (!r.id) {
40133
+ console.error("[schedule-actions] create returned no id while service connections were requested");
40134
+ return c.json({ ok: false, error: "The scheduled action was created without a usable id; service connections were not attached." }, 502);
40135
+ }
40136
+ try {
40137
+ const bound = await putScheduleConnectionBindings(user.email, r.id, connections);
40138
+ return c.json({ ...r, connections: bound });
40139
+ } catch (err) {
40140
+ const rollback = await memoryCall("deleteScheduledActionTool", { id: r.id }, key).catch(() => ({ ok: false }));
40141
+ if (!rollback.ok) console.error("[schedule-actions] failed to compensate action after connection binding failure", r.id);
40142
+ const response = scheduleConnectionError(c, err, "The scheduled action was not created because its service connections could not be attached.");
40143
+ response.headers.set("x-schedule-create-rolled-back", rollback.ok ? "true" : "false");
40144
+ return response;
40145
+ }
39599
40146
  });
39600
40147
  app.post("/schedule-actions/propose", auth2, async (c) => {
39601
40148
  const user = c.get("user");
@@ -39605,6 +40152,33 @@ var init_server = __esm({
39605
40152
  const r = await memoryCall("proposeScheduledActionTool", body, key);
39606
40153
  return c.json(r);
39607
40154
  });
40155
+ app.get("/schedule-actions/:id/connections", auth2, async (c) => {
40156
+ try {
40157
+ const user = c.get("user");
40158
+ const scheduleActionId = c.req.param("id");
40159
+ const bindings = await getScheduleConnectionBindings(user.email, scheduleActionId);
40160
+ return c.json({ ok: true, connections: bindingsForSchedule(bindings, scheduleActionId) });
40161
+ } catch (err) {
40162
+ return scheduleConnectionError(c, err, "Unable to load scheduled action service connections.");
40163
+ }
40164
+ });
40165
+ app.put("/schedule-actions/:id/connections", auth2, async (c) => {
40166
+ const user = c.get("user");
40167
+ const body = await c.req.json().catch(() => ({}));
40168
+ let connections;
40169
+ try {
40170
+ connections = sanitizeScheduleConnectionSelections(body.connections);
40171
+ } catch (err) {
40172
+ return scheduleConnectionError(c, err, "Invalid service connections.");
40173
+ }
40174
+ try {
40175
+ const scheduleActionId = c.req.param("id");
40176
+ const bindings = await putScheduleConnectionBindings(user.email, scheduleActionId, connections);
40177
+ return c.json({ ok: true, connections: bindings });
40178
+ } catch (err) {
40179
+ return scheduleConnectionError(c, err, "Unable to update scheduled action service connections.");
40180
+ }
40181
+ });
39608
40182
  app.post("/schedule-actions/:id/pause", auth2, async (c) => {
39609
40183
  const user = c.get("user");
39610
40184
  const { key, error } = await getOrCreateUserMemoryKey(user);
@@ -39623,8 +40197,16 @@ var init_server = __esm({
39623
40197
  const user = c.get("user");
39624
40198
  const { key, error } = await getOrCreateUserMemoryKey(user);
39625
40199
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
39626
- const r = await memoryCall("deleteScheduledActionTool", { id: c.req.param("id") }, key);
39627
- return c.json(r);
40200
+ const scheduleActionId = c.req.param("id");
40201
+ const r = await memoryCall("deleteScheduledActionTool", { id: scheduleActionId }, key);
40202
+ if (!r.ok) return c.json(r);
40203
+ try {
40204
+ await deleteScheduleConnectionBindings(user.email, scheduleActionId);
40205
+ return c.json(r);
40206
+ } catch (err) {
40207
+ console.error("[schedule-actions] action deleted but connection binding cleanup failed", scheduleActionId, err instanceof Error ? err.message : String(err));
40208
+ return c.json({ ...r, connectionBindingCleanupPending: true });
40209
+ }
39628
40210
  });
39629
40211
  app.post("/schedule-link/revoke", auth2, async (c) => {
39630
40212
  try {