@zackbart/connecta 0.23.0 → 0.24.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 (78) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +18 -10
  4. package/dist/activity-friction.d.ts +3 -0
  5. package/dist/activity-friction.js +19 -0
  6. package/dist/activity.d.ts +11 -2
  7. package/dist/activity.js +15 -19
  8. package/dist/auth/downstream-oauth.d.ts +2 -1
  9. package/dist/auth/downstream-oauth.js +10 -1
  10. package/dist/branding.d.ts +67 -0
  11. package/dist/branding.js +176 -0
  12. package/dist/connectors/remote-mcp.js +3 -5
  13. package/dist/credential-contract.d.ts +24 -0
  14. package/dist/credential-contract.js +1 -0
  15. package/dist/credential-rules.d.ts +85 -0
  16. package/dist/credential-rules.js +107 -0
  17. package/dist/credentials.d.ts +4 -100
  18. package/dist/credentials.js +3 -107
  19. package/dist/index.d.ts +22 -55
  20. package/dist/index.js +30 -58
  21. package/dist/invocation.js +2 -3
  22. package/dist/meta-tools.d.ts +4 -0
  23. package/dist/meta-tools.js +8 -4
  24. package/dist/module-contracts.d.ts +19 -0
  25. package/dist/module-contracts.js +1 -0
  26. package/dist/operator-ui/generated.js +2 -2
  27. package/dist/operator-ui/model.d.ts +6 -3
  28. package/dist/operator-ui/view.d.ts +2 -18
  29. package/dist/operator-ui/view.js +3 -20
  30. package/dist/registry.d.ts +4 -1
  31. package/dist/registry.js +4 -6
  32. package/dist/routes/activity.js +1 -1
  33. package/dist/routes/credentials.js +5 -2
  34. package/dist/routes/mcp.js +7 -3
  35. package/dist/routes/oauth-management.d.ts +2 -0
  36. package/dist/routes/oauth-management.js +108 -0
  37. package/dist/routes/oauth.d.ts +0 -1
  38. package/dist/routes/oauth.js +21 -121
  39. package/dist/routes/shared.d.ts +19 -17
  40. package/dist/routes/shared.js +48 -44
  41. package/dist/routes/ui.js +36 -33
  42. package/dist/server.js +6 -26
  43. package/dist/types.d.ts +2 -0
  44. package/dist/ui.d.ts +15 -70
  45. package/dist/ui.js +176 -317
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/documentation/architecture.md +26 -17
  49. package/documentation/auth.md +61 -106
  50. package/documentation/cloudflare.md +1 -1
  51. package/documentation/connectors.md +1 -1
  52. package/documentation/linear.md +1 -1
  53. package/documentation/meta-tools.md +6 -4
  54. package/documentation/mixpanel.md +1 -1
  55. package/documentation/notion.md +2 -2
  56. package/documentation/operations.md +12 -14
  57. package/documentation/operator-ui.md +82 -104
  58. package/documentation/optional-modules-upgrade.md +243 -0
  59. package/documentation/provider-conventions.md +5 -3
  60. package/documentation/revenuecat.md +1 -1
  61. package/documentation/storage-and-credentials.md +59 -40
  62. package/documentation/stripe.md +1 -1
  63. package/documentation/upgrading.md +29 -4
  64. package/ethos.md +22 -30
  65. package/examples/worker/AGENTS.md +3 -1
  66. package/examples/worker/README.md +68 -84
  67. package/examples/worker/src/d1-activity.ts +1 -1
  68. package/examples/worker/src/index.ts +11 -6
  69. package/package.json +18 -2
  70. package/templates/node/AGENTS.md +8 -6
  71. package/templates/node/README.md +56 -67
  72. package/templates/node/package.json +1 -1
  73. package/templates/node/src/file-activity.ts +1 -1
  74. package/templates/node/src/index.ts +11 -12
  75. package/dist/access-tokens.d.ts +0 -31
  76. package/dist/access-tokens.js +0 -236
  77. package/dist/routes/access-tokens.d.ts +0 -6
  78. package/dist/routes/access-tokens.js +0 -83
