@jskit-ai/connectors-catalog 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/docs/ai-upstream-notices.md +51 -0
- package/docs/ai.md +191 -0
- package/docs/airtable.md +130 -0
- package/docs/algolia.md +190 -0
- package/docs/amazon-redshift.md +229 -0
- package/docs/amplitude.md +210 -0
- package/docs/apify.md +162 -0
- package/docs/apollo-io.md +200 -0
- package/docs/asana.md +126 -0
- package/docs/ashby.md +188 -0
- package/docs/atlassian.md +224 -0
- package/docs/attention.md +162 -0
- package/docs/aws-athena.md +150 -0
- package/docs/aws-credentials.md +75 -0
- package/docs/aws-s3.md +193 -0
- package/docs/bigquery.md +146 -0
- package/docs/brevo.md +122 -0
- package/docs/calendly.md +158 -0
- package/docs/canva.md +191 -0
- package/docs/chargebee.md +192 -0
- package/docs/clay.md +194 -0
- package/docs/clickhouse.md +242 -0
- package/docs/confidence-exp.md +74 -0
- package/docs/confidence-flags.md +75 -0
- package/docs/confidence-oauth.md +137 -0
- package/docs/contentful.md +123 -0
- package/docs/databricks.md +180 -0
- package/docs/dbt-semantic-layer.md +175 -0
- package/docs/elevenlabs.md +102 -0
- package/docs/figma.md +132 -0
- package/docs/firebase-cloud-messaging.md +262 -0
- package/docs/firecrawl.md +137 -0
- package/docs/fireflies.md +95 -0
- package/docs/fireworks-ai.md +53 -0
- package/docs/gatewayapi.md +113 -0
- package/docs/gemini-enterprise.md +224 -0
- package/docs/github-api.md +133 -0
- package/docs/gitlab-api.md +138 -0
- package/docs/gmail.md +133 -0
- package/docs/gong.md +205 -0
- package/docs/google-ads.md +156 -0
- package/docs/google-analytics.md +202 -0
- package/docs/google-docs.md +105 -0
- package/docs/google-drive.md +108 -0
- package/docs/google-maps-platform.md +208 -0
- package/docs/google-oauth.md +123 -0
- package/docs/google-search-console.md +124 -0
- package/docs/google-sheets.md +94 -0
- package/docs/google-slides.md +95 -0
- package/docs/granola.md +178 -0
- package/docs/hex.md +161 -0
- package/docs/heygen.md +124 -0
- package/docs/hubspot.md +142 -0
- package/docs/incident-io.md +118 -0
- package/docs/inngest.md +178 -0
- package/docs/klipy.md +149 -0
- package/docs/lexware.md +144 -0
- package/docs/lightspeed.md +193 -0
- package/docs/linear.md +137 -0
- package/docs/linkedin.md +196 -0
- package/docs/logo-dev.md +152 -0
- package/docs/mailgun.md +153 -0
- package/docs/mapbox.md +174 -0
- package/docs/microsoft-excel.md +97 -0
- package/docs/microsoft-fabric.md +249 -0
- package/docs/microsoft-oauth.md +143 -0
- package/docs/microsoft-onedrive.md +76 -0
- package/docs/microsoft-onenote.md +75 -0
- package/docs/microsoft-outlook.md +90 -0
- package/docs/microsoft-powerpoint.md +119 -0
- package/docs/microsoft-sharepoint.md +75 -0
- package/docs/microsoft-teams.md +72 -0
- package/docs/microsoft-word.md +119 -0
- package/docs/miro.md +117 -0
- package/docs/n8n.md +169 -0
- package/docs/notion.md +137 -0
- package/docs/oura.md +161 -0
- package/docs/paddle.md +168 -0
- package/docs/perplexity.md +85 -0
- package/docs/pipedrive.md +120 -0
- package/docs/polar.md +63 -0
- package/docs/posthog.md +227 -0
- package/docs/prestashop.md +151 -0
- package/docs/replicate.md +60 -0
- package/docs/resend.md +153 -0
- package/docs/salesforce.md +234 -0
- package/docs/sanity.md +122 -0
- package/docs/semrush.md +276 -0
- package/docs/sentry.md +112 -0
- package/docs/sevdesk.md +107 -0
- package/docs/shopify.md +206 -0
- package/docs/slack.md +257 -0
- package/docs/snowflake.md +271 -0
- package/docs/storyblok.md +130 -0
- package/docs/stripe.md +77 -0
- package/docs/supabase.md +51 -0
- package/docs/tally.md +105 -0
- package/docs/telegram.md +165 -0
- package/docs/tiktok.md +201 -0
- package/docs/twilio.md +284 -0
- package/docs/twitch.md +428 -0
- package/docs/wave.md +440 -0
- package/docs/wix.md +1249 -0
- package/docs/wiz.md +177 -0
- package/docs/woocommerce.md +400 -0
- package/docs/wordpress-com.md +358 -0
- package/docs/wordpress-self-hosted.md +252 -0
- package/docs/workday.md +292 -0
- package/docs/x-twitter.md +188 -0
- package/docs/xero.md +429 -0
- package/docs/zoho-books.md +214 -0
- package/docs/zoho-crm.md +192 -0
- package/package.json +153 -0
- package/patterns/ai-connections/PATTERN.md +84 -0
- package/patterns/ai-connections/example/ai-model.js +11 -0
- package/patterns/ai-connections/example/perplexity-answer.js +29 -0
- package/patterns/api-key-connection/PATTERN.md +368 -0
- package/patterns/api-key-connection/example/connections.js +18 -0
- package/patterns/api-key-connection/example/integrations.json +19 -0
- package/patterns/assistant-mcp/PATTERN.md +110 -0
- package/patterns/assistant-mcp/example/integrations.json +13 -0
- package/patterns/assistant-mcp-oauth/PATTERN.md +235 -0
- package/patterns/assistant-mcp-oauth/example/integrations.json +102 -0
- package/patterns/aws-storage-queries/PATTERN.md +169 -0
- package/patterns/aws-storage-queries/example/formats/data-formats.js +35 -0
- package/patterns/aws-storage-queries/example/formats/package-lock.json +49 -0
- package/patterns/aws-storage-queries/example/formats/package.json +12 -0
- package/patterns/aws-storage-queries/example/formats/verify-formats.mjs +37 -0
- package/patterns/aws-storage-queries/example/integrations.json +38 -0
- package/patterns/aws-storage-queries/example/s3-transfer.js +34 -0
- package/patterns/event-delivery/PATTERN.md +151 -0
- package/patterns/event-delivery/example/integrations.json +17 -0
- package/patterns/firebase-messaging/PATTERN.md +156 -0
- package/patterns/firebase-messaging/example/server/notifications.js +23 -0
- package/patterns/google-ads-search/PATTERN.md +92 -0
- package/patterns/google-ads-search/example/ads-setup.js +25 -0
- package/patterns/oauth-connection/PATTERN.md +724 -0
- package/patterns/oauth-connection/example/integrations.json +20 -0
- package/patterns/paddle-catalogue/PATTERN.md +78 -0
- package/patterns/paddle-catalogue/example/create-products.js +38 -0
- package/patterns/public-image/PATTERN.md +84 -0
- package/patterns/public-image/example/integrations.json +13 -0
- package/patterns/public-image/example/logo-url.js +16 -0
- package/patterns/redshift-queries/PATTERN.md +150 -0
- package/patterns/redshift-queries/example/integrations.json +33 -0
- package/patterns/source-scanning/PATTERN.md +83 -0
- package/patterns/source-scanning/example/source-scanner.js +13 -0
- package/src/client/firebase-cloud-messaging.js +10 -0
- package/src/client/google-analytics.js +72 -0
- package/src/client/google-maps-platform.js +39 -0
- package/src/client/logo-dev.js +36 -0
- package/src/server/ai.js +46 -0
- package/src/server/airtable.js +67 -0
- package/src/server/algolia.js +63 -0
- package/src/server/amazon-redshift.js +96 -0
- package/src/server/amplitude.js +31 -0
- package/src/server/apify.js +68 -0
- package/src/server/apollo-io.js +85 -0
- package/src/server/asana.js +64 -0
- package/src/server/ashby.js +61 -0
- package/src/server/atlassian.js +32 -0
- package/src/server/attention.js +93 -0
- package/src/server/aws-athena.js +82 -0
- package/src/server/aws-s3.js +49 -0
- package/src/server/awsTransport.js +75 -0
- package/src/server/bigquery.js +97 -0
- package/src/server/brevo.js +97 -0
- package/src/server/calendly.js +77 -0
- package/src/server/canva.js +13 -0
- package/src/server/chargebee.js +88 -0
- package/src/server/clay.js +78 -0
- package/src/server/clickhouse.js +66 -0
- package/src/server/confidence.js +29 -0
- package/src/server/contentful.js +40 -0
- package/src/server/databricks.js +55 -0
- package/src/server/dbt-semantic-layer.js +95 -0
- package/src/server/elevenlabs.js +81 -0
- package/src/server/figma.js +24 -0
- package/src/server/firebase-cloud-messaging.js +111 -0
- package/src/server/firecrawl.js +70 -0
- package/src/server/fireflies.js +31 -0
- package/src/server/fireworks-ai.js +16 -0
- package/src/server/gatewayapi.js +48 -0
- package/src/server/gemini-enterprise.js +47 -0
- package/src/server/github-api.js +90 -0
- package/src/server/gitlab-api.js +67 -0
- package/src/server/gmail.js +60 -0
- package/src/server/gong.js +52 -0
- package/src/server/google-ads-search.js +134 -0
- package/src/server/google-ads.js +80 -0
- package/src/server/google-docs.js +36 -0
- package/src/server/google-drive.js +71 -0
- package/src/server/google-maps-platform.js +90 -0
- package/src/server/google-search-console.js +31 -0
- package/src/server/google-sheets.js +60 -0
- package/src/server/google-slides.js +36 -0
- package/src/server/google.js +61 -0
- package/src/server/granola.js +115 -0
- package/src/server/graphqlOperation.js +15 -0
- package/src/server/hex.js +29 -0
- package/src/server/heygen.js +54 -0
- package/src/server/hubspot.js +85 -0
- package/src/server/incident-io.js +68 -0
- package/src/server/inngest.js +86 -0
- package/src/server/jsonOperation.js +34 -0
- package/src/server/klipy.js +34 -0
- package/src/server/lexware.js +59 -0
- package/src/server/lightspeed.js +82 -0
- package/src/server/linear.js +52 -0
- package/src/server/linkedin.js +74 -0
- package/src/server/mailgun.js +76 -0
- package/src/server/mapbox.js +46 -0
- package/src/server/mcpRegistration.js +41 -0
- package/src/server/mcpTransport.js +60 -0
- package/src/server/microsoft-excel.js +67 -0
- package/src/server/microsoft-fabric.js +81 -0
- package/src/server/microsoft-onedrive.js +58 -0
- package/src/server/microsoft-onenote.js +52 -0
- package/src/server/microsoft-outlook.js +58 -0
- package/src/server/microsoft-powerpoint.js +5 -0
- package/src/server/microsoft-sharepoint.js +46 -0
- package/src/server/microsoft-teams.js +29 -0
- package/src/server/microsoft-word.js +5 -0
- package/src/server/microsoft.js +88 -0
- package/src/server/miro.js +29 -0
- package/src/server/n8n.js +78 -0
- package/src/server/notion.js +76 -0
- package/src/server/oura.js +48 -0
- package/src/server/paddle.js +83 -0
- package/src/server/perplexity.js +15 -0
- package/src/server/pipedrive.js +95 -0
- package/src/server/polar.js +20 -0
- package/src/server/posthog.js +44 -0
- package/src/server/prestashop.js +42 -0
- package/src/server/replicate.js +42 -0
- package/src/server/resend.js +93 -0
- package/src/server/salesforce.js +84 -0
- package/src/server/sanity.js +31 -0
- package/src/server/semrush.js +359 -0
- package/src/server/sentry.js +30 -0
- package/src/server/sevdesk.js +20 -0
- package/src/server/shopify.js +99 -0
- package/src/server/slack.js +143 -0
- package/src/server/snowflake.js +139 -0
- package/src/server/storyblok.js +49 -0
- package/src/server/stripe.js +13 -0
- package/src/server/supabase.js +13 -0
- package/src/server/tally.js +78 -0
- package/src/server/telegram.js +55 -0
- package/src/server/tiktok.js +86 -0
- package/src/server/twilio.js +90 -0
- package/src/server/twitch.js +409 -0
- package/src/server/wave.js +520 -0
- package/src/server/wix.js +1048 -0
- package/src/server/wiz.js +117 -0
- package/src/server/woocommerce.js +412 -0
- package/src/server/wordpress-com.js +236 -0
- package/src/server/wordpress-self-hosted.js +134 -0
- package/src/server/workday.js +136 -0
- package/src/server/x-twitter.js +110 -0
- package/src/server/xero.js +397 -0
- package/src/server/zoho-books.js +90 -0
- package/src/server/zoho-crm.js +89 -0
- package/src/server/zohoOAuth.js +21 -0
- package/src/shared/ai-models.json +8049 -0
- package/src/shared/ai.js +63 -0
- package/src/shared/algolia.js +29 -0
- package/src/shared/amazon-redshift.js +51 -0
- package/src/shared/amplitude.js +28 -0
- package/src/shared/atlassian.js +43 -0
- package/src/shared/aws.js +72 -0
- package/src/shared/canva.js +66 -0
- package/src/shared/chargebee.js +26 -0
- package/src/shared/clickhouse.js +38 -0
- package/src/shared/confidence.js +28 -0
- package/src/shared/databricks.js +33 -0
- package/src/shared/dbt-semantic-layer.js +32 -0
- package/src/shared/definitions.js +140 -0
- package/src/shared/figma.js +21 -0
- package/src/shared/firebase-cloud-messaging.js +45 -0
- package/src/shared/gemini-enterprise.js +33 -0
- package/src/shared/gong.js +33 -0
- package/src/shared/google-ads-search.js +31 -0
- package/src/shared/google-ads.js +28 -0
- package/src/shared/google-maps-platform.js +28 -0
- package/src/shared/google.js +133 -0
- package/src/shared/granola.js +25 -0
- package/src/shared/hex.js +28 -0
- package/src/shared/inngest.js +35 -0
- package/src/shared/lightspeed.js +75 -0
- package/src/shared/linkedin.js +27 -0
- package/src/shared/logo-dev.js +21 -0
- package/src/shared/mapbox.js +38 -0
- package/src/shared/mcp.js +92 -0
- package/src/shared/microsoft-fabric.js +47 -0
- package/src/shared/microsoft.js +231 -0
- package/src/shared/miro.js +27 -0
- package/src/shared/oura.js +23 -0
- package/src/shared/posthog.js +33 -0
- package/src/shared/prestashop.js +23 -0
- package/src/shared/salesforce.js +36 -0
- package/src/shared/semrush.js +24 -0
- package/src/shared/shopify.js +51 -0
- package/src/shared/siteUrl.js +12 -0
- package/src/shared/slack.js +88 -0
- package/src/shared/snowflake.js +55 -0
- package/src/shared/tiktok.js +22 -0
- package/src/shared/tokens.js +747 -0
- package/src/shared/twilio.js +41 -0
- package/src/shared/twitch.js +49 -0
- package/src/shared/wave.js +52 -0
- package/src/shared/wix.js +40 -0
- package/src/shared/wiz.js +41 -0
- package/src/shared/wordpress-com.js +30 -0
- package/src/shared/wordpress.js +69 -0
- package/src/shared/workday.js +50 -0
- package/src/shared/x-twitter.js +19 -0
- package/src/shared/xero.js +46 -0
- package/src/shared/zoho-books.js +39 -0
- package/src/shared/zoho-crm.js +43 -0
- package/src/shared/zoho-regions.js +12 -0
- package/test/ai.test.js +136 -0
- package/test/airtable.test.js +125 -0
- package/test/algolia.test.js +207 -0
- package/test/amazon-redshift.test.js +272 -0
- package/test/amplitude.test.js +239 -0
- package/test/apify.test.js +93 -0
- package/test/apollo-io.test.js +125 -0
- package/test/asana.test.js +99 -0
- package/test/ashby.test.js +97 -0
- package/test/atlassian.test.js +249 -0
- package/test/attention.test.js +79 -0
- package/test/aws.test.js +405 -0
- package/test/brevo.test.js +71 -0
- package/test/calendly.test.js +136 -0
- package/test/canva.test.js +258 -0
- package/test/chargebee.test.js +195 -0
- package/test/clickhouse.test.js +278 -0
- package/test/confidence.test.js +205 -0
- package/test/databricks.test.js +253 -0
- package/test/dbt-semantic-layer.test.js +221 -0
- package/test/designMcp.test.js +336 -0
- package/test/elevenlabs.test.js +52 -0
- package/test/firebase-cloud-messaging.test.js +242 -0
- package/test/firecrawl.test.js +66 -0
- package/test/gemini-enterprise.test.js +218 -0
- package/test/github-api.test.js +167 -0
- package/test/gitlab-api.test.js +160 -0
- package/test/gong.test.js +192 -0
- package/test/google-ads.test.js +350 -0
- package/test/google-maps-platform.test.js +191 -0
- package/test/google.test.js +710 -0
- package/test/granola.test.js +240 -0
- package/test/hex.test.js +253 -0
- package/test/heygen.test.js +121 -0
- package/test/hubspot.test.js +103 -0
- package/test/inngest.test.js +219 -0
- package/test/lightspeed.test.js +250 -0
- package/test/linear.test.js +146 -0
- package/test/linkedin.test.js +243 -0
- package/test/logo-dev.test.js +79 -0
- package/test/mailgun.test.js +107 -0
- package/test/mapbox.test.js +181 -0
- package/test/mcp.test.js +227 -0
- package/test/microsoft-fabric.test.js +258 -0
- package/test/microsoft.test.js +554 -0
- package/test/n8nOAuth.test.js +188 -0
- package/test/notionOAuth.test.js +183 -0
- package/test/oura.test.js +211 -0
- package/test/paddleCatalogue.test.js +51 -0
- package/test/paddlePermissions.test.js +101 -0
- package/test/perplexityComposition.test.js +39 -0
- package/test/pipedriveOAuth.test.js +140 -0
- package/test/posthog.test.js +200 -0
- package/test/prestashop.test.js +207 -0
- package/test/resend.test.js +163 -0
- package/test/salesforce.test.js +276 -0
- package/test/semrush.test.js +479 -0
- package/test/sentry.test.js +35 -0
- package/test/shopify.test.js +247 -0
- package/test/slack.test.js +320 -0
- package/test/snowflake.test.js +391 -0
- package/test/tally.test.js +75 -0
- package/test/telegram.test.js +58 -0
- package/test/tiktok.test.js +243 -0
- package/test/tokens.test.js +1079 -0
- package/test/twilio.test.js +221 -0
- package/test/twitch.test.js +573 -0
- package/test/wave.test.js +678 -0
- package/test/wix.test.js +1099 -0
- package/test/wiz.test.js +185 -0
- package/test/wordpress-com.test.js +469 -0
- package/test/wordpress.test.js +660 -0
- package/test/workday.test.js +314 -0
- package/test/x-twitter.test.js +256 -0
- package/test/xero.test.js +700 -0
- package/test/zoho-books.test.js +253 -0
- package/test/zoho-crm.test.js +223 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { microsoftExcelDefinition } from "../shared/microsoft.js";
|
|
4
|
+
import { graphRead, microsoftProvider, driveItemId, pageSize } from "./microsoft.js";
|
|
5
|
+
|
|
6
|
+
const sessionId = { type: "string", minLength: 1, maxLength: 1024,
|
|
7
|
+
validator: (value) => /^[^\r\n]+$/u.test(value) || "Use the returned workbook session ID." };
|
|
8
|
+
const rangeFields = {
|
|
9
|
+
itemId: driveItemId,
|
|
10
|
+
worksheetId: { type: "string", required: true, minLength: 1, maxLength: 256,
|
|
11
|
+
validator: (value) => /^[^/\\?#'\r\n]+$/u.test(value) || "Use a worksheet ID or name." },
|
|
12
|
+
address: { type: "string", required: true, maxLength: 30,
|
|
13
|
+
validator: (value) => /^[A-Z]{1,3}[1-9][0-9]{0,6}(?::[A-Z]{1,3}[1-9][0-9]{0,6})?$/u.test(value) || "Enter a bounded A1 range, such as A1:B2." },
|
|
14
|
+
sessionId
|
|
15
|
+
};
|
|
16
|
+
const rangeResult = (value) => typeof value?.address === "string" && Array.isArray(value.values);
|
|
17
|
+
function rangeOperation(write) {
|
|
18
|
+
const schema = createSchema({ ...rangeFields, ...(write ? {
|
|
19
|
+
values: { type: "array" }, formulas: { type: "array" }
|
|
20
|
+
} : {}) });
|
|
21
|
+
return { scopes: ["Files.ReadWrite"], validateResult: rangeResult, request(input) {
|
|
22
|
+
const params = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
23
|
+
const cells = params.address.split(":").map((cell) => {
|
|
24
|
+
const [, letters, row] = /^([A-Z]+)([0-9]+)$/u.exec(cell);
|
|
25
|
+
return { column: [...letters].reduce((n, c) => n * 26 + c.charCodeAt(0) - 64, 0), row: Number(row) };
|
|
26
|
+
});
|
|
27
|
+
const first = cells[0], last = cells[1] || first;
|
|
28
|
+
const rows = last.row - first.row + 1, columns = last.column - first.column + 1;
|
|
29
|
+
const bounded = rows > 0 && columns > 0 && rows * columns <= 10000 && last.row <= 1048576 && last.column <= 16384;
|
|
30
|
+
const matrix = (value) => Array.isArray(value) && value.length === rows && value.every((row) =>
|
|
31
|
+
Array.isArray(row) && row.length === columns && row.every((cell) => cell === null ||
|
|
32
|
+
typeof cell === "string" || typeof cell === "boolean" || (typeof cell === "number" && Number.isFinite(cell))));
|
|
33
|
+
const validation = createSchema({ valid: { type: "boolean", required: true,
|
|
34
|
+
validator: (value) => value || "Use at most 10,000 valid cells and exactly matching values or formulas, not both." } });
|
|
35
|
+
validateSchemaPayload({ schema: validation, mode: "replace" }, { valid: bounded && (!write ||
|
|
36
|
+
((params.values !== undefined) !== (params.formulas !== undefined)) && matrix(params.values ?? params.formulas)) }, { statusCode: 422 });
|
|
37
|
+
return { method: write ? "PATCH" : "GET",
|
|
38
|
+
url: `https://graph.microsoft.com/v1.0/me/drive/items/${encodeURIComponent(params.itemId)}/workbook/worksheets/${encodeURIComponent(params.worksheetId)}/range(address='${params.address}')`,
|
|
39
|
+
headers: params.sessionId ? { "workbook-session-id": params.sessionId } : {},
|
|
40
|
+
...(write ? { body: params.values ? { values: params.values } : { formulas: params.formulas } } : {}) };
|
|
41
|
+
} };
|
|
42
|
+
}
|
|
43
|
+
const createSessionSchema = createSchema({ itemId: driveItemId, persistChanges: { type: "boolean", required: true } });
|
|
44
|
+
const closeSessionSchema = createSchema({ itemId: driveItemId, sessionId: { ...sessionId, required: true } });
|
|
45
|
+
|
|
46
|
+
const microsoftExcelProvider = microsoftProvider(microsoftExcelDefinition, "common", "items.list", {
|
|
47
|
+
"ranges.get": rangeOperation(false),
|
|
48
|
+
"ranges.update": rangeOperation(true),
|
|
49
|
+
"sessions.create": { scopes: ["Files.ReadWrite"],
|
|
50
|
+
validateResult: (value) => typeof value?.id === "string" && value.id.length > 0 && typeof value.persistChanges === "boolean",
|
|
51
|
+
request(input) {
|
|
52
|
+
const { itemId, persistChanges } = validateSchemaPayload({ schema: createSessionSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
53
|
+
return { method: "POST", url: `https://graph.microsoft.com/v1.0/me/drive/items/${encodeURIComponent(itemId)}/workbook/createSession`, body: { persistChanges } };
|
|
54
|
+
} },
|
|
55
|
+
"sessions.close": { scopes: ["Files.ReadWrite"], request(input) {
|
|
56
|
+
const { itemId, sessionId } = validateSchemaPayload({ schema: closeSessionSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
57
|
+
return { method: "POST", url: `https://graph.microsoft.com/v1.0/me/drive/items/${encodeURIComponent(itemId)}/workbook/closeSession`, headers: { "workbook-session-id": sessionId } };
|
|
58
|
+
} },
|
|
59
|
+
"items.list": graphRead("Files.Read", { pageSize, folderId: { ...driveItemId, required: false } }, ({ pageSize, folderId }) => ({
|
|
60
|
+
pathname: folderId ? `/me/drive/items/${encodeURIComponent(folderId)}/children` : "/me/drive/root/children",
|
|
61
|
+
query: { $top: pageSize, $select: "id,name,file,folder,webUrl" }
|
|
62
|
+
})),
|
|
63
|
+
"worksheets.list": graphRead("Files.ReadWrite", { itemId: driveItemId }, ({ itemId }) => ({
|
|
64
|
+
pathname: `/me/drive/items/${encodeURIComponent(itemId)}/workbook/worksheets`
|
|
65
|
+
}))
|
|
66
|
+
});
|
|
67
|
+
export { microsoftExcelProvider };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { microsoftFabricDefinition, delegatedScope, serviceScope } from "../shared/microsoft-fabric.js";
|
|
4
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
5
|
+
|
|
6
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
|
+
const name = (value) => typeof value === "string" && /^[_A-Za-z][_0-9A-Za-z]*$/u.test(value);
|
|
8
|
+
const validResult = (value) => object(value?.data) &&
|
|
9
|
+
(value.errors === undefined || (Array.isArray(value.errors) && value.errors.length === 0));
|
|
10
|
+
const endpoint = (settings) => settings.graphqlEndpoint;
|
|
11
|
+
const permissions = [delegatedScope, serviceScope];
|
|
12
|
+
|
|
13
|
+
function fixedQuery(query, validateData) {
|
|
14
|
+
const operation = jsonOperation(endpoint, {}, (value) => validResult(value) && validateData(value.data), "POST");
|
|
15
|
+
return { ...operation, scopes: permissions, request(input, settings) {
|
|
16
|
+
return { ...operation.request(input, settings), body: { query } };
|
|
17
|
+
} };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const microsoftFabricProvider = Object.freeze({
|
|
21
|
+
...microsoftFabricDefinition,
|
|
22
|
+
oauth: ({ tenantId }) => ({
|
|
23
|
+
issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`,
|
|
24
|
+
authorization_endpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
|
|
25
|
+
token_endpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`
|
|
26
|
+
}),
|
|
27
|
+
authorizationParameters: { response_mode: "query" },
|
|
28
|
+
apiOrigins: ["https://api.fabric.microsoft.com"], checkOperation: "connection.check",
|
|
29
|
+
async normalizeTokenResponse(response) {
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
let error;
|
|
32
|
+
try { error = (await response.clone().json())?.error; } catch { /* OAuth handles the response */ }
|
|
33
|
+
if (["interaction_required", "login_required", "consent_required"].includes(error)) {
|
|
34
|
+
throw new ConnectorError("connector_reconnect_required", "Microsoft requires a new sign-in or consent.", { statusCode: 401 });
|
|
35
|
+
}
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
let value;
|
|
39
|
+
try { value = await response.clone().json(); } catch { /* rejected below */ }
|
|
40
|
+
if (!Number.isSafeInteger(value?.expires_in) || value.expires_in <= 30 ||
|
|
41
|
+
(value.scope !== undefined && (typeof value.scope !== "string" || !/^[^\s]+(?: [^\s]+)*$/u.test(value.scope)))) {
|
|
42
|
+
throw new ConnectorError("connector_response_invalid", "Microsoft returned an invalid token grant.", { statusCode: 502 });
|
|
43
|
+
}
|
|
44
|
+
// Entra may return the permission name without the resource URI requested in consent.
|
|
45
|
+
if (value.scope?.split(" ").includes("GraphQLApi.Execute.All")) {
|
|
46
|
+
value.scope = value.scope.split(" ").map((scope) => scope === "GraphQLApi.Execute.All" ? delegatedScope : scope).join(" ");
|
|
47
|
+
return Response.json(value, { status: response.status });
|
|
48
|
+
}
|
|
49
|
+
return response;
|
|
50
|
+
},
|
|
51
|
+
operations: {
|
|
52
|
+
"connection.check": fixedQuery("query ConnectorFabricCheck { __typename }", (data) => name(data.__typename)),
|
|
53
|
+
"schema.types": fixedQuery("query ConnectorFabricTypes { __schema { types { name kind } queryType { name } mutationType { name } } }",
|
|
54
|
+
(data) => object(data.__schema) && Array.isArray(data.__schema.types) && data.__schema.types.every((type) => name(type?.name) &&
|
|
55
|
+
["SCALAR", "OBJECT", "INTERFACE", "UNION", "ENUM", "INPUT_OBJECT", "LIST", "NON_NULL"].includes(type.kind)) &&
|
|
56
|
+
name(data.__schema.queryType?.name) && (data.__schema.mutationType === null || name(data.__schema.mutationType?.name))),
|
|
57
|
+
"graphql.execute": {
|
|
58
|
+
...jsonOperation(endpoint, {
|
|
59
|
+
query: { type: "string", required: true, minLength: 1, maxLength: 65_536, validator: (value) => value.trim().length > 0 || "Provide a GraphQL document." },
|
|
60
|
+
variables: { type: "object", additionalProperties: true, validator(value) {
|
|
61
|
+
try {
|
|
62
|
+
const encoded = JSON.stringify(value);
|
|
63
|
+
if (encoded.length <= 65_536 && isDeepStrictEqual(value, JSON.parse(encoded))) return true;
|
|
64
|
+
} catch { /* invalid JSON values */ }
|
|
65
|
+
return "Use JSON variables within 65536 characters.";
|
|
66
|
+
} },
|
|
67
|
+
operationName: { type: "string", maxLength: 256, validator: (value) => name(value) || "Use a GraphQL operation name." }
|
|
68
|
+
}, validResult, "POST"), scopes: permissions
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
async exchange(address, options, { request }) {
|
|
72
|
+
const result = await request(address, options);
|
|
73
|
+
if (Array.isArray(result?.errors) && result.errors.length > 0) {
|
|
74
|
+
// A mutation can have taken effect before partial errors arrive. Do not replay or expose provider text.
|
|
75
|
+
throw new ConnectorError("connector_provider_failed", "Fabric could not complete the GraphQL document. Check its schema and permissions before retrying.", { statusCode: 502 });
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export { microsoftFabricProvider };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
2
|
+
import { createSchema } from "json-rest-schema";
|
|
3
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
4
|
+
import { microsoftOneDriveDefinition } from "../shared/microsoft.js";
|
|
5
|
+
import { graphRead, microsoftProvider, pageSize, driveItemId } from "./microsoft.js";
|
|
6
|
+
|
|
7
|
+
const fields = "id,name,size,webUrl,file,folder,parentReference";
|
|
8
|
+
const item = (value) => {
|
|
9
|
+
if (typeof value?.id !== "string" || typeof value.name !== "string") return false;
|
|
10
|
+
if (value["@microsoft.graph.downloadUrl"] === undefined) return true;
|
|
11
|
+
try {
|
|
12
|
+
const url = new URL(value["@microsoft.graph.downloadUrl"]);
|
|
13
|
+
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
14
|
+
} catch { return false; }
|
|
15
|
+
};
|
|
16
|
+
const getSchema = createSchema({ itemId: driveItemId });
|
|
17
|
+
const uploadSchema = createSchema({
|
|
18
|
+
parentId: driveItemId,
|
|
19
|
+
name: { type: "string", required: true, minLength: 1, maxLength: 255,
|
|
20
|
+
// eslint-disable-next-line no-control-regex -- Reject literal control characters in provider input.
|
|
21
|
+
validator: (value) => !/[\\/\x00-\x1f"*:<>?|]/u.test(value) && ![".", ".."].includes(value) || "Use a simple file name without reserved characters." },
|
|
22
|
+
bodyBase64: { type: "string", required: true, noTrim: true, maxLength: 6666668 },
|
|
23
|
+
conflictBehavior: { type: "string", enum: ["fail", "replace", "rename"], defaultTo: "fail" }
|
|
24
|
+
});
|
|
25
|
+
const base = microsoftProvider(microsoftOneDriveDefinition, "common", "items.list", {
|
|
26
|
+
"items.list": graphRead("Files.Read", { pageSize, folderId: { ...driveItemId, required: false } }, ({ pageSize, folderId }) => ({
|
|
27
|
+
pathname: folderId ? `/me/drive/items/${encodeURIComponent(folderId)}/children` : "/me/drive/root/children",
|
|
28
|
+
query: { $top: pageSize, $select: fields }
|
|
29
|
+
})),
|
|
30
|
+
"items.get": { scopes: ["Files.Read"], validateResult: item, request(input) {
|
|
31
|
+
const { itemId } = validateSchemaPayload({ schema: getSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
32
|
+
const url = new URL(`https://graph.microsoft.com/v1.0/me/drive/items/${encodeURIComponent(itemId)}`);
|
|
33
|
+
url.searchParams.set("$select", `${fields},@microsoft.graph.downloadUrl`);
|
|
34
|
+
return { method: "GET", url: url.href };
|
|
35
|
+
} },
|
|
36
|
+
"files.upload": { scopes: ["Files.ReadWrite"], validateResult: item, request(input) {
|
|
37
|
+
const { parentId, name, bodyBase64, conflictBehavior } = validateSchemaPayload({ schema: uploadSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
38
|
+
const body = Buffer.from(bodyBase64, "base64");
|
|
39
|
+
if (body.toString("base64") !== bodyBase64 || body.length > 5_000_000) {
|
|
40
|
+
throw new ConnectorError("connector_input_invalid", "Use standard padded base64 up to 5 MB; larger files need a native upload session.", { statusCode: 422 });
|
|
41
|
+
}
|
|
42
|
+
const url = new URL(`https://graph.microsoft.com/v1.0/me/drive/items/${encodeURIComponent(parentId)}:/${encodeURIComponent(name)}:/content`);
|
|
43
|
+
url.searchParams.set("@microsoft.graph.conflictBehavior", conflictBehavior);
|
|
44
|
+
return { method: "PUT", url: url.href, headers: { "Content-Type": "application/octet-stream" }, body };
|
|
45
|
+
} }
|
|
46
|
+
});
|
|
47
|
+
const microsoftOneDriveProvider = Object.freeze({ ...base,
|
|
48
|
+
async exchange(address, options, { request, fetchImpl }) {
|
|
49
|
+
if (options.method !== "PUT") return request(address, options);
|
|
50
|
+
const response = await fetchImpl(address, { ...options, credentials: "omit", redirect: "error" });
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
await response.body?.cancel();
|
|
53
|
+
throw Object.assign(new Error("OneDrive upload failed; inspect the folder before retrying."), { status: response.status });
|
|
54
|
+
}
|
|
55
|
+
return response.json();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
export { microsoftOneDriveProvider };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
|
+
import { microsoftOneNoteDefinition } from "../shared/microsoft.js";
|
|
5
|
+
import { graphRead, microsoftProvider, pageSize } from "./microsoft.js";
|
|
6
|
+
|
|
7
|
+
const id = { type: "string", required: true, minLength: 1, maxLength: 1024,
|
|
8
|
+
validator: value => /^[A-Za-z0-9!_{}:.-]+$/u.test(value) && value !== "." && value !== ".." || "Use the returned OneNote resource ID." };
|
|
9
|
+
const text = { type: "string", required: true, minLength: 1, maxLength: 100000, noTrim: true };
|
|
10
|
+
const escape = value => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11
|
+
const createSchemaPage = createSchema({ sectionId: id, title: { ...text, maxLength: 255 }, text });
|
|
12
|
+
const appendSchema = createSchema({ pageId: id, text });
|
|
13
|
+
const contentSchema = createSchema({ pageId: id });
|
|
14
|
+
const origin = "https://graph.microsoft.com/v1.0/me/onenote";
|
|
15
|
+
const base = microsoftProvider(microsoftOneNoteDefinition, "common", "notebooks.list", {
|
|
16
|
+
"notebooks.list": graphRead("Notes.Read", {}, () => ({ pathname: "/me/onenote/notebooks" })),
|
|
17
|
+
"sections.list": graphRead("Notes.Read", { notebookId: id }, ({ notebookId }) => ({ pathname: `/me/onenote/notebooks/${encodeURIComponent(notebookId)}/sections` })),
|
|
18
|
+
"pages.list": graphRead("Notes.Read", { sectionId: id, pageSize }, ({ sectionId, pageSize }) => ({ pathname: `/me/onenote/sections/${encodeURIComponent(sectionId)}/pages`, query: { $top: pageSize } })),
|
|
19
|
+
"pages.content": { scopes: ["Notes.Read"], validateResult: value => typeof value?.html === "string", request(input) {
|
|
20
|
+
const { pageId } = validateSchemaPayload({ schema: contentSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
21
|
+
return { method: "GET", url: `${origin}/pages/${encodeURIComponent(pageId)}/content?includeIDs=true` };
|
|
22
|
+
} },
|
|
23
|
+
"pages.create": { scopes: ["Notes.Create", "Notes.ReadWrite", "Notes.ReadWrite.All"], validateResult: value => typeof value?.id === "string", request(input) {
|
|
24
|
+
const { sectionId, title, text } = validateSchemaPayload({ schema: createSchemaPage, mode: "replace" }, input, { statusCode: 422 });
|
|
25
|
+
return { method: "POST", url: `${origin}/sections/${encodeURIComponent(sectionId)}/pages`, headers: { "Content-Type": "text/html" },
|
|
26
|
+
body: `<!DOCTYPE html><html><head><title>${escape(title)}</title></head><body><p>${escape(text).replaceAll("\n", "<br />")}</p></body></html>` };
|
|
27
|
+
} },
|
|
28
|
+
"pages.append": { scopes: ["Notes.ReadWrite", "Notes.ReadWrite.All"], request(input) {
|
|
29
|
+
const { pageId, text } = validateSchemaPayload({ schema: appendSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
30
|
+
return { method: "PATCH", url: `${origin}/pages/${encodeURIComponent(pageId)}/content`,
|
|
31
|
+
body: [{ target: "body", action: "append", content: `<p>${escape(text).replaceAll("\n", "<br />")}</p>` }] };
|
|
32
|
+
} }
|
|
33
|
+
});
|
|
34
|
+
const microsoftOneNoteProvider = Object.freeze({ ...base,
|
|
35
|
+
async exchange(address, options, { request, fetchImpl }) {
|
|
36
|
+
const content = options.method === "GET" && new URL(address).pathname.endsWith("/content");
|
|
37
|
+
const create = new Headers(options.headers).get("content-type") === "text/html";
|
|
38
|
+
if (!content && !create) return request(address, options);
|
|
39
|
+
const response = await fetchImpl(address, { ...options, credentials: "omit", redirect: "error" });
|
|
40
|
+
if (!response.ok) { await response.body?.cancel(); throw Object.assign(new Error("OneNote request failed."), { status: response.status }); }
|
|
41
|
+
if (create) return response.json();
|
|
42
|
+
const reader = response.body?.getReader(); const chunks = []; let size = 0;
|
|
43
|
+
if (reader) try { while (true) {
|
|
44
|
+
const { done, value } = await reader.read(); if (done) break;
|
|
45
|
+
size += value.byteLength;
|
|
46
|
+
if (size > 1048576) { await reader.cancel(); throw new ConnectorError("connector_response_too_large", "Use the native OneNote client for pages above 1 MiB.", { statusCode: 413 }); }
|
|
47
|
+
chunks.push(value);
|
|
48
|
+
} } finally { reader.releaseLock(); }
|
|
49
|
+
return { html: Buffer.concat(chunks).toString("utf8") };
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
export { microsoftOneNoteProvider };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
|
+
import { microsoftOutlookDefinition } from "../shared/microsoft.js";
|
|
5
|
+
import { graphRead, microsoftProvider, pageSize } from "./microsoft.js";
|
|
6
|
+
|
|
7
|
+
const id = { type: "string", required: true, minLength: 1, maxLength: 2048,
|
|
8
|
+
validator: value => /^[A-Za-z0-9_+=-]+$/u.test(value) || "Use the returned Outlook resource ID." };
|
|
9
|
+
const text = { type: "string", required: true, minLength: 1, maxLength: 100000, noTrim: true };
|
|
10
|
+
const instant = { type: "string", required: true, maxLength: 30,
|
|
11
|
+
validator: value => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u.test(value) && Number.isFinite(Date.parse(value)) || "Use a UTC ISO timestamp ending in Z." };
|
|
12
|
+
const resource = value => typeof value?.id === "string";
|
|
13
|
+
function operation(scopes, fields, destination, validateResult = resource) {
|
|
14
|
+
const schema = createSchema(fields);
|
|
15
|
+
return { scopes, validateResult, request(input) {
|
|
16
|
+
return destination(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
|
|
17
|
+
} };
|
|
18
|
+
}
|
|
19
|
+
const url = path => `https://graph.microsoft.com/v1.0/me${path}`;
|
|
20
|
+
|
|
21
|
+
const base = microsoftProvider(microsoftOutlookDefinition, "common", "folders.list", {
|
|
22
|
+
"messages.get": operation(["Mail.Read"], { messageId: id }, ({ messageId }) => ({ method: "GET", url: url(`/messages/${encodeURIComponent(messageId)}`), headers: { Prefer: 'outlook.body-content-type="text"' } }), value => resource(value) && typeof value.body?.content === "string"),
|
|
23
|
+
"attachments.list": graphRead("Mail.Read", { messageId: id }, ({ messageId }) => ({ pathname: `/me/messages/${encodeURIComponent(messageId)}/attachments` })),
|
|
24
|
+
"attachments.get": operation(["Mail.Read"], { messageId: id, attachmentId: id }, ({ messageId, attachmentId }) => ({ method: "GET", url: url(`/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`) }), value => resource(value) && value["@odata.type"] === "#microsoft.graph.fileAttachment" && typeof value.contentBytes === "string" && value.contentBytes.length <= 6666668),
|
|
25
|
+
"messages.setRead": operation(["Mail.ReadWrite"], { messageId: id, isRead: { type: "boolean", required: true } }, ({ messageId, isRead }) => ({ method: "PATCH", url: url(`/messages/${encodeURIComponent(messageId)}`), body: { isRead } })),
|
|
26
|
+
"messages.move": operation(["Mail.ReadWrite"], { messageId: id, destinationId: id }, ({ messageId, destinationId }) => ({ method: "POST", url: url(`/messages/${encodeURIComponent(messageId)}/move`), body: { destinationId } })),
|
|
27
|
+
"messages.send": operation(["Mail.Send"], {
|
|
28
|
+
subject: { ...text, maxLength: 998 }, text,
|
|
29
|
+
to: { type: "array", required: true, validator: value => value.length > 0 && value.length <= 20 && value.every(address => typeof address === "string" && address.length <= 320 && /^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/u.test(address)) || "Supply 1–20 recipient email addresses." }
|
|
30
|
+
}, ({ subject, text, to }) => ({ method: "POST", url: url("/sendMail"), body: { message: { subject, body: { contentType: "Text", content: text }, toRecipients: to.map(address => ({ emailAddress: { address } })) }, saveToSentItems: true } }), value => value?.accepted === true),
|
|
31
|
+
"calendars.list": { ...graphRead("Calendars.Read", {}, () => ({ pathname: "/me/calendars" })), scopes: ["Calendars.Read", "Calendars.ReadWrite"] },
|
|
32
|
+
"events.list": { ...graphRead("Calendars.Read", { calendarId: id, pageSize }, ({ calendarId, pageSize }) => ({ pathname: `/me/calendars/${encodeURIComponent(calendarId)}/events`, query: { $top: pageSize } })), scopes: ["Calendars.Read", "Calendars.ReadWrite"] },
|
|
33
|
+
"events.create": operation(["Calendars.ReadWrite"], { calendarId: id, subject: { ...text, maxLength: 255 }, start: instant, end: instant }, ({ calendarId, subject, start, end }) => {
|
|
34
|
+
if (Date.parse(end) <= Date.parse(start)) throw new ConnectorError("connector_input_invalid", "End must follow start.", { statusCode: 422 });
|
|
35
|
+
return { method: "POST", url: url(`/calendars/${encodeURIComponent(calendarId)}/events`), body: { subject, start: { dateTime: start.slice(0, -1), timeZone: "UTC" }, end: { dateTime: end.slice(0, -1), timeZone: "UTC" } } };
|
|
36
|
+
}),
|
|
37
|
+
"folders.list": graphRead("Mail.Read", {
|
|
38
|
+
pageSize, includeHiddenFolders: { type: "boolean", defaultTo: false }
|
|
39
|
+
}, (params) => ({ pathname: "/me/mailFolders", query: {
|
|
40
|
+
$top: params.pageSize, includeHiddenFolders: params.includeHiddenFolders,
|
|
41
|
+
$select: "id,displayName,childFolderCount,unreadItemCount,totalItemCount"
|
|
42
|
+
} })),
|
|
43
|
+
"inbox.list": graphRead("Mail.Read", { pageSize }, (params) => ({
|
|
44
|
+
pathname: "/me/mailFolders/inbox/messages", query: {
|
|
45
|
+
$top: params.pageSize, $select: "id,subject,from,receivedDateTime,isRead"
|
|
46
|
+
}
|
|
47
|
+
}))
|
|
48
|
+
});
|
|
49
|
+
const microsoftOutlookProvider = Object.freeze({ ...base,
|
|
50
|
+
async exchange(address, options, { request, fetchImpl }) {
|
|
51
|
+
if (new URL(address).pathname !== "/v1.0/me/sendMail") return request(address, options);
|
|
52
|
+
const response = await fetchImpl(address, { ...options, body: JSON.stringify(options.body), headers: { ...options.headers, "Content-Type": "application/json" }, credentials: "omit", redirect: "error" });
|
|
53
|
+
await response.body?.cancel();
|
|
54
|
+
if (response.status !== 202) throw Object.assign(new Error("Outlook did not accept the message."), { status: response.ok ? 502 : response.status });
|
|
55
|
+
return { accepted: true };
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
export { microsoftOutlookProvider };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { microsoftPowerPointDefinition } from "../shared/microsoft.js";
|
|
2
|
+
import { microsoftDocumentProvider } from "./microsoft.js";
|
|
3
|
+
|
|
4
|
+
const microsoftPowerPointProvider = microsoftDocumentProvider(microsoftPowerPointDefinition, ["ppt", "pptx", "pptm", "pot", "potx", "potm", "pps", "ppsx", "ppsm"]);
|
|
5
|
+
export { microsoftPowerPointProvider };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { microsoftSharePointDefinition } from "../shared/microsoft.js";
|
|
4
|
+
import { graphRead, microsoftProvider, driveItemId, pageSize } from "./microsoft.js";
|
|
5
|
+
import { microsoftOneDriveProvider } from "./microsoft-onedrive.js";
|
|
6
|
+
|
|
7
|
+
const siteId = { type: "string", required: true, minLength: 1, maxLength: 512,
|
|
8
|
+
validator: value => /^[A-Za-z0-9.,_-]+$/u.test(value) && value !== "." && value !== ".." || "Use the site's returned ID, not its URL." };
|
|
9
|
+
const listId = driveItemId;
|
|
10
|
+
const listFields = { type: "object", required: true, additionalProperties: true, validator: value => {
|
|
11
|
+
const entries = Object.entries(value);
|
|
12
|
+
return entries.length > 0 && entries.length <= 100 && JSON.stringify(value).length <= 65536 && entries.every(([key, cell]) =>
|
|
13
|
+
/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key) && (cell === null || ["string", "boolean"].includes(typeof cell) || typeof cell === "number" && Number.isFinite(cell))) || "Use up to 100 internal column names with scalar values (64 KiB maximum).";
|
|
14
|
+
} };
|
|
15
|
+
const createItemSchema = createSchema({ siteId, listId, fields: listFields });
|
|
16
|
+
const updateItemSchema = createSchema({ siteId, listId, itemId: driveItemId, fields: listFields,
|
|
17
|
+
eTag: { type: "string", required: true, minLength: 1, maxLength: 512, validator: value => !/[\r\n]/u.test(value) && value !== "*" || "Use the item's current ETag, not a wildcard." } });
|
|
18
|
+
const driveSchema = createSchema({ driveId: driveItemId });
|
|
19
|
+
function fileOperation(name, scopes) {
|
|
20
|
+
const source = microsoftOneDriveProvider.operations[name];
|
|
21
|
+
return { ...source, scopes, request(input) {
|
|
22
|
+
const { driveId, ...values } = input || {};
|
|
23
|
+
const valid = validateSchemaPayload({ schema: driveSchema, mode: "replace" }, { driveId }, { statusCode: 422 });
|
|
24
|
+
const result = source.request(values);
|
|
25
|
+
return { ...result, url: result.url.replace("/me/drive/items/", `/drives/${encodeURIComponent(valid.driveId)}/items/`) };
|
|
26
|
+
} };
|
|
27
|
+
}
|
|
28
|
+
const base = microsoftProvider(microsoftSharePointDefinition, "organizations", "sites.search", {
|
|
29
|
+
"sites.search": graphRead("Sites.Read.All", { search: { type: "string", required: true, minLength: 1, maxLength: 1024 } }, ({ search }) => ({ pathname: "/sites", query: { search } })),
|
|
30
|
+
"libraries.list": graphRead("Sites.Read.All", { siteId }, ({ siteId }) => ({ pathname: `/sites/${encodeURIComponent(siteId)}/drives` })),
|
|
31
|
+
"files.list": graphRead("Sites.Read.All", { driveId: driveItemId, folderId: { ...driveItemId, required: false }, pageSize }, ({ driveId, folderId, pageSize }) => ({ pathname: `/drives/${encodeURIComponent(driveId)}/${folderId ? `items/${encodeURIComponent(folderId)}` : "root"}/children`, query: { $top: pageSize } })),
|
|
32
|
+
"files.get": fileOperation("items.get", ["Sites.Read.All"]),
|
|
33
|
+
"files.upload": fileOperation("files.upload", ["Sites.ReadWrite.All"]),
|
|
34
|
+
"lists.list": graphRead("Sites.Read.All", { siteId }, ({ siteId }) => ({ pathname: `/sites/${encodeURIComponent(siteId)}/lists` })),
|
|
35
|
+
"listItems.list": graphRead("Sites.Read.All", { siteId, listId, pageSize }, ({ siteId, listId, pageSize }) => ({ pathname: `/sites/${encodeURIComponent(siteId)}/lists/${encodeURIComponent(listId)}/items`, query: { $expand: "fields", $top: pageSize } })),
|
|
36
|
+
"listItems.create": { scopes: ["Sites.ReadWrite.All"], validateResult: value => typeof value?.id === "string", request(input) {
|
|
37
|
+
const { siteId, listId, fields } = validateSchemaPayload({ schema: createItemSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
38
|
+
return { method: "POST", url: `https://graph.microsoft.com/v1.0/sites/${encodeURIComponent(siteId)}/lists/${encodeURIComponent(listId)}/items`, body: { fields } };
|
|
39
|
+
} },
|
|
40
|
+
"listItems.update": { scopes: ["Sites.ReadWrite.All"], validateResult: value => value !== null && typeof value === "object" && !Array.isArray(value), request(input) {
|
|
41
|
+
const { siteId, listId, itemId, fields, eTag } = validateSchemaPayload({ schema: updateItemSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
42
|
+
return { method: "PATCH", url: `https://graph.microsoft.com/v1.0/sites/${encodeURIComponent(siteId)}/lists/${encodeURIComponent(listId)}/items/${encodeURIComponent(itemId)}/fields`, headers: { "If-Match": eTag }, body: fields };
|
|
43
|
+
} }
|
|
44
|
+
});
|
|
45
|
+
const microsoftSharePointProvider = Object.freeze({ ...base, exchange: microsoftOneDriveProvider.exchange });
|
|
46
|
+
export { microsoftSharePointProvider };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { microsoftTeamsDefinition } from "../shared/microsoft.js";
|
|
4
|
+
import { graphRead, microsoftProvider } from "./microsoft.js";
|
|
5
|
+
|
|
6
|
+
const id = { type: "string", required: true, minLength: 1, maxLength: 1024,
|
|
7
|
+
validator: value => /^[A-Za-z0-9_:@.=-]+$/u.test(value) && value !== "." && value !== ".." || "Use the returned Teams resource ID." };
|
|
8
|
+
const channel = { teamId: id, channelId: id };
|
|
9
|
+
const pageSize = { type: "integer", min: 1, max: 50, defaultTo: 25 };
|
|
10
|
+
const channelPath = ({ teamId, channelId }) => `/teams/${encodeURIComponent(teamId)}/channels/${encodeURIComponent(channelId)}`;
|
|
11
|
+
function send(scope, fields, path) {
|
|
12
|
+
const schema = createSchema({ ...fields, text: { type: "string", required: true, minLength: 1, maxLength: 20000, noTrim: true } });
|
|
13
|
+
return { scopes: [scope], validateResult: value => typeof value?.id === "string", request(input) {
|
|
14
|
+
const values = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
15
|
+
return { method: "POST", url: `https://graph.microsoft.com/v1.0${path(values)}`, body: { body: { contentType: "text", content: values.text } } };
|
|
16
|
+
} };
|
|
17
|
+
}
|
|
18
|
+
const microsoftTeamsProvider = microsoftProvider(microsoftTeamsDefinition, "organizations", "teams.list", {
|
|
19
|
+
"teams.list": graphRead("Team.ReadBasic.All", {}, () => ({ pathname: "/me/joinedTeams" })),
|
|
20
|
+
"channels.list": graphRead("Channel.ReadBasic.All", { teamId: id }, ({ teamId }) => ({ pathname: `/teams/${encodeURIComponent(teamId)}/channels` })),
|
|
21
|
+
"messages.list": graphRead("ChannelMessage.Read.All", { ...channel, pageSize }, input => ({ pathname: `${channelPath(input)}/messages`, query: { $top: input.pageSize } })),
|
|
22
|
+
"replies.list": graphRead("ChannelMessage.Read.All", { ...channel, messageId: id }, input => ({ pathname: `${channelPath(input)}/messages/${encodeURIComponent(input.messageId)}/replies` })),
|
|
23
|
+
"messages.send": send("ChannelMessage.Send", channel, input => `${channelPath(input)}/messages`),
|
|
24
|
+
"replies.send": send("ChannelMessage.Send", { ...channel, messageId: id }, input => `${channelPath(input)}/messages/${encodeURIComponent(input.messageId)}/replies`),
|
|
25
|
+
"chats.list": graphRead("Chat.ReadWrite", {}, () => ({ pathname: "/me/chats" })),
|
|
26
|
+
"chatMessages.list": graphRead("Chat.ReadWrite", { chatId: id, pageSize }, ({ chatId, pageSize }) => ({ pathname: `/chats/${encodeURIComponent(chatId)}/messages`, query: { $top: pageSize } })),
|
|
27
|
+
"chatMessages.send": send("Chat.ReadWrite", { chatId: id }, ({ chatId }) => `/chats/${encodeURIComponent(chatId)}/messages`)
|
|
28
|
+
});
|
|
29
|
+
export { microsoftTeamsProvider };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { microsoftWordDefinition } from "../shared/microsoft.js";
|
|
2
|
+
import { microsoftDocumentProvider } from "./microsoft.js";
|
|
3
|
+
|
|
4
|
+
const microsoftWordProvider = microsoftDocumentProvider(microsoftWordDefinition, ["doc", "docx", "docm", "dot", "dotx", "dotm"]);
|
|
5
|
+
export { microsoftWordProvider };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
|
+
|
|
5
|
+
const graphOrigin = "https://graph.microsoft.com";
|
|
6
|
+
const pageSize = { type: "integer", min: 1, max: 100, defaultTo: 25 };
|
|
7
|
+
const driveItemId = {
|
|
8
|
+
type: "string", required: true, minLength: 1, maxLength: 1024,
|
|
9
|
+
validator: (value) => /^[a-zA-Z0-9_!-]+$/u.test(value) || "Enter the drive item's ID, not a file URL or path."
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function graphRead(permission, fields, destination) {
|
|
13
|
+
const schema = createSchema({ ...fields, nextLink: { type: "string", minLength: 1, maxLength: 16384 } });
|
|
14
|
+
return {
|
|
15
|
+
scopes: [permission],
|
|
16
|
+
validateResult: (value) => Array.isArray(value?.value) &&
|
|
17
|
+
(value["@odata.nextLink"] === undefined || typeof value["@odata.nextLink"] === "string"),
|
|
18
|
+
request(input) {
|
|
19
|
+
const params = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
20
|
+
const { pathname, query = {} } = destination(params);
|
|
21
|
+
const url = new URL(`/v1.0${pathname}`, graphOrigin);
|
|
22
|
+
for (const [key, value] of Object.entries(query)) {
|
|
23
|
+
if (value !== undefined) url.searchParams.set(key, String(value));
|
|
24
|
+
}
|
|
25
|
+
if (!params.nextLink) return { method: "GET", url: url.href };
|
|
26
|
+
// Graph owns the opaque paging query. A continuation must stay on this operation's resource.
|
|
27
|
+
const continuation = createSchema({ nextLink: {
|
|
28
|
+
type: "string", required: true,
|
|
29
|
+
validator(value) {
|
|
30
|
+
try {
|
|
31
|
+
const next = new URL(value);
|
|
32
|
+
return (next.origin === url.origin && next.pathname === url.pathname &&
|
|
33
|
+
!next.username && !next.password && !next.hash) || "Use the next link returned for this operation and resource.";
|
|
34
|
+
} catch { return "Use the next link returned for this operation and resource."; }
|
|
35
|
+
}
|
|
36
|
+
} });
|
|
37
|
+
validateSchemaPayload({ schema: continuation, mode: "replace" }, { nextLink: params.nextLink }, { statusCode: 422 });
|
|
38
|
+
return { method: "GET", url: params.nextLink };
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function microsoftProvider(definition, authority, checkOperation, operations) {
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
...definition,
|
|
46
|
+
oauth: ({ tenantId = authority } = {}) => ({
|
|
47
|
+
issuer: "https://login.microsoftonline.com/{tenantid}/v2.0",
|
|
48
|
+
authorization_endpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
|
|
49
|
+
token_endpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`
|
|
50
|
+
}),
|
|
51
|
+
authorizationParameters: { response_mode: "query" }, apiOrigins: [graphOrigin], checkOperation, operations
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function microsoftDocumentProvider(definition, extensions) {
|
|
56
|
+
const fields = "id,name,size,webUrl,file,folder,parentReference,lastModifiedDateTime,eTag";
|
|
57
|
+
const facet = (value) => value === undefined || (value !== null && typeof value === "object" && !Array.isArray(value));
|
|
58
|
+
const metadata = (value) => typeof value?.id === "string" && value.id.length > 0 &&
|
|
59
|
+
typeof value.name === "string" && value.name.length > 0 && facet(value.file) && facet(value.folder);
|
|
60
|
+
const matches = (value) => value?.file && extensions.includes(value.name.split(".").at(-1).toLowerCase());
|
|
61
|
+
const list = graphRead("Files.Read", { pageSize, folderId: { ...driveItemId, required: false } }, (params) => ({
|
|
62
|
+
pathname: params.folderId ? `/me/drive/items/${encodeURIComponent(params.folderId)}/children` : "/me/drive/root/children",
|
|
63
|
+
query: { $top: params.pageSize, $select: fields }
|
|
64
|
+
}));
|
|
65
|
+
const itemSchema = createSchema({ itemId: driveItemId });
|
|
66
|
+
const provider = microsoftProvider(definition, "common", "items.list", {
|
|
67
|
+
"items.list": { ...list, validateResult: (value) => list.validateResult(value) && value.value.every(metadata) },
|
|
68
|
+
"items.get": { scopes: ["Files.Read"], validateResult: metadata, request(input) {
|
|
69
|
+
const { itemId } = validateSchemaPayload({ schema: itemSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
70
|
+
const url = new URL(`/v1.0/me/drive/items/${encodeURIComponent(itemId)}`, graphOrigin);
|
|
71
|
+
url.searchParams.set("$select", fields);
|
|
72
|
+
return { method: "GET", url: url.href };
|
|
73
|
+
} }
|
|
74
|
+
});
|
|
75
|
+
return Object.freeze({ ...provider, async exchange(address, options, { request }) {
|
|
76
|
+
const result = await request(address, options);
|
|
77
|
+
if (new URL(address).pathname.endsWith("/children")) {
|
|
78
|
+
if (!provider.operations["items.list"].validateResult(result)) {
|
|
79
|
+
throw new ConnectorError("connector_response_invalid", "Microsoft returned an invalid file list.", { statusCode: 502 });
|
|
80
|
+
}
|
|
81
|
+
return { ...result, value: result.value.filter((item) => item.folder || matches(item)) };
|
|
82
|
+
}
|
|
83
|
+
if (!metadata(result)) throw new ConnectorError("connector_response_invalid", "Microsoft returned invalid file metadata.", { statusCode: 502 });
|
|
84
|
+
if (!matches(result)) throw new ConnectorError("connector_document_type_invalid", `Choose a ${definition.name} file.`);
|
|
85
|
+
return result;
|
|
86
|
+
} });
|
|
87
|
+
}
|
|
88
|
+
export { graphRead, microsoftProvider, microsoftDocumentProvider, pageSize, driveItemId };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { miroDefinition } from "../shared/miro.js";
|
|
4
|
+
import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
|
|
5
|
+
import { mcpRegistrationFields, registerMcpClient } from "./mcpRegistration.js";
|
|
6
|
+
|
|
7
|
+
const resource = "https://mcp.miro.com/";
|
|
8
|
+
const oauth = Object.freeze({
|
|
9
|
+
issuer: resource, authorization_endpoint: "https://mcp.miro.com/authorize",
|
|
10
|
+
token_endpoint: "https://mcp.miro.com/token", registration_endpoint: "https://mcp.miro.com/register",
|
|
11
|
+
response_types_supported: ["code"]
|
|
12
|
+
});
|
|
13
|
+
const miroProvider = Object.freeze({
|
|
14
|
+
...miroDefinition, oauth, oauthResource: resource, apiOrigins: ["https://mcp.miro.com"],
|
|
15
|
+
checkOperation: "tools.list", exchange: exchangeMcp, operations: mcpOperations(() => resource)
|
|
16
|
+
});
|
|
17
|
+
const registrationSchema = createSchema({
|
|
18
|
+
...mcpRegistrationFields,
|
|
19
|
+
scopes: { type: "array", required: true, minLength: 1,
|
|
20
|
+
items: { type: "string", enum: miroDefinition.scopes.map((scope) => scope.value) },
|
|
21
|
+
validator: (scopes) => scopes.length > 0 && new Set(scopes).size === scopes.length || "Select at least one permission and include each once." }
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
async function registerMiroClient(input, options) {
|
|
25
|
+
const values = validateSchemaPayload({ schema: registrationSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
26
|
+
return registerMcpClient("Miro", oauth, values, options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { miroProvider, registerMiroClient };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { discoverOAuthProtectedResourceMetadata, discoverAuthorizationServerMetadata } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
4
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
5
|
+
import { mcpRegistrationFields, registerMcpClient } from "./mcpRegistration.js";
|
|
6
|
+
import { n8nDefinition, validN8nOAuthDiscovery } from "../shared/mcp.js";
|
|
7
|
+
import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
|
|
8
|
+
|
|
9
|
+
const n8nProvider = Object.freeze({
|
|
10
|
+
...n8nDefinition,
|
|
11
|
+
apiOrigins: (settings) => [new URL(settings.serverUrl).origin],
|
|
12
|
+
oauth: (settings) => {
|
|
13
|
+
const discovery = settings.oauthDiscovery;
|
|
14
|
+
if (!validN8nOAuthDiscovery(discovery) || discovery.resource !== new URL(settings.serverUrl).href) {
|
|
15
|
+
throw new ConnectorError("connector_discovery_required", "Discover OAuth settings for this exact n8n MCP URL before connecting.");
|
|
16
|
+
}
|
|
17
|
+
return discovery.oauth;
|
|
18
|
+
},
|
|
19
|
+
oauthResource: (settings) => new URL(settings.serverUrl).href,
|
|
20
|
+
apiKey: { headers: (token) => ({ Authorization: `Bearer ${token}` }) },
|
|
21
|
+
checkOperation: "tools.list",
|
|
22
|
+
exchange: exchangeMcp,
|
|
23
|
+
operations: mcpOperations((settings) => new URL(settings.serverUrl).href)
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// The trusted setup host owns outbound-network policy, including private instances.
|
|
27
|
+
// Discovery returns public metadata only; it never registers or sends credentials.
|
|
28
|
+
async function discoverN8nOAuth({ serverUrl }, { fetchImpl = globalThis.fetch, signal } = {}) {
|
|
29
|
+
const settings = validateSchemaPayload({ schema: n8nDefinition.settingsSchema, mode: "replace" }, { serverUrl }, { statusCode: 422 });
|
|
30
|
+
const resource = new URL(settings.serverUrl).href;
|
|
31
|
+
const requestSignal = AbortSignal.any([...(signal ? [signal] : []), AbortSignal.timeout(15000)]);
|
|
32
|
+
let permittedOrigin = new URL(resource).origin;
|
|
33
|
+
const fetchFn = async (address, options) => {
|
|
34
|
+
const url = new URL(address);
|
|
35
|
+
if (url.protocol !== "https:" || url.origin !== permittedOrigin || url.username || url.password || url.search || url.hash ||
|
|
36
|
+
!url.pathname.includes("/.well-known/") || options?.method && options.method !== "GET") throw new Error("Invalid discovery destination.");
|
|
37
|
+
requestSignal.throwIfAborted();
|
|
38
|
+
return fetchImpl(url.href, { ...options, method: "GET", credentials: "omit", redirect: "error", signal: requestSignal });
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
const protectedResource = await discoverOAuthProtectedResourceMetadata(resource, undefined, fetchFn);
|
|
42
|
+
if (protectedResource.resource !== resource || protectedResource.authorization_servers?.length !== 1) throw new Error("Ambiguous resource authority.");
|
|
43
|
+
const issuer = new URL(protectedResource.authorization_servers[0]);
|
|
44
|
+
if (issuer.protocol !== "https:" || issuer.username || issuer.password || issuer.search || issuer.hash) throw new Error("Invalid issuer.");
|
|
45
|
+
permittedOrigin = issuer.origin;
|
|
46
|
+
const metadata = await discoverAuthorizationServerMetadata(issuer, { fetchFn });
|
|
47
|
+
const scopes = protectedResource.scopes_supported || metadata?.scopes_supported || [];
|
|
48
|
+
const discovery = { resource, oauth: metadata, scopes: [...scopes] };
|
|
49
|
+
if (metadata?.issuer !== protectedResource.authorization_servers[0] || !validN8nOAuthDiscovery(discovery)) {
|
|
50
|
+
throw new Error("Unsupported n8n OAuth metadata.");
|
|
51
|
+
}
|
|
52
|
+
return discovery;
|
|
53
|
+
} catch {
|
|
54
|
+
if (requestSignal.aborted) throw new ConnectorError("connector_discovery_interrupted", "n8n OAuth discovery was interrupted.");
|
|
55
|
+
throw new ConnectorError("connector_discovery_failed", "n8n OAuth discovery failed. Check the MCP URL, advertised authority and supported server version.", { statusCode: 502 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const registrationSchema = createSchema({
|
|
60
|
+
...mcpRegistrationFields,
|
|
61
|
+
serverUrl: { type: "string", required: true, minLength: 1, maxLength: 2048 },
|
|
62
|
+
scopes: { type: "array", required: true, minLength: 1,
|
|
63
|
+
items: { type: "string", minLength: 1, maxLength: 200 },
|
|
64
|
+
validator: (values) => new Set(values).size === values.length || "Select each permission once." }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// The privileged setup caller owns authorization and private storage of this result.
|
|
68
|
+
async function registerN8nClient(input, options) {
|
|
69
|
+
const values = validateSchemaPayload({ schema: registrationSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
70
|
+
const discovery = await discoverN8nOAuth({ serverUrl: values.serverUrl }, options);
|
|
71
|
+
if (values.scopes.some((scope) => !discovery.scopes.includes(scope))) {
|
|
72
|
+
throw new ConnectorError("connector_scope_unavailable", "Select only permissions advertised by this n8n MCP server.", { statusCode: 422 });
|
|
73
|
+
}
|
|
74
|
+
const client = await registerMcpClient("n8n", discovery.oauth, values, options);
|
|
75
|
+
return { ...client, ...discovery, requestedScopes: [...values.scopes] };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { n8nProvider, discoverN8nOAuth, registerN8nClient };
|