@zackbart/connecta 0.28.1 → 0.30.0

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 (332) hide show
  1. package/CHANGELOG.md +855 -5
  2. package/README.md +125 -116
  3. package/bin/connecta.mjs +121 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/argument-redaction.js +230 -0
  12. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  13. package/dist/auth/clerk-sdk/client.js +5 -0
  14. package/dist/auth/clerk-sdk/generated.js +1845 -0
  15. package/dist/auth/clerk-transport.js +19 -0
  16. package/dist/auth/clerk.d.ts +17 -7
  17. package/dist/auth/clerk.js +189 -107
  18. package/dist/auth/cloudflare-access.js +25 -26
  19. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  20. package/dist/auth/downstream-client-metadata.js +133 -0
  21. package/dist/auth/downstream-oauth.js +1584 -1580
  22. package/dist/auth/downstream-revocation.js +106 -0
  23. package/dist/auth/oauth-token-response.js +231 -0
  24. package/dist/auth/oauth-v2-migration.js +116 -0
  25. package/dist/auth/static-oauth.js +93 -71
  26. package/dist/auth-elicitation.js +288 -0
  27. package/dist/authorization-handoff.js +111 -0
  28. package/dist/branding.d.ts +8 -16
  29. package/dist/branding.js +26 -59
  30. package/dist/byte-read-response.js +39 -0
  31. package/dist/call-admission.js +12 -16
  32. package/dist/catalog-cache.js +739 -0
  33. package/dist/catalog-drift.d.ts +50 -15
  34. package/dist/catalog-drift.js +365 -128
  35. package/dist/catalog-fingerprint.js +2 -4
  36. package/dist/catalog-freshness.js +13 -0
  37. package/dist/catalog-limits.js +2 -2
  38. package/dist/catalog-service.js +409 -225
  39. package/dist/catalog.js +43 -84
  40. package/dist/concurrency.js +3 -4
  41. package/dist/config-defaults.d.ts +39 -0
  42. package/dist/config-defaults.js +46 -0
  43. package/dist/config-schema.d.ts +181 -0
  44. package/dist/config-schema.js +435 -0
  45. package/dist/config-value-sources.js +102 -0
  46. package/dist/config.d.ts +633 -0
  47. package/dist/config.js +508 -0
  48. package/dist/connector-access.d.ts +1 -0
  49. package/dist/connector-access.js +13 -15
  50. package/dist/connector-caller.d.ts +8 -0
  51. package/dist/connector-caller.js +15 -6
  52. package/dist/connector-scope.js +1 -1
  53. package/dist/connectors/api-connector.d.ts +59 -7
  54. package/dist/connectors/api-connector.js +106 -19
  55. package/dist/connectors/api.js +4 -1
  56. package/dist/connectors/guarded-fetch.d.ts +144 -0
  57. package/dist/connectors/guarded-fetch.js +87 -38
  58. package/dist/connectors/negotiation-cache.js +46 -0
  59. package/dist/connectors/option-shapes.js +27 -0
  60. package/dist/connectors/remote-mcp.d.ts +30 -4
  61. package/dist/connectors/remote-mcp.js +1774 -320
  62. package/dist/connectors/resource-uri.js +335 -0
  63. package/dist/credential-contract.d.ts +10 -0
  64. package/dist/credential-rules.js +1 -3
  65. package/dist/credential-url.js +108 -0
  66. package/dist/credentials.d.ts +6 -1
  67. package/dist/credentials.js +27 -18
  68. package/dist/d1.d.ts +130 -0
  69. package/dist/d1.js +290 -0
  70. package/dist/describe-config.d.ts +165 -0
  71. package/dist/describe-config.js +315 -0
  72. package/dist/described.js +106 -0
  73. package/dist/downstream-input-context.js +128 -0
  74. package/dist/downstream-input.js +325 -0
  75. package/dist/errors.d.ts +132 -31
  76. package/dist/errors.js +175 -87
  77. package/dist/execute.js +798 -318
  78. package/dist/executor-admission.d.ts +1 -0
  79. package/dist/executor-admission.js +11 -15
  80. package/dist/executor-contract.d.ts +20 -0
  81. package/dist/executor-contract.js +48 -0
  82. package/dist/executor-result.js +30 -13
  83. package/dist/executors/quickjs-child.js +15 -9
  84. package/dist/executors/quickjs-protocol.js +5 -6
  85. package/dist/executors/quickjs-runtime.js +152 -59
  86. package/dist/executors/quickjs.js +49 -39
  87. package/dist/favicon.js +1 -2
  88. package/dist/fix-prompt.js +1 -3
  89. package/dist/guest-runtime.js +238 -0
  90. package/dist/guest-types.d.ts +218 -0
  91. package/dist/guest-types.js +1 -0
  92. package/dist/html-security.js +24 -0
  93. package/dist/identity.js +1 -3
  94. package/dist/inbound-credential.js +52 -0
  95. package/dist/index.d.ts +24 -256
  96. package/dist/index.js +65 -478
  97. package/dist/invocation-auth.js +69 -0
  98. package/dist/invocation.js +262 -79
  99. package/dist/json-schema.d.ts +1 -1
  100. package/dist/mcp-client-context.js +35 -0
  101. package/dist/mcp-result.js +11 -7
  102. package/dist/meta-output.js +61 -0
  103. package/dist/meta-tool-names.d.ts +2 -0
  104. package/dist/meta-tool-names.js +3 -0
  105. package/dist/meta-tools.js +419 -272
  106. package/dist/module-contracts.d.ts +8 -23
  107. package/dist/node.d.ts +0 -2
  108. package/dist/node.js +2 -2
  109. package/dist/oauth-callback-outcome.js +1 -3
  110. package/dist/oauth-handoff.js +194 -0
  111. package/dist/oauth-sealing.js +20 -0
  112. package/dist/operator-record.d.ts +137 -0
  113. package/dist/operator-record.js +420 -0
  114. package/dist/operator-ui/assets.js +31 -0
  115. package/dist/operator-ui/contract.d.ts +72 -0
  116. package/dist/operator-ui/contract.js +1 -0
  117. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  118. package/dist/operator-ui/generated.js +5 -4
  119. package/dist/operator-ui/model.d.ts +12 -87
  120. package/dist/operator-ui/model.js +2 -9
  121. package/dist/operator-ui/setup-commands.js +1 -3
  122. package/dist/operator-ui/view.d.ts +323 -0
  123. package/dist/operator-ui/view.js +62 -189
  124. package/dist/page-styles.js +2 -3
  125. package/dist/program-result.js +5 -0
  126. package/dist/program-source.js +14 -2
  127. package/dist/{exempt-writes.js → program-writes.js} +60 -19
  128. package/dist/provider.d.ts +142 -0
  129. package/dist/provider.js +187 -0
  130. package/dist/providers/_shared/google/result-size.js +57 -0
  131. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  132. package/dist/providers/_shared/google/service-account.js +500 -0
  133. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  134. package/dist/providers/_shared/google/workspace.js +518 -0
  135. package/dist/providers/_shared/request-token.js +83 -0
  136. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  137. package/dist/providers/basecamp/index.js +997 -0
  138. package/dist/providers/basecamp/skill.generated.js +9 -0
  139. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  140. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  141. package/dist/providers/breeze/skill.generated.js +9 -0
  142. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  143. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  144. package/dist/providers/ccb/skill.generated.js +9 -0
  145. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  146. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  147. package/dist/providers/cloudflare/skill.generated.js +10 -0
  148. package/dist/providers/docs/index.d.ts +54 -0
  149. package/dist/providers/docs/index.js +1237 -0
  150. package/dist/providers/docs/skill.generated.js +9 -0
  151. package/dist/providers/drive/index.d.ts +53 -0
  152. package/dist/providers/drive/index.js +1523 -0
  153. package/dist/providers/drive/skill.generated.js +9 -0
  154. package/dist/providers/forms/index.d.ts +54 -0
  155. package/dist/providers/forms/index.js +1217 -0
  156. package/dist/providers/forms/skill.generated.js +11 -0
  157. package/dist/providers/github/auth.d.ts +32 -0
  158. package/dist/providers/github/auth.js +361 -0
  159. package/dist/providers/github/hosted.js +218 -0
  160. package/dist/providers/github/index.d.ts +13 -0
  161. package/dist/providers/github/index.js +140 -0
  162. package/dist/providers/github/key.js +100 -0
  163. package/dist/providers/github/rest.js +321 -0
  164. package/dist/providers/github/scope.d.ts +29 -0
  165. package/dist/providers/github/scope.js +124 -0
  166. package/dist/providers/github/skill.generated.js +8 -0
  167. package/dist/providers/gmail/index.d.ts +51 -0
  168. package/dist/providers/gmail/index.js +1436 -0
  169. package/dist/providers/gmail/skill.generated.js +9 -0
  170. package/dist/providers/infisical/index.d.ts +7 -0
  171. package/dist/providers/infisical/index.js +713 -0
  172. package/dist/providers/infisical/skill.generated.js +8 -0
  173. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  174. package/dist/providers/linear/index.js +352 -0
  175. package/dist/providers/linear/skill.generated.js +8 -0
  176. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  177. package/dist/providers/mixpanel/index.js +416 -0
  178. package/dist/providers/mixpanel/skill.generated.js +9 -0
  179. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  180. package/dist/providers/{notion.js → notion/index.js} +226 -239
  181. package/dist/providers/notion/skill.generated.js +10 -0
  182. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  183. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  184. package/dist/providers/overflow/skill.generated.js +8 -0
  185. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  186. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  187. package/dist/providers/planning-center/skill.generated.js +10 -0
  188. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  189. package/dist/providers/revenuecat/index.js +557 -0
  190. package/dist/providers/revenuecat/skill.generated.js +11 -0
  191. package/dist/providers/sheets/index.d.ts +54 -0
  192. package/dist/providers/sheets/index.js +1237 -0
  193. package/dist/providers/sheets/skill.generated.js +10 -0
  194. package/dist/providers/slides/index.d.ts +53 -0
  195. package/dist/providers/slides/index.js +2645 -0
  196. package/dist/providers/slides/skill.generated.js +9 -0
  197. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  198. package/dist/providers/stripe/index.js +266 -0
  199. package/dist/providers/stripe/skill.generated.js +11 -0
  200. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  201. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  202. package/dist/providers/tithely/skill.generated.js +8 -0
  203. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  204. package/dist/providers/vercel/index.js +1038 -0
  205. package/dist/providers/vercel/mcp-ownership.js +212 -0
  206. package/dist/providers/vercel/skill.generated.js +11 -0
  207. package/dist/registry.d.ts +110 -171
  208. package/dist/registry.js +588 -927
  209. package/dist/request-state.js +21 -0
  210. package/dist/resource-read.js +103 -0
  211. package/dist/result-shapes.js +17 -49
  212. package/dist/retired-toolkits.js +1 -1
  213. package/dist/routes/access-tokens.js +3 -6
  214. package/dist/routes/activity-context.js +21 -0
  215. package/dist/routes/activity-disclosure.js +40 -0
  216. package/dist/routes/activity.js +59 -21
  217. package/dist/routes/actor-labels.js +3 -50
  218. package/dist/routes/connect.js +221 -0
  219. package/dist/routes/credentials.js +15 -27
  220. package/dist/routes/mcp.js +365 -130
  221. package/dist/routes/oauth-client-metadata.js +15 -0
  222. package/dist/routes/oauth-management.js +45 -107
  223. package/dist/routes/oauth.js +110 -57
  224. package/dist/routes/operator.js +3 -3
  225. package/dist/routes/shared.d.ts +25 -64
  226. package/dist/routes/shared.js +160 -80
  227. package/dist/routes/skills.js +30 -0
  228. package/dist/routes/ui-config.js +225 -0
  229. package/dist/routes/ui-data.js +40 -35
  230. package/dist/routes/ui.js +66 -56
  231. package/dist/runtime/admission.js +4 -2
  232. package/dist/runtime/call-admission.js +3 -1
  233. package/dist/runtime/connector-scope.js +1 -4
  234. package/dist/runtime/run.js +10 -39
  235. package/dist/runtime/services.js +12 -117
  236. package/dist/runtime/shared-read.js +1 -3
  237. package/dist/runtime/storage.js +8 -1
  238. package/dist/sent-secrets.d.ts +93 -0
  239. package/dist/sent-secrets.js +1013 -0
  240. package/dist/server.js +28 -71
  241. package/dist/skills.js +416 -174
  242. package/dist/sqlite.d.ts +48 -0
  243. package/dist/sqlite.js +181 -0
  244. package/dist/storage/keys.js +443 -0
  245. package/dist/storage/memory.js +16 -4
  246. package/dist/storage/sql.d.ts +66 -0
  247. package/dist/storage/sql.js +543 -0
  248. package/dist/timeout.js +4 -2
  249. package/dist/tool-name.js +17 -0
  250. package/dist/tool-safety.d.ts +9 -35
  251. package/dist/tool-safety.js +20 -40
  252. package/dist/types.d.ts +336 -56
  253. package/dist/types.js +0 -1
  254. package/dist/ui.d.ts +7 -19
  255. package/dist/ui.js +76 -90
  256. package/dist/url-safety.js +3 -6
  257. package/dist/usage-guide.js +146 -0
  258. package/dist/validate.d.ts +7 -0
  259. package/dist/validate.js +309 -47
  260. package/dist/version.d.ts +1 -7
  261. package/dist/version.js +3 -7
  262. package/dist/worker.d.ts +11 -0
  263. package/dist/worker.js +435 -0
  264. package/documentation/README.md +63 -0
  265. package/documentation/architecture.md +696 -231
  266. package/documentation/auth.md +1069 -546
  267. package/documentation/code-mode.md +662 -356
  268. package/documentation/deploying.md +146 -0
  269. package/documentation/integrating.md +159 -0
  270. package/documentation/meta-tools.md +570 -216
  271. package/documentation/operating.md +135 -0
  272. package/documentation/operator-tests.md +97 -0
  273. package/documentation/operator-ui.md +132 -0
  274. package/documentation/provider-migration-0.29.md +65 -0
  275. package/examples/worker/AGENTS.md +34 -12
  276. package/examples/worker/README.md +293 -255
  277. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  278. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  279. package/examples/worker/src/connecta.config.ts +151 -0
  280. package/examples/worker/src/index.ts +14 -197
  281. package/examples/worker/wrangler.jsonc +15 -35
  282. package/package.json +126 -54
  283. package/templates/node/.dockerignore +1 -2
  284. package/templates/node/.env.example +20 -21
  285. package/templates/node/AGENTS.md +21 -17
  286. package/templates/node/Dockerfile +3 -5
  287. package/templates/node/README.md +140 -58
  288. package/templates/node/docker-compose.yml +11 -12
  289. package/templates/node/package.json +4 -2
  290. package/templates/node/src/connecta.config.ts +119 -0
  291. package/templates/node/src/index.ts +9 -154
  292. package/templates/node/src/provision-token.ts +17 -0
  293. package/AGENTS.md +0 -170
  294. package/dist/artifacts/connector.d.ts +0 -58
  295. package/dist/artifacts/connector.js +0 -730
  296. package/dist/artifacts/document.js +0 -86
  297. package/dist/artifacts/guide.js +0 -140
  298. package/dist/artifacts/html-scan.js +0 -329
  299. package/dist/artifacts/json.js +0 -78
  300. package/dist/artifacts/kv-store.d.ts +0 -20
  301. package/dist/artifacts/kv-store.js +0 -140
  302. package/dist/artifacts/markdown.js +0 -478
  303. package/dist/artifacts/module.d.ts +0 -37
  304. package/dist/artifacts/module.js +0 -66
  305. package/dist/artifacts/operations.d.ts +0 -197
  306. package/dist/artifacts/operations.js +0 -1002
  307. package/dist/artifacts/refresh.d.ts +0 -55
  308. package/dist/artifacts/refresh.js +0 -236
  309. package/dist/artifacts/routes.js +0 -206
  310. package/dist/artifacts/types.d.ts +0 -214
  311. package/dist/artifacts/types.js +0 -22
  312. package/dist/artifacts/validate.d.ts +0 -62
  313. package/dist/artifacts/validate.js +0 -651
  314. package/dist/artifacts.d.ts +0 -6
  315. package/dist/artifacts.js +0 -9
  316. package/dist/auth/bearer.d.ts +0 -11
  317. package/dist/auth/bearer.js +0 -50
  318. package/dist/providers/basecamp.js +0 -445
  319. package/dist/providers/linear.js +0 -217
  320. package/dist/providers/mixpanel.js +0 -247
  321. package/dist/providers/revenuecat.js +0 -317
  322. package/dist/providers/stripe.js +0 -253
  323. package/dist/providers/vercel.js +0 -1253
  324. package/dist/storage/file.d.ts +0 -15
  325. package/dist/storage/file.js +0 -455
  326. package/ethos.md +0 -93
  327. package/examples/worker/src/cloudflare-kv.ts +0 -43
  328. package/examples/worker/src/d1-activity-row.ts +0 -155
  329. package/examples/worker/src/d1-activity.ts +0 -147
  330. package/examples/worker/src/d1-storage.ts +0 -138
  331. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  332. package/templates/node/src/file-activity.ts +0 -182