package/dist/index.js CHANGED
@@ -1,8 +1,7 @@
1
- import { CredentialVault, credentialTestRule, describeCredentialTestMismatch, } from "./credentials.js";
2
- import { AccessTokenManager } from "./access-tokens.js";
1
+ import { credentialTestRule, describeCredentialTestMismatch, } from "./credential-rules.js";
3
2
  import { Registry } from "./registry.js";
4
3
  import { createFetchHandler } from "./server.js";
5
- import { droppedBrandingUrls, droppedUiAuthUrls } from "./ui.js";
4
+ import { droppedBrandingUrls, droppedUiAuthUrls } from "./branding.js";
6
5
  import { memoryStorage } from "./storage/memory.js";
7
6
  import { CONNECTA_VERSION } from "./version.js";
8
7
  import { AdmissionController, executorName, isAdmittingExecutor, withExecutorAdmission, } from "./executor-admission.js";
@@ -53,21 +52,15 @@ const CONFIG_SCHEMA = {
53
52
  auth: null,
54
53
  identity: {
55
54
  connectorAccess: null,
56
- operatorAccess: null,
55
+ activityAccess: null,
56
+ credentialAdministration: null,
57
+ personalConnection: null,
57
58
  },
58
59
  storage: null,
59
60
  publicUrl: null,
60
- activity: {
61
- store: null,
62
- readGate: null,
63
- deploymentId: null,
64
- },
65
- credentials: {
66
- encryptionKey: null,
67
- },
68
- accessTokens: {
69
- maxActive: null,
70
- },
61
+ activity: null,
62
+ vault: null,
63
+ ui: null,
71
64
  discovery: {
72
65
  concurrency: null,
73
66
  catalogTtlSeconds: null,
@@ -87,20 +80,6 @@ const CONFIG_SCHEMA = {
87
80
  requests: admissionPoolSchema,
88
81
  code: admissionPoolSchema,
89
82
  },
90
- branding: {
91
- productName: null,
92
- productUrl: null,
93
- ownerName: null,
94
- ownerUrl: null,
95
- description: null,
96
- pageTitle: null,
97
- favicon: {
98
- svg: null,
99
- ico: null,
100
- href: null,
101
- },
102
- themeColor: null,
103
- },
104
83
  logger: null,
105
84
  serverInfo: {
106
85
  name: null,
@@ -144,17 +123,24 @@ function rejectUnknownOptions(paths) {
144
123
  if (paths.length === 0)
145
124
  return;
146
125
  throw new Error(`Unknown Connecta configuration option${paths.length === 1 ? "" : "s"}:\n` +
147
- paths.map((path) => `- ${path}`).join("\n"));
126
+ paths.map((path) => `- ${path}`).join("\n") +
127
+ (paths.includes("ConnectaConfig.credentials") ? "\nUse vault: encryptedCredentialVault(storage, key) from @zackbart/connecta/credentials." : "") +
128
+ (paths.includes("ConnectaConfig.accessTokens") ? "\nConnecta-issued tokens were removed. Configure an inbound auth adapter instead." : "") +
129
+ (paths.includes("ConnectaConfig.branding") ? "\nMove branding into ui: operatorUi({ branding }) from @zackbart/connecta/ui." : ""));
148
130
  }
149
131
  /** Reject JavaScript typos and removed options before construction does work. */
150
132
  function assertKnownConfig(config) {
151
133
  rejectUnknownOptions(unknownOptionPaths(config, "ConnectaConfig", CONFIG_SCHEMA));
134
+ if (config.vault && ["get", "getAll", "set", "setAll", "metadata", "delete"].some(key => typeof config.vault[key] !== "function"))
135
+ throw new Error("ConnectaConfig.vault must implement CredentialVault");
136
+ if (config.ui && (typeof config.ui.handle !== "function" || typeof config.ui.credentialHandoffUrl !== "function" || !Array.isArray(config.ui.reservedPaths)))
137
+ throw new Error("ConnectaConfig.ui must be created with operatorUi(...)");
152
138
  const activity = config.activity;
153
139
  if (activity !== undefined &&
154
140
  (typeof activity !== "object" ||
155
141
  activity === null ||
156
- typeof activity.store?.record !== "function")) {
157
- throw new Error("ConnectaConfig.activity.store must implement record(event)");
142
+ typeof activity.recordTool !== "function")) {
143
+ throw new Error("ConnectaConfig.activity must be created with activityHistory(...)");
158
144
  }
159
145
  }
160
146
  /**
@@ -171,7 +157,7 @@ function warnInsecureConfig(config, inboundAuth, logger) {
171
157
  if (inboundAuth.length === 0 &&
172
158
  (hasCredentialConnector || oauthConnectors.length > 0)) {
173
159
  logger.warn("[connecta] running with no inbound authentication: any caller can " +
174
- "invoke every connector and read or overwrite stored credentials. " +
160
+ "invoke every shared connector. " +
175
161
  "Configure `auth` (for example bearerToken(...) or Clerk) to gate access.");
176
162
  }
177
163
  // Unset publicUrl with OAuth connectors: the downstream redirect_uri is
@@ -186,7 +172,7 @@ function warnInsecureConfig(config, inboundAuth, logger) {
186
172
  // Branding URLs that failed their scheme gate. Rendering silently falls back
187
173
  // (a bad URL must not take the page down), so this warning is the only way an
188
174
  // operator learns their value never reached the page.
189
- const dropped = droppedBrandingUrls(config.branding);
175
+ const dropped = droppedBrandingUrls(config.ui?.branding);
190
176
  if (dropped.length > 0) {
191
177
  logger.warn(`[connecta] branding ${dropped.join(", ")} dropped: a branding URL is ` +
192
178
  "used as an href, so it must be an absolute http(s) URL (favicon.href " +
@@ -250,26 +236,11 @@ export function createConnecta(config) {
250
236
  '"@cloudflare/codemode" on Workers.');
251
237
  }
252
238
  const storage = config.storage ?? memoryStorage();
253
- const logger = config.logger ?? defaultLogger();
254
- const credentialConnectors = config.connectors.filter((c) => c.credential);
255
- const encryptionKey = config.credentials?.encryptionKey;
256
- if (credentialConnectors.length > 0 && !encryptionKey) {
257
- logger.warn("Human-managed credentials are unavailable because " +
258
- "credentials.encryptionKey is not configured for connectors: " +
259
- credentialConnectors.map((c) => c.id).join(", "));
260
- }
261
- const credentialVault = encryptionKey
262
- ? new CredentialVault(storage, encryptionKey)
263
- : undefined;
239
+ const logger = config.logger === "silent"
240
+ ? { debug() { }, info() { }, warn() { }, error() { } }
241
+ : config.logger ?? defaultLogger();
242
+ const credentialVault = config.vault;
264
243
  const configuredAuth = normalizeAuth(config.auth);
265
- const accessTokens = config.accessTokens
266
- ? new AccessTokenManager(storage, config.accessTokens)
267
- : undefined;
268
- if (accessTokens &&
269
- !configuredAuth.some((provider) => provider.interactiveOperator)) {
270
- throw new Error("accessTokens requires an interactive operator auth provider: only an eligible " +
271
- "operator may create, rename, or revoke deployment access tokens");
272
- }
273
244
  const serverInfo = {
274
245
  ...config.serverInfo,
275
246
  name: config.serverInfo?.name ?? "connecta",
@@ -279,9 +250,11 @@ export function createConnecta(config) {
279
250
  storage,
280
251
  logger,
281
252
  credentialVault,
253
+ credentialUi: Boolean(config.ui),
282
254
  catalogDriftActivity: config.activity?.store
283
255
  ? {
284
256
  sink: config.activity.store,
257
+ recordDrift: config.activity.recordDrift,
285
258
  serverInfo,
286
259
  ...(config.activity.deploymentId !== undefined
287
260
  ? { deploymentId: config.activity.deploymentId }
@@ -293,7 +266,7 @@ export function createConnecta(config) {
293
266
  toolCatalogStaleSeconds: config.discovery?.staleCatalogSeconds,
294
267
  maxResultBytes: config.calls?.maxResultBytes,
295
268
  });
296
- const inboundAuth = normalizeAuth(accessTokens ? [accessTokens.auth, ...configuredAuth] : configuredAuth);
269
+ const inboundAuth = configuredAuth;
297
270
  warnInsecureConfig(config, inboundAuth, logger);
298
271
  const requestAdmission = admissionController(config.admission?.requests, REQUEST_ADMISSION_DEFAULTS);
299
272
  const configuredCodeAdmission = admissionController(config.admission?.code, CODE_ADMISSION_DEFAULTS);
@@ -319,6 +292,7 @@ export function createConnecta(config) {
319
292
  serverInfo,
320
293
  logger,
321
294
  activity: config.activity?.store,
295
+ activityModule: config.activity,
322
296
  activityReadGate: config.activity?.readGate,
323
297
  activityDeploymentId: config.activity?.deploymentId,
324
298
  executor,
@@ -330,9 +304,9 @@ export function createConnecta(config) {
330
304
  maxEmittedBytes: config.execute?.maxEmittedBytes,
331
305
  maxEmittedBlocks: config.execute?.maxEmittedBlocks,
332
306
  credentialVault,
333
- accessTokens,
307
+ ui: config.ui,
334
308
  deploymentInfo: config.deploymentInfo,
335
- branding: config.branding,
309
+ branding: config.ui?.branding,
336
310
  });
337
311
  let closePromise;
338
312
  return {
@@ -358,7 +332,5 @@ export { ConnectorCallError } from "./errors.js";
358
332
  // implement the Connector interface directly. Returns the error rather than
359
333
  // throwing so the caller decides what to do with it.
360
334
  export { validateToolInput } from "./validate.js";
361
- export { bearerToken } from "./auth/bearer.js";
362
335
  export { memoryStorage } from "./storage/memory.js";
363
336
  export { CONNECTA_VERSION } from "./version.js";
364
- export { InvalidActivityCursorError } from "./activity.js";
@@ -1,4 +1,3 @@
1
- import { recordToolActivity, } from "./activity.js";
2
1
  import { isCallAdmissionError } from "./call-admission.js";
3
2
  import { classifyCallError, ConnectorCallError, echoedCallArgs, framingError, } from "./errors.js";
4
3
  import { unwrapMcpResult } from "./mcp-result.js";
@@ -28,7 +27,7 @@ function callerCancelledDetails() {
28
27
  function recoveryMode(registry, connector, baseUrl) {
29
28
  if (connector.startAuth)
30
29
  return "oauth";
31
- if (connector.credential &&
30
+ if (registry.credentialUiAvailable() && connector.credential &&
32
31
  registry.contextFor(connector.id, baseUrl).credential) {
33
32
  return "operator_config";
34
33
  }
@@ -116,7 +115,7 @@ export class InvocationService {
116
115
  : attempted;
117
116
  if (!identity)
118
117
  return;
119
- recordToolActivity(this.activity, {
118
+ this.activity?.recordTool?.(this.activity, {
120
119
  connectorId: identity.connectorId,
121
120
  toolName: identity.toolName,
122
121
  address: `${identity.connectorId}.${identity.toolName}`,
@@ -94,6 +94,8 @@ export declare function createMetaTools(registry: RegistryView, baseUrl: string,
94
94
  /** Maximum simultaneous connector discovery operations. Default 4. */
95
95
  discoveryConcurrency?: number | undefined;
96
96
  activity?: ActivityRequestContext | undefined;
97
+ canManageAuth?: ((id: string) => boolean) | undefined;
98
+ credentialHandoffUrl?: string | undefined;
97
99
  /** Inbound request cancellation shared by every call this request makes. */
98
100
  requestSignal?: AbortSignal | undefined;
99
101
  /** Runtime-owned tail for stale catalog refreshes. */
@@ -120,6 +122,8 @@ export declare function registerMetaTools(server: McpServer, registry: RegistryV
120
122
  probeTimeoutMs?: number | undefined;
121
123
  discoveryConcurrency?: number | undefined;
122
124
  activity?: ActivityRequestContext | undefined;
125
+ canManageAuth?: ((id: string) => boolean) | undefined;
126
+ credentialHandoffUrl?: string | undefined;
123
127
  requestSignal?: AbortSignal | undefined;
124
128
  defer?: DeferredWork | undefined;
125
129
  }): void;
