@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.
Files changed (399) hide show
  1. package/README.md +192 -0
  2. package/docs/ai-upstream-notices.md +51 -0
  3. package/docs/ai.md +191 -0
  4. package/docs/airtable.md +130 -0
  5. package/docs/algolia.md +190 -0
  6. package/docs/amazon-redshift.md +229 -0
  7. package/docs/amplitude.md +210 -0
  8. package/docs/apify.md +162 -0
  9. package/docs/apollo-io.md +200 -0
  10. package/docs/asana.md +126 -0
  11. package/docs/ashby.md +188 -0
  12. package/docs/atlassian.md +224 -0
  13. package/docs/attention.md +162 -0
  14. package/docs/aws-athena.md +150 -0
  15. package/docs/aws-credentials.md +75 -0
  16. package/docs/aws-s3.md +193 -0
  17. package/docs/bigquery.md +146 -0
  18. package/docs/brevo.md +122 -0
  19. package/docs/calendly.md +158 -0
  20. package/docs/canva.md +191 -0
  21. package/docs/chargebee.md +192 -0
  22. package/docs/clay.md +194 -0
  23. package/docs/clickhouse.md +242 -0
  24. package/docs/confidence-exp.md +74 -0
  25. package/docs/confidence-flags.md +75 -0
  26. package/docs/confidence-oauth.md +137 -0
  27. package/docs/contentful.md +123 -0
  28. package/docs/databricks.md +180 -0
  29. package/docs/dbt-semantic-layer.md +175 -0
  30. package/docs/elevenlabs.md +102 -0
  31. package/docs/figma.md +132 -0
  32. package/docs/firebase-cloud-messaging.md +262 -0
  33. package/docs/firecrawl.md +137 -0
  34. package/docs/fireflies.md +95 -0
  35. package/docs/fireworks-ai.md +53 -0
  36. package/docs/gatewayapi.md +113 -0
  37. package/docs/gemini-enterprise.md +224 -0
  38. package/docs/github-api.md +133 -0
  39. package/docs/gitlab-api.md +138 -0
  40. package/docs/gmail.md +133 -0
  41. package/docs/gong.md +205 -0
  42. package/docs/google-ads.md +156 -0
  43. package/docs/google-analytics.md +202 -0
  44. package/docs/google-docs.md +105 -0
  45. package/docs/google-drive.md +108 -0
  46. package/docs/google-maps-platform.md +208 -0
  47. package/docs/google-oauth.md +123 -0
  48. package/docs/google-search-console.md +124 -0
  49. package/docs/google-sheets.md +94 -0
  50. package/docs/google-slides.md +95 -0
  51. package/docs/granola.md +178 -0
  52. package/docs/hex.md +161 -0
  53. package/docs/heygen.md +124 -0
  54. package/docs/hubspot.md +142 -0
  55. package/docs/incident-io.md +118 -0
  56. package/docs/inngest.md +178 -0
  57. package/docs/klipy.md +149 -0
  58. package/docs/lexware.md +144 -0
  59. package/docs/lightspeed.md +193 -0
  60. package/docs/linear.md +137 -0
  61. package/docs/linkedin.md +196 -0
  62. package/docs/logo-dev.md +152 -0
  63. package/docs/mailgun.md +153 -0
  64. package/docs/mapbox.md +174 -0
  65. package/docs/microsoft-excel.md +97 -0
  66. package/docs/microsoft-fabric.md +249 -0
  67. package/docs/microsoft-oauth.md +143 -0
  68. package/docs/microsoft-onedrive.md +76 -0
  69. package/docs/microsoft-onenote.md +75 -0
  70. package/docs/microsoft-outlook.md +90 -0
  71. package/docs/microsoft-powerpoint.md +119 -0
  72. package/docs/microsoft-sharepoint.md +75 -0
  73. package/docs/microsoft-teams.md +72 -0
  74. package/docs/microsoft-word.md +119 -0
  75. package/docs/miro.md +117 -0
  76. package/docs/n8n.md +169 -0
  77. package/docs/notion.md +137 -0
  78. package/docs/oura.md +161 -0
  79. package/docs/paddle.md +168 -0
  80. package/docs/perplexity.md +85 -0
  81. package/docs/pipedrive.md +120 -0
  82. package/docs/polar.md +63 -0
  83. package/docs/posthog.md +227 -0
  84. package/docs/prestashop.md +151 -0
  85. package/docs/replicate.md +60 -0
  86. package/docs/resend.md +153 -0
  87. package/docs/salesforce.md +234 -0
  88. package/docs/sanity.md +122 -0
  89. package/docs/semrush.md +276 -0
  90. package/docs/sentry.md +112 -0
  91. package/docs/sevdesk.md +107 -0
  92. package/docs/shopify.md +206 -0
  93. package/docs/slack.md +257 -0
  94. package/docs/snowflake.md +271 -0
  95. package/docs/storyblok.md +130 -0
  96. package/docs/stripe.md +77 -0
  97. package/docs/supabase.md +51 -0
  98. package/docs/tally.md +105 -0
  99. package/docs/telegram.md +165 -0
  100. package/docs/tiktok.md +201 -0
  101. package/docs/twilio.md +284 -0
  102. package/docs/twitch.md +428 -0
  103. package/docs/wave.md +440 -0
  104. package/docs/wix.md +1249 -0
  105. package/docs/wiz.md +177 -0
  106. package/docs/woocommerce.md +400 -0
  107. package/docs/wordpress-com.md +358 -0
  108. package/docs/wordpress-self-hosted.md +252 -0
  109. package/docs/workday.md +292 -0
  110. package/docs/x-twitter.md +188 -0
  111. package/docs/xero.md +429 -0
  112. package/docs/zoho-books.md +214 -0
  113. package/docs/zoho-crm.md +192 -0
  114. package/package.json +153 -0
  115. package/patterns/ai-connections/PATTERN.md +84 -0
  116. package/patterns/ai-connections/example/ai-model.js +11 -0
  117. package/patterns/ai-connections/example/perplexity-answer.js +29 -0
  118. package/patterns/api-key-connection/PATTERN.md +368 -0
  119. package/patterns/api-key-connection/example/connections.js +18 -0
  120. package/patterns/api-key-connection/example/integrations.json +19 -0
  121. package/patterns/assistant-mcp/PATTERN.md +110 -0
  122. package/patterns/assistant-mcp/example/integrations.json +13 -0
  123. package/patterns/assistant-mcp-oauth/PATTERN.md +235 -0
  124. package/patterns/assistant-mcp-oauth/example/integrations.json +102 -0
  125. package/patterns/aws-storage-queries/PATTERN.md +169 -0
  126. package/patterns/aws-storage-queries/example/formats/data-formats.js +35 -0
  127. package/patterns/aws-storage-queries/example/formats/package-lock.json +49 -0
  128. package/patterns/aws-storage-queries/example/formats/package.json +12 -0
  129. package/patterns/aws-storage-queries/example/formats/verify-formats.mjs +37 -0
  130. package/patterns/aws-storage-queries/example/integrations.json +38 -0
  131. package/patterns/aws-storage-queries/example/s3-transfer.js +34 -0
  132. package/patterns/event-delivery/PATTERN.md +151 -0
  133. package/patterns/event-delivery/example/integrations.json +17 -0
  134. package/patterns/firebase-messaging/PATTERN.md +156 -0
  135. package/patterns/firebase-messaging/example/server/notifications.js +23 -0
  136. package/patterns/google-ads-search/PATTERN.md +92 -0
  137. package/patterns/google-ads-search/example/ads-setup.js +25 -0
  138. package/patterns/oauth-connection/PATTERN.md +724 -0
  139. package/patterns/oauth-connection/example/integrations.json +20 -0
  140. package/patterns/paddle-catalogue/PATTERN.md +78 -0
  141. package/patterns/paddle-catalogue/example/create-products.js +38 -0
  142. package/patterns/public-image/PATTERN.md +84 -0
  143. package/patterns/public-image/example/integrations.json +13 -0
  144. package/patterns/public-image/example/logo-url.js +16 -0
  145. package/patterns/redshift-queries/PATTERN.md +150 -0
  146. package/patterns/redshift-queries/example/integrations.json +33 -0
  147. package/patterns/source-scanning/PATTERN.md +83 -0
  148. package/patterns/source-scanning/example/source-scanner.js +13 -0
  149. package/src/client/firebase-cloud-messaging.js +10 -0
  150. package/src/client/google-analytics.js +72 -0
  151. package/src/client/google-maps-platform.js +39 -0
  152. package/src/client/logo-dev.js +36 -0
  153. package/src/server/ai.js +46 -0
  154. package/src/server/airtable.js +67 -0
  155. package/src/server/algolia.js +63 -0
  156. package/src/server/amazon-redshift.js +96 -0
  157. package/src/server/amplitude.js +31 -0
  158. package/src/server/apify.js +68 -0
  159. package/src/server/apollo-io.js +85 -0
  160. package/src/server/asana.js +64 -0
  161. package/src/server/ashby.js +61 -0
  162. package/src/server/atlassian.js +32 -0
  163. package/src/server/attention.js +93 -0
  164. package/src/server/aws-athena.js +82 -0
  165. package/src/server/aws-s3.js +49 -0
  166. package/src/server/awsTransport.js +75 -0
  167. package/src/server/bigquery.js +97 -0
  168. package/src/server/brevo.js +97 -0
  169. package/src/server/calendly.js +77 -0
  170. package/src/server/canva.js +13 -0
  171. package/src/server/chargebee.js +88 -0
  172. package/src/server/clay.js +78 -0
  173. package/src/server/clickhouse.js +66 -0
  174. package/src/server/confidence.js +29 -0
  175. package/src/server/contentful.js +40 -0
  176. package/src/server/databricks.js +55 -0
  177. package/src/server/dbt-semantic-layer.js +95 -0
  178. package/src/server/elevenlabs.js +81 -0
  179. package/src/server/figma.js +24 -0
  180. package/src/server/firebase-cloud-messaging.js +111 -0
  181. package/src/server/firecrawl.js +70 -0
  182. package/src/server/fireflies.js +31 -0
  183. package/src/server/fireworks-ai.js +16 -0
  184. package/src/server/gatewayapi.js +48 -0
  185. package/src/server/gemini-enterprise.js +47 -0
  186. package/src/server/github-api.js +90 -0
  187. package/src/server/gitlab-api.js +67 -0
  188. package/src/server/gmail.js +60 -0
  189. package/src/server/gong.js +52 -0
  190. package/src/server/google-ads-search.js +134 -0
  191. package/src/server/google-ads.js +80 -0
  192. package/src/server/google-docs.js +36 -0
  193. package/src/server/google-drive.js +71 -0
  194. package/src/server/google-maps-platform.js +90 -0
  195. package/src/server/google-search-console.js +31 -0
  196. package/src/server/google-sheets.js +60 -0
  197. package/src/server/google-slides.js +36 -0
  198. package/src/server/google.js +61 -0
  199. package/src/server/granola.js +115 -0
  200. package/src/server/graphqlOperation.js +15 -0
  201. package/src/server/hex.js +29 -0
  202. package/src/server/heygen.js +54 -0
  203. package/src/server/hubspot.js +85 -0
  204. package/src/server/incident-io.js +68 -0
  205. package/src/server/inngest.js +86 -0
  206. package/src/server/jsonOperation.js +34 -0
  207. package/src/server/klipy.js +34 -0
  208. package/src/server/lexware.js +59 -0
  209. package/src/server/lightspeed.js +82 -0
  210. package/src/server/linear.js +52 -0
  211. package/src/server/linkedin.js +74 -0
  212. package/src/server/mailgun.js +76 -0
  213. package/src/server/mapbox.js +46 -0
  214. package/src/server/mcpRegistration.js +41 -0
  215. package/src/server/mcpTransport.js +60 -0
  216. package/src/server/microsoft-excel.js +67 -0
  217. package/src/server/microsoft-fabric.js +81 -0
  218. package/src/server/microsoft-onedrive.js +58 -0
  219. package/src/server/microsoft-onenote.js +52 -0
  220. package/src/server/microsoft-outlook.js +58 -0
  221. package/src/server/microsoft-powerpoint.js +5 -0
  222. package/src/server/microsoft-sharepoint.js +46 -0
  223. package/src/server/microsoft-teams.js +29 -0
  224. package/src/server/microsoft-word.js +5 -0
  225. package/src/server/microsoft.js +88 -0
  226. package/src/server/miro.js +29 -0
  227. package/src/server/n8n.js +78 -0
  228. package/src/server/notion.js +76 -0
  229. package/src/server/oura.js +48 -0
  230. package/src/server/paddle.js +83 -0
  231. package/src/server/perplexity.js +15 -0
  232. package/src/server/pipedrive.js +95 -0
  233. package/src/server/polar.js +20 -0
  234. package/src/server/posthog.js +44 -0
  235. package/src/server/prestashop.js +42 -0
  236. package/src/server/replicate.js +42 -0
  237. package/src/server/resend.js +93 -0
  238. package/src/server/salesforce.js +84 -0
  239. package/src/server/sanity.js +31 -0
  240. package/src/server/semrush.js +359 -0
  241. package/src/server/sentry.js +30 -0
  242. package/src/server/sevdesk.js +20 -0
  243. package/src/server/shopify.js +99 -0
  244. package/src/server/slack.js +143 -0
  245. package/src/server/snowflake.js +139 -0
  246. package/src/server/storyblok.js +49 -0
  247. package/src/server/stripe.js +13 -0
  248. package/src/server/supabase.js +13 -0
  249. package/src/server/tally.js +78 -0
  250. package/src/server/telegram.js +55 -0
  251. package/src/server/tiktok.js +86 -0
  252. package/src/server/twilio.js +90 -0
  253. package/src/server/twitch.js +409 -0
  254. package/src/server/wave.js +520 -0
  255. package/src/server/wix.js +1048 -0
  256. package/src/server/wiz.js +117 -0
  257. package/src/server/woocommerce.js +412 -0
  258. package/src/server/wordpress-com.js +236 -0
  259. package/src/server/wordpress-self-hosted.js +134 -0
  260. package/src/server/workday.js +136 -0
  261. package/src/server/x-twitter.js +110 -0
  262. package/src/server/xero.js +397 -0
  263. package/src/server/zoho-books.js +90 -0
  264. package/src/server/zoho-crm.js +89 -0
  265. package/src/server/zohoOAuth.js +21 -0
  266. package/src/shared/ai-models.json +8049 -0
  267. package/src/shared/ai.js +63 -0
  268. package/src/shared/algolia.js +29 -0
  269. package/src/shared/amazon-redshift.js +51 -0
  270. package/src/shared/amplitude.js +28 -0
  271. package/src/shared/atlassian.js +43 -0
  272. package/src/shared/aws.js +72 -0
  273. package/src/shared/canva.js +66 -0
  274. package/src/shared/chargebee.js +26 -0
  275. package/src/shared/clickhouse.js +38 -0
  276. package/src/shared/confidence.js +28 -0
  277. package/src/shared/databricks.js +33 -0
  278. package/src/shared/dbt-semantic-layer.js +32 -0
  279. package/src/shared/definitions.js +140 -0
  280. package/src/shared/figma.js +21 -0
  281. package/src/shared/firebase-cloud-messaging.js +45 -0
  282. package/src/shared/gemini-enterprise.js +33 -0
  283. package/src/shared/gong.js +33 -0
  284. package/src/shared/google-ads-search.js +31 -0
  285. package/src/shared/google-ads.js +28 -0
  286. package/src/shared/google-maps-platform.js +28 -0
  287. package/src/shared/google.js +133 -0
  288. package/src/shared/granola.js +25 -0
  289. package/src/shared/hex.js +28 -0
  290. package/src/shared/inngest.js +35 -0
  291. package/src/shared/lightspeed.js +75 -0
  292. package/src/shared/linkedin.js +27 -0
  293. package/src/shared/logo-dev.js +21 -0
  294. package/src/shared/mapbox.js +38 -0
  295. package/src/shared/mcp.js +92 -0
  296. package/src/shared/microsoft-fabric.js +47 -0
  297. package/src/shared/microsoft.js +231 -0
  298. package/src/shared/miro.js +27 -0
  299. package/src/shared/oura.js +23 -0
  300. package/src/shared/posthog.js +33 -0
  301. package/src/shared/prestashop.js +23 -0
  302. package/src/shared/salesforce.js +36 -0
  303. package/src/shared/semrush.js +24 -0
  304. package/src/shared/shopify.js +51 -0
  305. package/src/shared/siteUrl.js +12 -0
  306. package/src/shared/slack.js +88 -0
  307. package/src/shared/snowflake.js +55 -0
  308. package/src/shared/tiktok.js +22 -0
  309. package/src/shared/tokens.js +747 -0
  310. package/src/shared/twilio.js +41 -0
  311. package/src/shared/twitch.js +49 -0
  312. package/src/shared/wave.js +52 -0
  313. package/src/shared/wix.js +40 -0
  314. package/src/shared/wiz.js +41 -0
  315. package/src/shared/wordpress-com.js +30 -0
  316. package/src/shared/wordpress.js +69 -0
  317. package/src/shared/workday.js +50 -0
  318. package/src/shared/x-twitter.js +19 -0
  319. package/src/shared/xero.js +46 -0
  320. package/src/shared/zoho-books.js +39 -0
  321. package/src/shared/zoho-crm.js +43 -0
  322. package/src/shared/zoho-regions.js +12 -0
  323. package/test/ai.test.js +136 -0
  324. package/test/airtable.test.js +125 -0
  325. package/test/algolia.test.js +207 -0
  326. package/test/amazon-redshift.test.js +272 -0
  327. package/test/amplitude.test.js +239 -0
  328. package/test/apify.test.js +93 -0
  329. package/test/apollo-io.test.js +125 -0
  330. package/test/asana.test.js +99 -0
  331. package/test/ashby.test.js +97 -0
  332. package/test/atlassian.test.js +249 -0
  333. package/test/attention.test.js +79 -0
  334. package/test/aws.test.js +405 -0
  335. package/test/brevo.test.js +71 -0
  336. package/test/calendly.test.js +136 -0
  337. package/test/canva.test.js +258 -0
  338. package/test/chargebee.test.js +195 -0
  339. package/test/clickhouse.test.js +278 -0
  340. package/test/confidence.test.js +205 -0
  341. package/test/databricks.test.js +253 -0
  342. package/test/dbt-semantic-layer.test.js +221 -0
  343. package/test/designMcp.test.js +336 -0
  344. package/test/elevenlabs.test.js +52 -0
  345. package/test/firebase-cloud-messaging.test.js +242 -0
  346. package/test/firecrawl.test.js +66 -0
  347. package/test/gemini-enterprise.test.js +218 -0
  348. package/test/github-api.test.js +167 -0
  349. package/test/gitlab-api.test.js +160 -0
  350. package/test/gong.test.js +192 -0
  351. package/test/google-ads.test.js +350 -0
  352. package/test/google-maps-platform.test.js +191 -0
  353. package/test/google.test.js +710 -0
  354. package/test/granola.test.js +240 -0
  355. package/test/hex.test.js +253 -0
  356. package/test/heygen.test.js +121 -0
  357. package/test/hubspot.test.js +103 -0
  358. package/test/inngest.test.js +219 -0
  359. package/test/lightspeed.test.js +250 -0
  360. package/test/linear.test.js +146 -0
  361. package/test/linkedin.test.js +243 -0
  362. package/test/logo-dev.test.js +79 -0
  363. package/test/mailgun.test.js +107 -0
  364. package/test/mapbox.test.js +181 -0
  365. package/test/mcp.test.js +227 -0
  366. package/test/microsoft-fabric.test.js +258 -0
  367. package/test/microsoft.test.js +554 -0
  368. package/test/n8nOAuth.test.js +188 -0
  369. package/test/notionOAuth.test.js +183 -0
  370. package/test/oura.test.js +211 -0
  371. package/test/paddleCatalogue.test.js +51 -0
  372. package/test/paddlePermissions.test.js +101 -0
  373. package/test/perplexityComposition.test.js +39 -0
  374. package/test/pipedriveOAuth.test.js +140 -0
  375. package/test/posthog.test.js +200 -0
  376. package/test/prestashop.test.js +207 -0
  377. package/test/resend.test.js +163 -0
  378. package/test/salesforce.test.js +276 -0
  379. package/test/semrush.test.js +479 -0
  380. package/test/sentry.test.js +35 -0
  381. package/test/shopify.test.js +247 -0
  382. package/test/slack.test.js +320 -0
  383. package/test/snowflake.test.js +391 -0
  384. package/test/tally.test.js +75 -0
  385. package/test/telegram.test.js +58 -0
  386. package/test/tiktok.test.js +243 -0
  387. package/test/tokens.test.js +1079 -0
  388. package/test/twilio.test.js +221 -0
  389. package/test/twitch.test.js +573 -0
  390. package/test/wave.test.js +678 -0
  391. package/test/wix.test.js +1099 -0
  392. package/test/wiz.test.js +185 -0
  393. package/test/wordpress-com.test.js +469 -0
  394. package/test/wordpress.test.js +660 -0
  395. package/test/workday.test.js +314 -0
  396. package/test/x-twitter.test.js +256 -0
  397. package/test/xero.test.js +700 -0
  398. package/test/zoho-books.test.js +253 -0
  399. package/test/zoho-crm.test.js +223 -0
