Releases: zackbart/connecta
Release list
0.26.2
This patch restores named client tokens as an optional auth module. Deployments
upgrading from v0.23 can keep their existing stored records and client secrets,
without rotation, by configuring accessTokens(storage) with the same storage
namespace and preserving their identity grants. The old boolean configuration
must be replaced. Deployments that omit the module are unchanged. The eight MCP
tools remain unchanged, and the Node template now pins 0.26.2.
Added
@zackbart/connecta/auth/access-tokensverifies v0.23cta_…tokens and
restores create, show-once, list, rename, and revoke in the operator UI.
identity.accessTokenManagementexplicitly permits interactive operators;
client tokens cannot administer tokens or connection credentials. New issuance
requires atomic storage and reserves capacity across concurrent instances.
Fixed
- Existing token ids, activity labels, and principal bindings survive the upgrade.
Malformed or corrupt records fail closed, including principal fields that
JavaScript regular expressions previously coerced into strings. Revocation
deletes the admission lookup before updating metadata.
Full change: #620
0.26.1
This patch fixes slow OAuth restarts, unbounded downstream authorization waits,
and operator pages that signed people out after a temporary load failure.
Connect now opens consent in one click, and browser-facing pages share the
configured theme. Agent guidance preserves one-time write results and keeps
an authorized batch in one resumable program. Approval defaults and the eight
MCP tools are unchanged. No configuration or storage migration is required;
deployments without the operator UI can ignore the page changes. OAuth starts
abort downstream work after 30 seconds, but uncancellable storage resets and
cleanup must finish before the response. Restart reuses a matching client
registration; a revoked client may still fail at consent or callback and need
another restart. The Node template now pins 0.26.1.
Added
- Continue or restart an operator OAuth start.
POST /ui/oauth/<id>
takes?mode=continueor?mode=restart.restart, still the default
whenmodeis absent, creates a new epoch and clears the grant and
discovery while retaining a matching issuer-bound client registration.continuehands back the pending
authorization URL when it was written in the last ten minutes. Otherwise it
starts a flow in the current epoch, reusing a stored registration when one
exists. A first connection still registers dynamically. A disconnected
connector resets first. A response with a URL now carriesreused. A continue that reused a
URL or found the connection healthy leaves the cached catalog alone. Any
othermodeis a 400. After apublicUrlchange, continue keeps a client
registered for the old callback, which the authorization server refuses;
restart recovers.ConnectorStatusgains the optional
authorizationReused, set byremoteMcp()'sstartAuth.
Changed
-
Pending authorization URLs expire for reuse. A non-forced
startAuth,
includingauthorize_connectorwithoutforce, reissues a pending URL only
when it is under ten minutes old. The pending URL's envelope now records its
write time, which older readers ignore. A URL written before this release,
or under a pre-epoch generation, starts a fresh flow instead. -
OAuth cleanup deletes run concurrently. A restart's epoch cleanup,
clearPending, andinvalidateCredentials("all")keep up to six deletes
in flight, the Workers limit on simultaneous connections. Every delete is
still attempted, and a falsy rejection now counts as a failure. A retired
epoch's manifest is still removed only after all of its values. Catalog
invalidation deletes the root and its chunks together under the registry's
chunk I/O bound, so a failed root delete no longer skips the chunks. -
Every page connecta renders for a person now shares one token layer and one
layout: the operator shell, the OAuth callback, a browser's 404, and the
artifact frame followbranding.themeand light or dark. The OAuth callback
names the outcome in its heading with a status mark, names the connector by
title only after the state check, usesbranding.productNamein its copy,
and folds the agent fix prompt under "Details for the operator". Refusals
stay byte-identical across connectors and paths. -
A request whose
Acceptnamestext/htmlgets a themed 404 page; every
other client keeps the plainNot Foundbody. -
Markdown artifacts take the deployment's resolved scheme and tokens instead
of the OS palette, and drop an opening# Headingthat only repeats the
title the viewer already shows. The frame waits on the viewer's surface with
a loading line, and says so if the page never arrives. -
Without the operator UI, callback and 404 pages no longer link the default
/favicon.svg, which only the UI serves. -
The operator UI starts OAuth in one click. Connect opens the provider's
page in a new tab straight away and asks the server to continue a pending
authorization rather than restart it; only Reconnect on a healthy
connection restarts, behind an in-page confirm. A tab returning to the page
quietly re-reads the status of connectors waiting on authorization. -
Operator UI notices appear in the card that caused them, in fixed
sentences rather than the route's error text; problems fixed by authorizing
or adding a credential are warnings rather than errors, with one primary
action per row. Activity and artifact states, the confirm dialogs, the
mobile masthead, and the vocabulary shown to people (outcomes, actors,
timestamps) now read the same across pages and usebranding.productName.
Fixed
- Operator OAuth starts now abort downstream discovery and registration after
30 seconds or browser cancellation, with a fixed timeout response. Every
reset already started by the request, including issuer-mismatch recovery,
drains before catalog invalidation and response so a delayed generation
write cannot replace a later flow. These uncancellable storage waits can
exceed the deadline. Disconnect still finishes after browser cancellation. - Forced OAuth restarts reuse registrations bound to the issuer, redirect URI,
client metadata, connector settings, and owner partition. Credentials are
re-sealed for the replacement epoch, and fresh discovery detects issuer
changes. Disconnect and issuer-mismatch recovery discard the registration.
The SDK cannot detect a revoked client while constructing a consent URL;
a callback refusal clears it so the next restart can register again. - Agent sampling advice now applies to reads. For a one-time write, agents
reduce the full result in the program or page a direct-call result through
get_result, without repeating the write to recover discarded output. - Authorized batches stay in one resumable program. Guidance explains how
tool-scoped approval covers repeated calls to one address while other
approval-required writes retain their own approvals. The call-scoped
default and host enforcement are unchanged. - A restart's cleanup no longer grows with every earlier restart. Each
OAuth restart re-deleted every epoch the connector had ever retired, one key
at a time, and restart 1,001 failed forever with a full cleanup backlog. A
restart now deletes the epoch it retires, retries any of the eight most
recent epochs whose cleanup failed (their manifest outlives their values),
and sweeps at most 16 epochs retired more than 24 hours ago, dropping each
from the lineage only when all of its keys are gone. A Disconnect that
reported a failed cleanup still deletes the old grant when retried. The
cleanup work stays bounded independently of earlier resets. Residue a late
writer leaves in a younger epoch stays unreadable behind the fence until the
sweep reaches it. A late writer whose cleanup fails in an epoch already
listed now restarts that epoch's grace. The accepted assumption is that no
request holds a retired epoch for a day. The lineage records retirement
times in a sibling record older releases ignore, and its cap rises to 5,000
epochs. A connector stuck at the old 1,000 wall restarts the day it
upgrades. Rolling back is clean unless a lineage has grown past 1,000. See
auth. - A failed or unreachable
/ui/datano longer signs the operator out. Only a
401 or 403 on that read returns to the token gate; anything else keeps the
page and offers Retry. A connector whose details fail to load says whether
the session, the browser's connection, or the downstream service is at
fault, and only the last offers the fix prompt.
0.26.0
0.26.0 — 2026-09-25
Programs can now write with host approval. When a program reaches a tool that
is not explicitly read-only, it pauses before the call and returns the exact
address, arguments, and a token. The new eighth MCP tool,
resume_execution, repeats that call to show the host the real write and
replays the program from its journal without resending an uncertain outcome.
tools/list now has eight tools on every deployment, so client allowlists and
connecta doctor installations pinned to 0.25 need updating. Resumable
writes turn on by default when storage supports compareAndSet (memory, file,
or the Worker example's D1 adapter); a program write that previously failed
now pauses. Cloudflare KV cannot make that claim atomically, so resumable
writes stay off there. Set execute.resumableWrites: false to silence its
startup warning. Program clocks and randomness are pinned for replay.
Artifacts arrive as an optional module with versioned JSON documents, a
sandboxed viewer and library, and scheduled refresh of one data document from
a read-only program. A deployment supplies a Node timer or Worker cron;
connecta starts no background job. Stored pages use the operator sign-in,
and a dedicated artifact origin may require signing in again because browser
storage belongs to one origin. Page scripts run with an opaque origin and no
network by default; only explicitly allowlisted script, style, and font
origins may load. Artifact version and configuration writes are approval-exempt inside
programs by default; deployment config can restore the approval prompt.
run_refresh requires host approval and is excluded from that exemption.
Deployments that omit the module need no
artifact storage, R2 bucket, cron, browser binding, or second domain.
Before upgrading a Worker deployment that uses D1 activity, add the
approval column shown below. Every admitted /mcp request, on Node and
Workers, has a five-minute total lifetime by default; set
admission.requests.maxDurationMs above any longer authorized request. The
Worker example also enables enable_request_signal for live disconnects.
To add artifacts to that example, use CAS-capable D1
storage, optionally R2 for bodies, and explicitly configure any CDN origins.
The Node template now pins 0.26.0 and shows the optional artifact timer.
ConnectorCallErrorCode gains conflict for stale artifact edits, while an
identity can opt into reviewed exact tool grants that remain visible only while
the loaded catalog classifies them read-only. The operator UI no longer
returns downstream OAuth or credential-test text in notices; callers that
read those route messages must use the fixed outcome and consult server logs
for diagnostics.
Added
-
Guarded read-only identity grants (#601). A deployment may put
{ tool: "connector.tool", requireReadOnly: true }in
identity.connectorAccess. The reviewed exact address stays visible only
while its loaded catalog explicitly says read-only without a contradictory
destructive hint. A reclassified tool disappears from discovery and every
call path, includingcall_destructive_tooland approval-exempt programs;
new names are never added automatically. Existing string grants keep their
additive meaning, and a pool can still only narrow the identity's view.
A stale catalog or a dishonest downstream annotation remains a trust limit. -
Resumable writes and
resume_execution(#565). A program's call to a
tool that is not explicitly read-only pauses the run before anything is
sent and returns{ paused: { address, args, token, expiresAt, nextAction, hint } }.resume_execution— always listed, annotated destructive — must
repeat the address and args exactly (approval_mismatchotherwise, before
anything is claimed); it claims the run by compare-and-set and replays the
program from a journal in the caller's result storage, answering every
earlier host call from its record — no catalog, permit, connector, or
activity — and sending the approved write once.approval: "tool"approves
the rest of the run's calls to that tool. Each live write is marked on the
run's header before it is sent, so racing or crashed resumes never send it
twice. A write sent but never answered — or answered with anything short
of a refusal or the downstream tool's own error — stops the run as
write_outcome_unknown, even beside a concurrent pause, and is never sent
again; a program that stops matching its journal fails
execution_diverged; a resumed play that sent writes and then ended without
pausing (a sandbox failure, a lapsed claim) failsexecution_interrupted
rather than replaying reads it never journaled. Every error from a resumed
play carrieswritescounts, and once a write landed or may have, its
advice is to check those before re-running rather than a plain re-run; a
run that sent writes keeps those counts past its deadline. Every paused-run
storage call has a deadline (the host-call deadline, at least 5 s), so
storage that stops answering fails the run rather than holding it. A paused
run survives a restart and expiresexecute.pausedRunTtlSeconds(default 1,800) after its
first pause;execute.maxWrites(default 10) caps a run's writes on top of
the host-call budget.execute.resumableWritesdefaults to whether the
storage hascompareAndSet;truewithout it refuses to construct.
/healthreportsresumableWrites, andconnecta doctorexpects the eight
tools and says when resumable writes are off. See code mode "Pausing and
resuming" (W1–W11,X12). -
Config approval exemptions (#566).
execute.approvalmaps connector ids
andconnector.tooladdresses to"never"or"ask": a program calls an
exempt write without pausing, while it still spends the write budget, is
journaled, and records activity. The address wins over the connector entry,
which wins over a connector's ownapproval: "never"default — reserved for
connectors connecta ships, such as the artifacts connector — so
"ask"switches such a default off. It works with resumable writes off
too, where an exempt write the program leaves unawaited still finishes
before the run ends, and one with an unknown outcome is the result. Exempt is never read-only: discovery keeps the tool approval-required
and marks its rowapproval: "exempt",call_toolstill refuses it, and no
downstream annotation can grant it. An unknown connector id, or anapi()
address its tools do not include, refuses to construct. The operator UI's
per-tool badge gains its third state, "exempt from approval". -
Activity for pauses and approvals.
ActivityOutcomegainspausedand
approved, both with zero attempts;ActivityCallSourcegains
resume_execution;ToolCallActivityEventgains an optionalapproval
("call"or"tool"), set only on an approval. All three are enums — the
approved arguments are never recorded. The Worker example's D1 activity
store writes anapprovalcolumn; add it with
ALTER TABLE tool_call_activity ADD COLUMN approval TEXT;before deploying
the updated adapter. The operator activity page labels both outcomes and
says what an approval covered. -
Artifact storage and validation, at
@zackbart/connecta/artifacts
(#562).kvArtifactStore(storage, { blobs?, prefix? })keeps artifacts
in anyKVStoragewithcompareAndSetandlist— one head record per
artifact, swapped by compare-and-set, with every earlier version immutable
beside it and bodies content-addressed — and refuses Cloudflare Workers KV
at construction, because a write that cannot compare-and-set its head can
lose a teammate's edit.validateArtifact({ kind, source, documents? })is
the static check every save runs: oneid="artifact-root", no frames,
plugins, forms, or<base>, external scripts only from explicitly
allowlisted origins, and no relative or unapproved network resource URLs,
with a line number and a fix in every message. The Worker example gains
r2-artifact-blobs.tsfor keeping bodies
in R2 beside a D1 store. The subpath adds no dependency and no Effect. -
The built-in artifacts connector (#562, #564).
createConnecta({ artifacts: artifacts({ store }) })appends anartifactsconnector with five reads —
list_artifacts,get_artifact,get_document,validate_artifact, and
get_refresh— and nine writes:create_artifact,update_artifact,patch_artifact(exact
find/replace, every edit matching once or nothing changes),
set_documents(plural and atomic),rollback_artifact,
archive_artifact,restore_artifact,set_refresh, andrun_refresh.
Version-changing writes name their expected base, and every write records
its actor from the request's authorization. The connector skips approval, except forrun_refresh,
insideexecute_codethrough its ownapproval: "never"—execute.approval: { artifacts: "ask" }turns that off — while
still spending the write budget and recording activity;call_toolstill
refuses it. The connector serves a publishing guide as
connector:artifacts, required before the first write. An optional
renderCheckhook receives the exact frame document and CSP a viewer will
load and can refuse a write; without one, static validation is the floor.
The module needspublicUrl, and a configured connector namedartifacts
refuses to construct. -
Artifact library and sandboxed viewer (#563).
/artifactslists pages;
/artifacts/<id>opens one after the same inbound sign-in as the operator
UI. A viewer snapshot pins exact view and document versions. Page HTML runs
in an opaque-origin frame with no fetch or worker access. A deployment may
allow exact CDN origins for scripts, styles, and fonts; none are allowed by
default. WithartifactOrigin, the main host redirects artifact paths and
the dedicated host serv...
0.25.0
0.25.0 — 2026-09-23
The Effect core is the release. Admission, deadlines, invocation,
execute_code, discovery, the registry, remote MCP connections, OAuth refresh,
and the request pipeline now run on Effect v4 behind the same Promise API:
connectors, createConnecta, and every shipped declaration name no Effect
type, and no connector code changes. The rewrite paid for itself in lifetimes —
about a dozen bugs where a permit, a lease, or a wait outlived the request that
owned it, each listed under Fixed — and the release also seals downstream OAuth
state, stops treating a dead refresh grant as an outage, and bounds results
below what clients cut off. What breaks: effect is a new hard dependency,
pinned to exactly 4.0.0-rc.117 (about 53 MB installed, +44 KB gzip on the
root bundle, about +20 ms of Worker cold start); calls.maxResultBytes
defaults to 24,000 instead of 50,000, and a truncated result now leads with its
notice; get_result pages are a header line plus raw text, with maxBytes
clamped to the cap; with encryptedCredentialVault(), OAuth tokens are sealed
the first time they are read, so rolling back to 0.24 means authorizing every
OAuth connector again; /ui/connectors/<id> no longer returns message; the
OAuth callback page names a classified reason instead of echoing the
provider's; an OAuth URL a downstream advertises off its configured origin must
be HTTPS on a public host; and npm run check now needs Chromium. A deployment
can ignore the rest: every new surface is optional or defaulted
(KVStorage.compareAndSet, CredentialVault.seal/open,
execute.watchdogMs, the typescript schema format), no existing option
changes meaning (the result cap changes only its default), and results stashed
by 0.24 keep paging until they expire.
Added
KVStorage.compareAndSet(key, expected, next, { ttlSeconds }). An
optional atomic claim:expected: nullmeans absent (an expired entry
counts),next: nulldeletes, and a write takes a TTL exactly assetdoes.
Memory and file storage provide it — the file store atomically under its
lock, persisted across reopen, and rolled back when the write cannot be
persisted — and the namespaced views core hands connectors forward it only
when the store underneath has it. The Worker example gains a D1-backed
KVStoragewith compare-and-set and TTL (examples/worker/src/d1-storage.ts,
its schema in the example README, a commentedSTORAGE_DBbinding). The
Cloudflare KV adapter declares none, because eventually consistent storage
cannot keep that promise. Nothing requires it; downstream OAuth uses it where
it exists.CredentialVault.seal/open. Optional members for sealing values that
are not credentials.encryptedCredentialVault()implements them with the
vault's AES-GCM key, binding the connector id, the owner, and the physical
storage key (which carries the authorization epoch) into the authenticated
data, so ciphertext copied to another connector, principal, or epoch does not
open. A vault without them draws one startup warning per OAuth connector.execute.watchdogMs. A ceiling on how long connecta waits for an
executor to settle, enforced outside the sandbox: 120,000 ms by default,
above both executors' own deadlines, with an invalid value falling back like
its sibling keys. A run still unsettled at the ceiling ends as a
non-retryableexecutor_failedcalling the sandbox unresponsive, and its
admission lease is released. See code modeL3.- TypeScript signatures in discovery.
includeSchemas: "typescript"on
search_toolsandconnecta.search, andformat: "typescript"on
connecta.describe, replace both schema fields with onesignature— the
functionconnecta.callresolves to, such as
(args: { team: string }) => Promise<unknown>— for an agent to read, never
to run; programs stay JavaScript. It rides the compact renderer's walk and
budgets, and an observed output opens with/* observed, not declared */.
The default stayscompact: agents in the eval never chosetypescript, even
when nudged. - The operator page says what each tool may do and how to fix what is
wrong. Every tool carries the path core enforces for it — "runs in
programs" for an explicitly read-only tool, "needs approval" for everything
that crossescall_destructive_tool. Every failure the page shows offers
"Copy fix prompt", fixed text chosen by a server-classified problem and the
configured connector id, with no parameter an error message, token, or URL
could travel through. The endpoint block offers Claude Code, Codex, and
mcpServersJSON setup for/mcpand for each/mcp/<pool>the viewer's
grant admits; none carries a token. - An eval harness, for contributors.
eval/drives a deployment only
through its public surface:eval:agentruns headless Claude Code against six
stateful fake MCP servers and grades the fakes' final state and call ledger,
never the agent's prose;eval:smokeboots the Node template and the Worker
example;eval:perfmeasures the root Worker bundle and request latency;
eval:reportrenders a comparison against the baselines in
eval/baselines/. None of it runs innpm run checkor ships in the
tarball.
Changed
- The core runs on Effect. Each converted module keeps its exported class
or function exactly and runs an Effect program behind it; one runner,
src/runtime/run.ts, is the only place a fiber starts, and it rethrows
connecta's own error classes and a caller's abort reason unchanged.
documentation/architecture.mddescribes the shape and the two workerd rules
it had to learn.effectis a runtime dependency at one exact version, never
a range — the version Alchemy pins, so a deployment using both resolves one
copy — and an upgrade is its own release. The cost, against 0.24.4: the root
entry 235,346 → 279,526 B gzip, the Worker example 263,949 → 314,336 B, each
provider about +36 KB,./ui+39 KB,./activity+32 KB, and about 53 MB
on disk (effecthas no dependencies of its own). Effect Schema and
HttpApiwere measured and not used;zodstays for the meta-tool inputs. - Truncated results lead with their handle, under client cutoffs. Claude
Code (2.1.280) swaps any MCP result over 50,000 characters for a spill-file
pointer and a 2,000-character preview, and past twice
MAX_MCP_OUTPUT_TOKENSrejects it with no content at all. The old layout, a
50,000-byte preview with theget_resultnotice at the tail, crossed that
line every time: agents never saw the handle, and two of three models re-ran
a billable export three times to look again. Acall_toolor
call_destructive_toolresult over its cap now opens with a one-line JSON
notice (resultId,totalBytes,hint,nextAction), then the preview;
the defaultcalls.maxResultBytesdrops to 24,000 bytes so notice plus
preview stays under 25,000; a lone text block is measured, previewed, and
paged as its text rather than a JSON-escaped content envelope; and a call not
explicitly read-only says the write already ran and must not be repeated.
Per-connector overrides and validation are unchanged, and a deployment whose
clients take more can raise the cap. A result between 24,000 and 50,000
bytes that used to arrive whole now pages. - A truncated read offers a program before paging. The notice on an
explicitly read-only call now says: to find something specific, repeat the
read insideexecute_codewithconnecta.calland filter or search it
there; to read it in full, page withget_result. It used to name paging
alone, and once the handle was visible the eval's Haiku paged a 185 KB CI
log 24,000 bytes at a time, skipped ranges or stopped after two pages, and
named the flaky test near the top in all three trials. With the new hint it
reduced the log in a program and found the real failure in three of three,
at about a third of the cost per trial. A write's notice is unchanged, since
repeating a write is what it forbids, andnextActionis still the page
handle for both. get_resultpages are raw text. A page is one text block: a JSON header
(resultId,offset,bytes,totalBytes,hasMore,nextAction), a
newline, then the page as stored, where it used to be
{ offset, nextOffset?, totalBytes, text }with every quote and newline in
the page escaped a second time.maxBytesis an upper bound clamped to the
inline cap of the call that stashed the result, recorded in the stash entry;
a larger request is answered, not refused, because agents that could finally
see the handle asked for 50,000-byte pages and Claude Code rejected every
answer. Entries stashed before the cap was recorded page at the deployment
cap.- Connector status text stays in the server log. A status message can quote
a downstream error body, and that body can quote the secret it rejected, so
/ui/connectors/<id>no longer returnsmessageand the page shows fixed
copy for a classifiedprobleminstead. The raw text is logged as
[connecta] connector "<id>" operator status <state>: <message>, at warn for
failures and info forauth_required. - The OAuth callback page names a closed reason —
denied,
provider_error,invalid_callback,handoff_failed, orexchange_failed—
instead of echoing the provider'serrorparameter or the exchange error,
which goes to the operator log. An expired callback reads as
invalid_callback, so refusals stay indistinguishable. - Faster requests. Each
/mcprequest builds a freshMcpServer, and zod
re-rendered all seven meta-tool input schemas for every one — about 65% of
tools/listCPU. They are now derived once and memoized;tools/list
bodies are byte-identical. On the eval's Node deployment, p50 against 0.24.4:
tools/list1.43 → 0.68 ms, `call_...
0.24.4
The operator UI is the release. Its connections page is now a summary line and
one row per connector instead of a wall of expanded cards, and the stylesheet
behind it resolves through design tokens a deployment can set with the new
branding.theme. Nothing a deployment configures today changes meaning, no
storage format moved, and the page shows exactly what it showed before under
the same gates — an operator who has the page bookmarked will find it reads
differently, and that is the whole of the upgrade. The documentation cut to
four guides lands here too, along with one execution-path fix.
Added
branding.theme. Five tokens —accent,radius,fontFamily,
monoFamily, andcolorScheme— set onoperatorUi({ branding }). Every
other color on the operator page is mixed from those. Light and dark are the
same tokens and follow the operator's OS setting unlesscolorSchemepins
one. Each token is gated: a hex color, a CSS length, a plain font-family
list, one ofsystem/light/dark. A rejected value takes the default and
gets named in a startup warning (#554).
Changed
- The operator UI reads as a dashboard. One summary line, then one row per
connector; the description, permission line, OAuth actions, credential panel,
diagnostics, drift, and tool list are behind a row an operator expands. Same
payload, same gates (#554). - The documentation is four guides.
architecture.md,meta-tools.md,
code-mode.md, andauth.md; each release's changelog paragraph is the
upgrade guidance (#552, #553).
Fixed
get_resultpaging cost. A page reads and decodes only the chunks it
covers rather than the whole stash. Offsets,nextOffset,totalBytes, and
character-boundary alignment are unchanged, and entries stashed in the
previous formats stay readable for the rest of their 15-minute TTL (#540).
Full changelog: https://github.com/zackbart/connecta/blob/v0.24.4/CHANGELOG.md
v0.24.3
A bug-fix release from a full audit of the execution path, invocation, catalog
rendering, downstream OAuth, and the route table, plus the one MCP 2026-07-28
requirement the previous inventory had missed. Two changes can be felt by a
deployment: /mcp now validates the browser Origin header, so a browser MCP
client hosted on an origin other than publicUrl or loopback needs
allowedOrigins; and the overload and shutdown JSON-RPC error codes moved
from -32001/-32002 to -31001/-31002. Everything else is a fix a
deployment can take without action. No storage format changed, but
fileStorage now refuses a second process on the same state file, which a
deployment sharing one file between two processes was never safe doing.
Added
allowedOrigins./mcpand every/mcp/<pool>path refuse a
disallowedOriginwith a fixed 403 before redirects, admission, auth, or
preflight, as the Streamable HTTP transport requires against DNS rebinding.
The default admits thepublicUrlorigin and HTTP(S) loopback at any port;
a list replaces the default;"*"keeps unrestricted CORS. Clients that
send noOriginare unaffected. Allowed preflight now returns 204 without
admission or auth and echoes valid requestedmcp-param-*header names.- Host-owned refresh recovery. A valid token response is a consumed
refresh token. When the request that owned a refresh is cancelled,
redirected to authorization, or invalidated before the SDK saves the
response, the coordinator persists the rotation itself, holds contenders
behind the pending-mutation marker until that write lands, and hands them
the saved rotation. A retired refresh token is never redeemed twice, and two
deterministic waves of eight overlapping scopes pin one grant per wave
(#526). - Downstream JSON-RPC
-32602refusals map toinvalid_args, and 4xx
responses with a JSON message keep that message, bounded, without inferring
retryability from prose. 429 staysrate_limitedand 408 becomestimeout. Retry-Afteraccepts the HTTP-date form as well as delta-seconds.- Personal connectors admit calls through their principal registry's own
budget instead of sharing one root budget; shutdown closes both, and
eviction never discards a live budget. records/mcp-2026-07-28.mdgains rows for Origin validation, deterministic
tools/listorder, the bearer challenge, SEP-2243 parameter headers, and
trace propagation, and corrects the stale extensions row.- Bounded
get_resultstash.results.maxStashBytes(default 8 MiB)
andresults.maxStashEntries(default 64) bound the paging stash per
runtime, with accounting that reserves capacity before concurrent writes
finish and reclaims expired entries before reuse. A refused stash keeps the
call successful with its preview and the paging-unavailable notice. - Byte-range paging. Stashed results are stored as a byte-addressable
envelope, soget_resultdecodes only the requested page instead of
re-encoding the whole result on every call. Entries stashed before the
upgrade still page until their TTL expires. - Identity-partitioned results. The stash partition derives from any
authenticated subject or principal, no longer only from providers that
declareactivityActorNamespace. Open deployments share one partition. - Sanitized
unavailabledetail. Unreachable downstreams may carry
details.host(origin only) anddetails.code(a closed allowlist of
network errnos, ortimeout) so an outage is distinguishable from a typo
without leaking a path, query, or credential. Activity stays payload-free. fileStoragewriter lock. A second instance or process opening the
same state file fails at construction naming the holder. The lock is
heartbeat-based, so a container restart that reuses a pid cannot wedge a
deployment, and each write uses a unique temp file. The returned store now
hasclose().- Logs survive every executor failure. QuickJS streams console entries
to the parent, so a program that is cancelled, killed at the deadline,
crashed, or lost to an IPC failure still returns what it printed.
Changed
/healthno longer names connectors: drift reports are keyed by a truncated
SHA-256 of the connector id and downstream admission is summed without ids.
connecta doctorkeeps its stale-allowlist signal.- Overload and shutdown JSON-RPC codes are
-31001and-31002, outside the
reserved range, per the specification's allocation policy. connecta.searchandconnecta.describespend the same host-call budget as
connecta.call; onlyconnecta.emitis exempt (L4, M7).- One per-call deadline now covers catalog resolution, admission, and the
connector call, so a hung connector fails with a catchabletimeoutinstead
of consuming the whole execution wall clock. - Compact describe caps each shape at 8,192 UTF-8 bytes and sets
inputSchemaTruncated/outputSchemaTruncated; useformat: "json"for
the exact schema. - Top-level discovery measures the complete tool result, both copies and JSON
escaping, against the 256,000-byte ceiling. - Downstream
isErrortext is bounded at 512 UTF-8 bytes, and error framing
fits the call's result cap in both result modes. - Open deployments warn whenever any connector is configured, since
api()
headers can carry secrets without declaring credential hooks.
Fixed
- A long downstream error could leak the sandbox's per-run secret to guest
code and let a program forge a typed failure. The QuickJS bridge sliced a
rejected host call at 4,000 characters, which clipped the authenticated
failure frame mid-JSON. Details are bounded before framing, the bridge
refuses an oversized frame whole, the prelude hides a malformed frame, and
the raw transport lives in a private closure so guest code cannot swap
Erroror reach the bridge. - A completed destructive call was reported as a retryable failure when the
get_resultstash write failed. The truncated preview is returned with a
paging-unavailable notice, activity records success, and
result_processing_failedis never retryable. - The OAuth refresh gate wedged permanently when a token fetch succeeded
but the SDK's save never ran, answering 503 to every later refresh in the
isolate until a forced reauthorization. - Compact schema rendering went quartic on
allOf-of-$refschemas. A
3.5 KB downstream schema took over four seconds of synchronous CPU, and the
describe path produced a 1.9 MB string from 1.8 KB. Rendering now spends a
shared 2,000-visit budget with memoized$refexpansion. call_toolin MCP result mode returned{"content":[]}for a downstream
result carrying onlystructuredContent; a text block is now synthesized,
and anullstructured value survives unwrapping.- In-program
connecta.searchreturnedoffset: nulland an empty page for a
non-numeric offset, and threw a rawTypeErrorfor a non-string query; both
areinvalid_args. - Caller-authored text in
get_result,authorize_connector,skills, and
search_toolsrefusals is bounded; aconnectorover 512 bytes is
invalid_args. A failed stash read is a typedunavailable. - Legacy
mcp-session-idDELETE now runs on credential rotation, generation
change, disconnect, and abandoned connects, not only on scope close. - Pool names outside
[a-z0-9_-]fell through to a generic 404 without CORS
or authentication; every/mcp/suffix now reaches the identical pool 404
after auth. - The absent-grant warning set was unbounded and keyed by caller-derivable
text; it is capped at 1,024 entries. requiredInputKeyscould name keys the schema did not declare.- Validation error detail embedded whole enums; drift digests recursed without
a depth bound; refresh token responses were read without a byte ceiling;
credential revision races could loop without bound. - Containment matching for escaped failures ignores messages under eight
characters, and a QuickJS host-result reply that cannot be serialized settles
the call instead of hanging until the wall deadline. - Every
connecta.callattempt spends one host call on entry, so unknown
addresses cannot loop for free; the escaped-failure list keeps the most
recent 64; an empty terminal error string is a failure, not a success. - The compact renderer renders
prefixItemsas a tuple, marks
dependentSchemasandif/then/elseshapes conditional with the
truncation flag, and resolves$dynamicReflike$ref. - The guarded transport's no-body-stream path enforces the byte ceiling on
text()andjson(). - The pool-name timing oracle is accepted and documented;
authorize_connector
honoring tool-level grants is documented and pinned.
v0.24.2
connectorAccess can now grant individual tools, and a deployment can declare
named pools served at /mcp/<pool>. Nothing changes for a deployment that
returns "all" or connector ids and declares no pools.
Added
-
Named tool pools at
/mcp/<pool>.ConnectaConfig.poolsdeclares a
slice of connector ids and exactconnector.tooladdresses plus agrant
predicate over the authenticated identity, denied by default. The endpoint
serves the pool intersected with the identity'sconnectorAccess, so it can
only narrow. An undeclared name, a refusing grant, and a throwing grant are
one identical 404. Misdeclared pools refuse to boot. Clerk's 401 challenge
and protected-resource metadata follow the pool path so OAuth discovery
matches the URL the client used. Ethos records the decision. -
Tool-level grants in
identity.connectorAccess. Entries may be a
connector id (every tool) or an exactconnector.tooladdress (that tool
only); grants are additive. The scoped registry view filters below the
catalog service, sosearch_tools,describe_tools,call_tool,
call_destructive_tool, a program'sconnecta.searchandconnecta.call,
and the connection UI all see the same list, and an ungranted tool fails as
unknown_toolexactly like an absent one. There is no wildcard: a remote
catalog that drifts cannot widen a grant. An address the catalog lacks is
unreachable and warned once per isolate. An unparseable entry refuses the
request with 403 rather than failing open.
0.24.1
Added
execute.maxHostCallsandexecute.hostCallTimeoutMsconfigure theexecute_codehost-call budget and per-call deadline, previously fixed at 20 calls and 15 seconds. The tool description advertises the configured values.- A
warnlog line for every failed connector call, carrying the connector, tool, source, error code, attempts, duration, and a bounded downstream message. Activity rows remain payload-free.
See CHANGELOG.md.
connecta 0.24.0
Deployments now select UI, encrypted credentials, activity history, and inbound
auth through explicit module imports. Core keeps discovery, execution,
invocation, and enforcement together. This breaks configuration and removes
Connecta-issued client tokens; migrate those clients before upgrading. Shared
and personal auth management now require explicit permissions. Existing vault
and OAuth state need no format migration. See the
detailed migration guide
for before-and-after configuration, team and personal deployment examples,
client migration, and verification.
Added
- Optional
operatorUi,encryptedCredentialVault, andactivityHistory
factories behind/ui,/credentials, and/activity, with contracts in core. - Separate config-derived
credentialAdministrationandpersonalConnection
permissions, both denied by default;activityAccesscontrols history reads. - Explicit
logger: "silent", independent of activity recording.
Changed
- Move configured bearer authentication to
/auth/bearer. Remove Connecta-issued
tokens and their management routes; old records remain inert in storage. - Move root branding into UI options, replace
credentialswithvault, and
construct activity through its factory. Removed configuration fails at startup. - Put credential and OAuth controls inside Connections; remove separate
Credentials and Tokens tabs. Show the current user's effective permissions. - Keep optional implementations outside the root import graph. Omitted modules
contribute no runtime work or UI routes; OAuth callbacks remain in core.
Fixed
- Return the configured connection list without awaiting provider checks, then
load bounded connection details independently. Auth action feedback no longer
waits for an unrelated catalog reload. - Start OAuth only through explicit authorized actions, never status reads.
- Return unavailable credential recovery when no UI or vault is mounted, and
complete UI-free OAuth without a dead return link.
Install with npm install --save-exact @zackbart/connecta@0.24.0 after following the migration guide.
connecta 0.23.0
This release removes server-owned program views, connector HTTP routes, and
three convenience APIs. It also exposes configured account titles during
discovery and clarifies schema inspection and dependent calls.
Programs use canonical tool addresses and JavaScript promises; callers own
retry timing. Stored programs using shortcut globals, connecta.batch, or
connecta.ui need migration. Direct calls must omit maxRetries. Deployments
using none of these need no configuration or storage changes. The seven tools,
operator pages, credentials, and emitted media remain. Cloudflare Global API
Key authentication and multi-field credentials remain supported.
Added
- Optional
investigateguidance for purchase verification, experiments, and
customer or deployment investigations. It explains account selection,
evidence requirements, and when to stop with an unresolved gap (#527). - Bounded configured account titles in the initial connector inventory and
program search results, without provider calls or changes to ranking (#527).
Changed
- Remove connector
handleRequestroutes. Stale declarations fail at
construction; custom routes belong to the existing deployment fetch handler. - Remove MCP Apps rendering, its HTML shell, resource handlers, extension
declaration, and tool UI metadata. Clients render returned data. - Remove connector shortcut globals and their sanitization and collision rules.
Programs call the canonical address throughconnecta.call. - Remove
connecta.batch. UsePromise.allorPromise.allSettledunder the
same host-call and connector admission limits. - Remove automatic retries and backoff timing from direct calls. Failures keep
their classification and provider retry hint. Direct-call schemas reject
unknown arguments, including the removedmaxRetriesoption. - Clarify JSON schema inspection, provider result shapes, and promise failures.
Keep discovery and calls together when schemas suffice; allow a small sample
for unfamiliar results before continuing in another program (#527).
Fixed
- Remove the stale CI invocation of the retired evaluation
auditscript.
The benchmark self-tests remain in CI; whole-agent comparisons run locally.
See the 0.23.0 migration guide.