@@ -448,12 +448,12 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
448
448
  });
449
449
  }
450
450
  const ctx = registry.contextFor(connector.id, baseUrl, requestScope);
451
- if (!ctx.credential) {
451
+ if (!ctx.credential || !opts.credentialHandoffUrl) {
452
452
  return jsonResult({
453
453
  connector: connector.id,
454
454
  recovery: "unavailable",
455
- message: "Credential storage is not configured. Configure " +
456
- "credentials.encryptionKey, redeploy, then call " +
455
+ message: "Credential recovery needs both a vault and the optional UI. Configure " +
456
+ "vault and ui in deployment code, then call " +
457
457
  "authorize_connector again.",
458
458
  });
459
459
  }
@@ -474,7 +474,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
474
474
  label: connector.credential.label,
475
475
  fields,
476
476
  },
477
- operatorUrl: new URL("/credentials", baseUrl).toString(),
477
+ operatorUrl: opts.credentialHandoffUrl,
478
478
  instructions: "Have the operator open operatorUrl, set and test the credential, " +
479
479
  "then retry the original call. No redeploy is needed. " +
480
480
  (connector.authScope === "personal"
@@ -482,6 +482,8 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
482
482
  : "Shared credential mutation requires a signed-in human with access to this connector."),
483
483
  });
