@zackbart/connecta 0.24.4 → 0.26.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 +43 -12
- package/CHANGELOG.md +524 -0
- package/README.md +32 -15
- package/SECURITY.md +6 -5
- package/bin/connecta.mjs +5 -1
- package/dist/activity.d.ts +10 -3
- package/dist/activity.js +10 -1
- package/dist/advertised-schema.js +53 -0
- package/dist/artifacts/connector.d.ts +55 -0
- package/dist/artifacts/connector.js +724 -0
- package/dist/artifacts/document.js +76 -0
- package/dist/artifacts/guide.js +140 -0
- package/dist/artifacts/html-scan.js +329 -0
- package/dist/artifacts/json.js +68 -0
- package/dist/artifacts/kv-store.d.ts +20 -0
- package/dist/artifacts/kv-store.js +140 -0
- package/dist/artifacts/markdown.js +449 -0
- package/dist/artifacts/module.d.ts +37 -0
- package/dist/artifacts/module.js +60 -0
- package/dist/artifacts/operations.d.ts +195 -0
- package/dist/artifacts/operations.js +944 -0
- package/dist/artifacts/refresh.d.ts +53 -0
- package/dist/artifacts/refresh.js +235 -0
- package/dist/artifacts/routes.js +194 -0
- package/dist/artifacts/types.d.ts +214 -0
- package/dist/artifacts/types.js +22 -0
- package/dist/artifacts/validate.d.ts +62 -0
- package/dist/artifacts/validate.js +652 -0
- package/dist/artifacts.d.ts +6 -0
- package/dist/artifacts.js +9 -0
- package/dist/auth/downstream-oauth.js +490 -106
- package/dist/call-admission.js +139 -113
- package/dist/catalog-service.js +152 -106
- package/dist/catalog.js +297 -38
- package/dist/concurrency.js +24 -23
- package/dist/connector-access.d.ts +18 -4
- package/dist/connector-access.js +65 -20
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +19 -0
- package/dist/connector-scope.js +6 -37
- package/dist/connectors/api.d.ts +2 -2
- package/dist/connectors/remote-mcp.d.ts +6 -5
- package/dist/connectors/remote-mcp.js +330 -240
- package/dist/credential-contract.d.ts +9 -0
- package/dist/credential-rules.js +2 -2
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +45 -3
- package/dist/errors.d.ts +16 -1
- package/dist/errors.js +28 -0
- package/dist/execute.js +745 -282
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +145 -63
- package/dist/executors/quickjs-runtime.js +4 -2
- package/dist/executors/quickjs.js +255 -212
- package/dist/fix-prompt.js +37 -0
- package/dist/index.d.ts +72 -7
- package/dist/index.js +281 -26
- package/dist/invocation.js +371 -286
- package/dist/meta-tools.js +213 -94
- package/dist/module-contracts.d.ts +21 -1
- package/dist/oauth-callback-outcome.js +94 -0
- package/dist/oauth-sealing.js +29 -0
- package/dist/operator-ui/fix-prompts.js +87 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +103 -1
- package/dist/operator-ui/setup-commands.js +53 -0
- package/dist/operator-ui/view.js +128 -3
- package/dist/program-source.js +321 -0
- package/dist/providers/notion.js +2 -2
- package/dist/providers/revenuecat.js +32 -5
- package/dist/providers/stripe.d.ts +3 -2
- package/dist/providers/vercel.js +1 -1
- package/dist/registry.d.ts +43 -7
- package/dist/registry.js +434 -288
- package/dist/resumable.js +1291 -0
- package/dist/routes/activity.js +74 -154
- package/dist/routes/actor-labels.js +114 -0
- package/dist/routes/credentials.js +196 -163
- package/dist/routes/mcp.js +228 -112
- package/dist/routes/oauth-management.js +73 -71
- package/dist/routes/oauth.js +44 -16
- package/dist/routes/operator.js +83 -0
- package/dist/routes/shared.d.ts +25 -1
- package/dist/routes/shared.js +20 -2
- package/dist/routes/ui-data.js +211 -0
- package/dist/routes/ui.js +76 -35
- package/dist/run-journal.js +347 -0
- package/dist/runtime/admission.js +43 -0
- package/dist/runtime/call-admission.js +67 -0
- package/dist/runtime/connector-scope.js +72 -0
- package/dist/runtime/run.js +174 -0
- package/dist/runtime/services.js +130 -0
- package/dist/runtime/shared-read.js +106 -0
- package/dist/runtime/storage.js +66 -0
- package/dist/server.js +137 -89
- package/dist/skills.js +28 -9
- package/dist/storage/file.js +39 -2
- package/dist/storage/memory.js +28 -16
- package/dist/timeout.js +39 -43
- package/dist/tool-safety.d.ts +27 -1
- package/dist/tool-safety.js +31 -0
- package/dist/types.d.ts +45 -7
- package/dist/ui.d.ts +25 -4
- package/dist/ui.js +77 -167
- package/dist/url-safety.js +157 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +367 -16
- package/documentation/auth.md +163 -2
- package/documentation/code-mode.md +274 -56
- package/documentation/meta-tools.md +200 -44
- package/ethos.md +46 -47
- package/examples/worker/AGENTS.md +6 -0
- package/examples/worker/README.md +224 -14
- package/examples/worker/src/d1-activity-row.ts +14 -2
- package/examples/worker/src/d1-activity.ts +4 -2
- package/examples/worker/src/d1-storage.ts +116 -0
- package/examples/worker/src/index.ts +42 -7
- package/examples/worker/src/r2-artifact-blobs.ts +29 -0
- package/examples/worker/wrangler.jsonc +21 -5
- package/package.json +16 -4
- package/templates/node/AGENTS.md +9 -2
- package/templates/node/README.md +31 -3
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +27 -0
- package/dist/auth/downstream-oauth.d.ts +0 -200
- package/dist/catalog-fingerprint.d.ts +0 -19
- package/dist/catalog-limits.d.ts +0 -9
- package/dist/catalog-service.d.ts +0 -263
- package/dist/catalog.d.ts +0 -87
- package/dist/concurrency.d.ts +0 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -96
- package/dist/credential-rules.d.ts +0 -85
- package/dist/execute.d.ts +0 -154
- package/dist/executor-result.d.ts +0 -10
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -67
- package/dist/favicon.d.ts +0 -1
- package/dist/identity.d.ts +0 -4
- package/dist/invocation.d.ts +0 -74
- package/dist/mcp-result.d.ts +0 -8
- package/dist/meta-tools.d.ts +0 -129
- package/dist/operator-ui/generated.d.ts +0 -2
- package/dist/operator-ui/view.d.ts +0 -188
- package/dist/result-shapes.d.ts +0 -13
- package/dist/retired-toolkits.d.ts +0 -2
- package/dist/routes/activity.d.ts +0 -2
- package/dist/routes/credentials.d.ts +0 -2
- package/dist/routes/mcp.d.ts +0 -10
- package/dist/routes/oauth-management.d.ts +0 -2
- package/dist/routes/oauth.d.ts +0 -2
- package/dist/routes/ui.d.ts +0 -2
- package/dist/server.d.ts +0 -9
- package/dist/skills.d.ts +0 -44
- package/dist/timeout.d.ts +0 -21
package/dist/connector-access.js
CHANGED
|
@@ -12,38 +12,69 @@ const hasControlCharacter = (value) => [...value].some((ch) => {
|
|
|
12
12
|
* Normalize a grant list. A bare connector id grants every tool on that
|
|
13
13
|
* connector; a `connector.tool` address grants one tool. Grants are additive,
|
|
14
14
|
* so a bare id beside addresses for the same connector means the whole
|
|
15
|
-
* connector.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* connector. Identity resolution can opt into `{ tool, requireReadOnly: true }`
|
|
16
|
+
* for a fixed address that also needs a current explicit read-only annotation;
|
|
17
|
+
* pool declarations remain strings. Anything else throws, and the caller
|
|
18
|
+
* decides whether that is a construction failure or a 403: a grant that
|
|
19
|
+
* cannot be parsed must never fail open.
|
|
18
20
|
*/
|
|
19
|
-
export function parseConnectorAccess(value) {
|
|
21
|
+
export function parseConnectorAccess(value, options = {}) {
|
|
20
22
|
if (value === "all")
|
|
21
23
|
return { connectorIds: "all" };
|
|
22
24
|
if (!Array.isArray(value))
|
|
23
25
|
throw new Error("invalid connector permission");
|
|
24
26
|
const whole = new Set();
|
|
25
|
-
const
|
|
27
|
+
const plain = new Map();
|
|
28
|
+
const guarded = new Map();
|
|
26
29
|
for (const entry of value) {
|
|
27
|
-
if (typeof entry
|
|
28
|
-
throw new Error("invalid connector permission");
|
|
29
|
-
if (CONNECTOR_ID_RE.test(entry)) {
|
|
30
|
+
if (typeof entry === "string" && CONNECTOR_ID_RE.test(entry)) {
|
|
30
31
|
whole.add(entry);
|
|
31
32
|
continue;
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
const isGuarded = typeof entry !== "string";
|
|
35
|
+
if (isGuarded && (!options.allowReadOnly || entry === null ||
|
|
36
|
+
typeof entry !== "object" || Array.isArray(entry) ||
|
|
37
|
+
!Object.hasOwn(entry, "tool") || !Object.hasOwn(entry, "requireReadOnly") ||
|
|
38
|
+
Object.keys(entry).length !== 2 || entry.requireReadOnly !== true)) {
|
|
39
|
+
throw new Error("invalid connector permission");
|
|
40
|
+
}
|
|
41
|
+
const address = isGuarded ? entry.tool : entry;
|
|
42
|
+
if (typeof address !== "string" || !TOOL_ADDRESS_RE.test(address) || hasControlCharacter(address)) {
|
|
34
43
|
throw new Error("invalid connector permission");
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
}
|
|
45
|
+
const dot = address.indexOf(".");
|
|
46
|
+
const connectorId = address.slice(0, dot);
|
|
47
|
+
const target = isGuarded ? guarded : plain;
|
|
48
|
+
const tools = target.get(connectorId) ?? new Set();
|
|
49
|
+
tools.add(address.slice(dot + 1));
|
|
50
|
+
target.set(connectorId, tools);
|
|
51
|
+
}
|
|
52
|
+
for (const id of whole) {
|
|
53
|
+
plain.delete(id);
|
|
54
|
+
guarded.delete(id);
|
|
55
|
+
}
|
|
56
|
+
for (const [id, names] of plain) {
|
|
57
|
+
const restricted = guarded.get(id);
|
|
58
|
+
if (!restricted)
|
|
59
|
+
continue;
|
|
60
|
+
for (const name of names)
|
|
61
|
+
restricted.delete(name);
|
|
62
|
+
if (restricted.size === 0)
|
|
63
|
+
guarded.delete(id);
|
|
64
|
+
}
|
|
65
|
+
const partial = new Map(plain);
|
|
66
|
+
for (const [id, names] of guarded) {
|
|
67
|
+
const combined = partial.get(id) ?? new Set();
|
|
68
|
+
for (const name of names)
|
|
69
|
+
combined.add(name);
|
|
70
|
+
partial.set(id, combined);
|
|
40
71
|
}
|
|
41
|
-
for (const id of whole)
|
|
42
|
-
partial.delete(id);
|
|
43
72
|
const connectorIds = [...new Set([...whole, ...partial.keys()])];
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
return {
|
|
74
|
+
connectorIds,
|
|
75
|
+
...(partial.size > 0 ? { toolAccess: partial } : {}),
|
|
76
|
+
...(guarded.size > 0 ? { guardedToolAccess: guarded } : {}),
|
|
77
|
+
};
|
|
47
78
|
}
|
|
48
79
|
/**
|
|
49
80
|
* The view a pool endpoint serves: the pool's grants, never wider than the
|
|
@@ -59,6 +90,7 @@ export function intersectAccess(ceiling, pool) {
|
|
|
59
90
|
: new Set(ceiling.connectorIds);
|
|
60
91
|
const connectorIds = [];
|
|
61
92
|
const toolAccess = new Map();
|
|
93
|
+
const guardedToolAccess = new Map();
|
|
62
94
|
for (const id of pool.connectorIds) {
|
|
63
95
|
if (allowedIds && !allowedIds.has(id))
|
|
64
96
|
continue;
|
|
@@ -73,7 +105,20 @@ export function intersectAccess(ceiling, pool) {
|
|
|
73
105
|
else if (fromPool ?? fromCeiling) {
|
|
74
106
|
toolAccess.set(id, (fromPool ?? fromCeiling));
|
|
75
107
|
}
|
|
108
|
+
const guarded = ceiling.guardedToolAccess?.get(id);
|
|
109
|
+
if (guarded) {
|
|
110
|
+
const allowed = toolAccess.get(id);
|
|
111
|
+
const retained = allowed
|
|
112
|
+
? new Set([...guarded].filter((name) => allowed.has(name)))
|
|
113
|
+
: guarded;
|
|
114
|
+
if (retained.size > 0)
|
|
115
|
+
guardedToolAccess.set(id, retained);
|
|
116
|
+
}
|
|
76
117
|
connectorIds.push(id);
|
|
77
118
|
}
|
|
78
|
-
return
|
|
119
|
+
return {
|
|
120
|
+
connectorIds,
|
|
121
|
+
...(toolAccess.size > 0 ? { toolAccess } : {}),
|
|
122
|
+
...(guardedToolAccess.size > 0 ? { guardedToolAccess } : {}),
|
|
123
|
+
};
|
|
79
124
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AuthenticatedIdentity, ConnectorContext } from "./types.js";
|
|
2
|
+
export interface ConnectorCaller {
|
|
3
|
+
identity: AuthenticatedIdentity;
|
|
4
|
+
/** The `/mcp/<pool>` the call arrived on, when it was a pool. */
|
|
5
|
+
pool?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function attachCaller(ctx: ConnectorContext, caller: ConnectorCaller | undefined): ConnectorContext;
|
|
8
|
+
export declare function callerOf(ctx: ConnectorContext): ConnectorCaller | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Who is calling, for the connectors connecta ships and nothing else.
|
|
2
|
+
//
|
|
3
|
+
// `ConnectorContext` deliberately carries no identity: a deployment's own
|
|
4
|
+
// connector acts for the deployment, and one that could read its caller would
|
|
5
|
+
// invite per-user behavior the access rules never saw. The built-in artifacts
|
|
6
|
+
// connector is the exception — every version it writes records who wrote it —
|
|
7
|
+
// so the identity rides beside the context, the way the OAuth sealer does,
|
|
8
|
+
// where only in-repo code can read it. Core sets it from the authorization
|
|
9
|
+
// that admitted the request, on the scoped registry view that request
|
|
10
|
+
// receives; no argument, header, or program can name it.
|
|
11
|
+
const callers = new WeakMap();
|
|
12
|
+
export function attachCaller(ctx, caller) {
|
|
13
|
+
if (caller)
|
|
14
|
+
callers.set(ctx, caller);
|
|
15
|
+
return ctx;
|
|
16
|
+
}
|
|
17
|
+
export function callerOf(ctx) {
|
|
18
|
+
return callers.get(ctx);
|
|
19
|
+
}
|
package/dist/connector-scope.js
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const CONNECTOR_SCOPE_CLOSE_BUDGET_MS = 100;
|
|
4
|
-
/**
|
|
5
|
-
* Bound on cleanup continued after the caller-facing window expires.
|
|
6
|
-
*
|
|
7
|
-
* `remoteMcp` spends at most one second asking the downstream to terminate its
|
|
8
|
-
* session, leaving another second for the local close. Custom hooks still get a
|
|
9
|
-
* finite background window: handing a never-settling promise to a Worker's
|
|
10
|
-
* `waitUntil` would otherwise keep the invocation alive until the platform cap.
|
|
11
|
-
*/
|
|
12
|
-
const CONNECTOR_SCOPE_DEFER_BUDGET_MS = 2_000;
|
|
13
|
-
/** Resolve when `work` settles or `budgetMs` expires; never reject. */
|
|
14
|
-
function waitAtMost(work, budgetMs) {
|
|
15
|
-
return Promise.race([work, sleep(budgetMs)]).then(() => { });
|
|
16
|
-
}
|
|
1
|
+
import { closeScope } from "./runtime/connector-scope.js";
|
|
2
|
+
import { runEdge } from "./runtime/run.js";
|
|
17
3
|
/**
|
|
18
4
|
* Tell a connector that a scope owned by the core has ended.
|
|
19
5
|
*
|
|
@@ -25,25 +11,8 @@ function waitAtMost(work, budgetMs) {
|
|
|
25
11
|
* window. Callers own the at-most-once guarantee and must not use the scope
|
|
26
12
|
* again after this returns.
|
|
27
13
|
*/
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
33
|
-
// Attach both handlers before either timer can win, so a late rejection is
|
|
34
|
-
// consumed rather than becoming an unhandled rejection.
|
|
35
|
-
const settled = closing.then(() => { }, () => { });
|
|
36
|
-
if (defer) {
|
|
37
|
-
try {
|
|
38
|
-
defer(waitAtMost(settled, CONNECTOR_SCOPE_DEFER_BUDGET_MS));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// A runtime hook is best-effort too; the caller cap still applies.
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
await waitAtMost(settled, CONNECTOR_SCOPE_CLOSE_BUDGET_MS);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// The scope is over whether or not the connector managed to clean it up.
|
|
48
|
-
}
|
|
14
|
+
export function closeConnectorScope(connector, ctx, defer) {
|
|
15
|
+
// The Promise face of closeScope (src/runtime/connector-scope.ts), which
|
|
16
|
+
// owns the budgets; an Effect caller registers it as a Scope finalizer.
|
|
17
|
+
return runEdge(closeScope(connector, ctx, defer));
|
|
49
18
|
}
|
package/dist/connectors/api.d.ts
CHANGED
|
@@ -52,9 +52,9 @@ export interface ApiOptions {
|
|
|
52
52
|
* discovery metadata. See `Connector.usageGuide`.
|
|
53
53
|
*/
|
|
54
54
|
usageGuide?: string | ConnectorUsageGuide;
|
|
55
|
-
/** Optional operator-managed credential exposed through ctx.credential and
|
|
55
|
+
/** Optional operator-managed credential exposed through ctx.credential and the connection in the operator UI. */
|
|
56
56
|
credential?: ConnectorCredentialConfig;
|
|
57
|
-
/** Optional validation behind
|
|
57
|
+
/** Optional validation behind the connection's Test action in the operator UI. */
|
|
58
58
|
testCredential?: (value: string, ctx: ConnectorContext) => Promise<CredentialTestResult>;
|
|
59
59
|
/** Optional validation for named multi-field credentials. */
|
|
60
60
|
testCredentials?: (values: ConnectorCredentialValues, ctx: ConnectorContext) => Promise<CredentialTestResult>;
|
|
@@ -2,8 +2,8 @@ import type { FetchLike, Transport } from "@modelcontextprotocol/client";
|
|
|
2
2
|
import { ConnectorCallError } from "../errors.js";
|
|
3
3
|
import type { Connector, ConnectorCallAdmissionPolicy, ConnectorContext, ConnectorCredentialConfig, ConnectorUsageGuide, Logger } from "../types.js";
|
|
4
4
|
/**
|
|
5
|
-
* A static downstream credential the
|
|
6
|
-
* rather than
|
|
5
|
+
* A static downstream credential supplied through the connection in the
|
|
6
|
+
* operator UI rather than baked into deployment source.
|
|
7
7
|
*
|
|
8
8
|
* The connector, its endpoint, and the credential *slot* stay declared in
|
|
9
9
|
* code; only the secret arrives through the operator route, exactly as for
|
|
@@ -15,8 +15,8 @@ import type { Connector, ConnectorCallAdmissionPolicy, ConnectorContext, Connect
|
|
|
15
15
|
interface RemoteMcpCredentialAuth {
|
|
16
16
|
type: "credential";
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
* `{ label: "API key" }`; a maintained provider passes the name the provider
|
|
18
|
+
* Slot description rendered in the connection in the operator UI. Defaults
|
|
19
|
+
* to `{ label: "API key" }`; a maintained provider passes the name the provider
|
|
20
20
|
* itself uses. Named `fields` are refused — this shape reads the reserved
|
|
21
21
|
* `value` field only.
|
|
22
22
|
*/
|
|
@@ -122,7 +122,8 @@ export declare class RemoteMcpRedirectError extends ConnectorCallError {
|
|
|
122
122
|
* Wrap fetch with explicit, bounded redirect handling.
|
|
123
123
|
*
|
|
124
124
|
* The starting URL of each fetch call is trusted by its caller (the configured
|
|
125
|
-
* MCP endpoint, or an OAuth URL
|
|
125
|
+
* MCP endpoint, or an OAuth URL the pinned SDK discovered and
|
|
126
|
+
* `learnedUrlSafeFetch` already admitted). Only Location
|
|
126
127
|
* values are policy-controlled here. No rejected target is ever fetched, so
|
|
127
128
|
* arbitrary static header names receive the same protection as Authorization.
|
|
128
129
|
*/
|