@zackbart/connecta 0.15.1 → 0.16.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 (335) hide show
  1. package/AGENTS.md +40 -20
  2. package/CHANGELOG.md +653 -0
  3. package/README.md +25 -5
  4. package/bin/connecta.mjs +44 -3
  5. package/dist/access-tokens.d.ts +0 -1
  6. package/dist/access-tokens.js +0 -1
  7. package/dist/activity.d.ts +50 -2
  8. package/dist/activity.js +34 -1
  9. package/dist/apps-shell.d.ts +0 -1
  10. package/dist/apps-shell.js +0 -1
  11. package/dist/auth/bearer.d.ts +0 -1
  12. package/dist/auth/bearer.js +0 -1
  13. package/dist/auth/clerk.d.ts +0 -1
  14. package/dist/auth/clerk.js +45 -9
  15. package/dist/auth/downstream-oauth.d.ts +0 -1
  16. package/dist/auth/downstream-oauth.js +0 -1
  17. package/dist/call-admission.d.ts +0 -1
  18. package/dist/call-admission.js +0 -1
  19. package/dist/catalog-drift.d.ts +99 -0
  20. package/dist/catalog-drift.js +251 -0
  21. package/dist/catalog-fingerprint.d.ts +0 -1
  22. package/dist/catalog-fingerprint.js +0 -1
  23. package/dist/catalog-limits.d.ts +0 -1
  24. package/dist/catalog-limits.js +0 -1
  25. package/dist/catalog-service.d.ts +0 -1
  26. package/dist/catalog-service.js +41 -5
  27. package/dist/catalog.d.ts +11 -1
  28. package/dist/catalog.js +20 -1
  29. package/dist/concurrency.d.ts +0 -1
  30. package/dist/concurrency.js +0 -1
  31. package/dist/connector-scope.d.ts +0 -1
  32. package/dist/connector-scope.js +0 -1
  33. package/dist/connectors/api.d.ts +29 -21
  34. package/dist/connectors/api.js +45 -19
  35. package/dist/connectors/guarded-fetch.d.ts +106 -0
  36. package/dist/connectors/guarded-fetch.js +289 -0
  37. package/dist/connectors/remote-mcp.d.ts +0 -1
  38. package/dist/connectors/remote-mcp.js +0 -1
  39. package/dist/credentials.d.ts +0 -1
  40. package/dist/credentials.js +0 -1
  41. package/dist/errors.d.ts +30 -3
  42. package/dist/errors.js +1 -1
  43. package/dist/execute.d.ts +0 -1
  44. package/dist/execute.js +0 -1
  45. package/dist/executor-admission.d.ts +11 -1
  46. package/dist/executor-admission.js +28 -1
  47. package/dist/executor-result.d.ts +0 -1
  48. package/dist/executor-result.js +0 -1
  49. package/dist/executors/quickjs-child.d.ts +0 -1
  50. package/dist/executors/quickjs-child.js +0 -1
  51. package/dist/executors/quickjs-protocol.d.ts +0 -1
  52. package/dist/executors/quickjs-protocol.js +0 -1
  53. package/dist/executors/quickjs-runtime.d.ts +0 -1
  54. package/dist/executors/quickjs-runtime.js +0 -1
  55. package/dist/executors/quickjs.d.ts +0 -1
  56. package/dist/executors/quickjs.js +2 -1
  57. package/dist/favicon.d.ts +0 -1
  58. package/dist/favicon.js +0 -1
  59. package/dist/index.d.ts +2 -3
  60. package/dist/index.js +24 -7
  61. package/dist/invocation.d.ts +0 -1
  62. package/dist/invocation.js +0 -1
  63. package/dist/json-schema.d.ts +0 -1
  64. package/dist/json-schema.js +0 -1
  65. package/dist/mcp-result.d.ts +0 -1
  66. package/dist/mcp-result.js +0 -1
  67. package/dist/meta-tools.d.ts +0 -1
  68. package/dist/meta-tools.js +0 -1
  69. package/dist/node.d.ts +0 -1
  70. package/dist/node.js +0 -1
  71. package/dist/operator-ui/generated.d.ts +2 -3
  72. package/dist/operator-ui/generated.js +3 -4
  73. package/dist/operator-ui/model.d.ts +10 -1
  74. package/dist/operator-ui/model.js +0 -1
  75. package/dist/operator-ui/view.d.ts +167 -0
  76. package/dist/operator-ui/view.js +270 -0
  77. package/dist/providers/cloudflare.d.ts +0 -1
  78. package/dist/providers/cloudflare.js +261 -299
  79. package/dist/providers/linear.d.ts +26 -5
  80. package/dist/providers/linear.js +32 -61
  81. package/dist/providers/mixpanel.d.ts +35 -4
  82. package/dist/providers/mixpanel.js +53 -79
  83. package/dist/providers/notion.d.ts +0 -1
  84. package/dist/providers/notion.js +87 -42
  85. package/dist/providers/stripe.d.ts +13 -1
  86. package/dist/providers/stripe.js +31 -60
  87. package/dist/registry.d.ts +34 -2
  88. package/dist/registry.js +85 -5
  89. package/dist/retired-toolkits.d.ts +0 -1
  90. package/dist/retired-toolkits.js +0 -1
  91. package/dist/routes/access-tokens.d.ts +0 -1
  92. package/dist/routes/access-tokens.js +0 -1
  93. package/dist/routes/activity.d.ts +0 -1
  94. package/dist/routes/activity.js +0 -1
  95. package/dist/routes/credentials.d.ts +0 -1
  96. package/dist/routes/credentials.js +0 -1
  97. package/dist/routes/mcp.d.ts +0 -1
  98. package/dist/routes/mcp.js +0 -1
  99. package/dist/routes/oauth.d.ts +0 -1
  100. package/dist/routes/oauth.js +0 -1
  101. package/dist/routes/shared.d.ts +2 -1
  102. package/dist/routes/shared.js +0 -1
  103. package/dist/routes/ui.d.ts +0 -1
  104. package/dist/routes/ui.js +0 -1
  105. package/dist/server.d.ts +0 -1
  106. package/dist/server.js +11 -1
  107. package/dist/skills.d.ts +0 -1
  108. package/dist/skills.js +0 -1
  109. package/dist/storage/file.d.ts +0 -1
  110. package/dist/storage/file.js +0 -1
  111. package/dist/storage/memory.d.ts +0 -1
  112. package/dist/storage/memory.js +0 -1
  113. package/dist/timeout.d.ts +0 -1
  114. package/dist/timeout.js +0 -1
  115. package/dist/tool-safety.d.ts +0 -1
  116. package/dist/tool-safety.js +0 -1
  117. package/dist/types.d.ts +45 -1
  118. package/dist/types.js +0 -1
  119. package/dist/ui.d.ts +0 -1
  120. package/dist/ui.js +13 -137
  121. package/dist/validate.d.ts +13 -15
  122. package/dist/validate.js +24 -11
  123. package/dist/version.d.ts +1 -2
  124. package/dist/version.js +1 -2
  125. package/documentation/architecture.md +186 -5
  126. package/documentation/auth.md +11 -0
  127. package/documentation/call-admission.md +142 -6
  128. package/documentation/cloudflare.md +92 -10
  129. package/documentation/code-first-exploration.md +2 -2
  130. package/documentation/code-mode.md +14 -14
  131. package/documentation/connector-guides.md +172 -5
  132. package/documentation/connectors.md +89 -0
  133. package/documentation/linear.md +20 -2
  134. package/documentation/mcp-ui-design.md +2 -2
  135. package/documentation/meta-tools.md +7 -1
  136. package/documentation/mixpanel.md +42 -9
  137. package/documentation/notion.md +23 -2
  138. package/documentation/operations.md +325 -5
  139. package/documentation/operator-ui.md +120 -5
  140. package/documentation/provider-audit.md +160 -0
  141. package/documentation/provider-conventions.md +667 -0
  142. package/documentation/request-admission.md +167 -5
  143. package/documentation/rich-output-design.md +1 -2
  144. package/documentation/stripe.md +15 -0
  145. package/documentation/upgrading.md +454 -0
  146. package/ethos.md +14 -9
  147. package/examples/worker/README.md +102 -12
  148. package/examples/worker/src/index.ts +46 -1
  149. package/examples/worker/wrangler.jsonc +14 -1
  150. package/package.json +10 -6
  151. package/templates/node/.dockerignore +10 -0
  152. package/templates/node/.env.example +40 -3
  153. package/templates/node/AGENTS.md +16 -1
  154. package/templates/node/Dockerfile +49 -0
  155. package/templates/node/README.md +117 -2
  156. package/templates/node/docker-compose.yml +39 -0
  157. package/templates/node/package.json +1 -1
  158. package/templates/node/src/file-activity.ts +182 -0
  159. package/templates/node/src/index.ts +80 -4
  160. package/assets/connecta-clay-hero.png +0 -0
  161. package/dist/access-tokens.d.ts.map +0 -1
  162. package/dist/access-tokens.js.map +0 -1
  163. package/dist/activity.d.ts.map +0 -1
  164. package/dist/activity.js.map +0 -1
  165. package/dist/apps-shell.d.ts.map +0 -1
  166. package/dist/apps-shell.js.map +0 -1
  167. package/dist/auth/bearer.d.ts.map +0 -1
  168. package/dist/auth/bearer.js.map +0 -1
  169. package/dist/auth/clerk.d.ts.map +0 -1
  170. package/dist/auth/clerk.js.map +0 -1
  171. package/dist/auth/downstream-oauth.d.ts.map +0 -1
  172. package/dist/auth/downstream-oauth.js.map +0 -1
  173. package/dist/call-admission.d.ts.map +0 -1
  174. package/dist/call-admission.js.map +0 -1
  175. package/dist/catalog-fingerprint.d.ts.map +0 -1
  176. package/dist/catalog-fingerprint.js.map +0 -1
  177. package/dist/catalog-limits.d.ts.map +0 -1
  178. package/dist/catalog-limits.js.map +0 -1
  179. package/dist/catalog-service.d.ts.map +0 -1
  180. package/dist/catalog-service.js.map +0 -1
  181. package/dist/catalog.d.ts.map +0 -1
  182. package/dist/catalog.js.map +0 -1
  183. package/dist/concurrency.d.ts.map +0 -1
  184. package/dist/concurrency.js.map +0 -1
  185. package/dist/connector-scope.d.ts.map +0 -1
  186. package/dist/connector-scope.js.map +0 -1
  187. package/dist/connectors/api.d.ts.map +0 -1
  188. package/dist/connectors/api.js.map +0 -1
  189. package/dist/connectors/remote-mcp.d.ts.map +0 -1
  190. package/dist/connectors/remote-mcp.js.map +0 -1
  191. package/dist/credentials.d.ts.map +0 -1
  192. package/dist/credentials.js.map +0 -1
  193. package/dist/errors.d.ts.map +0 -1
  194. package/dist/errors.js.map +0 -1
  195. package/dist/execute.d.ts.map +0 -1
  196. package/dist/execute.js.map +0 -1
  197. package/dist/executor-admission.d.ts.map +0 -1
  198. package/dist/executor-admission.js.map +0 -1
  199. package/dist/executor-result.d.ts.map +0 -1
  200. package/dist/executor-result.js.map +0 -1
  201. package/dist/executors/quickjs-child.d.ts.map +0 -1
  202. package/dist/executors/quickjs-child.js.map +0 -1
  203. package/dist/executors/quickjs-protocol.d.ts.map +0 -1
  204. package/dist/executors/quickjs-protocol.js.map +0 -1
  205. package/dist/executors/quickjs-runtime.d.ts.map +0 -1
  206. package/dist/executors/quickjs-runtime.js.map +0 -1
  207. package/dist/executors/quickjs.d.ts.map +0 -1
  208. package/dist/executors/quickjs.js.map +0 -1
  209. package/dist/favicon.d.ts.map +0 -1
  210. package/dist/favicon.js.map +0 -1
  211. package/dist/index.d.ts.map +0 -1
  212. package/dist/index.js.map +0 -1
  213. package/dist/invocation.d.ts.map +0 -1
  214. package/dist/invocation.js.map +0 -1
  215. package/dist/json-schema.d.ts.map +0 -1
  216. package/dist/json-schema.js.map +0 -1
  217. package/dist/mcp-result.d.ts.map +0 -1
  218. package/dist/mcp-result.js.map +0 -1
  219. package/dist/meta-tools.d.ts.map +0 -1
  220. package/dist/meta-tools.js.map +0 -1
  221. package/dist/node.d.ts.map +0 -1
  222. package/dist/node.js.map +0 -1
  223. package/dist/operator-ui/generated.d.ts.map +0 -1
  224. package/dist/operator-ui/generated.js.map +0 -1
  225. package/dist/operator-ui/model.d.ts.map +0 -1
  226. package/dist/operator-ui/model.js.map +0 -1
  227. package/dist/providers/cloudflare.d.ts.map +0 -1
  228. package/dist/providers/cloudflare.js.map +0 -1
  229. package/dist/providers/linear.d.ts.map +0 -1
  230. package/dist/providers/linear.js.map +0 -1
  231. package/dist/providers/mixpanel.d.ts.map +0 -1
  232. package/dist/providers/mixpanel.js.map +0 -1
  233. package/dist/providers/notion.d.ts.map +0 -1
  234. package/dist/providers/notion.js.map +0 -1
  235. package/dist/providers/stripe.d.ts.map +0 -1
  236. package/dist/providers/stripe.js.map +0 -1
  237. package/dist/registry.d.ts.map +0 -1
  238. package/dist/registry.js.map +0 -1
  239. package/dist/retired-toolkits.d.ts.map +0 -1
  240. package/dist/retired-toolkits.js.map +0 -1
  241. package/dist/routes/access-tokens.d.ts.map +0 -1
  242. package/dist/routes/access-tokens.js.map +0 -1
  243. package/dist/routes/activity.d.ts.map +0 -1
  244. package/dist/routes/activity.js.map +0 -1
  245. package/dist/routes/credentials.d.ts.map +0 -1
  246. package/dist/routes/credentials.js.map +0 -1
  247. package/dist/routes/mcp.d.ts.map +0 -1
  248. package/dist/routes/mcp.js.map +0 -1
  249. package/dist/routes/oauth.d.ts.map +0 -1
  250. package/dist/routes/oauth.js.map +0 -1
  251. package/dist/routes/shared.d.ts.map +0 -1
  252. package/dist/routes/shared.js.map +0 -1
  253. package/dist/routes/ui.d.ts.map +0 -1
  254. package/dist/routes/ui.js.map +0 -1
  255. package/dist/server.d.ts.map +0 -1
  256. package/dist/server.js.map +0 -1
  257. package/dist/skills.d.ts.map +0 -1
  258. package/dist/skills.js.map +0 -1
  259. package/dist/storage/file.d.ts.map +0 -1
  260. package/dist/storage/file.js.map +0 -1
  261. package/dist/storage/memory.d.ts.map +0 -1
  262. package/dist/storage/memory.js.map +0 -1
  263. package/dist/timeout.d.ts.map +0 -1
  264. package/dist/timeout.js.map +0 -1
  265. package/dist/tool-safety.d.ts.map +0 -1
  266. package/dist/tool-safety.js.map +0 -1
  267. package/dist/types.d.ts.map +0 -1
  268. package/dist/types.js.map +0 -1
  269. package/dist/ui.d.ts.map +0 -1
  270. package/dist/ui.js.map +0 -1
  271. package/dist/validate.d.ts.map +0 -1
  272. package/dist/validate.js.map +0 -1
  273. package/dist/version.d.ts.map +0 -1
  274. package/dist/version.js.map +0 -1
  275. package/examples/node/README.md +0 -52
  276. package/examples/node/src/index.ts +0 -71
  277. package/src/access-tokens.ts +0 -289
  278. package/src/activity.ts +0 -234
  279. package/src/apps-shell.ts +0 -370
  280. package/src/auth/bearer.ts +0 -61
  281. package/src/auth/clerk.ts +0 -553
  282. package/src/auth/downstream-oauth.ts +0 -575
  283. package/src/call-admission.ts +0 -521
  284. package/src/catalog-fingerprint.ts +0 -52
  285. package/src/catalog-limits.ts +0 -11
  286. package/src/catalog-service.ts +0 -1153
  287. package/src/catalog.ts +0 -790
  288. package/src/concurrency.ts +0 -39
  289. package/src/connector-scope.ts +0 -66
  290. package/src/connectors/api.ts +0 -159
  291. package/src/connectors/remote-mcp.ts +0 -1090
  292. package/src/credentials.ts +0 -428
  293. package/src/errors.ts +0 -364
  294. package/src/execute.ts +0 -1406
  295. package/src/executor-admission.ts +0 -328
  296. package/src/executor-result.ts +0 -107
  297. package/src/executors/quickjs-child.ts +0 -172
  298. package/src/executors/quickjs-protocol.ts +0 -99
  299. package/src/executors/quickjs-runtime.ts +0 -470
  300. package/src/executors/quickjs.ts +0 -776
  301. package/src/favicon.ts +0 -53
  302. package/src/index.ts +0 -666
  303. package/src/invocation.ts +0 -655
  304. package/src/json-schema.ts +0 -11
  305. package/src/mcp-result.ts +0 -43
  306. package/src/meta-tools.ts +0 -1607
  307. package/src/node.ts +0 -206
  308. package/src/operator-ui/browser.css +0 -423
  309. package/src/operator-ui/browser.ts +0 -1281
  310. package/src/operator-ui/generated.ts +0 -4
  311. package/src/operator-ui/model.ts +0 -111
  312. package/src/providers/cloudflare.ts +0 -3999
  313. package/src/providers/linear.ts +0 -302
  314. package/src/providers/mixpanel.ts +0 -229
  315. package/src/providers/notion.ts +0 -1879
  316. package/src/providers/stripe.ts +0 -307
  317. package/src/registry.ts +0 -1085
  318. package/src/retired-toolkits.ts +0 -14
  319. package/src/routes/access-tokens.ts +0 -115
  320. package/src/routes/activity.ts +0 -215
  321. package/src/routes/credentials.ts +0 -254
  322. package/src/routes/mcp.ts +0 -429
  323. package/src/routes/oauth.ts +0 -322
  324. package/src/routes/shared.ts +0 -247
  325. package/src/routes/ui.ts +0 -152
  326. package/src/server.ts +0 -195
  327. package/src/skills.ts +0 -271
  328. package/src/storage/file.ts +0 -123
  329. package/src/storage/memory.ts +0 -44
  330. package/src/timeout.ts +0 -84
  331. package/src/tool-safety.ts +0 -15
  332. package/src/types.ts +0 -523
  333. package/src/ui.ts +0 -744
  334. package/src/validate.ts +0 -387
  335. package/src/version.ts +0 -7