@@ -0,0 +1,60 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+ import { googleSheetsDefinition } from "../shared/google.js";
4
+ import { googleRead, googleProvider, googleFileVerification, documentId } from "./google.js";
5
+ const origin = "https://sheets.googleapis.com";
6
+ const scopes = ["spreadsheets.readonly", "spreadsheets", "drive.readonly", "drive.file", "drive"];
7
+ const writable = ["spreadsheets", "drive.file", "drive"].map(scope => `https://www.googleapis.com/auth/${scope}`);
8
+ const range = { type: "string", required: true, minLength: 1, maxLength: 2048 };
9
+ const inputOption = { type: "string", enum: ["RAW", "USER_ENTERED"], default: "RAW" };
10
+ const values = { type: "array", required: true };
11
+ const validSheet = value => typeof value?.spreadsheetId === "string" && typeof value.properties?.title === "string";
12
+ const sheetUrl = id => `${origin}/v4/spreadsheets/${encodeURIComponent(id)}`;
13
+ const get = googleRead(scopes, { spreadsheetId: documentId }, ({ spreadsheetId }) => ({ url: sheetUrl(spreadsheetId), query: { includeGridData: false } }), validSheet);
14
+ function write(fields, destination, validateResult) {
15
+ const schema = createSchema(fields);
16
+ return { scopes: writable, validateResult, request(input) {
17
+ return destination(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
18
+ } };
19
+ }
20
+ function checkValues(rows) {
21
+ if (!rows.length || rows.length > 10000 || rows.some(row => !Array.isArray(row) || row.some(cell => cell !== null && typeof cell !== "string" && typeof cell !== "boolean" && !(typeof cell === "number" && Number.isFinite(cell)))) || rows.reduce((count, row) => count + row.length, 0) > 10000 || Buffer.byteLength(JSON.stringify(rows)) > 1048576) {
22
+ throw Object.assign(new Error("Supply a matrix of at most 10,000 cells and 1 MiB, containing strings, finite numbers, booleans or null."), { statusCode: 422 });
23
+ }
24
+ return rows;
25
+ }
26
+ const validUpdate = value => typeof value?.spreadsheetId === "string" && typeof value.updatedRange === "string";
27
+ const base = googleProvider(googleSheetsDefinition, origin, "connection.verify", {
28
+ "connection.verify": googleFileVerification(get, "spreadsheetId"),
29
+ "spreadsheets.get": get,
30
+ "spreadsheets.create": write({ title: { type: "string", required: true, minLength: 1, maxLength: 1024 } }, ({ title }) => ({ method: "POST", url: `${origin}/v4/spreadsheets`, body: { properties: { title } } }), validSheet),
31
+ "values.get": googleRead(scopes, {
32
+ spreadsheetId: documentId, range,
33
+ valueRenderOption: { type: "string", enum: ["FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA"] },
34
+ dateTimeRenderOption: { type: "string", enum: ["SERIAL_NUMBER", "FORMATTED_STRING"] }
35
+ }, ({ spreadsheetId, range, ...query }) => ({ url: `${sheetUrl(spreadsheetId)}/values/${encodeURIComponent(range)}`, query }), value => typeof value?.range === "string" && (value.values === undefined || Array.isArray(value.values))),
36
+ "values.update": write({ spreadsheetId: documentId, range, values, valueInputOption: inputOption }, ({ spreadsheetId, range, values, valueInputOption = "RAW" }) => ({
37
+ method: "PUT", url: `${sheetUrl(spreadsheetId)}/values/${encodeURIComponent(range)}?valueInputOption=${valueInputOption}`,
38
+ body: { range, majorDimension: "ROWS", values: checkValues(values) }
39
+ }), validUpdate),
40
+ "values.append": write({ spreadsheetId: documentId, range, values, valueInputOption: inputOption, insertDataOption: { type: "string", enum: ["INSERT_ROWS", "OVERWRITE"], default: "INSERT_ROWS" } }, ({ spreadsheetId, range, values, valueInputOption = "RAW", insertDataOption = "INSERT_ROWS" }) => ({
41
+ method: "POST", url: `${sheetUrl(spreadsheetId)}/values/${encodeURIComponent(range)}:append?valueInputOption=${valueInputOption}&insertDataOption=${insertDataOption}`,
42
+ body: { range, majorDimension: "ROWS", values: checkValues(values) }
43
+ }), value => typeof value?.spreadsheetId === "string" && validUpdate(value.updates)),
44
+ "values.clear": write({ spreadsheetId: documentId, range }, ({ spreadsheetId, range }) => ({ method: "POST", url: `${sheetUrl(spreadsheetId)}/values/${encodeURIComponent(range)}:clear`, body: {} }), value => typeof value?.spreadsheetId === "string" && typeof value.clearedRange === "string"),
45
+ "values.batchUpdate": write({ spreadsheetId: documentId, data: { type: "array", required: true }, valueInputOption: inputOption }, ({ spreadsheetId, data, valueInputOption = "RAW" }) => {
46
+ const schema = createSchema({ range, values });
47
+ if (!data.length || data.length > 100 || Buffer.byteLength(JSON.stringify(data)) > 1048576) throw Object.assign(new Error("Supply 1–100 ranges within 1 MiB."), { statusCode: 422 });
48
+ const ranges = data.map(value => {
49
+ const parsed = validateSchemaPayload({ schema, mode: "replace" }, value, { statusCode: 422 });
50
+ return { ...parsed, majorDimension: "ROWS", values: checkValues(parsed.values) };
51
+ });
52
+ return { method: "POST", url: `${sheetUrl(spreadsheetId)}/values:batchUpdate`, body: { data: ranges, valueInputOption } };
53
+ }, value => typeof value?.spreadsheetId === "string" && Array.isArray(value.responses)),
54
+ "spreadsheets.batchUpdate": write({ spreadsheetId: documentId, requests: { type: "array", required: true } }, ({ spreadsheetId, requests }) => {
55
+ if (!requests.length || requests.length > 100 || Buffer.byteLength(JSON.stringify(requests)) > 1048576 || requests.some(request => !request || typeof request !== "object" || Array.isArray(request) || Object.keys(request).length !== 1 || !/^[a-zA-Z][a-zA-Z0-9]+$/.test(Object.keys(request)[0]) || !Object.values(request)[0] || typeof Object.values(request)[0] !== "object" || Array.isArray(Object.values(request)[0]))) throw Object.assign(new Error("Supply 1–100 native Sheets request objects, one operation per object, within 1 MiB."), { statusCode: 422 });
56
+ return { method: "POST", url: `${sheetUrl(spreadsheetId)}:batchUpdate`, body: { requests } };
57
+ }, value => typeof value?.spreadsheetId === "string" && (value.replies === undefined || Array.isArray(value.replies)))
58
+ });
59
+ const googleSheetsProvider = Object.freeze({ ...base, apiOrigins: [origin, "https://www.googleapis.com"] });
60
+ export { googleSheetsProvider };
@@ -0,0 +1,36 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+ import { googleSlidesDefinition } from "../shared/google.js";
4
+ import { googleRead, googleProvider, googleFileVerification, documentId } from "./google.js";
5
+ const origin = "https://slides.googleapis.com";
6
+ const readScopes = ["presentations.readonly", "presentations", "drive.readonly", "drive.file", "drive"];
7
+ const writable = ["presentations", "drive.file", "drive"].map(scope => `https://www.googleapis.com/auth/${scope}`);
8
+ const validPresentation = value => typeof value?.presentationId === "string" && typeof value.title === "string";
9
+ const get = googleRead(readScopes, { presentationId: documentId }, ({ presentationId }) => ({
10
+ url: `${origin}/v1/presentations/${encodeURIComponent(presentationId)}`
11
+ }), validPresentation);
12
+ const pageId = { ...documentId, validator: value => /^[a-zA-Z0-9_][a-zA-Z0-9_:-]*$/.test(value) || "Enter a slide object ID, not its URL." };
13
+ const create = createSchema({ title: { type: "string", required: true, minLength: 1, maxLength: 1024 } });
14
+ const batch = createSchema({ presentationId: documentId, requests: { type: "array", required: true }, requiredRevisionId: { type: "string", minLength: 1, maxLength: 1024 } });
15
+ const base = googleProvider(googleSlidesDefinition, origin, "connection.verify", {
16
+ "connection.verify": googleFileVerification(get, "presentationId"),
17
+ "presentations.get": get,
18
+ "presentations.create": { scopes: writable, validateResult: validPresentation, request(input) {
19
+ return { method: "POST", url: `${origin}/v1/presentations`, body: validateSchemaPayload({ schema: create, mode: "replace" }, input, { statusCode: 422 }) };
20
+ } },
21
+ "presentations.batchUpdate": { scopes: writable,
22
+ validateResult: value => typeof value?.presentationId === "string" && (value.replies === undefined || Array.isArray(value.replies)),
23
+ request(input) {
24
+ const { presentationId, requests, requiredRevisionId } = validateSchemaPayload({ schema: batch, mode: "replace" }, input, { statusCode: 422 });
25
+ if (!requests.length || requests.length > 100 || Buffer.byteLength(JSON.stringify(requests)) > 1048576 || requests.some(request => !request || typeof request !== "object" || Array.isArray(request) || Object.keys(request).length !== 1 || !/^[a-zA-Z][a-zA-Z0-9]+$/.test(Object.keys(request)[0]) || !Object.values(request)[0] || typeof Object.values(request)[0] !== "object" || Array.isArray(Object.values(request)[0]))) throw Object.assign(new Error("Supply 1–100 native Slides request objects, one operation per object, within 1 MiB."), { statusCode: 422 });
26
+ return { method: "POST", url: `${origin}/v1/presentations/${encodeURIComponent(presentationId)}:batchUpdate`, body: { requests, ...(requiredRevisionId ? { writeControl: { requiredRevisionId } } : {}) } };
27
+ }
28
+ },
29
+ "pages.get": googleRead(readScopes, { presentationId: documentId, pageObjectId: pageId }, ({ presentationId, pageObjectId }) => ({ url: `${origin}/v1/presentations/${encodeURIComponent(presentationId)}/pages/${encodeURIComponent(pageObjectId)}` }), value => typeof value?.objectId === "string"),
30
+ "pages.getThumbnail": googleRead(readScopes, { presentationId: documentId, pageObjectId: pageId, size: { type: "string", enum: ["SMALL", "MEDIUM", "LARGE"] } }, ({ presentationId, pageObjectId, size }) => ({ url: `${origin}/v1/presentations/${encodeURIComponent(presentationId)}/pages/${encodeURIComponent(pageObjectId)}/thumbnail`, query: { "thumbnailProperties.mimeType": "PNG", "thumbnailProperties.thumbnailSize": size } }), value => {
31
+ if (typeof value?.contentUrl !== "string" || !Number.isFinite(value.width) || !Number.isFinite(value.height) || value.width <= 0 || value.height <= 0) return false;
32
+ try { const url = new URL(value.contentUrl); return url.protocol === "https:" && !url.username && !url.password; } catch { return false; }
33
+ })
34
+ });
35
+ const googleSlidesProvider = Object.freeze({ ...base, apiOrigins: [origin, "https://www.googleapis.com"] });
36
+ export { googleSlidesProvider };
@@ -0,0 +1,61 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+
4
+ const oauth = Object.freeze({
5
+ issuer: "https://accounts.google.com",
6
+ authorization_endpoint: "https://accounts.google.com/o/oauth2/v2/auth",
7
+ token_endpoint: "https://oauth2.googleapis.com/token"
8
+ });
9
+ const googleScope = (name) => name.startsWith("https:") ? name : `https://www.googleapis.com/auth/${name}`;
10
+ const documentId = { type: "string", required: true, minLength: 1, maxLength: 1024, validator: (value) => /^[a-zA-Z0-9_-]+$/u.test(value) || "Enter the document ID from its URL." };
11
+ const pageToken = { type: "string", minLength: 1, maxLength: 4096 };
12
+
13
+ function googleOperation(scopes, fields, request, validateResult) {
14
+ const schema = createSchema(fields);
15
+ return {
16
+ scopes: scopes.map(scope => `https://www.googleapis.com/auth/${scope}`),
17
+ validateResult,
18
+ request(input) {
19
+ return request(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
20
+ }
21
+ };
22
+ }
23
+
24
+ function googleRead(scopes, fields, destination, validateResult) {
25
+ const schema = createSchema(fields);
26
+ return {
27
+ scopes: scopes.map(googleScope), validateResult,
28
+ request(input) {
29
+ const params = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
30
+ const { url: address, query = {} } = destination(params);
31
+ const url = new URL(address);
32
+ for (const [key, value] of Object.entries(query)) {
33
+ if (value !== undefined) {
34
+ for (const item of Array.isArray(value) ? value : [value]) url.searchParams.append(key, String(item));
35
+ }
36
+ }
37
+ return { method: "GET", url: url.href };
38
+ }
39
+ };
40
+ }
41
+
42
+ function googleProvider(definition, origin, checkOperation, operations) {
43
+ return Object.freeze({
44
+ ...definition, oauth, apiOrigins: [origin],
45
+ authorizationParameters: { access_type: "offline", prompt: "consent" },
46
+ checkOperation, operations
47
+ });
48
+ }
49
+ function googleFileVerification(get, field) {
50
+ const schema = createSchema({ [field]: { ...documentId, required: false } });
51
+ return {
52
+ scopes: get.scopes,
53
+ request(input = {}) {
54
+ const params = validateSchemaPayload({ schema, mode: "replace" }, { ...input, ...(input[field] === "" ? { [field]: undefined } : {}) }, { statusCode: 422 });
55
+ if (params[field]) return get.request(params);
56
+ return { method: "GET", url: "https://www.googleapis.com/drive/v3/files?pageSize=1&fields=files(id)&q=trashed%3Dfalse" };
57
+ },
58
+ validateResult: value => get.validateResult(value) || Array.isArray(value?.files)
59
+ };
60
+ }
61
+ export { googleOperation, googleRead, googleProvider, googleFileVerification, documentId, pageToken };
@@ -0,0 +1,115 @@
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 { granolaDefinition } from "../shared/granola.js";
5
+ import { jsonOperation } from "./jsonOperation.js";
6
+ import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
7
+ import { mcpRegistrationFields, registerMcpClient } from "./mcpRegistration.js";
8
+
9
+ const apiOrigin = "https://public-api.granola.ai";
10
+ const noteId = (value) => typeof value === "string" && /^not_[A-Za-z0-9]{14}$/u.test(value);
11
+ const folderId = (value) => typeof value === "string" && /^fol_[A-Za-z0-9]{14}$/u.test(value);
12
+ const cursorValid = (value) => typeof value === "string" && value.length > 0 && value.length <= 8192 && !/[\s\p{Cc}]/u.test(value);
13
+ const cursor = { type: "string", noTrim: true, validator: (value) => cursorValid(value) || "Use the cursor returned by the previous page." };
14
+ const date = { type: "string", noTrim: true, validator: (value) => {
15
+ const match = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z)?$/u.test(value);
16
+ return match && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().startsWith(value.slice(0, 10)) || "Use a valid YYYY-MM-DD date or UTC timestamp.";
17
+ } };
18
+ const noteSummary = (value) => noteId(value?.id) && value.object === "note" && (value.title === null || typeof value.title === "string") &&
19
+ typeof value.owner?.email === "string" && typeof value.created_at === "string" && typeof value.updated_at === "string";
20
+ const transcriptItem = (value) => typeof value?.text === "string" && typeof value.speaker?.source === "string" &&
21
+ typeof value.start_time === "string" && typeof value.end_time === "string";
22
+ const validFolder = (value) => folderId(value?.id) && value.object === "folder" && typeof value.name === "string" &&
23
+ (value.parent_folder_id === null || folderId(value.parent_folder_id));
24
+ const validPage = (value, field, itemValid) => typeof value?.hasMore === "boolean" &&
25
+ (value.cursor === null || cursorValid(value.cursor)) && (!value.hasMore || cursorValid(value.cursor)) &&
26
+ Array.isArray(value[field]) && value[field].every(itemValid);
27
+
28
+ function noteRead(suffix, fields, validateResult) {
29
+ const schema = createSchema({ note_id: { type: "string", required: true, validator: (value) => noteId(value) || "Use the not_ ID returned by notes.list." }, ...fields });
30
+ return {
31
+ scopes: [], validateResult,
32
+ request(input) {
33
+ const { note_id, ...values } = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
34
+ const url = new URL(`${apiOrigin}/v1/notes/${note_id}${suffix}`);
35
+ for (const [name, value] of Object.entries(values)) url.searchParams.set(name, String(value));
36
+ return { method: "GET", url: url.href };
37
+ }
38
+ };
39
+ }
40
+
41
+ const granolaApiProvider = Object.freeze({
42
+ ...granolaDefinition, apiOrigins: [apiOrigin], checkOperation: "notes.list",
43
+ apiKey: { headers: (key) => {
44
+ if (!key.startsWith("grn_") || key.length <= 4 || /[\s\p{Cc}]/u.test(key)) {
45
+ throw new ConnectorError("connector_binding_missing", "Use the Granola grn_ API key without whitespace or control characters.");
46
+ }
47
+ return { Authorization: `Bearer ${key}` };
48
+ } },
49
+ operations: {
50
+ "notes.list": jsonOperation(`${apiOrigin}/v1/notes`, {
51
+ created_before: date, created_after: date, updated_after: date,
52
+ folder_id: { type: "string", validator: (value) => folderId(value) || "Use a fol_ ID returned by folders.list." },
53
+ cursor, page_size: { type: "integer", min: 1, max: 30, defaultTo: 10 }
54
+ }, (value) => validPage(value, "notes", noteSummary)),
55
+ "folders.list": jsonOperation(`${apiOrigin}/v1/folders`, {
56
+ cursor, page_size: { type: "integer", min: 1, max: 30, defaultTo: 10 }
57
+ }, (value) => validPage(value, "folders", validFolder)),
58
+ "notes.get": noteRead("", { include: { type: "string", enum: ["transcript"] } }, (value) => noteSummary(value) &&
59
+ typeof value.summary_text === "string" && (value.summary_markdown === null || typeof value.summary_markdown === "string") &&
60
+ (value.private_notes_text === null || typeof value.private_notes_text === "string") &&
61
+ (value.private_notes_markdown === null || typeof value.private_notes_markdown === "string") &&
62
+ (value.transcript === null || Array.isArray(value.transcript) && value.transcript.every(transcriptItem))),
63
+ "transcripts.list": noteRead("/transcript", {
64
+ cursor, page_size: { type: "integer", min: 1, max: 100, defaultTo: 50 }
65
+ }, (value) => validPage(value, "transcript", transcriptItem))
66
+ },
67
+ async exchange(address, options, { fetchImpl }) {
68
+ const response = await fetchImpl(address, { ...options, credentials: "omit" });
69
+ if (response.status === 404) throw new ConnectorError("connector_resource_not_found", "The Granola note is unavailable, inaccessible or still being processed.", { statusCode: 404 });
70
+ if (response.status === 413) throw new ConnectorError("connector_response_too_large", "Read the transcript in pages with transcripts.list.", { statusCode: 413 });
71
+ if (!response.ok) throw Object.assign(new Error("Granola request failed."), { status: response.status });
72
+ let value;
73
+ try { value = await response.json(); } catch { /* checked below */ }
74
+ if (response.status !== 200 || !value || typeof value !== "object" || value.error) {
75
+ throw new ConnectorError("connector_response_invalid", "Granola returned an unexpected response.", { statusCode: 502 });
76
+ }
77
+ const url = new URL(address);
78
+ const list = url.pathname === "/v1/notes" ? "notes" : url.pathname === "/v1/folders" ? "folders" : url.pathname.endsWith("/transcript") ? "transcript" : null;
79
+ if (list ? value[list]?.length > Number(url.searchParams.get("page_size")) : value.id !== url.pathname.split("/").at(-1)) {
80
+ throw new ConnectorError("connector_response_invalid", "Granola returned data that does not match this request.", { statusCode: 502 });
81
+ }
82
+ return value;
83
+ }
84
+ });
85
+
86
+ const mcpResource = "https://mcp.granola.ai/mcp";
87
+ const oauth = Object.freeze({
88
+ issuer: "https://mcp-auth.granola.ai",
89
+ authorization_endpoint: "https://mcp-auth.granola.ai/oauth2/authorize",
90
+ token_endpoint: "https://mcp-auth.granola.ai/oauth2/token",
91
+ registration_endpoint: "https://mcp-auth.granola.ai/oauth2/register",
92
+ response_types_supported: ["code"], code_challenge_methods_supported: ["S256"]
93
+ });
94
+ const granolaProvider = Object.freeze({
95
+ ...granolaApiProvider, oauth, oauthResource: mcpResource,
96
+ apiOrigins: [apiOrigin, "https://mcp.granola.ai"],
97
+ checkOperation: (method) => method === "oauth2" ? "tools.list" : "notes.list",
98
+ operations: {
99
+ ...Object.fromEntries(Object.entries(granolaApiProvider.operations).map(([name, operation]) =>
100
+ [name, { ...operation, authenticationMethods: ["api-key"] }])),
101
+ ...Object.fromEntries(Object.entries(mcpOperations(() => mcpResource)).map(([name, operation]) =>
102
+ [name, { ...operation, authenticationMethods: ["oauth2"], scopes: ["openid"] }]))
103
+ },
104
+ exchange(address, options, context) {
105
+ return address === mcpResource ? exchangeMcp(address, options, context) : granolaApiProvider.exchange(address, options, context);
106
+ }
107
+ });
108
+ const registrationSchema = createSchema(mcpRegistrationFields);
109
+
110
+ async function registerGranolaClient(input, options) {
111
+ const values = validateSchemaPayload({ schema: registrationSchema, mode: "replace" }, input, { statusCode: 422 });
112
+ return registerMcpClient("Granola", oauth, { ...values, scopes: granolaDefinition.scopes.map((scope) => scope.value) }, options);
113
+ }
114
+
115
+ export { granolaProvider, registerGranolaClient };
@@ -0,0 +1,15 @@
1
+ import { jsonOperation } from "./jsonOperation.js";
2
+
3
+ function graphqlOperation(url, query, fields, validateData) {
4
+ const operation = jsonOperation(url, fields,
5
+ (result) => (result?.errors === undefined || (Array.isArray(result.errors) && result.errors.length === 0)) && validateData(result?.data), "POST");
6
+ return {
7
+ ...operation,
8
+ request(input, settings) {
9
+ const request = operation.request(input, settings);
10
+ return { ...request, body: { query, variables: request.body } };
11
+ }
12
+ };
13
+ }
14
+
15
+ export { graphqlOperation };
@@ -0,0 +1,29 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+ import { hexDefinition, hexHosts, hexEndpointField } from "../shared/hex.js";
4
+ import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
5
+ import { mcpRegistrationFields, registerMcpClient } from "./mcpRegistration.js";
6
+
7
+ const origin = (settings) => `https://${hexHosts[settings.endpoint]}`;
8
+ const resource = (settings) => `${origin(settings)}/mcp`;
9
+ const oauth = (settings) => {
10
+ const issuer = `https://auth.${hexHosts[settings.endpoint]}`;
11
+ return { issuer, authorization_endpoint: `${issuer}/oauth2/authorize`, token_endpoint: `${issuer}/oauth2/token`,
12
+ registration_endpoint: `${issuer}/oauth2/register`, response_types_supported: ["code"], code_challenge_methods_supported: ["S256"] };
13
+ };
14
+ const hexProvider = Object.freeze({
15
+ ...hexDefinition, oauth, oauthResource: resource, apiOrigins: (settings) => [origin(settings)],
16
+ checkOperation: "tools.list", exchange: exchangeMcp,
17
+ operations: Object.fromEntries(Object.entries(mcpOperations(resource)).map(([name, operation]) => [name, { ...operation, scopes: ["openid"] }]))
18
+ });
19
+ const registrationSchema = createSchema({ endpoint: hexEndpointField, ...mcpRegistrationFields,
20
+ scopes: { type: "array", required: true, minLength: 1, items: { type: "string", enum: hexDefinition.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 registerHexClient(input, options) {
25
+ const values = validateSchemaPayload({ schema: registrationSchema, mode: "replace" }, input, { statusCode: 422 });
26
+ return registerMcpClient("Hex", oauth(values), values, options);
27
+ }
28
+
29
+ export { hexProvider, registerHexClient };
@@ -0,0 +1,54 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+ import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
4
+ import { mcpRegistrationFields, registerMcpClient } from "./mcpRegistration.js";
5
+ import { heygenDefinition } from "../shared/tokens.js";
6
+ import { jsonOperation } from "./jsonOperation.js";
7
+
8
+ const heygenApiProvider = Object.freeze({
9
+ ...heygenDefinition, apiOrigins: ["https://api.heygen.com"],
10
+ apiKey: { headers: (key) => ({ "x-api-key": key }) },
11
+ checkOperation: "profile.read",
12
+ operations: {
13
+ "profile.read": jsonOperation("https://api.heygen.com/v3/users/me", {},
14
+ (result) => typeof result?.data?.username === "string"),
15
+ "voices.list": jsonOperation("https://api.heygen.com/v3/voices", {
16
+ limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
17
+ token: { type: "string", maxLength: 4096 },
18
+ type: { type: "string", enum: ["public", "private"], defaultTo: "public" },
19
+ engine: { type: "string", maxLength: 100 },
20
+ language: { type: "string", maxLength: 100 },
21
+ gender: { type: "string", enum: ["male", "female"] }
22
+ }, (result) => Array.isArray(result?.data) && typeof result.has_more === "boolean")
23
+ }
24
+ });
25
+ const mcpEndpoint = "https://mcp.heygen.com/mcp/v1/";
26
+ const oauth = Object.freeze({
27
+ issuer: "https://api2.heygen.com",
28
+ authorization_endpoint: "https://api2.heygen.com/v1/oauth/authorize",
29
+ token_endpoint: "https://api2.heygen.com/v1/oauth/token",
30
+ registration_endpoint: "https://api2.heygen.com/v1/oauth/register",
31
+ response_types_supported: ["code"], code_challenge_methods_supported: ["S256"]
32
+ });
33
+ const heygenProvider = Object.freeze({
34
+ ...heygenApiProvider, oauth, oauthResource: "https://mcp.heygen.com",
35
+ apiOrigins: ["https://api.heygen.com", "https://mcp.heygen.com"],
36
+ checkOperation: (method) => method === "oauth2" ? "tools.list" : "profile.read",
37
+ operations: {
38
+ ...Object.fromEntries(Object.entries(heygenApiProvider.operations).map(([name, operation]) =>
39
+ [name, { ...operation, authenticationMethods: ["api-key"] }])),
40
+ ...Object.fromEntries(Object.entries(mcpOperations(() => mcpEndpoint)).map(([name, operation]) =>
41
+ [name, { ...operation, authenticationMethods: ["oauth2"], scopes: ["openid"] }]))
42
+ },
43
+ exchange(address, options, context) {
44
+ return address === mcpEndpoint ? exchangeMcp(address, options, context) : context.request(address, options);
45
+ }
46
+ });
47
+ const registrationSchema = createSchema(mcpRegistrationFields);
48
+
49
+ async function registerHeyGenClient(input, options) {
50
+ const values = validateSchemaPayload({ schema: registrationSchema, mode: "replace" }, input, { statusCode: 422 });
51
+ return registerMcpClient("HeyGen", oauth, { ...values, scopes: heygenDefinition.scopes.map((scope) => scope.value) }, options);
52
+ }
53
+
54
+ export { heygenProvider, registerHeyGenClient };
@@ -0,0 +1,85 @@
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 { hubspotDefinition } from "../shared/tokens.js";
5
+ import { jsonOperation } from "./jsonOperation.js";
6
+
7
+ const base = "https://api.hubapi.com/crm/objects/2026-09";
8
+ const recordId = { type: "string", required: true, minLength: 1, maxLength: 100,
9
+ validator: value => /^[0-9]+$/u.test(value) || "Use the HubSpot numeric record ID." };
10
+ const properties = { type: "object", required: true, validator: value => {
11
+ const entries = Object.entries(value);
12
+ return entries.length > 0 && entries.length <= 100 && entries.every(([key, item]) =>
13
+ /^[a-zA-Z_][a-zA-Z0-9_]*$/u.test(key) && typeof item === "string") &&
14
+ Buffer.byteLength(JSON.stringify(value)) <= 100000 || "Provide 1–100 named string properties, within 100KB.";
15
+ } };
16
+ const readFields = {
17
+ properties: { type: "string", maxLength: 4096, validator: value => /^[a-zA-Z0-9_,]+$/u.test(value) || "Use comma-separated property names." },
18
+ associations: { type: "string", enum: ["contacts", "deals", "contacts,deals"] },
19
+ archived: { type: "boolean", defaultTo: false }
20
+ };
21
+ const validRecord = value => typeof value?.id === "string" && value.properties && typeof value.properties === "object" && !Array.isArray(value.properties);
22
+ function objectOperations(object) {
23
+ const readScope = `crm.objects.${object}.read`, writeScope = `crm.objects.${object}.write`;
24
+ const read = createSchema({ id: recordId, ...readFields });
25
+ const write = createSchema({ id: recordId, properties });
26
+ const create = createSchema({ properties });
27
+ return {
28
+ [`${object}.list`]: { ...jsonOperation(`${base}/${object}`, {
29
+ limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
30
+ after: { type: "string", maxLength: 4096 }, ...readFields
31
+ }, value => Array.isArray(value?.results)), scopes: [readScope] },
32
+ [`${object}.get`]: { scopes: [readScope], request(input) {
33
+ const { id, ...query } = validateSchemaPayload({ schema: read, mode: "replace" }, input, { statusCode: 422 });
34
+ const url = new URL(`${base}/${object}/${encodeURIComponent(id)}`);
35
+ for (const [key, value] of Object.entries(query)) url.searchParams.set(key, String(value));
36
+ return { method: "GET", url: url.href };
37
+ }, validateResult: validRecord },
38
+ [`${object}.create`]: { scopes: [writeScope], request(input) {
39
+ const body = validateSchemaPayload({ schema: create, mode: "replace" }, input, { statusCode: 422 });
40
+ if (object === "contacts" ? !["email", "firstname", "lastname"].some(key => body.properties[key]?.trim()) :
41
+ !body.properties.dealname?.trim() || !body.properties.dealstage?.trim()) {
42
+ throw new ConnectorError("connector_input_invalid", object === "contacts" ? "Supply email, firstname or lastname." : "Supply dealname and the internal dealstage ID.", { statusCode: 422 });
43
+ }
44
+ return { method: "POST", url: `${base}/${object}`, body };
45
+ }, validateResult: validRecord },
46
+ [`${object}.update`]: { scopes: [writeScope], request(input) {
47
+ const { id, ...body } = validateSchemaPayload({ schema: write, mode: "replace" }, input, { statusCode: 422 });
48
+ return { method: "PATCH", url: `${base}/${object}/${encodeURIComponent(id)}`, body };
49
+ }, validateResult: validRecord }
50
+ };
51
+ }
52
+ const associationSchema = createSchema({ contactId: recordId, dealId: recordId });
53
+
54
+ const hubspotProvider = Object.freeze({
55
+ ...hubspotDefinition, apiOrigins: ["https://api.hubapi.com"],
56
+ apiKey: { headers: (key) => ({ Authorization: `Bearer ${key}` }) },
57
+ oauth: {
58
+ issuer: "https://api.hubspot.com",
59
+ authorization_endpoint: "https://app.hubspot.com/oauth/authorize",
60
+ token_endpoint: "https://api.hubspot.com/oauth/2026-09/token"
61
+ },
62
+ // HubSpot documents confidential-client code grants without PKCE.
63
+ oauthPkce: false,
64
+ async normalizeTokenResponse(response) {
65
+ if (!response.ok) return response;
66
+ let value;
67
+ try { value = await response.clone().json(); } catch {
68
+ throw new ConnectorError("connector_response_invalid", "HubSpot returned an invalid token response.", { statusCode: 502 });
69
+ }
70
+ if (!Array.isArray(value?.scopes) || !value.scopes.every((scope) => typeof scope === "string" && /^[^\s]+$/u.test(scope))) {
71
+ throw new ConnectorError("connector_response_invalid", "HubSpot returned an invalid permission grant.", { statusCode: 502 });
72
+ }
73
+ return Response.json({ ...value, scope: value.scopes.join(" ") }, { status: response.status });
74
+ },
75
+ checkOperation: "contacts.list",
76
+ operations: {
77
+ ...objectOperations("contacts"), ...objectOperations("deals"),
78
+ "deals.associateContact": { scopes: ["crm.objects.contacts.write", "crm.objects.deals.write"], request(input) {
79
+ const { contactId, dealId } = validateSchemaPayload({ schema: associationSchema, mode: "replace" }, input, { statusCode: 422 });
80
+ return { method: "PUT", url: `${base}/deals/${dealId}/associations/default/contacts/${contactId}` };
81
+ }, validateResult: value => Array.isArray(value?.results) },
82
+ "pipelines.list": { ...jsonOperation("https://api.hubapi.com/crm/pipelines/2026-09/deals", {}, value => Array.isArray(value?.results)), scopes: ["crm.objects.deals.read"] }
83
+ }
84
+ });
85
+ export { hubspotProvider };
@@ -0,0 +1,68 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { incidentIoDefinition } from "../shared/tokens.js";
3
+ import { jsonOperation, validatedOperation } from "./jsonOperation.js";
4
+
5
+ const id = { type: "string", required: true, minLength: 1, maxLength: 200,
6
+ validator: value => /^[a-zA-Z0-9_-]+$/u.test(value) || "Use the incident.io resource ID, not a URL." };
7
+ const text = { type: "string", minLength: 1, maxLength: 20000 };
8
+ const optionalId = { ...id, required: false };
9
+ const after = { type: "string", minLength: 1, maxLength: 4096 };
10
+ const page = max => ({ type: "integer", min: 1, max, defaultTo: 25 });
11
+ const result = key => value => typeof value?.[key]?.id === "string";
12
+ const address = path => `https://api.incident.io${path}`;
13
+ const incidentFields = { name: text, summary: text, severity_id: optionalId, incident_status_id: optionalId };
14
+ const followUpFields = { title: { ...text, required: true, maxLength: 1000 }, description: text,
15
+ assignee_id: optionalId, assignee_team_id: optionalId, follow_up_category_id: optionalId, follow_up_priority_option_id: optionalId };
16
+ const literal = value => value && typeof value === "object" && !Array.isArray(value) &&
17
+ Object.keys(value).length === 1 && typeof value.literal === "string" && value.literal.length <= 20000;
18
+ const attributes = { type: "object", required: true, additionalProperties: true,
19
+ validator: value => Object.keys(value).length <= 250 && Buffer.byteLength(JSON.stringify(value)) <= 100000 &&
20
+ Object.entries(value).every(([key, binding]) => /^[a-zA-Z0-9_-]+$/u.test(key) && binding && typeof binding === "object" && !Array.isArray(binding) &&
21
+ Object.keys(binding).every(field => ["value", "array_value"].includes(field)) &&
22
+ (binding.value === undefined || literal(binding.value)) &&
23
+ (binding.array_value === undefined || Array.isArray(binding.array_value) && binding.array_value.length <= 100 && binding.array_value.every(literal))) ||
24
+ "Use at most 250 attributes with literal string value/array_value bindings, within 100KB." };
25
+
26
+ const incidentIoProvider = Object.freeze({
27
+ ...incidentIoDefinition, apiOrigins: ["https://api.incident.io"],
28
+ apiKey: { headers: (key) => ({ Authorization: `Bearer ${key}` }) },
29
+ checkOperation: "incidents.list",
30
+ operations: {
31
+ ...Object.fromEntries([
32
+ ["incidents.get", "/v2/incidents", "incident"], ["followUps.get", "/v2/follow_ups", "follow_up"],
33
+ ["alerts.get", "/v2/alerts", "alert"], ["schedules.get", "/v2/schedules", "schedule"],
34
+ ["catalog.get", "/v3/catalog_entries", "catalog_entry"]
35
+ ].map(([name, path, key]) => [name, validatedOperation({ id }, ({ id }) => ({ method: "GET", url: address(`${path}/${id}`) }), result(key))])),
36
+ "incidents.create": validatedOperation({ ...incidentFields, idempotency_key: { ...text, required: true, maxLength: 200 },
37
+ visibility: { type: "string", required: true, enum: ["public", "private"] },
38
+ mode: { type: "string", enum: ["standard", "retrospective", "test", "tutorial"], defaultTo: "standard" }, incident_type_id: optionalId
39
+ }, body => ({ method: "POST", url: address("/v2/incidents"), body }), result("incident")),
40
+ "incidents.update": validatedOperation({ id, incident: { type: "object", required: true, schema: createSchema(incidentFields),
41
+ validator: value => Object.keys(value).length > 0 || "Provide at least one incident field." },
42
+ notify_incident_channel: { type: "boolean", required: true }
43
+ }, ({ id, ...body }) => ({ method: "POST", url: address(`/v2/incidents/${id}/actions/edit`), body }), result("incident")),
44
+ "followUps.list": jsonOperation(address("/v2/follow_ups"), { incident_id: optionalId, assignee_team_id: optionalId,
45
+ incident_mode: { type: "string", enum: ["standard", "retrospective", "test", "tutorial", "stream"] }
46
+ }, value => Array.isArray(value?.follow_ups)),
47
+ "followUps.create": validatedOperation({ ...followUpFields, incident_id: id }, body => ({ method: "POST", url: address("/v2/follow_ups"), body }), result("follow_up")),
48
+ "followUps.update": validatedOperation({ id, ...followUpFields, status: { type: "string", required: true, enum: ["outstanding", "completed", "deleted", "not_doing"] }
49
+ }, ({ id, ...body }) => ({ method: "PUT", url: address(`/v2/follow_ups/${id}`), body }), result("follow_up")),
50
+ "alerts.list": jsonOperation(address("/v2/alerts"), { page_size: page(50), after }, value => Array.isArray(value?.alerts)),
51
+ "alerts.resolve": validatedOperation({ id }, ({ id }) => ({ method: "POST", url: address(`/v2/alerts/${id}/actions/resolve`), body: {} }), result("alert")),
52
+ "schedules.list": jsonOperation(address("/v2/schedules"), { page_size: page(25), after }, value => Array.isArray(value?.schedules)),
53
+ "catalog.types": jsonOperation(address("/v3/catalog_types"), {}, value => Array.isArray(value?.catalog_types)),
54
+ "catalog.list": jsonOperation(address("/v3/catalog_entries"), { catalog_type_id: id, page_size: page(250), after, identifier: text }, value => Array.isArray(value?.catalog_entries)),
55
+ "catalog.create": validatedOperation({ catalog_type_id: id, name: { ...text, required: true, maxLength: 1000 }, external_id: text,
56
+ attribute_values: attributes
57
+ }, body => ({ method: "POST", url: address("/v3/catalog_entries"), body }), result("catalog_entry")),
58
+ "catalog.update": validatedOperation({ id, name: { ...text, required: true, maxLength: 1000 }, external_id: text,
59
+ attribute_values: attributes
60
+ }, ({ id, ...body }) => ({ method: "PUT", url: address(`/v3/catalog_entries/${id}`), body }), result("catalog_entry")),
61
+ "incidents.list": jsonOperation("https://api.incident.io/v2/incidents", {
62
+ page_size: { type: "integer", min: 1, max: 500, defaultTo: 25 },
63
+ after: { type: "string", maxLength: 4096 },
64
+ sort_by: { type: "string", enum: ["created_at_newest_first", "created_at_oldest_first"], defaultTo: "created_at_newest_first" }
65
+ }, (result) => Array.isArray(result?.incidents) && (result.pagination_meta === undefined || Number.isInteger(result.pagination_meta?.page_size)))
66
+ }
67
+ });
68
+ export { incidentIoProvider };
@@ -0,0 +1,86 @@
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 { inngestDefinition } from "../shared/inngest.js";
5
+ import { jsonOperation } from "./jsonOperation.js";
6
+
7
+ const api = "https://api.inngest.com/v2";
8
+ const eventEndpoint = "https://inn.gs/e/";
9
+ const pageFields = {
10
+ limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
11
+ cursor: { type: "string", minLength: 1, maxLength: 4096 }
12
+ };
13
+ const functionsSchema = createSchema({
14
+ appId: { type: "string", required: true, minLength: 1, maxLength: 255,
15
+ validator: (value) => ![".", ".."].includes(value) && !/[\p{Cc}]/u.test(value) || "Enter a valid Inngest app ID." },
16
+ ...pageFields
17
+ });
18
+ const eventSchema = createSchema({
19
+ name: { type: "string", required: true, minLength: 1, maxLength: 255,
20
+ validator: (value) => Boolean(value.trim()) && !/[\p{Cc}]/u.test(value) || "Enter an event name without control characters." },
21
+ data: { type: "object", required: true },
22
+ id: { type: "string", minLength: 1, maxLength: 512 },
23
+ ts: { type: "integer", min: 315532800000, max: Number.MAX_SAFE_INTEGER },
24
+ v: { type: "string", minLength: 1, maxLength: 255 },
25
+ user: { type: "object" }
26
+ });
27
+
28
+ function validPage(result) {
29
+ return Array.isArray(result?.data) && result.data.every((item) => typeof item?.id === "string" && item.id.length > 0) &&
30
+ typeof result.page?.hasMore === "boolean" && Number.isInteger(result.page.limit) && result.page.limit >= 1 &&
31
+ (result.page.cursor == null || typeof result.page.cursor === "string");
32
+ }
33
+
34
+ const inngestProvider = Object.freeze({
35
+ ...inngestDefinition,
36
+ apiOrigins: ["https://api.inngest.com", "https://inn.gs"],
37
+ apiKey: { headers: (key) => ({ Authorization: `Bearer ${key}` }) },
38
+ checkOperation: "apps.list",
39
+ async exchange(url, options, { request, resolveReference }) {
40
+ if (url !== eventEndpoint) return request(url, options);
41
+ const { eventKeyRef, branchEnvironment, event } = options.body;
42
+ let key;
43
+ try {
44
+ key = await resolveReference(eventKeyRef);
45
+ if (typeof key !== "string" || !key.trim() || /[\p{Cc}]/u.test(key)) throw new Error("Invalid Event Key.");
46
+ } catch {
47
+ throw new ConnectorError("connector_binding_missing", "The Inngest Event Key binding is missing or invalid.");
48
+ }
49
+ options.signal.throwIfAborted();
50
+ // The Event API uses only the Event Key in its path. Never forward the Signing Key.
51
+ return request(`${eventEndpoint}${encodeURIComponent(key)}`, {
52
+ ...options, body: event,
53
+ headers: { Accept: "application/json", ...(branchEnvironment ? { "x-inngest-env": branchEnvironment } : {}) }
54
+ });
55
+ },
56
+ operations: {
57
+ "apps.list": jsonOperation(`${api}/apps`, { ...pageFields, archived: { type: "boolean", defaultTo: false } }, validPage),
58
+ "functions.list": {
59
+ scopes: [],
60
+ request(input) {
61
+ const { appId, ...query } = validateSchemaPayload({ schema: functionsSchema, mode: "replace" }, input, { statusCode: 422 });
62
+ const url = new URL(`${api}/apps/${encodeURIComponent(appId)}/functions`);
63
+ for (const [name, value] of Object.entries(query)) url.searchParams.set(name, value);
64
+ return { method: "GET", url: url.href };
65
+ },
66
+ validateResult: validPage
67
+ },
68
+ "events.send": {
69
+ scopes: [],
70
+ request(input, settings) {
71
+ const event = validateSchemaPayload({ schema: eventSchema, mode: "replace" }, input, { statusCode: 422 });
72
+ return { method: "POST", url: eventEndpoint,
73
+ body: { event, eventKeyRef: settings.eventKeyRef, branchEnvironment: settings.branchEnvironment } };
74
+ },
75
+ validateResult(result) {
76
+ if (result?.status === 401) throw new ConnectorError("connector_reconnect_required", "Configure a valid Inngest Event Key and reconnect.", { statusCode: 401 });
77
+ if (result?.status === 403) throw new ConnectorError("connector_permission_denied", "Inngest denied this event.", { statusCode: 403 });
78
+ if (result?.status === 429) throw new ConnectorError("connector_rate_limited", "Inngest's event limit was reached.", { statusCode: 429 });
79
+ return result?.status === 200 && !result.error && Array.isArray(result.ids) && result.ids.length === 1 &&
80
+ result.ids.every((id) => typeof id === "string" && id.length > 0);
81
+ }
82
+ }
83
+ }
84
+ });
85
+
86
+ export { inngestProvider };