484
484
  }
485
+ if (!opts.canManageAuth?.(connector.id))
486
+ return jsonResult({ connector: connector.id, recovery: "unavailable", message: "Your identity is not permitted to manage authentication for this connection." });
485
487
  const ctx = registry.contextFor(connector.id, baseUrl, requestScope);
486
488
  try {
487
489
  const status = await connector.startAuth(ctx, args.force !== undefined ? { force: args.force } : {});
@@ -581,6 +583,8 @@ export function registerMetaTools(server, registry, ctx) {
581
583
  probeTimeoutMs: ctx.probeTimeoutMs,
582
584
  discoveryConcurrency: ctx.discoveryConcurrency,
583
585
  activity: ctx.activity,
586
+ canManageAuth: ctx.canManageAuth,
587
+ credentialHandoffUrl: ctx.credentialHandoffUrl,
584
588
  requestSignal: ctx.requestSignal,
585
589
  defer: ctx.defer,
586
590
  });
@@ -0,0 +1,19 @@
1
+ import type { ConnectaBranding } from "./types.js";
2
+ import type { ActivityStore, ActivityReadGate, recordToolActivity, recordCatalogDriftActivity } from "./activity.js";
3
+ import type { RouteContext } from "./routes/shared.js";
4
+ /** Construction-time UI contract. Core never imports the implementation. */
5
+ export interface OperatorSurface {
6
+ readonly branding?: ConnectaBranding;
7
+ readonly reservedPaths: readonly string[];
8
+ credentialHandoffUrl(baseUrl: string): string;
9
+ handle(context: RouteContext): Promise<Response | null>;
10
+ }
11
+ /** Optional recording and reading callbacks supplied by /activity. */
12
+ export interface ActivityModule {
13
+ readonly store: ActivityStore;
14
+ readonly deploymentId?: string;
15
+ readonly readGate?: ActivityReadGate;
16
+ handle(context: RouteContext): Promise<Response | null>;
17
+ readonly recordTool: typeof recordToolActivity;
18
+ readonly recordDrift: typeof recordCatalogDriftActivity;
19
+ }
@@ -0,0 +1 @@
1
+ export {};