@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,34 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+
4
+ // Providers with custom paths or bodies share input validation, while retaining
5
+ // ownership of their request construction and response checks.
6
+ function validatedOperation(fields, build, validateResult) {
7
+ const schema = createSchema(fields);
8
+ return {
9
+ scopes: [],
10
+ request(input) {
11
+ return build(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
12
+ },
13
+ validateResult
14
+ };
15
+ }
16
+
17
+ // Provider endpoints with either query parameters or a JSON request body.
18
+ function jsonOperation(url, fields, validateResult, method = "GET") {
19
+ const schema = createSchema(fields);
20
+ return {
21
+ scopes: [],
22
+ request(input, settings = {}) {
23
+ const values = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
24
+ const endpoint = typeof url === "function" ? url(settings) : url;
25
+ if (method === "POST") return { method, url: endpoint, body: values };
26
+ const destination = new URL(endpoint);
27
+ for (const [name, value] of Object.entries(values)) destination.searchParams.set(name, String(value));
28
+ return { method, url: destination.href };
29
+ },
30
+ validateResult
31
+ };
32
+ }
33
+
34
+ export { jsonOperation, validatedOperation };
@@ -0,0 +1,34 @@
1
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
2
+ import { klipyDefinition } from "../shared/tokens.js";
3
+ import { jsonOperation } from "./jsonOperation.js";
4
+
5
+ const mediaFields = {
6
+ page: { type: "integer", min: 1, max: 100000, defaultTo: 1 },
7
+ per_page: { type: "integer", min: 1, max: 50, defaultTo: 24 },
8
+ customer_id: { type: "string", minLength: 1, maxLength: 256 },
9
+ locale: { type: "string", validator: (value) => /^[A-Za-z]{2}$/.test(value) || "Use a two-letter locale such as us." },
10
+ content_filter: { type: "string", enum: ["off", "low", "medium", "high"], defaultTo: "high" }
11
+ };
12
+
13
+ function mediaResult(response) {
14
+ if (response?.result === false) {
15
+ throw new ConnectorError("connector_provider_failed", "KLIPY rejected the operation.", { statusCode: 502 });
16
+ }
17
+ return response?.result === true && Array.isArray(response.data?.data);
18
+ }
19
+
20
+ const klipyProvider = Object.freeze({
21
+ ...klipyDefinition,
22
+ apiOrigins: ["https://api.klipy.com"],
23
+ apiKey: { pathPrefix: (key) => `/api/v1/${encodeURIComponent(key)}` },
24
+ checkOperation: "clips.trending",
25
+ operations: Object.fromEntries(["clips", "gifs", "stickers", "emojis"].flatMap((family) => [
26
+ [`${family}.trending`, jsonOperation(`https://api.klipy.com/${family}/trending`, mediaFields, mediaResult)],
27
+ [`${family}.search`, jsonOperation(`https://api.klipy.com/${family}/search`, {
28
+ ...mediaFields,
29
+ per_page: { type: "integer", min: 8, max: 50, defaultTo: 24 },
30
+ q: { type: "string", required: true, minLength: 1, maxLength: 500 }
31
+ }, mediaResult)]
32
+ ]))
33
+ });
34
+ export { klipyProvider };
@@ -0,0 +1,59 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+ import { lexwareDefinition } from "../shared/tokens.js";
4
+ import { jsonOperation } from "./jsonOperation.js";
5
+
6
+ const id = { type: "string", required: true, validator: value => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value) || "Use a Lexware resource UUID." };
7
+ const text = { type: "string", minLength: 1, maxLength: 1000 };
8
+ const date = { type: "string", required: true, validator: value => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/u.test(value) && Number.isFinite(Date.parse(value)) && new Date(`${value.slice(0, 10)}T00:00:00Z`).toISOString().startsWith(value.slice(0, 10)) || "Use an ISO timestamp with timezone." };
9
+ const paging = { page: { type: "integer", min: 0, max: 100000, defaultTo: 0 }, size: { type: "integer", min: 1, max: 250, defaultTo: 25 } };
10
+ const pageResult = result => Array.isArray(result?.content) && typeof result.first === "boolean" && typeof result.last === "boolean" &&
11
+ ["totalPages", "totalElements", "numberOfElements", "number"].every(field => Number.isInteger(result[field]) && result[field] >= 0) && Number.isInteger(result.size) && result.size > 0;
12
+ const resourceResult = result => typeof result?.id === "string";
13
+ function operation(fields, build, validateResult = resourceResult) {
14
+ const schema = createSchema(fields);
15
+ return { scopes: [], request(input) { return build(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 })); }, validateResult };
16
+ }
17
+ const line = createSchema({
18
+ name: { ...text, required: true }, description: { ...text, maxLength: 5000 },
19
+ quantity: { type: "number", required: true, min: 0.000001, max: 1000000 },
20
+ unitName: { ...text, required: true, maxLength: 100 },
21
+ netAmount: { type: "number", required: true, min: 0, max: 1000000000 },
22
+ taxRatePercentage: { type: "number", required: true, min: 0, max: 100 }
23
+ });
24
+ const lexwareProvider = Object.freeze({
25
+ ...lexwareDefinition,
26
+ apiOrigins: ["https://api.lexware.io"],
27
+ apiKey: { headers: key => ({ Authorization: `Bearer ${key}` }) },
28
+ checkOperation: "contacts.list",
29
+ operations: {
30
+ "contacts.list": jsonOperation("https://api.lexware.io/v1/contacts", {
31
+ ...paging, customer: { type: "boolean" }, vendor: { type: "boolean" }
32
+ }, pageResult),
33
+ ...Object.fromEntries(["contacts", "articles", "invoices", "vouchers"].map(resource => [
34
+ `${resource}.get`, operation({ id }, ({ id }) => ({ method: "GET", url: `https://api.lexware.io/v1/${resource}/${id}` }))
35
+ ])),
36
+ "articles.list": jsonOperation("https://api.lexware.io/v1/articles", {
37
+ ...paging, articleNumber: text, gtin: text, type: { type: "string", enum: ["PRODUCT", "SERVICE"] }
38
+ }, pageResult),
39
+ "vouchers.list": jsonOperation("https://api.lexware.io/v1/voucherlist", {
40
+ ...paging,
41
+ voucherType: { type: "string", required: true, enum: ["any", "invoice", "salesinvoice", "salescreditnote", "purchaseinvoice", "purchasecreditnote"] },
42
+ voucherStatus: { type: "string", required: true, enum: ["any", "draft", "open", "overdue", "paid", "paidoff", "voided", "transferred", "sepadebit", "unchecked"] },
43
+ contactId: { ...id, required: false }, archived: { type: "boolean" }
44
+ }, pageResult),
45
+ "invoices.createDraft": operation({
46
+ contactId: id, voucherDate: date, shippingDate: date,
47
+ shippingType: { type: "string", required: true, enum: ["service", "delivery"] },
48
+ lineItems: { type: "array", required: true, validator: value => value.length > 0 && value.length <= 100 || "Use 1–100 invoice lines.",
49
+ items: { type: "object", schema: line } },
50
+ title: text, introduction: { ...text, maxLength: 5000 }, remark: { ...text, maxLength: 5000 }
51
+ }, ({ contactId, voucherDate, shippingDate, shippingType, lineItems, ...other }) => ({
52
+ method: "POST", url: "https://api.lexware.io/v1/invoices?finalize=false",
53
+ body: { ...other, voucherDate, address: { contactId },
54
+ lineItems: lineItems.map(({ netAmount, taxRatePercentage, ...item }) => ({ ...item, type: "custom", unitPrice: { currency: "EUR", netAmount, taxRatePercentage } })),
55
+ totalPrice: { currency: "EUR" }, taxConditions: { taxType: "net" }, shippingConditions: { shippingDate, shippingType } }
56
+ }))
57
+ }
58
+ });
59
+ export { lexwareProvider };
@@ -0,0 +1,82 @@
1
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
2
+ import { lightspeedDefinition } from "../shared/lightspeed.js";
3
+ import { jsonOperation } from "./jsonOperation.js";
4
+
5
+ const apiOrigin = (settings) => `https://${settings.domainPrefix}.retail.lightspeed.app`;
6
+ const versionNumber = (value) => Number.isSafeInteger(value) && value >= 0;
7
+ const identifier = (value) => typeof value === "string" && /^[a-zA-Z0-9_-]{1,128}$/u.test(value);
8
+
9
+ function collection(resource) {
10
+ const operation = jsonOperation((settings) => `${apiOrigin(settings)}/api/2026-07/${resource}`, {
11
+ after: { type: "integer", min: 0, max: Number.MAX_SAFE_INTEGER },
12
+ before: { type: "integer", min: 0, max: Number.MAX_SAFE_INTEGER },
13
+ page_size: { type: "integer", min: 1, max: 100, defaultTo: 20 },
14
+ ...(resource === "sales" ? {} : { deleted: { type: "boolean", defaultTo: false } })
15
+ }, (result) => {
16
+ if (!Array.isArray(result?.data) || !result.data.every((row) => identifier(row?.id) && versionNumber(row.version))) return false;
17
+ // The customer reference includes a response without the optional envelope version.
18
+ if (resource === "customers" && result.version === undefined) return true;
19
+ const { min, max } = result.version || {};
20
+ return result.data.length === 0 ? min === null && max === null :
21
+ versionNumber(min) && versionNumber(max) && min <= max && result.data.every((row) => row.version >= min && row.version <= max);
22
+ });
23
+ return {
24
+ ...operation, scopes: [`${resource}:read`],
25
+ request(input, settings) {
26
+ const request = operation.request(input, settings);
27
+ const query = new URL(request.url).searchParams;
28
+ if (query.has("after") && query.has("before") && Number(query.get("after")) >= Number(query.get("before"))) {
29
+ throw new ConnectorError("connector_input_invalid", "The lower version must be below the upper version.", { statusCode: 422 });
30
+ }
31
+ return request;
32
+ }
33
+ };
34
+ }
35
+
36
+ const inventoryRead = jsonOperation(settings => `${apiOrigin(settings)}/api/2026-07/inventory`, {
37
+ after: { type: "integer", min: 0, max: Number.MAX_SAFE_INTEGER },
38
+ before: { type: "integer", min: 0, max: Number.MAX_SAFE_INTEGER },
39
+ size: { type: "integer", min: 1, max: 1000, defaultTo: 100 },
40
+ include_deleted: { type: "boolean", defaultTo: false },
41
+ sort_direction: { type: "string", enum: ["asc", "desc"], defaultTo: "asc" },
42
+ product_id: { type: "string", validator: value => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value) || "Use a product UUID." },
43
+ variants: { type: "boolean", defaultTo: false }
44
+ }, result => Array.isArray(result) && result.every(row => row && typeof row === "object" && !Array.isArray(row) && versionNumber(row.version)), "POST");
45
+
46
+ const lightspeedProvider = Object.freeze({
47
+ ...lightspeedDefinition,
48
+ oauth: (settings) => ({ issuer: "https://secure.retail.lightspeed.app", authorization_endpoint: "https://secure.retail.lightspeed.app/connect",
49
+ token_endpoint: `${apiOrigin(settings)}/api/1.0/token` }),
50
+ apiOrigins: (settings) => [apiOrigin(settings)], checkOperation: "products.list",
51
+ async normalizeTokenResponse(response, { settings }) {
52
+ if (response.ok) {
53
+ let value;
54
+ try { value = await response.clone().json(); } catch { /* reported below */ }
55
+ if (value?.domain_prefix !== settings.domainPrefix || typeof value.refresh_token !== "string" || !value.refresh_token.trim() ||
56
+ !Number.isSafeInteger(value.expires_in) || value.expires_in <= 0 ||
57
+ typeof value.scope !== "string" || !/^[^\s]+(?: [^\s]+)*$/u.test(value.scope)) {
58
+ throw new ConnectorError("connector_response_invalid", "Lightspeed returned an invalid token grant or a different store.", { statusCode: 502 });
59
+ }
60
+ }
61
+ return response;
62
+ },
63
+ operations: { "products.list": collection("products"), "customers.list": collection("customers"), "outlets.list": collection("outlets"), "sales.list": collection("sales"),
64
+ "inventory.list": { ...inventoryRead, scopes: ["inventory:read"], request(input, settings) {
65
+ const request = inventoryRead.request(input, settings);
66
+ const { after, before, variants, product_id } = request.body;
67
+ if (after !== undefined && before !== undefined && after >= before || variants && !product_id)
68
+ throw new ConnectorError("connector_input_invalid", "Use ordered inventory version bounds and a product ID when requesting variants.", { statusCode: 422 });
69
+ return request;
70
+ } }
71
+ },
72
+ async exchange(address, options, { request }) {
73
+ const result = await request(address, options);
74
+ const inventory = new URL(address).pathname.endsWith("/inventory");
75
+ const limit = inventory ? options.body.size : Number(new URL(address).searchParams.get("page_size"));
76
+ if ((inventory ? result?.length : result?.data?.length) > limit) {
77
+ throw new ConnectorError("connector_response_invalid", "Lightspeed returned more records than requested.", { statusCode: 502 });
78
+ }
79
+ return result;
80
+ }
81
+ });
82
+ export { lightspeedProvider };
@@ -0,0 +1,52 @@
1
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
2
+ import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
3
+ import { linearDefinition } from "../shared/tokens.js";
4
+ import { graphqlOperation } from "./graphqlOperation.js";
5
+
6
+ function queryOperation(query, fields, validateData) {
7
+ return { ...graphqlOperation("https://api.linear.app/graphql", query, fields, validateData), scopes: ["read"] };
8
+ }
9
+
10
+ const linearProvider = Object.freeze({
11
+ ...linearDefinition, apiOrigins: ["https://api.linear.app", "https://mcp.linear.app"],
12
+ oauth: {
13
+ issuer: "https://linear.app",
14
+ authorization_endpoint: "https://linear.app/oauth/authorize",
15
+ token_endpoint: "https://api.linear.app/oauth/token"
16
+ },
17
+ scopeSeparator: ",",
18
+ authorizationParameters: { actor: "user", prompt: "consent" },
19
+ async normalizeTokenResponse(response) {
20
+ if (!response.ok) return response;
21
+ let value;
22
+ try { value = await response.clone().json(); } catch {
23
+ throw new ConnectorError("connector_response_invalid", "Linear returned an invalid token response.", { statusCode: 502 });
24
+ }
25
+ // Linear accepts commas at authorization but returns space-separated grants.
26
+ const scopes = typeof value?.scope === "string" ? value.scope.split(" ").filter(Boolean) : value?.scope;
27
+ if (!Array.isArray(scopes) || !scopes.every(scope => typeof scope === "string" && /^[^\s,]+$/u.test(scope))) {
28
+ throw new ConnectorError("connector_response_invalid", "Linear returned an invalid permission grant.", { statusCode: 502 });
29
+ }
30
+ return Response.json({ ...value, scope: scopes.join(",") }, { status: response.status });
31
+ },
32
+ apiKey: { headers: (key) => ({ Authorization: key }) },
33
+ checkOperation: "profile.read",
34
+ exchange(address, options, context) {
35
+ if (address === "https://mcp.linear.app/mcp") {
36
+ const headers = context.apiKey === undefined ? options.headers : { ...options.headers, Authorization: `Bearer ${context.apiKey}` };
37
+ return exchangeMcp(address, { ...options, headers }, context);
38
+ }
39
+ return context.request(address, options);
40
+ },
41
+ operations: {
42
+ ...Object.fromEntries(Object.entries(mcpOperations(() => "https://mcp.linear.app/mcp")).map(([name, operation]) =>
43
+ [name, { ...operation, scopes: ["read"] }])),
44
+ "profile.read": queryOperation("query ConnectorViewer { viewer { id name email } }", {},
45
+ (data) => typeof data?.viewer?.id === "string" && typeof data.viewer.name === "string"),
46
+ "issues.list": queryOperation("query ConnectorIssues($first: Int!, $after: String) { issues(first: $first, after: $after) { nodes { id identifier title } pageInfo { hasNextPage endCursor } } }", {
47
+ first: { type: "integer", min: 1, max: 100, defaultTo: 50 },
48
+ after: { type: "string", maxLength: 4096 }
49
+ }, (data) => Array.isArray(data?.issues?.nodes) && typeof data.issues.pageInfo?.hasNextPage === "boolean")
50
+ }
51
+ });
52
+ export { linearProvider };
@@ -0,0 +1,74 @@
1
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
2
+ import { linkedinDefinition } from "../shared/linkedin.js";
3
+ import { jsonOperation } from "./jsonOperation.js";
4
+
5
+ async function normalizeTokenResponse(response, { grantType }) {
6
+ let value;
7
+ try { value = await response.clone().json(); } catch {
8
+ if (!response.ok) return response;
9
+ throw new ConnectorError("connector_response_invalid", "LinkedIn returned an invalid token response.", { statusCode: 502 });
10
+ }
11
+ if (!response.ok) {
12
+ if (grantType === "refresh_token" && response.status === 400 && value?.error === "invalid_request") {
13
+ throw new ConnectorError("connector_reconnect_required", "Connect this LinkedIn account again.", { statusCode: 401 });
14
+ }
15
+ return response;
16
+ }
17
+ if (!value || typeof value !== "object" || Array.isArray(value) ||
18
+ !Number.isSafeInteger(value.expires_in) || value.expires_in <= 0 ||
19
+ (value.scope !== undefined && (typeof value.scope !== "string" || !/^[^\s]+(?: [^\s]+)*$/u.test(value.scope)))) {
20
+ throw new ConnectorError("connector_response_invalid", "LinkedIn returned an invalid permission grant.", { statusCode: 502 });
21
+ }
22
+ if (value.scope !== undefined && !["openid", "profile"].every((scope) => value.scope.split(" ").includes(scope))) {
23
+ throw new ConnectorError("connector_reconnect_required", "Connect LinkedIn with OpenID and profile permissions.", { statusCode: 401 });
24
+ }
25
+ // LinkedIn documents bearer responses without token_type. Identity tokens are
26
+ // unused: this adapter reads userinfo with the access token, never logs users in.
27
+ delete value.id_token;
28
+ return Response.json({ ...value, token_type: value.token_type === undefined ? "Bearer" : value.token_type }, { status: response.status });
29
+ }
30
+
31
+ const publish = jsonOperation("https://api.linkedin.com/v2/ugcPosts", {
32
+ text: { type: "string", required: true, minLength: 1, maxLength: 3000,
33
+ validator: value => value.trim().length > 0 || "Enter the post text." },
34
+ visibility: { type: "string", required: true, enum: ["PUBLIC", "CONNECTIONS"] }
35
+ }, value => /^urn:li:(?:share|ugcPost):[0-9]+$/u.test(value?.id), "POST");
36
+
37
+ const linkedinProvider = Object.freeze({
38
+ ...linkedinDefinition,
39
+ oauth: {
40
+ issuer: "https://www.linkedin.com",
41
+ authorization_endpoint: "https://www.linkedin.com/oauth/v2/authorization",
42
+ token_endpoint: "https://www.linkedin.com/oauth/v2/accessToken"
43
+ },
44
+ normalizeTokenResponse,
45
+ apiOrigins: ["https://api.linkedin.com"], checkOperation: "profile.read",
46
+ async exchange(address, options, { request, fetchImpl }) {
47
+ if (address !== "https://api.linkedin.com/v2/ugcPosts") return request(address, options);
48
+ const member = await request("https://api.linkedin.com/v2/userinfo", { ...options, method: "GET", body: undefined });
49
+ if (typeof member?.sub !== "string" || !/^[a-zA-Z0-9_-]{1,1024}$/u.test(member.sub))
50
+ throw new ConnectorError("connector_response_invalid", "LinkedIn returned an invalid member identity.", { statusCode: 502 });
51
+ const response = await fetchImpl(address, { ...options, credentials: "omit", redirect: "error",
52
+ headers: { ...options.headers, "Content-Type": "application/json", "X-Restli-Protocol-Version": "2.0.0" },
53
+ body: JSON.stringify({ author: `urn:li:person:${member.sub}`, lifecycleState: "PUBLISHED",
54
+ specificContent: { "com.linkedin.ugc.ShareContent": { shareCommentary: { text: options.body.text }, shareMediaCategory: "NONE" } },
55
+ visibility: { "com.linkedin.ugc.MemberNetworkVisibility": options.body.visibility } }) });
56
+ await response.body?.cancel();
57
+ if (!response.ok) throw Object.assign(new Error("LinkedIn publishing failed."), { status: response.status });
58
+ if (response.status !== 201) throw new ConnectorError("connector_response_invalid", "LinkedIn did not confirm post creation.", { statusCode: 502 });
59
+ return { id: response.headers.get("x-restli-id") };
60
+ },
61
+ operations: {
62
+ "posts.create": { ...publish, scopes: ["w_member_social"] },
63
+ "profile.read": {
64
+ ...jsonOperation("https://api.linkedin.com/v2/userinfo", {}, (value) =>
65
+ typeof value?.sub === "string" && value.sub.length > 0 && value.sub.length <= 1024 &&
66
+ ["name", "given_name", "family_name", "picture", "locale", "email"].every((field) =>
67
+ value[field] === undefined || typeof value[field] === "string") &&
68
+ (value.email_verified === undefined || typeof value.email_verified === "boolean")),
69
+ scopes: ["profile"]
70
+ }
71
+ }
72
+ });
73
+
74
+ export { linkedinProvider };
@@ -0,0 +1,76 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+ import { mailgunDefinition } from "../shared/tokens.js";
4
+ import { jsonOperation } from "./jsonOperation.js";
5
+
6
+ const origins = { us: "https://api.mailgun.net", eu: "https://api.eu.mailgun.net" };
7
+ const domainField = { type: "string", required: true, minLength: 3, maxLength: 253,
8
+ pattern: "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$" };
9
+ const domainSchema = createSchema({ domain: domainField });
10
+ const sendSchema = createSchema({
11
+ domain: domainField,
12
+ from: { type: "string", required: true, minLength: 3, maxLength: 320, pattern: "^[^\\r\\n]+$" },
13
+ to: { type: "array", required: true,
14
+ validator: value => (value.length > 0 && value.length <= 1000) || "Provide 1–1000 recipients.",
15
+ items: { type: "string", minLength: 3, maxLength: 320, pattern: "^[^\\s@]+@[^\\s@]+$" } },
16
+ subject: { type: "string", required: true, minLength: 1, maxLength: 998, pattern: "^[^\\r\\n]+$" },
17
+ text: { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 1000000 },
18
+ html: { type: "string", noTrim: true, minLength: 1, maxLength: 1000000 }
19
+ });
20
+ const logsSchema = createSchema({
21
+ domain: domainField,
22
+ duration: { type: "string", defaultTo: "1d", pattern: "^[1-9][0-9]*[dh]$", maxLength: 12 },
23
+ limit: { type: "integer", min: 1, max: 100, defaultTo: 50 },
24
+ token: { type: "string", minLength: 1, maxLength: 8192 },
25
+ end: { type: "string", minLength: 1, maxLength: 100 }
26
+ });
27
+ const validDomain = result => typeof result?.domain?.name === "string" &&
28
+ Array.isArray(result.sending_dns_records) && Array.isArray(result.receiving_dns_records);
29
+
30
+ // Domain access and sending remain separate authorized application operations.
31
+ function domainOperation(method, suffix = "") {
32
+ return { scopes: [], request(input, settings) {
33
+ const { domain } = validateSchemaPayload({ schema: domainSchema, mode: "replace" }, input, { statusCode: 422 });
34
+ return { method, url: `${origins[settings.region]}/v4/domains/${encodeURIComponent(domain)}${suffix}` };
35
+ }, validateResult: validDomain };
36
+ }
37
+
38
+ const mailgunProvider = Object.freeze({
39
+ ...mailgunDefinition, apiOrigins: Object.values(origins),
40
+ apiKey: { headers: (key) => ({ Authorization: `Basic ${Buffer.from(`api:${key}`).toString("base64")}` }) },
41
+ checkOperation: "domains.list",
42
+ operations: {
43
+ "messages.send": { scopes: [], request(input, settings) {
44
+ const { domain, to, ...fields } = validateSchemaPayload({ schema: sendSchema, mode: "replace" }, input, { statusCode: 422 });
45
+ const body = new FormData();
46
+ for (const [name, value] of Object.entries(fields)) body.append(name, value);
47
+ for (const recipient of to) body.append("to", recipient);
48
+ return { method: "POST", url: `${origins[settings.region]}/v3/${encodeURIComponent(domain)}/messages`, body };
49
+ }, validateResult: result => typeof result?.id === "string" && result.id.length > 0 && typeof result.message === "string" },
50
+ "domains.get": domainOperation("GET"),
51
+ "domains.verify": domainOperation("PUT", "/verify"),
52
+ "domains.create": { scopes: [], request(input, settings) {
53
+ const { domain } = validateSchemaPayload({ schema: domainSchema, mode: "replace" }, input, { statusCode: 422 });
54
+ const body = new FormData();
55
+ body.append("name", domain);
56
+ return { method: "POST", url: `${origins[settings.region]}/v4/domains`, body };
57
+ }, validateResult: validDomain },
58
+ "logs.list": { scopes: [], request(input, settings) {
59
+ const { domain, limit, token, ...range } = validateSchemaPayload({ schema: logsSchema, mode: "replace" }, input, { statusCode: 422 });
60
+ return { method: "POST", url: `${origins[settings.region]}/v1/analytics/logs`, body: {
61
+ ...range, include_subaccounts: false,
62
+ filter: { AND: [{ attribute: "domain", comparator: "=", values: [{ label: domain, value: domain }] }] },
63
+ pagination: { limit, sort: "timestamp:asc", ...(token ? { token } : {}) }
64
+ } };
65
+ }, validateResult: result => Array.isArray(result?.items) && !!result.pagination && typeof result.pagination === "object" },
66
+ "domains.list": jsonOperation((settings) => `${origins[settings.region]}/v4/domains`, {
67
+ limit: { type: "integer", min: 1, max: 1000, defaultTo: 100 },
68
+ skip: { type: "integer", min: 0, max: 2147483647, defaultTo: 0 },
69
+ state: { type: "string", enum: ["active", "unverified", "disabled"] },
70
+ sort: { type: "string", enum: ["name", "name:asc", "name:desc"] },
71
+ search: { type: "string", maxLength: 253 },
72
+ include_subaccounts: { type: "boolean", defaultTo: false }
73
+ }, (result) => Array.isArray(result?.items) && Number.isInteger(result.total_count) && result.total_count >= 0)
74
+ }
75
+ });
76
+ export { mailgunProvider };
@@ -0,0 +1,46 @@
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 { mapboxDefinition } from "../shared/mapbox.js";
5
+ import { jsonOperation } from "./jsonOperation.js";
6
+
7
+ const directionsSchema = createSchema({
8
+ profile: { type: "string", enum: ["driving", "driving-traffic", "walking", "cycling"], defaultTo: "driving" },
9
+ coordinates: { type: "array", required: true, validator: points => points.length >= 2 && points.length <= 25 || "Use 2–25 waypoints.",
10
+ items: { type: "object", schema: createSchema({ longitude: { type: "number", required: true, min: -180, max: 180 }, latitude: { type: "number", required: true, min: -90, max: 90 } }) } },
11
+ steps: { type: "boolean", defaultTo: false }
12
+ });
13
+ const geocodingResult = (result) => result?.type === "FeatureCollection" && Array.isArray(result.features) && typeof result.attribution === "string";
14
+ const mapboxProvider = Object.freeze({
15
+ ...mapboxDefinition,
16
+ apiOrigins: ["https://api.mapbox.com"],
17
+ apiKey: { queryParameter: "access_token" },
18
+ checkOperation: "token.read",
19
+ operations: {
20
+ "directions.get": { scopes: [], request(input) {
21
+ const { profile, coordinates, steps } = validateSchemaPayload({ schema: directionsSchema, mode: "replace" }, input, { statusCode: 422 });
22
+ const points = coordinates.map(point => `${point.longitude},${point.latitude}`).join(";");
23
+ return { method: "GET", url: `https://api.mapbox.com/directions/v5/mapbox/${profile}/${points}?geometries=geojson&overview=full&steps=${steps}` };
24
+ }, validateResult: result => ["NoRoute", "NoSegment"].includes(result?.code) || result?.code === "Ok" && Array.isArray(result.routes) && result.routes.every(route =>
25
+ Number.isFinite(route.distance) && route.distance >= 0 && Number.isFinite(route.duration) && route.duration >= 0 && route.geometry?.type === "LineString" && Array.isArray(route.geometry.coordinates)) },
26
+ "token.read": jsonOperation("https://api.mapbox.com/tokens/v2", {}, (result) => {
27
+ if (["TokenMalformed", "TokenInvalid", "TokenExpired", "TokenRevoked"].includes(result?.code)) {
28
+ throw new ConnectorError("connector_reconnect_required", "Configure a valid Mapbox token and connect again.", { statusCode: 401 });
29
+ }
30
+ return result?.code === "TokenValid" && ["pk", "sk", "tk"].includes(result.token?.usage) && typeof result.token.user === "string";
31
+ }),
32
+ "geocoding.forward": jsonOperation("https://api.mapbox.com/search/geocode/v6/forward", {
33
+ q: { type: "string", required: true, minLength: 1, maxLength: 256,
34
+ validator: (value) => !value.includes(";") && (value.match(/[\p{L}\p{N}]+/gu) || []).length <= 20 || "Use at most 20 words or numbers and no semicolons." },
35
+ limit: { type: "integer", min: 1, max: 10, defaultTo: 5 },
36
+ autocomplete: { type: "boolean", defaultTo: false },
37
+ permanent: { type: "boolean", defaultTo: false }
38
+ }, geocodingResult),
39
+ "geocoding.reverse": jsonOperation("https://api.mapbox.com/search/geocode/v6/reverse", {
40
+ longitude: { type: "number", required: true, min: -180, max: 180 },
41
+ latitude: { type: "number", required: true, min: -90, max: 90 },
42
+ permanent: { type: "boolean", defaultTo: false }
43
+ }, geocodingResult)
44
+ }
45
+ });
46
+ export { mapboxProvider };
@@ -0,0 +1,41 @@
1
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
2
+ import { registerClient } from "@modelcontextprotocol/sdk/client/auth.js";
3
+
4
+ const mcpRegistrationFields = {
5
+ clientName: { type: "string", required: true, minLength: 1, maxLength: 200 },
6
+ callbackUrl: { type: "string", required: true, validator(value) {
7
+ try {
8
+ const url = new URL(value);
9
+ return (url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) &&
10
+ !url.username && !url.password && !url.search && !url.hash || "Use an HTTPS callback or local loopback URL, without credentials, query or fragment.";
11
+ } catch { return "Enter the callback URL served by the assistant host."; }
12
+ } }
13
+ };
14
+
15
+ // Provider wrappers validate input; the trusted setup caller owns authorization and secret storage.
16
+ async function registerMcpClient(providerName, metadata, { clientName, callbackUrl, scopes }, { fetchImpl = globalThis.fetch, signal } = {}) {
17
+ const redirectUri = new URL(callbackUrl).href;
18
+ const requestSignal = AbortSignal.any([...(signal ? [signal] : []), AbortSignal.timeout(15000)]);
19
+ try {
20
+ requestSignal.throwIfAborted();
21
+ const client = await registerClient(metadata.issuer, {
22
+ metadata,
23
+ clientMetadata: { client_name: clientName, redirect_uris: [redirectUri], token_endpoint_auth_method: "client_secret_post",
24
+ grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], scope: scopes.join(" ") },
25
+ fetchFn: async (address, init) => {
26
+ if (String(address) !== metadata.registration_endpoint) throw new Error("Unexpected registration destination.");
27
+ return fetchImpl(address, { ...init, credentials: "omit", redirect: "error", signal: requestSignal });
28
+ }
29
+ });
30
+ if (typeof client.client_id !== "string" || !client.client_id.trim() || typeof client.client_secret !== "string" || !client.client_secret.trim() ||
31
+ client.token_endpoint_auth_method !== "client_secret_post" || client.redirect_uris?.length !== 1 || client.redirect_uris[0] !== redirectUri ||
32
+ /[\p{Cc}]/u.test(client.client_id + client.client_secret)) throw new Error("Invalid client registration.");
33
+ return { clientId: client.client_id, clientSecret: client.client_secret,
34
+ ...(client.client_secret_expires_at === undefined ? {} : { clientSecretExpiresAt: client.client_secret_expires_at }) };
35
+ } catch {
36
+ if (requestSignal.aborted) throw new ConnectorError("connector_registration_interrupted", "Registration was interrupted. Check the provider before creating another client.");
37
+ throw new ConnectorError("connector_registration_failed", `${providerName} client registration failed. Check the provider before creating another client.`, { statusCode: 502 });
38
+ }
39
+ }
40
+
41
+ export { mcpRegistrationFields, registerMcpClient };
@@ -0,0 +1,60 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
4
+ import { jsonOperation } from "./jsonOperation.js";
5
+
6
+ function mcpOperations(endpoint) {
7
+ const operation = (method, fields) => {
8
+ const definition = jsonOperation(endpoint, fields, undefined, "POST");
9
+ return { ...definition, request(input, settings) {
10
+ const request = definition.request(input, settings);
11
+ return { ...request, body: { method, params: request.body } };
12
+ } };
13
+ };
14
+ return {
15
+ "tools.list": operation("tools/list", { cursor: { type: "string", minLength: 1, maxLength: 4096 } }),
16
+ "tools.call": operation("tools/call", {
17
+ name: { type: "string", required: true, minLength: 1, maxLength: 128,
18
+ validator: (value) => /^[a-z0-9_.-]+$/iu.test(value) || "Enter an MCP tool name using letters, digits, underscores, dots or hyphens." },
19
+ arguments: { type: "object", required: true }
20
+ })
21
+ };
22
+ }
23
+
24
+ async function exchangeMcp(endpoint, { headers, signal, body }, { fetchImpl }) {
25
+ const client = new Client({ name: "jskit-connectors", version: "0.1.0" }, { capabilities: {} });
26
+ let cleanup = false;
27
+ const transport = new StreamableHTTPClientTransport(new URL(endpoint), {
28
+ reconnectionOptions: { initialReconnectionDelay: 1000, maxReconnectionDelay: 1000, reconnectionDelayGrowFactor: 1, maxRetries: 0 },
29
+ fetch: async (address, init = {}) => {
30
+ if (new URL(String(address)).href !== endpoint) {
31
+ throw new ConnectorError("connector_destination_invalid", "The MCP transport requested an unexpected destination.");
32
+ }
33
+ const requestHeaders = new Headers(init.headers);
34
+ // Keep MCP's Accept/protocol/session headers, and bind credentials to this endpoint only.
35
+ requestHeaders.set("Authorization", headers.Authorization);
36
+ const requestSignal = cleanup ? AbortSignal.timeout(2000) : AbortSignal.any([...(init.signal ? [init.signal] : []), signal]);
37
+ const response = await fetchImpl(address, { ...init, headers: requestHeaders, credentials: "omit", redirect: "error", signal: requestSignal });
38
+ if (!response.ok && !(response.status === 405 && ["GET", "DELETE"].includes(init.method))) {
39
+ await response.body?.cancel();
40
+ const error = new Error("MCP request failed.");
41
+ error.statusCode = response.status;
42
+ throw error;
43
+ }
44
+ return response;
45
+ }
46
+ });
47
+ try {
48
+ signal.throwIfAborted();
49
+ await client.connect(transport, { signal });
50
+ if (body.method === "tools/list") return await client.listTools(body.params, { signal });
51
+ return await client.callTool(body.params, undefined, { signal });
52
+ } finally {
53
+ cleanup = true;
54
+ // Closing a local client alone does not delete a server-side MCP session.
55
+ try { await transport.terminateSession(); } catch { /* Cleanup failure must not replace the operation's result. */ }
56
+ await client.close();
57
+ }
58
+ }
59
+
60
+ export { mcpOperations, exchangeMcp };