@zackbart/connecta 0.29.0 → 0.30.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/CHANGELOG.md +67 -0
- package/README.md +123 -134
- package/bin/connecta.mjs +13 -2
- package/bin/version.mjs +1 -1
- package/dist/argument-redaction.js +230 -0
- package/dist/auth-elicitation.js +14 -9
- package/dist/authorization-handoff.js +111 -0
- package/dist/catalog-cache.js +2 -2
- package/dist/catalog-service.js +103 -63
- package/dist/config.d.ts +2 -0
- package/dist/config.js +12 -7
- package/dist/connectors/guarded-fetch.js +3 -3
- package/dist/connectors/negotiation-cache.js +1 -1
- package/dist/downstream-input-context.js +9 -0
- package/dist/downstream-input.js +13 -11
- package/dist/errors.d.ts +36 -12
- package/dist/errors.js +19 -9
- package/dist/execute.js +375 -43
- package/dist/executor-result.js +11 -9
- package/dist/executors/quickjs-child.js +12 -5
- package/dist/executors/quickjs-protocol.js +5 -6
- package/dist/executors/quickjs-runtime.js +6 -1
- package/dist/executors/quickjs.js +20 -4
- package/dist/guest-runtime.js +52 -2
- package/dist/guest-types.d.ts +47 -5
- package/dist/index.d.ts +1 -1
- package/dist/invocation.js +40 -14
- package/dist/meta-output.js +0 -15
- package/dist/meta-tools.js +166 -95
- package/dist/oauth-handoff.js +88 -11
- package/dist/program-result.js +5 -0
- package/dist/program-writes.js +5 -1
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/request-token.js +83 -0
- package/dist/providers/infisical/index.d.ts +7 -0
- package/dist/providers/infisical/index.js +713 -0
- package/dist/providers/infisical/skill.generated.js +8 -0
- package/dist/registry.d.ts +8 -6
- package/dist/registry.js +134 -61
- package/dist/resource-read.js +4 -1
- package/dist/routes/mcp.js +11 -2
- package/dist/sent-secrets.d.ts +54 -13
- package/dist/sent-secrets.js +770 -231
- package/dist/skills.js +26 -16
- package/dist/sqlite.js +21 -6
- package/dist/storage/keys.js +20 -5
- package/dist/storage/memory.js +9 -4
- package/dist/storage/sql.js +18 -9
- package/dist/types.d.ts +17 -3
- package/dist/usage-guide.js +49 -40
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +2 -1
- package/documentation/README.md +63 -0
- package/documentation/architecture.md +121 -89
- package/documentation/auth.md +138 -5
- package/documentation/code-mode.md +224 -107
- package/documentation/deploying.md +146 -0
- package/documentation/integrating.md +159 -0
- package/documentation/meta-tools.md +135 -30
- package/documentation/operating.md +135 -0
- package/documentation/operator-tests.md +4 -0
- package/documentation/operator-ui.md +132 -0
- package/documentation/provider-migration-0.29.md +5 -0
- package/examples/worker/README.md +7 -0
- package/package.json +7 -1
- package/templates/node/README.md +7 -0
- package/templates/node/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,73 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.30.0 — 2026-10-09
|
|
6
|
+
|
|
7
|
+
Connecta 0.30.0 makes large results, sign-in and rich output dependable for agents, on both direct calls and programs. The six-tool surface from 0.29.0 stays. We measured the alternative, folding everything into `execute_code`, against it on Sonnet 5.5 and GPT-6-Luna (#765). The code-only surface scored lower and made a duplicate billable write, so it was not adopted.
|
|
8
|
+
|
|
9
|
+
Default `call_tool` and `call_destructive_tool` results now put their data in `content` and no longer send `structuredContent` or advertise `outputSchema`. Clients that prefer structured content, Claude Code among them, previously saw only a `{"format":"json"}` stub. Read the format from `_meta["dev.connecta/format"]`. For a truncated result, parse the first line of the text as a JSON notice. `resultMode: "value"` keeps the full structured envelope.
|
|
10
|
+
|
|
11
|
+
Programs no longer lose large data. A `connecta.call` result over the executor's inline cap is rebuilt from stash pages up to 1 MiB, and larger results come back as a handle for `connecta.result`. A program that returns more than 24,000 characters gets a pageable `resultId` in place of a truncated preview. If that run made a write, the notice says the write already ran. Paging checks the caller's connector grants on every page. Programs can forward images and other rich content with `connecta.emit(result)` or `result.content`, within the existing byte and block limits.
|
|
12
|
+
|
|
13
|
+
Downstream-OAuth handoff links use compact, opaque tokens that agents copy intact; links issued before the upgrade keep working until they expire. Auth failures on direct calls, program calls and catalog search now carry the sign-in link or operator instructions themselves, so agents no longer need a separate `authorize_connector` step. URL elicitation is unchanged.
|
|
14
|
+
|
|
15
|
+
Values submitted in JSON-Schema `writeOnly` argument fields are now treated like credentials. They are redacted from downstream result text, error text, program output and stored pages, with bounded work and memory. Values under 8 characters are replaced only where a structured field equals them, and prose containing them is withheld. Credential redaction of very large or deeply nested results no longer risks exhausting memory.
|
|
16
|
+
|
|
17
|
+
Custom `KVStorage` adapters must honor the new `expiresAtMs` option on `set` and `compareAndSet` and declare `capabilities: { absoluteExpiry: true }`. Construction rejects adapters without that declaration. The built-in memory, SQLite and D1 drivers already comply. Stash capacity charges now use storage time and hold until orphaned chunks expire, so a skewed or failing host can no longer over-admit stashes. The new maintained Infisical provider lets deployments manage projects, folders and secrets with machine-identity Universal Auth and no hand-written connector.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Add a maintained Infisical REST provider for machine-identity Universal Auth, projects, folders, and secret reads and writes. Tokens stay in memory and refresh before expiry, lists omit values by default, writes omit values and expose pending approvals, and credential-free OpenAPI checks cover every endpoint. Value-free results, including imports and approvals, return only validated identifiers and omit comments, descriptions, free-form names, reminder notes, and arbitrary nested objects. Project slugs are withheld because Infisical can derive them from free-text project names. Write recovery echoes omit secret values, secret comments, and folder descriptions without changing dispatched arguments. Comments are available only from explicit value reads. `metadataOmitted` reports withheld or unavailable metadata. This protects against careless human data entry on an honest server; adversarial encodings by a malicious server are out of scope. Known, accepted residual risk: environment slugs are returned as required routing identifiers. Infisical's UI derives them from environment names by default, so secrets entered in those names can appear in returned slugs. Operators must not put secrets in environment names or slugs. Secrets placed in identifiers or key names are out of scope. Submitted-value matching and request-secret registration are removed, preserving later program arguments and repeated writes with the same value. Rejected tokens never automatically resend writes; reconcile before an explicit retry. Existing `clientId`/`clientSecret` credentials work when the connector id is preserved. Core uncertainty and retry envelopes omit nested and composed `writeOnly` fields, withhold argument echoes when sensitivity cannot be resolved, and mark partial or withheld echoes with `argsRedacted: true`. Retry using the original arguments. Guarded base-URL validation refuses invalid configuration without quoting its value.
|
|
22
|
+
- Add an eval-only code arm, explicit route/outcome grading, offline baseline regrading and a paired A/B report with a pre-registered correctness and write-safety decision rule. Both agent runners assert the selected tool list; Claude also supports Haiku 5.5.
|
|
23
|
+
- Forward MCP rich content from programs with `connecta.emit(result)` or `result.content`, preserving native blocks and metadata within the existing paging and emission limits. Snapshot retained native blocks within byte, node, and depth limits before paging can yield so later connector mutations cannot change their content or bypass emission limits. Page oversized structured data before inspecting native presentation, and refuse cyclic or over-budget presentation without retaining it.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Auth failures on direct calls, program calls, and catalog search/describe now include the authorization URL or operator credential instructions that `authorize_connector` returns. Each call or program run reuses one handoff per connector, without forcing reauthorization. Programs can read typed recovery through `error.data` as well as `error.details`; URL elicitation is unchanged.
|
|
28
|
+
- Separate the human product overview from agent task documentation. Route endpoint operation, deployment, integration, and repository maintenance through an agent index, preserve technical contracts and existing section links, and move the operator UI contract into its own guide.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Read Vercel's complete public MCP inventory from its linked category references, with bounded credential-free fetching and explicit unavailable findings for incomplete documentation.
|
|
33
|
+
|
|
34
|
+
Reconcile category URLs discovered across the entire index with published counts and category tool headings before comparing drift. Unrecognized formatting reports unavailable instead of silently omitting tools, and unused unrelated reference definitions do not block the inventory.
|
|
35
|
+
|
|
36
|
+
Resolve relative category destinations before requiring a count on each category link, exclude CommonMark code examples, and require the tool's known category to reconcile before reporting a removal. Disappearing categories require review, while unrelated count prose does not block the check.
|
|
37
|
+
- **Breaking:** Book each result stash reservation once with a finite deadline of booking time
|
|
38
|
+
plus a 30-second write budget plus the chunk TTL, normally 15 minutes. Stop the
|
|
39
|
+
write loop on timeout and return no paging ID. Absolute chunk and ledger expiry
|
|
40
|
+
prevents late or ambiguous writes from extending that bound. Custom KVStorage
|
|
41
|
+
adapters must honor the new `expiresAtMs` option on set and compareAndSet and
|
|
42
|
+
declare `capabilities: { absoluteExpiry: true }`; construction rejects legacy
|
|
43
|
+
adapters without that explicit opt-in. Rejected write responses retain the
|
|
44
|
+
original reservation because their dispatched operation may commit later.
|
|
45
|
+
Settlement and cleanup shorten or remove charges when possible; after failure
|
|
46
|
+
or exhausted retries, capacity may be over-held until the booked deadline.
|
|
47
|
+
Bound each cleanup/release or settlement path to 15 seconds, including storage
|
|
48
|
+
I/O, and abandon further work when its budget expires so stalled operations
|
|
49
|
+
cannot hang the caller after the write timeout.
|
|
50
|
+
Remove durable completion receipts and their recovery path.
|
|
51
|
+
State-file import and `migrate-state` report database failures with fixed
|
|
52
|
+
step descriptions, preventing SQLite trigger errors from printing imported
|
|
53
|
+
values (#726).
|
|
54
|
+
- Serve identical eval code-arm usage guidance through text, structured content, resource aliases and guest skills. Require clean matching runtime and task/harness provenance before evaluating the registered A/B decision; explicit mismatch diagnostics mark decisions NON-COMPARABLE.
|
|
55
|
+
- Require strict per-trial model, CLI, task and deadline provenance before the eval A/B decision. Count timeout, interruption and runner errors as failures regardless of cached status, report failure kinds per arm, and derive native code-arm skill manifests from the exact served guide bytes.
|
|
56
|
+
- Clarify the cross-connector join eval to select the highest-MRR customer with an open bug, and accept value-mode retained-result notices in the result-paging grader while preserving same-ID paging and single-fetch checks.
|
|
57
|
+
- **Breaking:** Keep default direct-call data and truncation previews visible in clients that prefer structured content. Successful `call_tool` and `call_destructive_tool` results with `resultMode` omitted or set to `"mcp"` now omit `structuredContent`, including its former `format` and paging fields. These tools also stop advertising `outputSchema`.
|
|
58
|
+
|
|
59
|
+
Migrate clients to read result data from `content` and format from `_meta["dev.connecta/format"]` instead of `structuredContent.format`. For truncated default-mode results, parse the first line of the content text as a JSON notice to read `resultId`, `nextOffset`, and `nextAction`, then page with `connecta.result`. Clients that need a structured envelope can request `resultMode: "value"` and keep reading `structuredContent.format` and the paging notice in `structuredContent.data`. Remove assumptions that either direct-call tool advertises an `outputSchema`; use content handling or the value-mode envelope instead. Value-mode and error envelopes retain their complete JSON mirrors.
|
|
60
|
+
- Require storage-side proof of expiry before pruning stash capacity charges, so an ahead caller clock cannot admit another stash while charged chunks remain readable.
|
|
61
|
+
- Make stash charge deadline tests deterministic under runner load across memory, SQLite, and D1.
|
|
62
|
+
- Page oversized call results inside programs through the existing result stash. Reconstruct bounded values across executors and return pageable handles for larger results, keeping completed writes successful and avoiding duplicate downstream calls. Preserve U+FEFF at result and page starts in program reconstruction and direct paging. Report unpageable oversized raw text containing unpaired UTF-16 surrogates without silently replacing its code units.
|
|
63
|
+
- Issue compact downstream-OAuth handoff URLs with opaque, unpadded base64url tokens so agents can copy them intact. Preserve exact signature verification, all identity and lifetime checks, and pre-upgrade links until their original expiry. Authorization guidance now tells agents to copy the URL unchanged and request a fresh link when it is invalid or expired.
|
|
64
|
+
- Stash oversized execute_code returns behind a pageable resultId, preserving the full redacted value without repeating completed writes. Share direct-call expiry, authority checks, page caps, and capacity limits; keep bounded truncation when paging is unavailable. Skill and catalog reads retain their grant dependencies through subsequent return stashes, including caught resolution failures and recovery metadata. Local guides follow connector visibility under exact-tool grants; downstream skills and resources retain whole-connector enforcement. Write warnings reflect only writes completed during the current run.
|
|
65
|
+
- Count Claude eval runner cleanup after successful conversation completion as a normal exit in paired comparisons.
|
|
66
|
+
Accept numeric 143 or signal SIGTERM only after successful completion and a harness SIGTERM sent before any observed child exit.
|
|
67
|
+
|
|
68
|
+
### Security
|
|
69
|
+
|
|
70
|
+
- Redact submitted `writeOnly` argument values, including property names inside private objects and their encoded forms, from downstream results, errors, resource reads, connector usage guides, downstream skill files, program returns, logs, and emits before they reach agents, guest programs, or result paging storage. JSON results stay valid, with matching numbers and booleans replaced by a placeholder string, and nested JSON strings are decoded and checked. Short private values redact only equal structured fields and withhold prose that contains them, so identifiers and approval state remain; empty strings are exempt. Matching costs amortized constant work per scanned character regardless of how many values are registered, plus one budgeted confirmation per length where a long value's prefix appears; the matcher's memory, including at most 16 Ki runs of waiting confirmations per scanned text, is bounded across every credential and private value, and text that needs more work or runs is withheld. Redaction walks results iteratively and copies only what changes, so an oversized or deeply nested downstream result can no longer exhaust the heap or stack before paging, and one bounded work budget per redaction withholds only the affected field.
|
|
71
|
+
|
|
5
72
|
## 0.29.0 — 2026-10-08
|
|
6
73
|
|
|
7
74
|
Connecta 0.29.0 adopts the MCP 2026-07-28 request envelope, binding client capabilities and client identity to each request. `server/discover` advertises the extensions served and private cache hints. Connecta-owned access, pool and deadline refusals return JSON-RPC errors; admission codes move to `-33001` and `-33002`. Modern clients must preserve the protocol-version and declared argument headers. `tools.listChanged` is false, and `subscriptions/listen` remains unsupported.
|
package/README.md
CHANGED
|
@@ -2,149 +2,144 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
One
|
|
6
|
-
|
|
5
|
+
One connection between your agent and the services you choose.
|
|
6
|
+
|
|
7
|
+
Connecta brings your MCP servers and HTTP APIs together behind one endpoint.
|
|
8
|
+
Connect your agent once, then choose which integrations it can use. You run
|
|
9
|
+
Connecta yourself, on your own machine or infrastructure.
|
|
7
10
|
|
|
8
11
|
## The mental model
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
Ask your agent to find open issues, compare them with customer feedback, or
|
|
14
|
+
prepare a report. Connecta gives it a small set of tools for finding and using
|
|
15
|
+
the services you connected, without loading every service's tool definitions
|
|
16
|
+
into the conversation.
|
|
17
|
+
|
|
18
|
+
The agent writes a short program that runs in a sandbox beside those
|
|
19
|
+
integrations. It can gather results, combine them, and return the parts that
|
|
20
|
+
answer your question. Fifty issues can become a short list grouped by owner
|
|
21
|
+
before the result reaches the conversation. The agent writes the code; you
|
|
22
|
+
ask for the work.
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
flowchart LR
|
|
26
|
+
Agent["Your agent"] <-->|"One connection"| Connecta["Your Connecta deployment"]
|
|
27
|
+
Connecta <--> Work["Work tools<br/>Linear, GitHub, Notion"]
|
|
28
|
+
Connecta <--> Business["Business services<br/>Stripe, RevenueCat, Mixpanel"]
|
|
29
|
+
Connecta <--> Yours["Your other services<br/>Remote MCP servers and HTTP APIs"]
|
|
30
|
+
|
|
31
|
+
style Connecta fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b,stroke-width:2px
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
You choose which services to connect and who can use them. Your agent keeps
|
|
35
|
+
the same connection as those services change.
|
|
12
36
|
|
|
13
|
-
|
|
14
|
-
no matter how many services sit behind it.
|
|
15
|
-
2. It writes a short JavaScript program. Connecta runs it in a sandbox next to
|
|
16
|
-
your integrations. The program can search for tools, call them, chain the
|
|
17
|
-
calls, and shape the result.
|
|
18
|
-
3. Only the answer comes back into the agent's context — not raw pages of
|
|
19
|
-
API output.
|
|
20
|
-
4. If the agent wants to change something — create, update, delete — it
|
|
21
|
-
can do that from a program in a `trusted` pool. A `read-only` pool
|
|
22
|
-
routes each write through `call_destructive_tool`. The host controls approval; trusted endpoints annotate `execute_code` as a
|
|
23
|
-
write. Pool trust defaults to `read-only`.
|
|
37
|
+
## From a question to an answer
|
|
24
38
|
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
Say you ask, "Which open issues affect our highest-value customers?" Your agent
|
|
40
|
+
can gather information from your issue tracker and customer records, combine
|
|
41
|
+
it inside Connecta, and bring back a focused answer.
|
|
27
42
|
|
|
28
43
|
```mermaid
|
|
29
44
|
flowchart TB
|
|
30
|
-
|
|
45
|
+
Question["You ask a question"] --> Plan["Your agent works out what to look up"]
|
|
46
|
+
Plan --> Gather["A program in Connecta gathers the relevant records"]
|
|
47
|
+
Gather --> Tracker["Open issues from your tracker"]
|
|
48
|
+
Gather --> Customers["Customer records from a connected service"]
|
|
49
|
+
Tracker --> Combine["The program matches, filters, and groups the results"]
|
|
50
|
+
Customers --> Combine
|
|
51
|
+
Combine --> Answer["Your agent explains the answer"]
|
|
52
|
+
|
|
53
|
+
style Combine fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b,stroke-width:2px
|
|
54
|
+
style Answer fill:#dcfce7,stroke:#15803d,color:#14532d
|
|
55
|
+
```
|
|
31
56
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end
|
|
57
|
+
The records are an example; the available information depends on the services
|
|
58
|
+
and access you have configured. Processing results inside Connecta keeps the
|
|
59
|
+
conversation focused on the answer.
|
|
36
60
|
|
|
37
|
-
|
|
61
|
+
## What you can do with it
|
|
38
62
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
Connect hosted MCP services, add an internal HTTP API, and let the agent work
|
|
64
|
+
across them in one task. Changing your connected services does not require
|
|
65
|
+
changing the agent's connection. Your deployment decides which services and
|
|
66
|
+
operations each caller can reach.
|
|
67
|
+
|
|
68
|
+
Connecta includes maintained connections for <!-- providers:start -->Basecamp, Breeze ChMS, Church Community Builder, Cloudflare, GitHub, Gmail, Google Docs, Google Drive, Google Forms, Google Sheets, Google Slides, Infisical, Linear, Mixpanel, Notion, Overflow, Planning Center, RevenueCat, Stripe, Tithe.ly, and Vercel<!-- providers:end -->.
|
|
69
|
+
Each connection has its own setup guide.
|
|
70
|
+
Notion, Vercel, and Cloudflare offer hosted MCP connections and selected HTTP
|
|
71
|
+
API capabilities. You can also connect other remote MCP servers or define the
|
|
72
|
+
operations you need from an HTTP API.
|
|
73
|
+
|
|
74
|
+
Google Workspace connections use the account your deployment assigns to each
|
|
75
|
+
signed-in person. A Workspace administrator authorizes them for your domain.
|
|
76
|
+
Gmail reads mail and writes drafts, but does not send mail. Google Drive can
|
|
77
|
+
read, write, and share files, but does not permanently delete them. Available
|
|
78
|
+
operations and account access depend on the integration and your deployment.
|
|
79
|
+
|
|
80
|
+
Large results need not fill the conversation. The agent can filter and
|
|
81
|
+
summarize them in its program, or read a large result in smaller pieces.
|
|
44
82
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
83
|
+
## Writes, credentials, and access
|
|
84
|
+
|
|
85
|
+
Connecta distinguishes reads from writes. By default, programs can read, and
|
|
86
|
+
each change uses a separate visible tool call. Your agent's host controls
|
|
87
|
+
whether it asks for approval. You can explicitly allow programs to make
|
|
88
|
+
changes too; the host then sees the program tool as a write. This choice does
|
|
89
|
+
not give a caller access to additional services.
|
|
90
|
+
|
|
91
|
+
```mermaid
|
|
92
|
+
flowchart TB
|
|
93
|
+
Change["Your agent wants to change something"] --> Choice{"Are programs allowed<br/>to make changes?"}
|
|
94
|
+
Choice -->|"Default: no"| Separate["A separate visible call for each change"]
|
|
95
|
+
Choice -->|"Explicit choice: yes"| Program["A program that can make changes"]
|
|
96
|
+
Separate --> Host["Your agent's host handles approval"]
|
|
97
|
+
Program --> Host
|
|
98
|
+
Host --> Access["Connecta checks the caller's access"]
|
|
99
|
+
Access --> Services["The connected service receives the change"]
|
|
100
|
+
|
|
101
|
+
style Host fill:#fef3c7,stroke:#b45309,color:#78350f
|
|
102
|
+
style Access fill:#e0e7ff,stroke:#4338ca,color:#1e1b4b,stroke-width:2px
|
|
56
103
|
```
|
|
57
104
|
|
|
58
|
-
|
|
105
|
+
Connecta handles integration credentials on the server. Its sandbox does not
|
|
106
|
+
receive them. Optional encrypted storage protects saved credentials. Connections can use
|
|
107
|
+
shared credentials or a person's own account, with access rules set by the
|
|
108
|
+
deployment owner. Permission to use a connection is separate from permission
|
|
109
|
+
to manage its credentials.
|
|
59
110
|
|
|
60
|
-
|
|
111
|
+
People sign in through Clerk on Node or Cloudflare Access on Workers. Machine
|
|
112
|
+
clients use tokens issued by your Connecta deployment. A deployment can serve
|
|
113
|
+
several people within one tenant, each with the access you assign.
|
|
114
|
+
|
|
115
|
+
## A place to see your connections
|
|
61
116
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
way. Google Slides reads, creates, edits, and comments on each user's decks the
|
|
77
|
-
same way.
|
|
78
|
-
Google Forms reads and edits forms and reads their responses the same way.
|
|
79
|
-
- **Let the agent work in code.** Search, chain, filter, join, and reduce
|
|
80
|
-
inside the sandbox instead of round-tripping every call through the model.
|
|
81
|
-
- **Teach undeclared result shapes by using them.** Successful read-only calls
|
|
82
|
-
retain field names and broad types in bounded runtime memory, never scalar
|
|
83
|
-
values, so later programs can project a remote MCP result its provider never
|
|
84
|
-
documented.
|
|
85
|
-
- **Keep large results usable.** Oversized direct calls return a bounded
|
|
86
|
-
preview with a handle for paging through the rest. For read-only work, the
|
|
87
|
-
notice also points the agent to reduce or search the result inside a
|
|
88
|
-
program. Discovery can show compact schemas, exact JSON Schema, or a
|
|
89
|
-
TypeScript signature to read while writing JavaScript.
|
|
90
|
-
- **Keep writes deliberate.** The registry classifies tools as reads or writes.
|
|
91
|
-
Read-only pools route writes through `call_destructive_tool`; trusted pools
|
|
92
|
-
let programs write and annotate `execute_code` as a write. The host controls
|
|
93
|
-
approval. Exact deployment classification overrides beat provider review
|
|
94
|
-
and downstream annotations.
|
|
95
|
-
- **Run it on Node or Cloudflare Workers.** The core is shared; each deployment
|
|
96
|
-
supplies its platform's executor and one store: a D1 database on Workers
|
|
97
|
-
(`@zackbart/connecta/d1`), a SQLite file on Node (`@zackbart/connecta/sqlite`).
|
|
98
|
-
The Node template also runs unchanged in Docker.
|
|
99
|
-
|
|
100
|
-
Deployments explicitly compose optional features: `operatorUi()` from
|
|
101
|
-
`@zackbart/connecta/ui`, `encryptedCredentialVault()` from `/credentials`,
|
|
102
|
-
`activityHistory()` from `/activity`, and
|
|
103
|
-
inbound authentication adapters from `/auth/*`. Omit a module and its implementation does no runtime work. Core
|
|
104
|
-
keeps connector discovery, execution, invocation, and enforcement together.
|
|
105
|
-
Both deployment shapes write configuration as `defineConfig((env) => …)`, with
|
|
106
|
-
each optional module a type-checked expression the environment switches on.
|
|
107
|
-
`createConnecta` validates it against one schema — an unknown option or an
|
|
108
|
-
unusable value refuses to boot — and `connecta.describeConfig()` returns a
|
|
109
|
-
secret-free snapshot of what the deployment runs with. With `operatorUi()` enabled,
|
|
110
|
-
`GET /ui/api/config` returns that snapshot scoped to the caller, live connector
|
|
111
|
-
facts, and the caller's grants and permissions. `CONNECTA_TOKEN=<bearer> connecta
|
|
112
|
-
doctor --config --url https://connecta.example` prints only the scoped snapshot
|
|
113
|
-
as JSON, using the same serializer. It does not run the diagnostic program.
|
|
114
|
-
|
|
115
|
-
The optional UI has Overview, Connectors, Tools, Access, Activity and
|
|
116
|
-
Config pages. Connector detail separates configuration, schemas, authentication,
|
|
117
|
-
activity and diagnostics into tabs. Overview shows health, attention items and
|
|
118
|
-
endpoint setup in a drawer; Config marks each snapshot value as default or
|
|
119
|
-
configured. Catalog descriptions and schemas render as text. Activity filters
|
|
120
|
-
stay in the URL and calls group by recorded request id. Authentication, history,
|
|
121
|
-
and token controls use the signed-in person's existing permissions.
|
|
122
|
-
Connector selection and access rules remain in deployment code.
|
|
123
|
-
|
|
124
|
-
One deployment may serve several authenticated people inside the same tenant.
|
|
125
|
-
Cloudflare Access supplies Worker identity; Node uses Clerk for human auth. Machine clients use connecta-issued `cta_`
|
|
126
|
-
access tokens. Static bearer secrets are retired. Connecta owns no accounts or groups. Shared-credential administration and personal connection
|
|
127
|
-
setup require separate explicit permissions, both denied by default. See
|
|
128
|
-
[inbound auth](./documentation/auth.md#principals-visibility-and-operators).
|
|
129
|
-
Clerk deployments enable `aud_claim_enabled` and use resource-bound JWT or
|
|
130
|
-
opaque OAuth tokens by default. An explicit `allowedOAuthClientIds` list is a
|
|
131
|
-
fallback for unbound tokens from dedicated clients. Clerk session
|
|
132
|
-
tokens authenticate operator routes only; [Clerk auth](./documentation/auth.md#clerk-oauth-tokens-and-operator-sessions)
|
|
133
|
-
describes endpoint audience validation and the configuration migration.
|
|
134
|
-
|
|
135
|
-
Connecta is not a platform, a marketplace, a policy engine, or a multi-tenant
|
|
136
|
-
service. The [principles](https://github.com/zackbart/connecta/blob/main/PRINCIPLES.md)
|
|
137
|
-
state the goals and invariants; [decision records](https://github.com/zackbart/connecta/tree/main/decisions)
|
|
138
|
-
explain past choices.
|
|
117
|
+
The optional operator UI shows connected services, available tools, access,
|
|
118
|
+
and configuration. Authorized people can connect accounts, manage credentials,
|
|
119
|
+
and issue client tokens. Optional activity history shows who called what,
|
|
120
|
+
when, and whether it worked, without recording arguments, results, or program
|
|
121
|
+
code. The UI's controls follow the signed-in person's permissions.
|
|
122
|
+
|
|
123
|
+
Your agent or developer changes the integrations and access rules in deployment
|
|
124
|
+
code. The UI helps you inspect them and manage authentication.
|
|
125
|
+
|
|
126
|
+
## Where it runs
|
|
127
|
+
|
|
128
|
+
Run Connecta locally or on a server with Node, including in Docker, or host it
|
|
129
|
+
on Cloudflare Workers. Both offer the same agent tools. Running programs on
|
|
130
|
+
Workers requires Cloudflare's Workers Paid plan.
|
|
139
131
|
|
|
140
132
|
## Getting started
|
|
141
133
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
134
|
+
Give your agent the [agent documentation](./documentation/README.md) and ask it
|
|
135
|
+
to set up Connecta with the integrations you want. Tell it where you want to
|
|
136
|
+
run it and who should have access. The setup guides cover Node, Docker, and
|
|
137
|
+
Cloudflare Workers, including connecting your agent client.
|
|
138
|
+
|
|
139
|
+
<a id="existing-client-tokens"></a>
|
|
140
|
+
|
|
141
|
+
For an existing deployment, ask your agent to follow the
|
|
142
|
+
[upgrade guidance](./documentation/deploying.md#upgrade-an-existing-deployment).
|
|
148
143
|
|
|
149
144
|
## Status
|
|
150
145
|
|
|
@@ -152,13 +147,7 @@ Built for its author's deployments first and published openly. Breaking
|
|
|
152
147
|
changes are expected before 1.0. See the [changelog](./CHANGELOG.md) and
|
|
153
148
|
[security policy](./SECURITY.md).
|
|
154
149
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
`accessTokens: true` with `accessTokens: accessTokens(storage)`, and keep the
|
|
160
|
-
same persistent storage namespace and identity/tool/pool grant rules. Enable
|
|
161
|
-
`identity.accessTokenManagement` only for the interactive operators who should
|
|
162
|
-
manage tokens. Records written by v0.23 and later move with the store: a 0.28
|
|
163
|
-
JSON state file migrates with `connecta migrate-state`, and a D1 table is
|
|
164
|
-
read as is. See the package's `documentation/auth.md` for the migration.
|
|
150
|
+
The [principles](https://github.com/zackbart/connecta/blob/main/PRINCIPLES.md)
|
|
151
|
+
state the goals and tested rules. [Decision records](https://github.com/zackbart/connecta/tree/main/decisions)
|
|
152
|
+
explain past choices. [GitHub issues](https://github.com/zackbart/connecta/issues)
|
|
153
|
+
track planned work; a plan is not a shipped feature.
|
package/bin/connecta.mjs
CHANGED
|
@@ -276,7 +276,13 @@ async function migrateState() {
|
|
|
276
276
|
}
|
|
277
277
|
const [statePath, databasePath] = args.map((arg) => resolve(process.cwd(), arg));
|
|
278
278
|
const { importStateFile, openSqlite } = await import(pathToFileURL(join(packageRoot, "dist", "sqlite.js")).href);
|
|
279
|
-
|
|
279
|
+
let database;
|
|
280
|
+
try {
|
|
281
|
+
database = openSqlite(databasePath);
|
|
282
|
+
} catch {
|
|
283
|
+
throw new Error("State migration failed: could not open the SQLite database");
|
|
284
|
+
}
|
|
285
|
+
let closeFailed = false;
|
|
280
286
|
try {
|
|
281
287
|
const result = importStateFile(database, statePath);
|
|
282
288
|
console.log(
|
|
@@ -284,8 +290,13 @@ async function migrateState() {
|
|
|
284
290
|
` (${result.kept} already present, ${result.expired} expired).`,
|
|
285
291
|
);
|
|
286
292
|
} finally {
|
|
287
|
-
|
|
293
|
+
try {
|
|
294
|
+
database.close();
|
|
295
|
+
} catch {
|
|
296
|
+
closeFailed = true;
|
|
297
|
+
}
|
|
288
298
|
}
|
|
299
|
+
if (closeFailed) throw new Error("State migration failed: could not close the SQLite database");
|
|
289
300
|
}
|
|
290
301
|
|
|
291
302
|
try {
|
package/bin/version.mjs
CHANGED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const PUBLIC = {};
|
|
2
|
+
const OMIT = { omit: true };
|
|
3
|
+
const OMITTED = Symbol("omitted argument");
|
|
4
|
+
const MAX_DEPTH = 32;
|
|
5
|
+
const MAX_SCHEMAS = 2048;
|
|
6
|
+
function object(value) {
|
|
7
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function sensitive(plan) {
|
|
10
|
+
return Object.keys(plan).length > 0;
|
|
11
|
+
}
|
|
12
|
+
function equal(left, right) {
|
|
13
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
14
|
+
}
|
|
15
|
+
function item(plan, index) {
|
|
16
|
+
return plan.prefix?.[index] ?? plan.items ?? PUBLIC;
|
|
17
|
+
}
|
|
18
|
+
/** Combine restrictions from references, siblings, and allOf without losing array indices. */
|
|
19
|
+
function merge(left, right) {
|
|
20
|
+
if (left.omit || right.omit)
|
|
21
|
+
return OMIT;
|
|
22
|
+
const names = new Set([...Object.keys(left.properties ?? {}), ...Object.keys(right.properties ?? {})]);
|
|
23
|
+
const properties = Object.fromEntries([...names]
|
|
24
|
+
.sort()
|
|
25
|
+
.map((name) => [
|
|
26
|
+
name,
|
|
27
|
+
merge(Object.hasOwn(left.properties ?? {}, name) ? left.properties[name] : PUBLIC, Object.hasOwn(right.properties ?? {}, name) ? right.properties[name] : PUBLIC),
|
|
28
|
+
]));
|
|
29
|
+
const items = mergeItems(left.items ?? PUBLIC, right.items ?? PUBLIC);
|
|
30
|
+
const prefix = Array.from({ length: Math.max(left.prefix?.length ?? 0, right.prefix?.length ?? 0) }, (_, index) => mergeItems(item(left, index), item(right, index)));
|
|
31
|
+
// Equivalent plans compare equally even when one schema spells out public prefixes.
|
|
32
|
+
while (prefix.length && equal(prefix.at(-1), items))
|
|
33
|
+
prefix.pop();
|
|
34
|
+
return {
|
|
35
|
+
...(names.size ? { properties } : {}),
|
|
36
|
+
...(prefix.length ? { prefix } : {}),
|
|
37
|
+
...(sensitive(items) ? { items } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function mergeItems(left, right) {
|
|
41
|
+
if (!sensitive(left))
|
|
42
|
+
return right;
|
|
43
|
+
if (!sensitive(right))
|
|
44
|
+
return left;
|
|
45
|
+
return merge(left, right);
|
|
46
|
+
}
|
|
47
|
+
/** Compile a bounded plan. Unknown sensitivity refuses the entire echo, never just a branch. */
|
|
48
|
+
function compile(root, unionAlternatives = false) {
|
|
49
|
+
let visited = 0;
|
|
50
|
+
const active = new Set();
|
|
51
|
+
const unresolved = () => {
|
|
52
|
+
throw new Error("Unresolved argument sensitivity");
|
|
53
|
+
};
|
|
54
|
+
function reference(ref) {
|
|
55
|
+
if (typeof ref !== "string" || !ref.startsWith("#"))
|
|
56
|
+
return unresolved();
|
|
57
|
+
const pointer = decodeURIComponent(ref.slice(1));
|
|
58
|
+
if (pointer === "")
|
|
59
|
+
return root;
|
|
60
|
+
if (!pointer.startsWith("/"))
|
|
61
|
+
return unresolved();
|
|
62
|
+
let target = root;
|
|
63
|
+
for (const encoded of pointer.slice(1).split("/")) {
|
|
64
|
+
if (/~[^01]|~$/.test(encoded))
|
|
65
|
+
return unresolved();
|
|
66
|
+
const key = encoded.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
67
|
+
if (target === null || typeof target !== "object" || !Object.hasOwn(target, key))
|
|
68
|
+
return unresolved();
|
|
69
|
+
target = target[key];
|
|
70
|
+
}
|
|
71
|
+
return target;
|
|
72
|
+
}
|
|
73
|
+
function walk(schema, depth) {
|
|
74
|
+
if (++visited > MAX_SCHEMAS || depth > MAX_DEPTH)
|
|
75
|
+
return unresolved();
|
|
76
|
+
if (typeof schema === "boolean")
|
|
77
|
+
return PUBLIC;
|
|
78
|
+
if (!object(schema) || active.has(schema))
|
|
79
|
+
return unresolved();
|
|
80
|
+
if (schema["writeOnly"] !== undefined && typeof schema["writeOnly"] !== "boolean")
|
|
81
|
+
return unresolved();
|
|
82
|
+
if (schema["writeOnly"] === true)
|
|
83
|
+
return OMIT;
|
|
84
|
+
if ((schema !== root && schema["$id"] !== undefined) ||
|
|
85
|
+
schema["$dynamicRef"] !== undefined ||
|
|
86
|
+
schema["$recursiveRef"] !== undefined)
|
|
87
|
+
return unresolved();
|
|
88
|
+
active.add(schema);
|
|
89
|
+
try {
|
|
90
|
+
let plan = PUBLIC;
|
|
91
|
+
const child = (value) => walk(value, depth + 1);
|
|
92
|
+
if (schema["$ref"] !== undefined)
|
|
93
|
+
plan = merge(plan, child(reference(schema["$ref"])));
|
|
94
|
+
if (schema["properties"] !== undefined) {
|
|
95
|
+
if (!object(schema["properties"]))
|
|
96
|
+
return unresolved();
|
|
97
|
+
const properties = Object.entries(schema["properties"])
|
|
98
|
+
.map(([name, value]) => [name, child(value)])
|
|
99
|
+
.filter(([, value]) => sensitive(value))
|
|
100
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
101
|
+
plan = merge(plan, properties.length ? { properties: Object.fromEntries(properties) } : PUBLIC);
|
|
102
|
+
}
|
|
103
|
+
const prefixes = schema["prefixItems"] ?? (Array.isArray(schema["items"]) ? schema["items"] : undefined);
|
|
104
|
+
if (prefixes !== undefined && !Array.isArray(prefixes))
|
|
105
|
+
return unresolved();
|
|
106
|
+
if (schema["prefixItems"] !== undefined && Array.isArray(schema["items"]))
|
|
107
|
+
return unresolved();
|
|
108
|
+
const tail = Array.isArray(schema["items"]) ? schema["additionalItems"] : schema["items"];
|
|
109
|
+
const arrayPlan = {
|
|
110
|
+
...(prefixes ? { prefix: prefixes.map(child) } : {}),
|
|
111
|
+
...(tail !== undefined ? { items: child(tail) } : {}),
|
|
112
|
+
};
|
|
113
|
+
plan = merge(plan, arrayPlan);
|
|
114
|
+
for (const keyword of ["allOf", "oneOf", "anyOf"]) {
|
|
115
|
+
const branches = schema[keyword];
|
|
116
|
+
if (branches === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
if (!Array.isArray(branches) || !branches.length)
|
|
119
|
+
return unresolved();
|
|
120
|
+
const plans = branches.map(child);
|
|
121
|
+
if (keyword === "allOf" || unionAlternatives)
|
|
122
|
+
for (const branch of plans)
|
|
123
|
+
plan = merge(plan, branch);
|
|
124
|
+
else {
|
|
125
|
+
if (plans.some((branch) => !equal(branch, plans[0])))
|
|
126
|
+
return unresolved();
|
|
127
|
+
plan = merge(plan, plans[0]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// These applicators need matching/evaluation rules we do not implement.
|
|
131
|
+
// Public-only schemas are harmless; sensitivity or an unresolved reference fails closed.
|
|
132
|
+
for (const keyword of [
|
|
133
|
+
"additionalProperties",
|
|
134
|
+
"unevaluatedProperties",
|
|
135
|
+
"unevaluatedItems",
|
|
136
|
+
"contains",
|
|
137
|
+
"not",
|
|
138
|
+
"if",
|
|
139
|
+
"then",
|
|
140
|
+
"else",
|
|
141
|
+
])
|
|
142
|
+
if (schema[keyword] !== undefined && sensitive(child(schema[keyword])))
|
|
143
|
+
return unresolved();
|
|
144
|
+
for (const keyword of ["patternProperties", "dependentSchemas", "dependencies"]) {
|
|
145
|
+
if (schema[keyword] === undefined)
|
|
146
|
+
continue;
|
|
147
|
+
if (!object(schema[keyword]))
|
|
148
|
+
return unresolved();
|
|
149
|
+
for (const value of Object.values(schema[keyword])) {
|
|
150
|
+
if (keyword === "dependencies" && Array.isArray(value))
|
|
151
|
+
continue;
|
|
152
|
+
if (sensitive(child(value)))
|
|
153
|
+
return unresolved();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return plan;
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
active.delete(schema);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return walk(root, 0);
|
|
163
|
+
}
|
|
164
|
+
/** Omit whole array values when private elements cannot be removed without shifting indices. */
|
|
165
|
+
function apply(value, plan, mark, depth = 0) {
|
|
166
|
+
if (!sensitive(plan))
|
|
167
|
+
return value;
|
|
168
|
+
if (depth > MAX_DEPTH)
|
|
169
|
+
throw new Error("Argument redaction depth exceeded");
|
|
170
|
+
if (plan.omit) {
|
|
171
|
+
mark(value);
|
|
172
|
+
return OMITTED;
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
const values = value.map((entry, index) => apply(entry, item(plan, index), mark, depth + 1));
|
|
176
|
+
return values.includes(OMITTED) ? OMITTED : values;
|
|
177
|
+
}
|
|
178
|
+
if (!object(value))
|
|
179
|
+
return value;
|
|
180
|
+
return Object.fromEntries(Object.entries(value).flatMap(([name, entry]) => {
|
|
181
|
+
const field = Object.hasOwn(plan.properties ?? {}, name) ? plan.properties[name] : PUBLIC;
|
|
182
|
+
const filtered = apply(entry, field, mark, depth + 1);
|
|
183
|
+
return filtered === OMITTED ? [] : [[name, filtered]];
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
/** Output redaction protects the union of private fields across alternatives.
|
|
187
|
+
* Other sensitivity rules and unresolved-schema handling follow echo redaction. */
|
|
188
|
+
export function visitPrivateCallArguments(args, schema, visit) {
|
|
189
|
+
// Public-only schemas need no sensitivity compilation, regardless of size.
|
|
190
|
+
// Iterative traversal visits each object once, including definitions and cycles.
|
|
191
|
+
const pending = [schema];
|
|
192
|
+
const seen = new Set();
|
|
193
|
+
let privateSchema = false;
|
|
194
|
+
while (pending.length) {
|
|
195
|
+
const value = pending.pop();
|
|
196
|
+
if (value === null || typeof value !== "object" || seen.has(value))
|
|
197
|
+
continue;
|
|
198
|
+
seen.add(value);
|
|
199
|
+
if (Object.hasOwn(value, "writeOnly") && value["writeOnly"] === true) {
|
|
200
|
+
privateSchema = true;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
for (const entry of Object.values(value))
|
|
204
|
+
pending.push(entry);
|
|
205
|
+
}
|
|
206
|
+
if (!privateSchema)
|
|
207
|
+
return;
|
|
208
|
+
try {
|
|
209
|
+
apply(args, compile(schema, true), visit);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// When a private annotation exists but its location cannot be resolved,
|
|
213
|
+
// treat every submitted value as private, just as the echo is withheld.
|
|
214
|
+
visit(args);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** Internal schema filter; the single echo helper owns snapshotting and byte budgeting. */
|
|
218
|
+
export function redactCallArguments(args, schema) {
|
|
219
|
+
try {
|
|
220
|
+
const plan = compile(schema);
|
|
221
|
+
let redacted = false;
|
|
222
|
+
const value = apply(args, plan, () => {
|
|
223
|
+
redacted = true;
|
|
224
|
+
});
|
|
225
|
+
return { ...(value !== OMITTED ? { value } : {}), redacted };
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return { redacted: true };
|
|
229
|
+
}
|
|
230
|
+
}
|