@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.
Files changed (155) hide show
  1. package/AGENTS.md +43 -12
  2. package/CHANGELOG.md +524 -0
  3. package/README.md +32 -15
  4. package/SECURITY.md +6 -5
  5. package/bin/connecta.mjs +5 -1
  6. package/dist/activity.d.ts +10 -3
  7. package/dist/activity.js +10 -1
  8. package/dist/advertised-schema.js +53 -0
  9. package/dist/artifacts/connector.d.ts +55 -0
  10. package/dist/artifacts/connector.js +724 -0
  11. package/dist/artifacts/document.js +76 -0
  12. package/dist/artifacts/guide.js +140 -0
  13. package/dist/artifacts/html-scan.js +329 -0
  14. package/dist/artifacts/json.js +68 -0
  15. package/dist/artifacts/kv-store.d.ts +20 -0
  16. package/dist/artifacts/kv-store.js +140 -0
  17. package/dist/artifacts/markdown.js +449 -0
  18. package/dist/artifacts/module.d.ts +37 -0
  19. package/dist/artifacts/module.js +60 -0
  20. package/dist/artifacts/operations.d.ts +195 -0
  21. package/dist/artifacts/operations.js +944 -0
  22. package/dist/artifacts/refresh.d.ts +53 -0
  23. package/dist/artifacts/refresh.js +235 -0
  24. package/dist/artifacts/routes.js +194 -0
  25. package/dist/artifacts/types.d.ts +214 -0
  26. package/dist/artifacts/types.js +22 -0
  27. package/dist/artifacts/validate.d.ts +62 -0
  28. package/dist/artifacts/validate.js +652 -0
  29. package/dist/artifacts.d.ts +6 -0
  30. package/dist/artifacts.js +9 -0
  31. package/dist/auth/downstream-oauth.js +490 -106
  32. package/dist/call-admission.js +139 -113
  33. package/dist/catalog-service.js +152 -106
  34. package/dist/catalog.js +297 -38
  35. package/dist/concurrency.js +24 -23
  36. package/dist/connector-access.d.ts +18 -4
  37. package/dist/connector-access.js +65 -20
  38. package/dist/connector-caller.d.ts +8 -0
  39. package/dist/connector-caller.js +19 -0
  40. package/dist/connector-scope.js +6 -37
  41. package/dist/connectors/api.d.ts +2 -2
  42. package/dist/connectors/remote-mcp.d.ts +6 -5
  43. package/dist/connectors/remote-mcp.js +330 -240
  44. package/dist/credential-contract.d.ts +9 -0
  45. package/dist/credential-rules.js +2 -2
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +45 -3
  48. package/dist/errors.d.ts +16 -1
  49. package/dist/errors.js +28 -0
  50. package/dist/execute.js +745 -282
  51. package/dist/executor-admission.d.ts +11 -1
  52. package/dist/executor-admission.js +145 -63
  53. package/dist/executors/quickjs-runtime.js +4 -2
  54. package/dist/executors/quickjs.js +255 -212
  55. package/dist/fix-prompt.js +37 -0
  56. package/dist/index.d.ts +72 -7
  57. package/dist/index.js +281 -26
  58. package/dist/invocation.js +371 -286
  59. package/dist/meta-tools.js +213 -94
  60. package/dist/module-contracts.d.ts +21 -1
  61. package/dist/oauth-callback-outcome.js +94 -0
  62. package/dist/oauth-sealing.js +29 -0
  63. package/dist/operator-ui/fix-prompts.js +87 -0
  64. package/dist/operator-ui/generated.js +2 -2
  65. package/dist/operator-ui/model.d.ts +103 -1
  66. package/dist/operator-ui/setup-commands.js +53 -0
  67. package/dist/operator-ui/view.js +128 -3
  68. package/dist/program-source.js +321 -0
  69. package/dist/providers/notion.js +2 -2
  70. package/dist/providers/revenuecat.js +32 -5
  71. package/dist/providers/stripe.d.ts +3 -2
  72. package/dist/providers/vercel.js +1 -1
  73. package/dist/registry.d.ts +43 -7
  74. package/dist/registry.js +434 -288
  75. package/dist/resumable.js +1291 -0
  76. package/dist/routes/activity.js +74 -154
  77. package/dist/routes/actor-labels.js +114 -0
  78. package/dist/routes/credentials.js +196 -163
  79. package/dist/routes/mcp.js +228 -112
  80. package/dist/routes/oauth-management.js +73 -71
  81. package/dist/routes/oauth.js +44 -16
  82. package/dist/routes/operator.js +83 -0
  83. package/dist/routes/shared.d.ts +25 -1
  84. package/dist/routes/shared.js +20 -2
  85. package/dist/routes/ui-data.js +211 -0
  86. package/dist/routes/ui.js +76 -35
  87. package/dist/run-journal.js +347 -0
  88. package/dist/runtime/admission.js +43 -0
  89. package/dist/runtime/call-admission.js +67 -0
  90. package/dist/runtime/connector-scope.js +72 -0
  91. package/dist/runtime/run.js +174 -0
  92. package/dist/runtime/services.js +130 -0
  93. package/dist/runtime/shared-read.js +106 -0
  94. package/dist/runtime/storage.js +66 -0
  95. package/dist/server.js +137 -89
  96. package/dist/skills.js +28 -9
  97. package/dist/storage/file.js +39 -2
  98. package/dist/storage/memory.js +28 -16
  99. package/dist/timeout.js +39 -43
  100. package/dist/tool-safety.d.ts +27 -1
  101. package/dist/tool-safety.js +31 -0
  102. package/dist/types.d.ts +45 -7
  103. package/dist/ui.d.ts +25 -4
  104. package/dist/ui.js +77 -167
  105. package/dist/url-safety.js +157 -0
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/documentation/architecture.md +367 -16
  109. package/documentation/auth.md +163 -2
  110. package/documentation/code-mode.md +274 -56
  111. package/documentation/meta-tools.md +200 -44
  112. package/ethos.md +46 -47
  113. package/examples/worker/AGENTS.md +6 -0
  114. package/examples/worker/README.md +224 -14
  115. package/examples/worker/src/d1-activity-row.ts +14 -2
  116. package/examples/worker/src/d1-activity.ts +4 -2
  117. package/examples/worker/src/d1-storage.ts +116 -0
  118. package/examples/worker/src/index.ts +42 -7
  119. package/examples/worker/src/r2-artifact-blobs.ts +29 -0
  120. package/examples/worker/wrangler.jsonc +21 -5
  121. package/package.json +16 -4
  122. package/templates/node/AGENTS.md +9 -2
  123. package/templates/node/README.md +31 -3
  124. package/templates/node/package.json +1 -1
  125. package/templates/node/src/index.ts +27 -0
  126. package/dist/auth/downstream-oauth.d.ts +0 -200
  127. package/dist/catalog-fingerprint.d.ts +0 -19
  128. package/dist/catalog-limits.d.ts +0 -9
  129. package/dist/catalog-service.d.ts +0 -263
  130. package/dist/catalog.d.ts +0 -87
  131. package/dist/concurrency.d.ts +0 -3
  132. package/dist/connectors/guarded-fetch.d.ts +0 -96
  133. package/dist/credential-rules.d.ts +0 -85
  134. package/dist/execute.d.ts +0 -154
  135. package/dist/executor-result.d.ts +0 -10
  136. package/dist/executors/quickjs-child.d.ts +0 -1
  137. package/dist/executors/quickjs-protocol.d.ts +0 -67
  138. package/dist/favicon.d.ts +0 -1
  139. package/dist/identity.d.ts +0 -4
  140. package/dist/invocation.d.ts +0 -74
  141. package/dist/mcp-result.d.ts +0 -8
  142. package/dist/meta-tools.d.ts +0 -129
  143. package/dist/operator-ui/generated.d.ts +0 -2
  144. package/dist/operator-ui/view.d.ts +0 -188
  145. package/dist/result-shapes.d.ts +0 -13
  146. package/dist/retired-toolkits.d.ts +0 -2
  147. package/dist/routes/activity.d.ts +0 -2
  148. package/dist/routes/credentials.d.ts +0 -2
  149. package/dist/routes/mcp.d.ts +0 -10
  150. package/dist/routes/oauth-management.d.ts +0 -2
  151. package/dist/routes/oauth.d.ts +0 -2
  152. package/dist/routes/ui.d.ts +0 -2
  153. package/dist/server.d.ts +0 -9
  154. package/dist/skills.d.ts +0 -44
  155. package/dist/timeout.d.ts +0 -21
