@zackbart/connecta 0.24.2 → 0.24.3

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 (57) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/dist/auth/bearer.js +2 -0
  3. package/dist/auth/downstream-oauth.d.ts +12 -1
  4. package/dist/auth/downstream-oauth.js +147 -35
  5. package/dist/call-admission.d.ts +4 -0
  6. package/dist/call-admission.js +26 -0
  7. package/dist/catalog-drift.js +9 -4
  8. package/dist/catalog-service.d.ts +2 -0
  9. package/dist/catalog-service.js +25 -8
  10. package/dist/catalog.d.ts +2 -0
  11. package/dist/catalog.js +246 -121
  12. package/dist/connectors/api.js +11 -1
  13. package/dist/connectors/guarded-fetch.d.ts +1 -1
  14. package/dist/connectors/guarded-fetch.js +27 -20
  15. package/dist/connectors/remote-mcp.js +84 -53
  16. package/dist/errors.d.ts +17 -0
  17. package/dist/errors.js +58 -0
  18. package/dist/execute.js +85 -23
  19. package/dist/executor-result.js +3 -1
  20. package/dist/executors/quickjs-child.js +5 -1
  21. package/dist/executors/quickjs-protocol.d.ts +4 -0
  22. package/dist/executors/quickjs-runtime.d.ts +1 -1
  23. package/dist/executors/quickjs-runtime.js +38 -21
  24. package/dist/executors/quickjs.js +68 -27
  25. package/dist/index.d.ts +14 -0
  26. package/dist/index.js +24 -3
  27. package/dist/invocation.js +134 -93
  28. package/dist/mcp-result.js +3 -2
  29. package/dist/meta-tools.js +118 -39
  30. package/dist/registry.d.ts +14 -2
  31. package/dist/registry.js +87 -13
  32. package/dist/routes/mcp.d.ts +4 -1
  33. package/dist/routes/mcp.js +84 -13
  34. package/dist/routes/oauth.js +4 -0
  35. package/dist/routes/shared.d.ts +1 -0
  36. package/dist/routes/shared.js +4 -4
  37. package/dist/server.js +15 -3
  38. package/dist/skills.js +6 -5
  39. package/dist/storage/file.d.ts +6 -2
  40. package/dist/storage/file.js +312 -34
  41. package/dist/storage/memory.js +12 -1
  42. package/dist/validate.js +3 -3
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/documentation/architecture.md +22 -6
  46. package/documentation/auth.md +42 -9
  47. package/documentation/call-admission.md +24 -8
  48. package/documentation/code-mode.md +34 -22
  49. package/documentation/connectors.md +47 -5
  50. package/documentation/meta-tools.md +74 -6
  51. package/documentation/operations.md +19 -19
  52. package/documentation/provider-conventions.md +7 -0
  53. package/documentation/request-admission.md +38 -4
  54. package/documentation/storage-and-credentials.md +54 -1
  55. package/documentation/upgrading.md +18 -4
  56. package/package.json +1 -1
  57. package/templates/node/package.json +1 -1
@@ -15,7 +15,7 @@ it.
15
15
 
16
16
  ## The pools
17
17
 
18
- Every non-preflight `/mcp` request takes one permit from a deployment-wide FIFO
18
+ Every non-preflight `/mcp` or `/mcp/<pool>` request with an admitted Origin takes one permit from a deployment-wide FIFO
19
19
  pool. Initialization, discovery, ordinary calls, and `execute_code` all pay it.
20
20
  A program then takes a *second* permit from the deliberately smaller code pool,
21
21
  so one request cannot trade ordinary capacity for an unbounded number of
@@ -54,6 +54,38 @@ identity rules give its principals different connector views
54
54
  ([`ethos.md`](../ethos.md)), so a global queue is not pretending to supply
55
55
  something it does not.
56
56
 
57
+ ## Origin before admission
58
+
59
+ MCP checks `Origin` before redirects, request admission, auth, and preflight.
60
+ A present, disallowed origin gets HTTP 403 with the exact body
61
+ `{"error":"origin not allowed"}` and `Cache-Control: no-store`. This local
62
+ check consumes no permit or auth lookup, even when the queue is full or closed.
63
+ Requests without Origin, including ordinary non-browser MCP clients, pass.
64
+ `/health`, OAuth callbacks, and auth metadata retain their existing behavior.
65
+
66
+ `allowedOrigins` accepts a list of exact HTTP(S) origins or `"*"`. An explicit
67
+ list replaces the defaults, including loopback; an empty list admits only
68
+ originless clients. By default the configured `publicUrl` origin and HTTP(S)
69
+ loopback origins at any port are admitted. Loopback means `localhost`,
70
+ `127.0.0.0/8`, or `[::1]`. Without `publicUrl`, only loopback is admitted.
71
+ The inbound Host header never chooses a trusted browser origin. Invalid list
72
+ entries, including paths, credentials, and opaque origins, refuse construction.
73
+
74
+ ```ts
75
+ createConnecta({
76
+ publicUrl: "https://connecta.example",
77
+ allowedOrigins: ["https://connecta.example", "https://client.example"],
78
+ // …
79
+ });
80
+ ```
81
+
82
+ For unrestricted browser access, set `allowedOrigins: "*"` explicitly. Otherwise
83
+ MCP responses reflect an admitted Origin and carry `Vary: Origin`; originless
84
+ and refused requests have no `Access-Control-Allow-Origin`. Allowed preflight
85
+ returns 204 without auth or admission. The SDK validates SEP-2243 parameter
86
+ headers, so preflight echoes valid requested `mcp-param-*` names alongside the
87
+ fixed MCP header list and varies by `Access-Control-Request-Headers` too.
88
+
57
89
  ## Admission before auth