package/src/routes/ui.ts DELETED
@@ -1,152 +0,0 @@
1
- import { CONNECTA_FAVICON_ICO } from "../favicon.js";
2
- import {
3
- buildUiData,
4
- CONNECTA_FAVICON_SVG,
5
- credentialManagementCapability,
6
- operatorPageForPath,
7
- renderUiHtml,
8
- } from "../ui.js";
9
- import {
10
- authorize,
11
- privateJson,
12
- type RouteContext,
13
- } from "./shared.js";
14
-
15
- /**
16
- * Headers that make an operator-supplied favicon body inert on this origin.
17
- * The SVG route is the sharp one: `image/svg+xml` is an *active* content type,
18
- * so a `<script>` inside a branding SVG would run on the deployment origin the
19
- * moment anyone navigated straight to `/favicon.svg` — strictly more powerful
20
- * than the `favicon.href` vector the branding gates close, because the payload
21
- * is same-origin. Neutralizing the response rather than inspecting the body
22
- * keeps every valid static SVG (the built-in mark included) byte-identical:
23
- *
24
- * - `sandbox` (no tokens ⇒ every restriction) drops the document into an opaque
25
- * origin with scripting off, so even a script that ran would have nothing to
26
- * reach.
27
- * - `default-src 'none'` denies script, network, and framing outright.
28
- * - `style-src 'unsafe-inline'` is the single allowance: the default mark styles
29
- * itself inline to follow the OS colour scheme, and CSS cannot script.
30
- * - `nosniff` keeps the declared type authoritative in both directions — an SVG
31
- * can never be re-read as HTML, and `.ico` bytes can never be re-read as SVG.
32
- *
33
- * `.ico` bodies are deliberately in scope: they are inert bytes rather than
34
- * active content, so they are still served verbatim, but they carry the same
35
- * headers so the invariant is "every favicon route is neutralized" rather than
36
- * "whichever route got attention".
37
- */
38
- const INERT_ICON_HEADERS = {
39
- "Content-Security-Policy":
40
- "default-src 'none'; style-src 'unsafe-inline'; sandbox",
41
- "X-Content-Type-Options": "nosniff",
42
- };
43
-
44
- /** Per-request base64 nonce for an operator shell's scripts (Node 20+ and Workers). */
45
- function uiScriptNonce(): string {
46
- const bytes = crypto.getRandomValues(new Uint8Array(16));
47
- let binary = "";
48
- for (const byte of bytes) binary += String.fromCharCode(byte);
49
- return btoa(binary);
50
- }
51
-
52
- export async function routeUi(
53
- context: RouteContext,
54
- ): Promise<Response | null> {
55
- const { request, url, path, baseUrl, opts, defer } = context;
56
- if (request.method === "GET" && path === "/favicon.svg") {
57
- return new Response(opts.branding?.favicon?.svg ?? CONNECTA_FAVICON_SVG, {
58
- headers: {
59
- "Content-Type": "image/svg+xml",
60
- "Cache-Control": "public, max-age=86400",
61
- ...INERT_ICON_HEADERS,
62
- },
63
- });
64
- }
65
- if (request.method === "GET" && path === "/favicon.ico") {
66
- return new Response(opts.branding?.favicon?.ico ?? CONNECTA_FAVICON_ICO, {
67
- headers: {
68
- "Content-Type": "image/x-icon",
69
- "Cache-Control": "public, max-age=86400",
70
- ...INERT_ICON_HEADERS,
71
- },
72
- });
73
- }
74
- if (path === "/ui") {
75
- if (request.method !== "GET") {
76
- return privateJson({ error: "method not allowed" }, { status: 405 });
77
- }
78
- const target = new URL(`/${url.search}`, baseUrl);
79
- return new Response(null, {
80
- status: 308,
81
- headers: { Location: target.toString() },
82
- });
83
- }
84
-
85
- const operatorPage = operatorPageForPath(path);
86
- if (operatorPage) {
87
- if (request.method !== "GET" && request.method !== "HEAD") {
88
- return privateJson({ error: "method not allowed" }, { status: 405 });
89
- }
90
- // Open shell — carries no operator data; everything comes from the
91
- // authenticated /ui/* APIs after the browser establishes a session.
92
- const uiAuth = opts.auth.find((provider) => provider.uiAuth)?.uiAuth;
93
- const mcpUrl = new URL("/mcp", baseUrl).toString();
94
- // Nonce the page's inline script (and the Clerk loader). 'strict-dynamic'
95
- // lets scripts the nonced Clerk loader injects at runtime execute; the
96
- // https:/'unsafe-inline' fallbacks are ignored by CSP3 browsers that
97
- // honour the nonce and only cover legacy ones. No default-src, so Clerk's
98
- // style/font/network needs and the page's inline <style> stay unrestricted
99
- // — only script execution, the XSS sink, is gated.
100
- const nonce = uiScriptNonce();
101
- return new Response(
102
- request.method === "HEAD"
103
- ? null
104
- : renderUiHtml(uiAuth, mcpUrl, opts.branding, nonce, operatorPage),
105
- {
106
- status: 200,
107
- headers: {
108
- "Content-Type": "text/html; charset=utf-8",
109
- "Content-Security-Policy":
110
- `script-src 'nonce-${nonce}' 'strict-dynamic' https: 'unsafe-inline'; ` +
111
- "object-src 'none'; base-uri 'none'; frame-ancestors 'none'",
112
- "X-Content-Type-Options": "nosniff",
113
- },
114
- },
115
- );
116
- }
117
- if (path !== "/ui/data") return null;
118
-
119
- const authz = await authorize(request, baseUrl, opts.auth);
120
- if (!authz.ok) return authz.response;
121
- const eligibleClerkOperator = authz.uiAdminEligible === true;
122
- const credentialManagement = credentialManagementCapability({
123
- eligibleClerkOperator,
124
- hasCredentialSlots: opts.registry
125
- .listConnectors()
126
- .some((connector) => Boolean(connector.credential)),
127
- hasCredentialVault: Boolean(opts.credentialVault),
128
- });
129
- // As with connector credentials, a Bearer-authenticated observer learns
130
- // only that Clerk is required—not whether this deployment has opted into
131
- // token issuance. Configuration topology is operator data.
132
- const accessTokenManagement = !eligibleClerkOperator
133
- ? "requires_clerk" as const
134
- : opts.accessTokens
135
- ? "available" as const
136
- : "not_configured" as const;
137
- const data = await buildUiData(
138
- opts.registry,
139
- baseUrl,
140
- opts.serverInfo,
141
- // The static headless bearer may read connector health, but only a
142
- // Clerk-authenticated operator receives credential metadata.
143
- eligibleClerkOperator ? opts.credentialVault : undefined,
144
- Boolean(opts.activity?.list),
145
- credentialManagement,
146
- defer,
147
- eligibleClerkOperator,
148
- opts.discoveryConcurrency,
149
- accessTokenManagement,
150
- );
151
- return privateJson(data);
152
- }
package/src/server.ts DELETED
@@ -1,195 +0,0 @@
1
- import { isAdmittingExecutor } from "./executor-admission.js";
2
- import { routeAccessTokens } from "./routes/access-tokens.js";
3
- import { routeActivity } from "./routes/activity.js";
4
- import { routeCredentials } from "./routes/credentials.js";
5
- import { createMcpRoute, MCP_CORS_HEADERS } from "./routes/mcp.js";
6
- import {
7
- routeOAuthCallback,
8
- routeOAuthManagement,
9
- } from "./routes/oauth.js";
10
- import {
11
- withSecurityHeaders,
12
- type RouteContext,
13
- type RuntimeExecutionContext,
14
- type ServerOptions,
15
- } from "./routes/shared.js";
16
- import { routeUi } from "./routes/ui.js";
17
-
18
- export type { ServerOptions } from "./routes/shared.js";
19
-
20
- /**
21
- * Build the Web-standard fetch handler.
22
- *
23
- * Route ordering is the contract: private mutation routes precede wildcard
24
- * OPTIONS, every built-in precedes connector-owned routes, and the security
25
- * wrapper is applied to every response.
26
- */
27
- export function createFetchHandler(
28
- opts: ServerOptions,
29
- ): (
30
- request: Request,
31
- runtimeContext?: RuntimeExecutionContext,
32
- ) => Promise<Response> {
33
- const { auth, publicUrl, registry } = opts;
34
- const routeMcp = createMcpRoute(opts);
35
-
36
- return async function fetch(
37
- request: Request,
38
- runtimeContext?: RuntimeExecutionContext,
39
- ): Promise<Response> {
40
- const url = new URL(request.url);
41
- const baseUrl = publicUrl ?? url.origin;
42
- const path = url.pathname;
43
- const defer = runtimeContext
44
- ? runtimeContext.waitUntil.bind(runtimeContext)
45
- : undefined;
46
-
47
- // Container and orchestrator probes reach /health over plain HTTP on
48
- // loopback, where no proxy has set X-Forwarded-Proto. Redirecting them to
49
- // the public origin would make an internal liveness check depend on
50
- // external DNS, TLS, and the tunnel in front of connecta — so /health is
51
- // exempt. It is unauthenticated, returns no user data, and sets no
52
- // cookies, so forcing HTTPS on it protects nothing.
53
- if (
54
- publicUrl &&
55
- path !== "/health" &&
56
- new URL(publicUrl).protocol === "https:" &&
57
- url.protocol === "http:"
58
- ) {
59
- // Assign the path and query onto the configured URL instead of resolving
60
- // attacker-controlled text against it. A pathname beginning with `//`
61
- // (including a backslash form normalized by URL parsing) is an authority
62
- // when passed to `new URL(value, base)` and would otherwise replace the
63
- // deployment host. `/ui` is canonicalized while upgrading it so an old
64
- // bookmark reaches the new Connections entry point in one permanent
65
- // redirect.
66
- const target = new URL(publicUrl);
67
- target.pathname = path === "/ui" ? "/" : url.pathname;
68
- target.search = url.search;
69
- target.hash = "";
70
- return withSecurityHeaders(
71
- new Response(null, {
72
- status: 308,
73
- headers: { Location: target.toString() },
74
- }),
75
- url,
76
- path,
77
- );
78
- }
79
-
80
- const context: RouteContext = {
81
- request,
82
- url,
83
- path,
84
- baseUrl,
85
- opts,
86
- defer,
87
- runtimeContext,
88
- };
89
-
90
- const route = async (): Promise<Response> => {
91
- // Private mutations own OPTIONS so they never inherit wildcard CORS.
92
- const accessTokens = await routeAccessTokens(context);
93
- if (accessTokens) return accessTokens;
94
-
95
- const credentials = await routeCredentials(context);
96
- if (credentials) return credentials;
97
-
98
- const oauthManagement = await routeOAuthManagement(context);
99
- if (oauthManagement) return oauthManagement;
100
-
101
- if (request.method === "OPTIONS") {
102
- for (const provider of auth) {
103
- if (provider.handleMetadata) {
104
- const response = await provider.handleMetadata(request, baseUrl);
105
- if (response) return response;
106
- }
107
- }
108
- return new Response(null, {
109
- status: 204,
110
- headers: MCP_CORS_HEADERS,
111
- });
112
- }
113
-
114
- if (path.startsWith("/.well-known/")) {
115
- for (const provider of auth) {
116
- if (provider.handleMetadata) {
117
- const response = await provider.handleMetadata(request, baseUrl);
118
- if (response) return response;
119
- }
120
- }
121
- return new Response("Not Found", { status: 404 });
122
- }
123
-
124
- if (path === "/health") {
125
- // The executor is required, so code admission always has a shape to
126
- // report: either the executor's own pool or the fallback controller
127
- // wrapped around it at construction.
128
- const codeAdmission = isAdmittingExecutor(opts.executor)
129
- ? opts.executor.admissionSnapshot?.()
130
- : undefined;
131
- return Response.json({
132
- status: "ok",
133
- connectors: registry.listConnectors().length,
134
- server: opts.serverInfo,
135
- admission: {
136
- policy: "global-fifo",
137
- requests: opts.requestAdmission.snapshot(),
138
- code: codeAdmission ?? { managedByExecutor: true },
139
- downstreamCalls: {
140
- policy: "connector-partitioned-per-runtime",
141
- connectors: registry.callAdmissionSnapshot(),
142
- },
143
- reservedRoutes: [
144
- "/health",
145
- "/",
146
- "/credentials",
147
- "/tokens",
148
- "/activity",
149
- "/ui",
150
- "/ui/*",
151
- ],
152
- },
153
- ...(opts.deploymentInfo ? { deployment: opts.deploymentInfo } : {}),
154
- });
155
- }
156
-
157
- const oauthCallback = await routeOAuthCallback(context);
158
- if (oauthCallback) return oauthCallback;
159
-
160
- const ui = await routeUi(context);
161
- if (ui) return ui;
162
-
163
- const activity = await routeActivity(context);
164
- if (activity) return activity;
165
-
166
- const mcp = await routeMcp(context);
167
- if (mcp) return mcp;
168
-
169
- // Connector-owned public routes, dispatched last: a connector can add a
170
- // route but never shadow one of connecta's own. A throw here is the
171
- // connector's bug, not a missing route, so it surfaces as 500 rather
172
- // than falling through to 404.
173
- for (const connector of registry.listConnectors()) {
174
- if (!connector.handleRequest) continue;
175
- try {
176
- const response = await connector.handleRequest(
177
- request,
178
- registry.contextFor(connector.id, baseUrl),
179
- );
180
- if (response) return response;
181
- } catch (error) {
182
- opts.logger.error(
183
- `[connecta] connector "${connector.id}" handleRequest failed`,
184
- error,
185
- );
186
- return new Response("Internal Server Error", { status: 500 });
187
- }
188
- }
189
-
190
- return new Response("Not Found", { status: 404 });
191
- };
192
-
193
- return withSecurityHeaders(await route(), url, path);
194
- };
195
- }
package/src/skills.ts DELETED
@@ -1,271 +0,0 @@
1
- import type { Connector } from "./types.js";
2
-
3
- export const CONNECTA_INSTRUCTIONS =
4
- 'Connecta exposes seven meta-tools. For one read at an unknown address, search_tools with 2–4 distinctive action/object terms and includeSchemas="compact", then one call_tool — a lone cold call is cheaper direct than a program. For read-only reduction, multiple or dependent calls, loops, joins, or branches, do not call top-level search_tools: make one execute_code call whose program searches, selects, calls, and reduces; never return discovery for another call. connecta.ui(html) is a guest function inside execute_code, never a connector address or search_tools result; pass one HTML string for display-only, or bind named read-only refresh/drill-down calls in its optional reads argument, and return the same initial summary data the HTML renders. Unannotated, write-capable, or destructive tools stay top level: search_tools, then call_destructive_tool; authorize_connector follows auth_required; get_result follows truncation. If this routing is unfamiliar, fetch skills({ name: "usage" }).';
5
-
6
- const USAGE_SKILL_BASE = `# Connecta usage
7
-
8
- ## The surface
9
-
10
- Seven tools: \`execute_code\`, \`search_tools\`, \`call_tool\`, \`call_destructive_tool\`, \`authorize_connector\`, \`get_result\`, \`skills\`. Broad discovery and multi-call work live in a program, not in top-level tools.
11
-
12
- ## Choose the smallest execution tool
13
-
14
- Use exact addresses from discovery; never invent one. Search 2–4 distinctive action/object terms, not the whole request.
15
-
16
- - One read at an unknown address: \`search_tools({ query, includeSchemas: "compact" })\`, then \`call_tool\` once — one cold call is cheaper direct than a program.
17
- - Anything wider — two or more calls, dependent steps, loops, joins, branching, a whole-catalog browse, or a result to reduce: one \`execute_code\` run.
18
- - Any unannotated, write-capable, or destructive call: \`call_destructive_tool\`, one at a time, after reviewing its schema and consequences.
19
- - Truncated result: retry with \`fields\`, else page it with \`get_result\`.
20
- - \`auth_required\`: \`authorize_connector\`, hand its recovery text to the operator, retry the call.
21
-
22
- ## Inside a program
23
-
24
- One async arrow function. The only capabilities are one global per connector (\`<connectorId>.<toolName>(args)\`), the \`connecta\` functions, and \`console.log\`.
25
-
26
- - \`connecta.search({})\` loads all catalogs; pass \`connector: "<id>"\` when obvious to load one. \`safety: "readOnly"\` keeps executable calls. Neither grants authority. Matches carry \`address\` and annotations.
27
- - Exact schemas: \`connecta.describe({ address: "connector.tool" })\` for one, \`{ addresses: [...] }\` for many; \`format: "json"\` only for exact constraints.
28
- - Two to ten independent calls: \`connecta.batch([...])\`. Each outcome is \`{ address, ok: true, data }\` or \`{ address, ok: false, error, errorDetails: { code, retryable } }\` — how a program tells a policy refusal from a transient failure.
29
- - Search inside the run, not before it; return only the reduction the answer needs, never raw payloads.
30
- - Only tools annotated \`readOnlyHint: true\` are reachable; the gate, credentials, and admission are enforced below the sandbox — nothing a program does widens its reach.
31
-
32
- ## Rendering a view
33
-
34
- \`connecta.ui(html)\` renders a display-only view on success for the client, never for the model. Fetch first, check the shape in code. On a surprise — empty array, missing key — return a trimmed first record instead of rendering: the wrong view becomes the sample you needed. Otherwise render from the variables you return; the model reads the return value, not the view.
35
-
36
- `;
37
-
38
- /** Deployment-scoped guide routing appended to the shared usage guide. */
39
- const CONNECTOR_GUIDES_SECTION = `
40
- ## Per-connector guides
41
-
42
- When a connector here ships a deployment-scoped usage guide, \`skills({})\` and discovery return the exact \`guide\` name plus a bounded \`guideSummary\` saying what it covers. Fetch only a listed or carried name with \`skills({ name: <guide> })\`; never infer one from a connector id. \`guideRequired: true\` is a hard stop: fetch before calling. \`guideRequiredReasons\` says why — \`connector_required\` and \`approval_required\` stand however you expand the schema; \`schema_truncated\` clears once describe returns the exact one. Otherwise fetch when the summary names a connector-specific sequence, unit, pagination rule, alias, or generic API convention relevant to the task. A read-only call whose compact schema is complete and unambiguous may proceed without fetching an otherwise irrelevant guide. Connector guides do not replace the shared Connecta usage guide and never apply to another deployment implicitly.
43
- `;
44
-
45
- /** Shared Connecta routing guidance, byte-identical across deployments. */
46
- export const USAGE_SKILL = USAGE_SKILL_BASE + CONNECTOR_GUIDES_SECTION;
47
-
48
- /** The always-loaded MCP `instructions` string. */
49
- export function instructionsFor(): string {
50
- return CONNECTA_INSTRUCTIONS;
51
- }
52
-
53
- /** True when at least one of `connectors` carries a usage guide. */
54
- export function hasConnectorGuides(connectors: readonly Connector[]): boolean {
55
- return connectors.some(
56
- (connector) => connectorGuide(connector) !== undefined,
57
- );
58
- }
59
-
60
- /**
61
- * The built-in usage guide is byte-identical across deployments, so an agent
62
- * that has read it once in a task never needs an equivalent deployment-local
63
- * copy. Guide-free deployments still pay no fixed tool-description cost: the
64
- * conditional notes in meta-tools.ts remain absent.
65
- */
66
- function usageSkill(_connectors: readonly Connector[]): string {
67
- return USAGE_SKILL;
68
- }
69
-
70
- const AVAILABLE_SKILLS = [
71
- {
72
- name: "usage",
73
- description:
74
- "How to route work between one execute_code program and Connecta's explicit call, authorization, and result tools.",
75
- content: usageSkill,
76
- },
77
- ] as const;
78
-
79
- /**
80
- * Namespace for operator-authored per-connector guides. Built-in skill names
81
- * are bare identifiers and never contain ":", so `connector:<id>` cannot
82
- * collide with one — not even when a connector's id is literally "usage".
83
- * The prefixed form is the ONLY way to reach a connector guide: a bare
84
- * connector id is never resolved, so nothing shadows anything silently.
85
- */
86
- const CONNECTOR_SKILL_PREFIX = "connector:";
87
-
88
- /** The skill name that fetches `connector`'s guide. */
89
- export function connectorSkillName(connectorId: string): string {
90
- return `${CONNECTOR_SKILL_PREFIX}${connectorId}`;
91
- }
92
-
93
- /** The connector's guide, or undefined when it declares none (or a blank one). */
94
- export function connectorGuide(connector: Connector): string | undefined {
95
- const guide = connector.usageGuide;
96
- const content = typeof guide === "string" ? guide : guide?.content;
97
- return content && content.trim() !== "" ? content : undefined;
98
- }
99
-
100
- const SUMMARY_LENGTH = 120;
101
-
102
- /** A `---`/`***`/`___` rule, which also opens and closes YAML frontmatter. */
103
- const RULE_RE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
104
-
105
- /** A fenced code block's delimiter. */
106
- const FENCE_RE = /^\s*(?:```|~~~)/;
107
-
108
- /**
109
- * Markup that carries no summary text of its own: horizontal rules, HTML
110
- * comments, and table rows. Skipped so a guide that opens with one is
111
- * summarized by its first real line instead of by punctuation.
112
- */
113
- const NOT_SUMMARY_RE = /^\s*(?:<!--|\|)|^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
114
-
115
- /** Drop a leading YAML frontmatter block — metadata, not summary text. */
116
- function withoutFrontmatter(lines: string[]): string[] {
117
- let start = 0;
118
- while (start < lines.length && (lines[start] ?? "").trim() === "") start++;
119
- const openingRule = lines[start];
120
- if (openingRule === undefined || !RULE_RE.test(openingRule)) return lines;
121
- const close = lines.findIndex((line, i) => i > start && RULE_RE.test(line));
122
- return close === -1 ? lines : lines.slice(close + 1);
123
- }
124
-
125
- /**
126
- * One line describing a guide, for the cheap list view: the guide's first
127
- * meaningful line (heading marks and list bullets stripped), falling back to
128
- * the connector's own description when the guide opens with nothing but
129
- * markup.
130
- */
131
- function boundedSummary(summary: string): string | undefined {
132
- const line = summary.replace(/\s+/g, " ").trim();
133
- if (line === "") return undefined;
134
- return line.length <= SUMMARY_LENGTH
135
- ? line
136
- : `${line.slice(0, SUMMARY_LENGTH - 1).trimEnd()}…`;
137
- }
138
-
139
- function summarizeGuide(connector: Connector, guide: string): string {
140
- let inFence = false;
141
- let headingFallback: string | undefined;
142
- for (const raw of withoutFrontmatter(guide.split("\n"))) {
143
- if (FENCE_RE.test(raw)) {
144
- inFence = !inFence;
145
- continue;
146
- }
147
- if (inFence) continue;
148
- if (raw.trim() === "" || NOT_SUMMARY_RE.test(raw)) continue;
149
- const heading = /^\s*#{1,6}/.test(raw);
150
- const line = raw
151
- // `\s*` (not `\s+`) so a bare `#` strips to nothing and is skipped, and
152
- // an unspaced `#Heading` is still read as a heading.
153
- .replace(/^\s*#{1,6}\s*/, "")
154
- .replace(/^\s*[-*+]\s+/, "")
155
- .replace(/\s+/g, " ")
156
- .trim();
157
- if (line === "") continue;
158
- if (heading) {
159
- headingFallback ??= boundedSummary(line);
160
- continue;
161
- }
162
- return boundedSummary(line) ?? line;
163
- }
164
- if (headingFallback) return headingFallback;
165
- const fallback = connector.description ?? `Usage guide for "${connector.id}".`;
166
- return boundedSummary(fallback) ?? `Usage guide for "${connector.id}".`;
167
- }
168
-
169
- /** Bounded, decision-useful discovery summary for a connector guide. */
170
- export function connectorGuideSummary(
171
- connector: Connector,
172
- ): string | undefined {
173
- const guide = connectorGuide(connector);
174
- if (!guide) return undefined;
175
- const configured =
176
- typeof connector.usageGuide === "object"
177
- ? boundedSummary(connector.usageGuide.summary ?? "")
178
- : undefined;
179
- return configured ?? summarizeGuide(connector, guide);
180
- }
181
-
182
- /** Whether correct use always depends on conventions outside the tool schema. */
183
- export function connectorGuideRequired(connector: Connector): boolean {
184
- return (
185
- connectorGuide(connector) !== undefined &&
186
- typeof connector.usageGuide === "object" &&
187
- connector.usageGuide.required === true
188
- );
189
- }
190
-
191
- export interface SkillListing {
192
- name: string;
193
- description: string;
194
- }
195
-
196
- /**
197
- * Every fetchable skill: the built-in guides plus one entry per connector that
198
- * carries a usage guide. Derived from the connector list passed in — the single
199
- * place guide visibility is decided.
200
- */
201
- export function listSkills(connectors: readonly Connector[]): SkillListing[] {
202
- const listing: SkillListing[] = AVAILABLE_SKILLS.map((skill) => ({
203
- name: skill.name,
204
- description: skill.description,
205
- }));
206
- for (const connector of connectors) {
207
- // Undefined here means "no guide" and nothing else: a connector that has
208
- // one always summarizes to a non-empty line, configured or derived.
209
- const summary = connectorGuideSummary(connector);
210
- if (!summary) continue;
211
- listing.push({
212
- name: connectorSkillName(connector.id),
213
- description: summary,
214
- });
215
- }
216
- return listing;
217
- }
218
-
219
- export type SkillLookup =
220
- { found: true; content: string } | { found: false; message: string };
221
-
222
- /**
223
- * Resolve one skill name. Built-in names match exactly; connector guides are
224
- * reachable only through the `connector:` prefix. Every miss — unknown name,
225
- * unknown connector, connector without a guide — is an explicit error, never a
226
- * silent fallback to the generic guide.
227
- */
228
- export function resolveSkill(
229
- name: string,
230
- connectors: readonly Connector[],
231
- ): SkillLookup {
232
- const builtIn = AVAILABLE_SKILLS.find((skill) => skill.name === name);
233
- if (builtIn) {
234
- return { found: true, content: builtIn.content(connectors) };
235
- }
236
- const available = () =>
237
- listSkills(connectors)
238
- .map((skill) => skill.name)
239
- .join(", ");
240
- if (name.startsWith(CONNECTOR_SKILL_PREFIX)) {
241
- const id = name.slice(CONNECTOR_SKILL_PREFIX.length);
242
- const connector = connectors.find((c) => c.id === id);
243
- if (!connector) {
244
- return {
245
- found: false,
246
- message: `Unknown connector "${id}". Available skills: ${available()}.`,
247
- };
248
- }
249
- const guide = connectorGuide(connector);
250
- if (!guide) {
251
- return {
252
- found: false,
253
- message: `Connector "${id}" has no usage guide. Available skills: ${available()}.`,
254
- };
255
- }
256
- return { found: true, content: guide };
257
- }
258
- const bare = connectors.find((c) => c.id === name);
259
- if (bare) {
260
- return {
261
- found: false,
262
- message: connectorGuide(bare)
263
- ? `Unknown skill "${name}". Connector guides are fetched as "${connectorSkillName(name)}". Available skills: ${available()}.`
264
- : `Connector "${name}" has no usage guide. Available skills: ${available()}.`,
265
- };
266
- }
267
- return {
268
- found: false,
269
- message: `Unknown skill "${name}". Available skills: ${available()}.`,
270
- };
271
- }