@zackbart/connecta 0.38.0 → 0.40.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 (180) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +3 -2
  3. package/bin/connecta.mjs +4 -8
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +6 -4
  6. package/dist/agent-routes.js +231 -0
  7. package/dist/auth/downstream-oauth/consent.js +519 -0
  8. package/dist/auth/downstream-oauth/grant-store.js +439 -0
  9. package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
  10. package/dist/auth/downstream-oauth/refresh-records.js +447 -0
  11. package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
  12. package/dist/auth/downstream-oauth.js +66 -2382
  13. package/dist/auth/native.js +37 -0
  14. package/dist/auth/oauth-grant.js +13 -0
  15. package/dist/auth/static-oauth.js +153 -131
  16. package/dist/auth-elicitation.js +7 -1
  17. package/dist/authorization-handoff.js +11 -5
  18. package/dist/catalog-cache.js +219 -59
  19. package/dist/catalog-policy.js +18 -0
  20. package/dist/catalog-service.js +284 -47
  21. package/dist/catalog.js +18 -2
  22. package/dist/config.d.ts +16 -11
  23. package/dist/config.js +9 -6
  24. package/dist/connector-caller.d.ts +1 -1
  25. package/dist/connector-health.js +59 -0
  26. package/dist/connector-status.js +117 -0
  27. package/dist/connectors/api-connector.d.ts +7 -2
  28. package/dist/connectors/api-connector.js +7 -4
  29. package/dist/connectors/negotiation-cache.js +22 -4
  30. package/dist/connectors/remote-mcp/call.js +164 -0
  31. package/dist/connectors/remote-mcp/catalog.js +278 -0
  32. package/dist/connectors/remote-mcp/config.js +263 -0
  33. package/dist/connectors/remote-mcp/connection.js +682 -0
  34. package/dist/connectors/remote-mcp/errors.js +488 -0
  35. package/dist/connectors/remote-mcp/oauth.js +473 -0
  36. package/dist/connectors/remote-mcp/resources.js +473 -0
  37. package/dist/connectors/remote-mcp/runtime.js +1 -0
  38. package/dist/connectors/remote-mcp/transport.js +555 -0
  39. package/dist/connectors/remote-mcp.js +62 -2863
  40. package/dist/credential-contract.d.ts +23 -0
  41. package/dist/credential-reads.js +17 -0
  42. package/dist/credential-verification.js +53 -0
  43. package/dist/credentials.d.ts +7 -0
  44. package/dist/credentials.js +60 -25
  45. package/dist/describe-config.d.ts +12 -2
  46. package/dist/describe-config.js +2 -0
  47. package/dist/discovery-health.js +199 -0
  48. package/dist/downstream-input.js +2 -0
  49. package/dist/errors.d.ts +55 -17
  50. package/dist/errors.js +95 -41
  51. package/dist/execute.js +152 -114
  52. package/dist/executor-result.js +3 -1
  53. package/dist/guest-functions.js +83 -0
  54. package/dist/guest-runtime.js +60 -19
  55. package/dist/guest-types.d.ts +19 -5
  56. package/dist/http/argument-problems.js +35 -0
  57. package/dist/http/errors.js +5 -4
  58. package/dist/http/fields.js +192 -13
  59. package/dist/http/hatch-response.js +73 -0
  60. package/dist/http/hatch.js +88 -23
  61. package/dist/http/oauth.js +3 -2
  62. package/dist/http/provider.js +32 -4
  63. package/dist/http/safety.js +8 -0
  64. package/dist/http/tools.js +44 -23
  65. package/dist/index.d.ts +1 -1
  66. package/dist/index.js +9 -2
  67. package/dist/invocation.js +26 -8
  68. package/dist/membership.d.ts +13 -5
  69. package/dist/membership.js +34 -2
  70. package/dist/meta-tools.js +54 -31
  71. package/dist/native-recovery.js +4 -16
  72. package/dist/native-tools.js +5 -2
  73. package/dist/oauth-sealing.js +10 -0
  74. package/dist/operation-vocabulary.js +135 -13
  75. package/dist/operator-record.d.ts +2 -2
  76. package/dist/operator-record.js +1 -0
  77. package/dist/operator-ui/contract.d.ts +10 -0
  78. package/dist/operator-ui/generated.js +2 -2
  79. package/dist/operator-ui/model.d.ts +22 -1
  80. package/dist/provider-call-facts.js +6 -2
  81. package/dist/providers/_shared/rest/operation-index.js +48 -50
  82. package/dist/providers/_shared/rest/value-safety.js +80 -0
  83. package/dist/providers/breeze/api.js +7 -1
  84. package/dist/providers/breeze/skill.generated.js +1 -1
  85. package/dist/providers/breeze/tools.js +4 -3
  86. package/dist/providers/ccb/api.js +2 -1
  87. package/dist/providers/ccb/index.js +5 -0
  88. package/dist/providers/ccb/skill.generated.js +1 -1
  89. package/dist/providers/cloudflare/api.js +8 -1
  90. package/dist/providers/cloudflare/skill.generated.js +1 -1
  91. package/dist/providers/docs/lib/schemas.js +24 -22
  92. package/dist/providers/docs/safety.js +2 -0
  93. package/dist/providers/docs/skill.generated.js +1 -1
  94. package/dist/providers/docs/tools.js +2 -4
  95. package/dist/providers/drive/api.js +1 -0
  96. package/dist/providers/drive/lib/contracts.js +8 -5
  97. package/dist/providers/drive/skill.generated.js +1 -1
  98. package/dist/providers/drive/tools.js +11 -16
  99. package/dist/providers/forms/api.js +1 -0
  100. package/dist/providers/forms/skill.generated.js +1 -1
  101. package/dist/providers/forms/tools.js +10 -7
  102. package/dist/providers/github/api.js +1 -0
  103. package/dist/providers/github/lib/discovery.js +1 -1
  104. package/dist/providers/github/skill.generated.js +1 -1
  105. package/dist/providers/github/tools.js +61 -17
  106. package/dist/providers/gmail/lib/schemas.js +60 -50
  107. package/dist/providers/gmail/skill.generated.js +1 -1
  108. package/dist/providers/gmail/tools.js +7 -5
  109. package/dist/providers/granola/index.js +2 -0
  110. package/dist/providers/granola/skill.generated.js +1 -1
  111. package/dist/providers/overflow/api.js +7 -1
  112. package/dist/providers/overflow/skill.generated.js +1 -1
  113. package/dist/providers/planning-center/api.js +3 -1
  114. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  115. package/dist/providers/planning-center/skill.generated.js +1 -1
  116. package/dist/providers/quickbooks/safety.js +80 -39
  117. package/dist/providers/quickbooks/skill.generated.js +1 -1
  118. package/dist/providers/sheets/api.js +1 -0
  119. package/dist/providers/sheets/skill.generated.js +1 -1
  120. package/dist/providers/sheets/tools.js +13 -14
  121. package/dist/providers/slides/skill.generated.js +1 -1
  122. package/dist/providers/slides/tools.js +15 -13
  123. package/dist/providers/stripe/skill.generated.js +1 -1
  124. package/dist/providers/tithely/api.js +3 -1
  125. package/dist/providers/tithely/skill.generated.js +1 -1
  126. package/dist/providers/vercel/api.js +1 -1
  127. package/dist/providers/vercel/skill.generated.js +1 -1
  128. package/dist/providers/vercel/tools.js +4 -3
  129. package/dist/registry.d.ts +10 -4
  130. package/dist/registry.js +41 -13
  131. package/dist/routes/credentials.js +129 -60
  132. package/dist/routes/dispatch.js +5 -1
  133. package/dist/routes/mcp.js +30 -14
  134. package/dist/routes/operator.js +2 -2
  135. package/dist/routes/shared.d.ts +6 -1
  136. package/dist/routes/shared.js +7 -1
  137. package/dist/routes/ui-config.js +2 -2
  138. package/dist/routes/ui-data.js +126 -20
  139. package/dist/routes/ui.js +16 -0
  140. package/dist/runtime/call.js +7 -2
  141. package/dist/runtime/native-storage.js +12 -0
  142. package/dist/runtime/request-reads.js +207 -0
  143. package/dist/runtime/request-scope.js +16 -0
  144. package/dist/runtime/storage.js +12 -0
  145. package/dist/skills.js +37 -20
  146. package/dist/storage/keys.js +53 -3
  147. package/dist/storage/memory.js +8 -2
  148. package/dist/storage/sql-native.js +53 -1
  149. package/dist/storage/sql.js +12 -2
  150. package/dist/storage-digest.js +32 -0
  151. package/dist/timeout.js +8 -0
  152. package/dist/types.d.ts +33 -0
  153. package/dist/usage-guide.js +16 -35
  154. package/dist/validate.d.ts +3 -0
  155. package/dist/validate.js +220 -158
  156. package/dist/version.d.ts +1 -1
  157. package/dist/version.js +1 -1
  158. package/dist/write-outcome.js +0 -3
  159. package/documentation/README.md +13 -13
  160. package/documentation/architecture.md +385 -20
  161. package/documentation/auth.md +233 -64
  162. package/documentation/code-mode.md +122 -39
  163. package/documentation/deploying.md +6 -3
  164. package/documentation/integrating.md +16 -2
  165. package/documentation/meta-tools.md +240 -49
  166. package/documentation/migration-0.33.md +5 -1
  167. package/documentation/migration-0.34.md +3 -1
  168. package/documentation/operator-tests.md +1 -1
  169. package/documentation/operator-ui.md +105 -12
  170. package/documentation/pool-membership-upgrade.md +44 -0
  171. package/documentation/providers.md +105 -0
  172. package/examples/worker/AGENTS.md +10 -10
  173. package/examples/worker/README.md +4 -3
  174. package/examples/worker/src/connecta.config.ts +1 -0
  175. package/guides/connecting-agents.md +2 -1
  176. package/guides/how-it-works.md +4 -2
  177. package/package.json +1 -1
  178. package/templates/node/AGENTS.md +13 -12
  179. package/templates/node/package.json +1 -1
  180. package/templates/node/src/connecta.config.ts +1 -0
