@zackbart/connecta 0.21.2 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +10 -3
  3. package/dist/access-tokens.d.ts +2 -2
  4. package/dist/access-tokens.js +14 -2
  5. package/dist/connectors/api.d.ts +2 -0
  6. package/dist/connectors/api.js +1 -0
  7. package/dist/connectors/remote-mcp.d.ts +2 -0
  8. package/dist/connectors/remote-mcp.js +6 -0
  9. package/dist/credentials.d.ts +6 -6
  10. package/dist/credentials.js +25 -21
  11. package/dist/identity.d.ts +4 -0
  12. package/dist/identity.js +17 -0
  13. package/dist/index.d.ts +16 -2
  14. package/dist/index.js +6 -1
  15. package/dist/meta-tools.js +7 -2
  16. package/dist/operator-ui/generated.js +1 -1
  17. package/dist/operator-ui/model.d.ts +4 -2
  18. package/dist/operator-ui/view.js +1 -1
  19. package/dist/providers/cloudflare.d.ts +2 -0
  20. package/dist/providers/cloudflare.js +1 -0
  21. package/dist/providers/linear.d.ts +2 -0
  22. package/dist/providers/linear.js +1 -0
  23. package/dist/providers/mixpanel.d.ts +2 -0
  24. package/dist/providers/mixpanel.js +1 -0
  25. package/dist/providers/notion.d.ts +2 -0
  26. package/dist/providers/notion.js +1 -0
  27. package/dist/providers/revenuecat.d.ts +2 -0
  28. package/dist/providers/revenuecat.js +1 -0
  29. package/dist/providers/stripe.d.ts +2 -0
  30. package/dist/providers/stripe.js +1 -0
  31. package/dist/providers/vercel.d.ts +25 -0
  32. package/dist/providers/vercel.js +1132 -0
  33. package/dist/registry.d.ts +25 -0
  34. package/dist/registry.js +200 -4
  35. package/dist/routes/access-tokens.js +2 -2
  36. package/dist/routes/activity.js +4 -1
  37. package/dist/routes/credentials.js +31 -12
  38. package/dist/routes/mcp.js +17 -2
  39. package/dist/routes/oauth.js +55 -11
  40. package/dist/routes/shared.d.ts +20 -4
  41. package/dist/routes/shared.js +92 -24
  42. package/dist/routes/ui.js +32 -13
  43. package/dist/types.d.ts +28 -2
  44. package/dist/ui.d.ts +3 -3
  45. package/dist/ui.js +18 -5
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/documentation/architecture.md +14 -8
  49. package/documentation/auth.md +89 -9
  50. package/documentation/code-mode.md +2 -2
  51. package/documentation/connector-guides.md +2 -2
  52. package/documentation/connectors.md +21 -6
  53. package/documentation/meta-tools.md +4 -3
  54. package/documentation/operations.md +5 -2
  55. package/documentation/operator-ui.md +13 -4
  56. package/documentation/provider-conventions.md +5 -5
  57. package/documentation/request-admission.md +2 -1
  58. package/documentation/storage-and-credentials.md +42 -4
  59. package/documentation/upgrading.md +35 -4
  60. package/documentation/vercel.md +194 -0
  61. package/ethos.md +8 -8
  62. package/examples/worker/AGENTS.md +44 -0
  63. package/examples/worker/README.md +63 -14
  64. package/examples/worker/src/index.ts +26 -22
  65. package/package.json +5 -1
  66. package/templates/node/README.md +7 -0
  67. package/templates/node/package.json +1 -1
  68. package/templates/node/src/index.ts +13 -4
