@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,77 @@
|
|
|
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 { calendlyDefinition } from "../shared/tokens.js";
|
|
5
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
6
|
+
|
|
7
|
+
const id = { type: "string", required: true, minLength: 1, maxLength: 100, pattern: "^[A-Za-z0-9_-]+$" };
|
|
8
|
+
const text = { type: "string", minLength: 1, maxLength: 1000 };
|
|
9
|
+
const email = { ...text, maxLength: 320, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" };
|
|
10
|
+
const date = { ...text, validator: value => /^\d{4}-\d{2}-\d{2}T.*Z$/.test(value) && Number.isFinite(Date.parse(value)) || "Use a UTC ISO timestamp ending in Z." };
|
|
11
|
+
const page = { count: { type: "integer", min: 1, max: 100, defaultTo: 20 }, page_token: { type: "string", maxLength: 4096 } };
|
|
12
|
+
const status = { type: "string", enum: ["active", "canceled"] };
|
|
13
|
+
const record = result => typeof result?.resource?.uri === "string";
|
|
14
|
+
const collection = result => Array.isArray(result?.collection) && Number.isInteger(result?.pagination?.count);
|
|
15
|
+
function uri(kind) { return { type: "string", maxLength: 2048, pattern: `^https://api\\.calendly\\.com/${kind}/[A-Za-z0-9_-]+$` }; }
|
|
16
|
+
function operation(method, endpoint, fields, scope, validateResult, prepare = value => value) {
|
|
17
|
+
const schema = createSchema(fields);
|
|
18
|
+
return { scopes: [scope], request(input) {
|
|
19
|
+
const { uuid, invitee_uuid, ...values } = prepare(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
|
|
20
|
+
const url = new URL(`https://api.calendly.com/${endpoint.replace("{uuid}", uuid || "").replace("{invitee_uuid}", invitee_uuid || "")}`);
|
|
21
|
+
if (method === "GET") { for (const [key, value] of Object.entries(values)) url.searchParams.set(key, String(value)); return { method, url: url.href }; }
|
|
22
|
+
return { method, url: url.href, ...(method === "POST" ? { body: values } : {}) };
|
|
23
|
+
}, validateResult };
|
|
24
|
+
}
|
|
25
|
+
const calendlyProvider = Object.freeze({
|
|
26
|
+
...calendlyDefinition, apiOrigins: ["https://api.calendly.com"],
|
|
27
|
+
apiKey: { headers: (key) => ({ Authorization: `Bearer ${key}` }) },
|
|
28
|
+
oauth: {
|
|
29
|
+
issuer: "https://auth.calendly.com",
|
|
30
|
+
authorization_endpoint: "https://auth.calendly.com/oauth/authorize",
|
|
31
|
+
token_endpoint: "https://auth.calendly.com/oauth/token"
|
|
32
|
+
},
|
|
33
|
+
checkOperation: "profile.read",
|
|
34
|
+
operations: {
|
|
35
|
+
"eventTypes.get": operation("GET", "event_types/{uuid}", { uuid: id }, "event_types:read", record),
|
|
36
|
+
"availability.list": operation("GET", "event_type_available_times", { event_type: { ...uri("event_types"), required: true },
|
|
37
|
+
start_time: { ...date, required: true }, end_time: { ...date, required: true }
|
|
38
|
+
}, "availability:read", result => Array.isArray(result?.collection), value => {
|
|
39
|
+
const span = Date.parse(value.end_time) - Date.parse(value.start_time);
|
|
40
|
+
if (span <= 0 || span > 31 * 86400000) throw new ConnectorError("connector_input_invalid", "Choose a positive range of at most 31 days.", { statusCode: 422 }); return value;
|
|
41
|
+
}),
|
|
42
|
+
"events.list": operation("GET", "scheduled_events", { user: uri("users"), organization: uri("organizations"), group: uri("groups"),
|
|
43
|
+
invitee_email: email, status, ...page, sort: { type: "string", enum: ["start_time:asc", "start_time:desc"] }, min_start_time: date, max_start_time: date
|
|
44
|
+
}, "scheduled_events:read", collection, value => {
|
|
45
|
+
if (!(value.user || value.organization || value.group)) throw new ConnectorError("connector_input_invalid", "Choose the user, organization or group whose events to read.", { statusCode: 422 }); return value;
|
|
46
|
+
}),
|
|
47
|
+
"events.get": operation("GET", "scheduled_events/{uuid}", { uuid: id }, "scheduled_events:read", record),
|
|
48
|
+
"events.cancel": operation("POST", "scheduled_events/{uuid}/cancellation", { uuid: id, reason: { ...text, maxLength: 10000 } },
|
|
49
|
+
"scheduled_events:write", result => typeof result?.resource?.canceled_by === "string" && typeof result.resource.created_at === "string"),
|
|
50
|
+
"invitees.list": operation("GET", "scheduled_events/{uuid}/invitees", { uuid: id, ...page, status, email,
|
|
51
|
+
sort: { type: "string", enum: ["created_at:asc", "created_at:desc"] } }, "scheduled_events:read", collection),
|
|
52
|
+
"invitees.get": operation("GET", "scheduled_events/{uuid}/invitees/{invitee_uuid}", { uuid: id, invitee_uuid: id }, "scheduled_events:read", record),
|
|
53
|
+
"invitees.create": operation("POST", "invitees", { event_type: { ...uri("event_types"), required: true }, start_time: { ...date, required: true },
|
|
54
|
+
invitee: { type: "object", required: true, schema: createSchema({ email: { ...email, required: true }, name: text,
|
|
55
|
+
timezone: { ...text, required: true, validator: value => { try { new Intl.DateTimeFormat("en", { timeZone: value }); return true; } catch { return "Choose an IANA timezone."; } } } }) },
|
|
56
|
+
questions_and_answers: { type: "array", items: { type: "object", schema: createSchema({ question: { ...text, required: true }, answer: { ...text, required: true }, position: { type: "integer", required: true, min: 0 } }) },
|
|
57
|
+
validator: value => value.length <= 100 || "Use at most 100 answers." },
|
|
58
|
+
event_guests: { type: "array", items: email, validator: value => value.length <= 10 || "Use at most 10 guests." }
|
|
59
|
+
}, "scheduled_events:write", record),
|
|
60
|
+
"schedulingLinks.create": operation("POST", "scheduling_links", { owner: { ...uri("event_types"), required: true },
|
|
61
|
+
owner_type: { type: "string", enum: ["EventType"], defaultTo: "EventType" }, max_event_count: { type: "integer", enum: [1], defaultTo: 1 }
|
|
62
|
+
}, "scheduling_links:write", result => typeof result?.resource?.booking_url === "string" && typeof result.resource.owner === "string"),
|
|
63
|
+
"noShows.create": operation("POST", "invitee_no_shows", { invitee: { type: "string", required: true,
|
|
64
|
+
pattern: "^https://api\\.calendly\\.com/scheduled_events/[A-Za-z0-9_-]+/invitees/[A-Za-z0-9_-]+$" } }, "scheduled_events:write", record),
|
|
65
|
+
"noShows.get": operation("GET", "invitee_no_shows/{uuid}", { uuid: id }, "scheduled_events:read", record),
|
|
66
|
+
"noShows.delete": operation("DELETE", "invitee_no_shows/{uuid}", { uuid: id }, "scheduled_events:write", result => result === null),
|
|
67
|
+
"profile.read": { ...jsonOperation("https://api.calendly.com/users/me", {},
|
|
68
|
+
(result) => typeof result?.resource?.uri === "string" && typeof result.resource.name === "string"), scopes: ["users:read"] },
|
|
69
|
+
"eventTypes.list": { ...jsonOperation("https://api.calendly.com/event_types", {
|
|
70
|
+
user: { type: "string", required: true, minLength: 1, maxLength: 2048 },
|
|
71
|
+
count: { type: "integer", min: 1, max: 100, defaultTo: 20 },
|
|
72
|
+
page_token: { type: "string", maxLength: 4096 },
|
|
73
|
+
active: { type: "boolean" }
|
|
74
|
+
}, (result) => Array.isArray(result?.collection) && Number.isInteger(result.pagination?.count)), scopes: ["event_types:read"] }
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
export { calendlyProvider };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { canvaDefinition } from "../shared/canva.js";
|
|
2
|
+
import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
|
|
3
|
+
|
|
4
|
+
const canvaProvider = Object.freeze({
|
|
5
|
+
...canvaDefinition,
|
|
6
|
+
oauth: { issuer: "https://mcp.canva.com", authorization_endpoint: "https://mcp.canva.com/authorize",
|
|
7
|
+
token_endpoint: "https://mcp.canva.com/token", response_types_supported: ["code"] },
|
|
8
|
+
oauthResource: "https://mcp.canva.com", apiOrigins: ["https://mcp.canva.com"],
|
|
9
|
+
requestTimeoutMs: 60_000, checkOperation: "tools.list", exchange: exchangeMcp,
|
|
10
|
+
operations: mcpOperations(() => "https://mcp.canva.com/mcp")
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export { canvaProvider };
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { chargebeeDefinition } from "../shared/chargebee.js";
|
|
5
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
6
|
+
|
|
7
|
+
const origin = ({ siteName }) => `https://${siteName.toLowerCase()}.chargebee.com`;
|
|
8
|
+
const text = { type: "string", minLength: 1, maxLength: 250 };
|
|
9
|
+
const resource = { ...text, required: true, maxLength: 100, pattern: "^[A-Za-z0-9_-]+$" };
|
|
10
|
+
const pagination = { limit: { type: "integer", min: 1, max: 100, defaultTo: 20 }, offset: { ...text, maxLength: 1000 } };
|
|
11
|
+
const itemList = { type: "array", required: true, items: { type: "object", schema: createSchema({
|
|
12
|
+
item_price_id: { ...resource }, quantity: { type: "integer", min: 1, required: true }
|
|
13
|
+
}) }, validator: values => values.length > 0 && values.length <= 100 || "Choose between 1 and 100 item prices." };
|
|
14
|
+
const customerFields = { email: { ...text, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" }, first_name: text, last_name: text, company: text };
|
|
15
|
+
const returnUrl = { ...text, required: true, validator: value => {
|
|
16
|
+
try { const u = new URL(value); return !u.username && !u.password && (u.protocol === "https:" || u.protocol === "http:" && ["localhost", "127.0.0.1"].includes(u.hostname)) || "Use an HTTPS or localhost return URL."; }
|
|
17
|
+
catch { return "Use an absolute return URL."; }
|
|
18
|
+
} };
|
|
19
|
+
const record = key => result => typeof result?.[key]?.id === "string" && result[key].id.length > 0;
|
|
20
|
+
const collection = key => result => Array.isArray(result?.list) && result.list.every(entry => record(key)(entry)) &&
|
|
21
|
+
(result.next_offset === undefined || typeof result.next_offset === "string");
|
|
22
|
+
function operation(method, endpoint, fields, validateResult, defaults = {}) {
|
|
23
|
+
const schema = createSchema({ ...fields, ...(method === "POST" ? { idempotencyKey: { ...resource } } : {}) });
|
|
24
|
+
return { scopes: [], request(input, settings) {
|
|
25
|
+
const { resource: id, idempotencyKey, ...values } = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
26
|
+
const body = new URLSearchParams();
|
|
27
|
+
for (const [key, value] of Object.entries({ ...values, ...defaults })) {
|
|
28
|
+
if (Array.isArray(value)) value.forEach((entry, index) => { for (const [field, child] of Object.entries(entry)) body.set(`${key}[${field}][${index}]`, String(child)); });
|
|
29
|
+
else if (value && typeof value === "object") { for (const [field, child] of Object.entries(value)) body.set(`${key}[${field}]`, String(child)); }
|
|
30
|
+
else body.set(key, String(value));
|
|
31
|
+
}
|
|
32
|
+
const url = new URL(`${origin(settings)}/api/v2/${endpoint.replace("{resource}", id || "")}`);
|
|
33
|
+
if (method === "GET") { url.search = body.toString(); return { method, url: url.href }; }
|
|
34
|
+
return { method, url: url.href, body: body.toString(), headers: { "Content-Type": "application/x-www-form-urlencoded", "chargebee-idempotency-key": idempotencyKey } };
|
|
35
|
+
}, validateResult };
|
|
36
|
+
}
|
|
37
|
+
const chargebeeProvider = Object.freeze({
|
|
38
|
+
...chargebeeDefinition,
|
|
39
|
+
apiOrigins: (settings) => [origin(settings)],
|
|
40
|
+
apiKey: { headers: (key) => {
|
|
41
|
+
if (/[:\s\p{Cc}]/u.test(key)) {
|
|
42
|
+
throw new ConnectorError("connector_binding_missing", "Use the Chargebee API key without spaces, control characters or colons.");
|
|
43
|
+
}
|
|
44
|
+
return { Authorization: `Basic ${Buffer.from(`${key}:`).toString("base64")}` };
|
|
45
|
+
} },
|
|
46
|
+
checkOperation: "customers.list",
|
|
47
|
+
operations: {
|
|
48
|
+
"customers.get": operation("GET", "customers/{resource}", { resource }, record("customer")),
|
|
49
|
+
"customers.create": operation("POST", "customers", { ...customerFields, email: { ...customerFields.email, required: true }, id: { ...resource, required: false } }, record("customer")),
|
|
50
|
+
"customers.update": operation("POST", "customers/{resource}", { resource, ...customerFields }, record("customer")),
|
|
51
|
+
"itemFamilies.list": operation("GET", "item_families", pagination, collection("item_family")),
|
|
52
|
+
"items.list": operation("GET", "items", pagination, collection("item")),
|
|
53
|
+
"items.get": operation("GET", "items/{resource}", { resource }, record("item")),
|
|
54
|
+
"itemPrices.list": operation("GET", "item_prices", pagination, collection("item_price")),
|
|
55
|
+
"itemPrices.get": operation("GET", "item_prices/{resource}", { resource }, record("item_price")),
|
|
56
|
+
"subscriptions.list": operation("GET", "subscriptions", { ...pagination, "customer_id[is]": text }, collection("subscription")),
|
|
57
|
+
"subscriptions.get": operation("GET", "subscriptions/{resource}", { resource }, record("subscription")),
|
|
58
|
+
"subscriptions.create": operation("POST", "customers/{resource}/subscription_for_items", { resource, subscription_items: itemList,
|
|
59
|
+
auto_collection: { type: "string", required: true, enum: ["on", "off"] }, invoice_immediately: { type: "boolean", required: true },
|
|
60
|
+
id: { ...resource, required: false }, trial_end: { type: "integer", min: 0 }
|
|
61
|
+
}, record("subscription")),
|
|
62
|
+
"subscriptions.updateAtTermEnd": operation("POST", "subscriptions/{resource}/update_for_items", { resource, subscription_items: itemList,
|
|
63
|
+
replace_items_list: { type: "boolean", required: true }
|
|
64
|
+
}, record("subscription"), { end_of_term: true }),
|
|
65
|
+
"subscriptions.cancelAtTermEnd": operation("POST", "subscriptions/{resource}/cancel_for_items", { resource, cancel_reason_code: text }, record("subscription"), { cancel_option: "end_of_term" }),
|
|
66
|
+
"invoices.list": operation("GET", "invoices", { ...pagination, "customer_id[is]": text, "subscription_id[is]": text }, collection("invoice")),
|
|
67
|
+
"invoices.get": operation("GET", "invoices/{resource}", { resource, line_items_limit: { type: "integer", min: 1, max: 300 }, line_items_offset: { ...text, maxLength: 1000 } }, record("invoice")),
|
|
68
|
+
"invoices.pdf": operation("POST", "invoices/{resource}/pdf", { resource, disposition_type: { type: "string", enum: ["inline", "attachment"], defaultTo: "attachment" } }, result => typeof result?.download?.download_url === "string"),
|
|
69
|
+
"hostedPages.checkoutNew": operation("POST", "hosted_pages/checkout_new_for_items", { subscription_items: itemList,
|
|
70
|
+
customer: { type: "object", required: true, schema: createSchema({ id: { ...resource } }) }, redirect_url: returnUrl, cancel_url: returnUrl
|
|
71
|
+
}, record("hosted_page")),
|
|
72
|
+
"hostedPages.checkoutExisting": operation("POST", "hosted_pages/checkout_existing_for_items", { subscription_items: itemList,
|
|
73
|
+
subscription: { type: "object", required: true, schema: createSchema({ id: { ...resource } }) }, redirect_url: returnUrl, cancel_url: returnUrl
|
|
74
|
+
}, record("hosted_page")),
|
|
75
|
+
"hostedPages.get": operation("GET", "hosted_pages/{resource}", { resource }, record("hosted_page")),
|
|
76
|
+
"portalSessions.create": operation("POST", "portal_sessions", { customer: { type: "object", required: true, schema: createSchema({ id: { ...resource } }) }, redirect_url: returnUrl }, record("portal_session")),
|
|
77
|
+
"events.list": operation("GET", "events", pagination, collection("event")),
|
|
78
|
+
"events.get": operation("GET", "events/{resource}", { resource }, record("event")),
|
|
79
|
+
"customers.list": jsonOperation((settings) => `${origin(settings)}/api/v2/customers`, {
|
|
80
|
+
limit: { type: "integer", min: 1, max: 100, defaultTo: 10 },
|
|
81
|
+
offset: { type: "string", minLength: 1, maxLength: 1000 },
|
|
82
|
+
include_deleted: { type: "boolean", defaultTo: false }
|
|
83
|
+
}, (result) => Array.isArray(result?.list) && result.list.every((entry) =>
|
|
84
|
+
entry?.customer && typeof entry.customer.id === "string" && entry.customer.id.length > 0) &&
|
|
85
|
+
(result.next_offset === undefined || typeof result.next_offset === "string" && result.next_offset.length > 0 && result.next_offset.length <= 1000))
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
export { chargebeeProvider };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { clayDefinition } from "../shared/tokens.js";
|
|
4
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
5
|
+
|
|
6
|
+
const endpoint = "https://api.clay.com/public/v0";
|
|
7
|
+
const pageSchema = createSchema({
|
|
8
|
+
searchId: { type: "string", required: true, minLength: 1, maxLength: 1024,
|
|
9
|
+
validator: (value) => /^[a-zA-Z0-9_-]+$/u.test(value) || "Use the search ID returned by Clay." },
|
|
10
|
+
limit: { type: "integer", min: 1, max: 500, defaultTo: 20 }
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const routineId = { type: "string", required: true, minLength: 1, maxLength: 64,
|
|
14
|
+
validator: value => /^[a-zA-Z0-9_:-]+$/u.test(value) || "Use the routine ID from Clay, including function: for custom functions." };
|
|
15
|
+
const object = { type: "object", additionalProperties: true };
|
|
16
|
+
const array = (items, maximum, minimum = 0) => ({ type: "array", items,
|
|
17
|
+
validator: value => value.length >= minimum && value.length <= maximum || `Use ${minimum}–${maximum} entries.` });
|
|
18
|
+
const text = { type: "string", minLength: 1 };
|
|
19
|
+
|
|
20
|
+
function routineOperation(method, suffix, fields, validateResult) {
|
|
21
|
+
const schema = createSchema({ routineId, ...fields });
|
|
22
|
+
return { scopes: [], request(input) {
|
|
23
|
+
const { routineId: id, ...values } = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
24
|
+
const url = new URL(`${endpoint}/routines/${suffix.replace("{id}", encodeURIComponent(id))}`);
|
|
25
|
+
if (method === "POST") return { method, url: url.href, body: values };
|
|
26
|
+
for (const [key, value] of Object.entries(values)) url.searchParams.set(key, String(value));
|
|
27
|
+
return { method, url: url.href };
|
|
28
|
+
}, validateResult };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const clayProvider = Object.freeze({
|
|
32
|
+
...clayDefinition,
|
|
33
|
+
apiOrigins: ["https://api.clay.com"],
|
|
34
|
+
apiKey: { headers: (key) => ({ "clay-api-key": key }) },
|
|
35
|
+
checkOperation: "identity.read",
|
|
36
|
+
operations: {
|
|
37
|
+
"identity.read": jsonOperation(`${endpoint}/me`, {}, (result) =>
|
|
38
|
+
typeof result?.user?.id === "string" && result.user.id.length > 0 &&
|
|
39
|
+
typeof result?.workspace?.id === "string" && result.workspace.id.length > 0),
|
|
40
|
+
"credits.balance": jsonOperation(`${endpoint}/credits/balance`, {}, result => typeof result?.balance === "number"),
|
|
41
|
+
"searches.reference": jsonOperation(`${endpoint}/search/query-mode/reference`, {}, result => typeof result?.reference === "string"),
|
|
42
|
+
"routines.run": routineOperation("POST", "{id}/run", {
|
|
43
|
+
items: { ...array({ type: "object", schema: createSchema({
|
|
44
|
+
id: { ...text, required: true, maxLength: 64 }, inputs: { ...object, required: true }
|
|
45
|
+
}) }, 100, 1), required: true },
|
|
46
|
+
webhook_id: { ...text, maxLength: 64 }
|
|
47
|
+
}, result => typeof result?.routine_run_id === "string" && result.routine_run_id.length > 0 && result.status === "in_progress"),
|
|
48
|
+
"routines.results": routineOperation("GET", "run/{id}/results", {
|
|
49
|
+
cursor: text, limit: { type: "integer", min: 1, max: 100, defaultTo: 20 }
|
|
50
|
+
}, result => typeof result?.routine_run_id === "string" && typeof result.total === "number" && typeof result.finished === "number" &&
|
|
51
|
+
(result.status === "in_progress" || result.status === "complete" && Array.isArray(result.data))),
|
|
52
|
+
"tables.query": jsonOperation(`${endpoint}/tables/query`, {
|
|
53
|
+
query: { type: "object", required: true, schema: createSchema({
|
|
54
|
+
tables: { ...array({ type: "object", schema: createSchema({ id: { ...text, required: true }, alias: { type: "string" } }) }, 5, 1), required: true },
|
|
55
|
+
field_mode: { type: "string", enum: ["names", "ids"] },
|
|
56
|
+
filter: object, select: array(object, 20), join: array(object, 4),
|
|
57
|
+
group_by: array(text, 5), order_by: array(object, 3)
|
|
58
|
+
}) },
|
|
59
|
+
cursor: text, limit: { type: "integer", min: 1, max: 100, defaultTo: 50 }
|
|
60
|
+
}, result => Array.isArray(result?.data), "POST"),
|
|
61
|
+
"searches.create": jsonOperation(`${endpoint}/search/query-mode`, {
|
|
62
|
+
query: { type: "string", required: true, minLength: 1, maxLength: 16000,
|
|
63
|
+
validator: (value) => value.trim().length > 0 || "Enter a Clay search query." }
|
|
64
|
+
}, (result) => typeof result?.search_id === "string" && result.search_id.length > 0 &&
|
|
65
|
+
["people", "companies"].includes(result.source_type), "POST"),
|
|
66
|
+
"searches.next": {
|
|
67
|
+
scopes: [],
|
|
68
|
+
request(input) {
|
|
69
|
+
const { searchId, limit } = validateSchemaPayload({ schema: pageSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
70
|
+
return { method: "POST", url: `${endpoint}/search/query-mode/${encodeURIComponent(searchId)}/run`, body: { limit } };
|
|
71
|
+
},
|
|
72
|
+
validateResult: (result) => Array.isArray(result?.data) && typeof result.has_more === "boolean" &&
|
|
73
|
+
["people", "companies"].includes(result.source_type)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export { clayProvider };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { clickhouseDefinition } from "../shared/clickhouse.js";
|
|
4
|
+
|
|
5
|
+
const name = { type: "string", required: true, minLength: 1, maxLength: 256,
|
|
6
|
+
validator: (value) => !/[\p{Cc}]/u.test(value) || "Use a name without control characters." };
|
|
7
|
+
const pagination = {
|
|
8
|
+
limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
|
|
9
|
+
offset: { type: "integer", min: 0, max: 1000000, defaultTo: 0 }
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function queryOperation(query, fields, validateData, parameters = values => values) {
|
|
13
|
+
const schema = createSchema(fields);
|
|
14
|
+
return {
|
|
15
|
+
scopes: [],
|
|
16
|
+
request(input, settings) {
|
|
17
|
+
const values = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
18
|
+
const url = new URL(settings.httpUrl);
|
|
19
|
+
const sql = typeof query === "function" ? query(values) : query;
|
|
20
|
+
url.searchParams.set("query", `${sql} FORMAT JSON`);
|
|
21
|
+
url.searchParams.set("readonly", "1");
|
|
22
|
+
url.searchParams.set("wait_end_of_query", "1");
|
|
23
|
+
url.searchParams.set("max_execution_time", "10");
|
|
24
|
+
url.searchParams.set("max_result_rows", "100");
|
|
25
|
+
url.searchParams.set("max_result_bytes", "5242880");
|
|
26
|
+
url.searchParams.set("result_overflow_mode", "throw");
|
|
27
|
+
for (const [key, value] of Object.entries(parameters(values))) url.searchParams.set(`param_${key}`, String(value).replaceAll("\\", "\\\\"));
|
|
28
|
+
return { method: "GET", url: url.href };
|
|
29
|
+
},
|
|
30
|
+
validateResult(result) {
|
|
31
|
+
return Boolean(result && !result.exception && !result.error && Array.isArray(result.meta) &&
|
|
32
|
+
result.meta.every((column) => typeof column?.name === "string" && typeof column.type === "string") &&
|
|
33
|
+
Array.isArray(result.data) && result.data.length <= 100 && Number.isInteger(result.rows) && result.rows === result.data.length &&
|
|
34
|
+
result.data.every((row) => row && typeof row === "object" && !Array.isArray(row)) && (!validateData || validateData(result.data)));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const clickhouseProvider = Object.freeze({
|
|
40
|
+
...clickhouseDefinition,
|
|
41
|
+
apiOrigins: (settings) => [new URL(settings.httpUrl).origin],
|
|
42
|
+
apiKey: { headers: (password, settings) => ({ Authorization: `Basic ${Buffer.from(`${settings.username || "default"}:${password}`).toString("base64")}` }) },
|
|
43
|
+
checkOperation: "connection.check",
|
|
44
|
+
operations: {
|
|
45
|
+
"connection.check": queryOperation("SELECT 1 AS ok, currentUser() AS user", {},
|
|
46
|
+
(rows) => rows.length === 1 && rows[0].ok === 1 && typeof rows[0].user === "string" && rows[0].user.length > 0),
|
|
47
|
+
"queries.read": queryOperation(values => values.sql, {
|
|
48
|
+
sql: { type: "string", required: true, minLength: 1, maxLength: 16000,
|
|
49
|
+
// eslint-disable-next-line no-control-regex -- Reject literal control characters in provider input.
|
|
50
|
+
validator: value => /^\s*(SELECT|WITH)\b/iu.test(value) && !/[;\u0000]/u.test(value) || "Use one SELECT/WITH query without a terminating semicolon; pass values as typed parameters." },
|
|
51
|
+
parameters: { type: "object", additionalProperties: true,
|
|
52
|
+
validator: value => Object.keys(value).length <= 100 && Object.entries(value).every(([key, item]) =>
|
|
53
|
+
/^[a-zA-Z_][a-zA-Z0-9_]*$/u.test(key) && typeof item === "string" && item.length <= 16000 && !/[\p{Cc}]/u.test(item)) || "Use up to 100 named string parameters without control characters." }
|
|
54
|
+
}, undefined, values => values.parameters || {}),
|
|
55
|
+
"tables.list": queryOperation((values) => `SELECT database, name, engine FROM system.tables${values.database === undefined ? "" : " WHERE database = {database:String}"} ORDER BY database, name LIMIT {limit:UInt32} OFFSET {offset:UInt64}`,
|
|
56
|
+
{ ...pagination, database: { ...name, required: false } },
|
|
57
|
+
(rows) => rows.every((row) => typeof row.database === "string" && typeof row.name === "string" && typeof row.engine === "string")),
|
|
58
|
+
"columns.list": queryOperation("SELECT name, type, position, default_kind, default_expression FROM system.columns WHERE database = {database:String} AND table = {table:String} ORDER BY position LIMIT {limit:UInt32} OFFSET {offset:UInt64}",
|
|
59
|
+
{ database: name, table: name, ...pagination },
|
|
60
|
+
(rows) => rows.every((row) => typeof row.name === "string" && typeof row.type === "string")),
|
|
61
|
+
"rows.list": queryOperation((values) => `SELECT * FROM {database:Identifier}.{table:Identifier}${values.orderBy === undefined ? "" : " ORDER BY {orderBy:Identifier}"} LIMIT {limit:UInt32} OFFSET {offset:UInt64}`,
|
|
62
|
+
{ database: name, table: name, orderBy: { ...name, required: false }, ...pagination })
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export { clickhouseProvider };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { confidenceFlagsDefinition, confidenceExpDefinition } from "../shared/confidence.js";
|
|
4
|
+
import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
|
|
5
|
+
import { mcpRegistrationFields, registerMcpClient } from "./mcpRegistration.js";
|
|
6
|
+
|
|
7
|
+
const origin = "https://mcp.confidence.dev";
|
|
8
|
+
const oauth = Object.freeze({ issuer: origin, authorization_endpoint: `${origin}/authorize`, token_endpoint: `${origin}/token`,
|
|
9
|
+
registration_endpoint: `${origin}/register`, response_types_supported: ["code"], code_challenge_methods_supported: ["S256"] });
|
|
10
|
+
|
|
11
|
+
function provider(definition, path) {
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
...definition, oauth, oauthResource: `${origin}/mcp`, apiOrigins: [origin], checkOperation: "tools.list", exchange: exchangeMcp,
|
|
14
|
+
operations: Object.fromEntries(Object.entries(mcpOperations(() => `${origin}/mcp/${path}`)).map(([name, operation]) => [name, { ...operation, scopes: ["openid"] }]))
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const confidenceFlagsProvider = provider(confidenceFlagsDefinition, "flags");
|
|
18
|
+
const confidenceExpProvider = provider(confidenceExpDefinition, "experiments");
|
|
19
|
+
const registrationSchema = createSchema({ ...mcpRegistrationFields,
|
|
20
|
+
scopes: { type: "array", required: true, minLength: 1, items: { type: "string", enum: confidenceFlagsDefinition.scopes.map((scope) => scope.value) },
|
|
21
|
+
validator: (scopes) => scopes.includes("openid") && new Set(scopes).size === scopes.length || "Select account identity and include each permission once." }
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
async function registerConfidenceClient(input, options) {
|
|
25
|
+
const values = validateSchemaPayload({ schema: registrationSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
26
|
+
return registerMcpClient("Confidence", oauth, values, options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { confidenceFlagsProvider, confidenceExpProvider, registerConfidenceClient };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { contentfulDefinition } from "../shared/tokens.js";
|
|
4
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
5
|
+
|
|
6
|
+
const text = { type: "string", minLength: 1, maxLength: 256 };
|
|
7
|
+
const page = { limit: { type: "integer", min: 1, max: 1000, defaultTo: 1 }, skip: { type: "integer", min: 0, defaultTo: 0 } };
|
|
8
|
+
const localized = { locale: text };
|
|
9
|
+
const resource = type => result => result?.sys?.type === type && typeof result.sys.id === "string" && result.sys.id.length > 0;
|
|
10
|
+
const collection = type => result => result?.sys?.type === "Array" && Array.isArray(result.items) && result.items.every(resource(type)) &&
|
|
11
|
+
Number.isInteger(result.total) && result.total >= 0 && Number.isInteger(result.skip) && result.skip >= 0 && Number.isInteger(result.limit) && result.limit >= 0;
|
|
12
|
+
const endpoint = (settings, path) => `https://${settings.region === "eu" ? "cdn.eu.contentful.com" : "cdn.contentful.com"}/spaces/${encodeURIComponent(settings.spaceId)}/environments/${encodeURIComponent(settings.environmentId || "master")}/${path}`;
|
|
13
|
+
function getOperation(path, type, fields = {}) {
|
|
14
|
+
const schema = createSchema({ id: { type: "string", required: true, minLength: 1, maxLength: 64,
|
|
15
|
+
validator: value => /^[a-zA-Z0-9_-]+$/u.test(value) || "Use the Contentful resource ID." }, ...fields });
|
|
16
|
+
return { scopes: [], request(input, settings) {
|
|
17
|
+
const { id, ...values } = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
18
|
+
const url = new URL(endpoint(settings, `${path}/${encodeURIComponent(id)}`));
|
|
19
|
+
for (const [name, value] of Object.entries(values)) url.searchParams.set(name, String(value));
|
|
20
|
+
return { method: "GET", url: url.href };
|
|
21
|
+
}, validateResult: resource(type) };
|
|
22
|
+
}
|
|
23
|
+
const contentfulProvider = Object.freeze({
|
|
24
|
+
...contentfulDefinition, apiOrigins: ["https://cdn.contentful.com", "https://cdn.eu.contentful.com"],
|
|
25
|
+
apiKey: { headers: key => ({ Authorization: `Bearer ${key}` }) }, checkOperation: "entries.list",
|
|
26
|
+
operations: {
|
|
27
|
+
"entries.list": jsonOperation(settings => endpoint(settings, "entries"), {
|
|
28
|
+
...page, ...localized, content_type: { ...text, maxLength: 64 },
|
|
29
|
+
include: { type: "integer", min: 0, max: 10 }, order: text, query: { ...text, maxLength: 1000 },
|
|
30
|
+
"sys.id": { ...text, maxLength: 64 }
|
|
31
|
+
}, collection("Entry")),
|
|
32
|
+
"entries.get": getOperation("entries", "Entry", localized),
|
|
33
|
+
"assets.list": jsonOperation(settings => endpoint(settings, "assets"), { ...page, ...localized, order: text, query: { ...text, maxLength: 1000 } }, collection("Asset")),
|
|
34
|
+
"assets.get": getOperation("assets", "Asset", localized),
|
|
35
|
+
"contentTypes.list": jsonOperation(settings => endpoint(settings, "content_types"), page, collection("ContentType")),
|
|
36
|
+
"contentTypes.get": getOperation("content_types", "ContentType"),
|
|
37
|
+
"locales.list": jsonOperation(settings => endpoint(settings, "locales"), page, collection("Locale"))
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
export { contentfulProvider };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
2
|
+
import { databricksDefinition } from "../shared/databricks.js";
|
|
3
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
4
|
+
|
|
5
|
+
const origin = (settings) => new URL(settings.workspaceUrl).origin;
|
|
6
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
|
+
const identifier = (value) => Number.isSafeInteger(value) && value > 0;
|
|
8
|
+
const pageToken = { type: "string", minLength: 1, maxLength: 8192 };
|
|
9
|
+
const pagination = (value) => ["next_page_token", "prev_page_token"].every((key) => value[key] === undefined ||
|
|
10
|
+
typeof value[key] === "string" && value[key].length > 0 && value[key].length <= 8192);
|
|
11
|
+
const job = (value) => object(value) && identifier(value.job_id) &&
|
|
12
|
+
(value.settings === undefined || object(value.settings)) && (value.has_more === undefined || typeof value.has_more === "boolean");
|
|
13
|
+
const jobsList = jsonOperation((settings) => `${origin(settings)}/api/2.2/jobs/list`, {
|
|
14
|
+
limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
|
|
15
|
+
expand_tasks: { type: "boolean", defaultTo: false }, name: { type: "string", minLength: 1, maxLength: 4096 }, page_token: pageToken
|
|
16
|
+
}, (value) => object(value) && pagination(value) && (value.jobs === undefined || Array.isArray(value.jobs) && value.jobs.every(job)));
|
|
17
|
+
const jobsGet = jsonOperation((settings) => `${origin(settings)}/api/2.2/jobs/get`, {
|
|
18
|
+
job_id: { type: "integer", required: true, min: 1, max: Number.MAX_SAFE_INTEGER },
|
|
19
|
+
include_trigger_state: { type: "boolean", defaultTo: false }, page_token: pageToken
|
|
20
|
+
}, (value) => job(value) && pagination(value));
|
|
21
|
+
|
|
22
|
+
const databricksProvider = Object.freeze({
|
|
23
|
+
...databricksDefinition,
|
|
24
|
+
oauth: (settings) => ({ issuer: `${origin(settings)}/oidc`, authorization_endpoint: `${origin(settings)}/oidc/v1/authorize`,
|
|
25
|
+
token_endpoint: `${origin(settings)}/oidc/v1/token` }),
|
|
26
|
+
apiOrigins: (settings) => [origin(settings)], checkOperation: "jobs.list", tokenRefreshLeewayMs: 40_000,
|
|
27
|
+
async normalizeTokenResponse(response) {
|
|
28
|
+
if (response.ok) {
|
|
29
|
+
let value;
|
|
30
|
+
try { value = await response.clone().json(); } catch { /* reported below */ }
|
|
31
|
+
if (!Number.isSafeInteger(value?.expires_in) || value.expires_in <= 40 ||
|
|
32
|
+
(value.scope !== undefined && (typeof value.scope !== "string" || !/^[^\s]+(?: [^\s]+)*$/u.test(value.scope))) ||
|
|
33
|
+
(value.refresh_token !== undefined && (typeof value.refresh_token !== "string" || !value.refresh_token.trim()))) {
|
|
34
|
+
throw new ConnectorError("connector_response_invalid", "Databricks returned an invalid token grant.", { statusCode: 502 });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return response;
|
|
38
|
+
},
|
|
39
|
+
operations: { "jobs.list": { ...jobsList, scopes: ["all-apis", "jobs"] }, "jobs.get": { ...jobsGet, scopes: ["all-apis", "jobs"] } },
|
|
40
|
+
async exchange(address, options, { fetchImpl }) {
|
|
41
|
+
const url = new URL(address);
|
|
42
|
+
// An empty jobs list is {}, but JSON null or an unparseable body is not a verified list.
|
|
43
|
+
const response = await fetchImpl(address, { ...options, credentials: "omit" });
|
|
44
|
+
if (!response.ok) throw Object.assign(new Error("Databricks request failed."), { status: response.status });
|
|
45
|
+
let result;
|
|
46
|
+
try { result = await response.json(); } catch { /* checked below */ }
|
|
47
|
+
if (response.status !== 200 || !object(result) || result.error_code || result.error ||
|
|
48
|
+
(url.pathname.endsWith("/list") && result?.jobs?.length > Number(url.searchParams.get("limit"))) ||
|
|
49
|
+
(url.pathname.endsWith("/get") && result?.job_id !== Number(url.searchParams.get("job_id")))) {
|
|
50
|
+
throw new ConnectorError("connector_response_invalid", "Databricks returned an unexpected job response.", { statusCode: 502 });
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
export { databricksProvider };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { dbtSemanticLayerDefinition } from "../shared/dbt-semantic-layer.js";
|
|
4
|
+
import { graphqlOperation } from "./graphqlOperation.js";
|
|
5
|
+
|
|
6
|
+
const origin = (settings) => `https://${settings.host}`;
|
|
7
|
+
const endpoint = (settings) => `${origin(settings)}/api/graphql`;
|
|
8
|
+
const text = (value) => typeof value === "string" && value.length > 0;
|
|
9
|
+
const description = (value) => value === null || typeof value === "string";
|
|
10
|
+
const paging = {
|
|
11
|
+
pageNum: { type: "integer", min: 1, max: 2_147_483_647, defaultTo: 1 },
|
|
12
|
+
pageSize: { type: "integer", min: 1, max: 100, defaultTo: 20 },
|
|
13
|
+
search: { type: "string", maxLength: 512 }
|
|
14
|
+
};
|
|
15
|
+
const pageFields = "pageNum pageSize totalItems totalPages items { name description type }";
|
|
16
|
+
const validPage = (page, typed = true) => Array.isArray(page?.items) && page.items.length <= 100 &&
|
|
17
|
+
page.items.every((item) => text(item?.name) && description(item.description) && (!typed || text(item.type))) &&
|
|
18
|
+
Number.isSafeInteger(page.pageNum) && page.pageNum >= 1 && Number.isSafeInteger(page.pageSize) && page.pageSize >= 1 && page.pageSize <= 100 &&
|
|
19
|
+
Number.isSafeInteger(page.totalItems) && page.totalItems >= page.items.length && Number.isSafeInteger(page.totalPages) && page.totalPages >= 0;
|
|
20
|
+
|
|
21
|
+
function environmentOperation(query, fields, validateData) {
|
|
22
|
+
const operation = graphqlOperation(endpoint, query, fields, validateData);
|
|
23
|
+
return { ...operation, request(input, settings) {
|
|
24
|
+
const request = operation.request(input, settings);
|
|
25
|
+
request.body.variables.environmentId = settings.environmentId;
|
|
26
|
+
return request;
|
|
27
|
+
} };
|
|
28
|
+
}
|
|
29
|
+
const dbtSemanticLayerProvider = Object.freeze({
|
|
30
|
+
...dbtSemanticLayerDefinition,
|
|
31
|
+
apiOrigins: (settings) => [origin(settings)],
|
|
32
|
+
apiKey: { headers: (key) => {
|
|
33
|
+
if (/[\s\p{Cc}]/u.test(key)) throw new ConnectorError("connector_binding_missing", "Use a dbt service token without spaces or control characters.");
|
|
34
|
+
return { Authorization: `Bearer ${key}` };
|
|
35
|
+
} },
|
|
36
|
+
checkOperation: "environment.read",
|
|
37
|
+
operations: {
|
|
38
|
+
"queries.create": environmentOperation("mutation ConnectorDbtQuery($environmentId: BigInt!, $metrics: [MetricInput!]!, $groupBy: [GroupByInput!], $where: [WhereInput!], $limit: Int!) { createQuery(environmentId: $environmentId, metrics: $metrics, groupBy: $groupBy, where: $where, limit: $limit) { queryId } }", {
|
|
39
|
+
metrics: { type: "array", required: true, validator: (value) => (value.length >= 1 && value.length <= 100) || "Choose between 1 and 100 metrics.",
|
|
40
|
+
items: { type: "object", schema: createSchema({ name: { type: "string", required: true, minLength: 1, maxLength: 512 }, alias: { type: "string", minLength: 1, maxLength: 512 } }) } },
|
|
41
|
+
groupBy: { type: "array", validator: (value) => value.length <= 100 || "Choose at most 100 dimensions.",
|
|
42
|
+
items: { type: "object", schema: createSchema({ name: { type: "string", required: true, minLength: 1, maxLength: 512 }, grain: { type: "string", enum: ["DAY", "WEEK", "MONTH", "QUARTER", "YEAR"] } }) } },
|
|
43
|
+
where: { type: "array", validator: (value) => value.length <= 20 || "Use at most 20 filters.",
|
|
44
|
+
items: { type: "object", schema: createSchema({ sql: { type: "string", required: true, minLength: 1, maxLength: 4000 } }) } },
|
|
45
|
+
limit: { type: "integer", min: 1, max: 10000, defaultTo: 100 }
|
|
46
|
+
}, (data) => text(data?.createQuery?.queryId)),
|
|
47
|
+
"queries.get": environmentOperation("query ConnectorDbtQueryResult($environmentId: BigInt!, $queryId: String!, $pageNum: Int!) { query(environmentId: $environmentId, queryId: $queryId, pageNum: $pageNum) { status error totalPages jsonResult(orient: TABLE, encoded: false) } }", {
|
|
48
|
+
queryId: { type: "string", required: true, minLength: 1, maxLength: 512 },
|
|
49
|
+
pageNum: { type: "integer", min: 1, max: 2_147_483_647, defaultTo: 1 }
|
|
50
|
+
}, (data) => text(data?.query?.status) && description(data.query.error) &&
|
|
51
|
+
(data.query.totalPages === null || (Number.isSafeInteger(data.query.totalPages) && data.query.totalPages >= 0)) &&
|
|
52
|
+
(data.query.jsonResult === null || (typeof data.query.jsonResult === "object" && Array.isArray(data.query.jsonResult?.data) && Array.isArray(data.query.jsonResult?.schema?.fields)))),
|
|
53
|
+
"environment.read": environmentOperation("query ConnectorDbtEnvironment($environmentId: BigInt!) { environmentInfo(environmentId: $environmentId) { dialect } }", {},
|
|
54
|
+
(data) => text(data?.environmentInfo?.dialect)),
|
|
55
|
+
"metrics.list": environmentOperation(`query ConnectorDbtMetrics($environmentId: BigInt!, $pageNum: Int!, $pageSize: Int!, $search: String) { metricsPaginated(environmentId: $environmentId, pageNum: $pageNum, pageSize: $pageSize, search: $search) { ${pageFields} } }`, paging,
|
|
56
|
+
(data) => validPage(data?.metricsPaginated)),
|
|
57
|
+
"dimensions.list": environmentOperation(`query ConnectorDbtDimensions($environmentId: BigInt!, $metrics: [MetricInput!]!, $pageNum: Int!, $pageSize: Int!, $search: String) { dimensionsPaginated(environmentId: $environmentId, metrics: $metrics, pageNum: $pageNum, pageSize: $pageSize, search: $search) { ${pageFields} } }`, {
|
|
58
|
+
...paging, metrics: { type: "array", required: true, validator: (value) => (value.length >= 1 && value.length <= 100) || "Choose between 1 and 100 metrics.",
|
|
59
|
+
items: { type: "object", schema: createSchema({ name: { type: "string", required: true, minLength: 1, maxLength: 512 } }) } }
|
|
60
|
+
}, (data) => validPage(data?.dimensionsPaginated)),
|
|
61
|
+
"savedQueries.list": environmentOperation(`query ConnectorDbtSavedQueries($environmentId: BigInt!, $pageNum: Int!, $pageSize: Int!, $search: String) { savedQueriesPaginated(environmentId: $environmentId, pageNum: $pageNum, pageSize: $pageSize, search: $search) { pageNum pageSize totalItems totalPages items { name description } } }`, paging,
|
|
62
|
+
(data) => validPage(data?.savedQueriesPaginated, false))
|
|
63
|
+
},
|
|
64
|
+
async exchange(address, options, { request }) {
|
|
65
|
+
const result = await request(address, options);
|
|
66
|
+
if (Array.isArray(result?.errors) && result.errors.length > 0) {
|
|
67
|
+
throw new ConnectorError("connector_provider_failed", "dbt could not complete the requested operation. Check the environment and service token permissions.", { statusCode: 502 });
|
|
68
|
+
}
|
|
69
|
+
const queryResult = result?.data?.query;
|
|
70
|
+
if (queryResult) {
|
|
71
|
+
if (queryResult.status === "FAILED") {
|
|
72
|
+
throw new ConnectorError("connector_provider_failed", "dbt query failed. Inspect its status in dbt; do not automatically submit it again.", { statusCode: 502 });
|
|
73
|
+
}
|
|
74
|
+
if (queryResult.jsonResult !== null && queryResult.jsonResult !== undefined) {
|
|
75
|
+
if (typeof queryResult.jsonResult !== "string" || Buffer.byteLength(queryResult.jsonResult) > 5 * 1024 * 1024) {
|
|
76
|
+
throw new ConnectorError("connector_response_invalid", "dbt returned an invalid or oversized JSON result page.", { statusCode: 502 });
|
|
77
|
+
}
|
|
78
|
+
try { queryResult.jsonResult = JSON.parse(queryResult.jsonResult); }
|
|
79
|
+
catch { throw new ConnectorError("connector_response_invalid", "dbt returned an invalid JSON result page.", { statusCode: 502 }); }
|
|
80
|
+
if (!Array.isArray(queryResult.jsonResult?.data) || queryResult.jsonResult.data.length > 1024 || !Array.isArray(queryResult.jsonResult?.schema?.fields)) {
|
|
81
|
+
throw new ConnectorError("connector_response_invalid", "dbt returned an invalid JSON table.", { statusCode: 502 });
|
|
82
|
+
}
|
|
83
|
+
} else if (queryResult.status === "SUCCESSFUL") {
|
|
84
|
+
throw new ConnectorError("connector_response_invalid", "dbt completed without a JSON result table.", { statusCode: 502 });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const page = result?.data?.metricsPaginated || result?.data?.dimensionsPaginated || result?.data?.savedQueriesPaginated;
|
|
88
|
+
if (page && (page.pageNum !== options.body.variables.pageNum || page.pageSize !== options.body.variables.pageSize || page.items?.length > options.body.variables.pageSize)) {
|
|
89
|
+
throw new ConnectorError("connector_response_invalid", "dbt returned an unexpected metadata page.", { statusCode: 502 });
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export { dbtSemanticLayerProvider };
|