@zackbart/connecta 0.15.1 → 0.16.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 +28 -15
- package/CHANGELOG.md +466 -0
- package/README.md +19 -4
- package/bin/connecta.mjs +44 -3
- package/dist/access-tokens.d.ts +0 -1
- package/dist/access-tokens.js +0 -1
- package/dist/activity.d.ts +50 -2
- package/dist/activity.js +34 -1
- package/dist/apps-shell.d.ts +0 -1
- package/dist/apps-shell.js +0 -1
- package/dist/auth/bearer.d.ts +0 -1
- package/dist/auth/bearer.js +0 -1
- package/dist/auth/clerk.d.ts +0 -1
- package/dist/auth/clerk.js +45 -9
- package/dist/auth/downstream-oauth.d.ts +0 -1
- package/dist/auth/downstream-oauth.js +0 -1
- package/dist/call-admission.d.ts +0 -1
- package/dist/call-admission.js +0 -1
- package/dist/catalog-drift.d.ts +99 -0
- package/dist/catalog-drift.js +251 -0
- package/dist/catalog-fingerprint.d.ts +0 -1
- package/dist/catalog-fingerprint.js +0 -1
- package/dist/catalog-limits.d.ts +0 -1
- package/dist/catalog-limits.js +0 -1
- package/dist/catalog-service.d.ts +0 -1
- package/dist/catalog-service.js +0 -1
- package/dist/catalog.d.ts +0 -1
- package/dist/catalog.js +0 -1
- package/dist/concurrency.d.ts +0 -1
- package/dist/concurrency.js +0 -1
- package/dist/connector-scope.d.ts +0 -1
- package/dist/connector-scope.js +0 -1
- package/dist/connectors/api.d.ts +29 -21
- package/dist/connectors/api.js +45 -19
- package/dist/connectors/guarded-fetch.d.ts +106 -0
- package/dist/connectors/guarded-fetch.js +289 -0
- package/dist/connectors/remote-mcp.d.ts +0 -1
- package/dist/connectors/remote-mcp.js +0 -1
- package/dist/credentials.d.ts +0 -1
- package/dist/credentials.js +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js +0 -1
- package/dist/execute.d.ts +0 -1
- package/dist/execute.js +0 -1
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +28 -1
- package/dist/executor-result.d.ts +0 -1
- package/dist/executor-result.js +0 -1
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-child.js +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -1
- package/dist/executors/quickjs-protocol.js +0 -1
- package/dist/executors/quickjs-runtime.d.ts +0 -1
- package/dist/executors/quickjs-runtime.js +0 -1
- package/dist/executors/quickjs.d.ts +0 -1
- package/dist/executors/quickjs.js +2 -1
- package/dist/favicon.d.ts +0 -1
- package/dist/favicon.js +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +24 -7
- package/dist/invocation.d.ts +0 -1
- package/dist/invocation.js +0 -1
- package/dist/json-schema.d.ts +0 -1
- package/dist/json-schema.js +0 -1
- package/dist/mcp-result.d.ts +0 -1
- package/dist/mcp-result.js +0 -1
- package/dist/meta-tools.d.ts +0 -1
- package/dist/meta-tools.js +0 -1
- package/dist/node.d.ts +0 -1
- package/dist/node.js +0 -1
- package/dist/operator-ui/generated.d.ts +2 -3
- package/dist/operator-ui/generated.js +3 -4
- package/dist/operator-ui/model.d.ts +10 -1
- package/dist/operator-ui/model.js +0 -1
- package/dist/operator-ui/view.d.ts +167 -0
- package/dist/operator-ui/view.js +270 -0
- package/dist/providers/cloudflare.d.ts +0 -1
- package/dist/providers/cloudflare.js +250 -276
- package/dist/providers/linear.d.ts +26 -5
- package/dist/providers/linear.js +32 -61
- package/dist/providers/mixpanel.d.ts +35 -4
- package/dist/providers/mixpanel.js +53 -79
- package/dist/providers/notion.d.ts +0 -1
- package/dist/providers/notion.js +87 -42
- package/dist/providers/stripe.d.ts +13 -1
- package/dist/providers/stripe.js +31 -60
- package/dist/registry.d.ts +34 -2
- package/dist/registry.js +85 -5
- package/dist/retired-toolkits.d.ts +0 -1
- package/dist/retired-toolkits.js +0 -1
- package/dist/routes/access-tokens.d.ts +0 -1
- package/dist/routes/access-tokens.js +0 -1
- package/dist/routes/activity.d.ts +0 -1
- package/dist/routes/activity.js +0 -1
- package/dist/routes/credentials.d.ts +0 -1
- package/dist/routes/credentials.js +0 -1
- package/dist/routes/mcp.d.ts +0 -1
- package/dist/routes/mcp.js +0 -1
- package/dist/routes/oauth.d.ts +0 -1
- package/dist/routes/oauth.js +0 -1
- package/dist/routes/shared.d.ts +2 -1
- package/dist/routes/shared.js +0 -1
- package/dist/routes/ui.d.ts +0 -1
- package/dist/routes/ui.js +0 -1
- package/dist/server.d.ts +0 -1
- package/dist/server.js +11 -1
- package/dist/skills.d.ts +0 -1
- package/dist/skills.js +0 -1
- package/dist/storage/file.d.ts +0 -1
- package/dist/storage/file.js +0 -1
- package/dist/storage/memory.d.ts +0 -1
- package/dist/storage/memory.js +0 -1
- package/dist/timeout.d.ts +0 -1
- package/dist/timeout.js +0 -1
- package/dist/tool-safety.d.ts +0 -1
- package/dist/tool-safety.js +0 -1
- package/dist/types.d.ts +45 -1
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +0 -1
- package/dist/ui.js +13 -137
- package/dist/validate.d.ts +13 -15
- package/dist/validate.js +24 -11
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/documentation/architecture.md +186 -5
- package/documentation/auth.md +11 -0
- package/documentation/call-admission.md +142 -6
- package/documentation/cloudflare.md +58 -8
- package/documentation/code-mode.md +12 -12
- package/documentation/connector-guides.md +172 -5
- package/documentation/connectors.md +89 -0
- package/documentation/linear.md +20 -2
- package/documentation/mcp-ui-design.md +2 -2
- package/documentation/mixpanel.md +42 -9
- package/documentation/notion.md +16 -0
- package/documentation/operations.md +283 -5
- package/documentation/operator-ui.md +120 -5
- package/documentation/provider-audit.md +160 -0
- package/documentation/provider-conventions.md +631 -0
- package/documentation/request-admission.md +167 -5
- package/documentation/rich-output-design.md +1 -2
- package/documentation/stripe.md +15 -0
- package/ethos.md +13 -8
- package/examples/worker/README.md +96 -12
- package/examples/worker/src/index.ts +46 -1
- package/examples/worker/wrangler.jsonc +14 -1
- package/package.json +4 -5
- package/templates/node/.dockerignore +10 -0
- package/templates/node/.env.example +40 -3
- package/templates/node/AGENTS.md +11 -1
- package/templates/node/Dockerfile +49 -0
- package/templates/node/README.md +117 -2
- package/templates/node/docker-compose.yml +39 -0
- package/templates/node/package.json +1 -1
- package/templates/node/src/file-activity.ts +182 -0
- package/templates/node/src/index.ts +80 -4
- package/assets/connecta-clay-hero.png +0 -0
- package/dist/access-tokens.d.ts.map +0 -1
- package/dist/access-tokens.js.map +0 -1
- package/dist/activity.d.ts.map +0 -1
- package/dist/activity.js.map +0 -1
- package/dist/apps-shell.d.ts.map +0 -1
- package/dist/apps-shell.js.map +0 -1
- package/dist/auth/bearer.d.ts.map +0 -1
- package/dist/auth/bearer.js.map +0 -1
- package/dist/auth/clerk.d.ts.map +0 -1
- package/dist/auth/clerk.js.map +0 -1
- package/dist/auth/downstream-oauth.d.ts.map +0 -1
- package/dist/auth/downstream-oauth.js.map +0 -1
- package/dist/call-admission.d.ts.map +0 -1
- package/dist/call-admission.js.map +0 -1
- package/dist/catalog-fingerprint.d.ts.map +0 -1
- package/dist/catalog-fingerprint.js.map +0 -1
- package/dist/catalog-limits.d.ts.map +0 -1
- package/dist/catalog-limits.js.map +0 -1
- package/dist/catalog-service.d.ts.map +0 -1
- package/dist/catalog-service.js.map +0 -1
- package/dist/catalog.d.ts.map +0 -1
- package/dist/catalog.js.map +0 -1
- package/dist/concurrency.d.ts.map +0 -1
- package/dist/concurrency.js.map +0 -1
- package/dist/connector-scope.d.ts.map +0 -1
- package/dist/connector-scope.js.map +0 -1
- package/dist/connectors/api.d.ts.map +0 -1
- package/dist/connectors/api.js.map +0 -1
- package/dist/connectors/remote-mcp.d.ts.map +0 -1
- package/dist/connectors/remote-mcp.js.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js.map +0 -1
- package/dist/executor-admission.d.ts.map +0 -1
- package/dist/executor-admission.js.map +0 -1
- package/dist/executor-result.d.ts.map +0 -1
- package/dist/executor-result.js.map +0 -1
- package/dist/executors/quickjs-child.d.ts.map +0 -1
- package/dist/executors/quickjs-child.js.map +0 -1
- package/dist/executors/quickjs-protocol.d.ts.map +0 -1
- package/dist/executors/quickjs-protocol.js.map +0 -1
- package/dist/executors/quickjs-runtime.d.ts.map +0 -1
- package/dist/executors/quickjs-runtime.js.map +0 -1
- package/dist/executors/quickjs.d.ts.map +0 -1
- package/dist/executors/quickjs.js.map +0 -1
- package/dist/favicon.d.ts.map +0 -1
- package/dist/favicon.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invocation.d.ts.map +0 -1
- package/dist/invocation.js.map +0 -1
- package/dist/json-schema.d.ts.map +0 -1
- package/dist/json-schema.js.map +0 -1
- package/dist/mcp-result.d.ts.map +0 -1
- package/dist/mcp-result.js.map +0 -1
- package/dist/meta-tools.d.ts.map +0 -1
- package/dist/meta-tools.js.map +0 -1
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js.map +0 -1
- package/dist/operator-ui/generated.d.ts.map +0 -1
- package/dist/operator-ui/generated.js.map +0 -1
- package/dist/operator-ui/model.d.ts.map +0 -1
- package/dist/operator-ui/model.js.map +0 -1
- package/dist/providers/cloudflare.d.ts.map +0 -1
- package/dist/providers/cloudflare.js.map +0 -1
- package/dist/providers/linear.d.ts.map +0 -1
- package/dist/providers/linear.js.map +0 -1
- package/dist/providers/mixpanel.d.ts.map +0 -1
- package/dist/providers/mixpanel.js.map +0 -1
- package/dist/providers/notion.d.ts.map +0 -1
- package/dist/providers/notion.js.map +0 -1
- package/dist/providers/stripe.d.ts.map +0 -1
- package/dist/providers/stripe.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/retired-toolkits.d.ts.map +0 -1
- package/dist/retired-toolkits.js.map +0 -1
- package/dist/routes/access-tokens.d.ts.map +0 -1
- package/dist/routes/access-tokens.js.map +0 -1
- package/dist/routes/activity.d.ts.map +0 -1
- package/dist/routes/activity.js.map +0 -1
- package/dist/routes/credentials.d.ts.map +0 -1
- package/dist/routes/credentials.js.map +0 -1
- package/dist/routes/mcp.d.ts.map +0 -1
- package/dist/routes/mcp.js.map +0 -1
- package/dist/routes/oauth.d.ts.map +0 -1
- package/dist/routes/oauth.js.map +0 -1
- package/dist/routes/shared.d.ts.map +0 -1
- package/dist/routes/shared.js.map +0 -1
- package/dist/routes/ui.d.ts.map +0 -1
- package/dist/routes/ui.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js.map +0 -1
- package/dist/storage/file.d.ts.map +0 -1
- package/dist/storage/file.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/timeout.d.ts.map +0 -1
- package/dist/timeout.js.map +0 -1
- package/dist/tool-safety.d.ts.map +0 -1
- package/dist/tool-safety.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
- package/examples/node/README.md +0 -52
- package/examples/node/src/index.ts +0 -71
- package/src/access-tokens.ts +0 -289
- package/src/activity.ts +0 -234
- package/src/apps-shell.ts +0 -370
- package/src/auth/bearer.ts +0 -61
- package/src/auth/clerk.ts +0 -553
- package/src/auth/downstream-oauth.ts +0 -575
- package/src/call-admission.ts +0 -521
- package/src/catalog-fingerprint.ts +0 -52
- package/src/catalog-limits.ts +0 -11
- package/src/catalog-service.ts +0 -1153
- package/src/catalog.ts +0 -790
- package/src/concurrency.ts +0 -39
- package/src/connector-scope.ts +0 -66
- package/src/connectors/api.ts +0 -159
- package/src/connectors/remote-mcp.ts +0 -1090
- package/src/credentials.ts +0 -428
- package/src/errors.ts +0 -364
- package/src/execute.ts +0 -1406
- package/src/executor-admission.ts +0 -328
- package/src/executor-result.ts +0 -107
- package/src/executors/quickjs-child.ts +0 -172
- package/src/executors/quickjs-protocol.ts +0 -99
- package/src/executors/quickjs-runtime.ts +0 -470
- package/src/executors/quickjs.ts +0 -776
- package/src/favicon.ts +0 -53
- package/src/index.ts +0 -666
- package/src/invocation.ts +0 -655
- package/src/json-schema.ts +0 -11
- package/src/mcp-result.ts +0 -43
- package/src/meta-tools.ts +0 -1607
- package/src/node.ts +0 -206
- package/src/operator-ui/browser.css +0 -423
- package/src/operator-ui/browser.ts +0 -1281
- package/src/operator-ui/generated.ts +0 -4
- package/src/operator-ui/model.ts +0 -111
- package/src/providers/cloudflare.ts +0 -3999
- package/src/providers/linear.ts +0 -302
- package/src/providers/mixpanel.ts +0 -229
- package/src/providers/notion.ts +0 -1879
- package/src/providers/stripe.ts +0 -307
- package/src/registry.ts +0 -1085
- package/src/retired-toolkits.ts +0 -14
- package/src/routes/access-tokens.ts +0 -115
- package/src/routes/activity.ts +0 -215
- package/src/routes/credentials.ts +0 -254
- package/src/routes/mcp.ts +0 -429
- package/src/routes/oauth.ts +0 -322
- package/src/routes/shared.ts +0 -247
- package/src/routes/ui.ts +0 -152
- package/src/server.ts +0 -195
- package/src/skills.ts +0 -271
- package/src/storage/file.ts +0 -123
- package/src/storage/memory.ts +0 -44
- package/src/timeout.ts +0 -84
- package/src/tool-safety.ts +0 -15
- package/src/types.ts +0 -523
- package/src/ui.ts +0 -744
- package/src/validate.ts +0 -387
- package/src/version.ts +0 -7
|
@@ -1,7 +1,169 @@
|
|
|
1
1
|
# Request admission
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Connecta bounds work at the Web-standard request boundary — before inbound
|
|
4
|
+
auth, before the MCP server exists, before any catalog is touched. A burst
|
|
5
|
+
therefore meets an explicit active count and an explicit queue instead of
|
|
6
|
+
asking traffic shape and the runtime allocator to pick the process high-water
|
|
7
|
+
mark.
|
|
8
|
+
|
|
9
|
+
There are two pools here and a third elsewhere. This guide covers the first
|
|
10
|
+
two: the deployment-wide `/mcp` pool and the fallback code pool. Per-connector
|
|
11
|
+
downstream bounds are [call admission](./call-admission.md), which is a
|
|
12
|
+
different question — request admission bounds the whole MCP envelope, call
|
|
13
|
+
admission bounds the individual `Connector.callTool` attempts fanned out inside
|
|
14
|
+
it.
|
|
15
|
+
|
|
16
|
+
## The pools
|
|
17
|
+
|
|
18
|
+
Every non-preflight `/mcp` request takes one permit from a deployment-wide FIFO
|
|
19
|
+
pool. Initialization, discovery, ordinary calls, and `execute_code` all pay it.
|
|
20
|
+
A program then takes a *second* permit from the deliberately smaller code pool,
|
|
21
|
+
so one request cannot trade ordinary capacity for an unbounded number of
|
|
22
|
+
sandboxes.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
const connecta = createConnecta({
|
|
26
|
+
admission: {
|
|
27
|
+
requests: { concurrency: 16, maxQueueSize: 32, queueTimeoutMs: 5_000, retryAfterMs: 1_000 },
|
|
28
|
+
code: { concurrency: 2, maxQueueSize: 8, queueTimeoutMs: 5_000, retryAfterMs: 1_000 },
|
|
29
|
+
},
|
|
30
|
+
// …
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Those values are the defaults, and both pools are the same
|
|
35
|
+
`AdmissionController` (`src/executor-admission.ts`) with different numbers.
|
|
36
|
+
`maxQueueSize: 0` is the fail-fast shape. Every value is a finite whole number;
|
|
37
|
+
concurrency and the queue timeout must be positive, while queue size and the
|
|
38
|
+
retry hint may be zero. Invalid bounds throw at construction rather than
|
|
39
|
+
silently removing the deployment's memory boundary — a pool that quietly became
|
|
40
|
+
unbounded is worse than a deployment that refuses to boot.
|
|
41
|
+
|
|
42
|
+
`admission.code` is a *fallback*. An executor that implements `acquire()` is an
|
|
43
|
+
`AdmittingExecutor` and already owns a bounded pool, so its own settings win and
|
|
44
|
+
connecta warns that `admission.code` was ignored. `quickJsExecutor()` is one of
|
|
45
|
+
those: it defaults to one active execution and 32 queued callers, configured on
|
|
46
|
+
the executor rather than here. Cloudflare's `DynamicWorkerExecutor` is not, so
|
|
47
|
+
a Worker deployment gets the fallback pool wrapped around it at construction —
|
|
48
|
+
which is also why `/health` always has a code-admission shape to report.
|
|
49
|
+
|
|
50
|
+
The request pool is global FIFO across identities. It is a capacity boundary,
|
|
51
|
+
not tenant fairness: one busy caller can occupy it. Per-tenant fairness needs a
|
|
52
|
+
policy above connecta, and one deployment serves one audience anyway
|
|
53
|
+
([`ethos.md`](../ethos.md)), so a global queue is not pretending to supply
|
|
54
|
+
something it does not.
|
|
55
|
+
|
|
56
|
+
## Admission before auth
|
|
57
|
+
|
|
58
|
+
`/mcp` acquires its permit *before* running the auth gate. This looks backwards
|
|
59
|
+
until you price it: authenticating first means an unauthenticated flood buys a
|
|
60
|
+
Clerk network lookup per request, so the cheapest possible attack becomes the
|
|
61
|
+
most expensive request the server can serve. Admitting first means it buys a
|
|
62
|
+
queue slot and a 503.
|
|
63
|
+
|
|
64
|
+
The permit is released with the response *body*, not when the handler returns.
|
|
65
|
+
A slow client draining a large result still counts as active work, because its
|
|
66
|
+
bytes and its socket still exist. `releaseAdmissionWithResponse` re-wraps the
|
|
67
|
+
response stream to do this, absorbs a rejecting `cancel()` rather than leaking
|
|
68
|
+
an unhandled rejection, and releases exactly once — release is idempotent, and
|
|
69
|
+
`test/request-admission.test.ts` pins both the stream-cancel path and the
|
|
70
|
+
double-release case.
|
|
71
|
+
|
|
72
|
+
Every other route bypasses the pool entirely. `/health` and the operator
|
|
73
|
+
surface stay responsive while MCP is saturated, which is the whole point: an
|
|
74
|
+
operator diagnosing an overload must not have to queue behind it. `/health`
|
|
75
|
+
names the exempt routes in `admission.reservedRoutes` so the claim is checkable
|
|
76
|
+
from outside.
|
|
77
|
+
|
|
78
|
+
## Overload, cancellation, shutdown
|
|
79
|
+
|
|
80
|
+
A full queue or an expired queue deadline answers HTTP 503 with `Retry-After`,
|
|
81
|
+
CORS headers, and a stable JSON-RPC error:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"jsonrpc": "2.0",
|
|
86
|
+
"id": null,
|
|
87
|
+
"error": {
|
|
88
|
+
"code": -32001,
|
|
89
|
+
"message": "Server capacity is exhausted. Retry later.",
|
|
90
|
+
"data": { "code": "server_overloaded", "retryable": true, "retryAfterMs": 1000 }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`Retry-After` is that hint rounded up to at least one whole second. It is
|
|
96
|
+
advice, not a reservation. Shutdown uses `-32002` / `server_shutting_down` and
|
|
97
|
+
is not retryable. Code-pool overload never reaches this layer: it surfaces as
|
|
98
|
+
an ordinary MCP tool error with `executor_overloaded`, `retryable: true`, and
|
|
99
|
+
the executor's own `retryAfterMs`.
|
|
100
|
+
|
|
101
|
+
A cancelled queued request is removed immediately and never receives a later
|
|
102
|
+
permit — cancelling and then being admitted would hold capacity for a caller
|
|
103
|
+
that is gone. An admitted request keeps its permit until its body completes,
|
|
104
|
+
errors, or is cancelled.
|
|
105
|
+
|
|
106
|
+
`connecta.close()` closes both queues before releasing executor resources:
|
|
107
|
+
queued and future MCP work is rejected with `server_shutting_down` while
|
|
108
|
+
admitted work drains. Node's `listen()` calls it on SIGTERM or SIGINT, stops
|
|
109
|
+
accepting connections, drains, and enforces `shutdownTimeoutMs` (10 s default)
|
|
110
|
+
— SIGTERM arrives on every `docker compose up` recreate, and Node's default
|
|
111
|
+
response to it is to die mid-request.
|
|
112
|
+
|
|
113
|
+
## What admission is not
|
|
114
|
+
|
|
115
|
+
The Node adapter's `maxBodyBytes` (10 MiB default) is a separate ingress guard.
|
|
116
|
+
It caps the HTTP body while constructing the Web `Request`, which happens
|
|
117
|
+
*before* the portable `/mcp` boundary can run. Admission bounds MCP, auth,
|
|
118
|
+
catalog, and response work; it is not a byte budget for many simultaneous slow
|
|
119
|
+
or near-limit uploads. Hostile public traffic wants an ingress proxy with a
|
|
120
|
+
body-rate limit in front, and `maxBodyBytes` set to the smallest value the
|
|
121
|
+
deployment actually needs.
|
|
122
|
+
|
|
123
|
+
The rejection warning is rate-limited to one per second, and each line reports
|
|
124
|
+
how many were suppressed since the last one. The `/health` totals count every
|
|
125
|
+
rejection, so the log is a sample and the counters are the record. Queue waits
|
|
126
|
+
log at debug level. Nothing on this path records or exposes request bodies,
|
|
127
|
+
tool arguments, identities, or results.
|
|
128
|
+
|
|
129
|
+
## Observations
|
|
130
|
+
|
|
131
|
+
`/health` exposes payload-free snapshots under `admission.requests`,
|
|
132
|
+
`admission.code`, and `admission.downstreamCalls`. The first two carry
|
|
133
|
+
configured bounds, current active and queued counts, cumulative
|
|
134
|
+
admitted/queued/rejected/cancelled/closed totals, and queue-wait count, total,
|
|
135
|
+
and maximum. The request policy is labelled `global-fifo`; downstream policy is
|
|
136
|
+
labelled `connector-partitioned-per-runtime`. An executor that owns its own
|
|
137
|
+
pool and exposes no snapshot reports `{ managedByExecutor: true }`.
|
|
138
|
+
|
|
139
|
+
With `execute_code({ diagnostics: true })`, a caller sees the same split from
|
|
140
|
+
the inside: `admissionMs` is time spent waiting for a permit and `connectorMs`
|
|
141
|
+
is the admitted attempt.
|
|
142
|
+
|
|
143
|
+
## Measuring capacity
|
|
144
|
+
|
|
145
|
+
`npm run load:admission` builds the package, then starts server and generator
|
|
146
|
+
in separate processes over real loopback TCP, warms a 10,000-tool catalog,
|
|
147
|
+
verifies every returned value, and records throughput, p50/p95/p99, server-only
|
|
148
|
+
peak RSS, RSS after forced GC, and live heap after GC. Each matrix cell gets a
|
|
149
|
+
fresh server so an earlier allocator high-water mark cannot contaminate the
|
|
150
|
+
next baseline; the three-round soak deliberately reuses one, because allocator
|
|
151
|
+
high-water retention and a live-object climb look identical in a single run and
|
|
152
|
+
different across three.
|
|
153
|
+
|
|
154
|
+
`CONNECTA_LOAD_CATALOG_SIZE`, `CONNECTA_LOAD_CONCURRENCY`, and
|
|
155
|
+
`CONNECTA_LOAD_MAX_QUEUE_SIZE` change catalog size, server concurrency, and
|
|
156
|
+
queue depth. The script prints its own numbers; no baseline is checked in,
|
|
157
|
+
deliberately. A laptop matrix is an example capacity profile, not a portable
|
|
158
|
+
SLO, and downstream payload size moves it more than any setting here does — pin
|
|
159
|
+
a runner before enforcing a regression ratio, and measure the connector mix the
|
|
160
|
+
deployment actually runs.
|
|
161
|
+
|
|
162
|
+
## Tests that enforce this
|
|
163
|
+
|
|
164
|
+
| Invariant | Suite |
|
|
165
|
+
| --- | --- |
|
|
166
|
+
| FIFO bounds, active and queue ceilings, stable retryable overload, queue timeout, cancellation removal, idempotent release, shutdown | `test/executor-admission.test.ts` (Node + Workers) |
|
|
167
|
+
| `/mcp` bounded before auth, stable 503 and `Retry-After`, health and operator responsiveness under saturation, payload-free counters, queued cancellation, shutdown rejection while active work drains, the separate fallback code pool | `test/request-admission.test.ts` |
|
|
168
|
+
| A client disconnect propagating through the Web `Request` into a program's connector call, releasing both permits | `test/node.test.ts` |
|
|
169
|
+
| The `/health` admission payload alongside the drift counts | `test/catalog-drift.test.ts`, `test/server.test.ts` |
|
|
@@ -208,5 +208,4 @@ count of what was discarded (`M4`).
|
|
|
208
208
|
|
|
209
209
|
The implementation issue carries these as acceptance criteria; the clauses
|
|
210
210
|
fold into `code-mode.md` under a new "Emitted output" section when it closes,
|
|
211
|
-
and the
|
|
212
|
-
suite.
|
|
211
|
+
and the suite takes its row in the [test map](./operations.md#the-test-map).
|
package/documentation/stripe.md
CHANGED
|
@@ -158,6 +158,14 @@ serves, an unclassified and unannotated `create_customer` lands on the approval
|
|
|
158
158
|
path. Expect the undocumented Treasury tools Stripe alludes to to arrive
|
|
159
159
|
unclassified as well — annotated ones will be taken at their word.
|
|
160
160
|
|
|
161
|
+
The upshot is that this account's tool list is not a fixed set, and the usage
|
|
162
|
+
guide tells the agent so: search this connector for what it actually exposes
|
|
163
|
+
rather than assuming a documented tool is present. The guide also names the id
|
|
164
|
+
discipline the downstream schemas cannot enforce — Stripe ids are typed
|
|
165
|
+
prefixes (`cus_`, `sub_`, `ch_`, `pi_`, `in_`, `acct_`), a plausible-looking one
|
|
166
|
+
belongs to a different object or to nobody, and the id a write takes comes from
|
|
167
|
+
`stripe_api_search` or a list read rather than from a guess.
|
|
168
|
+
|
|
161
169
|
Stripe publishes no stability or deprecation policy for this tool set and
|
|
162
170
|
invites tool requests by email, so treat the list as unversioned. `get_balance_summary`
|
|
163
171
|
is Treasury, which Stripe labels public preview and gates behind an access
|
|
@@ -200,3 +208,10 @@ about rather than read:
|
|
|
200
208
|
are Stripe's documented API conventions; how they thread through the generic
|
|
201
209
|
tools' arguments is not documented. The guide states them because an agent
|
|
202
210
|
that ignores them is wrong either way.
|
|
211
|
+
|
|
212
|
+
## Conventions
|
|
213
|
+
|
|
214
|
+
This connection is audited against
|
|
215
|
+
[the provider conventions](./provider-conventions.md). Its verdict per
|
|
216
|
+
convention, including every recorded exception, is the Stripe section of
|
|
217
|
+
[the provider audit](./provider-audit.md).
|
package/ethos.md
CHANGED
|
@@ -38,22 +38,22 @@ order, and amending it is a design decision, not a drive-by edit.
|
|
|
38
38
|
- **One fetch-native core, two runtimes.** The same code runs unchanged on
|
|
39
39
|
Cloudflare Workers and in Node — a Worker or a Docker stack, your pick. Web
|
|
40
40
|
APIs only in the core; Node touches live behind explicit subpaths.
|
|
41
|
-
- **Observable,
|
|
42
|
-
masked credentials, and payload-free activity
|
|
43
|
-
they
|
|
41
|
+
- **Observable, actionable only over authentication material.** Operator pages
|
|
42
|
+
show connector status, masked credentials, and payload-free activity — and
|
|
43
|
+
they act: rotate a credential, issue or revoke an access token, run a
|
|
44
|
+
downstream OAuth flow. Declared capability is what they cannot touch.
|
|
44
45
|
|
|
45
46
|
## What this isn't
|
|
46
47
|
|
|
47
48
|
- **Not a platform.** No runtime connector registration, no admin UI that
|
|
48
|
-
changes
|
|
49
|
+
changes declared capability, no policy engine, no approvals, no pauses.
|
|
49
50
|
- **Not a schema ingester.** No OpenAPI or GraphQL → tools. Generated tool
|
|
50
51
|
sprawl is the disease the meta-tools treat, not a feature to add.
|
|
51
52
|
- **Not multi-tenant.** No accounts dimension, no per-user credential store,
|
|
52
53
|
no org hierarchy. Two accounts on one service are two connector instances.
|
|
53
54
|
- **Not stateful.** No protocol sessions, no server push. Scope resolves per
|
|
54
55
|
request — which is also where the MCP spec itself has now arrived.
|
|
55
|
-
- **Not a nanny.** Credentials
|
|
56
|
-
connecta doesn't probe them behind your back.
|
|
56
|
+
- **Not a nanny.** Credentials fail loudly at use; connecta never probes one.
|
|
57
57
|
- **Not a promise to strangers — yet.** Built for its author's deployments
|
|
58
58
|
first, published openly. Breaking changes are cheap and the version number
|
|
59
59
|
signals change, not stability.
|
|
@@ -71,6 +71,7 @@ proposing one without a new argument is not.
|
|
|
71
71
|
| Runtime connector registration | refused | config-as-code is the security model |
|
|
72
72
|
| Prebuilt connections as the preferred authoring path | accepted | an a-la-carte provider constructor, imported and constructed in the deployment file, encodes maintained defaults for providers connecta actually uses — preferred *when maintained*, with no promise of one per provider; it returns exactly one ordinary `Connector` with no extra privileges — never a bundle, a group, a preset, or a registry — its tools are hand-written or proxied from a downstream MCP catalog, never generated from a schema document; its vetted annotations classify what the downstream leaves unannotated and otherwise preserve explicit annotations, with one fail-closed exception: a release-reviewed destructive classification outranks a contradictory downstream `readOnlyHint: true`, because Connecta has independently established that the tool mutates existing state; `remoteMcp()` and `api()` stay first-class ([#297](https://github.com/zackbart/connecta/issues/297), [#315](https://github.com/zackbart/connecta/issues/315)) |
|
|
73
73
|
| Guarded raw REST escape hatches in a prebuilt connection | accepted | a large, fast-moving provider cannot be honestly represented by a small frozen list: a GET-only tool may expose provider-relative reads, while JSON mutations and explicit-content uploads stay separate and always cross the destructive boundary; the connector owns authentication, rate limits, error mapping, URL confinement, and safe method classification, while the provider token remains the capability boundary — this is not schema ingestion, runtime connector registration, or permission widening |
|
|
74
|
+
| Hosted-provider drift detection during catalog refreshes | accepted | a vetted classification is a claim about somebody else's catalog, and an allowlist nobody can tell is stale is an allowlist that is wrong — so each maintained hosted-MCP proxy ships the tool names, classifications, and (once a release records them) schemas it reviewed, and compares them against the live listing *inside* a catalog refresh the deployment already asked for; the boundary is the piggyback itself, which is what keeps this from being proactive credential liveness wearing a new hat ([#179](https://github.com/zackbart/connecta/issues/179)): no scheduled job, no background request, no credential probe, and not one byte of network traffic that would not have happened anyway; what it produces is four counts — unclassified additions, names no longer served, explicit annotation conflicts, schema changes — on connector status, `connecta doctor`, and one payload-free activity event with nowhere to put a tool name or a schema; the two read surfaces answer for the runtime that served the refresh, because the observation is isolate-local and only the activity event is durable, and unknown tools already fail closed, so drift costs approval round trips and stale guidance rather than capability ([#343](https://github.com/zackbart/connecta/issues/343)) |
|
|
74
75
|
| Provider registry / integration marketplace | refused | prebuilt connections are imports, not listings; discovery happens in documentation, never at runtime ([#297](https://github.com/zackbart/connecta/issues/297)) |
|
|
75
76
|
| Protocol sessions & server push | refused | stateless per request |
|
|
76
77
|
| Resources & prompts aggregation | refused | tools only; connecta's own Apps shell is the one `resources/read` carve-out ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
@@ -80,6 +81,7 @@ proposing one without a new argument is not.
|
|
|
80
81
|
| Proactive credential liveness | removed | fail-at-use is enough ([#179](https://github.com/zackbart/connecta/issues/179)) |
|
|
81
82
|
| Agent credential recovery | accepted | one `auth_required` route through `authorize_connector`; only an operator handles secrets ([#192](https://github.com/zackbart/connecta/issues/192)) |
|
|
82
83
|
| Operator-issued MCP access tokens | accepted | named, revocable authentication gives header-capable clients a small alternative to OAuth; tokens identify callers but never scope tools or become operator credentials |
|
|
84
|
+
| Operator boundary reworded: authentication material, never declared capability | accepted | supersedes "observable, never administrable", which had stopped describing the surface: operator routes already rotate credentials, issue and revoke access tokens, and drive downstream OAuth, each under its own accepted row, and the owner has decided the surface stays actionable — so the boundary now says what is actually true, that operator routes may manage authentication material for capabilities declared in deployment configuration and may not change the connector set, the declared tool catalog or annotations, requested OAuth scopes, admission policy, authorization rules, or caller tool scope; the claim is deliberately about *declared* capability, and twice so, because replacing an API token with a broader-scoped one widens downstream reach and no browser page can honestly promise otherwise, and because a remote MCP server's catalog is discovered rather than declared — connecta declares the connector, its credential slot, and its admission policy, while the tools that server serves are its own answer, so storing a credential or finishing an OAuth flow can legitimately take an `mcp()` connector from no tools to N, which is discovery arriving, not an operator editing the deployment ([#338](https://github.com/zackbart/connecta/issues/338)) |
|
|
83
85
|
| Structured result surface | accepted | canonical `structuredContent` plus complete compact `content`; summary-only text is gated on host-forwarding evidence ([#191](https://github.com/zackbart/connecta/issues/191)) |
|
|
84
86
|
| Code mode (`execute_code`) | accepted | the primary read, discovery, and composition surface: smaller serialized definitions, far smaller results once composition and projection happen before the model sees them, and a cold-start model that read the interface without help ([exploration](./documentation/code-first-exploration.md), [#224](https://github.com/zackbart/connecta/issues/224)) |
|
|
85
87
|
| Code-first as the default; the eval gate retired | accepted | owner decision, 2026-07-30: one operator, no deploy-time flip; [`eval/code-first-gate`](./eval/code-first-gate/README.md) survives as measurement, but nothing waits on its verdict ([#222](https://github.com/zackbart/connecta/issues/222), [#224](https://github.com/zackbart/connecta/issues/224)) |
|
|
@@ -132,8 +134,11 @@ Breaking one is not a bug fix — it is a design change wearing a disguise.
|
|
|
132
134
|
`node:` builtin.
|
|
133
135
|
- **The published surface is a boundary.** Heavyweight or platform-bound code
|
|
134
136
|
goes behind an optional-peer subpath, never into core.
|
|
135
|
-
- **
|
|
136
|
-
|
|
137
|
+
- **Operator routes manage authentication material, never declared
|
|
138
|
+
capability.** Authenticating a declared capability is allowed; the connector
|
|
139
|
+
set, declared catalog and annotations, OAuth scopes, admission, authorization
|
|
140
|
+
rules, and caller tool scope take a config edit. A downstream catalog is
|
|
141
|
+
discovered, not declared — remote MCP tools appear when its credential does.
|
|
137
142
|
- **Structural mistakes throw at construction.** A deployment that boots into
|
|
138
143
|
the wrong shape is worse than one that refuses to boot.
|
|
139
144
|
|
|
@@ -8,7 +8,7 @@ and requires the Workers Paid plan.
|
|
|
8
8
|
This is also the **starting template for a deployment**: a real deployment
|
|
9
9
|
should be its own repository that pins an exact `@zackbart/connecta` version and
|
|
10
10
|
owns only its connector configuration, auth policy, domain, bindings,
|
|
11
|
-
migrations, and secrets. See [
|
|
11
|
+
migrations, and secrets. See [the Cloudflare guide](../../documentation/cloudflare.md).
|
|
12
12
|
|
|
13
13
|
## Files
|
|
14
14
|
|
|
@@ -41,6 +41,7 @@ wrangler secret put SUPPORT_TOKEN # one headless client
|
|
|
41
41
|
wrangler secret put EXEC_TOKEN # another headless client
|
|
42
42
|
wrangler secret put CLERK_SECRET_KEY
|
|
43
43
|
wrangler secret put DOWNSTREAM_TOKEN
|
|
44
|
+
wrangler secret put CREDENTIAL_ENCRYPTION_KEY # base64 32-byte AES key
|
|
44
45
|
wrangler deploy
|
|
45
46
|
```
|
|
46
47
|
|
|
@@ -49,9 +50,91 @@ Enable Dynamic Client Registration on the Clerk instance (OAuth Applications →
|
|
|
49
50
|
DCR) so Claude/Cursor can self-register — full walkthrough in
|
|
50
51
|
[setting up Clerk](../../documentation/auth.md).
|
|
51
52
|
|
|
53
|
+
### Copied into its own repository
|
|
54
|
+
|
|
55
|
+
The `npm install` above is the connecta repository's, which already has every
|
|
56
|
+
dependency this file imports. A copy with its own `package.json` installs three
|
|
57
|
+
things, because two of them are not part of connecta and never install with it:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
npm install @zackbart/connecta @cloudflare/codemode @clerk/backend
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`@cloudflare/codemode` is the executor behind `execute_code`, and
|
|
64
|
+
`@clerk/backend` is the optional peer behind `@zackbart/connecta/auth/clerk` —
|
|
65
|
+
which `src/index.ts` imports at the top level, so wrangler must resolve it at
|
|
66
|
+
build time. Miss it and the build stops at
|
|
67
|
+
`Could not resolve "@clerk/backend"`, which is a missing peer rather than a
|
|
68
|
+
broken example. Drop `clerkAuth` from `auth` if this deployment has no operator
|
|
69
|
+
sign-in, and the peer goes with it — but read
|
|
70
|
+
[the operator surface](#the-operator-surface) first, because a deployment
|
|
71
|
+
without it can never write a credential or issue an access token.
|
|
72
|
+
|
|
52
73
|
Then point an MCP client at `<PUBLIC_URL>/mcp`, and open `<PUBLIC_URL>/` for
|
|
53
74
|
Connections. Credentials is at `/credentials`, named MCP access tokens are at
|
|
54
|
-
`/tokens`, Activity is at `/activity`, and legacy `/ui` redirects to `/`.
|
|
75
|
+
`/tokens`, Activity is at `/activity`, and legacy `/ui` redirects to `/`. Each
|
|
76
|
+
of those three appears in the nav only when this deployment can serve it — see
|
|
77
|
+
the next section for what turns each one on.
|
|
78
|
+
|
|
79
|
+
## The operator surface
|
|
80
|
+
|
|
81
|
+
This example ships the whole operator feature set. Three quarters of it is on
|
|
82
|
+
as deployed; the fourth needs a database, so it is commented in place.
|
|
83
|
+
|
|
84
|
+
**Operator sign-in** is the `clerkAuth` entry in `src/index.ts`, alongside two
|
|
85
|
+
static bearers. The split is deliberate: a bearer is a client key that may call
|
|
86
|
+
tools and read connector status, while writing a credential or issuing an
|
|
87
|
+
access token requires an interactive Clerk identity. Narrow who that can be
|
|
88
|
+
with `allowedDomains`, or with a `gate` for anything a domain cannot express.
|
|
89
|
+
|
|
90
|
+
**The credential vault** is `credentials: { encryptionKey: … }`, backed by the
|
|
91
|
+
same KV namespace as everything else and encrypted with the
|
|
92
|
+
`CREDENTIAL_ENCRYPTION_KEY` secret before a value reaches it. Generate one with:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
node -e "console.log(crypto.randomBytes(32).toString('base64'))"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Leave the secret unset and the deployment still runs — `/credentials` stays
|
|
99
|
+
read-only and connecta says so at startup. Keep the key in Worker secrets and
|
|
100
|
+
nowhere near KV: it is the only thing that makes a copied namespace useless.
|
|
101
|
+
Rotation takes effect on the next call, with no redeploy and no liveness probe,
|
|
102
|
+
because credentials fail at use.
|
|
103
|
+
|
|
104
|
+
The vault is ready here, and the Credentials page is still hidden, because that
|
|
105
|
+
page lists connector credential slots rather than deployments. Neither
|
|
106
|
+
connector in `src/index.ts` declares one — Notion carries a deployment-owned
|
|
107
|
+
static header and echo has no secret — so nothing would be on the page. Add
|
|
108
|
+
`credential: { label: "API token" }` to an `api()` connector (the commented
|
|
109
|
+
shape on `echo` is exactly it) or use a provider connector such as `notion()`,
|
|
110
|
+
which declares its own, and Credentials appears for a signed-in operator on the
|
|
111
|
+
next load.
|
|
112
|
+
|
|
113
|
+
**Access tokens** are `accessTokens: {}`. A signed-in operator mints named,
|
|
114
|
+
revocable Bearer tokens at `/tokens` for header-capable clients that will not do
|
|
115
|
+
OAuth. Secrets are shown once and only their hashes enter KV; a lost token is
|
|
116
|
+
reissued, never recovered. Note the KV caveat above — revocation is visible
|
|
117
|
+
everywhere only as fast as the namespace converges.
|
|
118
|
+
|
|
119
|
+
**Activity** is the commented block in `src/index.ts` and the commented
|
|
120
|
+
`d1_databases` binding in `wrangler.jsonc`; the section below creates the
|
|
121
|
+
database and applies the schema.
|
|
122
|
+
|
|
123
|
+
None of these change what agents can reach. Operator routes manage the
|
|
124
|
+
authentication material behind capabilities `src/index.ts` already declares —
|
|
125
|
+
never the connector set, the tool catalog, or its annotations.
|
|
126
|
+
|
|
127
|
+
`connecta doctor` reports the same line here as for a deployment with none of
|
|
128
|
+
this on: connector count, executor, seven tools. The executor it names is this
|
|
129
|
+
one — `DynamicWorkerExecutor executed`, not the Node template's QuickJS, which
|
|
130
|
+
is what doctor used to claim everywhere
|
|
131
|
+
([#368](https://github.com/zackbart/connecta/issues/368)). It carries a bearer, and a
|
|
132
|
+
bearer learns the model-facing surface rather than the deployment's
|
|
133
|
+
configuration topology. Confirm the operator surface the way an operator will:
|
|
134
|
+
sign in at `<PUBLIC_URL>/` and check that Tokens is live. Credentials joins it
|
|
135
|
+
once a connector declares a `credential` slot, and Activity once the D1 wiring
|
|
136
|
+
below is on — the nav shows a page when the deployment can actually serve it,
|
|
137
|
+
so a missing page is the honest report that its half is still off.
|
|
55
138
|
|
|
56
139
|
## Code mode
|
|
57
140
|
|
|
@@ -64,20 +147,19 @@ The required Worker Loader binding is checked into `wrangler.jsonc`:
|
|
|
64
147
|
```
|
|
65
148
|
|
|
66
149
|
`src/index.ts` constructs `DynamicWorkerExecutor` from `env.LOADER` and serves
|
|
67
|
-
the seven-tool surface.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```sh
|
|
71
|
-
npm install @cloudflare/codemode
|
|
72
|
-
```
|
|
150
|
+
the seven-tool surface. The executor package is one of the installs a copied
|
|
151
|
+
deployment owns — see
|
|
152
|
+
[copied into its own repository](#copied-into-its-own-repository).
|
|
73
153
|
|
|
74
154
|
## Activity history (optional)
|
|
75
155
|
|
|
76
156
|
`src/d1-activity.ts` is a complete `ActivityStore` over D1 — keyset paging on
|
|
77
|
-
`(occurred_at_ms, id)` plus a batched retention pass — but
|
|
78
|
-
|
|
157
|
+
`(occurred_at_ms, id)` plus a batched retention pass — but the wiring in
|
|
158
|
+
`src/index.ts` is **commented out**, so the example deploys without a database.
|
|
159
|
+
To enable it:
|
|
79
160
|
|
|
80
|
-
1. Create the database and
|
|
161
|
+
1. Create the database and uncomment the `d1_databases` binding in
|
|
162
|
+
`wrangler.jsonc`, pasting in the id it prints:
|
|
81
163
|
|
|
82
164
|
```sh
|
|
83
165
|
wrangler d1 create connecta-activity
|
|
@@ -137,7 +219,9 @@ into `src/index.ts`**, so the example deploys without a database. To enable it:
|
|
|
137
219
|
the failure and returns the tool result unharmed — so the symptom is not an
|
|
138
220
|
error your agent sees, it is an activity log that quietly stops recording.
|
|
139
221
|
|
|
140
|
-
3.
|
|
222
|
+
3. In `src/index.ts`, uncomment the `d1ActivityStore` import, the `ACTIVITY_DB`
|
|
223
|
+
field on `Env`, and the `activity` block — the three commented fragments
|
|
224
|
+
that together read:
|
|
141
225
|
|
|
142
226
|
```ts
|
|
143
227
|
import { d1ActivityStore } from "./d1-activity.js";
|
|
@@ -6,16 +6,25 @@
|
|
|
6
6
|
* namespace. The required Worker Loader binding in wrangler.jsonc backs the
|
|
7
7
|
* seven-tool surface.
|
|
8
8
|
*
|
|
9
|
+
* The operator surface is wired here except for activity history, which needs
|
|
10
|
+
* a database this example does not create for you: sign-in, the credential
|
|
11
|
+
* vault, and access-token issuance are on, and activity is three commented
|
|
12
|
+
* lines below. README.md § "The operator surface" walks through all four.
|
|
13
|
+
*
|
|
9
14
|
* Setup (this example has no package.json of its own — it self-references the
|
|
10
15
|
* installed `@zackbart/connecta` package):
|
|
11
16
|
* 1. `npm install` in the connecta package root (../../ from here) so the
|
|
12
|
-
* package import and wrangler resolve.
|
|
17
|
+
* package import and wrangler resolve. A copy in its own repository
|
|
18
|
+
* installs `@zackbart/connecta @cloudflare/codemode @clerk/backend`
|
|
19
|
+
* instead — the last two are not part of connecta, and the Clerk import
|
|
20
|
+
* below is an optional peer wrangler resolves at build time.
|
|
13
21
|
* 2. Create a KV namespace and put its id in wrangler.jsonc under `kv_namespaces`.
|
|
14
22
|
* 3. Set secrets:
|
|
15
23
|
* wrangler secret put SUPPORT_TOKEN
|
|
16
24
|
* wrangler secret put EXEC_TOKEN
|
|
17
25
|
* wrangler secret put CLERK_SECRET_KEY
|
|
18
26
|
* wrangler secret put DOWNSTREAM_TOKEN
|
|
27
|
+
* wrangler secret put CREDENTIAL_ENCRYPTION_KEY
|
|
19
28
|
* and CLERK_PUBLISHABLE_KEY + PUBLIC_URL as plain vars in wrangler.jsonc.
|
|
20
29
|
* 4. Enable Dynamic Client Registration in the Clerk dashboard
|
|
21
30
|
* (OAuth Applications -> DCR toggle) so Claude/Cursor can self-register.
|
|
@@ -32,6 +41,8 @@ import {
|
|
|
32
41
|
} from "@zackbart/connecta";
|
|
33
42
|
import { clerkAuth } from "@zackbart/connecta/auth/clerk";
|
|
34
43
|
import { cloudflareKvStorage } from "./cloudflare-kv.js";
|
|
44
|
+
// Activity history, off by default because it needs a D1 database.
|
|
45
|
+
// import { d1ActivityStore } from "./d1-activity.js";
|
|
35
46
|
|
|
36
47
|
interface Env {
|
|
37
48
|
CONNECTA_KV: KVNamespace;
|
|
@@ -41,8 +52,16 @@ interface Env {
|
|
|
41
52
|
EXEC_TOKEN: string;
|
|
42
53
|
CLERK_PUBLISHABLE_KEY: string;
|
|
43
54
|
CLERK_SECRET_KEY: string;
|
|
55
|
+
/**
|
|
56
|
+
* Base64 32-byte AES key encrypting operator-managed credentials in KV.
|
|
57
|
+
* Unset means no vault: /credentials stays read-only and connecta says so at
|
|
58
|
+
* startup. Never put it in KV — it is what protects KV.
|
|
59
|
+
*/
|
|
60
|
+
CREDENTIAL_ENCRYPTION_KEY: string;
|
|
44
61
|
DOWNSTREAM_TOKEN: string;
|
|
45
62
|
PUBLIC_URL: string;
|
|
63
|
+
/** Uncomment with the `d1_databases` binding to enable activity history. */
|
|
64
|
+
// ACTIVITY_DB: D1Database;
|
|
46
65
|
/**
|
|
47
66
|
* Worker Loader binding (wrangler.jsonc `worker_loaders`) powering
|
|
48
67
|
* execute_code. Dynamic Workers require the Workers Paid plan.
|
|
@@ -73,9 +92,30 @@ function build(env: Env) {
|
|
|
73
92
|
// allowedDomains: ["acme.com"],
|
|
74
93
|
}),
|
|
75
94
|
],
|
|
95
|
+
// Connectors that declare a `credential` slot become editable at
|
|
96
|
+
// /credentials, encrypted with this key before anything reaches KV. A
|
|
97
|
+
// saved replacement takes effect on the next call — no redeploy, and no
|
|
98
|
+
// liveness probe: credentials fail at use.
|
|
99
|
+
//
|
|
100
|
+
// The key is the vault, not the page: /credentials is a list of connector
|
|
101
|
+
// slots, so it stays hidden until a connector declares one. Neither
|
|
102
|
+
// connector below does — Notion here carries a deployment-owned static
|
|
103
|
+
// header and echo has no secret at all — so this example ships the vault
|
|
104
|
+
// ready and the page empty. Declare a slot (see the commented shape on
|
|
105
|
+
// `echo`, or use a provider connector like `notion()`, which declares its
|
|
106
|
+
// own) and the page appears on the next load.
|
|
107
|
+
credentials: { encryptionKey: env.CREDENTIAL_ENCRYPTION_KEY },
|
|
76
108
|
// Eligible Clerk operators can create named, revocable MCP Bearer tokens
|
|
77
109
|
// at /tokens. Secrets are shown once; only their hashes enter KV.
|
|
78
110
|
accessTokens: {},
|
|
111
|
+
// Payload-free activity at /activity, off until a database exists to hold
|
|
112
|
+
// it. Uncomment the `d1_databases` binding in wrangler.jsonc, apply the
|
|
113
|
+
// schema in README.md § "Activity history", then these three lines and the
|
|
114
|
+
// import above.
|
|
115
|
+
// activity: {
|
|
116
|
+
// store: d1ActivityStore(env.ACTIVITY_DB),
|
|
117
|
+
// deploymentId: "production",
|
|
118
|
+
// },
|
|
79
119
|
connectors: [
|
|
80
120
|
remoteMcp("notion", {
|
|
81
121
|
url: "https://mcp.notion.com/mcp",
|
|
@@ -87,6 +127,11 @@ function build(env: Env) {
|
|
|
87
127
|
}),
|
|
88
128
|
api("echo", {
|
|
89
129
|
description: "Echo — text transforms",
|
|
130
|
+
// What a vault-backed connector adds — an operator edits this slot at
|
|
131
|
+
// /credentials and the handler reads it with
|
|
132
|
+
// `await ctx.credential?.get()`, so the secret never lives in source
|
|
133
|
+
// or in a Worker variable:
|
|
134
|
+
// credential: { label: "API token" },
|
|
90
135
|
tools: [
|
|
91
136
|
{
|
|
92
137
|
name: "shout",
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
"observability": { "enabled": true },
|
|
8
8
|
|
|
9
9
|
// Plain vars. Secrets (SUPPORT_TOKEN, EXEC_TOKEN, CLERK_SECRET_KEY,
|
|
10
|
-
// DOWNSTREAM_TOKEN) are set with
|
|
10
|
+
// CREDENTIAL_ENCRYPTION_KEY, DOWNSTREAM_TOKEN) are set with
|
|
11
|
+
// `wrangler secret put <NAME>`, not here.
|
|
11
12
|
"vars": {
|
|
12
13
|
"PUBLIC_URL": "https://connecta.example.workers.dev",
|
|
13
14
|
"CLERK_PUBLISHABLE_KEY": "pk_test_replace-me"
|
|
@@ -18,6 +19,18 @@
|
|
|
18
19
|
{ "binding": "CONNECTA_KV", "id": "replace-with-kv-namespace-id" }
|
|
19
20
|
],
|
|
20
21
|
|
|
22
|
+
// Activity history. Create with `wrangler d1 create connecta-activity`,
|
|
23
|
+
// paste the id, apply the schema in README.md, then uncomment the matching
|
|
24
|
+
// lines in src/index.ts. Off by default so this example deploys without a
|
|
25
|
+
// database.
|
|
26
|
+
// "d1_databases": [
|
|
27
|
+
// {
|
|
28
|
+
// "binding": "ACTIVITY_DB",
|
|
29
|
+
// "database_name": "connecta-activity",
|
|
30
|
+
// "database_id": "replace-with-d1-database-id"
|
|
31
|
+
// }
|
|
32
|
+
// ],
|
|
33
|
+
|
|
21
34
|
// Required by execute_code; Dynamic Workers use the Workers Paid plan.
|
|
22
35
|
"worker_loaders": [{ "binding": "LOADER" }]
|
|
23
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zackbart/connecta",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "One MCP to rule them all — a single MCP endpoint aggregating many downstream connectors behind a code-first surface of seven meta-tools.",
|
|
@@ -31,10 +31,7 @@
|
|
|
31
31
|
"files": [
|
|
32
32
|
"bin",
|
|
33
33
|
"dist",
|
|
34
|
-
"src",
|
|
35
|
-
"assets",
|
|
36
34
|
"documentation",
|
|
37
|
-
"examples/node",
|
|
38
35
|
"examples/worker",
|
|
39
36
|
"templates",
|
|
40
37
|
"AGENTS.md",
|
|
@@ -100,10 +97,11 @@
|
|
|
100
97
|
"check:docs": "node scripts/check-doc-links.mjs",
|
|
101
98
|
"check:lint": "oxlint -D correctness --report-unused-disable-directives .",
|
|
102
99
|
"check:unused": "knip --include dependencies,unlisted,exports,nsExports,types,nsTypes",
|
|
103
|
-
"check:examples": "tsc -p tsconfig.
|
|
100
|
+
"check:examples": "tsc -p tsconfig.template.node.json && tsc -p tsconfig.examples.worker.json",
|
|
104
101
|
"check:security": "npm audit --omit=dev --audit-level=moderate",
|
|
105
102
|
"check:package": "node scripts/check-package.mjs",
|
|
106
103
|
"load:admission": "npm run build && node scripts/admission-load.mjs",
|
|
104
|
+
"drift:check": "tsx scripts/drift-check.mjs",
|
|
107
105
|
"check": "npm run check:docs && npm run check:operator-ui && npm run check:lint && npm run check:unused && npm run typecheck && npm run test && npm run build && npm run check:examples",
|
|
108
106
|
"release:check": "npm run check && npm run check:security && npm run check:package",
|
|
109
107
|
"prepack": "npm run check",
|
|
@@ -137,6 +135,7 @@
|
|
|
137
135
|
"esbuild": "^0.28.0",
|
|
138
136
|
"knip": "^6.29.0",
|
|
139
137
|
"oxlint": "^1.76.0",
|
|
138
|
+
"preact": "^10.29.8",
|
|
140
139
|
"quickjs-emscripten": "^0.32.0",
|
|
141
140
|
"tsx": "^4.23.1",
|
|
142
141
|
"typescript": "^5.6.0",
|
|
@@ -1,5 +1,42 @@
|
|
|
1
|
-
# Required
|
|
2
|
-
|
|
1
|
+
# Required, and deliberately empty. Both Compose and src/index.ts refuse to
|
|
2
|
+
# start while it is, so a copied .env cannot become a running deployment on a
|
|
3
|
+
# placeholder printed in this template. Use a long random value and keep it out
|
|
4
|
+
# of source control:
|
|
5
|
+
# node -e "console.log(crypto.randomBytes(32).toString('base64url'))"
|
|
6
|
+
CONNECTA_TOKEN=
|
|
3
7
|
|
|
4
|
-
# Optional. Defaults to 8787.
|
|
8
|
+
# Optional. Defaults to 8787. Compose publishes this port on the host too.
|
|
5
9
|
PORT=8787
|
|
10
|
+
|
|
11
|
+
# Optional. The origin this deployment answers on, once it answers on more
|
|
12
|
+
# than this machine: downstream OAuth calls back to
|
|
13
|
+
# <PUBLIC_URL>/oauth/callback/<connectorId>. Defaults to http://localhost:<PORT>.
|
|
14
|
+
PUBLIC_URL=
|
|
15
|
+
|
|
16
|
+
# Optional. Where fileStorage keeps downstream-OAuth tokens and catalogs.
|
|
17
|
+
# Defaults to ./.connecta-state.json locally and /data/connecta-state.json in
|
|
18
|
+
# the container — keep the container path under /data so state lands on the
|
|
19
|
+
# named volume and survives a rebuild.
|
|
20
|
+
CONNECTA_STATE_FILE=
|
|
21
|
+
|
|
22
|
+
# --- Operator surface ---------------------------------------------------
|
|
23
|
+
# Each of these is read only once the matching block in src/index.ts is
|
|
24
|
+
# uncommented. Setting one on its own changes nothing.
|
|
25
|
+
|
|
26
|
+
# Operator sign-in with Clerk. Only a signed-in operator may write a credential
|
|
27
|
+
# or issue an access token; the bearer above is a client key, not an admin key.
|
|
28
|
+
# Also run `npm install @clerk/backend` — it is an optional peer.
|
|
29
|
+
CLERK_PUBLISHABLE_KEY=
|
|
30
|
+
CLERK_SECRET_KEY=
|
|
31
|
+
|
|
32
|
+
# Credential vault. Base64-encoded 32-byte AES key encrypting the values an
|
|
33
|
+
# operator saves at /credentials. Generate one with:
|
|
34
|
+
# node -e "console.log(crypto.randomBytes(32).toString('base64'))"
|
|
35
|
+
# Keep it here or in a secret store — never in the state file it protects, and
|
|
36
|
+
# never in source control. Losing it makes stored credentials unreadable.
|
|
37
|
+
CONNECTA_CREDENTIAL_KEY=
|
|
38
|
+
|
|
39
|
+
# Payload-free activity history. Defaults to ./.connecta-activity.jsonl locally
|
|
40
|
+
# and /data/connecta-activity.jsonl in the container — same reasoning as the
|
|
41
|
+
# state file: keep it under /data so history survives a rebuild.
|
|
42
|
+
CONNECTA_ACTIVITY_FILE=
|