@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.
- package/CHANGELOG.md +69 -0
- package/README.md +10 -3
- package/dist/access-tokens.d.ts +2 -2
- package/dist/access-tokens.js +14 -2
- package/dist/connectors/api.d.ts +2 -0
- package/dist/connectors/api.js +1 -0
- package/dist/connectors/remote-mcp.d.ts +2 -0
- package/dist/connectors/remote-mcp.js +6 -0
- package/dist/credentials.d.ts +6 -6
- package/dist/credentials.js +25 -21
- package/dist/identity.d.ts +4 -0
- package/dist/identity.js +17 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +6 -1
- package/dist/meta-tools.js +7 -2
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +4 -2
- package/dist/operator-ui/view.js +1 -1
- package/dist/providers/cloudflare.d.ts +2 -0
- package/dist/providers/cloudflare.js +1 -0
- package/dist/providers/linear.d.ts +2 -0
- package/dist/providers/linear.js +1 -0
- package/dist/providers/mixpanel.d.ts +2 -0
- package/dist/providers/mixpanel.js +1 -0
- package/dist/providers/notion.d.ts +2 -0
- package/dist/providers/notion.js +1 -0
- package/dist/providers/revenuecat.d.ts +2 -0
- package/dist/providers/revenuecat.js +1 -0
- package/dist/providers/stripe.d.ts +2 -0
- package/dist/providers/stripe.js +1 -0
- package/dist/providers/vercel.d.ts +25 -0
- package/dist/providers/vercel.js +1132 -0
- package/dist/registry.d.ts +25 -0
- package/dist/registry.js +200 -4
- package/dist/routes/access-tokens.js +2 -2
- package/dist/routes/activity.js +4 -1
- package/dist/routes/credentials.js +31 -12
- package/dist/routes/mcp.js +17 -2
- package/dist/routes/oauth.js +55 -11
- package/dist/routes/shared.d.ts +20 -4
- package/dist/routes/shared.js +92 -24
- package/dist/routes/ui.js +32 -13
- package/dist/types.d.ts +28 -2
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +18 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +14 -8
- package/documentation/auth.md +89 -9
- package/documentation/code-mode.md +2 -2
- package/documentation/connector-guides.md +2 -2
- package/documentation/connectors.md +21 -6
- package/documentation/meta-tools.md +4 -3
- package/documentation/operations.md +5 -2
- package/documentation/operator-ui.md +13 -4
- package/documentation/provider-conventions.md +5 -5
- package/documentation/request-admission.md +2 -1
- package/documentation/storage-and-credentials.md +42 -4
- package/documentation/upgrading.md +35 -4
- package/documentation/vercel.md +194 -0
- package/ethos.md +8 -8
- package/examples/worker/AGENTS.md +44 -0
- package/examples/worker/README.md +63 -14
- package/examples/worker/src/index.ts +26 -22
- package/package.json +5 -1
- package/templates/node/README.md +7 -0
- package/templates/node/package.json +1 -1
- 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
|
|
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.
|
package/dist/access-tokens.d.ts
CHANGED
|
@@ -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;
|
package/dist/access-tokens.js
CHANGED
|
@@ -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 {
|
|
230
|
+
return {
|
|
231
|
+
ok: true,
|
|
232
|
+
subjectId: record.id,
|
|
233
|
+
...(record.principal ? { principal: { ...record.principal } } : {}),
|
|
234
|
+
};
|
|
223
235
|
}
|
|
224
236
|
}
|
package/dist/connectors/api.d.ts
CHANGED
|
@@ -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
|
package/dist/connectors/api.js
CHANGED
|
@@ -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
|
: {}),
|
package/dist/credentials.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/credentials.js
CHANGED
|
@@ -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
|
|
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(
|
|
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>;
|
package/dist/identity.js
ADDED
|
@@ -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("
|
|
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,
|
package/dist/meta-tools.js
CHANGED
|
@@ -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.
|
|
483
|
-
|
|
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.`);
|