@@ -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. Anything else — an unknown shape, an empty tool name, a
16
- * non-string — throws, and the caller decides whether that is a construction
17
- * failure or a 403: a grant that cannot be parsed must never fail open.
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 partial = new Map();
27
+ const plain = new Map();
28
+ const guarded = new Map();
26
29
  for (const entry of value) {
27
- if (typeof entry !== "string")
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
- if (!TOOL_ADDRESS_RE.test(entry) || hasControlCharacter(entry))
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
- const dot = entry.indexOf(".");
36
- const connectorId = entry.slice(0, dot);
37
- const tools = partial.get(connectorId) ?? new Set();
38
- tools.add(entry.slice(dot + 1));
39
- partial.set(connectorId, tools);
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 partial.size > 0
45
- ? { connectorIds, toolAccess: partial }
46
- : { connectorIds };
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 toolAccess.size > 0 ? { connectorIds, toolAccess } : { connectorIds };
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
+ }
@@ -1,19 +1,5 @@
1
- import { sleep } from "./timeout.js";
2
- /** Enough for local transport abort/close without letting cleanup own latency. */
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 async function closeConnectorScope(connector, ctx, defer) {
29
- try {
30
- const closing = connector.closeScope?.(ctx);
31
- if (!closing)
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
  }
@@ -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 /credentials. */
55
+ /** Optional operator-managed credential exposed through ctx.credential and the connection in the operator UI. */
56
56
  credential?: ConnectorCredentialConfig;
57
- /** Optional validation behind /credentials' Test action. */
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 operator supplies at `/credentials`
6
- * rather than the deployment baking into its source.
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
- * Operator-facing slot description rendered on `/credentials`. Defaults to
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 discovered by the pinned SDK). Only Location
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
  */