@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.
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +40 -0
- package/README.md +18 -10
- package/dist/activity-friction.d.ts +3 -0
- package/dist/activity-friction.js +19 -0
- package/dist/activity.d.ts +11 -2
- package/dist/activity.js +15 -19
- package/dist/auth/downstream-oauth.d.ts +2 -1
- package/dist/auth/downstream-oauth.js +10 -1
- package/dist/branding.d.ts +67 -0
- package/dist/branding.js +176 -0
- package/dist/connectors/remote-mcp.js +3 -5
- package/dist/credential-contract.d.ts +24 -0
- package/dist/credential-contract.js +1 -0
- package/dist/credential-rules.d.ts +85 -0
- package/dist/credential-rules.js +107 -0
- package/dist/credentials.d.ts +4 -100
- package/dist/credentials.js +3 -107
- package/dist/index.d.ts +22 -55
- package/dist/index.js +30 -58
- package/dist/invocation.js +2 -3
- package/dist/meta-tools.d.ts +4 -0
- package/dist/meta-tools.js +8 -4
- package/dist/module-contracts.d.ts +19 -0
- package/dist/module-contracts.js +1 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +6 -3
- package/dist/operator-ui/view.d.ts +2 -18
- package/dist/operator-ui/view.js +3 -20
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +4 -6
- package/dist/routes/activity.js +1 -1
- package/dist/routes/credentials.js +5 -2
- package/dist/routes/mcp.js +7 -3
- package/dist/routes/oauth-management.d.ts +2 -0
- package/dist/routes/oauth-management.js +108 -0
- package/dist/routes/oauth.d.ts +0 -1
- package/dist/routes/oauth.js +21 -121
- package/dist/routes/shared.d.ts +19 -17
- package/dist/routes/shared.js +48 -44
- package/dist/routes/ui.js +36 -33
- package/dist/server.js +6 -26
- package/dist/types.d.ts +2 -0
- package/dist/ui.d.ts +15 -70
- package/dist/ui.js +176 -317
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +26 -17
- package/documentation/auth.md +61 -106
- package/documentation/cloudflare.md +1 -1
- package/documentation/connectors.md +1 -1
- package/documentation/linear.md +1 -1
- package/documentation/meta-tools.md +6 -4
- package/documentation/mixpanel.md +1 -1
- package/documentation/notion.md +2 -2
- package/documentation/operations.md +12 -14
- package/documentation/operator-ui.md +82 -104
- package/documentation/optional-modules-upgrade.md +243 -0
- package/documentation/provider-conventions.md +5 -3
- package/documentation/revenuecat.md +1 -1
- package/documentation/storage-and-credentials.md +59 -40
- package/documentation/stripe.md +1 -1
- package/documentation/upgrading.md +29 -4
- package/ethos.md +22 -30
- package/examples/worker/AGENTS.md +3 -1
- package/examples/worker/README.md +68 -84
- package/examples/worker/src/d1-activity.ts +1 -1
- package/examples/worker/src/index.ts +11 -6
- package/package.json +18 -2
- package/templates/node/AGENTS.md +8 -6
- package/templates/node/README.md +56 -67
- package/templates/node/package.json +1 -1
- package/templates/node/src/file-activity.ts +1 -1
- package/templates/node/src/index.ts +11 -12
- package/dist/access-tokens.d.ts +0 -31
- package/dist/access-tokens.js +0 -236
- package/dist/routes/access-tokens.d.ts +0 -6
- package/dist/routes/access-tokens.js +0 -83
package/dist/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
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 "./
|
|
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
|
-
|
|
55
|
+
activityAccess: null,
|
|
56
|
+
credentialAdministration: null,
|
|
57
|
+
personalConnection: null,
|
|
57
58
|
},
|
|
58
59
|
storage: null,
|
|
59
60
|
publicUrl: null,
|
|
60
|
-
activity:
|
|
61
|
-
|
|
62
|
-
|
|
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.
|
|
157
|
-
throw new Error("ConnectaConfig.activity
|
|
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
|
|
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
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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 =
|
|
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
|
-
|
|
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";
|
package/dist/invocation.js
CHANGED
|
@@ -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
|
-
|
|
118
|
+
this.activity?.recordTool?.(this.activity, {
|
|
120
119
|
connectorId: identity.connectorId,
|
|
121
120
|
toolName: identity.toolName,
|
|
122
121
|
address: `${identity.connectorId}.${identity.toolName}`,
|
package/dist/meta-tools.d.ts
CHANGED
|
@@ -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;
|
package/dist/meta-tools.js
CHANGED
|
@@ -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
|
|
456
|
-
"
|
|
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:
|
|
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 {};
|