package/CHANGELOG.md CHANGED
@@ -2,6 +2,75 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## 0.22.1 — 2026-08-31
6
+
7
+ This patch adds a maintained Vercel connection over the public REST API. It is
8
+ a new opt-in provider subpath: existing imports, connector declarations,
9
+ credentials, and deployment behavior do not change. Deployments that do not
10
+ import the Vercel provider can ignore this release.
11
+
12
+ ### Added
13
+
14
+ - **Maintained Vercel REST connection.** `@zackbart/connecta/providers/vercel`
15
+ ships 18 projected project, deployment, log, domain, environment-variable,
16
+ and lifecycle tools plus separate GET, JSON-mutation, and upload hatches. It
17
+ uses one operator-managed access token, defaults account calls to an optional
18
+ team, never decrypts environment values in named reads, and adds no Vercel
19
+ SDK dependency.
20
+
21
+ ## 0.22.0 — 2026-08-31
22
+
23
+ This release lets one deployment serve several authenticated people without
24
+ becoming an account system. Connector visibility comes from deployment config,
25
+ and downstream auth may be shared across the tenant or isolated per human.
26
+ Existing connectors stay shared, every visible connector stays visible, and
27
+ every interactive human stays an operator unless the deployment opts into the
28
+ new identity rules. A signed-in human may manage authentication for every
29
+ connector their code-derived view includes; operator status separately controls
30
+ tokens and global activity. Worker deployments also gain the complete Managed
31
+ OAuth callback allowlist in the shipped example and agent instructions. The
32
+ one upgrade seam is deliberate: in-flight `get_result` ids created by a named
33
+ caller do not cross into the new subject partition, so finish paging them
34
+ before deployment. Single-user deployments need no identity configuration,
35
+ and non-Worker deployments can ignore the callback-policy change.
36
+
37
+ ### Added
38
+
39
+ - **Identity-derived connector views.** `identity.connectorAccess` selects
40
+ declared connector ids from the authenticated actor, subject, and principal.
41
+ The view reaches discovery, direct and program calls, status, catalogs,
42
+ observed output shapes, and paged results. Unknown ids and resolver failures
43
+ fail closed.
44
+ - **Shared and personal connector auth.** `authScope: "personal"` partitions
45
+ connector storage, encrypted credentials, OAuth state and tokens, catalogs,
46
+ and runtime observations by a hashed principal identity. Interactive members
47
+ manage auth for every visible connector: their own partition for personal
48
+ auth, or the deployment-wide grant for shared auth. `identity.operatorAccess`
49
+ reserves deployment access tokens and global activity for configured
50
+ operators.
51
+ - **Principal-bound access tokens and OAuth callbacks.** A new connecta access
52
+ token retains its creator's principal without gaining operator rights.
53
+ Personal OAuth handoffs bind a hash of state to the initiating principal for
54
+ 15 minutes before the public callback can exchange a code.
55
+
56
+ ### Changed
57
+
58
+ - **Result pages follow authenticated subjects.** `get_result` storage is now
59
+ partitioned for every namespaced subject, including existing Clerk, Access,
60
+ and connecta-token callers. In-flight result ids created before upgrading do
61
+ not cross that storage boundary; finish paging them before deployment when
62
+ that matters.
63
+ - **Worker Managed OAuth setup.** The Worker guide, source comment, upgrade
64
+ guide, and local `AGENTS.md` require Claude's fixed callback plus ChatGPT's
65
+ stable and callback-id forms in
66
+ `dynamic_client_registration.allowed_uris`.
67
+ - **One tenant, several people.** The ethos now refuses a connecta-owned account
68
+ model while allowing externally authenticated principals, config-derived
69
+ connector visibility, and personal downstream credentials inside one tenant.
70
+ - **Reviewed Cloudflare deletion contract.** Cloudflare removed a spurious
71
+ required empty request body from Worker script deletion. Connecta already
72
+ sent no body, so only the reviewed endpoint digest changes (#517).
73
+
5
74
  ## 0.21.2 — 2026-08-31
6
75
 
7
76
  This patch closes two runtime isolation gaps: concurrent request scopes now
package/README.md CHANGED
@@ -33,7 +33,7 @@ flowchart TB
33
33
  Explicit["call_destructive_tool<br/>one visible call per write<br/>your client can ask you first"]
34
34
  end
35
35
 
36
- Integrations["The integrations you chose<br/>Linear · Stripe · Notion · your HTTP API · any MCP server"]
36
+ Integrations["The integrations you chose<br/>Linear · Stripe · Notion · Vercel · your HTTP API · any MCP server"]
37
37
 
38
38
  Client -->|"one connection"| Sandbox
39
39
  Client --> Explicit
@@ -63,8 +63,8 @@ Fifty issues in, one small object out. Your context window notices.
63
63
  - **Wrap any HTTP API by hand.** A few lines per tool. No OpenAPI conversion —
64
64
  generated tool sprawl is the problem, not the fix.
65
65
  - **Use maintained connections** for Cloudflare, Linear, Mixpanel, Notion,
66
- RevenueCat, and Stripe — known endpoints, auth defaults, and vetted read/write
67
- classifications, imported one at a time.
66
+ RevenueCat, Stripe, and Vercel — known endpoints, auth defaults, and vetted
67
+ read/write classifications, imported one at a time.
68
68
  - **Let the agent work in code.** Search, chain, filter, join, and reduce
69
69
  inside the sandbox instead of round-tripping every call through the model.
70
70
  - **Teach undeclared result shapes by using them.** Successful read-only calls
@@ -82,6 +82,13 @@ payload-free activity log. Worker deployments can use Cloudflare Access for
82
82
  both MCP and operator identity; Node deployments and existing Workers can use
83
83
  Clerk.
84
84
 
85
+ One deployment may serve several authenticated people inside the same tenant.
86
+ Configuration can derive connector visibility from the admitted identity, and
87
+ each connector may keep one shared downstream grant or a separate encrypted
88
+ grant per human. Connecta does not own accounts or groups; Clerk or Cloudflare
89
+ Access remains the identity provider. See [inbound auth](./documentation/auth.md#principals-visibility-and-operators)
90
+ and [shared and personal auth](./documentation/storage-and-credentials.md#shared-and-personal-auth).
91
+
85
92
  Connecta is not a platform, a marketplace, a policy engine, or a multi-tenant
86
93
  service. Those are decisions, and the [ethos](./ethos.md) records each one
87
94
  and why.
@@ -1,4 +1,4 @@
1
- import type { InboundAuth, KVStorage } from "./types.js";
1
+ import type { IdentityReference, InboundAuth, KVStorage } from "./types.js";
2
2
  export interface AccessTokenMetadata {
3
3
  id: string;
4
4
  name: string;
@@ -24,7 +24,7 @@ export declare class AccessTokenManager {
24
24
  });
25
25
  private read;
26
26
  list(): Promise<AccessTokenMetadata[]>;
27
- create(name: unknown, createdBy: string): Promise<CreatedAccessToken>;
27
+ create(name: unknown, createdBy: string | IdentityReference): Promise<CreatedAccessToken>;
28
28
  rename(id: string, name: unknown): Promise<AccessTokenMetadata | null>;
29
29
  revoke(id: string, revokedBy: string): Promise<AccessTokenMetadata | null>;
30
30
  private authorize;
@@ -1,3 +1,4 @@
1
+ import { validIdentityReference } from "./identity.js";
1
2
  const TOKEN_PREFIX = "cta_";
2
3
  const TOKEN_BYTES = 32;
3
4
  const TOKEN_VALUE_RE = /^cta_[A-Za-z0-9_-]{43}$/;
@@ -54,6 +55,8 @@ function parseRecord(raw) {
54
55
  typeof value.tokenPrefix !== "string" ||
55
56
  typeof value.createdAt !== "string" ||
56
57
  typeof value.createdBy !== "string" ||
58
+ (value.principal !== undefined &&
59
+ !validIdentityReference(value.principal)) ||
57
60
  (value.revokedAt !== undefined &&
58
61
  typeof value.revokedAt !== "string") ||
59
62
  (value.revokedBy !== undefined &&
@@ -167,7 +170,12 @@ export class AccessTokenManager {
167
170
  tokenHash: hash,
168
171
  tokenPrefix: token.slice(0, 12),
169
172
  createdAt: new Date().toISOString(),
170
- createdBy,
173
+ createdBy: typeof createdBy === "string"
174
+ ? createdBy
175
+ : `${createdBy.namespace}:${createdBy.id}`,
176
+ ...(typeof createdBy === "string"
177
+ ? {}
178
+ : { principal: { ...createdBy } }),
171
179
  };
172
180
  await this.storage.set(recordKey(record.id), JSON.stringify(record));
173
181
  try {
@@ -219,6 +227,10 @@ export class AccessTokenManager {
219
227
  if (!record || record.revokedAt || record.tokenHash !== hash) {
220
228
  return unauthorized();
221
229
  }
222
- return { ok: true, subjectId: record.id };
230
+ return {
231
+ ok: true,
232
+ subjectId: record.id,
233
+ ...(record.principal ? { principal: { ...record.principal } } : {}),
234
+ };
223
235
  }
224
236
  }
@@ -34,6 +34,8 @@ export interface ApiOptions {
34
34
  /** Human-readable display name; the connector id remains the address prefix. */
35
35
  title?: string;
36
36
  description?: string;
37
+ /** Downstream auth ownership. Defaults to one shared deployment grant. */
38
+ authScope?: "shared" | "personal";
37
39
  /**
38
40
  * Max inline result size (bytes) for this connector's tools before
39
41
  * call_tool truncates and stashes the full text for get_result
@@ -40,6 +40,7 @@ export function api(id, opts) {
40
40
  kind: "api",
41
41
  ...defined({
42
42
  description: opts.description,
43
+ authScope: opts.authScope,
43
44
  maxResultBytes: opts.maxResultBytes,
44
45
  callAdmission: opts.callAdmission,
45
46
  usageGuide: opts.usageGuide,
@@ -59,6 +59,8 @@ export interface RemoteMcpOptions {
59
59
  /** Human-readable display name; the connector id remains the address prefix. */
60
60
  title?: string;
61
61
  description?: string;
62
+ /** Downstream auth ownership. Defaults to one shared deployment grant. */
63
+ authScope?: "shared" | "personal";
62
64
  /**
63
65
  * Max inline result size (bytes) for this connector's tools before
64
66
  * call_tool truncates and stashes the full text for get_result
@@ -320,6 +320,11 @@ export function redirectSafeFetch(connectorId, policy = "none", baseFetch = fetc
320
320
  * server or hide other connectors).
321
321
  */
322
322
  export function remoteMcp(id, opts) {
323
+ if (opts.authScope === "personal" && opts.auth?.type === "headers") {
324
+ throw new Error(`[connecta] connector "${id}" cannot combine authScope "personal" ` +
325
+ "with static headers. Use credential or OAuth auth so each principal " +
326
+ "can own a different grant.");
327
+ }
323
328
  // Weak keys ensure a completed request does not leave its SDK client,
324
329
  // transport, response bodies, AbortSignals, or connection promise reachable
325
330
  // from the isolate singleton. Those are request-bound in Cloudflare Workers.
@@ -753,6 +758,7 @@ export function remoteMcp(id, opts) {
753
758
  ...(opts.description !== undefined
754
759
  ? { description: opts.description }
755
760
  : {}),
761
+ ...(opts.authScope !== undefined ? { authScope: opts.authScope } : {}),
756
762
  ...(opts.maxResultBytes !== undefined
757
763
  ? { maxResultBytes: opts.maxResultBytes }
758
764
  : {}),
@@ -107,11 +107,11 @@ export declare class CredentialVault {
107
107
  constructor(storage: KVStorage, encryptionKey: string);
108
108
  private additionalData;
109
109
  private read;
110
- get(connectorId: string, field?: string): Promise<string | null>;
111
- getAll(connectorId: string): Promise<ConnectorCredentialValues | null>;
112
- metadata(connectorId: string): Promise<CredentialMetadata | null>;
113
- set(connectorId: string, value: string, updatedBy: string): Promise<CredentialMetadata>;
114
- setAll(connectorId: string, values: ConnectorCredentialValues, updatedBy: string): Promise<CredentialMetadata>;
115
- delete(connectorId: string): Promise<void>;
110
+ get(connectorId: string, field?: string, owner?: string): Promise<string | null>;
111
+ getAll(connectorId: string, owner?: string): Promise<ConnectorCredentialValues | null>;
112
+ metadata(connectorId: string, owner?: string): Promise<CredentialMetadata | null>;
113
+ set(connectorId: string, value: string, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
114
+ setAll(connectorId: string, values: ConnectorCredentialValues, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
115
+ delete(connectorId: string, owner?: string): Promise<void>;
116
116
  }
117
117
  export {};
@@ -110,8 +110,10 @@ export function describeCredentialTestMismatch(mismatch) {
110
110
  "`testCredential(value, ctx)` can test, but implements " +
111
111
  "`testCredentials`";
112
112
  }
113
- function storageKey(connectorId) {
114
- return `conn:${connectorId}:credential:v1`;
113
+ function storageKey(connectorId, owner) {
114
+ return owner
115
+ ? `principal:${owner}:conn:${connectorId}:credential:v1`
116
+ : `conn:${connectorId}:credential:v1`;
115
117
  }
116
118
  function bytesToBase64(bytes) {
117
119
  let binary = "";
@@ -205,11 +207,13 @@ export class CredentialVault {
205
207
  }
206
208
  this.key = crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
207
209
  }
208
- additionalData(connectorId) {
209
- return encoder.encode(`connecta:credential:${connectorId}:v1`);
210
+ additionalData(connectorId, owner) {
211
+ return encoder.encode(owner
212
+ ? `connecta:credential:principal:${owner}:${connectorId}:v1`
213
+ : `connecta:credential:${connectorId}:v1`);
210
214
  }
211
- async read(connectorId) {
212
- const raw = await this.storage.get(storageKey(connectorId));
215
+ async read(connectorId, owner) {
216
+ const raw = await this.storage.get(storageKey(connectorId, owner));
213
217
  if (!raw)
214
218
  return null;
215
219
  const envelope = parseEnvelope(raw);
@@ -217,7 +221,7 @@ export class CredentialVault {
217
221
  const plaintext = await crypto.subtle.decrypt({
218
222
  name: "AES-GCM",
219
223
  iv: base64ToBytes(envelope.iv),
220
- additionalData: this.additionalData(connectorId),
224
+ additionalData: this.additionalData(connectorId, owner),
221
225
  }, await this.key, base64ToBytes(envelope.ciphertext));
222
226
  return parsePlaintext(decoder.decode(plaintext));
223
227
  }
@@ -225,15 +229,15 @@ export class CredentialVault {
225
229
  throw new Error("Stored credential could not be decrypted");
226
230
  }
227
231
  }
228
- async get(connectorId, field = "value") {
229
- return (await this.read(connectorId))?.values[field] ?? null;
232
+ async get(connectorId, field = "value", owner) {
233
+ return (await this.read(connectorId, owner))?.values[field] ?? null;
230
234
  }
231
- async getAll(connectorId) {
232
- const credential = await this.read(connectorId);
235
+ async getAll(connectorId, owner) {
236
+ const credential = await this.read(connectorId, owner);
233
237
  return credential ? { ...credential.values } : null;
234
238
  }
235
- async metadata(connectorId) {
236
- const credential = await this.read(connectorId);
239
+ async metadata(connectorId, owner) {
240
+ const credential = await this.read(connectorId, owner);
237
241
  if (!credential)
238
242
  return null;
239
243
  const fields = Object.fromEntries(Object.entries(credential.values).map(([field, value]) => [
@@ -254,13 +258,13 @@ export class CredentialVault {
254
258
  fields,
255
259
  };
256
260
  }
257
- async set(connectorId, value, updatedBy) {
261
+ async set(connectorId, value, updatedBy, owner) {
258
262
  // `await` (not a bare promise return) so a validation throw inside setAll
259
263
  // never sits handler-less for the thenable-adoption microtask — workerd
260
264
  // reports that gap as an unhandled rejection.
261
- return await this.setAll(connectorId, { value }, updatedBy);
265
+ return await this.setAll(connectorId, { value }, updatedBy, owner);
262
266
  }
263
- async setAll(connectorId, values, updatedBy) {
267
+ async setAll(connectorId, values, updatedBy, owner) {
264
268
  const normalized = validateValues(values);
265
269
  const plaintext = {
266
270
  values: normalized,
@@ -271,7 +275,7 @@ export class CredentialVault {
271
275
  const ciphertext = await crypto.subtle.encrypt({
272
276
  name: "AES-GCM",
273
277
  iv,
274
- additionalData: this.additionalData(connectorId),
278
+ additionalData: this.additionalData(connectorId, owner),
275
279
  }, await this.key, encoder.encode(JSON.stringify(plaintext)));
276
280
  const envelope = {
277
281
  version: 1,
@@ -279,10 +283,10 @@ export class CredentialVault {
279
283
  iv: bytesToBase64(iv),
280
284
  ciphertext: bytesToBase64(new Uint8Array(ciphertext)),
281
285
  };
282
- await this.storage.set(storageKey(connectorId), JSON.stringify(envelope));
283
- return (await this.metadata(connectorId));
286
+ await this.storage.set(storageKey(connectorId, owner), JSON.stringify(envelope));
287
+ return (await this.metadata(connectorId, owner));
284
288
  }
285
- async delete(connectorId) {
286
- await this.storage.delete(storageKey(connectorId));
289
+ async delete(connectorId, owner) {
290
+ await this.storage.delete(storageKey(connectorId, owner));
287
291
  }
288
292
  }
@@ -0,0 +1,4 @@
1
+ import type { IdentityReference } from "./types.js";
2
+ export declare function validIdentityReference(value: IdentityReference | undefined): value is IdentityReference;
3
+ /** Deterministic pseudonymous partition; raw emails and ids do not enter keys. */
4
+ export declare function identityStorageKey(identity: IdentityReference): Promise<string>;
@@ -0,0 +1,17 @@
1
+ const IDENTITY_PART_RE = /^[\x21-\x7e]{1,256}$/;
2
+ const encoder = new TextEncoder();
3
+ export function validIdentityReference(value) {
4
+ return Boolean(value &&
5
+ IDENTITY_PART_RE.test(value.namespace) &&
6
+ IDENTITY_PART_RE.test(value.id));
7
+ }
8
+ function bytesToHex(bytes) {
9
+ return [...bytes]
10
+ .map((byte) => byte.toString(16).padStart(2, "0"))
11
+ .join("");
12
+ }
13
+ /** Deterministic pseudonymous partition; raw emails and ids do not enter keys. */
14
+ export async function identityStorageKey(identity) {
15
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(`${identity.namespace}\n${identity.id}`));
16
+ return bytesToHex(new Uint8Array(digest));
17
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Registry } from "./registry.js";
2
2
  import type { ActivityReadGate, ActivityStore } from "./activity.js";
3
- import type { Connector, ConnectaBranding, Executor, InboundAuth, KVStorage, Logger } from "./types.js";
3
+ import type { AuthenticatedIdentity, Connector, ConnectaBranding, Executor, IdentityReference, InboundAuth, KVStorage, Logger } from "./types.js";
4
4
  /** Payload-free activity storage and operator-read policy. */
5
5
  export interface ConnectaActivityConfig {
6
6
  /**
@@ -115,10 +115,24 @@ export interface ConnectaAdmissionConfig {
115
115
  */
116
116
  code?: AdmissionPoolConfig;
117
117
  }
118
+ /** Config-owned identity rules for one deployment and tenant. */
119
+ export interface ConnectaIdentityConfig {
120
+ /** Connector ids this admitted identity may discover and call. */
121
+ connectorAccess?(identity: Readonly<AuthenticatedIdentity>): "all" | readonly string[] | Promise<"all" | readonly string[]>;
122
+ /**
123
+ * Whether a human may manage deployment access tokens and global activity.
124
+ * Connector access already permits that human to manage the visible
125
+ * connector's shared or personal auth. Omit to preserve the existing
126
+ * all-interactive-humans operator rule.
127
+ */
128
+ operatorAccess?(principal: Readonly<IdentityReference>): boolean | Promise<boolean>;
129
+ }
118
130
  export interface ConnectaConfig {
119
131
  connectors: Connector[];
120
132
  /** Inbound auth adapters. Includes bearerToken(...); omit for open (dev). */
121
133
  auth?: InboundAuth | InboundAuth[];
134
+ /** Identity-derived connector visibility and deployment operator membership. */
135
+ identity?: ConnectaIdentityConfig;
122
136
  /** KVStorage impl. Defaults to memoryStorage(). */
123
137
  storage?: KVStorage;
124
138
  /**
@@ -192,6 +206,6 @@ export { CONNECTA_VERSION } from "./version.js";
192
206
  export type { Registry } from "./registry.js";
193
207
  export type { RemoteMcpOptions, RemoteMcpAuth, RemoteMcpRedirectPolicy, } from "./connectors/remote-mcp.js";
194
208
  export type { ApiOptions, ApiTool } from "./connectors/api.js";
195
- export type { CatalogDriftCounts, CatalogDriftReport, Connector, ConnectorCallAdmissionInput, ConnectorCallAdmissionPolicy, ConnectorCallAdmissionRule, ConnectorRollingWindowBudget, ConnectaBranding, ConnectorCredentialAccess, ConnectorCredentialConfig, ConnectorCredentialFieldConfig, ConnectorCredentialValues, ConnectorContext, ConnectorUsageGuide, ConnectorStatus, CredentialTestResult, AdmittingExecutor, AdmissionSnapshot, ExecuteResult, Executor, ExecutorLease, ExecutorProvider, InboundAuth, InboundAuthRuntimeContext, UiAuthConfig, AuthResult, JsonSchema, KVStorage, Logger, ToolDef, ToolAnnotations, } from "./types.js";
209
+ export type { CatalogDriftCounts, CatalogDriftReport, Connector, ConnectorCallAdmissionInput, ConnectorCallAdmissionPolicy, ConnectorCallAdmissionRule, ConnectorRollingWindowBudget, ConnectaBranding, ConnectorCredentialAccess, ConnectorCredentialConfig, ConnectorCredentialFieldConfig, ConnectorCredentialValues, ConnectorContext, ConnectorUsageGuide, ConnectorStatus, CredentialTestResult, AdmittingExecutor, AdmissionSnapshot, ExecuteResult, Executor, ExecutorLease, ExecutorProvider, InboundAuth, InboundAuthRuntimeContext, UiAuthConfig, AuthResult, AuthenticatedIdentity, IdentityReference, JsonSchema, KVStorage, Logger, ToolDef, ToolAnnotations, } from "./types.js";
196
210
  export type { ActivityActor, ActivityCallSource, ActivityOutcome, ActivityPage, ActivityReadActor, ActivityReadEvent, ActivityReader, ActivityReadGate, ActivityReadPage, ActivitySink, ActivityStore, AgentFriction, CatalogDriftActivityEvent, ToolCallActivityEvent, } from "./activity.js";
197
211
  export { InvalidActivityCursorError } from "./activity.js";
package/dist/index.js CHANGED
@@ -51,6 +51,10 @@ const admissionPoolSchema = {
51
51
  const CONFIG_SCHEMA = {
52
52
  connectors: null,
53
53
  auth: null,
54
+ identity: {
55
+ connectorAccess: null,
56
+ operatorAccess: null,
57
+ },
54
58
  storage: null,
55
59
  publicUrl: null,
56
60
  activity: {
@@ -250,7 +254,7 @@ export function createConnecta(config) {
250
254
  const credentialConnectors = config.connectors.filter((c) => c.credential);
251
255
  const encryptionKey = config.credentials?.encryptionKey;
252
256
  if (credentialConnectors.length > 0 && !encryptionKey) {
253
- logger.warn("Operator-managed credentials are unavailable because " +
257
+ logger.warn("Human-managed credentials are unavailable because " +
254
258
  "credentials.encryptionKey is not configured for connectors: " +
255
259
  credentialConnectors.map((c) => c.id).join(", "));
256
260
  }
@@ -310,6 +314,7 @@ export function createConnecta(config) {
310
314
  const handler = createFetchHandler({
311
315
  registry,
312
316
  auth: inboundAuth,
317
+ identity: config.identity,
313
318
  publicUrl: config.publicUrl,
314
319
  serverInfo,
315
320
  logger,
@@ -479,13 +479,18 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
479
479
  },
480
480
  operatorUrl: new URL("/credentials", baseUrl).toString(),
481
481
  instructions: "Have the operator open operatorUrl, set and test the credential, " +
482
- "then retry the original call. No redeploy is needed. Credential " +
483
- "mutation requires a Clerk-authenticated operator.",
482
+ "then retry the original call. No redeploy is needed. " +
483
+ (connector.authScope === "personal"
484
+ ? "Credential mutation requires the signed-in principal who owns this connection."
485
+ : "Shared credential mutation requires a signed-in human with access to this connector."),
484
486
  });
485
487
  }
486
488
  const ctx = registry.contextFor(connector.id, baseUrl, requestScope);
487
489
  try {
488
490
  const status = await connector.startAuth(ctx, args.force !== undefined ? { force: args.force } : {});
491
+ if (status.authorizationUrl) {
492
+ await registry.bindOAuthHandoff(connector.id, status.authorizationUrl);
493
+ }
489
494
  if (status.state === "auth_required" && !status.authorizationUrl) {
490
495
  // auth_required with nothing to open is a dead end for the operator.
491
496
  return errorResult(`Connector "${connector.id}": authorization required but no URL is available — retry authorize_connector.`);