@@ -0,0 +1,315 @@
1
+ // `Connecta.describeConfig()`: a secret-free snapshot of what a deployment
2
+ // runs with, built once at construction for the operator UI and
3
+ // `connecta doctor --config`.
4
+ //
5
+ // It is an allowlist serializer. Every value is copied field by field from a
6
+ // known type, and nothing is spread: a config object, a module, a connector,
7
+ // or a connector's own `describe()` result can carry a secret beside the
8
+ // fields named here, and spreading it would publish that secret the day
9
+ // someone adds one. Functions become "custom"/"default" or a boolean and are
10
+ // never called or stringified; every URL, absolute or root-relative, passes
11
+ // through one sanitizer in src/described.ts and keeps origin and path at most;
12
+ // headers keep their names. test/describe-config.test.ts plants a sentinel secret in every
13
+ // secret-bearing position and asserts none survives.
14
+ import { resolveBranding } from "./branding.js";
15
+ import { CONFIG_DEFAULTS } from "./config-defaults.js";
16
+ import { describedEndpoint, describedHref, describedOrigin, describedTools, describedUrl } from "./described.js";
17
+ import { connectorGuideRequired, connectorGuideSummary } from "./skills.js";
18
+ import { CONNECTA_VERSION } from "./version.js";
19
+ const str = (value) => (typeof value === "string" ? value : undefined);
20
+ const num = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
21
+ /** Copy only the named keys whose values are strings or numbers. */
22
+ function pick(value, keys, read) {
23
+ const out = {};
24
+ if (typeof value !== "object" || value === null)
25
+ return out;
26
+ for (const key of keys) {
27
+ const item = read(value[key]);
28
+ if (item !== undefined)
29
+ out[key] = item;
30
+ }
31
+ return out;
32
+ }
33
+ function ownValue(value, key) {
34
+ return typeof value === "object" && value !== null && Object.hasOwn(value, key)
35
+ ? value[key]
36
+ : undefined;
37
+ }
38
+ /** Each resolved value, with "config" where the deployment set it explicitly. */
39
+ function limitGroup(resolved, raw, keys) {
40
+ const out = {};
41
+ for (const key of keys) {
42
+ const value = resolved[key];
43
+ out[key] = {
44
+ value: typeof value === "number" || typeof value === "boolean" ? value : null,
45
+ source: ownValue(raw, key) !== undefined ? "config" : "default",
46
+ };
47
+ }
48
+ return out;
49
+ }
50
+ /** An endpoint from a string or an `{ origin, path }` pair, re-parsed so only origin and path survive. */
51
+ function endpointOf(value) {
52
+ if (typeof value === "object" && value !== null && !(value instanceof URL)) {
53
+ const { origin, path } = value;
54
+ return typeof origin === "string"
55
+ ? describedEndpoint(`${origin}${typeof path === "string" ? path : ""}`)
56
+ : undefined;
57
+ }
58
+ return describedEndpoint(value);
59
+ }
60
+ const AUTH_MODES = new Set(["none", "headers", "credential", "oauth", "request"]);
61
+ const SOURCE_KINDS = new Set(["remote-mcp", "api", "custom"]);
62
+ /** Re-validate a connector's own description: a custom describe() may return anything. */
63
+ function copyAuth(value) {
64
+ if (typeof value !== "object" || value === null)
65
+ return undefined;
66
+ const auth = value;
67
+ if (typeof auth.mode !== "string" || !AUTH_MODES.has(auth.mode))
68
+ return undefined;
69
+ const names = (item) => Array.isArray(item) ? item.filter((name) => typeof name === "string") : undefined;
70
+ const authorizationEndpoint = endpointOf(auth.authorizationEndpoint);
71
+ const tokenEndpoint = endpointOf(auth.tokenEndpoint);
72
+ const clientMetadataUrl = describedUrl(auth.clientMetadataUrl);
73
+ const headerNames = names(auth.headerNames);
74
+ const apiOrigins = names(auth.apiOrigins)?.flatMap((origin) => describedOrigin(origin) ?? []);
75
+ const authorizationParamNames = names(auth.authorizationParamNames);
76
+ const tokenRequestHeaderNames = names(auth.tokenRequestHeaderNames);
77
+ return {
78
+ mode: auth.mode,
79
+ ...(headerNames ? { headerNames } : {}),
80
+ ...(str(auth.header) !== undefined ? { header: str(auth.header) } : {}),
81
+ ...(auth.scheme === null || typeof auth.scheme === "string" ? { scheme: auth.scheme } : {}),
82
+ ...(str(auth.scope) !== undefined ? { scope: str(auth.scope) } : {}),
83
+ ...(clientMetadataUrl ? { clientMetadataUrl } : {}),
84
+ ...(authorizationEndpoint ? { authorizationEndpoint } : {}),
85
+ ...(tokenEndpoint ? { tokenEndpoint } : {}),
86
+ ...(apiOrigins ? { apiOrigins } : {}),
87
+ ...(str(auth.tokenEndpointAuthMethod) !== undefined
88
+ ? { tokenEndpointAuthMethod: str(auth.tokenEndpointAuthMethod) }
89
+ : {}),
90
+ ...(typeof auth.confidentialClient === "boolean" ? { confidentialClient: auth.confidentialClient } : {}),
91
+ ...(typeof auth.pkce === "boolean" ? { pkce: auth.pkce } : {}),
92
+ ...(authorizationParamNames ? { authorizationParamNames } : {}),
93
+ ...(tokenRequestHeaderNames ? { tokenRequestHeaderNames } : {}),
94
+ };
95
+ }
96
+ function ownDescription(connector) {
97
+ let described;
98
+ try {
99
+ described = typeof connector.describe === "function" ? connector.describe() : undefined;
100
+ }
101
+ catch {
102
+ described = undefined;
103
+ }
104
+ const value = (typeof described === "object" && described !== null ? described : {});
105
+ const source = (typeof value.source === "object" && value.source !== null ? value.source : {});
106
+ const kind = typeof source.kind === "string" && SOURCE_KINDS.has(source.kind)
107
+ ? source.kind
108
+ : "custom";
109
+ const endpoint = endpointOf(value.endpoint);
110
+ const auth = copyAuth(value.auth);
111
+ const transportValue = value.transport;
112
+ const transport = typeof transportValue === "object" && transportValue !== null
113
+ ? (() => {
114
+ const { versionNegotiation, redirects, requireHttps } = transportValue;
115
+ return {
116
+ ...(versionNegotiation === "auto" || versionNegotiation === "legacy" ? { versionNegotiation } : {}),
117
+ ...(redirects === "none" || redirects === "same-origin" ? { redirects } : {}),
118
+ ...(typeof requireHttps === "boolean" ? { requireHttps } : {}),
119
+ };
120
+ })()
121
+ : undefined;
122
+ return {
123
+ source: { kind, ...(str(source.provider) !== undefined ? { provider: str(source.provider) } : {}) },
124
+ ...(endpoint ? { endpoint } : {}),
125
+ ...(auth ? { auth } : {}),
126
+ ...(transport ? { transport } : {}),
127
+ };
128
+ }
129
+ function describeConnector(connector, deploymentCap, registry) {
130
+ const own = ownDescription(connector);
131
+ const credential = connector.credential;
132
+ const admission = connector.callAdmission;
133
+ const guideSummary = connectorGuideSummary(connector);
134
+ const guided = connector.usageGuide !== undefined;
135
+ return {
136
+ id: connector.id,
137
+ ...(str(connector.title) !== undefined ? { title: connector.title } : {}),
138
+ ...(str(connector.description) !== undefined ? { description: connector.description } : {}),
139
+ authScope: connector.authScope === "personal" ? "personal" : "shared",
140
+ ...own,
141
+ ...(credential && typeof credential === "object"
142
+ ? {
143
+ credential: {
144
+ label: String(credential.label),
145
+ ...(str(credential.description) !== undefined ? { description: credential.description } : {}),
146
+ ...(Array.isArray(credential.fields)
147
+ ? {
148
+ fields: credential.fields.map((field) => ({
149
+ name: String(field.name),
150
+ label: String(field.label),
151
+ ...(str(field.description) !== undefined ? { description: field.description } : {}),
152
+ })),
153
+ }
154
+ : {}),
155
+ },
156
+ }
157
+ : {}),
158
+ maxResultBytes: num(connector.maxResultBytes) !== undefined
159
+ ? { value: connector.maxResultBytes, source: "connector" }
160
+ : { value: deploymentCap, source: "deployment" },
161
+ ...(admission && Array.isArray(admission.rules)
162
+ ? {
163
+ callAdmission: {
164
+ ...(num(admission.maxPartitions) !== undefined ? { maxPartitions: admission.maxPartitions } : {}),
165
+ rules: admission.rules.map((rule) => ({
166
+ ...pick(rule, ["maxConcurrency", "maxQueueSize", "queueTimeoutMs", "retryAfterMs"], num),
167
+ ...(rule.budget && num(rule.budget.maxCalls) !== undefined && num(rule.budget.windowMs) !== undefined
168
+ ? { budget: { maxCalls: rule.budget.maxCalls, windowMs: rule.budget.windowMs } }
169
+ : {}),
170
+ partitioned: typeof rule.partitionKey === "function",
171
+ })),
172
+ },
173
+ }
174
+ : {}),
175
+ ...(guided
176
+ ? {
177
+ usageGuide: {
178
+ ...(guideSummary !== undefined ? { summary: guideSummary } : {}),
179
+ required: connectorGuideRequired(connector),
180
+ },
181
+ }
182
+ : {}),
183
+ ...(connector.staticTools ? { tools: describedTools(registry.describeStaticTools(connector.id) ?? []) } : {}),
184
+ };
185
+ }
186
+ const IDENTITY_KEYS = [
187
+ "connectorAccess",
188
+ "activityAccess",
189
+ "credentialAdministration",
190
+ "accessTokenManagement",
191
+ "personalConnection",
192
+ ];
193
+ const STORE_KINDS = new Set(["memory", "d1", "sqlite"]);
194
+ const storeKind = (described) => typeof described?.kind === "string" && STORE_KINDS.has(described.kind)
195
+ ? described.kind
196
+ : "custom";
197
+ /** Build the snapshot. Pure: it reads configuration and calls only describe hooks. */
198
+ export function describeConfig(input) {
199
+ const { raw, config } = input;
200
+ const brand = resolveBranding(config.ui?.branding);
201
+ const accessTokens = config.accessTokens?.describe?.();
202
+ const storage = config.storage;
203
+ const activityStore = config.activity?.store.describe?.();
204
+ const D = CONFIG_DEFAULTS;
205
+ const websiteUrl = describedEndpoint(config.serverInfo.websiteUrl);
206
+ const publicUrl = describedEndpoint(config.publicUrl);
207
+ const productUrl = describedUrl(brand.productUrl);
208
+ const ownerUrl = describedUrl(brand.ownerUrl);
209
+ const codeLimits = limitGroup(config.admission.code, ownValue(ownValue(raw, "admission"), "code"), Object.keys(D.admission.code));
210
+ const description = {
211
+ schemaVersion: 1,
212
+ connectaVersion: CONNECTA_VERSION,
213
+ server: {
214
+ name: config.serverInfo.name,
215
+ version: config.serverInfo.version,
216
+ ...(config.serverInfo.title !== undefined ? { title: config.serverInfo.title } : {}),
217
+ ...(websiteUrl ? { websiteUrl } : {}),
218
+ icons: config.serverInfo.icons?.length ?? 0,
219
+ },
220
+ urls: {
221
+ ...(publicUrl ? { publicUrl } : {}),
222
+ mcpPath: "/mcp",
223
+ allowedOrigins: config.allowedOrigins === undefined
224
+ ? "default"
225
+ : config.allowedOrigins === "*"
226
+ ? "*"
227
+ : config.allowedOrigins.flatMap((origin) => describedOrigin(origin) ?? []),
228
+ },
229
+ executor: {
230
+ ...(input.executorName !== undefined ? { name: input.executorName } : {}),
231
+ admission: input.executorAdmits ? "executor" : codeLimits,
232
+ },
233
+ limits: {
234
+ discovery: limitGroup(config.discovery, ownValue(raw, "discovery"), Object.keys(D.discovery)),
235
+ calls: limitGroup(config.calls, ownValue(raw, "calls"), [
236
+ "defaultTimeoutMs",
237
+ "maxResultBytes",
238
+ ]),
239
+ results: limitGroup(config.results, ownValue(raw, "results"), Object.keys(D.results)),
240
+ execute: limitGroup(config.execute, ownValue(raw, "execute"), Object.keys(D.execute)),
241
+ requests: limitGroup(config.admission.requests, ownValue(ownValue(raw, "admission"), "requests"), Object.keys(D.admission.requests)),
242
+ },
243
+ trust: config.trust,
244
+ classification: structuredClone(config.classification ?? {}),
245
+ auth: config.auth.map((provider) => ({
246
+ kind: String(provider.kind),
247
+ interactive: provider.interactiveOperator === true,
248
+ ...(str(provider.uiAuth?.kind) !== undefined ? { ui: provider.uiAuth.kind } : {}),
249
+ })),
250
+ identity: Object.fromEntries(IDENTITY_KEYS.map((key) => [key, typeof config.identity[key] === "function" ? "custom" : "default"])),
251
+ pools: Object.entries(config.pools ?? {}).map(([name, pool]) => ({
252
+ trust: pool.trust,
253
+ name,
254
+ path: `/mcp/${name}`,
255
+ tools: pool.tools.map(String),
256
+ hasGrant: typeof pool.grant === "function",
257
+ })),
258
+ modules: {
259
+ ui: { enabled: config.ui !== undefined },
260
+ vault: config.vault
261
+ ? {
262
+ enabled: true,
263
+ sealsOAuth: typeof config.vault.seal === "function" && typeof config.vault.open === "function",
264
+ }
265
+ : { enabled: false },
266
+ activity: config.activity
267
+ ? {
268
+ enabled: true,
269
+ readable: typeof config.activity.store.list === "function",
270
+ ...(str(config.activity.deploymentId) !== undefined ? { deploymentId: config.activity.deploymentId } : {}),
271
+ store: {
272
+ kind: storeKind(activityStore),
273
+ ...(num(activityStore?.retentionDays) !== undefined
274
+ ? { retentionDays: activityStore.retentionDays }
275
+ : {}),
276
+ },
277
+ }
278
+ : { enabled: false },
279
+ accessTokens: config.accessTokens
280
+ ? {
281
+ enabled: true,
282
+ ...(num(accessTokens?.maxActive) !== undefined ? { maxActive: accessTokens.maxActive } : {}),
283
+ }
284
+ : { enabled: false },
285
+ },
286
+ storage: {
287
+ configured: raw.storage !== undefined,
288
+ kind: storeKind(storage.describe?.()),
289
+ },
290
+ branding: {
291
+ productName: brand.productName,
292
+ ...(productUrl !== undefined ? { productUrl } : {}),
293
+ ...(brand.ownerName !== undefined ? { ownerName: brand.ownerName } : {}),
294
+ ...(ownerUrl !== undefined ? { ownerUrl } : {}),
295
+ description: brand.description,
296
+ faviconHref: describedHref(brand.faviconHref) ?? "/favicon.svg",
297
+ themeColor: brand.themeColor,
298
+ theme: {
299
+ ...pick(brand.theme, ["accent", "radius", "fontFamily", "monoFamily"], str),
300
+ colorScheme: brand.theme.colorScheme,
301
+ },
302
+ },
303
+ deploymentInfo: Object.keys(config.deploymentInfo ?? {}),
304
+ connectors: config.connectors.map((connector) => describeConnector(connector, config.calls.maxResultBytes, input.registry)),
305
+ };
306
+ return deepFreeze(description);
307
+ }
308
+ function deepFreeze(value) {
309
+ if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
310
+ Object.freeze(value);
311
+ for (const item of Object.values(value))
312
+ deepFreeze(item);
313
+ }
314
+ return value;
315
+ }
@@ -0,0 +1,106 @@
1
+ // Small, allowlisting builders shared by every `Connector.describe()` and by
2
+ // `describeConfig()` itself. Each one copies named fields of known types and
3
+ // nothing else, so a value that carries a secret — a URL's userinfo or query,
4
+ // a header's value, a function's source — has no path into a description.
5
+ /**
6
+ * Origin and path of an absolute http(s) URL; undefined for anything else.
7
+ *
8
+ * The scheme is allowlisted, not just parsed: `blob:` and other schemes that
9
+ * wrap a URL report the inner URL's origin while their `pathname` is the
10
+ * whole inner URL, userinfo included, and `data:` or `javascript:` carry
11
+ * their payload in the path. Every field a description reports is a web
12
+ * URL, so any other scheme is omitted rather than reduced.
13
+ */
14
+ export function describedEndpoint(value) {
15
+ if (typeof value !== "string" && !(value instanceof URL))
16
+ return undefined;
17
+ try {
18
+ const url = new URL(value);
19
+ if ((url.protocol !== "https:" && url.protocol !== "http:") || url.origin === "null")
20
+ return undefined;
21
+ return { origin: url.origin, path: url.pathname };
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ /**
28
+ * An absolute URL as `origin + path`: userinfo, query, and fragment dropped.
29
+ * Every URL a description emits as a string goes through this or
30
+ * {@link describedHref}, so none carries a token a deployment put in it.
31
+ */
32
+ export function describedUrl(value) {
33
+ const endpoint = describedEndpoint(value);
34
+ return endpoint ? `${endpoint.origin}${endpoint.path}` : undefined;
35
+ }
36
+ /** An origin alone, from any absolute URL. */
37
+ export function describedOrigin(value) {
38
+ return describedEndpoint(value)?.origin;
39
+ }
40
+ /**
41
+ * An href that may be root-relative (`/favicon.svg?v=…`): an absolute one as
42
+ * {@link describedUrl}, a relative one as its path alone.
43
+ */
44
+ export function describedHref(value) {
45
+ if (typeof value !== "string")
46
+ return undefined;
47
+ const absolute = describedUrl(value);
48
+ if (absolute !== undefined)
49
+ return absolute;
50
+ if (!value.startsWith("/") || value.startsWith("//"))
51
+ return undefined;
52
+ try {
53
+ return new URL(value, "https://connecta.invalid").pathname;
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ const ANNOTATION_KEYS = {
60
+ title: "string",
61
+ readOnlyHint: "boolean",
62
+ destructiveHint: "boolean",
63
+ idempotentHint: "boolean",
64
+ openWorldHint: "boolean",
65
+ };
66
+ function describedAnnotations(value) {
67
+ if (typeof value !== "object" || value === null)
68
+ return undefined;
69
+ const out = {};
70
+ for (const [key, type] of Object.entries(ANNOTATION_KEYS)) {
71
+ const item = value[key];
72
+ if (typeof item === type)
73
+ out[key] = item;
74
+ }
75
+ return Object.keys(out).length > 0 ? out : undefined;
76
+ }
77
+ /**
78
+ * A JSON Schema as plain data. Schemas are declared in source and describe
79
+ * arguments, never hold them; the round trip drops anything that is not JSON.
80
+ */
81
+ function describedSchema(value) {
82
+ if (typeof value !== "object" || value === null || Array.isArray(value))
83
+ return undefined;
84
+ try {
85
+ return JSON.parse(JSON.stringify(value));
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ }
91
+ /** Static tool definitions as `describeConfig()` reports them. */
92
+ export function describedTools(tools) {
93
+ return tools.map((tool) => {
94
+ const annotations = describedAnnotations(tool.annotations);
95
+ const inputSchema = describedSchema(tool.inputSchema);
96
+ const outputSchema = describedSchema(tool.outputSchema);
97
+ return {
98
+ name: String(tool.name),
99
+ ...(typeof tool.description === "string" ? { description: tool.description } : {}),
100
+ ...(annotations ? { annotations } : {}),
101
+ ...(inputSchema ? { inputSchema } : {}),
102
+ ...(outputSchema ? { outputSchema } : {}),
103
+ ...(tool.classification ? { classification: tool.classification } : {}),
104
+ };
105
+ });
106
+ }
@@ -0,0 +1,128 @@
1
+ import { ConnectorCallError } from "./errors.js";
2
+ import { percentDecoded } from "./credential-url.js";
3
+ export function isDownstreamInputResult(value) {
4
+ return value !== null && typeof value === "object" && "resultType" in value && value.resultType === "input_required";
5
+ }
6
+ const continuations = new WeakMap();
7
+ const capabilities = new WeakMap();
8
+ export function bindDownstreamCapabilities(scope, declared) {
9
+ capabilities.set(scope, declared);
10
+ }
11
+ /** Only the relayable declarations travel to the downstream SDK, never grants. */
12
+ export function downstreamInputCapabilities(scope) {
13
+ return capabilities.get(scope) ?? {};
14
+ }
15
+ export function bindDownstreamContinuation(scope, target) {
16
+ continuations.set(scope, target);
17
+ }
18
+ export function clearDownstreamContinuation(scope) {
19
+ continuations.delete(scope);
20
+ }
21
+ /** The invocation's schema-filtered snapshot, never raw continuation arguments. */
22
+ export function recordDownstreamArgumentEcho(scope, echo) {
23
+ const continuation = continuations.get(scope);
24
+ if (continuation)
25
+ continuation.argumentEcho = echo;
26
+ }
27
+ export function downstreamArgumentEcho(scope) {
28
+ return continuations.get(scope)?.argumentEcho ?? {};
29
+ }
30
+ export function downstreamWriteContinuation(scope) {
31
+ return continuations.get(scope)?.write === true;
32
+ }
33
+ /** Check raw output before redaction, paging, caching, or shape observation. */
34
+ export function assertNoPrivateStateEchoes(value, states) {
35
+ const refused = () => new ConnectorCallError("input_required_invalid", "The downstream response exposes private continuation state.");
36
+ try {
37
+ // Literal matching avoids constructing a regex from a 64 KiB opaque value.
38
+ // There is no credential length floor, and percent hex is case insensitive.
39
+ const percent = (text) => text.replace(/%[0-9a-f]{2}/gi, (hex) => hex.toUpperCase());
40
+ const forms = new Set();
41
+ for (const state of states)
42
+ if (state)
43
+ for (const framed of [state, `Bearer ${state}`, `token ${state}`]) {
44
+ forms.add(percent(framed));
45
+ try {
46
+ forms.add(percent(encodeURIComponent(framed)));
47
+ forms.add(percent(encodeURI(framed)));
48
+ }
49
+ catch {
50
+ /* Lone surrogates still match literal and JSON-escaped views. */
51
+ }
52
+ forms.add(percent(new URLSearchParams({ value: framed }).toString().slice(6)));
53
+ let binary = "";
54
+ for (const byte of new TextEncoder().encode(framed))
55
+ binary += String.fromCharCode(byte);
56
+ const encoded = btoa(binary);
57
+ forms.add(encoded);
58
+ forms.add(encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""));
59
+ }
60
+ if (!forms.size)
61
+ return;
62
+ const escapes = {
63
+ '"': '"',
64
+ "\\": "\\",
65
+ "/": "/",
66
+ b: "\b",
67
+ f: "\f",
68
+ n: "\n",
69
+ r: "\r",
70
+ t: "\t",
71
+ };
72
+ const matches = (text) => {
73
+ for (let pass = 0; pass < 3; pass++) {
74
+ const unescaped = percentDecoded(text);
75
+ const views = [percent(text), percent(unescaped)];
76
+ if ([...forms].some((form) => views.some((view) => view.includes(form))))
77
+ return true;
78
+ const decoded = unescaped.replace(/\\(?:u[0-9a-fA-F]{4}|["\\/bfnrt])/g, (escape) => escape[1] === "u" ? String.fromCharCode(parseInt(escape.slice(2), 16)) : escapes[escape[1]]);
79
+ if (decoded === text)
80
+ break;
81
+ text = decoded;
82
+ }
83
+ return false;
84
+ };
85
+ const seen = new WeakSet();
86
+ const visit = (item) => {
87
+ if (typeof item === "string")
88
+ return matches(item);
89
+ if (item === null || typeof item === "boolean" || typeof item === "number")
90
+ return matches(JSON.stringify(item));
91
+ if (!item || typeof item !== "object" || seen.has(item))
92
+ return false;
93
+ seen.add(item);
94
+ if (item instanceof Error && matches(item.message))
95
+ return true;
96
+ const content = Object.getOwnPropertyDescriptor(item, "content")?.value;
97
+ if (Array.isArray(content)) {
98
+ const texts = content
99
+ .filter((block) => block?.type === "text" && typeof block.text === "string")
100
+ .map((block) => block.text);
101
+ if (matches(texts.join("")) || matches(texts.join("\n")))
102
+ return true;
103
+ }
104
+ return Object.entries(Object.getOwnPropertyDescriptors(item)).some(([key, descriptor]) => matches(key) || ("value" in descriptor && visit(descriptor.value)));
105
+ };
106
+ if (visit(value))
107
+ throw refused();
108
+ }
109
+ catch {
110
+ // A privacy-check failure must never expose a matcher diagnostic or state.
111
+ throw refused();
112
+ }
113
+ }
114
+ export function assertDownstreamOutputSafe(scope, value) {
115
+ const continuation = continuations.get(scope);
116
+ if (continuation)
117
+ assertNoPrivateStateEchoes(value, continuation.privateStates);
118
+ }
119
+ /** The host binds one verified target before invocation constructs its context. */
120
+ export function downstreamContinuation(scope, connector, address) {
121
+ const continuation = continuations.get(scope);
122
+ if (!continuation)
123
+ return undefined;
124
+ if (continuation.connector !== connector || continuation.address !== address) {
125
+ throw new ConnectorCallError("input_required_invalid", "The resolved target no longer matches this continuation.");
126
+ }
127
+ return continuation.input;
128
+ }