mcp-scraper 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/api-server.cjs +682 -7
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +133 -1
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/chunk-B7BAJU7K.js +7 -0
- package/dist/chunk-B7BAJU7K.js.map +1 -0
- package/dist/{chunk-ITJC3NN4.js → chunk-IRYBBEZJ.js} +134 -2
- package/dist/chunk-IRYBBEZJ.js.map +1 -0
- package/dist/{server-WBDVVHR6.js → server-KTA7CFX6.js} +544 -8
- package/dist/server-KTA7CFX6.js.map +1 -0
- package/docs/oauth-legal-review.md +38 -0
- package/package.json +1 -1
- package/dist/chunk-3MKSXDJ7.js +0 -7
- package/dist/chunk-3MKSXDJ7.js.map +0 -1
- package/dist/chunk-ITJC3NN4.js.map +0 -1
- package/dist/server-WBDVVHR6.js.map +0 -1
package/dist/bin/api-server.cjs
CHANGED
|
@@ -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.
|
|
27455
|
+
PACKAGE_VERSION = "0.10.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, ReadServiceConnectionInputSchema, ReadServiceConnectionOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
|
|
27629
27629
|
var init_mcp_tool_schemas = __esm({
|
|
27630
27630
|
"src/mcp/mcp-tool-schemas.ts"() {
|
|
27631
27631
|
"use strict";
|
|
@@ -28650,6 +28650,78 @@ 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
|
+
readTools: import_zod33.z.array(import_zod33.z.string()).describe("Tool names this connection can be read with via read_service_connection."),
|
|
28662
|
+
vaultName: import_zod33.z.string().nullable().describe("Memory vault this connection's digest writes into, if it has run at least once. Search it with memory-search."),
|
|
28663
|
+
tableName: import_zod33.z.string().nullable().describe("Table this connection's digest writes structured rows into, if it has run at least once. Query it with table-query.")
|
|
28664
|
+
}))
|
|
28665
|
+
};
|
|
28666
|
+
ReadServiceConnectionInputSchema = {
|
|
28667
|
+
connectionId: import_zod33.z.string().min(1).describe("A connectionId from list_service_connections."),
|
|
28668
|
+
tool: import_zod33.z.string().min(1).describe("One of that connection's readTools (from list_service_connections). An unlisted tool name is rejected with the allowed list."),
|
|
28669
|
+
args: import_zod33.z.record(import_zod33.z.string(), import_zod33.z.unknown()).optional().describe("Arguments for the tool, if it needs any (e.g. a channel id, a date filter).")
|
|
28670
|
+
};
|
|
28671
|
+
ReadServiceConnectionOutputSchema = {
|
|
28672
|
+
ok: import_zod33.z.boolean(),
|
|
28673
|
+
result: import_zod33.z.unknown().optional(),
|
|
28674
|
+
error: NullableString
|
|
28675
|
+
};
|
|
28676
|
+
SlackSendMessageInputSchema = {
|
|
28677
|
+
connectionId: import_zod33.z.string().min(1).describe("A Slack connectionId from list_service_connections, with actionsEnabled true."),
|
|
28678
|
+
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.`),
|
|
28679
|
+
text: import_zod33.z.string().min(1).max(4e3).describe("Message text to send.")
|
|
28680
|
+
};
|
|
28681
|
+
SlackSendMessageOutputSchema = {
|
|
28682
|
+
ok: import_zod33.z.boolean(),
|
|
28683
|
+
result: import_zod33.z.unknown().optional(),
|
|
28684
|
+
error: NullableString
|
|
28685
|
+
};
|
|
28686
|
+
GmailSendMessageInputSchema = {
|
|
28687
|
+
connectionId: import_zod33.z.string().min(1).describe("A Gmail connectionId from list_service_connections, with actionsEnabled true."),
|
|
28688
|
+
to: import_zod33.z.string().email().describe("Recipient email address."),
|
|
28689
|
+
subject: import_zod33.z.string().min(1).max(500).describe("Email subject line."),
|
|
28690
|
+
body: import_zod33.z.string().min(1).max(5e4).describe("Plain-text email body.")
|
|
28691
|
+
};
|
|
28692
|
+
GmailSendMessageOutputSchema = {
|
|
28693
|
+
ok: import_zod33.z.boolean(),
|
|
28694
|
+
result: import_zod33.z.unknown().optional(),
|
|
28695
|
+
error: NullableString
|
|
28696
|
+
};
|
|
28697
|
+
GoogleCalendarCreateEventInputSchema = {
|
|
28698
|
+
connectionId: import_zod33.z.string().min(1).describe("A Google Calendar connectionId from list_service_connections, with actionsEnabled true."),
|
|
28699
|
+
calendarId: import_zod33.z.string().min(1).default("primary").describe('Calendar to create the event in. Default "primary".'),
|
|
28700
|
+
summary: import_zod33.z.string().min(1).max(500).describe("Event title."),
|
|
28701
|
+
description: import_zod33.z.string().max(5e3).optional().describe("Event description."),
|
|
28702
|
+
location: import_zod33.z.string().max(500).optional().describe("Event location."),
|
|
28703
|
+
startDateTime: import_zod33.z.string().min(1).describe('Start time, ISO 8601, e.g. "2026-07-15T09:00:00-06:00".'),
|
|
28704
|
+
endDateTime: import_zod33.z.string().min(1).describe('End time, ISO 8601, e.g. "2026-07-15T10:00:00-06:00".'),
|
|
28705
|
+
timeZone: import_zod33.z.string().max(100).optional().describe('IANA timezone, e.g. "America/Denver". Applies to both start and end.')
|
|
28706
|
+
};
|
|
28707
|
+
GoogleCalendarCreateEventOutputSchema = {
|
|
28708
|
+
ok: import_zod33.z.boolean(),
|
|
28709
|
+
result: import_zod33.z.unknown().optional(),
|
|
28710
|
+
error: NullableString
|
|
28711
|
+
};
|
|
28712
|
+
ZoomCreateMeetingInputSchema = {
|
|
28713
|
+
connectionId: import_zod33.z.string().min(1).describe("A Zoom connectionId from list_service_connections, with actionsEnabled true."),
|
|
28714
|
+
topic: import_zod33.z.string().min(1).max(200).describe("Meeting topic/title."),
|
|
28715
|
+
startDateTime: import_zod33.z.string().min(1).describe('Start time, ISO 8601, e.g. "2026-07-15T09:00:00-06:00".'),
|
|
28716
|
+
durationMinutes: import_zod33.z.number().int().min(1).max(1440).default(30).describe("Meeting duration in minutes. Default 30."),
|
|
28717
|
+
timezone: import_zod33.z.string().max(100).optional().describe('IANA timezone, e.g. "America/Denver".'),
|
|
28718
|
+
agenda: import_zod33.z.string().max(2e3).optional().describe("Meeting description/agenda.")
|
|
28719
|
+
};
|
|
28720
|
+
ZoomCreateMeetingOutputSchema = {
|
|
28721
|
+
ok: import_zod33.z.boolean(),
|
|
28722
|
+
result: import_zod33.z.unknown().optional(),
|
|
28723
|
+
error: NullableString
|
|
28724
|
+
};
|
|
28653
28725
|
}
|
|
28654
28726
|
});
|
|
28655
28727
|
|
|
@@ -29329,6 +29401,48 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
29329
29401
|
openWorldHint: false
|
|
29330
29402
|
}
|
|
29331
29403
|
}, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
|
|
29404
|
+
server.registerTool("list_service_connections", {
|
|
29405
|
+
title: "List Connected Services",
|
|
29406
|
+
description: "List the third-party service connections (Slack, Gmail, Google Calendar, Zoom, Xero) the account has authorized, with each connection's id, whether it is enabled for live actions, its live readTools, and its digest vault/table (if the digest has run). Get a connectionId from here before calling read_service_connection, slack_send_message, gmail_send_message, google_calendar_create_event, or zoom_create_meeting. For already-digested history, search vaultName with memory-search or query tableName with table-query instead of a live read.",
|
|
29407
|
+
inputSchema: ListServiceConnectionsInputSchema,
|
|
29408
|
+
outputSchema: recordOutputSchema("list_service_connections", ListServiceConnectionsOutputSchema),
|
|
29409
|
+
annotations: { title: "List Connected Services", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
29410
|
+
}, async (input) => executor.listServiceConnections(input));
|
|
29411
|
+
server.registerTool("slack_send_message", {
|
|
29412
|
+
title: "Send Slack Message",
|
|
29413
|
+
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.",
|
|
29414
|
+
inputSchema: SlackSendMessageInputSchema,
|
|
29415
|
+
outputSchema: recordOutputSchema("slack_send_message", SlackSendMessageOutputSchema),
|
|
29416
|
+
annotations: { title: "Send Slack Message", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
29417
|
+
}, async (input) => executor.slackSendMessage(input));
|
|
29418
|
+
server.registerTool("gmail_send_message", {
|
|
29419
|
+
title: "Send Gmail Message",
|
|
29420
|
+
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.",
|
|
29421
|
+
inputSchema: GmailSendMessageInputSchema,
|
|
29422
|
+
outputSchema: recordOutputSchema("gmail_send_message", GmailSendMessageOutputSchema),
|
|
29423
|
+
annotations: { title: "Send Gmail Message", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
29424
|
+
}, async (input) => executor.gmailSendMessage(input));
|
|
29425
|
+
server.registerTool("google_calendar_create_event", {
|
|
29426
|
+
title: "Create Calendar Event",
|
|
29427
|
+
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.",
|
|
29428
|
+
inputSchema: GoogleCalendarCreateEventInputSchema,
|
|
29429
|
+
outputSchema: recordOutputSchema("google_calendar_create_event", GoogleCalendarCreateEventOutputSchema),
|
|
29430
|
+
annotations: { title: "Create Calendar Event", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
29431
|
+
}, async (input) => executor.googleCalendarCreateEvent(input));
|
|
29432
|
+
server.registerTool("zoom_create_meeting", {
|
|
29433
|
+
title: "Create Zoom Meeting",
|
|
29434
|
+
description: "Create a meeting on a connected, action-enabled Zoom connection. Requires a connectionId from list_service_connections with actionsEnabled true; the person must have explicitly turned actions on for that connection.",
|
|
29435
|
+
inputSchema: ZoomCreateMeetingInputSchema,
|
|
29436
|
+
outputSchema: recordOutputSchema("zoom_create_meeting", ZoomCreateMeetingOutputSchema),
|
|
29437
|
+
annotations: { title: "Create Zoom Meeting", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
29438
|
+
}, async (input) => executor.zoomCreateMeeting(input));
|
|
29439
|
+
server.registerTool("read_service_connection", {
|
|
29440
|
+
title: "Read Connected Service",
|
|
29441
|
+
description: "Call one live, read-only tool on a connected service (Slack/Gmail/Google Calendar/Zoom/Xero) to get current data \u2014 e.g. get-conversation-history, list-messages, list-invoices. Requires a connectionId and a tool name from that connection's readTools in list_service_connections; an unlisted tool name is rejected. For history that's already been digested, prefer memory-search over the connection's vaultName or table-query over its tableName \u2014 faster and free.",
|
|
29442
|
+
inputSchema: ReadServiceConnectionInputSchema,
|
|
29443
|
+
outputSchema: recordOutputSchema("read_service_connection", ReadServiceConnectionOutputSchema),
|
|
29444
|
+
annotations: { title: "Read Connected Service", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
29445
|
+
}, async (input) => executor.readServiceConnection(input));
|
|
29332
29446
|
}
|
|
29333
29447
|
var import_mcp, import_node_fs9, import_node_path12, import_node_crypto10;
|
|
29334
29448
|
var init_paa_mcp_server = __esm({
|
|
@@ -29637,6 +29751,24 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
29637
29751
|
creditsInfo(input) {
|
|
29638
29752
|
return this.call("/billing/credits", input);
|
|
29639
29753
|
}
|
|
29754
|
+
listServiceConnections(input) {
|
|
29755
|
+
return this.getJson("/schedule-connections");
|
|
29756
|
+
}
|
|
29757
|
+
slackSendMessage(input) {
|
|
29758
|
+
return this.call("/schedule-connections/actions/slack/send-message", input);
|
|
29759
|
+
}
|
|
29760
|
+
gmailSendMessage(input) {
|
|
29761
|
+
return this.call("/schedule-connections/actions/gmail/send-message", input);
|
|
29762
|
+
}
|
|
29763
|
+
googleCalendarCreateEvent(input) {
|
|
29764
|
+
return this.call("/schedule-connections/actions/google-calendar/create-event", input);
|
|
29765
|
+
}
|
|
29766
|
+
zoomCreateMeeting(input) {
|
|
29767
|
+
return this.call("/schedule-connections/actions/zoom/create-meeting", input);
|
|
29768
|
+
}
|
|
29769
|
+
readServiceConnection(input) {
|
|
29770
|
+
return this.call("/schedule-connections/actions/read", input);
|
|
29771
|
+
}
|
|
29640
29772
|
captureSerpSnapshot(input) {
|
|
29641
29773
|
return this.call("/serp-intelligence/capture", input, this.serpIntelligenceTimeoutMs);
|
|
29642
29774
|
}
|
|
@@ -38705,6 +38837,323 @@ var init_memory_db = __esm({
|
|
|
38705
38837
|
}
|
|
38706
38838
|
});
|
|
38707
38839
|
|
|
38840
|
+
// src/api/nango-control.ts
|
|
38841
|
+
function controlBaseUrl() {
|
|
38842
|
+
return (process.env.NANGO_CONTROL_URL?.trim() || DEFAULT_NANGO_CONTROL_URL).replace(/\/$/, "");
|
|
38843
|
+
}
|
|
38844
|
+
function controlSecret() {
|
|
38845
|
+
const secret2 = process.env.SCHEDULE_INTEGRATIONS_SECRET?.trim();
|
|
38846
|
+
if (!secret2) throw new NangoControlError("Scheduled service connections are not configured.", 503);
|
|
38847
|
+
return secret2;
|
|
38848
|
+
}
|
|
38849
|
+
function isRecord(value) {
|
|
38850
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
38851
|
+
}
|
|
38852
|
+
function unwrapData(value) {
|
|
38853
|
+
if (isRecord(value) && isRecord(value.data)) return value.data;
|
|
38854
|
+
return value;
|
|
38855
|
+
}
|
|
38856
|
+
function cleanString(value, max = 300) {
|
|
38857
|
+
if (typeof value !== "string") return null;
|
|
38858
|
+
const trimmed = value.trim();
|
|
38859
|
+
return trimmed ? trimmed.slice(0, max) : null;
|
|
38860
|
+
}
|
|
38861
|
+
function firstString(record, keys, max = 300) {
|
|
38862
|
+
for (const key of keys) {
|
|
38863
|
+
const value = cleanString(record[key], max);
|
|
38864
|
+
if (value) return value;
|
|
38865
|
+
}
|
|
38866
|
+
return null;
|
|
38867
|
+
}
|
|
38868
|
+
function cleanTools(value) {
|
|
38869
|
+
if (!Array.isArray(value)) return [];
|
|
38870
|
+
const tools = value.map((tool) => cleanString(tool, 200)).filter((tool) => !!tool);
|
|
38871
|
+
return [...new Set(tools)].slice(0, 200);
|
|
38872
|
+
}
|
|
38873
|
+
function cleanStringArray(value, maxItems = 20, maxLength = 100) {
|
|
38874
|
+
if (!Array.isArray(value)) return [];
|
|
38875
|
+
const items = value.map((item) => cleanString(item, maxLength)).filter((item) => !!item);
|
|
38876
|
+
return [...new Set(items)].slice(0, maxItems);
|
|
38877
|
+
}
|
|
38878
|
+
function cleanHttpsUrl(value) {
|
|
38879
|
+
const candidate = cleanString(value, 2e3);
|
|
38880
|
+
if (!candidate) return null;
|
|
38881
|
+
try {
|
|
38882
|
+
const url = new URL(candidate);
|
|
38883
|
+
return url.protocol === "https:" ? url.toString() : null;
|
|
38884
|
+
} catch {
|
|
38885
|
+
return null;
|
|
38886
|
+
}
|
|
38887
|
+
}
|
|
38888
|
+
function arrayFromPayload(value, keys) {
|
|
38889
|
+
const unwrapped = unwrapData(value);
|
|
38890
|
+
if (Array.isArray(unwrapped)) return unwrapped;
|
|
38891
|
+
if (!isRecord(unwrapped)) return [];
|
|
38892
|
+
for (const key of keys) {
|
|
38893
|
+
if (Array.isArray(unwrapped[key])) return unwrapped[key];
|
|
38894
|
+
}
|
|
38895
|
+
return [];
|
|
38896
|
+
}
|
|
38897
|
+
async function controlRequest(path6, init) {
|
|
38898
|
+
const response = await fetch(`${controlBaseUrl()}${path6}`, {
|
|
38899
|
+
...init,
|
|
38900
|
+
headers: {
|
|
38901
|
+
accept: "application/json",
|
|
38902
|
+
authorization: `Bearer ${controlSecret()}`,
|
|
38903
|
+
...init?.body ? { "content-type": "application/json" } : {},
|
|
38904
|
+
...init?.headers ?? {}
|
|
38905
|
+
},
|
|
38906
|
+
signal: AbortSignal.timeout(2e4)
|
|
38907
|
+
}).catch((err) => {
|
|
38908
|
+
throw new NangoControlError(`Scheduled service connection control is unavailable: ${err instanceof Error ? err.message : "network error"}`);
|
|
38909
|
+
});
|
|
38910
|
+
const text = await response.text();
|
|
38911
|
+
let body = {};
|
|
38912
|
+
try {
|
|
38913
|
+
body = text ? JSON.parse(text) : {};
|
|
38914
|
+
} catch {
|
|
38915
|
+
body = {};
|
|
38916
|
+
}
|
|
38917
|
+
if (!response.ok) {
|
|
38918
|
+
const upstream = isRecord(body) ? cleanString(body.error, 300) : null;
|
|
38919
|
+
throw new NangoControlError(upstream || `Scheduled service connection control failed (${response.status}).`);
|
|
38920
|
+
}
|
|
38921
|
+
return body;
|
|
38922
|
+
}
|
|
38923
|
+
function sanitizeScheduleConnectionSelections(value) {
|
|
38924
|
+
if (value == null) return [];
|
|
38925
|
+
if (!Array.isArray(value)) throw new ScheduleConnectionValidationError("connections must be an array.");
|
|
38926
|
+
if (value.length > 20) throw new ScheduleConnectionValidationError("A scheduled action can use at most 20 service connections.");
|
|
38927
|
+
const selections = [];
|
|
38928
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38929
|
+
for (const item of value) {
|
|
38930
|
+
if (!isRecord(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
|
|
38931
|
+
const connectionId = firstString(item, ["connectionId", "connection_id"]);
|
|
38932
|
+
const providerConfigKey = firstString(item, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id"]);
|
|
38933
|
+
const allowedTools = cleanTools(item.allowedTools ?? item.allowed_tools ?? item.allowedCapabilities ?? item.allowed_capabilities);
|
|
38934
|
+
if (!connectionId || !providerConfigKey) throw new ScheduleConnectionValidationError("Each service connection needs a connectionId and providerConfigKey.");
|
|
38935
|
+
if (allowedTools.length === 0) throw new ScheduleConnectionValidationError("Each selected service needs at least one allowed tool.");
|
|
38936
|
+
const key = `${providerConfigKey}:${connectionId}`;
|
|
38937
|
+
if (seen.has(key)) continue;
|
|
38938
|
+
seen.add(key);
|
|
38939
|
+
selections.push({ connectionId, providerConfigKey, allowedTools });
|
|
38940
|
+
}
|
|
38941
|
+
return selections;
|
|
38942
|
+
}
|
|
38943
|
+
async function getNangoCatalog() {
|
|
38944
|
+
const body = await controlRequest("/api/internal/nango/catalog");
|
|
38945
|
+
const rows = arrayFromPayload(body, ["providers", "catalog", "integrations", "services"]);
|
|
38946
|
+
const result = [];
|
|
38947
|
+
for (const row of rows) {
|
|
38948
|
+
if (!isRecord(row)) continue;
|
|
38949
|
+
const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "id"]);
|
|
38950
|
+
if (!providerConfigKey) continue;
|
|
38951
|
+
const provider = firstString(row, ["provider"]);
|
|
38952
|
+
const label = firstString(row, ["label", "displayName", "display_name", "name"]) || providerConfigKey;
|
|
38953
|
+
const description = firstString(row, ["description"], 500);
|
|
38954
|
+
const logoUrl = cleanHttpsUrl(row.logoUrl ?? row.logo_url ?? row.logo);
|
|
38955
|
+
const docsUrl = cleanHttpsUrl(row.docsUrl ?? row.docs_url ?? row.docs);
|
|
38956
|
+
const authMode = firstString(row, ["authMode", "auth_mode"], 100);
|
|
38957
|
+
const categories = cleanStringArray(row.categories);
|
|
38958
|
+
const disabledTools = DISABLED_NANGO_TOOLS[providerConfigKey];
|
|
38959
|
+
const safeDefaultAllowedTools = cleanTools(
|
|
38960
|
+
row.safeDefaultAllowedTools ?? row.safe_default_allowed_tools ?? row.defaultAllowedTools ?? row.default_allowed_tools ?? row.allowedTools ?? row.allowed_tools
|
|
38961
|
+
).filter((tool) => !disabledTools?.has(tool));
|
|
38962
|
+
result.push({
|
|
38963
|
+
providerConfigKey,
|
|
38964
|
+
provider,
|
|
38965
|
+
label,
|
|
38966
|
+
description,
|
|
38967
|
+
logoUrl,
|
|
38968
|
+
docsUrl,
|
|
38969
|
+
authMode,
|
|
38970
|
+
categories,
|
|
38971
|
+
safeDefaultAllowedTools
|
|
38972
|
+
});
|
|
38973
|
+
}
|
|
38974
|
+
return result;
|
|
38975
|
+
}
|
|
38976
|
+
async function getNangoConnections(identity) {
|
|
38977
|
+
const query = new URLSearchParams({ identity }).toString();
|
|
38978
|
+
const body = await controlRequest(`/api/internal/nango/connections?${query}`);
|
|
38979
|
+
const rows = arrayFromPayload(body, ["connections"]);
|
|
38980
|
+
const result = [];
|
|
38981
|
+
for (const row of rows) {
|
|
38982
|
+
if (!isRecord(row)) continue;
|
|
38983
|
+
const connectionId = firstString(row, ["connectionId", "connection_id", "id"]);
|
|
38984
|
+
const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
38985
|
+
if (!connectionId || !providerConfigKey) continue;
|
|
38986
|
+
const provider = firstString(row, ["provider"]);
|
|
38987
|
+
const label = firstString(row, ["label", "accountLabel", "account_label", "displayName", "display_name", "endUserEmail", "end_user_email"]) || providerConfigKey;
|
|
38988
|
+
const rawStatus = firstString(row, ["status"], 64) || "connected";
|
|
38989
|
+
const reconnectRequired = row.reconnectRequired === true || row.reconnect_required === true || rawStatus === "needs_reauth" || rawStatus === "reauth_required" || rawStatus === "invalid" || rawStatus === "error" || rawStatus === "revoked" || rawStatus === "disabled";
|
|
38990
|
+
result.push({
|
|
38991
|
+
connectionId,
|
|
38992
|
+
providerConfigKey,
|
|
38993
|
+
provider,
|
|
38994
|
+
label,
|
|
38995
|
+
status: reconnectRequired ? "needs_reauth" : "connected",
|
|
38996
|
+
reconnectRequired,
|
|
38997
|
+
actionsEnabled: row.actionsEnabled === true || row.actions_enabled === true,
|
|
38998
|
+
readTools: cleanTools(row.readTools ?? row.read_tools),
|
|
38999
|
+
vaultName: firstString(row, ["vaultName", "vault_name"], 100),
|
|
39000
|
+
tableName: firstString(row, ["tableName", "table_name"], 100),
|
|
39001
|
+
createdAt: firstString(row, ["createdAt", "created_at"], 100),
|
|
39002
|
+
updatedAt: firstString(row, ["updatedAt", "updated_at"], 100)
|
|
39003
|
+
});
|
|
39004
|
+
}
|
|
39005
|
+
return result;
|
|
39006
|
+
}
|
|
39007
|
+
function sanitizeConnectSession(body) {
|
|
39008
|
+
const data = unwrapData(body);
|
|
39009
|
+
if (!isRecord(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
|
|
39010
|
+
const connectLink = firstString(data, ["connectLink", "connect_link"], 2e3);
|
|
39011
|
+
if (!connectLink) throw new NangoControlError("The connection service did not return a connect link.");
|
|
39012
|
+
return {
|
|
39013
|
+
connectLink,
|
|
39014
|
+
sessionToken: firstString(data, ["sessionToken", "session_token", "token"], 2e3),
|
|
39015
|
+
expiresAt: firstString(data, ["expiresAt", "expires_at"], 100)
|
|
39016
|
+
};
|
|
39017
|
+
}
|
|
39018
|
+
async function createNangoConnectSession(identity, providerConfigKey) {
|
|
39019
|
+
const body = await controlRequest("/api/internal/nango/connect-session", {
|
|
39020
|
+
method: "POST",
|
|
39021
|
+
body: JSON.stringify({ identity, email: identity, provider: providerConfigKey })
|
|
39022
|
+
});
|
|
39023
|
+
return sanitizeConnectSession(body);
|
|
39024
|
+
}
|
|
39025
|
+
async function createNangoReconnectSession(identity, connectionId) {
|
|
39026
|
+
const body = await controlRequest("/api/internal/nango/reconnect-session", {
|
|
39027
|
+
method: "POST",
|
|
39028
|
+
body: JSON.stringify({ identity, connectionId, email: identity })
|
|
39029
|
+
});
|
|
39030
|
+
return sanitizeConnectSession(body);
|
|
39031
|
+
}
|
|
39032
|
+
function sanitizeBindingRows(body, defaultScheduleActionId) {
|
|
39033
|
+
const data = unwrapData(body);
|
|
39034
|
+
const flattened = [];
|
|
39035
|
+
if (isRecord(data) && isRecord(data.bindings) && !Array.isArray(data.bindings)) {
|
|
39036
|
+
for (const [scheduleActionId, value] of Object.entries(data.bindings)) {
|
|
39037
|
+
if (Array.isArray(value)) value.forEach((row) => flattened.push({ row, scheduleActionId }));
|
|
39038
|
+
}
|
|
39039
|
+
} else {
|
|
39040
|
+
const rows = arrayFromPayload(data, ["bindings", "connections"]);
|
|
39041
|
+
for (const row of rows) {
|
|
39042
|
+
if (isRecord(row) && Array.isArray(row.connections)) {
|
|
39043
|
+
const scheduleActionId = firstString(row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || defaultScheduleActionId;
|
|
39044
|
+
row.connections.forEach((connection) => flattened.push({ row: connection, scheduleActionId }));
|
|
39045
|
+
} else {
|
|
39046
|
+
flattened.push({ row, scheduleActionId: defaultScheduleActionId });
|
|
39047
|
+
}
|
|
39048
|
+
}
|
|
39049
|
+
}
|
|
39050
|
+
const result = [];
|
|
39051
|
+
for (const item of flattened) {
|
|
39052
|
+
if (!isRecord(item.row)) continue;
|
|
39053
|
+
const connectionId = firstString(item.row, ["connectionId", "connection_id"]);
|
|
39054
|
+
const providerConfigKey = firstString(item.row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
39055
|
+
if (!connectionId || !providerConfigKey) continue;
|
|
39056
|
+
result.push({
|
|
39057
|
+
scheduleActionId: firstString(item.row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || item.scheduleActionId,
|
|
39058
|
+
connectionId,
|
|
39059
|
+
providerConfigKey,
|
|
39060
|
+
allowedTools: cleanTools(item.row.allowedTools ?? item.row.allowed_tools ?? item.row.allowedCapabilities ?? item.row.allowed_capabilities),
|
|
39061
|
+
label: firstString(item.row, ["label", "accountLabel", "account_label", "displayName", "display_name"])
|
|
39062
|
+
});
|
|
39063
|
+
}
|
|
39064
|
+
return result;
|
|
39065
|
+
}
|
|
39066
|
+
async function getScheduleConnectionBindings(identity, scheduleActionId) {
|
|
39067
|
+
const query = new URLSearchParams({ identity, scheduleId: scheduleActionId });
|
|
39068
|
+
const body = await controlRequest(`/api/internal/nango/bindings?${query.toString()}`);
|
|
39069
|
+
return sanitizeBindingRows(body, scheduleActionId);
|
|
39070
|
+
}
|
|
39071
|
+
async function putScheduleConnectionBindings(identity, scheduleActionId, connections) {
|
|
39072
|
+
const body = await controlRequest("/api/internal/nango/bindings", {
|
|
39073
|
+
method: "PUT",
|
|
39074
|
+
body: JSON.stringify({
|
|
39075
|
+
identity,
|
|
39076
|
+
scheduleId: scheduleActionId,
|
|
39077
|
+
connections: connections.map((connection) => ({
|
|
39078
|
+
connectionId: connection.connectionId,
|
|
39079
|
+
allowedTools: connection.allowedTools
|
|
39080
|
+
}))
|
|
39081
|
+
})
|
|
39082
|
+
});
|
|
39083
|
+
const sanitized = sanitizeBindingRows(body, scheduleActionId);
|
|
39084
|
+
const expectedByConnectionId = new Map(connections.map((connection) => [
|
|
39085
|
+
connection.connectionId,
|
|
39086
|
+
{
|
|
39087
|
+
providerConfigKey: connection.providerConfigKey,
|
|
39088
|
+
allowedTools: [...connection.allowedTools].sort()
|
|
39089
|
+
}
|
|
39090
|
+
]));
|
|
39091
|
+
const responseMatchesRequest = sanitized.length === connections.length && sanitized.every((binding) => {
|
|
39092
|
+
const expected = expectedByConnectionId.get(binding.connectionId);
|
|
39093
|
+
return !!expected && binding.scheduleActionId === scheduleActionId && binding.providerConfigKey === expected.providerConfigKey && [...binding.allowedTools].sort().join("\0") === expected.allowedTools.join("\0");
|
|
39094
|
+
});
|
|
39095
|
+
if (!responseMatchesRequest) {
|
|
39096
|
+
throw new NangoControlError("Scheduled service connection control returned an invalid binding response.");
|
|
39097
|
+
}
|
|
39098
|
+
return sanitized;
|
|
39099
|
+
}
|
|
39100
|
+
async function deleteScheduleConnectionBindings(identity, scheduleActionId) {
|
|
39101
|
+
await controlRequest("/api/internal/nango/bindings", {
|
|
39102
|
+
method: "DELETE",
|
|
39103
|
+
body: JSON.stringify({ identity, scheduleId: scheduleActionId })
|
|
39104
|
+
});
|
|
39105
|
+
}
|
|
39106
|
+
async function setScheduleConnectionActionsEnabled(identity, connectionId, enabled) {
|
|
39107
|
+
const body = await controlRequest("/api/internal/nango/connections/actions/enable", {
|
|
39108
|
+
method: "POST",
|
|
39109
|
+
body: JSON.stringify({ identity, connectionId, enabled })
|
|
39110
|
+
});
|
|
39111
|
+
const data = unwrapData(body);
|
|
39112
|
+
if (!isRecord(data) || !isRecord(data.connection)) return enabled;
|
|
39113
|
+
return data.connection.actionsEnabled === true;
|
|
39114
|
+
}
|
|
39115
|
+
async function callScheduleConnectionAction(identity, connectionId, input) {
|
|
39116
|
+
const body = await controlRequest("/api/internal/nango/connections/actions/call", {
|
|
39117
|
+
method: "POST",
|
|
39118
|
+
body: JSON.stringify({ identity, connectionId, input })
|
|
39119
|
+
});
|
|
39120
|
+
const data = unwrapData(body);
|
|
39121
|
+
return isRecord(data) ? data.result ?? data : data;
|
|
39122
|
+
}
|
|
39123
|
+
async function callScheduleConnectionRead(identity, connectionId, tool, args) {
|
|
39124
|
+
const body = await controlRequest("/api/internal/nango/connections/actions/read", {
|
|
39125
|
+
method: "POST",
|
|
39126
|
+
body: JSON.stringify({ identity, connectionId, tool, args: args ?? {} })
|
|
39127
|
+
});
|
|
39128
|
+
const data = unwrapData(body);
|
|
39129
|
+
return isRecord(data) ? data.result ?? data : data;
|
|
39130
|
+
}
|
|
39131
|
+
var DEFAULT_NANGO_CONTROL_URL, DISABLED_NANGO_TOOLS, NangoControlError, ScheduleConnectionValidationError;
|
|
39132
|
+
var init_nango_control = __esm({
|
|
39133
|
+
"src/api/nango-control.ts"() {
|
|
39134
|
+
"use strict";
|
|
39135
|
+
DEFAULT_NANGO_CONTROL_URL = "https://mcp-scraper-scheduler.vercel.app";
|
|
39136
|
+
DISABLED_NANGO_TOOLS = {
|
|
39137
|
+
"google-mail": /* @__PURE__ */ new Set(["list-filters"]),
|
|
39138
|
+
slack: /* @__PURE__ */ new Set(["search-files", "search-messages"])
|
|
39139
|
+
};
|
|
39140
|
+
NangoControlError = class extends Error {
|
|
39141
|
+
status;
|
|
39142
|
+
constructor(message, status = 502) {
|
|
39143
|
+
super(message);
|
|
39144
|
+
this.name = "NangoControlError";
|
|
39145
|
+
this.status = status;
|
|
39146
|
+
}
|
|
39147
|
+
};
|
|
39148
|
+
ScheduleConnectionValidationError = class extends Error {
|
|
39149
|
+
constructor(message) {
|
|
39150
|
+
super(message);
|
|
39151
|
+
this.name = "ScheduleConnectionValidationError";
|
|
39152
|
+
}
|
|
39153
|
+
};
|
|
39154
|
+
}
|
|
39155
|
+
});
|
|
39156
|
+
|
|
38708
39157
|
// src/api/webhook.ts
|
|
38709
39158
|
async function deliverWebhook(url, payload, retries = 3) {
|
|
38710
39159
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
@@ -38930,6 +39379,20 @@ function maybeProvisionInBackground(user, vaultCount) {
|
|
|
38930
39379
|
}
|
|
38931
39380
|
})();
|
|
38932
39381
|
}
|
|
39382
|
+
function scheduleConnectionError(c, err, fallback) {
|
|
39383
|
+
if (err instanceof ScheduleConnectionValidationError) return c.json({ ok: false, error: err.message }, 400);
|
|
39384
|
+
if (err instanceof NangoControlError) return c.json({ ok: false, error: err.message }, err.status);
|
|
39385
|
+
console.error("[schedule-connections]", err instanceof Error ? err.message : String(err));
|
|
39386
|
+
return c.json({ ok: false, error: fallback }, 502);
|
|
39387
|
+
}
|
|
39388
|
+
function providerConfigKeyFrom(value) {
|
|
39389
|
+
if (typeof value !== "string") return null;
|
|
39390
|
+
const key = value.trim();
|
|
39391
|
+
return key && key.length <= 200 ? key : null;
|
|
39392
|
+
}
|
|
39393
|
+
function bindingsForSchedule(bindings, scheduleActionId) {
|
|
39394
|
+
return bindings.filter((binding) => binding.scheduleActionId === scheduleActionId);
|
|
39395
|
+
}
|
|
38933
39396
|
function combineAbortSignals(signals) {
|
|
38934
39397
|
const controller = new AbortController();
|
|
38935
39398
|
const abortFrom = (signal) => {
|
|
@@ -39037,6 +39500,7 @@ var init_server = __esm({
|
|
|
39037
39500
|
init_session();
|
|
39038
39501
|
init_memory();
|
|
39039
39502
|
init_memory_db();
|
|
39503
|
+
init_nango_control();
|
|
39040
39504
|
init_concurrency_gates();
|
|
39041
39505
|
secureCookies2 = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
|
|
39042
39506
|
isProduction2 = secureCookies2;
|
|
@@ -39569,6 +40033,139 @@ var init_server = __esm({
|
|
|
39569
40033
|
return c.json({ error: message }, 500);
|
|
39570
40034
|
}
|
|
39571
40035
|
});
|
|
40036
|
+
app.get("/schedule-connection-catalog", auth2, async (c) => {
|
|
40037
|
+
try {
|
|
40038
|
+
return c.json({ ok: true, catalog: await getNangoCatalog() });
|
|
40039
|
+
} catch (err) {
|
|
40040
|
+
return scheduleConnectionError(c, err, "Unable to load the service connection catalog.");
|
|
40041
|
+
}
|
|
40042
|
+
});
|
|
40043
|
+
app.get("/schedule-connections", auth2, async (c) => {
|
|
40044
|
+
try {
|
|
40045
|
+
const user = c.get("user");
|
|
40046
|
+
return c.json({ ok: true, connections: await getNangoConnections(user.email) });
|
|
40047
|
+
} catch (err) {
|
|
40048
|
+
return scheduleConnectionError(c, err, "Unable to load service connections.");
|
|
40049
|
+
}
|
|
40050
|
+
});
|
|
40051
|
+
app.post("/schedule-connections/session", auth2, async (c) => {
|
|
40052
|
+
const user = c.get("user");
|
|
40053
|
+
const body = await c.req.json().catch(() => ({}));
|
|
40054
|
+
const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
|
|
40055
|
+
if (!providerConfigKey) return c.json({ ok: false, error: "providerConfigKey is required." }, 400);
|
|
40056
|
+
try {
|
|
40057
|
+
const session = await createNangoConnectSession(user.email, providerConfigKey);
|
|
40058
|
+
return c.json({ ok: true, ...session });
|
|
40059
|
+
} catch (err) {
|
|
40060
|
+
return scheduleConnectionError(c, err, "Unable to start the service connection flow.");
|
|
40061
|
+
}
|
|
40062
|
+
});
|
|
40063
|
+
app.post("/schedule-connections/:id/reconnect-session", auth2, async (c) => {
|
|
40064
|
+
const user = c.get("user");
|
|
40065
|
+
try {
|
|
40066
|
+
const session = await createNangoReconnectSession(user.email, c.req.param("id"));
|
|
40067
|
+
return c.json({ ok: true, ...session });
|
|
40068
|
+
} catch (err) {
|
|
40069
|
+
return scheduleConnectionError(c, err, "Unable to start the service reconnection flow.");
|
|
40070
|
+
}
|
|
40071
|
+
});
|
|
40072
|
+
app.post("/schedule-connections/:id/enable-actions", auth2, async (c) => {
|
|
40073
|
+
const user = c.get("user");
|
|
40074
|
+
const body = await c.req.json().catch(() => ({}));
|
|
40075
|
+
if (typeof body.enabled !== "boolean") return c.json({ ok: false, error: "enabled must be a boolean." }, 400);
|
|
40076
|
+
try {
|
|
40077
|
+
const actionsEnabled = await setScheduleConnectionActionsEnabled(user.email, c.req.param("id"), body.enabled);
|
|
40078
|
+
return c.json({ ok: true, actionsEnabled });
|
|
40079
|
+
} catch (err) {
|
|
40080
|
+
return scheduleConnectionError(c, err, "Unable to update this connection's action setting.");
|
|
40081
|
+
}
|
|
40082
|
+
});
|
|
40083
|
+
app.post("/schedule-connections/actions/slack/send-message", auth2, async (c) => {
|
|
40084
|
+
const user = c.get("user");
|
|
40085
|
+
const body = await c.req.json().catch(() => ({}));
|
|
40086
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
40087
|
+
if (!connectionId || typeof body.channel !== "string" || typeof body.text !== "string") {
|
|
40088
|
+
return c.json({ ok: false, error: "connectionId, channel, and text are required." }, 400);
|
|
40089
|
+
}
|
|
40090
|
+
try {
|
|
40091
|
+
const result = await callScheduleConnectionAction(user.email, connectionId, { channel: body.channel, text: body.text });
|
|
40092
|
+
return c.json({ ok: true, result });
|
|
40093
|
+
} catch (err) {
|
|
40094
|
+
return scheduleConnectionError(c, err, "Unable to send the Slack message.");
|
|
40095
|
+
}
|
|
40096
|
+
});
|
|
40097
|
+
app.post("/schedule-connections/actions/gmail/send-message", auth2, async (c) => {
|
|
40098
|
+
const user = c.get("user");
|
|
40099
|
+
const body = await c.req.json().catch(() => ({}));
|
|
40100
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
40101
|
+
if (!connectionId || typeof body.to !== "string" || typeof body.subject !== "string" || typeof body.body !== "string") {
|
|
40102
|
+
return c.json({ ok: false, error: "connectionId, to, subject, and body are required." }, 400);
|
|
40103
|
+
}
|
|
40104
|
+
try {
|
|
40105
|
+
const result = await callScheduleConnectionAction(user.email, connectionId, { to: body.to, subject: body.subject, body: body.body });
|
|
40106
|
+
return c.json({ ok: true, result });
|
|
40107
|
+
} catch (err) {
|
|
40108
|
+
return scheduleConnectionError(c, err, "Unable to send the email.");
|
|
40109
|
+
}
|
|
40110
|
+
});
|
|
40111
|
+
app.post("/schedule-connections/actions/google-calendar/create-event", auth2, async (c) => {
|
|
40112
|
+
const user = c.get("user");
|
|
40113
|
+
const body = await c.req.json().catch(() => ({}));
|
|
40114
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
40115
|
+
if (!connectionId || typeof body.summary !== "string" || typeof body.startDateTime !== "string" || typeof body.endDateTime !== "string") {
|
|
40116
|
+
return c.json({ ok: false, error: "connectionId, summary, startDateTime, and endDateTime are required." }, 400);
|
|
40117
|
+
}
|
|
40118
|
+
const timeZone = typeof body.timeZone === "string" ? body.timeZone : void 0;
|
|
40119
|
+
try {
|
|
40120
|
+
const result = await callScheduleConnectionAction(user.email, connectionId, {
|
|
40121
|
+
calendarId: typeof body.calendarId === "string" ? body.calendarId : "primary",
|
|
40122
|
+
summary: body.summary,
|
|
40123
|
+
description: typeof body.description === "string" ? body.description : void 0,
|
|
40124
|
+
location: typeof body.location === "string" ? body.location : void 0,
|
|
40125
|
+
start: { dateTime: body.startDateTime, timeZone },
|
|
40126
|
+
end: { dateTime: body.endDateTime, timeZone }
|
|
40127
|
+
});
|
|
40128
|
+
return c.json({ ok: true, result });
|
|
40129
|
+
} catch (err) {
|
|
40130
|
+
return scheduleConnectionError(c, err, "Unable to create the calendar event.");
|
|
40131
|
+
}
|
|
40132
|
+
});
|
|
40133
|
+
app.post("/schedule-connections/actions/zoom/create-meeting", auth2, async (c) => {
|
|
40134
|
+
const user = c.get("user");
|
|
40135
|
+
const body = await c.req.json().catch(() => ({}));
|
|
40136
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
40137
|
+
if (!connectionId || typeof body.topic !== "string" || typeof body.startDateTime !== "string") {
|
|
40138
|
+
return c.json({ ok: false, error: "connectionId, topic, and startDateTime are required." }, 400);
|
|
40139
|
+
}
|
|
40140
|
+
try {
|
|
40141
|
+
const result = await callScheduleConnectionAction(user.email, connectionId, {
|
|
40142
|
+
topic: body.topic,
|
|
40143
|
+
startDateTime: body.startDateTime,
|
|
40144
|
+
durationMinutes: typeof body.durationMinutes === "number" ? body.durationMinutes : 30,
|
|
40145
|
+
timezone: typeof body.timezone === "string" ? body.timezone : void 0,
|
|
40146
|
+
agenda: typeof body.agenda === "string" ? body.agenda : void 0
|
|
40147
|
+
});
|
|
40148
|
+
return c.json({ ok: true, result });
|
|
40149
|
+
} catch (err) {
|
|
40150
|
+
return scheduleConnectionError(c, err, "Unable to create the Zoom meeting.");
|
|
40151
|
+
}
|
|
40152
|
+
});
|
|
40153
|
+
app.post("/schedule-connections/actions/read", auth2, async (c) => {
|
|
40154
|
+
const user = c.get("user");
|
|
40155
|
+
const body = await c.req.json().catch(() => ({}));
|
|
40156
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
40157
|
+
const tool = providerConfigKeyFrom(body.tool);
|
|
40158
|
+
if (!connectionId || !tool) {
|
|
40159
|
+
return c.json({ ok: false, error: "connectionId and tool are required." }, 400);
|
|
40160
|
+
}
|
|
40161
|
+
const args = body.args && typeof body.args === "object" && !Array.isArray(body.args) ? body.args : {};
|
|
40162
|
+
try {
|
|
40163
|
+
const result = await callScheduleConnectionRead(user.email, connectionId, tool, args);
|
|
40164
|
+
return c.json({ ok: true, result });
|
|
40165
|
+
} catch (err) {
|
|
40166
|
+
return scheduleConnectionError(c, err, "Unable to read from this connection.");
|
|
40167
|
+
}
|
|
40168
|
+
});
|
|
39572
40169
|
app.post("/schedule-link", auth2, async (c) => {
|
|
39573
40170
|
try {
|
|
39574
40171
|
const user = c.get("user");
|
|
@@ -39587,15 +40184,58 @@ var init_server = __esm({
|
|
|
39587
40184
|
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
39588
40185
|
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
39589
40186
|
const r = await memoryCall("listScheduledActionsTool", {}, key);
|
|
39590
|
-
return c.json(r);
|
|
40187
|
+
if (!r.ok || !Array.isArray(r.actions)) return c.json(r);
|
|
40188
|
+
try {
|
|
40189
|
+
const actions = await Promise.all(r.actions.map(async (action) => {
|
|
40190
|
+
const id = typeof action.id === "string" ? action.id : "";
|
|
40191
|
+
if (!id) return { ...action, connections: [] };
|
|
40192
|
+
const bindings = await getScheduleConnectionBindings(user.email, id);
|
|
40193
|
+
return { ...action, connections: bindingsForSchedule(bindings, id) };
|
|
40194
|
+
}));
|
|
40195
|
+
return c.json({
|
|
40196
|
+
...r,
|
|
40197
|
+
actions
|
|
40198
|
+
});
|
|
40199
|
+
} catch (err) {
|
|
40200
|
+
console.warn("[schedule-actions] connection bindings unavailable:", err instanceof Error ? err.message : String(err));
|
|
40201
|
+
return c.json({
|
|
40202
|
+
...r,
|
|
40203
|
+
actions: r.actions.map((action) => ({ ...action, connections: [] })),
|
|
40204
|
+
connectionBindingsUnavailable: true
|
|
40205
|
+
});
|
|
40206
|
+
}
|
|
39591
40207
|
});
|
|
39592
40208
|
app.post("/schedule-actions", auth2, async (c) => {
|
|
39593
40209
|
const user = c.get("user");
|
|
39594
40210
|
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
39595
40211
|
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
39596
40212
|
const body = await c.req.json().catch(() => ({}));
|
|
39597
|
-
const
|
|
39598
|
-
|
|
40213
|
+
const hasConnections = Object.prototype.hasOwnProperty.call(body, "connections");
|
|
40214
|
+
let connections;
|
|
40215
|
+
try {
|
|
40216
|
+
connections = sanitizeScheduleConnectionSelections(body.connections);
|
|
40217
|
+
} catch (err) {
|
|
40218
|
+
return scheduleConnectionError(c, err, "Invalid service connections.");
|
|
40219
|
+
}
|
|
40220
|
+
const actionBody = { ...body };
|
|
40221
|
+
delete actionBody.connections;
|
|
40222
|
+
const r = await memoryCall("createScheduledActionTool", actionBody, key);
|
|
40223
|
+
if (!r.ok || !hasConnections) return c.json(r);
|
|
40224
|
+
if (connections.length === 0) return c.json({ ...r, connections: [] });
|
|
40225
|
+
if (!r.id) {
|
|
40226
|
+
console.error("[schedule-actions] create returned no id while service connections were requested");
|
|
40227
|
+
return c.json({ ok: false, error: "The scheduled action was created without a usable id; service connections were not attached." }, 502);
|
|
40228
|
+
}
|
|
40229
|
+
try {
|
|
40230
|
+
const bound = await putScheduleConnectionBindings(user.email, r.id, connections);
|
|
40231
|
+
return c.json({ ...r, connections: bound });
|
|
40232
|
+
} catch (err) {
|
|
40233
|
+
const rollback = await memoryCall("deleteScheduledActionTool", { id: r.id }, key).catch(() => ({ ok: false }));
|
|
40234
|
+
if (!rollback.ok) console.error("[schedule-actions] failed to compensate action after connection binding failure", r.id);
|
|
40235
|
+
const response = scheduleConnectionError(c, err, "The scheduled action was not created because its service connections could not be attached.");
|
|
40236
|
+
response.headers.set("x-schedule-create-rolled-back", rollback.ok ? "true" : "false");
|
|
40237
|
+
return response;
|
|
40238
|
+
}
|
|
39599
40239
|
});
|
|
39600
40240
|
app.post("/schedule-actions/propose", auth2, async (c) => {
|
|
39601
40241
|
const user = c.get("user");
|
|
@@ -39605,6 +40245,33 @@ var init_server = __esm({
|
|
|
39605
40245
|
const r = await memoryCall("proposeScheduledActionTool", body, key);
|
|
39606
40246
|
return c.json(r);
|
|
39607
40247
|
});
|
|
40248
|
+
app.get("/schedule-actions/:id/connections", auth2, async (c) => {
|
|
40249
|
+
try {
|
|
40250
|
+
const user = c.get("user");
|
|
40251
|
+
const scheduleActionId = c.req.param("id");
|
|
40252
|
+
const bindings = await getScheduleConnectionBindings(user.email, scheduleActionId);
|
|
40253
|
+
return c.json({ ok: true, connections: bindingsForSchedule(bindings, scheduleActionId) });
|
|
40254
|
+
} catch (err) {
|
|
40255
|
+
return scheduleConnectionError(c, err, "Unable to load scheduled action service connections.");
|
|
40256
|
+
}
|
|
40257
|
+
});
|
|
40258
|
+
app.put("/schedule-actions/:id/connections", auth2, async (c) => {
|
|
40259
|
+
const user = c.get("user");
|
|
40260
|
+
const body = await c.req.json().catch(() => ({}));
|
|
40261
|
+
let connections;
|
|
40262
|
+
try {
|
|
40263
|
+
connections = sanitizeScheduleConnectionSelections(body.connections);
|
|
40264
|
+
} catch (err) {
|
|
40265
|
+
return scheduleConnectionError(c, err, "Invalid service connections.");
|
|
40266
|
+
}
|
|
40267
|
+
try {
|
|
40268
|
+
const scheduleActionId = c.req.param("id");
|
|
40269
|
+
const bindings = await putScheduleConnectionBindings(user.email, scheduleActionId, connections);
|
|
40270
|
+
return c.json({ ok: true, connections: bindings });
|
|
40271
|
+
} catch (err) {
|
|
40272
|
+
return scheduleConnectionError(c, err, "Unable to update scheduled action service connections.");
|
|
40273
|
+
}
|
|
40274
|
+
});
|
|
39608
40275
|
app.post("/schedule-actions/:id/pause", auth2, async (c) => {
|
|
39609
40276
|
const user = c.get("user");
|
|
39610
40277
|
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
@@ -39623,8 +40290,16 @@ var init_server = __esm({
|
|
|
39623
40290
|
const user = c.get("user");
|
|
39624
40291
|
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
39625
40292
|
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
39626
|
-
const
|
|
39627
|
-
|
|
40293
|
+
const scheduleActionId = c.req.param("id");
|
|
40294
|
+
const r = await memoryCall("deleteScheduledActionTool", { id: scheduleActionId }, key);
|
|
40295
|
+
if (!r.ok) return c.json(r);
|
|
40296
|
+
try {
|
|
40297
|
+
await deleteScheduleConnectionBindings(user.email, scheduleActionId);
|
|
40298
|
+
return c.json(r);
|
|
40299
|
+
} catch (err) {
|
|
40300
|
+
console.error("[schedule-actions] action deleted but connection binding cleanup failed", scheduleActionId, err instanceof Error ? err.message : String(err));
|
|
40301
|
+
return c.json({ ...r, connectionBindingCleanupPending: true });
|
|
40302
|
+
}
|
|
39628
40303
|
});
|
|
39629
40304
|
app.post("/schedule-link/revoke", auth2, async (c) => {
|
|
39630
40305
|
try {
|