@@ -0,0 +1,242 @@
1
+ import { authCall, authFlow, authInvoke, authMethod, authValue } from "../native.js";
2
+ import { discoveryIssuer } from "../oauth-grant.js";
3
+ import { aborted } from "./refresh-coordinator.js";
4
+ import { fingerprint } from "./grant-store.js";
5
+ import { oauthStateDigest, randomState } from "./consent.js";
6
+ /** KvOAuthProvider's SDK hooks; `authMethods` installs them. */
7
+ export const sdkHookMethods = {
8
+ clientInformation: function (ctx) {
9
+ return authFlow(function* () {
10
+ if (this.signal?.aborted)
11
+ throw aborted(this.signal);
12
+ const { body } = (yield authInvoke(this, "activeGrant", []));
13
+ if (this.signal?.aborted)
14
+ throw aborted(this.signal);
15
+ const client = this.clientOptions?.client;
16
+ if (!body.client && !this.allowAuthorization)
17
+ throw this.authorizationRefused();
18
+ if (client) {
19
+ if ((ctx && ctx.issuer !== client.issuer) || body.issuer !== client.issuer)
20
+ throw this.flowSuperseded();
21
+ if (body.client &&
22
+ (body.client.value.client_id !== client.clientId || body.client.binding !== this.clientBinding))
23
+ throw this.flowSuperseded();
24
+ return {
25
+ client_id: client.clientId,
26
+ issuer: client.issuer,
27
+ token_endpoint_auth_method: this.clientMetadata.token_endpoint_auth_method,
28
+ ...(client.clientSecret !== undefined ? { client_secret: client.clientSecret } : {}),
29
+ };
30
+ }
31
+ if (!body.client || (ctx && body.issuer !== ctx.issuer))
32
+ return undefined;
33
+ this.seen.client = fingerprint(body.client.value);
34
+ return ctx ? { ...body.client.value, issuer: ctx.issuer } : body.client.value;
35
+ }.bind(this));
36
+ },
37
+ saveClientInformation: function (info, ctx) {
38
+ return authFlow(function* () {
39
+ try {
40
+ const { body } = (yield authInvoke(this, "boundGrant", []));
41
+ const registrationPath = this.clientOptions?.client
42
+ ? "static"
43
+ : body.client?.value.client_id === info.client_id && body.client.registrationPath
44
+ ? body.client.registrationPath
45
+ : this.clientMetadataUrl &&
46
+ body.discovery?.authorizationServerMetadata?.client_id_metadata_document_supported === true
47
+ ? "cimd"
48
+ : "dcr";
49
+ (yield authInvoke(this, "writeCredential", [
50
+ "client",
51
+ {
52
+ value: this.clientOptions?.client ? { client_id: this.clientOptions.client.clientId } : info,
53
+ registrationPath,
54
+ ...(this.clientBinding !== undefined ? { binding: this.clientBinding } : {}),
55
+ },
56
+ ctx?.issuer,
57
+ ]));
58
+ }
59
+ catch (error) {
60
+ if (!this.signal?.aborted)
61
+ throw error;
62
+ }
63
+ }.bind(this));
64
+ },
65
+ registrationPath: function () {
66
+ return authFlow(function* () {
67
+ const { body } = (yield authInvoke(this, "boundGrant", []));
68
+ return body.client?.registrationPath;
69
+ }.bind(this));
70
+ },
71
+ validatedIssuerOrigin: function () {
72
+ return authFlow(function* () {
73
+ const { body } = (yield authInvoke(this, "boundGrant", []));
74
+ const state = body.discovery;
75
+ if (!state ||
76
+ state.authorizationServerMetadata?.issuer !== state.authorizationServerUrl ||
77
+ body.issuer !== state.authorizationServerUrl)
78
+ return undefined;
79
+ this.assertCredentialFree(state, body);
80
+ return new URL(state.authorizationServerUrl).origin;
81
+ }.bind(this));
82
+ },
83
+ discoveryState: function () {
84
+ return authFlow(function* () {
85
+ if (this.signal?.aborted)
86
+ throw aborted(this.signal);
87
+ const { body } = (yield authInvoke(this, "activeGrant", []));
88
+ if (this.signal?.aborted)
89
+ throw aborted(this.signal);
90
+ let state = body.discovery;
91
+ if (state?.resourceMetadataUrl && this.clientOptions?.resourceOrigin) {
92
+ let sameOrigin = false;
93
+ try {
94
+ sameOrigin = new URL(state.resourceMetadataUrl).origin === this.clientOptions.resourceOrigin;
95
+ }
96
+ catch {
97
+ // A malformed legacy URL is no more usable than a foreign one.
98
+ }
99
+ if (!sameOrigin) {
100
+ // Never hand the SDK a legacy foreign URL or its resource
101
+ // document. Keep the issuer binding for callbacks, but force
102
+ // same-origin resource discovery on every SDK path. This read
103
+ // does not mutate the stored grant.
104
+ state = { ...state };
105
+ delete state.resourceMetadataUrl;
106
+ delete state.resourceMetadata;
107
+ }
108
+ }
109
+ this.assertCredentialFree(state, body);
110
+ // Bind consent to durable discovery until the SDK saves a new
111
+ // state. A same-origin metadata 404 can leave the cache unchanged.
112
+ this.consentDiscovery = body.discovery;
113
+ return state;
114
+ }.bind(this));
115
+ },
116
+ saveDiscoveryState: function (state) {
117
+ return authFlow(function* () {
118
+ this.assertCredentialFree(state, (yield authInvoke(this, "boundGrant", [])).body);
119
+ const issuer = discoveryIssuer(state);
120
+ if (this.clientOptions?.client && issuer !== this.clientOptions.client.issuer)
121
+ throw this.flowSuperseded();
122
+ if (!this.allowAuthorization && !(yield authInvoke(this, "boundGrant", [])).body.client)
123
+ throw this.authorizationRefused();
124
+ this.consentDiscovery = state;
125
+ (yield authInvoke(this, "updateGrant", [
126
+ (grant) => {
127
+ const body = grant.body.issuer !== issuer ? (issuer !== undefined ? { issuer } : {}) : { ...grant.body };
128
+ this.assertCredentialFree(state, grant.body);
129
+ body.discovery = state;
130
+ return this.withIssuer(grant, body);
131
+ },
132
+ ]));
133
+ }.bind(this));
134
+ },
135
+ tokens: function (ctx) {
136
+ return authFlow(function* () {
137
+ if (this.signal?.aborted)
138
+ throw aborted(this.signal);
139
+ const grant = (yield authInvoke(this, "activeGrant", []));
140
+ if (this.signal?.aborted)
141
+ throw aborted(this.signal);
142
+ const { tokens } = grant.body;
143
+ if (!tokens || (ctx && grant.body.issuer !== ctx.issuer))
144
+ return undefined;
145
+ this.seen.tokens = fingerprint(tokens);
146
+ if (!ctx)
147
+ return tokens;
148
+ this.refreshBasis = { accessToken: tokens.access_token, epoch: grant.epoch, issuer: ctx.issuer };
149
+ return { ...tokens, issuer: ctx.issuer };
150
+ }.bind(this));
151
+ },
152
+ saveTokens: function (tokens, ctx) {
153
+ return authFlow(function* () {
154
+ if (this.signal?.aborted)
155
+ throw aborted(this.signal);
156
+ if (this.refreshFailure?.kind === "dead")
157
+ throw this.authorizationRefused();
158
+ // The coordinator stored this refresh already, or handed on another's.
159
+ if (this.storedRefresh !== undefined && tokens.access_token === this.storedRefresh)
160
+ return;
161
+ const refreshConsent = this.exchangeConsent &&
162
+ tokens.refresh_token !== undefined &&
163
+ this.exchangeConsent.digest ===
164
+ (yield authInvoke(this, "wait", [
165
+ authMethod(() => {
166
+ return authCall(oauthStateDigest, [tokens.refresh_token]);
167
+ }),
168
+ ]))
169
+ ? this.exchangeConsent
170
+ : undefined;
171
+ (yield authInvoke(this, "writeCredential", ["tokens", tokens, ctx?.issuer, refreshConsent]));
172
+ this.refreshFailure = undefined;
173
+ }.bind(this));
174
+ },
175
+ state: function () {
176
+ return authValue(() => {
177
+ if (!this.allowAuthorization)
178
+ throw this.authorizationRefused();
179
+ const state = randomState();
180
+ this.consent = { state };
181
+ return state;
182
+ });
183
+ },
184
+ saveCodeVerifier: function (verifier) {
185
+ return authValue(() => {
186
+ if (this.signal?.aborted)
187
+ throw aborted(this.signal);
188
+ if (!this.allowAuthorization)
189
+ throw this.authorizationRefused();
190
+ this.consent = { ...this.consent, verifier };
191
+ });
192
+ },
193
+ codeVerifier: function () {
194
+ return authValue(() => {
195
+ const verifier = this.callback?.flow.verifier ?? this.consent.verifier;
196
+ if (verifier === undefined) {
197
+ throw new Error(`No PKCE code verifier for "${this.connectorId}"`);
198
+ }
199
+ return verifier;
200
+ });
201
+ },
202
+ invalidateCredentials: function (scope) {
203
+ return authFlow(function* () {
204
+ if (this.signal?.aborted)
205
+ throw aborted(this.signal);
206
+ const fields = {
207
+ all: ["client", "tokens", "discovery"],
208
+ client: ["client"],
209
+ tokens: ["tokens"],
210
+ discovery: ["discovery"],
211
+ verifier: [],
212
+ }[scope];
213
+ if (fields.length > 0) {
214
+ (yield authInvoke(this, "updateGrant", [
215
+ (grant) => {
216
+ const body = { ...grant.body };
217
+ let changed = false;
218
+ for (const field of fields) {
219
+ const current = field === "client" ? body.client?.value : body[field];
220
+ if (current === undefined)
221
+ continue;
222
+ if (field !== "discovery" && fingerprint(current) !== this.seen[field])
223
+ continue;
224
+ delete body[field];
225
+ if (field === "tokens")
226
+ delete body.expiresAt;
227
+ changed = true;
228
+ }
229
+ return changed ? { ...grant, body } : undefined;
230
+ },
231
+ { quiet: true },
232
+ ]));
233
+ }
234
+ // The SDK invalidates after a refused refresh and then starts over, which
235
+ // on `invalid_client` means registering a new client. A passive request
236
+ // must not begin authorization, so it ends here, as auth_required.
237
+ if (!this.allowAuthorization && this.refreshFailure?.kind === "dead") {
238
+ throw this.authorizationRefused();
239
+ }
240
+ }.bind(this));
241
+ },
242
+ };