@zackbart/connecta 0.28.1 → 0.29.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 (319) hide show
  1. package/CHANGELOG.md +788 -5
  2. package/README.md +62 -42
  3. package/bin/connecta.mjs +110 -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/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1584 -1580
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +93 -71
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +59 -7
  52. package/dist/connectors/api-connector.js +106 -19
  53. package/dist/connectors/api.js +4 -1
  54. package/dist/connectors/guarded-fetch.d.ts +144 -0
  55. package/dist/connectors/guarded-fetch.js +84 -35
  56. package/dist/connectors/negotiation-cache.js +46 -0
  57. package/dist/connectors/option-shapes.js +27 -0
  58. package/dist/connectors/remote-mcp.d.ts +30 -4
  59. package/dist/connectors/remote-mcp.js +1774 -320
  60. package/dist/connectors/resource-uri.js +335 -0
  61. package/dist/credential-contract.d.ts +10 -0
  62. package/dist/credential-rules.js +1 -3
  63. package/dist/credential-url.js +108 -0
  64. package/dist/credentials.d.ts +6 -1
  65. package/dist/credentials.js +27 -18
  66. package/dist/d1.d.ts +130 -0
  67. package/dist/d1.js +290 -0
  68. package/dist/describe-config.d.ts +165 -0
  69. package/dist/describe-config.js +315 -0
  70. package/dist/described.js +106 -0
  71. package/dist/downstream-input-context.js +119 -0
  72. package/dist/downstream-input.js +323 -0
  73. package/dist/errors.d.ts +97 -20
  74. package/dist/errors.js +158 -80
  75. package/dist/execute.js +453 -305
  76. package/dist/executor-admission.d.ts +1 -0
  77. package/dist/executor-admission.js +11 -15
  78. package/dist/executor-contract.d.ts +20 -0
  79. package/dist/executor-contract.js +48 -0
  80. package/dist/executor-result.js +24 -9
  81. package/dist/executors/quickjs-child.js +3 -4
  82. package/dist/executors/quickjs-runtime.js +147 -59
  83. package/dist/executors/quickjs.js +29 -35
  84. package/dist/favicon.js +1 -2
  85. package/dist/fix-prompt.js +1 -3
  86. package/dist/guest-runtime.js +188 -0
  87. package/dist/guest-types.d.ts +176 -0
  88. package/dist/guest-types.js +1 -0
  89. package/dist/html-security.js +24 -0
  90. package/dist/identity.js +1 -3
  91. package/dist/inbound-credential.js +52 -0
  92. package/dist/index.d.ts +24 -256
  93. package/dist/index.js +65 -478
  94. package/dist/invocation-auth.js +69 -0
  95. package/dist/invocation.js +230 -73
  96. package/dist/json-schema.d.ts +1 -1
  97. package/dist/mcp-client-context.js +35 -0
  98. package/dist/mcp-result.js +11 -7
  99. package/dist/meta-output.js +76 -0
  100. package/dist/meta-tool-names.d.ts +2 -0
  101. package/dist/meta-tool-names.js +3 -0
  102. package/dist/meta-tools.js +285 -209
  103. package/dist/module-contracts.d.ts +8 -23
  104. package/dist/node.d.ts +0 -2
  105. package/dist/node.js +2 -2
  106. package/dist/oauth-callback-outcome.js +1 -3
  107. package/dist/oauth-handoff.js +117 -0
  108. package/dist/oauth-sealing.js +20 -0
  109. package/dist/operator-record.d.ts +137 -0
  110. package/dist/operator-record.js +420 -0
  111. package/dist/operator-ui/assets.js +31 -0
  112. package/dist/operator-ui/contract.d.ts +72 -0
  113. package/dist/operator-ui/contract.js +1 -0
  114. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  115. package/dist/operator-ui/generated.js +5 -4
  116. package/dist/operator-ui/model.d.ts +12 -87
  117. package/dist/operator-ui/model.js +2 -9
  118. package/dist/operator-ui/setup-commands.js +1 -3
  119. package/dist/operator-ui/view.d.ts +323 -0
  120. package/dist/operator-ui/view.js +62 -189
  121. package/dist/page-styles.js +2 -3
  122. package/dist/program-source.js +14 -2
  123. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  124. package/dist/provider.d.ts +142 -0
  125. package/dist/provider.js +187 -0
  126. package/dist/providers/_shared/google/result-size.js +57 -0
  127. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  128. package/dist/providers/_shared/google/service-account.js +500 -0
  129. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  130. package/dist/providers/_shared/google/workspace.js +518 -0
  131. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  132. package/dist/providers/basecamp/index.js +997 -0
  133. package/dist/providers/basecamp/skill.generated.js +9 -0
  134. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  135. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  136. package/dist/providers/breeze/skill.generated.js +9 -0
  137. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  138. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  139. package/dist/providers/ccb/skill.generated.js +9 -0
  140. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  141. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  142. package/dist/providers/cloudflare/skill.generated.js +10 -0
  143. package/dist/providers/docs/index.d.ts +54 -0
  144. package/dist/providers/docs/index.js +1237 -0
  145. package/dist/providers/docs/skill.generated.js +9 -0
  146. package/dist/providers/drive/index.d.ts +53 -0
  147. package/dist/providers/drive/index.js +1523 -0
  148. package/dist/providers/drive/skill.generated.js +9 -0
  149. package/dist/providers/forms/index.d.ts +54 -0
  150. package/dist/providers/forms/index.js +1217 -0
  151. package/dist/providers/forms/skill.generated.js +11 -0
  152. package/dist/providers/github/auth.d.ts +32 -0
  153. package/dist/providers/github/auth.js +361 -0
  154. package/dist/providers/github/hosted.js +218 -0
  155. package/dist/providers/github/index.d.ts +13 -0
  156. package/dist/providers/github/index.js +140 -0
  157. package/dist/providers/github/key.js +100 -0
  158. package/dist/providers/github/rest.js +321 -0
  159. package/dist/providers/github/scope.d.ts +29 -0
  160. package/dist/providers/github/scope.js +124 -0
  161. package/dist/providers/github/skill.generated.js +8 -0
  162. package/dist/providers/gmail/index.d.ts +51 -0
  163. package/dist/providers/gmail/index.js +1436 -0
  164. package/dist/providers/gmail/skill.generated.js +9 -0
  165. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  166. package/dist/providers/linear/index.js +352 -0
  167. package/dist/providers/linear/skill.generated.js +8 -0
  168. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  169. package/dist/providers/mixpanel/index.js +416 -0
  170. package/dist/providers/mixpanel/skill.generated.js +9 -0
  171. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  172. package/dist/providers/{notion.js → notion/index.js} +226 -239
  173. package/dist/providers/notion/skill.generated.js +10 -0
  174. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  175. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  176. package/dist/providers/overflow/skill.generated.js +8 -0
  177. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  178. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  179. package/dist/providers/planning-center/skill.generated.js +10 -0
  180. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  181. package/dist/providers/revenuecat/index.js +557 -0
  182. package/dist/providers/revenuecat/skill.generated.js +11 -0
  183. package/dist/providers/sheets/index.d.ts +54 -0
  184. package/dist/providers/sheets/index.js +1237 -0
  185. package/dist/providers/sheets/skill.generated.js +10 -0
  186. package/dist/providers/slides/index.d.ts +53 -0
  187. package/dist/providers/slides/index.js +2645 -0
  188. package/dist/providers/slides/skill.generated.js +9 -0
  189. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  190. package/dist/providers/stripe/index.js +266 -0
  191. package/dist/providers/stripe/skill.generated.js +11 -0
  192. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  193. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  194. package/dist/providers/tithely/skill.generated.js +8 -0
  195. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  196. package/dist/providers/vercel/index.js +1038 -0
  197. package/dist/providers/vercel/mcp-ownership.js +212 -0
  198. package/dist/providers/vercel/skill.generated.js +11 -0
  199. package/dist/registry.d.ts +108 -171
  200. package/dist/registry.js +513 -925
  201. package/dist/request-state.js +21 -0
  202. package/dist/resource-read.js +100 -0
  203. package/dist/result-shapes.js +17 -49
  204. package/dist/retired-toolkits.js +1 -1
  205. package/dist/routes/access-tokens.js +3 -6
  206. package/dist/routes/activity-context.js +21 -0
  207. package/dist/routes/activity-disclosure.js +40 -0
  208. package/dist/routes/activity.js +59 -21
  209. package/dist/routes/actor-labels.js +3 -50
  210. package/dist/routes/connect.js +221 -0
  211. package/dist/routes/credentials.js +15 -27
  212. package/dist/routes/mcp.js +356 -130
  213. package/dist/routes/oauth-client-metadata.js +15 -0
  214. package/dist/routes/oauth-management.js +45 -107
  215. package/dist/routes/oauth.js +110 -57
  216. package/dist/routes/operator.js +3 -3
  217. package/dist/routes/shared.d.ts +25 -64
  218. package/dist/routes/shared.js +160 -80
  219. package/dist/routes/skills.js +30 -0
  220. package/dist/routes/ui-config.js +225 -0
  221. package/dist/routes/ui-data.js +40 -35
  222. package/dist/routes/ui.js +66 -56
  223. package/dist/runtime/admission.js +4 -2
  224. package/dist/runtime/call-admission.js +3 -1
  225. package/dist/runtime/connector-scope.js +1 -4
  226. package/dist/runtime/run.js +10 -39
  227. package/dist/runtime/services.js +12 -117
  228. package/dist/runtime/shared-read.js +1 -3
  229. package/dist/runtime/storage.js +8 -1
  230. package/dist/sent-secrets.d.ts +52 -0
  231. package/dist/sent-secrets.js +474 -0
  232. package/dist/server.js +28 -71
  233. package/dist/skills.js +406 -174
  234. package/dist/sqlite.d.ts +48 -0
  235. package/dist/sqlite.js +166 -0
  236. package/dist/storage/keys.js +428 -0
  237. package/dist/storage/memory.js +7 -0
  238. package/dist/storage/sql.d.ts +66 -0
  239. package/dist/storage/sql.js +534 -0
  240. package/dist/timeout.js +4 -2
  241. package/dist/tool-name.js +17 -0
  242. package/dist/tool-safety.d.ts +9 -35
  243. package/dist/tool-safety.js +20 -40
  244. package/dist/types.d.ts +320 -54
  245. package/dist/types.js +0 -1
  246. package/dist/ui.d.ts +7 -19
  247. package/dist/ui.js +76 -90
  248. package/dist/url-safety.js +3 -6
  249. package/dist/usage-guide.js +137 -0
  250. package/dist/validate.d.ts +7 -0
  251. package/dist/validate.js +309 -47
  252. package/dist/version.d.ts +1 -7
  253. package/dist/version.js +3 -7
  254. package/dist/worker.d.ts +11 -0
  255. package/dist/worker.js +434 -0
  256. package/documentation/architecture.md +664 -231
  257. package/documentation/auth.md +936 -546
  258. package/documentation/code-mode.md +501 -312
  259. package/documentation/meta-tools.md +449 -200
  260. package/documentation/operator-tests.md +93 -0
  261. package/documentation/provider-migration-0.29.md +60 -0
  262. package/examples/worker/AGENTS.md +34 -12
  263. package/examples/worker/README.md +286 -255
  264. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  265. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  266. package/examples/worker/src/connecta.config.ts +151 -0
  267. package/examples/worker/src/index.ts +14 -197
  268. package/examples/worker/wrangler.jsonc +15 -35
  269. package/package.json +120 -54
  270. package/templates/node/.dockerignore +1 -2
  271. package/templates/node/.env.example +20 -21
  272. package/templates/node/AGENTS.md +21 -17
  273. package/templates/node/Dockerfile +3 -5
  274. package/templates/node/README.md +133 -58
  275. package/templates/node/docker-compose.yml +11 -12
  276. package/templates/node/package.json +4 -2
  277. package/templates/node/src/connecta.config.ts +119 -0
  278. package/templates/node/src/index.ts +9 -154
  279. package/templates/node/src/provision-token.ts +17 -0
  280. package/AGENTS.md +0 -170
  281. package/dist/artifacts/connector.d.ts +0 -58
  282. package/dist/artifacts/connector.js +0 -730
  283. package/dist/artifacts/document.js +0 -86
  284. package/dist/artifacts/guide.js +0 -140
  285. package/dist/artifacts/html-scan.js +0 -329
  286. package/dist/artifacts/json.js +0 -78
  287. package/dist/artifacts/kv-store.d.ts +0 -20
  288. package/dist/artifacts/kv-store.js +0 -140
  289. package/dist/artifacts/markdown.js +0 -478
  290. package/dist/artifacts/module.d.ts +0 -37
  291. package/dist/artifacts/module.js +0 -66
  292. package/dist/artifacts/operations.d.ts +0 -197
  293. package/dist/artifacts/operations.js +0 -1002
  294. package/dist/artifacts/refresh.d.ts +0 -55
  295. package/dist/artifacts/refresh.js +0 -236
  296. package/dist/artifacts/routes.js +0 -206
  297. package/dist/artifacts/types.d.ts +0 -214
  298. package/dist/artifacts/types.js +0 -22
  299. package/dist/artifacts/validate.d.ts +0 -62
  300. package/dist/artifacts/validate.js +0 -651
  301. package/dist/artifacts.d.ts +0 -6
  302. package/dist/artifacts.js +0 -9
  303. package/dist/auth/bearer.d.ts +0 -11
  304. package/dist/auth/bearer.js +0 -50
  305. package/dist/providers/basecamp.js +0 -445
  306. package/dist/providers/linear.js +0 -217
  307. package/dist/providers/mixpanel.js +0 -247
  308. package/dist/providers/revenuecat.js +0 -317
  309. package/dist/providers/stripe.js +0 -253
  310. package/dist/providers/vercel.js +0 -1253
  311. package/dist/storage/file.d.ts +0 -15
  312. package/dist/storage/file.js +0 -455
  313. package/ethos.md +0 -93
  314. package/examples/worker/src/cloudflare-kv.ts +0 -43
  315. package/examples/worker/src/d1-activity-row.ts +0 -155
  316. package/examples/worker/src/d1-activity.ts +0 -147
  317. package/examples/worker/src/d1-storage.ts +0 -138
  318. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  319. 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,119 @@
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
+ export function downstreamWriteContinuation(scope) {
22
+ return continuations.get(scope)?.write === true;
23
+ }
24
+ /** Check raw output before redaction, paging, caching, or shape observation. */
25
+ export function assertNoPrivateStateEchoes(value, states) {
26
+ const refused = () => new ConnectorCallError("input_required_invalid", "The downstream response exposes private continuation state.");
27
+ try {
28
+ // Literal matching avoids constructing a regex from a 64 KiB opaque value.
29
+ // There is no credential length floor, and percent hex is case insensitive.
30
+ const percent = (text) => text.replace(/%[0-9a-f]{2}/gi, (hex) => hex.toUpperCase());
31
+ const forms = new Set();
32
+ for (const state of states)
33
+ if (state)
34
+ for (const framed of [state, `Bearer ${state}`, `token ${state}`]) {
35
+ forms.add(percent(framed));
36
+ try {
37
+ forms.add(percent(encodeURIComponent(framed)));
38
+ forms.add(percent(encodeURI(framed)));
39
+ }
40
+ catch {
41
+ /* Lone surrogates still match literal and JSON-escaped views. */
42
+ }
43
+ forms.add(percent(new URLSearchParams({ value: framed }).toString().slice(6)));
44
+ let binary = "";
45
+ for (const byte of new TextEncoder().encode(framed))
46
+ binary += String.fromCharCode(byte);
47
+ const encoded = btoa(binary);
48
+ forms.add(encoded);
49
+ forms.add(encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""));
50
+ }
51
+ if (!forms.size)
52
+ return;
53
+ const escapes = {
54
+ '"': '"',
55
+ "\\": "\\",
56
+ "/": "/",
57
+ b: "\b",
58
+ f: "\f",
59
+ n: "\n",
60
+ r: "\r",
61
+ t: "\t",
62
+ };
63
+ const matches = (text) => {
64
+ for (let pass = 0; pass < 3; pass++) {
65
+ const unescaped = percentDecoded(text);
66
+ const views = [percent(text), percent(unescaped)];
67
+ if ([...forms].some((form) => views.some((view) => view.includes(form))))
68
+ return true;
69
+ 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]]);
70
+ if (decoded === text)
71
+ break;
72
+ text = decoded;
73
+ }
74
+ return false;
75
+ };
76
+ const seen = new WeakSet();
77
+ const visit = (item) => {
78
+ if (typeof item === "string")
79
+ return matches(item);
80
+ if (item === null || typeof item === "boolean" || typeof item === "number")
81
+ return matches(JSON.stringify(item));
82
+ if (!item || typeof item !== "object" || seen.has(item))
83
+ return false;
84
+ seen.add(item);
85
+ if (item instanceof Error && matches(item.message))
86
+ return true;
87
+ const content = Object.getOwnPropertyDescriptor(item, "content")?.value;
88
+ if (Array.isArray(content)) {
89
+ const texts = content
90
+ .filter((block) => block?.type === "text" && typeof block.text === "string")
91
+ .map((block) => block.text);
92
+ if (matches(texts.join("")) || matches(texts.join("\n")))
93
+ return true;
94
+ }
95
+ return Object.entries(Object.getOwnPropertyDescriptors(item)).some(([key, descriptor]) => matches(key) || ("value" in descriptor && visit(descriptor.value)));
96
+ };
97
+ if (visit(value))
98
+ throw refused();
99
+ }
100
+ catch {
101
+ // A privacy-check failure must never expose a matcher diagnostic or state.
102
+ throw refused();
103
+ }
104
+ }
105
+ export function assertDownstreamOutputSafe(scope, value) {
106
+ const continuation = continuations.get(scope);
107
+ if (continuation)
108
+ assertNoPrivateStateEchoes(value, continuation.privateStates);
109
+ }
110
+ /** The host binds one verified target before invocation constructs its context. */
111
+ export function downstreamContinuation(scope, connector, address) {
112
+ const continuation = continuations.get(scope);
113
+ if (!continuation)
114
+ return undefined;
115
+ if (continuation.connector !== connector || continuation.address !== address) {
116
+ throw new ConnectorCallError("input_required_invalid", "The resolved target no longer matches this continuation.");
117
+ }
118
+ return continuation.input;
119
+ }