58
90
 
59
91
  `/mcp` acquires its permit *before* running the auth gate. This looks backwards
@@ -86,7 +118,7 @@ CORS headers, and a stable JSON-RPC error:
86
118
  "jsonrpc": "2.0",
87
119
  "id": null,
88
120
  "error": {
89
- "code": -32001,
121
+ "code": -31001,
90
122
  "message": "Server capacity is exhausted. Retry later.",
91
123
  "data": { "code": "server_overloaded", "retryable": true, "retryAfterMs": 1000 }
92
124
  }
@@ -94,8 +126,10 @@ CORS headers, and a stable JSON-RPC error:
94
126
  ```
95
127
 
96
128
  `Retry-After` is that hint rounded up to at least one whole second. It is
97
- advice, not a reservation. Shutdown uses `-32002` / `server_shutting_down` and
98
- is not retryable. Code-pool overload never reaches this layer: it surfaces as
129
+ advice, not a reservation. Shutdown uses `-31002` / `server_shutting_down` and
130
+ is not retryable. These application codes sit outside JSON-RPC's reserved
131
+ range: MCP forbids new allocations in the legacy `-32000..-32019` range.
132
+ Code-pool overload never reaches this layer: it surfaces as
99
133
  an ordinary MCP tool error with `executor_overloaded`, `retryable: true`, and
100
134
  the executor's own `retryAfterMs`.
101
135
 
@@ -43,6 +43,57 @@ permissions default to none. Saving, testing, replacing, or removing a value
43
43
  never returns it. The vault is read for each call, so a saved replacement takes
44
44
  effect without restarting the deployment.
45
45
 
46
+ ## Result storage
47
+
48
+ Direct-call result paging uses the same KV interface with a 15-minute TTL.
49
+ `results.maxStashBytes` defaults to 8 MiB of stored paging envelopes, including
50
+ base64 overhead; `results.maxStashEntries` defaults to 64. Both are
51
+ non-negative safe integers, and zero disables stashing. One registry accounts
52
+ for all subjects and reserves capacity for pending writes. A full stash keeps
53
+ the successful call's preview and returns a paging-unavailable notice, without
54
+ a result id. Expired entries are deleted on later stash attempts before their
55
+ capacity is reused, even when the backend only expires entries on read.
56
+ A deletion failure keeps the reservation. Limits apply to writes by one
57
+ runtime; they do not coordinate other processes or Worker isolates, or count
58
+ entries left by a previous runtime.
59
+
60
+ The memory store also checks up to 16 existing keys on each `set`, rotating
61
+ through live keys so expired entries that nobody reads are eventually removed.
62
+ There are no timers or background sweeps. Paging values use an ASCII base64
63
+ envelope so only the requested bytes need decoding after the KV read. The
64
+ storage adapter's format and interface stay unchanged.
65
+
66
+ ## File storage
67
+
68
+ `fileStorage` is a single-process development store. It loads one snapshot and
69
+ rewrites the whole state synchronously on each mutation, including result
70
+ stashes. It acquires an exclusive `<path>.lock` before loading, so a second
71
+ instance or process opening the same path fails with the holder's pid instead
72
+ of overwriting a stale snapshot. An unref'd timer refreshes the lock's mtime
73
+ every 15 seconds. A heartbeat older than 60 seconds expires regardless of pid,
74
+ so a container restart cannot leave a reused pid holding the file forever.
75
+ The lock records the host/PID namespace as well as the pid. Within that same
76
+ namespace, a dead pid permits immediate recovery; a matching current pid is
77
+ live only when the in-process registry owns that lock. Other namespaces and
78
+ older locks without namespace metadata rely on heartbeat expiry.
79
+
80
+ Recovery uses a serialized `.lock.reclaim` guard, which also expires after
81
+ 60 seconds if its process crashes or pauses. An incomplete lock likewise
82
+ becomes recoverable after 60 seconds. A holder paused long enough to lose its
83
+ lock fails subsequent writes with "lock was lost". Writes check ownership
84
+ before changing state and again before rename; reads use the loaded snapshot
85
+ without filesystem lock checks. This remains an advisory development store
86
+ on a shared local filesystem, not a distributed storage adapter.
87
+
88
+ The returned store's `close()` releases the lock and refuses further operations.
89
+ Process exit also releases it, including Node `listen()`'s SIGTERM/SIGINT
90
+ shutdown. Each write uses a unique, exclusively created temp file, and the
91
+ state file's JSON format is unchanged.
92
+
93
+ Expired entries are removed before each write. Large direct-call results therefore increase both
94
+ retained state and write cost. Use `execute_code` to reduce read-only results
95
+ before returning them, and choose a storage adapter suited to the deployment.
96
+
46
97
  ## Storage continuity
47
98
 
48
99
  This module extraction changes no encrypted record keys, owner partitions, or
@@ -74,7 +125,9 @@ Personal connectors disappear from a request that has no stable human
74
125
  principal. For a principal that can see one, connecta partitions connector
75
126
  storage, encrypted vault records, catalog caches, OAuth generations, and
76
127
  observed result shapes under an opaque SHA-256 identity key. Results used by
77
- `get_result` are partitioned by the authenticated subject, so one authenticated subject cannot page another subject's call.
128
+ `get_result` are partitioned by the authenticated subject independently of
129
+ activity configuration. Open deployments and providers that supply no identity
130
+ share one results partition. See [the partition and paging contract](./meta-tools.md#result-representation).
78
131
 
79
132
  Literal `auth: { type: "headers" }` cannot be personal because its secret lives
80
133
  in deployment code. `remoteMcp()` refuses that combination at construction.
@@ -117,7 +117,7 @@ exist so far:
117
117
  | --- | --- | --- |
118
118
  | **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
119
119
  | **A** | 0.10.2 – 0.15.1 | `.env.example`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `src/index.ts`, `tsconfig.json` |
120
- | **B** | 0.16.0 – 0.24.2 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
120
+ | **B** | 0.16.0 – 0.24.3 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
121
121
 
122
122
  Generation A is a decade in template years and identifying it precisely does
123
123
  not matter, because you are about to reconstruct it exactly rather than guess
@@ -190,7 +190,7 @@ Generate the *current* template beside the base you already made, into the same
190
190
  `$SCRATCH`:
191
191
 
192
192
  ```sh
