@zackbart/connecta 0.27.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -6
- package/CHANGELOG.md +229 -0
- package/README.md +23 -24
- package/bin/connecta.mjs +1 -5
- package/dist/activity.d.ts +5 -4
- package/dist/activity.js +1 -10
- package/dist/artifacts/connector.js +1 -1
- package/dist/artifacts/json.js +10 -0
- package/dist/artifacts/operations.js +1 -1
- package/dist/artifacts/validate.js +1 -2
- package/dist/auth/downstream-oauth.js +147 -28
- package/dist/auth/static-oauth.js +504 -0
- package/dist/catalog-service.js +1 -1
- package/dist/connectors/api-connector.d.ts +168 -0
- package/dist/connectors/api-connector.js +127 -0
- package/dist/connectors/api.d.ts +8 -71
- package/dist/connectors/api.js +10 -99
- package/dist/connectors/guarded-fetch.js +37 -3
- package/dist/connectors/remote-mcp.js +7 -32
- package/dist/execute.js +92 -368
- package/dist/executors/quickjs.js +1 -1
- package/dist/exempt-writes.js +162 -0
- package/dist/index.d.ts +13 -32
- package/dist/index.js +14 -47
- package/dist/invocation.js +6 -19
- package/dist/meta-tools.js +1 -2
- package/dist/oauth-partition.js +17 -0
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +3 -3
- package/dist/operator-ui/view.js +6 -4
- package/dist/providers/basecamp.d.ts +65 -0
- package/dist/providers/basecamp.js +445 -0
- package/dist/providers/breeze.d.ts +34 -0
- package/dist/providers/breeze.js +1402 -0
- package/dist/providers/ccb.d.ts +103 -0
- package/dist/providers/ccb.js +1570 -0
- package/dist/providers/cloudflare.js +1 -1
- package/dist/providers/linear.js +1 -1
- package/dist/providers/mixpanel.js +1 -1
- package/dist/providers/notion.js +1 -1
- package/dist/providers/overflow.d.ts +60 -0
- package/dist/providers/overflow.js +1572 -0
- package/dist/providers/planning-center.d.ts +59 -0
- package/dist/providers/planning-center.js +2039 -0
- package/dist/providers/revenuecat.js +1 -1
- package/dist/providers/stripe.js +1 -1
- package/dist/providers/tithely.d.ts +49 -0
- package/dist/providers/tithely.js +1034 -0
- package/dist/providers/vercel.js +1 -1
- package/dist/routes/mcp.js +5 -21
- package/dist/routes/oauth.js +29 -3
- package/dist/routes/shared.d.ts +1 -6
- package/dist/server.js +0 -4
- package/dist/skills.js +8 -23
- package/dist/tool-safety.d.ts +1 -1
- package/dist/tool-safety.js +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +15 -17
- package/documentation/auth.md +120 -6
- package/documentation/code-mode.md +91 -194
- package/documentation/meta-tools.md +38 -41
- package/ethos.md +8 -9
- package/examples/worker/README.md +10 -14
- package/examples/worker/src/d1-activity-row.ts +4 -1
- package/examples/worker/src/index.ts +1 -9
- package/package.json +26 -2
- package/templates/node/AGENTS.md +4 -3
- package/templates/node/README.md +3 -4
- package/templates/node/package.json +1 -1
- package/dist/resumable.js +0 -1323
- package/dist/run-journal.js +0 -347
package/AGENTS.md
CHANGED
|
@@ -4,13 +4,13 @@ This is the canonical instruction file for coding agents. `CLAUDE.md` is a
|
|
|
4
4
|
symlink to this file so every agent works from the same conventions.
|
|
5
5
|
|
|
6
6
|
A single MCP endpoint aggregating downstream connectors (remote MCP servers and
|
|
7
|
-
plain HTTP APIs) behind
|
|
7
|
+
plain HTTP APIs) behind seven meta-tools, `execute_code` among them. Every
|
|
8
8
|
deployment configures an executor, and agents reach connectors by writing
|
|
9
9
|
JavaScript against it. One fetch-native core on Effect behind a Promise API,
|
|
10
|
-
running on both Node and Cloudflare Workers. A program
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
exists.
|
|
10
|
+
running on both Node and Cloudflare Workers. A program runs read-only and
|
|
11
|
+
config-exempt tools; every other write is its own `call_destructive_tool` call,
|
|
12
|
+
so the host's permission prompt is the only approval. The code and the guides
|
|
13
|
+
describe what exists.
|
|
14
14
|
|
|
15
15
|
- **[`ethos.md`](./ethos.md) is the constitution.** It states what connecta is
|
|
16
16
|
and isn't, and its decisions table carries a verdict for every shape already
|
|
@@ -74,7 +74,7 @@ pins the generated deployment to the CLI package's exact version, restores the
|
|
|
74
74
|
template `.gitignore` and the `CLAUDE.md` symlink, and refuses to merge into an
|
|
75
75
|
existing path.
|
|
76
76
|
`connecta doctor` verifies a running deployment's health, executor, and exact
|
|
77
|
-
|
|
77
|
+
seven-tool surface. The template carries its own `Dockerfile` and
|
|
78
78
|
`docker-compose.yml`, so the generated project is the container: setup changes
|
|
79
79
|
must keep the root README, the template (source, container files, and README),
|
|
80
80
|
and the `scripts/check-package.mjs` smoke — which builds and runs that
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,235 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.28.1 — 2026-10-05
|
|
6
|
+
|
|
7
|
+
This patch adds six maintained connections for church operations and the
|
|
8
|
+
downstream OAuth they needed. Planning Center, Overflow, Tithe.ly, Breeze ChMS,
|
|
9
|
+
and Church Community Builder (Pushpay ChMS) are hand-written `api()` surfaces
|
|
10
|
+
over their whole APIs, with every money-moving write behind approval; Basecamp
|
|
11
|
+
proxies 37signals' hosted MCP server. Church Community Builder accepts only
|
|
12
|
+
OAuth, so `api()` connectors can now declare a static OAuth 2.0
|
|
13
|
+
authorization-code grant that reuses the `remoteMcp()` machinery. Each
|
|
14
|
+
connection is a new opt-in provider subpath and the OAuth grant is opt-in per
|
|
15
|
+
connector: existing imports, connector declarations, credentials, and
|
|
16
|
+
deployment behavior do not change. Two fixes a deployment may notice: a forced
|
|
17
|
+
OAuth restart no longer reuses a client registration the provider has deleted
|
|
18
|
+
([#611](https://github.com/zackbart/connecta/issues/611)), and a failed OAuth
|
|
19
|
+
code exchange is now logged as fixed text rather than the provider's message.
|
|
20
|
+
Internally, the guarded transport can repeat a query key for an array value,
|
|
21
|
+
and `api()`'s builder moved into its own module so OAuth stays out of bundles
|
|
22
|
+
that never use it.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Downstream OAuth on `api()` connectors.** A hand-written connector can
|
|
27
|
+
declare `oauth: { authorizationEndpoint, tokenEndpoint, clientId,
|
|
28
|
+
clientSecret?, tokenEndpointAuthMethod?, scope?, pkce?, authorizationParams?,
|
|
29
|
+
tokenRequestHeaders?, apiOrigins }` for a REST API that accepts only the authorization code grant,
|
|
30
|
+
such as Church Community Builder. Nothing is discovered or registered: the
|
|
31
|
+
endpoints and the provider-issued client are configuration, and the client
|
|
32
|
+
secret comes from the deployment's environment and is never stored. Handlers
|
|
33
|
+
call `ctx.oauth.fetch(url, init)`, which sends the calling owner's token as a
|
|
34
|
+
bearer to a declared origin only, answers a 401 with one coordinated refresh
|
|
35
|
+
and one replay, and otherwise fails `auth_required`. The grant is the
|
|
36
|
+
`remoteMcp()` grant underneath — the same sealed epochs, shared and personal
|
|
37
|
+
ownership, rotating-refresh coordination, dead-versus-outage verdicts,
|
|
38
|
+
`/oauth/callback/<id>`, `authorize_connector`, and operator Connect,
|
|
39
|
+
Restart, and Disconnect. `oauth` and `credential` are exclusive on one
|
|
40
|
+
connector. See [downstream OAuth on `api()`](./documentation/auth.md#downstream-oauth-on-api).
|
|
41
|
+
- **Church Community Builder (Pushpay ChMS) connection.**
|
|
42
|
+
`@zackbart/connecta/providers/ccb` exports `ccb(id, { purpose, environment,
|
|
43
|
+
mode, clientId, clientSecret, access?, scopes?, subdomain? })`, a
|
|
44
|
+
hand-written v2 REST connection over `api()`'s OAuth grant. `environment`
|
|
45
|
+
(`production` or `sandbox`) and `mode` (`system`, one church-wide grant an
|
|
46
|
+
administrator approves; `identity`, each person signing in as themselves
|
|
47
|
+
with a personal grant) are required, because CCB credentials do not cross
|
|
48
|
+
environments and the two modes reach different data. Twenty-four named reads
|
|
49
|
+
cover people and families, groups and participants, events and attendance,
|
|
50
|
+
forms and responses, processes and queues, scheduling, notes, and giving
|
|
51
|
+
metrics and pledges, with projections that drop allergies, giving
|
|
52
|
+
identifiers, and prayer requests unless `raw: true`. `ccb_api_get` and
|
|
53
|
+
`ccb_api_search` reach every GET and advanced search read-only, and
|
|
54
|
+
`ccb_api_mutate` ships only when a write scope is requested. A default
|
|
55
|
+
60-per-minute admission window per endpoint transcribes CCB's documented
|
|
56
|
+
limit, and `npm run providers:check -- --provider ccb` compares the 41
|
|
57
|
+
touched endpoints with Pushpay's published OpenAPI document. Clients are
|
|
58
|
+
issued by hand through Pushpay's API access request form.
|
|
59
|
+
- **Maintained Planning Center connection.**
|
|
60
|
+
`@zackbart/connecta/providers/planning-center` exports `planningCenter(id,
|
|
61
|
+
options)`, a hand-written `api()` surface over Planning Center Online's REST
|
|
62
|
+
API with no SDK and no new dependency. Named tools cover the jobs church
|
|
63
|
+
staff run — People search, profiles, contact records, lists, workflows and
|
|
64
|
+
card actions, notes, custom fields, and form submissions; Services plans,
|
|
65
|
+
teams, schedules, songs, and scheduling; Groups memberships and events;
|
|
66
|
+
Check-Ins attendance; Calendar occurrences; Registrations signups; and Giving
|
|
67
|
+
donations, funds, batches, pledges, and recurring gifts as reads only — with
|
|
68
|
+
JSON:API flattened into lean records and `raw: true` beside them.
|
|
69
|
+
`pco_api_get` (read-only) and `pco_api_mutate` (always approval-gated) reach
|
|
70
|
+
every other endpoint, including every write that moves money. The credential
|
|
71
|
+
is a personal access token as two labeled fields, and the Test action names
|
|
72
|
+
the person and organization it acts as. Every request sends a reviewed
|
|
73
|
+
`X-PCO-API-Version` per product and a configurable `User-Agent`, and the
|
|
74
|
+
default call budget transcribes Planning Center's 100 requests per 20
|
|
75
|
+
seconds. `npm run providers:check -- --provider planning-center` compares
|
|
76
|
+
the touched endpoints with Planning Center's per-product OpenAPI documents
|
|
77
|
+
and reports a newly published version of any pinned product. Planning
|
|
78
|
+
Center's own hosted MCP server was considered and is not wrapped: its writes
|
|
79
|
+
stop at People.
|
|
80
|
+
- **Maintained Overflow connection.** `@zackbart/connecta/providers/overflow`
|
|
81
|
+
ships 16 projected reads over contributions, deposits and their line items
|
|
82
|
+
and totals, refunds, chargebacks, donors, recurring gifts, payment methods,
|
|
83
|
+
campaigns, locations, Tap events, and webhook delivery logs, plus a
|
|
84
|
+
read-only GET hatch and a destructive JSON-mutation hatch that carries every
|
|
85
|
+
Overflow write — charges, payment authorization, refunds, recurring-gift
|
|
86
|
+
changes, and donor edits — to the host's approval prompt. `environment`
|
|
87
|
+
(`"production"` or `"staging"`) is required with no default, the credential
|
|
88
|
+
is one operator slot with `clientId` and `apiKey` fields, list rows keep
|
|
89
|
+
donor contact details and webhook payloads out unless `raw: true`, and the
|
|
90
|
+
default admission budget transcribes Overflow's 120 requests per minute per
|
|
91
|
+
client. `npm run providers:check -- --provider overflow` compares all 38
|
|
92
|
+
operations with the OpenAPI document Overflow publishes on staging.
|
|
93
|
+
- A maintained Tithe.ly giving connection, `tithely(id, { purpose, environment })`
|
|
94
|
+
from `@zackbart/connecta/providers/tithely`: a hand-written `api()` surface
|
|
95
|
+
over Tithe.ly's v1 REST API. `environment` (`"live"` for tithe.ly, `"test"`
|
|
96
|
+
for tithelydev.com) is required with no default, because Tithe.ly's key pairs
|
|
97
|
+
are environment-specific and carry nothing that says which. The credential
|
|
98
|
+
slot takes the public and private keys as two fields, refuses an obviously
|
|
99
|
+
swapped pair without a request, and its Test action reports the organization
|
|
100
|
+
the keys reach; API access is by request through support@tithe.ly. Named
|
|
101
|
+
read-only tools cover organizations and their funds, donor accounts, charges
|
|
102
|
+
by organization or donor and created-date range, recurring gifts, and
|
|
103
|
+
payment-method metadata, with amounts as integer cents, dates as ISO 8601,
|
|
104
|
+
`hasMore` paging, and organization projections that omit payout bank and
|
|
105
|
+
legal-contact details. Every write moves money or donor payment state and is
|
|
106
|
+
reached only through the always-destructive, form-encoded
|
|
107
|
+
`tithely_api_mutate`; `tithely_api_get` reaches every v1 read. No call budget
|
|
108
|
+
is declared, since Tithe.ly publishes no rate limit.
|
|
109
|
+
`npm run providers:check -- --provider tithely` digests the OpenAPI snippet
|
|
110
|
+
each reference page embeds, for every documented v1 operation.
|
|
111
|
+
- A maintained Breeze ChMS connection (Tithely Church Management since
|
|
112
|
+
2025-11-11) at `@zackbart/connecta/providers/breeze`:
|
|
113
|
+
`breeze(id, { subdomain, purpose })` binds one church's
|
|
114
|
+
`<subdomain>.breezechms.com` — validated as a single hostname label at
|
|
115
|
+
construction, so no argument can steer the API key elsewhere — with an
|
|
116
|
+
operator `Api-Key` slot tested against the account summary. Named reads cover
|
|
117
|
+
people, profile fields, tags, events, calendars, attendance, forms,
|
|
118
|
+
volunteers, the account log, and giving (`list_contributions`, `list_funds`,
|
|
119
|
+
which call endpoints Breeze stopped documenting in 2023 and still serves);
|
|
120
|
+
named writes add and update people, assign and unassign tags, and record a
|
|
121
|
+
check-in. Because Breeze sends writes as GET too, the escape hatches split by
|
|
122
|
+
endpoint rather than method: `breeze_api_get` admits only a reviewed
|
|
123
|
+
allowlist of read endpoints and their documented parameters, and the
|
|
124
|
+
destructive `breeze_api_mutate` reaches everything else. No admission budget
|
|
125
|
+
is declared, since Breeze documents no rate limit; drift is review-only,
|
|
126
|
+
since it publishes no machine-readable specification.
|
|
127
|
+
- **`basecamp()` — a maintained Basecamp hosted-MCP connection** at
|
|
128
|
+
`@zackbart/connecta/providers/basecamp`, proxying
|
|
129
|
+
`https://mcp.basecamp.com/mcp` — a server 37signals runs but has not
|
|
130
|
+
documented or announced. OAuth only, and `clientMetadataUrl` is required:
|
|
131
|
+
Basecamp restricts dynamic client registration for HTTPS callbacks, so a
|
|
132
|
+
deployment hosts a Client ID Metadata Document listing
|
|
133
|
+
`<publicUrl>/oauth/callback/<id>`, and construction throws without one. A
|
|
134
|
+
grant reaches exactly one Basecamp account, chosen at consent, so the guide
|
|
135
|
+
leads with that, names `get_me` to confirm it, and a second account is a
|
|
136
|
+
second connector. All 227 tools in the live catalog are classified (105
|
|
137
|
+
read-only, 34 additive, 88 destructive); the four verdicts that part from the
|
|
138
|
+
server's own annotations, and `create_stream_ticket`'s agreement with its
|
|
139
|
+
read-only claim, are argued beside their rows. No access mode ships: the MCP
|
|
140
|
+
client requests the scope Basecamp's challenge names, so a `read` option
|
|
141
|
+
would be a label rather than a limit. Personal access tokens are not offered,
|
|
142
|
+
because nothing shows this endpoint accepts one. No default `callAdmission`:
|
|
143
|
+
Basecamp's limits are dynamic, multi-tier, and metered at its own server's
|
|
144
|
+
IP. With no public setup page or inventory, `npm run providers:check` reads
|
|
145
|
+
the server's OAuth discovery instead — the resource names the endpoint and
|
|
146
|
+
the authorization server still accepts metadata-document clients — and
|
|
147
|
+
catalog drift is runtime-only.
|
|
148
|
+
|
|
149
|
+
### Fixed
|
|
150
|
+
|
|
151
|
+
- **A forced OAuth restart can no longer wedge on a purged client
|
|
152
|
+
registration** ([#611](https://github.com/zackbart/connecta/issues/611)).
|
|
153
|
+
Restart already carried an issuer-bound dynamic registration into the new
|
|
154
|
+
epoch; it carried it unconditionally, and a provider that had forgotten the
|
|
155
|
+
client refuses it at consent without ever redirecting back, so every later
|
|
156
|
+
Restart reused the same dead client and only Disconnect escaped. A carried
|
|
157
|
+
client is now carried again only once a grant in its epoch proves the
|
|
158
|
+
provider still knows it, so two restarts in a row with no consent between
|
|
159
|
+
them register at most once more. A URL-based client (`clientMetadataUrl`)
|
|
160
|
+
and a client whose secret has expired are never carried. Continue no longer
|
|
161
|
+
hands back a pending consent URL whose client a callback's `invalid_client`
|
|
162
|
+
has dropped; it starts a flow that registers instead. Static `api()` OAuth
|
|
163
|
+
clients are configuration and unaffected.
|
|
164
|
+
- **A refresh rotation saved after its request was cancelled keeps its
|
|
165
|
+
issuer.** When the token endpoint answered after the owning request left,
|
|
166
|
+
connecta persisted the rotated tokens itself but unbound, and the next
|
|
167
|
+
issuer-aware read adopted them for whichever authorization server the
|
|
168
|
+
connector named by then — so a connector repointed at another server would
|
|
169
|
+
send the rotated refresh token there. The recovery save now stamps the issuer
|
|
170
|
+
the refresh answered to, as the SDK's own save does, and a repointed connector
|
|
171
|
+
fences the grant instead.
|
|
172
|
+
- **A failed OAuth code exchange no longer writes the provider's message to the
|
|
173
|
+
operator log.** The SDK quotes a token endpoint's `error_description` or raw
|
|
174
|
+
body, and a provider that echoes a `client_secret_post` request put the client
|
|
175
|
+
secret, code, and verifier in the log. The callback now logs fixed text and,
|
|
176
|
+
when the error carries one, a standard OAuth error code from an allowlist.
|
|
177
|
+
|
|
178
|
+
## 0.28.0 — 2026-10-04
|
|
179
|
+
|
|
180
|
+
This minor release removes program pauses ([#672](https://github.com/zackbart/connecta/issues/672)).
|
|
181
|
+
`execute_code` programs run explicitly read-only tools and the writes
|
|
182
|
+
`execute.approval` exempts, and every other write is its own
|
|
183
|
+
`call_destructive_tool` call, so the host's permission prompt on that call is
|
|
184
|
+
the only approval there is. Hosts approve one tool call at a time, and under
|
|
185
|
+
always-allow or bypass modes the agent called `resume_execution` itself: each
|
|
186
|
+
pause cost a round trip and a full replay and protected nothing. What breaks:
|
|
187
|
+
`resume_execution` is gone, so the surface is seven tools again, and a host
|
|
188
|
+
allow-list or cached tool list naming it should drop it.
|
|
189
|
+
`execute.resumableWrites` and `execute.pausedRunTtlSeconds` now refuse to
|
|
190
|
+
construct with an error naming
|
|
191
|
+
the removal — delete them, including the Worker example's
|
|
192
|
+
`execute: { resumableWrites: false }` line. A program's non-exempt write now
|
|
193
|
+
fails `destructive_tool_requires_approval` with a `nextAction` for
|
|
194
|
+
`call_destructive_tool`, exactly as it did on deployments without resumable
|
|
195
|
+
writes, and a run never returns `{ paused: … }`. `Date.now()` and
|
|
196
|
+
`Math.random()` inside a program are the runtime's own again rather than pinned
|
|
197
|
+
per run. A deployment that sets neither option and whose programs only read
|
|
198
|
+
can ignore all of this: approval exemptions, `execute.maxWrites`, storage
|
|
199
|
+
contracts, and the D1 activity schema are unchanged, and any paused runs left
|
|
200
|
+
in storage expire on their own TTL. The Node template now pins 0.28.0.
|
|
201
|
+
|
|
202
|
+
### Removed
|
|
203
|
+
|
|
204
|
+
- `resume_execution`, the run journal, deterministic replay, compare-and-set
|
|
205
|
+
run claims, and the pinned clock and randomness (code-mode `P6`) that
|
|
206
|
+
existed only so a replay could match. `W1`–`W8`, `W11`, `P6`, and `X12` are
|
|
207
|
+
retired clause ids.
|
|
208
|
+
- The pause-only error codes: `execution_paused`, `pending_write_too_large`,
|
|
209
|
+
`journal_too_large`, `execution_interrupted`, `execution_diverged`,
|
|
210
|
+
`execution_claim_lost`, `approval_mismatch`, `execution_token_stale`,
|
|
211
|
+
`execution_in_progress`, `execution_expired`, `execution_not_found`, and
|
|
212
|
+
`resumable_writes_unavailable`. `write_outcome_unknown` stays, for an exempt
|
|
213
|
+
write that was sent and never answered.
|
|
214
|
+
- `resumableWrites` from `/health`, and the "resumable writes off" note from
|
|
215
|
+
`connecta doctor`, which now verifies the exact seven-tool surface.
|
|
216
|
+
- The pause and resume agent eval tasks.
|
|
217
|
+
|
|
218
|
+
### Changed
|
|
219
|
+
|
|
220
|
+
- The MCP instructions, the `execute_code` description, and the `usage` skill
|
|
221
|
+
describe the read-only program route: reads and config-exempt writes in a
|
|
222
|
+
program, every other write through `call_destructive_tool`. The description
|
|
223
|
+
states the exempt-write budget.
|
|
224
|
+
- `execute.maxWrites` now bounds only config-exempt writes, the one kind a
|
|
225
|
+
program can still send. Its default stays 10.
|
|
226
|
+
- Activity no longer emits `paused` or `approved` outcomes, the
|
|
227
|
+
`resume_execution` source, or an `approval` scope. The types keep them as
|
|
228
|
+
read-only history, the operator UI still renders older rows (a historical
|
|
229
|
+
pause now reads "Paused for approval"), and the D1 example still stores and
|
|
230
|
+
reads the `approval` column.
|
|
231
|
+
- QuickJS still runs its child with `TZ=UTC`, now for parity with a Dynamic
|
|
232
|
+
Worker's local time rather than for replay.
|
|
233
|
+
|
|
5
234
|
## 0.27.0 — 2026-10-01
|
|
6
235
|
|
|
7
236
|
This minor release moves the runtime to stable Effect v4 and fixes execution,
|
package/README.md
CHANGED
|
@@ -10,19 +10,18 @@ writing code instead of loading a thousand tool definitions.
|
|
|
10
10
|
You ask your agent a question that touches a service — Linear, Stripe, an
|
|
11
11
|
internal API, anything you have connected. Here is what happens:
|
|
12
12
|
|
|
13
|
-
1. The agent talks to one endpoint, yours, and sees
|
|
13
|
+
1. The agent talks to one endpoint, yours, and sees seven tools. Always seven,
|
|
14
14
|
no matter how many services sit behind it.
|
|
15
15
|
2. It writes a short JavaScript program. Connecta runs it in a sandbox next to
|
|
16
16
|
your integrations. The program can search for tools, call them, chain the
|
|
17
17
|
calls, and shape the result.
|
|
18
18
|
3. Only the answer comes back into the agent's context — not raw pages of
|
|
19
19
|
API output.
|
|
20
|
-
4. If the
|
|
21
|
-
|
|
22
|
-
write
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
can also go straight through `call_destructive_tool`.
|
|
20
|
+
4. If the agent wants to change something — create, update, delete — it
|
|
21
|
+
cannot do that from a program, unless your config exempts that tool. The
|
|
22
|
+
program refuses the write before anything is sent and hands back the exact
|
|
23
|
+
call; the agent makes it through `call_destructive_tool`, one visible call
|
|
24
|
+
your MCP client can put in front of you first.
|
|
26
25
|
|
|
27
26
|
Credentials never leave the server. The program never sees them, and neither
|
|
28
27
|
does the agent.
|
|
@@ -31,9 +30,9 @@ does the agent.
|
|
|
31
30
|
flowchart TB
|
|
32
31
|
Client["Your MCP client<br/>Claude, Cursor, …"]
|
|
33
32
|
|
|
34
|
-
subgraph Connecta["Connecta — one endpoint,
|
|
35
|
-
Sandbox["execute_code<br/>the agent's program runs here<br/>
|
|
36
|
-
Explicit["
|
|
33
|
+
subgraph Connecta["Connecta — one endpoint, seven tools, your credentials"]
|
|
34
|
+
Sandbox["execute_code<br/>the agent's program runs here<br/>read-only tools, plus writes<br/>your config exempts"]
|
|
35
|
+
Explicit["call_destructive_tool<br/>one visible call per write<br/>your client can ask you first"]
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
Integrations["The integrations you chose<br/>Linear · Stripe · Notion · Vercel · your HTTP API · any MCP server"]
|
|
@@ -41,7 +40,6 @@ flowchart TB
|
|
|
41
40
|
Client -->|"one connection"| Sandbox
|
|
42
41
|
Client --> Explicit
|
|
43
42
|
Sandbox -->|"reads"| Integrations
|
|
44
|
-
Sandbox -.->|"paused write"| Explicit
|
|
45
43
|
Explicit -->|"writes"| Integrations
|
|
46
44
|
```
|
|
47
45
|
|
|
@@ -66,11 +64,14 @@ Fifty issues in, one small object out. Your context window notices.
|
|
|
66
64
|
services in a config file; your client never changes.
|
|
67
65
|
- **Wrap any HTTP API by hand.** A few lines per tool. No OpenAPI conversion —
|
|
68
66
|
generated tool sprawl is the problem, not the fix.
|
|
69
|
-
- **Use maintained connections** for
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
- **Use maintained connections** for Basecamp, Breeze ChMS, Church Community
|
|
68
|
+
Builder, Cloudflare, Linear, Mixpanel, Notion, Overflow, Planning Center,
|
|
69
|
+
RevenueCat, Stripe, Tithe.ly, and Vercel: known endpoints, auth defaults, and
|
|
70
|
+
vetted read/write classifications, imported one at a time. Cloudflare,
|
|
71
|
+
Notion, and Vercel each let the deployment choose their hand-written API
|
|
72
|
+
interface or official hosted MCP. Planning Center, Overflow, Tithe.ly,
|
|
73
|
+
Breeze, and Church Community Builder are hand-written over their whole APIs,
|
|
74
|
+
with every money-moving write behind approval.
|
|
74
75
|
- **Let the agent work in code.** Search, chain, filter, join, and reduce
|
|
75
76
|
inside the sandbox instead of round-tripping every call through the model.
|
|
76
77
|
- **Teach undeclared result shapes by using them.** Successful read-only calls
|
|
@@ -82,13 +83,11 @@ Fifty issues in, one small object out. Your context window notices.
|
|
|
82
83
|
notice also points the agent to reduce or search the result inside a
|
|
83
84
|
program. Discovery can show compact schemas, exact JSON Schema, or a
|
|
84
85
|
TypeScript signature to read while writing JavaScript.
|
|
85
|
-
- **Keep writes deliberate.** Only tools marked read-only run in a program
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
never sent twice. Config — and only config — can exempt a cheap,
|
|
91
|
-
reversible write from asking, per tool or per connector.
|
|
86
|
+
- **Keep writes deliberate.** Only tools marked read-only run in a program.
|
|
87
|
+
Every other write is a separate, visible `call_destructive_tool` call your
|
|
88
|
+
client can gate, so the host's permission prompt is the one approval there
|
|
89
|
+
is. Config — and only config — can exempt a cheap, reversible write from
|
|
90
|
+
asking, per tool or per connector, and then a program may make it.
|
|
92
91
|
- **Run it on Node or Cloudflare Workers.** The core is shared; each deployment
|
|
93
92
|
supplies its platform's executor and storage. The Node template also runs
|
|
94
93
|
unchanged in Docker.
|
|
@@ -122,7 +121,7 @@ and why.
|
|
|
122
121
|
|
|
123
122
|
Setup is written for an agent. Point yours at [`AGENTS.md`](./AGENTS.md) and
|
|
124
123
|
ask it to set up a Connecta deployment; the
|
|
125
|
-
[documentation](./documentation/) covers the architecture, the
|
|
124
|
+
[documentation](./documentation/) covers the architecture, the seven tools,
|
|
126
125
|
code mode, and inbound auth if you want to go deeper. When upgrading an
|
|
127
126
|
existing deployment, each [changelog](./CHANGELOG.md) release opens with what
|
|
128
127
|
breaks and what a deployment can ignore.
|
package/bin/connecta.mjs
CHANGED
|
@@ -257,7 +257,6 @@ async function doctor() {
|
|
|
257
257
|
"call_tool",
|
|
258
258
|
"execute_code",
|
|
259
259
|
"get_result",
|
|
260
|
-
"resume_execution",
|
|
261
260
|
"search_tools",
|
|
262
261
|
"skills",
|
|
263
262
|
];
|
|
@@ -310,10 +309,7 @@ async function doctor() {
|
|
|
310
309
|
console.log(
|
|
311
310
|
`Connecta doctor passed: ${health.connectors} connector(s), ` +
|
|
312
311
|
`${executorName ? `${executorName} executed` : "code executed"}, ` +
|
|
313
|
-
"prescribed
|
|
314
|
-
// resume_execution is listed either way; whether programs can write is
|
|
315
|
-
// what an operator cannot tell from the tool list, so say when not.
|
|
316
|
-
(health.resumableWrites === false ? ", resumable writes off" : "") +
|
|
312
|
+
"prescribed seven-tool surface" +
|
|
317
313
|
(drifted.length > 0
|
|
318
314
|
? `, catalog drift on ${drifted.length} connector(s).`
|
|
319
315
|
: "."),
|
package/dist/activity.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ActivityModule } from "./module-contracts.js";
|
|
|
2
2
|
import type { CatalogDriftCounts, Logger } from "./types.js";
|
|
3
3
|
export type ActivityCallSource = "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code" | "resume_execution";
|
|
4
4
|
export type ActivityOutcome = "success" | "error" | "timeout" | "cancelled" | "paused" | "approved";
|
|
5
|
-
/**
|
|
5
|
+
/** Read-only history: how much one `resume_execution` approved (#672). */
|
|
6
6
|
export type ActivityApproval = "call" | "tool";
|
|
7
7
|
export type AgentFriction = "tool_not_found" | "schema_retry" | "destructive_reroute" | "auth_required" | "result_too_large";
|
|
8
8
|
export { agentFrictionForCode } from "./activity-friction.js";
|
|
@@ -50,8 +50,9 @@ export interface ToolCallActivityEvent {
|
|
|
50
50
|
*/
|
|
51
51
|
friction?: AgentFriction;
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
53
|
+
* Read-only history: set only on an `approved` event written before issue
|
|
54
|
+
* #672, the scope the approval covered. An enum, like everything else
|
|
55
|
+
* here — the approved arguments were never recorded.
|
|
55
56
|
*/
|
|
56
57
|
approval?: ActivityApproval;
|
|
57
58
|
serverName: string;
|
|
@@ -149,7 +150,7 @@ export interface ActivityRequestContext {
|
|
|
149
150
|
defer?: (promise: Promise<unknown>) => void;
|
|
150
151
|
logger: Logger;
|
|
151
152
|
}
|
|
152
|
-
export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "errorCode" | "friction"
|
|
153
|
+
export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "toolName" | "address" | "source" | "outcome" | "durationMs" | "attempts" | "errorCode" | "friction">;
|
|
153
154
|
/**
|
|
154
155
|
* Best-effort by design: activity storage can never change a tool result.
|
|
155
156
|
* Workers attach async sinks to waitUntil; synchronous sinks such as Analytics
|
package/dist/activity.js
CHANGED
|
@@ -49,18 +49,9 @@ export function recordToolActivity(context, input) {
|
|
|
49
49
|
source: input.source,
|
|
50
50
|
outcome: input.outcome,
|
|
51
51
|
durationMs: Math.max(0, Math.trunc(input.durationMs)),
|
|
52
|
-
|
|
53
|
-
// events that honestly carry zero attempts. Every other outcome keeps its
|
|
54
|
-
// floor of one, as it always has.
|
|
55
|
-
attempts: input.outcome === "paused" || input.outcome === "approved"
|
|
56
|
-
? 0
|
|
57
|
-
: Math.max(1, Math.trunc(input.attempts)),
|
|
52
|
+
attempts: Math.max(1, Math.trunc(input.attempts)),
|
|
58
53
|
...(input.errorCode ? { errorCode: input.errorCode } : {}),
|
|
59
54
|
...(friction ? { friction } : {}),
|
|
60
|
-
...(input.outcome === "approved" &&
|
|
61
|
-
(input.approval === "call" || input.approval === "tool")
|
|
62
|
-
? { approval: input.approval }
|
|
63
|
-
: {}),
|
|
64
55
|
serverName: context.serverInfo.name,
|
|
65
56
|
serverVersion: context.serverInfo.version,
|
|
66
57
|
...(context.deploymentId
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Who made each version comes from the caller core attached to the context,
|
|
8
8
|
// never from arguments.
|
|
9
|
-
import { api } from "../connectors/api.js";
|
|
9
|
+
import { apiConnector as api } from "../connectors/api-connector.js";
|
|
10
10
|
import { callerOf } from "../connector-caller.js";
|
|
11
11
|
import { ConnectorCallError } from "../errors.js";
|
|
12
12
|
import { resolveTheme } from "../branding.js";
|
package/dist/artifacts/json.js
CHANGED
|
@@ -66,3 +66,13 @@ export function jsonProblem(value, maxDepth) {
|
|
|
66
66
|
}
|
|
67
67
|
return undefined;
|
|
68
68
|
}
|
|
69
|
+
const encoder = new TextEncoder();
|
|
70
|
+
/** A string's size in UTF-8 bytes: what every artifact limit counts. */
|
|
71
|
+
export function utf8Bytes(text) {
|
|
72
|
+
return encoder.encode(text).byteLength;
|
|
73
|
+
}
|
|
74
|
+
/** SHA-256 as lowercase hex: the content address of a stored body. */
|
|
75
|
+
export async function sha256Hex(text) {
|
|
76
|
+
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(text));
|
|
77
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
78
|
+
}
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
// Nothing here throws for a caller's mistake. Operations answer
|
|
15
15
|
// `{ ok: false, code, message }`, and the connector turns that into its typed
|
|
16
16
|
// error; a throw means storage failed.
|
|
17
|
-
import { sha256Hex, utf8Bytes } from "../run-journal.js";
|
|
18
17
|
import { buildFrameDocument, FRAME_OVERHEAD_BYTES } from "./document.js";
|
|
18
|
+
import { sha256Hex, utf8Bytes } from "./json.js";
|
|
19
19
|
import { markdownPage, MarkdownNestingError } from "./markdown.js";
|
|
20
20
|
import { ARTIFACT_ID, checkDocument, checkDocumentTotals, checkView, finish, validDocumentName, } from "./validate.js";
|
|
21
21
|
/** Physical head moves a write absorbs before giving up as busy. */
|
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
// finding names a line where there is one, quotes the source only through the
|
|
8
8
|
// shared echo bound, and says what to do instead.
|
|
9
9
|
import { boundedEchoText } from "../errors.js";
|
|
10
|
-
import { utf8Bytes } from "../run-journal.js";
|
|
11
10
|
import { lineLocator, scanHtml } from "./html-scan.js";
|
|
12
|
-
import { jsonProblem, scriptSafeJson } from "./json.js";
|
|
11
|
+
import { jsonProblem, scriptSafeJson, utf8Bytes } from "./json.js";
|
|
13
12
|
import { FRAME_OVERHEAD_BYTES } from "./document.js";
|
|
14
13
|
import { markdownPage, MarkdownNestingError } from "./markdown.js";
|
|
15
14
|
import { DEFAULT_ARTIFACT_ALLOWLIST, DEFAULT_ARTIFACT_LIMITS, } from "./types.js";
|