Skip to content

Releases: zackbart/connecta

0.26.2

Choose a tag to compare

@zackbart zackbart released this 27 Sep 23:16
67d3ab7

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-tokens verifies v0.23 cta_… tokens and
    restores create, show-once, list, rename, and revoke in the operator UI.
    identity.accessTokenManagement explicitly 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

Choose a tag to compare

@zackbart zackbart released this 27 Sep 01:54
484b6d5

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=continue or ?mode=restart. restart, still the default
    when mode is absent, creates a new epoch and clears the grant and
    discovery while retaining a matching issuer-bound client registration. continue hands 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 carries reused. A continue that reused a
    URL or found the connection healthy leaves the cached catalog alone. Any
    other mode is a 400. After a publicUrl change, continue keeps a client
    registered for the old callback, which the authorization server refuses;
    restart recovers. ConnectorStatus gains the optional
    authorizationReused, set by remoteMcp()'s startAuth.

Changed

  • Pending authorization URLs expire for reuse. A non-forced startAuth,
    including authorize_connector without force, 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, and invalidateCredentials("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 follow branding.theme and 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, uses branding.productName in its copy,
    and folds the agent fix prompt under "Details for the operator". Refusals
    stay byte-identical across connectors and paths.

  • A request whose Accept names text/html gets a themed 404 page; every
    other client keeps the plain Not Found body.

  • Markdown artifacts take the deployment's resolved scheme and tokens instead
    of the OS palette, and drop an opening # Heading that 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 use branding.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/data no 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

Choose a tag to compare

@zackbart zackbart released this 25 Sep 05:51
49d4d31

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, including call_destructive_tool and 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_mismatch otherwise, 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) fails execution_interrupted
    rather than replaying reads it never journaled. Every error from a resumed
    play carries writes counts, 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 expires execute.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.resumableWrites defaults to whether the
    storage has compareAndSet; true without it refuses to construct.
    /health reports resumableWrites, and connecta doctor expects the eight
    tools and says when resumable writes are off. See code mode "Pausing and
    resuming" (W1–W11, X12).

  • Config approval exemptions (#566). execute.approval maps connector ids
    and connector.tool addresses 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 own approval: "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 row approval: "exempt", call_tool still refuses it, and no
    downstream annotation can grant it. An unknown connector id, or an api()
    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. ActivityOutcome gains paused and
    approved, both with zero attempts; ActivityCallSource gains
    resume_execution; ToolCallActivityEvent gains an optional approval
    ("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 an approval column; 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 any KVStorage with compareAndSet and list — 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: one id="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.ts for 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 an artifacts connector 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, and run_refresh.
    Version-changing writes name their expected base, and every write records
    its actor from the request's authorization. The connector skips approval, except for run_refresh,
    inside execute_code through its own approval: "never" — execute.approval: { artifacts: "ask" } turns that off — while
    still spending the write budget and recording activity; call_tool still
    refuses it. The connector serves a publishing guide as
    connector:artifacts, required before the first write. An optional
    renderCheck hook 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 needs publicUrl, and a configured connector named artifacts
    refuses to construct.

  • Artifact library and sandboxed viewer (#563). /artifacts lists 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. With artifactOrigin, the main host redirects artifact paths and
    the dedicated host serv...

Read more

0.25.0

Choose a tag to compare

@zackbart zackbart released this 24 Sep 12:05

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: null means absent (an expired entry
    counts), next: null deletes, and a write takes a TTL exactly as set does.
    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
    KVStorage with compare-and-set and TTL (examples/worker/src/d1-storage.ts,
    its schema in the example README, a commented STORAGE_DB binding). 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-retryable executor_failed calling the sandbox unresponsive, and its
    admission lease is released. See code mode L3.
  • TypeScript signatures in discovery. includeSchemas: "typescript" on
    search_tools and connecta.search, and format: "typescript" on
    connecta.describe, replace both schema fields with one signature — the
    function connecta.call resolves 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 stays compact: agents in the eval never chose typescript, 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 crosses call_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
    mcpServers JSON setup for /mcp and 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:agent runs headless Claude Code against six
    stateful fake MCP servers and grades the fakes' final state and call ledger,
    never the agent's prose; eval:smoke boots the Node template and the Worker
    example; eval:perf measures the root Worker bundle and request latency;
    eval:report renders a comparison against the baselines in
    eval/baselines/. None of it runs in npm run check or 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.md describes the shape and the two workerd rules
    it had to learn. effect is 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 (effect has no dependencies of its own). Effect Schema and
    HttpApi were measured and not used; zod stays 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_TOKENS rejects it with no content at all. The old layout, a
    50,000-byte preview with the get_result notice 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. A call_tool or
    call_destructive_tool result over its cap now opens with a one-line JSON
    notice (resultId, totalBytes, hint, nextAction), then the preview;
    the default calls.maxResultBytes drops 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 inside execute_code with connecta.call and filter or search it
    there; to read it in full, page with get_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, and nextAction is still the page
    handle for both.
  • get_result pages 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. maxBytes is 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 returns message and the page shows fixed
    copy for a classified problem instead. The raw text is logged as
    [connecta] connector "<id>" operator status <state>: <message>, at warn for
    failures and info for auth_required.
  • The OAuth callback page names a closed reason — denied,
    provider_error, invalid_callback, handoff_failed, or exchange_failed —
    instead of echoing the provider's error parameter or the exchange error,
    which goes to the operator log. An expired callback reads as
    invalid_callback, so refusals stay indistinguishable.
  • Faster requests. Each /mcp request builds a fresh McpServer, and zod
    re-rendered all seven meta-tool input schemas for every one — about 65% of
    tools/list CPU. 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/list 1.43 → 0.68 ms, `call_...
Read more

0.24.4

Choose a tag to compare

@zackbart zackbart released this 17 Sep 23:20
6ed3894

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, and colorScheme — set on operatorUi({ 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 unless colorScheme pins
    one. Each token is gated: a hex color, a CSS length, a plain font-family
    list, one of system/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, and auth.md; each release's changelog paragraph is the
    upgrade guidance (#552, #553).

Fixed

  • get_result paging 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

Choose a tag to compare

@zackbart zackbart released this 16 Sep 19:33
a5e840b

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. /mcp and every /mcp/<pool> path refuse a
    disallowed Origin with a fixed 403 before redirects, admission, auth, or
    preflight, as the Streamable HTTP transport requires against DNS rebinding.
    The default admits the publicUrl origin and HTTP(S) loopback at any port;
    a list replaces the default; "*" keeps unrestricted CORS. Clients that
    send no Origin are unaffected. Allowed preflight now returns 204 without
    admission or auth and echoes valid requested mcp-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 -32602 refusals map to invalid_args, and 4xx
    responses with a JSON message keep that message, bounded, without inferring
    retryability from prose. 429 stays rate_limited and 408 becomes timeout.
  • Retry-After accepts 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.md gains rows for Origin validation, deterministic
    tools/list order, the bearer challenge, SEP-2243 parameter headers, and
    trace propagation, and corrects the stale extensions row.
  • Bounded get_result stash. results.maxStashBytes (default 8 MiB)
    and results.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, so get_result decodes 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
    declare activityActorNamespace. Open deployments share one partition.
  • Sanitized unavailable detail. Unreachable downstreams may carry
    details.host (origin only) and details.code (a closed allowlist of
    network errnos, or timeout) so an outage is distinguishable from a typo
    without leaking a path, query, or credential. Activity stays payload-free.
  • fileStorage writer 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
    has close().
  • 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

  • /health no longer names connectors: drift reports are keyed by a truncated
    SHA-256 of the connector id and downstream admission is summed without ids.
    connecta doctor keeps its stale-allowlist signal.
  • Overload and shutdown JSON-RPC codes are -31001 and -31002, outside the
    reserved range, per the specification's allocation policy.
  • connecta.search and connecta.describe spend the same host-call budget as
    connecta.call; only connecta.emit is exempt (L4, M7).
  • One per-call deadline now covers catalog resolution, admission, and the
    connector call, so a hung connector fails with a catchable timeout instead
    of consuming the whole execution wall clock.
  • Compact describe caps each shape at 8,192 UTF-8 bytes and sets
    inputSchemaTruncated / outputSchemaTruncated; use format: "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 isError text 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
    Error or reach the bridge.
  • A completed destructive call was reported as a retryable failure when the
    get_result stash write failed.
    The truncated preview is returned with a
    paging-unavailable notice, activity records success, and
    result_processing_failed is 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-$ref schemas. 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 $ref expansion.
  • call_tool in MCP result mode returned {"content":[]} for a downstream
    result carrying only structuredContent; a text block is now synthesized,
    and a null structured value survives unwrapping.
  • In-program connecta.search returned offset: null and an empty page for a
    non-numeric offset, and threw a raw TypeError for a non-string query; both
    are invalid_args.
  • Caller-authored text in get_result, authorize_connector, skills, and
    search_tools refusals is bounded; a connector over 512 bytes is
    invalid_args. A failed stash read is a typed unavailable.
  • Legacy mcp-session-id DELETE 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.
  • requiredInputKeys could 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.call attempt 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 prefixItems as a tuple, marks
    dependentSchemas and if/then/else shapes conditional with the
    truncation flag, and resolves $dynamicRef like $ref.
  • The guarded transport's no-body-stream path enforces the byte ceiling on
    text() and json().
  • The pool-name timing oracle is accepted and documented; authorize_connector
    honoring tool-level grants is documented and pinned.

v0.24.2

Choose a tag to compare

@zackbart zackbart released this 16 Sep 16:40
dd854c9

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.pools declares a
    slice of connector ids and exact connector.tool addresses plus a grant
    predicate over the authenticated identity, denied by default. The endpoint
    serves the pool intersected with the identity's connectorAccess, 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 exact connector.tool address (that tool
    only); grants are additive. The scoped registry view filters below the
    catalog service, so search_tools, describe_tools, call_tool,
    call_destructive_tool, a program's connecta.search and connecta.call,
    and the connection UI all see the same list, and an ungranted tool fails as
    unknown_tool exactly 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

Choose a tag to compare

@zackbart zackbart released this 08 Sep 18:38
3c15034

Added

  • execute.maxHostCalls and execute.hostCallTimeoutMs configure the execute_code host-call budget and per-call deadline, previously fixed at 20 calls and 15 seconds. The tool description advertises the configured values.
  • A warn log 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

Choose a tag to compare

@zackbart zackbart released this 07 Sep 23:08
615a3b8

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, and activityHistory
    factories behind /ui, /credentials, and /activity, with contracts in core.
  • Separate config-derived credentialAdministration and personalConnection
    permissions, both denied by default; activityAccess controls 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 credentials with vault, 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

Choose a tag to compare

@zackbart zackbart released this 07 Sep 16:46
2acbe79

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 investigate guidance 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 handleRequest routes. 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 through connecta.call.
  • Remove connecta.batch. Use Promise.all or Promise.allSettled under 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 removed maxRetries option.
  • 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 audit script.
    The benchmark self-tests remain in CI; whole-agent comparisons run locally.

See the 0.23.0 migration guide.

Included changes: #525, #527, #528.