@zackbart/connecta 0.24.4 → 0.25.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 +40 -10
- package/CHANGELOG.md +253 -0
- package/dist/advertised-schema.js +53 -0
- package/dist/auth/downstream-oauth.js +490 -106
- package/dist/call-admission.js +139 -113
- package/dist/catalog-service.js +141 -105
- package/dist/catalog.js +258 -33
- package/dist/concurrency.js +24 -23
- 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 +317 -241
- 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/execute.js +363 -257
- package/dist/executor-admission.js +67 -50
- package/dist/executors/quickjs-runtime.js +4 -2
- package/dist/executors/quickjs.js +251 -210
- package/dist/fix-prompt.js +37 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +51 -20
- package/dist/invocation.js +324 -285
- package/dist/meta-tools.js +206 -92
- 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 +46 -1
- package/dist/operator-ui/setup-commands.js +53 -0
- package/dist/operator-ui/view.js +38 -2
- package/dist/program-source.js +205 -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 +1 -1
- package/dist/registry.js +305 -260
- package/dist/routes/activity.js +120 -137
- package/dist/routes/credentials.js +174 -163
- package/dist/routes/mcp.js +129 -70
- package/dist/routes/oauth-management.js +60 -71
- package/dist/routes/oauth.js +44 -16
- package/dist/routes/operator.js +82 -0
- package/dist/routes/shared.d.ts +2 -0
- package/dist/routes/ui-data.js +210 -0
- package/dist/routes/ui.js +64 -32
- 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/storage.js +66 -0
- package/dist/server.js +111 -89
- package/dist/skills.js +1 -1
- package/dist/storage/file.js +39 -2
- package/dist/storage/memory.js +28 -16
- package/dist/timeout.js +17 -47
- package/dist/types.d.ts +27 -7
- package/dist/ui.d.ts +16 -2
- package/dist/ui.js +50 -166
- package/dist/url-safety.js +157 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +184 -10
- package/documentation/auth.md +80 -0
- package/documentation/code-mode.md +69 -24
- package/documentation/meta-tools.md +164 -27
- package/ethos.md +44 -47
- package/examples/worker/README.md +51 -5
- package/examples/worker/src/d1-storage.ts +116 -0
- package/examples/worker/src/index.ts +6 -6
- package/examples/worker/wrangler.jsonc +16 -4
- package/package.json +11 -3
- package/templates/node/package.json +1 -1
- 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/tool-safety.d.ts +0 -9
package/AGENTS.md
CHANGED
|
@@ -6,14 +6,18 @@ symlink to this file so every agent works from the same conventions.
|
|
|
6
6
|
A single MCP endpoint aggregating downstream connectors (remote MCP servers and
|
|
7
7
|
plain HTTP APIs) behind seven meta-tools, `execute_code` among them. Every
|
|
8
8
|
deployment configures an executor, and agents reach connectors by writing
|
|
9
|
-
JavaScript against it. One fetch-native core
|
|
10
|
-
Cloudflare Workers.
|
|
9
|
+
JavaScript against it. One fetch-native core on Effect behind a Promise API,
|
|
10
|
+
running on both Node and Cloudflare Workers. Two `planned` decisions change
|
|
11
|
+
that picture — resumable writes with an eighth tool, and an artifacts module —
|
|
12
|
+
and until they ship, the code and the guides describe what exists.
|
|
11
13
|
|
|
12
14
|
- **[`ethos.md`](./ethos.md) is the constitution.** It states what connecta is
|
|
13
15
|
and isn't, and its decisions table carries a verdict for every shape already
|
|
14
|
-
considered — refused, removed, provisional, or gated.
|
|
15
|
-
designing or building anything: a `
|
|
16
|
-
|
|
16
|
+
considered — accepted, planned, refused, removed, provisional, or gated.
|
|
17
|
+
Check the table before designing or building anything: a `planned` row is a
|
|
18
|
+
"yes" that has not shipped, binding on review now and absent from the guides
|
|
19
|
+
until it lands, so never document it there as if it exists; a `refused` row
|
|
20
|
+
is a "no" with the reason attached, and a `removed` row (toolkits
|
|
17
21
|
[#178](https://github.com/zackbart/connecta/issues/178), proactive credential
|
|
18
22
|
liveness [#179](https://github.com/zackbart/connecta/issues/179), the classic
|
|
19
23
|
executor-free surface
|
|
@@ -30,8 +34,12 @@ Cloudflare Workers.
|
|
|
30
34
|
|
|
31
35
|
`npm run check` must pass before you claim anything is done — `check:docs` →
|
|
32
36
|
`check:operator-ui` → `check:lint` → `check:unused` → `typecheck` → `test`
|
|
33
|
-
(both vitest projects) → `
|
|
34
|
-
|
|
37
|
+
(both vitest projects) → `test:browser` (the operator UI in Chromium; run
|
|
38
|
+
`npm run test:browser:install` once per machine, since `check` is also what
|
|
39
|
+
`npm pack` runs) → `build` (which prunes unreachable declarations) →
|
|
40
|
+
`check:declarations` (no Effect type in what ships) → `check:bundle` (per-entry
|
|
41
|
+
gzip caps in `scripts/bundle-budget.json`) → `check:examples`. It is also the
|
|
42
|
+
`prepack` hook. `npm run release:check` adds `check:security` and `check:package` and is
|
|
35
43
|
what CI runs on every pull request, on `main`, and again on publish; use it
|
|
36
44
|
when touching packaging, dependencies, or exports.
|
|
37
45
|
|
|
@@ -40,7 +48,7 @@ when touching packaging, dependencies, or exports.
|
|
|
40
48
|
- [`ethos.md`](./ethos.md) — what connecta is, what it refuses to be, the
|
|
41
49
|
decisions table, and the invariants every change must preserve. Check it
|
|
42
50
|
before building something new; "we already decided not to" is a real answer
|
|
43
|
-
there, and its removed/provisional verdicts override anything staler.
|
|
51
|
+
there, and its planned/removed/provisional verdicts override anything staler.
|
|
44
52
|
- [`documentation/`](./documentation/) — four guides for agents working on the
|
|
45
53
|
repo: `architecture.md`, `meta-tools.md`, `code-mode.md`, and `auth.md`.
|
|
46
54
|
Everything else — connectors, providers, admission, storage, the operator
|
|
@@ -82,7 +90,11 @@ Two boundaries CI enforces that are not obvious from reading a file:
|
|
|
82
90
|
(`src/executors/quickjs.ts` + child) are the Node-touching paths and must stay
|
|
83
91
|
unreachable from the root entry. `test/purity.test.ts` walks the import graph
|
|
84
92
|
and fails otherwise. Need a Node API? It goes behind an explicit Node-only
|
|
85
|
-
subpath (`/node` or `/quickjs`), never the root.
|
|
93
|
+
subpath (`/node` or `/quickjs`), never the root. `effect/testing` is banned
|
|
94
|
+
from `src/` on the same terms — its test clocks and layers have no business
|
|
95
|
+
in a runtime graph — and the purity walk catches it. The same walk keeps
|
|
96
|
+
the root entry to `effect` itself and lets only `src/runtime/run.ts` start
|
|
97
|
+
a fiber.
|
|
86
98
|
- **Optional modules.** Core owns catalog discovery, execution, invocation, and
|
|
87
99
|
enforcement. UI, activity history, encrypted credentials, and bearer auth
|
|
88
100
|
implementations stay behind explicit subpaths and outside the root import
|
|
@@ -101,7 +113,9 @@ Two boundaries CI enforces that are not obvious from reading a file:
|
|
|
101
113
|
without a subpath because a Worker deployment imports it directly. None may
|
|
102
114
|
become a dependency or install with core. Enforced by `test/package-surface.test.ts` and
|
|
103
115
|
`scripts/check-package.mjs`. Anything heavyweight or platform-bound gets a
|
|
104
|
-
subpath and an optional peer.
|
|
116
|
+
subpath and an optional peer. The Effect core is the one deliberate
|
|
117
|
+
exception: it is the core, so it is a hard dependency rather than a peer,
|
|
118
|
+
under the rule in [Conventions](#conventions).
|
|
105
119
|
|
|
106
120
|
## Where new tests go
|
|
107
121
|
|
|
@@ -118,6 +132,22 @@ an unclassified, double-classified, stale, or reasonless entry.
|
|
|
118
132
|
only; it does not enforce style. `npm run check:unused` runs Knip's
|
|
119
133
|
unused-export and dependency gate. Keep both clean, and prefer removing dead
|
|
120
134
|
declarations over suppressing a finding.
|
|
135
|
+
- **Effect inside, Promises at the edge.** The core runs on Effect v4 and
|
|
136
|
+
exports none of it; [Effect inside](./documentation/architecture.md#effect-inside)
|
|
137
|
+
has the shell/core shape, the one runner, and the cross-request rules.
|
|
138
|
+
`createConnecta`,
|
|
139
|
+
`remoteMcp()`, `api()`, custom connectors, and every published `.d.ts` stay
|
|
140
|
+
Promise-based and name no Effect type: a deployment author should never need
|
|
141
|
+
a second async paradigm to write a connector, so convert at the boundary,
|
|
142
|
+
not in the caller. Effect is a hard dependency pinned to one exact version,
|
|
143
|
+
never a range, and it is the version Alchemy pins, so a deployment that uses
|
|
144
|
+
both resolves one Effect rather than two. `effect/unstable/*` modules are
|
|
145
|
+
allowed, which is exactly why the pin is exact: they break in minor releases,
|
|
146
|
+
so an upgrade is its own deliberate pull request, never a drive-by in another
|
|
147
|
+
change. `effect/testing` stays out of `src/` (see import-graph purity). The
|
|
148
|
+
MCP edges do not move with the core — `@modelcontextprotocol/server` upward,
|
|
149
|
+
the SDK client downstream; Effect's `McpServer` is parked until the Effect
|
|
150
|
+
core is stable and those edges are re-proven.
|
|
121
151
|
- **Style.** There is no formatter. Match the surrounding code. The docs voice
|
|
122
152
|
is precise, occasionally wry, and always explains *why* — don't flatten it
|
|
123
153
|
into boilerplate.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,259 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.25.0 — 2026-09-23
|
|
6
|
+
|
|
7
|
+
The Effect core is the release. Admission, deadlines, invocation,
|
|
8
|
+
`execute_code`, discovery, the registry, remote MCP connections, OAuth refresh,
|
|
9
|
+
and the request pipeline now run on Effect v4 behind the same Promise API:
|
|
10
|
+
connectors, `createConnecta`, and every shipped declaration name no Effect
|
|
11
|
+
type, and no connector code changes. The rewrite paid for itself in lifetimes —
|
|
12
|
+
about a dozen bugs where a permit, a lease, or a wait outlived the request that
|
|
13
|
+
owned it, each listed under Fixed — and the release also seals downstream OAuth
|
|
14
|
+
state, stops treating a dead refresh grant as an outage, and bounds results
|
|
15
|
+
below what clients cut off. What breaks: `effect` is a new hard dependency,
|
|
16
|
+
pinned to exactly `4.0.0-rc.117` (about 53 MB installed, +44 KB gzip on the
|
|
17
|
+
root bundle, about +20 ms of Worker cold start); `calls.maxResultBytes`
|
|
18
|
+
defaults to 24,000 instead of 50,000, and a truncated result now leads with its
|
|
19
|
+
notice; `get_result` pages are a header line plus raw text, with `maxBytes`
|
|
20
|
+
clamped to the cap; with `encryptedCredentialVault()`, OAuth tokens are sealed
|
|
21
|
+
the first time they are read, so rolling back to 0.24 means authorizing every
|
|
22
|
+
OAuth connector again; `/ui/connectors/<id>` no longer returns `message`; the
|
|
23
|
+
OAuth callback page names a classified reason instead of echoing the
|
|
24
|
+
provider's; an OAuth URL a downstream advertises off its configured origin must
|
|
25
|
+
be HTTPS on a public host; and `npm run check` now needs Chromium. A deployment
|
|
26
|
+
can ignore the rest: every new surface is optional or defaulted
|
|
27
|
+
(`KVStorage.compareAndSet`, `CredentialVault.seal`/`open`,
|
|
28
|
+
`execute.watchdogMs`, the `typescript` schema format), no existing option
|
|
29
|
+
changes meaning (the result cap changes only its default), and results stashed
|
|
30
|
+
by 0.24 keep paging until they expire.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`KVStorage.compareAndSet(key, expected, next, { ttlSeconds })`.** An
|
|
35
|
+
optional atomic claim: `expected: null` means absent (an expired entry
|
|
36
|
+
counts), `next: null` deletes, and a write takes a TTL exactly as `set` does.
|
|
37
|
+
Memory and file storage provide it — the file store atomically under its
|
|
38
|
+
lock, persisted across reopen, and rolled back when the write cannot be
|
|
39
|
+
persisted — and the namespaced views core hands connectors forward it only
|
|
40
|
+
when the store underneath has it. The Worker example gains a D1-backed
|
|
41
|
+
`KVStorage` with compare-and-set and TTL (`examples/worker/src/d1-storage.ts`,
|
|
42
|
+
its schema in the example README, a commented `STORAGE_DB` binding). The
|
|
43
|
+
Cloudflare KV adapter declares none, because eventually consistent storage
|
|
44
|
+
cannot keep that promise. Nothing requires it; downstream OAuth uses it where
|
|
45
|
+
it exists.
|
|
46
|
+
- **`CredentialVault.seal` / `open`.** Optional members for sealing values that
|
|
47
|
+
are not credentials. `encryptedCredentialVault()` implements them with the
|
|
48
|
+
vault's AES-GCM key, binding the connector id, the owner, and the physical
|
|
49
|
+
storage key (which carries the authorization epoch) into the authenticated
|
|
50
|
+
data, so ciphertext copied to another connector, principal, or epoch does not
|
|
51
|
+
open. A vault without them draws one startup warning per OAuth connector.
|
|
52
|
+
- **`execute.watchdogMs`.** A ceiling on how long connecta waits for an
|
|
53
|
+
executor to settle, enforced outside the sandbox: 120,000 ms by default,
|
|
54
|
+
above both executors' own deadlines, with an invalid value falling back like
|
|
55
|
+
its sibling keys. A run still unsettled at the ceiling ends as a
|
|
56
|
+
non-retryable `executor_failed` calling the sandbox unresponsive, and its
|
|
57
|
+
admission lease is released. See code mode `L3`.
|
|
58
|
+
- **TypeScript signatures in discovery.** `includeSchemas: "typescript"` on
|
|
59
|
+
`search_tools` and `connecta.search`, and `format: "typescript"` on
|
|
60
|
+
`connecta.describe`, replace both schema fields with one `signature` — the
|
|
61
|
+
function `connecta.call` resolves to, such as
|
|
62
|
+
`(args: { team: string }) => Promise<unknown>` — for an agent to read, never
|
|
63
|
+
to run; programs stay JavaScript. It rides the compact renderer's walk and
|
|
64
|
+
budgets, and an observed output opens with `/* observed, not declared */`.
|
|
65
|
+
The default stays `compact`: agents in the eval never chose `typescript`, even
|
|
66
|
+
when nudged.
|
|
67
|
+
- **The operator page says what each tool may do and how to fix what is
|
|
68
|
+
wrong.** Every tool carries the path core enforces for it — "runs in
|
|
69
|
+
programs" for an explicitly read-only tool, "needs approval" for everything
|
|
70
|
+
that crosses `call_destructive_tool`. Every failure the page shows offers
|
|
71
|
+
"Copy fix prompt", fixed text chosen by a server-classified problem and the
|
|
72
|
+
configured connector id, with no parameter an error message, token, or URL
|
|
73
|
+
could travel through. The endpoint block offers Claude Code, Codex, and
|
|
74
|
+
`mcpServers` JSON setup for `/mcp` and for each `/mcp/<pool>` the viewer's
|
|
75
|
+
grant admits; none carries a token.
|
|
76
|
+
- **An eval harness, for contributors.** `eval/` drives a deployment only
|
|
77
|
+
through its public surface: `eval:agent` runs headless Claude Code against six
|
|
78
|
+
stateful fake MCP servers and grades the fakes' final state and call ledger,
|
|
79
|
+
never the agent's prose; `eval:smoke` boots the Node template and the Worker
|
|
80
|
+
example; `eval:perf` measures the root Worker bundle and request latency;
|
|
81
|
+
`eval:report` renders a comparison against the baselines in
|
|
82
|
+
`eval/baselines/`. None of it runs in `npm run check` or ships in the
|
|
83
|
+
tarball.
|
|
84
|
+
|
|
85
|
+
### Changed
|
|
86
|
+
|
|
87
|
+
- **The core runs on Effect.** Each converted module keeps its exported class
|
|
88
|
+
or function exactly and runs an Effect program behind it; one runner,
|
|
89
|
+
`src/runtime/run.ts`, is the only place a fiber starts, and it rethrows
|
|
90
|
+
connecta's own error classes and a caller's abort reason unchanged.
|
|
91
|
+
`documentation/architecture.md` describes the shape and the two workerd rules
|
|
92
|
+
it had to learn. `effect` is a runtime dependency at one exact version, never
|
|
93
|
+
a range — the version Alchemy pins, so a deployment using both resolves one
|
|
94
|
+
copy — and an upgrade is its own release. The cost, against 0.24.4: the root
|
|
95
|
+
entry 235,346 → 279,526 B gzip, the Worker example 263,949 → 314,336 B, each
|
|
96
|
+
provider about +36 KB, `./ui` +39 KB, `./activity` +32 KB, and about 53 MB
|
|
97
|
+
on disk (`effect` has no dependencies of its own). Effect Schema and
|
|
98
|
+
`HttpApi` were measured and not used; `zod` stays for the meta-tool inputs.
|
|
99
|
+
- **Truncated results lead with their handle, under client cutoffs.** Claude
|
|
100
|
+
Code (2.1.280) swaps any MCP result over 50,000 characters for a spill-file
|
|
101
|
+
pointer and a 2,000-character preview, and past twice
|
|
102
|
+
`MAX_MCP_OUTPUT_TOKENS` rejects it with no content at all. The old layout, a
|
|
103
|
+
50,000-byte preview with the `get_result` notice at the tail, crossed that
|
|
104
|
+
line every time: agents never saw the handle, and two of three models re-ran
|
|
105
|
+
a billable export three times to look again. A `call_tool` or
|
|
106
|
+
`call_destructive_tool` result over its cap now opens with a one-line JSON
|
|
107
|
+
notice (`resultId`, `totalBytes`, `hint`, `nextAction`), then the preview;
|
|
108
|
+
the default `calls.maxResultBytes` drops to 24,000 bytes so notice plus
|
|
109
|
+
preview stays under 25,000; a lone text block is measured, previewed, and
|
|
110
|
+
paged as its text rather than a JSON-escaped content envelope; and a call not
|
|
111
|
+
explicitly read-only says the write already ran and must not be repeated.
|
|
112
|
+
Per-connector overrides and validation are unchanged, and a deployment whose
|
|
113
|
+
clients take more can raise the cap. A result between 24,000 and 50,000
|
|
114
|
+
bytes that used to arrive whole now pages.
|
|
115
|
+
- **A truncated read offers a program before paging.** The notice on an
|
|
116
|
+
explicitly read-only call now says: to find something specific, repeat the
|
|
117
|
+
read inside `execute_code` with `connecta.call` and filter or search it
|
|
118
|
+
there; to read it in full, page with `get_result`. It used to name paging
|
|
119
|
+
alone, and once the handle was visible the eval's Haiku paged a 185 KB CI
|
|
120
|
+
log 24,000 bytes at a time, skipped ranges or stopped after two pages, and
|
|
121
|
+
named the flaky test near the top in all three trials. With the new hint it
|
|
122
|
+
reduced the log in a program and found the real failure in three of three,
|
|
123
|
+
at about a third of the cost per trial. A write's notice is unchanged, since
|
|
124
|
+
repeating a write is what it forbids, and `nextAction` is still the page
|
|
125
|
+
handle for both.
|
|
126
|
+
- **`get_result` pages are raw text.** A page is one text block: a JSON header
|
|
127
|
+
(`resultId`, `offset`, `bytes`, `totalBytes`, `hasMore`, `nextAction`), a
|
|
128
|
+
newline, then the page as stored, where it used to be
|
|
129
|
+
`{ offset, nextOffset?, totalBytes, text }` with every quote and newline in
|
|
130
|
+
the page escaped a second time. `maxBytes` is an upper bound clamped to the
|
|
131
|
+
inline cap of the call that stashed the result, recorded in the stash entry;
|
|
132
|
+
a larger request is answered, not refused, because agents that could finally
|
|
133
|
+
see the handle asked for 50,000-byte pages and Claude Code rejected every
|
|
134
|
+
answer. Entries stashed before the cap was recorded page at the deployment
|
|
135
|
+
cap.
|
|
136
|
+
- **Connector status text stays in the server log.** A status message can quote
|
|
137
|
+
a downstream error body, and that body can quote the secret it rejected, so
|
|
138
|
+
`/ui/connectors/<id>` no longer returns `message` and the page shows fixed
|
|
139
|
+
copy for a classified `problem` instead. The raw text is logged as
|
|
140
|
+
`[connecta] connector "<id>" operator status <state>: <message>`, at warn for
|
|
141
|
+
failures and info for `auth_required`.
|
|
142
|
+
- **The OAuth callback page names a closed reason** — `denied`,
|
|
143
|
+
`provider_error`, `invalid_callback`, `handoff_failed`, or `exchange_failed` —
|
|
144
|
+
instead of echoing the provider's `error` parameter or the exchange error,
|
|
145
|
+
which goes to the operator log. An expired callback reads as
|
|
146
|
+
`invalid_callback`, so refusals stay indistinguishable.
|
|
147
|
+
- **Faster requests.** Each `/mcp` request builds a fresh `McpServer`, and zod
|
|
148
|
+
re-rendered all seven meta-tool input schemas for every one — about 65% of
|
|
149
|
+
`tools/list` CPU. They are now derived once and memoized; `tools/list`
|
|
150
|
+
bodies are byte-identical. On the eval's Node deployment, p50 against 0.24.4:
|
|
151
|
+
`tools/list` 1.43 → 0.68 ms, `call_tool` 2.95 → 1.34 ms, `execute_code`
|
|
152
|
+
4.94 → 3.45 ms.
|
|
153
|
+
- **The tarball ships only declarations an export reaches.** The build prunes
|
|
154
|
+
every `.d.ts` file no `exports` target reaches — about thirty,
|
|
155
|
+
`src/runtime/` included — and `npm run check:declarations` fails if a shipped
|
|
156
|
+
declaration names an Effect type or is left orphaned. Nothing importable
|
|
157
|
+
changed; those paths were never a supported import.
|
|
158
|
+
- **`npm run check` needs Chromium.** It runs the operator UI's browser suite
|
|
159
|
+
after vitest (`npm run test:browser:install` once per machine; `npm pack`
|
|
160
|
+
runs `check` too), then `check:declarations` and `check:bundle`, which caps
|
|
161
|
+
every entry's gzip size against `scripts/bundle-budget.json`. CI and publish
|
|
162
|
+
install Chromium first.
|
|
163
|
+
- RevenueCat's product store-state plan family and targeting-rule tools arrive
|
|
164
|
+
classified instead of failing closed; the five tools RevenueCat deprecated
|
|
165
|
+
keep their verdicts, and the usage guide routes store changes through the
|
|
166
|
+
plan workflow (#560).
|
|
167
|
+
|
|
168
|
+
### Fixed
|
|
169
|
+
|
|
170
|
+
- **Downstream OAuth state at rest.** A `remoteMcp()` OAuth connector's tokens,
|
|
171
|
+
registered client secret, and PKCE verifier were written to storage as
|
|
172
|
+
plaintext JSON beside vault-encrypted credentials. With a vault that has
|
|
173
|
+
`seal`/`open` they are now sealed on write, and plaintext left by an older
|
|
174
|
+
release is resealed where it lies on first read, conditional on nothing
|
|
175
|
+
having changed it meanwhile. A value that does not open reads as absent:
|
|
176
|
+
`auth_required`, with a warning. Without a vault, stored bytes are
|
|
177
|
+
unchanged.
|
|
178
|
+
- **Dead refresh grants.** A refused refresh — any 4xx but 408, 425, or 429,
|
|
179
|
+
or a 2xx carrying an OAuth error such as GitHub's `bad_refresh_token` — was
|
|
180
|
+
reported as a generic failure and the dead token sent again, while an outage
|
|
181
|
+
the SDK could not parse was reported as needing consent. A refusal now ends
|
|
182
|
+
as `auth_required` with the refused tokens deleted, so the next
|
|
183
|
+
`authorize_connector` reaches consent; a 5xx, 408, 425, 429, or network
|
|
184
|
+
failure is a retryable `unavailable` (or `rate_limited`, with `retryAfterMs`
|
|
185
|
+
from `Retry-After`), the grant is kept, and a passive call writes no consent
|
|
186
|
+
URL. Every request joined on the refresh gets the same verdict.
|
|
187
|
+
- **A refused refresh token could be redeemed twice.** A request that owned a
|
|
188
|
+
refresh and was cancelled while deleting the refused grant released its
|
|
189
|
+
joiners early, so a request arriving meanwhile sent the refused token again.
|
|
190
|
+
Deleting it also no longer overwrites a consent that lands between the read
|
|
191
|
+
and the delete, where the store has `compareAndSet`.
|
|
192
|
+
- **OAuth URLs a downstream advertises.** Connecta fetched the authorization
|
|
193
|
+
servers, token endpoints, and registration endpoints a remote server's
|
|
194
|
+
metadata names from the host, so a compromised downstream could aim a Node or
|
|
195
|
+
Docker deployment at `169.254.169.254` or its LAN. A URL off the connector's
|
|
196
|
+
configured origin must now be HTTPS on a host that is not private by syntax,
|
|
197
|
+
and a refused one is never requested; it fails as a non-retryable
|
|
198
|
+
`connector_call_failed` naming only the host. A loopback-configured connector
|
|
199
|
+
may still learn loopback URLs. DNS rebinding is out of scope, as the auth
|
|
200
|
+
guide says.
|
|
201
|
+
- **Admission permits and leases that outlived their caller.**
|
|
202
|
+
- An `/mcp` request that stalled after admission — in inbound auth, a pool
|
|
203
|
+
grant, or a tool handler that ignores cancellation — kept its permit after
|
|
204
|
+
its client left. On Workers, where such a request is cancelled as hung, the
|
|
205
|
+
permit leaked for the isolate's life.
|
|
206
|
+
- `execute_code` awaited its executor with no outer bound. A wedged Dynamic
|
|
207
|
+
Worker never fired its in-isolate deadline, so the run held its code-pool
|
|
208
|
+
lease forever; two of those filled the default pool and every later
|
|
209
|
+
program queued behind them. `execute.watchdogMs` ends that wait.
|
|
210
|
+
- An admitting executor whose `acquire()` ignored the signal held a cancelled
|
|
211
|
+
request until it granted a lease.
|
|
212
|
+
- **Work that outlived a cancelled request.** `connecta.search` and
|
|
213
|
+
`connecta.describe` kept loading catalogs after their run was cancelled, and
|
|
214
|
+
one in flight held the program until its probe deadline; `search_tools`
|
|
215
|
+
probes ran on after their caller left; operator activity pages kept looking
|
|
216
|
+
up labels from the identity provider after the reader had gone; and legacy
|
|
217
|
+
`/mcp` requests never closed their `McpServer`.
|
|
218
|
+
- **Call admission on Workers.** Handing a permit to a queued call read the
|
|
219
|
+
waiter's abort signal from inside the releasing request, which workerd
|
|
220
|
+
refuses: the releasing call reported `connector_call_failed` despite
|
|
221
|
+
succeeding, and the waiter stranded until its deadline.
|
|
222
|
+
- **Remote MCP connections.** A call waiting on a connect could resume after
|
|
223
|
+
teardown, a forced re-authorization, or a credential rotation had cleared the
|
|
224
|
+
client, and fail with "Cannot read properties of null (reading 'callTool')".
|
|
225
|
+
And a transport whose close never settled held the connection's teardown
|
|
226
|
+
forever, hanging direct callers such as the credential Test action; closing a
|
|
227
|
+
session and the transport are now bounded to a second each.
|
|
228
|
+
- **A stale catalog persisted after invalidation.** Evicting a personal
|
|
229
|
+
registry while its catalog refresh was in flight let a replacement for the
|
|
230
|
+
same principal invalidate the catalog, and the evicted refresh then persisted
|
|
231
|
+
the pre-change listing again. Eviction now skips a registry with a refresh or
|
|
232
|
+
catalog write in flight.
|
|
233
|
+
- **QuickJS shutdown.** A child that ignored SIGTERM outlived `close()`, which
|
|
234
|
+
stopped waiting after a second; it now gets SIGKILL. `close()` also waits for
|
|
235
|
+
children recycled before it, not only its own.
|
|
236
|
+
- **Operator routes.** A credential PUT read its whole body before the
|
|
237
|
+
20,000-character check; reading now stops past 60,000 bytes with the same
|
|
238
|
+
413. An OAuth disconnect that rejected with no reason reported 204.
|
|
239
|
+
- A program ending in `};` failed on QuickJS with `SyntaxError: expecting ')'`,
|
|
240
|
+
because both executors evaluate a program inside parentheses; on a Dynamic
|
|
241
|
+
Worker it failed the same way or, with a comment after it, quietly returned
|
|
242
|
+
`undefined`. `execute_code` now drops semicolons that trail the arrow
|
|
243
|
+
expression, with whitespace and comments after them, before either executor
|
|
244
|
+
sees the program. Anything else stays as written, so `async () => 1; 2` is
|
|
245
|
+
still more than one expression (code mode `P1`).
|
|
246
|
+
- A scoped `connecta.search` whose probe timed out named `search_tools` in
|
|
247
|
+
`queryAnalysis.catalogError`, a tool a program cannot call.
|
|
248
|
+
- Recovery text for a missing or rejected static credential sent operators and
|
|
249
|
+
agents to a `/credentials` page that no longer exists; it now points at
|
|
250
|
+
`authorize_connector` for recovery options and at the connection in the
|
|
251
|
+
operator UI (#551).
|
|
252
|
+
- Compact schema rendering accumulated every member's text of an oversized
|
|
253
|
+
composite before checking it against the byte budget — megabytes of
|
|
254
|
+
transient text for a wide schema with long descriptions. It now stops
|
|
255
|
+
consuming members once the budget cannot be met; output within budget is
|
|
256
|
+
unchanged (#558).
|
|
257
|
+
|
|
5
258
|
## 0.24.4 — 2026-09-17
|
|
6
259
|
|
|
7
260
|
The operator UI is the release. Its connections page is now a summary line and
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
function deepFreeze(value) {
|
|
2
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
3
|
+
Object.freeze(value);
|
|
4
|
+
for (const child of Object.values(value))
|
|
5
|
+
deepFreeze(child);
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A meta-tool input schema whose JSON Schema rendering is derived once per
|
|
11
|
+
* process, not once per request.
|
|
12
|
+
*
|
|
13
|
+
* Every `/mcp` request builds a fresh `McpServer` (stateless per request), and
|
|
14
|
+
* the SDK renders each registered tool's input schema through
|
|
15
|
+
* `~standard.jsonSchema` at registration and again for `tools/list`. Zod
|
|
16
|
+
* re-walks the whole schema on every one of those calls, which made the
|
|
17
|
+
* rendering most of a `tools/list` request's CPU. The schemas themselves are
|
|
18
|
+
* module constants — no deployment option, identity, or scoped view reaches
|
|
19
|
+
* them; what does vary (descriptions) is a string computed per registration
|
|
20
|
+
* and never passes through here — so the rendering is immutable data that can
|
|
21
|
+
* outlive a request without being request-bound.
|
|
22
|
+
*
|
|
23
|
+
* This is the SDK's own `StandardSchemaWithJSON` contract, not a patch of it:
|
|
24
|
+
* validation delegates to zod unchanged, so a tool call is checked exactly as
|
|
25
|
+
* before, and the JSON Schema is zod's own output for the same target,
|
|
26
|
+
* memoized and deep-frozen so no request can alter what the next one sees.
|
|
27
|
+
* Conversions with library options are passed through uncached; nothing in
|
|
28
|
+
* the SDK sends them.
|
|
29
|
+
*/
|
|
30
|
+
export function advertisedSchema(schema) {
|
|
31
|
+
const standard = schema["~standard"];
|
|
32
|
+
const rendered = new Map();
|
|
33
|
+
const convert = (io) => (options) => {
|
|
34
|
+
if (options.libraryOptions !== undefined) {
|
|
35
|
+
return standard.jsonSchema[io](options);
|
|
36
|
+
}
|
|
37
|
+
const key = `${io}:${options.target}`;
|
|
38
|
+
let json = rendered.get(key);
|
|
39
|
+
if (json === undefined) {
|
|
40
|
+
json = deepFreeze(standard.jsonSchema[io](options));
|
|
41
|
+
rendered.set(key, json);
|
|
42
|
+
}
|
|
43
|
+
return json;
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
"~standard": {
|
|
47
|
+
version: 1,
|
|
48
|
+
vendor: standard.vendor,
|
|
49
|
+
validate: (value) => standard.validate(value),
|
|
50
|
+
jsonSchema: { input: convert("input"), output: convert("output") },
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|