193
- (cd "$SCRATCH" && npx @zackbart/connecta@0.24.2 init current)
193
+ (cd "$SCRATCH" && npx @zackbart/connecta@0.24.3 init current)
194
194
  ```
195
195
 
196
196
  You now have a three-way merge with a real base: `$SCRATCH/base` is what this
@@ -246,7 +246,7 @@ A deployment older than 0.10.2 has no base to diff against. Do not try to
246
246
  manufacture one. Instead:
247
247
 
248
248
  1. `SCRATCH=$(mktemp -d)`, then
249
- `(cd "$SCRATCH" && npx @zackbart/connecta@0.24.2 init current)` — there is no
249
+ `(cd "$SCRATCH" && npx @zackbart/connecta@0.24.3 init current)` — there is no
250
250
  `base` leg here, only the current template to read from.
251
251
  2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
252
252
  `src/index.ts`**.
@@ -267,7 +267,21 @@ first, so cross them bottom-up: start at the oldest one still above this
267
267
  deployment's pin and work back up the page, because each boundary assumes the
268
268
  older ones are already done.
269
269
 
270
- ### 0.23.0 → 0.24.2
270
+ ### 0.23.0 → 0.24.3
271
+
272
+ 0.24.3 validates the browser `Origin` header on `/mcp`. A browser MCP client
273
+ hosted on an origin other than `publicUrl` or loopback now gets a 403 until
274
+ the deployment lists it in `allowedOrigins` (or sets `"*"` to keep the old
275
+ open CORS); clients that send no `Origin`, which is every server-side and CLI
276
+ client, are unaffected. The overload and shutdown JSON-RPC error codes moved
277
+ from `-32001`/`-32002` to `-31001`/`-31002`; the `data.code` strings are
278
+ unchanged. See [request admission](./request-admission.md#origin-before-admission).
279
+ `fileStorage` now holds an exclusive lock on its state file: a second process
280
+ opening the same file fails at construction, so a deployment that shared one
281
+ file between two processes must give each its own. The returned store gained
282
+ `close()`. `get_result` stashes are bounded per runtime by the new `results`
283
+ config (8 MiB and 64 entries by default) and partitioned by any authenticated
284
+ subject, not only by providers that declare an activity namespace.
271
285
 
272
286
  Use the [optional-module migration](./optional-modules-upgrade.md) to select
273
287
  modules, grant auth-management permissions, and migrate issued-token clients.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.24.2",
3
+ "version": "0.24.3",
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.",
@@ -15,7 +15,7 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "@zackbart/connecta": "0.24.2",
18
+ "@zackbart/connecta": "0.24.3",
19
19
  "quickjs-emscripten": "0.32.0"
20
20
  },
21
21
  "devDependencies": {