@zackbart/connecta 0.18.1 → 0.18.3
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 +80 -0
- package/README.md +4 -0
- package/dist/catalog-service.d.ts +4 -0
- package/dist/catalog-service.js +22 -6
- package/dist/connectors/remote-mcp.d.ts +49 -2
- package/dist/connectors/remote-mcp.js +302 -7
- package/dist/invocation.js +9 -3
- package/dist/providers/linear.d.ts +7 -1
- package/dist/providers/linear.js +12 -2
- package/dist/providers/mixpanel.d.ts +5 -1
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/revenuecat.d.ts +3 -1
- package/dist/providers/revenuecat.js +12 -3
- package/dist/providers/stripe.d.ts +9 -3
- package/dist/providers/stripe.js +24 -5
- package/dist/registry.d.ts +7 -0
- package/dist/registry.js +9 -0
- package/dist/result-shapes.d.ts +13 -0
- package/dist/result-shapes.js +331 -0
- package/dist/skills.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +5 -2
- package/documentation/code-mode.md +6 -6
- package/documentation/connectors.md +35 -0
- package/documentation/linear.md +25 -4
- package/documentation/meta-tools.md +22 -3
- package/documentation/mixpanel.md +19 -0
- package/documentation/operations.md +3 -1
- package/documentation/provider-conventions.md +37 -21
- package/documentation/revenuecat.md +23 -1
- package/documentation/storage-and-credentials.md +55 -0
- package/documentation/stripe.md +22 -2
- package/documentation/upgrading.md +32 -4
- package/ethos.md +3 -3
- package/examples/worker/src/index.ts +5 -0
- package/package.json +1 -1
- package/templates/node/package.json +1 -1
|
@@ -78,21 +78,35 @@ headed to `call_tool` or generated code; `safety: "approvalRequired"` finds the
|
|
|
78
78
|
complementary set that must cross `call_destructive_tool`. Omitting `safety`,
|
|
79
79
|
or setting it to `"all"`, preserves the complete configured catalog. This is
|
|
80
80
|
only a discovery filter: it neither grants authority nor changes invocation admission.
|
|
81
|
-
`includeSchemas: "compact"` adds each match's input and any declared
|
|
82
|
-
shape.
|
|
81
|
+
`includeSchemas: "compact"` adds each match's input and any provider-declared
|
|
82
|
+
output shape. When the provider declared none but an earlier successful call
|
|
83
|
+
learned one, the same field carries the open observed schema beside
|
|
84
|
+
`outputSchemaSource: "observed"`. That marker matters: observed fields and broad
|
|
85
|
+
JSON types are routing evidence, not a provider contract, and every object field
|
|
86
|
+
remains optional and open to unseen names. A provider declaration always wins.
|
|
87
|
+
Bounded plain-object schemas also expose `inputKeys`,
|
|
83
88
|
`requiredInputKeys`, and `outputKeys`; a zero-input object keeps
|
|
84
89
|
`requiredInputKeys: []`, while an output object with no declared properties
|
|
85
90
|
omits `outputKeys`. A truncated shape omits its corresponding list rather than
|
|
86
91
|
repeating a large partial inventory. Matches carry declared
|
|
87
92
|
behavior annotations. Lexical rank is only one signal: select a candidate whose
|
|
88
93
|
required inputs are available, whose schema is complete enough for the call,
|
|
89
|
-
and whose safety and
|
|
94
|
+
and whose safety and available outputs fit the work. A reducer uses `outputKeys`
|
|
90
95
|
before inspecting the value; it does not assume a collection is named `items`
|
|
91
96
|
or `results`. When that shape is sufficient, call the returned address directly. Reserve schema
|
|
92
97
|
expansion through `connecta.describe` for a search without schemas, an
|
|
93
98
|
ambiguous compact shape, or exact
|
|
94
99
|
constraints that require `format: "json"`.
|
|
95
100
|
|
|
101
|
+
Observed schemas originate no provider traffic. A successful explicitly
|
|
102
|
+
read-only call the user already made contributes names and broad types after
|
|
103
|
+
Connecta unwraps the result. Arguments, scalar values, raw results, code,
|
|
104
|
+
credentials, and errors are not retained, though property names may themselves
|
|
105
|
+
be user-authored. Shapes merge in a 256-entry runtime cache for 24 hours under
|
|
106
|
+
the exact tool definition that produced them. A changed definition, process
|
|
107
|
+
restart, or Worker isolate eviction starts cold. Observation cannot fail the
|
|
108
|
+
call, and the declared catalog remains the fallback.
|
|
109
|
+
|
|
96
110
|
Compact search is deliberately a routing view, not a second copy of connector
|
|
97
111
|
documentation. Tool purposes are capped at 160 characters, connector
|
|
98
112
|
descriptions and property prose are omitted, required input fields render
|
|
@@ -310,6 +324,11 @@ handoff:
|
|
|
310
324
|
declared credential label and field names/guidance; or
|
|
311
325
|
- `unavailable`: an honest deployment/configuration message.
|
|
312
326
|
|
|
327
|
+
The class follows what the connector declares, not how it was authored: a
|
|
328
|
+
`remoteMcp()` connection using `auth: { type: "credential" }` declares a slot
|
|
329
|
+
and no OAuth flow, so it lands in `operator_config` beside every `api()`
|
|
330
|
+
credential.
|
|
331
|
+
|
|
313
332
|
The tool accepts no secret. `force` applies only to OAuth and may discard its
|
|
314
333
|
stored grant before restarting consent. Static credential values are written
|
|
315
334
|
only through the same-origin, Clerk-operator credential route. After OAuth
|
|
@@ -50,6 +50,25 @@ password, not ordinary configuration. Mixpanel currently labels service-account
|
|
|
50
50
|
MCP authentication beta. Prefer OAuth unless the deployment is intentionally
|
|
51
51
|
headless.
|
|
52
52
|
|
|
53
|
+
The same service account can arrive from `/credentials` instead, and there the
|
|
54
|
+
operator pastes the readable pair rather than an encoded blob:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
mixpanel("automation_analytics", {
|
|
58
|
+
purpose: "Headless release-health reporting",
|
|
59
|
+
auth: { type: "credential" },
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The slot renders as "Service account" and takes `username:secret`. Connecta
|
|
64
|
+
base64-encodes it and sends Mixpanel's documented `Bearer Basic` framing, so the
|
|
65
|
+
operator never has to encode anything by hand. **The two paths take different
|
|
66
|
+
strings:** the `headers` example above wants the already-encoded blob
|
|
67
|
+
(`echo -n "username:secret" | base64`), and this one wants the plaintext pair.
|
|
68
|
+
Migrating from one to the other means decoding, not copying. Until a value is
|
|
69
|
+
saved the connector is present and reports `auth_required`. See
|
|
70
|
+
[storage and credentials](./storage-and-credentials.md#a-remote-mcp-connectors-static-credential).
|
|
71
|
+
|
|
53
72
|
## Conditional input contracts
|
|
54
73
|
|
|
55
74
|
Mixpanel's hosted descriptions enforce three cross-field conditions that its
|
|
@@ -75,7 +75,7 @@ optional.
|
|
|
75
75
|
| `connectors` | — (required) | the connector set ([connectors](./connectors.md)) |
|
|
76
76
|
| `executor` | — (required) | the sandbox `execute_code` runs in ([code mode](./code-mode.md#what-an-executor-must-implement)) |
|
|
77
77
|
| `auth?` | none ⇒ open (dev only) | one `InboundAuth` or an array; bearer providers are checked before Clerk ([inbound auth](./auth.md)) |
|
|
78
|
-
| `storage?` | `memoryStorage()` | the one state seam ([storage](./storage-and-credentials.md)) |
|
|
78
|
+
| `storage?` | `memoryStorage()` | the one state seam for catalogs, result paging, credentials, and access tokens ([storage](./storage-and-credentials.md)) |
|
|
79
79
|
| `publicUrl?` | per-request origin | public base URL; an HTTPS value also redirects inbound HTTP |
|
|
80
80
|
| `logger?` | `console`, prefixed `[connecta]` | `{ debug, info, warn, error }` |
|
|
81
81
|
| `branding?` | neutral Connecta defaults | operator-page and OAuth result-page labels and marks |
|
|
@@ -247,8 +247,10 @@ in.
|
|
|
247
247
|
| `provider-conventions.test.ts` | the conventions a test can hold: hand-written providers refusing schemas they cannot enforce (H5), their compact discovery schemas staying complete (H7), Cloudflare stating its second pagination convention in the schema (H10), and Notion saying it has no escape hatch (H14) |
|
|
248
248
|
| `registry.test.ts` | construction and id validation, startup warnings, address resolution, catalog TTL/persistence/completeness, agent-only stale-while-revalidate with cross-request single-flight shared with blocking reads in both start orders, owned teardown, invalidation/fingerprint guards, blocking diagnostics, and broken-connector isolation |
|
|
249
249
|
| `remote-mcp.test.ts` | `remoteMcp()` against an in-process server through the `_transportFactory` seam: passthrough, downstream `isError`, Workers-safe output-schema validation, request-scoped client reuse and at-most-once scope close; plus the real transport's manual redirect policy, destination guard, credential containment, and downstream session termination |
|
|
250
|
+
| `remote-mcp-credential.test.ts` | `remoteMcp()` drawing a static key from `/credentials`: the declared slot and its refusal of named fields and bad header names, header framing (bearer, bare, and the two `Basic` forms) observed on the wire, an empty slot failing as `auth_required` rather than reaching the downstream, a value carrying a control character refused before framing and absent from every surface — `call_tool`, `status`, the Test result, `lastError`, and the thrown error — rotation replacing the cached client and a connect already in flight while a wiped value fails the next call, the Test action's catalog probe and scope close, the cleartext-destination warning, and the vault and `authorize_connector` handoff end to end |
|
|
250
251
|
| `remote-mcp-pagination.test.ts` | the `tools/list` cursor chain in both directions — exact cursor handoff, first-wins dedup, a failed later page rejecting rather than returning its prefix, the runaway backstops, the tool-metadata re-prime across pages, and paginated catalogs reaching the discovery path |
|
|
251
252
|
| `request-admission.test.ts` | `/mcp` bounded before auth, the stable 503 and `Retry-After`, health and operator responsiveness under saturation, payload-free counters, queued cancellation, shutdown rejection while active work drains, and the separate fallback code pool |
|
|
253
|
+
| `result-shapes.test.ts` | passive output-shape learning: value-free bounded inference, merging, 256-entry LRU eviction, 24-hour expiry, runtime isolation, read-only admission, declared-schema precedence, definition-change invalidation, discovery provenance, and failure isolation |
|
|
252
254
|
| `revenuecat-provider.test.ts` / `revenuecat-registry.test.ts` | the RevenueCat proxy's per-project key scoping and account-wide OAuth guides, its purpose-bearing summary, the argued borderline verdicts in its digest-free manifest, and the deliberately unclassified `render-paywall-screenshot`; then two project-scoped keys as two connectors in a real deployment |
|
|
253
255
|
| `server.test.ts` | end-to-end `/mcp` (401 → compact initialize instructions → seven compact definitions with bounded connector inventory → complete usage skill → `call_tool`), conditional guide pointers, open routes, Clerk `.well-known` metadata without network, code mode, and deferred catalog reads through both discovery surfaces |
|
|
254
256
|
| `server-route-contracts.test.ts` | the route contracts `server.ts` must keep byte-identical: every built-in answered ahead of connector routes inside the security wrapper, open data-free shells with framing denied, per-route auth and same-origin requirements with exact 401/403/405 bodies, and OAuth `verifyState`-before-`finishAuth` ordering |
|
|
@@ -412,33 +412,49 @@ retries.
|
|
|
412
412
|
|
|
413
413
|
OAuth per connector instance, stored in connector-scoped storage, is the
|
|
414
414
|
default. The provider's own headless credential — a personal API key, a
|
|
415
|
-
restricted key, a service account — is supported
|
|
416
|
-
auth, documented as a secret rather than configuration, and
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
415
|
+
restricted key, a service account — is supported two ways: explicit `headers`
|
|
416
|
+
auth, documented as a secret rather than configuration, and `{ type:
|
|
417
|
+
"credential" }`, which declares an operator slot and takes the same secret from
|
|
418
|
+
`/credentials` instead. Either way it is paired with the narrowest mode the
|
|
419
|
+
deployment can use, and the framing matches the provider's *published* contract
|
|
420
|
+
for the MCP endpoint — not a convention borrowed from that provider's other
|
|
421
|
+
APIs, and not this repository's earlier example, which is the same claim wearing
|
|
422
|
+
a circle. `requireHttps` is set. Recovery from an expired authorization is the
|
|
423
|
+
ordinary `auth_required` → `authorize_connector` route, which returns the
|
|
424
|
+
consent URL for OAuth and the `/credentials` handoff for a declared slot.
|
|
420
425
|
|
|
421
426
|
*Why:* one route back from an expired credential is what keeps a failed call
|
|
422
427
|
from becoming an abandoned task. *Cost:* wrong-tool selection.
|
|
423
428
|
|
|
424
|
-
### P10 —
|
|
429
|
+
### P10 — Nothing probes a credential unasked; a declared slot may be tested on request
|
|
425
430
|
|
|
426
|
-
A proxy declares
|
|
427
|
-
|
|
428
|
-
|
|
431
|
+
A proxy declares an operator credential slot exactly when its auth is `{ type:
|
|
432
|
+
"credential" }`, and then it inherits H12 whole
|
|
433
|
+
([#439](https://github.com/zackbart/connecta/issues/439)). The other two shapes
|
|
434
|
+
declare no slot and hold nothing for the credentials page: OAuth lives in
|
|
429
435
|
connector-scoped storage and is exercised by the authorization flow itself,
|
|
430
|
-
while a
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
`authorize_connector` route attached (P9).
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
436
|
+
while a `headers` key arrives as deployment configuration. H12 is owed in every
|
|
437
|
+
shape, and a proxy pays it in two places that do not depend on a slot:
|
|
438
|
+
construction throws when a recognizable credential contradicts the declared mode
|
|
439
|
+
(P4) — a check a vault-managed key cannot get, because there is nothing in the
|
|
440
|
+
deployment file to read — and a dead, revoked, or absent credential fails loudly
|
|
441
|
+
at use as `auth_required` with the `authorize_connector` route attached (P9).
|
|
442
|
+
|
|
443
|
+
`testCredential` exists only behind the operator-pressed Test action on
|
|
444
|
+
`/credentials`, and only for a declared slot. It connects with the stored value
|
|
445
|
+
and reports how many tools the downstream served, which is the whole honest
|
|
446
|
+
check for a proxy: which account, project, or mode a key reaches is the
|
|
447
|
+
provider's answer, not Connecta's. That is not the shape
|
|
448
|
+
[#179](https://github.com/zackbart/connecta/issues/179) removed. What was
|
|
449
|
+
removed is the *unasked* probe — a liveness call every deployment pays on a
|
|
450
|
+
schedule or at startup to answer a question only a misconfigured one has. A
|
|
451
|
+
human clicking Test has asked, `api()` has had that button since the vault
|
|
452
|
+
existed, and nothing here probes on its own: no timer, no warmup, no check on
|
|
453
|
+
the read path.
|
|
439
454
|
|
|
440
455
|
*Why:* an unasked-for liveness probe spends a call on every deployment to answer
|
|
441
|
-
a question only a misconfigured one has
|
|
456
|
+
a question only a misconfigured one has; a requested one spends a call the
|
|
457
|
+
person requesting it chose. *Cost:* result size.
|
|
442
458
|
|
|
443
459
|
### P11 — Connecta classifies the transport; the downstream owns the tool error
|
|
444
460
|
|
|
@@ -666,8 +682,8 @@ than by reading:
|
|
|
666
682
|
| P4 | endpoint or mode option exists, with the documented default (or no default, where none is safe) |
|
|
667
683
|
| P5 | reads and writes are named lists; an unlisted tool resolves to not-read-only; a reviewed destructive name beats a contradictory `readOnlyHint: true` |
|
|
668
684
|
| P6, P8 | the guide contains the catalog-varies note and the id-resolution rule |
|
|
669
|
-
| P9 | `auth` defaults to OAuth and `requireHttps` is set |
|
|
670
|
-
| P10 |
|
|
685
|
+
| P9 | `auth` defaults to OAuth and `requireHttps` is set; a credential-auth shape frames the key the way the provider's MCP documentation does |
|
|
686
|
+
| P10 | a `credential` slot exactly when auth is `{ type: "credential" }`; `testCredential` runs only from the operator's Test action, never on a timer or a read path; the mode/key contradiction still throws at construction |
|
|
671
687
|
| P11 | an authorization failure surfaces as `auth_required`; a downstream tool error is returned unchanged, with no code chosen from its prose |
|
|
672
688
|
| P12 | a declared budget matches a citable documented limit, or the absence is justified in the guide |
|
|
673
689
|
| P13 | classification lists are maintained in one place per provider and built into the manifest the wrapper classifies from, so the drift check compares against the same fact the caller is served |
|
|
@@ -80,6 +80,26 @@ connectors: [
|
|
|
80
80
|
]
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
Neither key has to be a runtime secret. Declare the slot instead and each
|
|
84
|
+
connector's key is pasted, tested, and rotated on `/credentials`:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
connectors: [
|
|
88
|
+
revenuecat("bepresent_ios", {
|
|
89
|
+
purpose: "Subscription state for the BePresent iOS project",
|
|
90
|
+
auth: { type: "credential", credential: { label: "API v2 secret key" } },
|
|
91
|
+
}),
|
|
92
|
+
revenuecat("biblescroll", {
|
|
93
|
+
purpose: "Subscription state for the BibleScroll project",
|
|
94
|
+
auth: { type: "credential", credential: { label: "API v2 secret key" } },
|
|
95
|
+
}),
|
|
96
|
+
]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Two ids, two slots, two single-project catalogs — the `credential` option is
|
|
100
|
+
optional, and omitting it gives the same "API v2 secret key" label. See
|
|
101
|
+
[storage and credentials](./storage-and-credentials.md#a-remote-mcp-connectors-static-credential).
|
|
102
|
+
|
|
83
103
|
That is config-as-code doing what an account model would otherwise do: one
|
|
84
104
|
credential per connector, each with its own catalog, storage namespace, health,
|
|
85
105
|
and admission counters. The two share a title, because Connecta cannot know
|
|
@@ -116,7 +136,9 @@ Keys are prefixed `sk_`, are issued read-only or write-enabled, and can be
|
|
|
116
136
|
revoked at any time by a project Admin. RevenueCat's setup guidance is to "use
|
|
117
137
|
a write-enabled key if you plan to create/modify resources"; "a read-only key
|
|
118
138
|
works if you only need to view data". Keep the key in the runtime's secret
|
|
119
|
-
store, never in the deployment file
|
|
139
|
+
store, never in the deployment file — or declare
|
|
140
|
+
`auth: { type: "credential" }` and let the operator hold it in the vault
|
|
141
|
+
instead, which is the shape the two-project example above uses.
|
|
120
142
|
|
|
121
143
|
**Connecta does not filter writes for a read-only key.** It has no way to tell
|
|
122
144
|
which kind a key is without spending a call, so every write in the catalog is
|
|
@@ -34,6 +34,61 @@ Credential mutation is intentionally narrower than MCP access:
|
|
|
34
34
|
The vault is read for each call. Once an operator saves a replacement,
|
|
35
35
|
the agent can retry immediately without restarting or redeploying Connecta.
|
|
36
36
|
|
|
37
|
+
## A remote MCP connector's static credential
|
|
38
|
+
|
|
39
|
+
`remoteMcp()` accepts a third auth shape beside OAuth and literal headers:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
remoteMcp("revenuecat_bepresent", {
|
|
43
|
+
url: "https://mcp.revenuecat.ai/mcp",
|
|
44
|
+
auth: { type: "credential", credential: { label: "API v2 secret key" } },
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The connector, its endpoint, and the credential *slot* stay declared in code;
|
|
49
|
+
only the secret arrives through `/credentials`. That is the same boundary
|
|
50
|
+
`api()` has always had, and the reason a project-wide key no longer has to be a
|
|
51
|
+
Worker secret or an environment variable
|
|
52
|
+
([#439](https://github.com/zackbart/connecta/issues/439)).
|
|
53
|
+
|
|
54
|
+
`header` defaults to `Authorization` and `scheme` to `Bearer`. `scheme: null`
|
|
55
|
+
sends the stored value verbatim, which is what Linear's personal API keys
|
|
56
|
+
expect. A scheme whose last token is `Basic` declares HTTP Basic credentials, so
|
|
57
|
+
the stored `user:secret` is base64-encoded first — `"Basic"` produces
|
|
58
|
+
`Basic <base64>`, and Mixpanel's documented `"Bearer Basic"` produces
|
|
59
|
+
`Bearer Basic <base64>`. There is one reserved `value` field and no multi-field
|
|
60
|
+
header composition: named `credential.fields` are refused at construction.
|
|
61
|
+
|
|
62
|
+
A stored value is checked before anything frames it: a line break or other
|
|
63
|
+
control character — what a key pasted across two lines leaves behind — is
|
|
64
|
+
refused as `auth_required` with a message naming the problem and never the
|
|
65
|
+
value. That check exists because the runtime that rejects such a header quotes
|
|
66
|
+
the whole offending value back in its `TypeError`, and that message would
|
|
67
|
+
otherwise reach the agent, the operator page, and the activity log. Behind it,
|
|
68
|
+
any error whose message quotes the credential or the header it became is
|
|
69
|
+
discarded whole and replaced; nothing is masked or truncated, because a
|
|
70
|
+
redaction that keeps part of a secret is still a leak.
|
|
71
|
+
|
|
72
|
+
An empty slot is not a boot failure and not a silently absent connector. The
|
|
73
|
+
connector is present, its status reads `auth_required`, calls fail with the same
|
|
74
|
+
typed error a missing OAuth grant produces, and `authorize_connector` returns
|
|
75
|
+
the `/credentials` handoff. With no vault configured at all, the failure names
|
|
76
|
+
`credentials.encryptionKey`, and Connecta already warned at startup.
|
|
77
|
+
|
|
78
|
+
The vault is read before any cached downstream client is trusted, so a rotation
|
|
79
|
+
lands on the next call rather than the next deploy. Connecta compares a SHA-256
|
|
80
|
+
digest of the value the cached client connected with; a different digest closes
|
|
81
|
+
that client and reconnects. The plaintext lives in the connect attempt's local
|
|
82
|
+
scope, never on connector state, never in a log, and never in a status or error
|
|
83
|
+
message. A cleartext `http://` destination warns at construction here exactly as
|
|
84
|
+
it does for literal headers — who owns the secret changed, not what the wire
|
|
85
|
+
carries.
|
|
86
|
+
|
|
87
|
+
`/credentials`' Test action connects with the stored value and reports how many
|
|
88
|
+
tools the downstream served. That is the whole honest check for a proxy: which
|
|
89
|
+
account, project, or mode the key reaches is the provider's answer, not
|
|
90
|
+
Connecta's.
|
|
91
|
+
|
|
37
92
|
## Downstream OAuth
|
|
38
93
|
|
|
39
94
|
`remoteMcp()` stores dynamic client registration, tokens, PKCE material, state,
|
package/documentation/stripe.md
CHANGED
|
@@ -95,12 +95,32 @@ Use a restricted key, not a secret key, and scope it to the operations the
|
|
|
95
95
|
agent actually needs; Stripe's own guidance is to "limit your agent's access to
|
|
96
96
|
exactly the functionality it requires". Keep it in the runtime's secret store.
|
|
97
97
|
|
|
98
|
+
The same key can come from `/credentials` instead:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
stripe("stripe_sandbox", {
|
|
102
|
+
mode: "sandbox",
|
|
103
|
+
purpose: "Automated billing rehearsal",
|
|
104
|
+
auth: { type: "credential" },
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`mode` is required either way — a static key answers for exactly one
|
|
109
|
+
environment and cannot report which. The literal-header form is checked against
|
|
110
|
+
the key's `_live_`/`_test_` prefix at construction; an operator-managed key is
|
|
111
|
+
not in the deployment file to read, so the declared mode stands alone and a key
|
|
112
|
+
pointed at the other environment fails at Stripe. Declare the mode carefully:
|
|
113
|
+
that check is the one guard Connecta can offer, and this shape does not get it.
|
|
114
|
+
See
|
|
115
|
+
[storage and credentials](./storage-and-credentials.md#a-remote-mcp-connectors-static-credential).
|
|
116
|
+
|
|
98
117
|
Organization accounts in one OAuth session are not Stripe Connect connected
|
|
99
118
|
accounts. Connect platforms can act as a connected account with
|
|
100
119
|
`connectedAccount`, which adds Stripe's documented `Stripe-Account` header at
|
|
101
120
|
connector construction. Stripe does not support OAuth for connected-account
|
|
102
|
-
calls,
|
|
103
|
-
|
|
121
|
+
calls, and `Stripe-Account` is a second header beside the credential's own,
|
|
122
|
+
which the operator-managed shape does not assemble — so this requires a
|
|
123
|
+
restricted key through `headers` auth and throws otherwise:
|
|
104
124
|
|
|
105
125
|
```ts
|
|
106
126
|
stripe("merchant_42", {
|
|
@@ -57,7 +57,7 @@ exist so far:
|
|
|
57
57
|
| --- | --- | --- |
|
|
58
58
|
| **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
|
|
59
59
|
| **A** | 0.10.2 – 0.15.1 | `.env.example`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `src/index.ts`, `tsconfig.json` |
|
|
60
|
-
| **B** | 0.16.0 – 0.18.
|
|
60
|
+
| **B** | 0.16.0 – 0.18.3 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
|
|
61
61
|
|
|
62
62
|
Generation A is a decade in template years and identifying it precisely does
|
|
63
63
|
not matter, because you are about to reconstruct it exactly rather than guess
|
|
@@ -106,7 +106,7 @@ know what to preserve, once to know what to re-verify at the end.
|
|
|
106
106
|
### Bump the pin and install
|
|
107
107
|
|
|
108
108
|
```sh
|
|
109
|
-
npm pkg set dependencies.@zackbart/connecta=0.18.
|
|
109
|
+
npm pkg set dependencies.@zackbart/connecta=0.18.3
|
|
110
110
|
npm install
|
|
111
111
|
```
|
|
112
112
|
|
|
@@ -130,7 +130,7 @@ Generate the *current* template beside the base you already made, into the same
|
|
|
130
130
|
`$SCRATCH`:
|
|
131
131
|
|
|
132
132
|
```sh
|
|
133
|
-
(cd "$SCRATCH" && npx @zackbart/connecta@0.18.
|
|
133
|
+
(cd "$SCRATCH" && npx @zackbart/connecta@0.18.3 init current)
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
You now have a three-way merge with a real base: `$SCRATCH/base` is what this
|
|
@@ -186,7 +186,7 @@ A deployment older than 0.10.2 has no base to diff against. Do not try to
|
|
|
186
186
|
manufacture one. Instead:
|
|
187
187
|
|
|
188
188
|
1. `SCRATCH=$(mktemp -d)`, then
|
|
189
|
-
`(cd "$SCRATCH" && npx @zackbart/connecta@0.18.
|
|
189
|
+
`(cd "$SCRATCH" && npx @zackbart/connecta@0.18.3 init current)` — there is no
|
|
190
190
|
`base` leg here, only the current template to read from.
|
|
191
191
|
2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
|
|
192
192
|
`src/index.ts`**.
|
|
@@ -207,6 +207,34 @@ first, so cross them bottom-up: start at the oldest one still above this
|
|
|
207
207
|
deployment's pin and work back up the page, because each boundary assumes the
|
|
208
208
|
older ones are already done.
|
|
209
209
|
|
|
210
|
+
### 0.18.2 → 0.18.3
|
|
211
|
+
|
|
212
|
+
Nothing throws for an existing deployment, and the version bump alone crosses
|
|
213
|
+
this boundary. Successful explicitly read-only calls whose provider declares
|
|
214
|
+
no output schema now teach later discovery the result's field names and broad
|
|
215
|
+
JSON types. The open optional-field shape is labeled
|
|
216
|
+
`outputSchemaSource: "observed"`, lives only in a bounded runtime cache, and
|
|
217
|
+
starts cold after 24 hours, a process restart, or Worker isolate eviction. No
|
|
218
|
+
configuration or storage migration is involved, and discovery still never
|
|
219
|
+
executes a tool.
|
|
220
|
+
|
|
221
|
+
### 0.18.1 → 0.18.2
|
|
222
|
+
|
|
223
|
+
Nothing throws for an existing deployment, and the version bump alone crosses
|
|
224
|
+
it. The release adds a third `auth` shape to `remoteMcp()` and every maintained
|
|
225
|
+
hosted connection — `{ type: "credential" }` — under which the connector
|
|
226
|
+
declares an operator slot on `/credentials` and reads the pasted value on each
|
|
227
|
+
request. A deployment carrying a static key as a runtime secret
|
|
228
|
+
(`auth: { type: "headers", headers: { Authorization: env.KEY } }`) keeps
|
|
229
|
+
working unchanged; moving it behind `/credentials` is an edit to the connector's
|
|
230
|
+
`auth` and one paste on the operator page, and needs `credentials.encryptionKey`
|
|
231
|
+
configured — a deployment without a vault gets a startup warning and
|
|
232
|
+
`recovery: "unavailable"` at use for that connector, not a boot failure. Two
|
|
233
|
+
Linear notes: the `headers` example in `documentation/linear.md` now shows
|
|
234
|
+
`Bearer ${key}` (Linear's MCP server documents that framing), and the credential
|
|
235
|
+
shape sends `Bearer` by default; a `headers` connector already sending a bare
|
|
236
|
+
key is untouched.
|
|
237
|
+
|
|
210
238
|
### 0.18.0 → 0.18.1
|
|
211
239
|
|
|
212
240
|
Nothing throws, no option moves, and every deployment crosses this on the
|
package/ethos.md
CHANGED
|
@@ -111,11 +111,11 @@ proposing one without a new argument is not.
|
|
|
111
111
|
| View-initiated read calls from program UI | accepted | named bindings materially improve refresh, cursor pagination, and drill-down without persistence or a new tool; the trusted shell delegates only to the existing fail-closed `call_tool`, and the one-string UI remains display-only ([evidence](./documentation/program-ui-read-calls.md), [#287](https://github.com/zackbart/connecta/issues/287), [#289](https://github.com/zackbart/connecta/issues/289)) |
|
|
112
112
|
| View-initiated mutation calls from program UI | gated | live-read utility says nothing about write consent: a click is not approval, stale/replayed effects need a host-tested story, and the ordinary destructive path keeps the action in the transcript ([#287](https://github.com/zackbart/connecta/issues/287)) |
|
|
113
113
|
| Result sampling on the catalog surface (`sample` / `dryRun`) | refused | sampling is execution and cannot ride a catalog read; most tools carry required arguments no sampler can invent, and undeclared `outputSchema` (measured 0/30 and 3/30 on real deployments) is a real gap that is not a sampleable one — a program that checks the shape before rendering already hands back the first record inside the run it was going to make anyway, at zero new surface ([#282](https://github.com/zackbart/connecta/issues/282)) |
|
|
114
|
+
| Passive observed output schemas | accepted | the sampling refusal stands: discovery originates no call and invents no arguments; instead, a successful explicitly read-only call whose provider declared no `outputSchema` records field names and broad JSON types only, under strict depth, breadth, property-name, node, and byte bounds, then merges that open optional-field shape in a 256-entry process-local cache for 24 hours; property names may be user-authored, search and describe label the shape `outputSchemaSource: "observed"`, a materially changed tool definition cannot inherit it, a provider declaration always wins, and no argument, scalar value, raw result, code, credential, or error is retained; this is new evidence rather than a rewrite of #282's facts: Blacksmith measured a value-free warm shape cache cutting one Linear code-mode task from 116.6 s / $1.91 to 56.6 s / $1.06, while live Connecta catalogs measured 246/378 missing on BePresent and 0/90 missing on OneMany ([study](https://www.blacksmith.sh/blog/code-smith-code-mode), [#442](https://github.com/zackbart/connecta/issues/442)) |
|
|
114
115
|
| Legacy embedded `UIResource` delivery | refused | superseded upstream and rendered by none of the clients connecta faces; per-request minted URIs also fight the caching the Apps spec assumes ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
115
116
|
## Invariants
|
|
116
117
|
|
|
117
|
-
One line each; the enforcing tests live beside the subsystem documentation.
|
|
118
|
-
Breaking one is not a bug fix — it is a design change wearing a disguise.
|
|
118
|
+
One line each; the enforcing tests live beside the subsystem documentation. Breaking one is not a bug fix — it is a design change wearing a disguise.
|
|
119
119
|
|
|
120
120
|
- **Fail-closed read-only.** A missing, false, or contradictory annotation
|
|
121
121
|
never gets the benefit of the doubt.
|
|
@@ -131,6 +131,7 @@ Breaking one is not a bug fix — it is a design change wearing a disguise.
|
|
|
131
131
|
is never cached, persisted, or served as if it were small.
|
|
132
132
|
- **Activity is payload-free by construction.** The event type has nowhere to
|
|
133
133
|
put arguments, results, code, or raw error text.
|
|
134
|
+
- **An observed shape is never a declaration.** It contains field names and broad JSON types only, remains open and optional, is labeled on discovery, and disappears behind any provider-declared output schema.
|
|
134
135
|
- **Credentials never leave the host.** Encrypted at rest, readable only by
|
|
135
136
|
the owning connector, never rendered by any surface.
|
|
136
137
|
- **Import-graph purity.** Nothing reachable from the root entry imports a
|
|
@@ -145,5 +146,4 @@ Breaking one is not a bug fix — it is a design change wearing a disguise.
|
|
|
145
146
|
- **Structural mistakes throw at construction.** A deployment that boots into
|
|
146
147
|
the wrong shape is worse than one that refuses to boot.
|
|
147
148
|
|
|
148
|
-
---
|
|
149
149
|
Connecta began as a radical simplification of [executor](https://github.com/UsefulSoftwareCo/executor); the table above is the record of that simplification holding.
|
|
@@ -123,6 +123,11 @@ function build(env: Env) {
|
|
|
123
123
|
auth: {
|
|
124
124
|
type: "headers",
|
|
125
125
|
headers: { Authorization: `Bearer ${env.DOWNSTREAM_TOKEN}` },
|
|
126
|
+
// The vault-backed alternative for a downstream that authenticates
|
|
127
|
+
// with a static key: the operator pastes it at /credentials and
|
|
128
|
+
// rotates it there, so no Worker secret holds it.
|
|
129
|
+
// type: "credential",
|
|
130
|
+
// credential: { label: "Notion internal integration token" },
|
|
126
131
|
},
|
|
127
132
|
}),
|
|
128
133
|
api("echo", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zackbart/connecta",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "One MCP to rule them all — a single MCP endpoint aggregating many downstream connectors behind a code-first surface of seven meta-tools.",
|