@zackbart/connecta 0.10.1 → 0.10.2

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.
Files changed (69) hide show
  1. package/AGENTS.md +113 -0
  2. package/CHANGELOG.md +51 -0
  3. package/README.md +53 -9
  4. package/bin/connecta.mjs +272 -0
  5. package/dist/catalog-service.d.ts +39 -1
  6. package/dist/catalog-service.d.ts.map +1 -1
  7. package/dist/catalog-service.js +133 -11
  8. package/dist/catalog-service.js.map +1 -1
  9. package/dist/catalog.d.ts +17 -0
  10. package/dist/catalog.d.ts.map +1 -1
  11. package/dist/catalog.js +113 -13
  12. package/dist/catalog.js.map +1 -1
  13. package/dist/execute.d.ts +45 -1
  14. package/dist/execute.d.ts.map +1 -1
  15. package/dist/execute.js +265 -68
  16. package/dist/execute.js.map +1 -1
  17. package/dist/invocation.d.ts.map +1 -1
  18. package/dist/invocation.js +1 -5
  19. package/dist/invocation.js.map +1 -1
  20. package/dist/meta-tools.d.ts +1 -0
  21. package/dist/meta-tools.d.ts.map +1 -1
  22. package/dist/meta-tools.js +410 -12
  23. package/dist/meta-tools.js.map +1 -1
  24. package/dist/skills.d.ts +1 -1
  25. package/dist/skills.d.ts.map +1 -1
  26. package/dist/skills.js +1 -1
  27. package/dist/tool-safety.d.ts +10 -0
  28. package/dist/tool-safety.d.ts.map +1 -0
  29. package/dist/tool-safety.js +12 -0
  30. package/dist/tool-safety.js.map +1 -0
  31. package/dist/version.d.ts +1 -1
  32. package/dist/version.js +1 -1
  33. package/documentation/architecture.md +7 -0
  34. package/documentation/auth.md +58 -0
  35. package/documentation/call-admission.md +7 -0
  36. package/documentation/code-first-exploration.md +292 -0
  37. package/documentation/code-mode.md +697 -0
  38. package/documentation/connector-guides.md +7 -0
  39. package/documentation/connectors.md +63 -0
  40. package/documentation/mcp-2026-07-28.md +46 -0
  41. package/documentation/meta-tools.md +167 -0
  42. package/documentation/operations.md +7 -0
  43. package/documentation/operator-ui.md +7 -0
  44. package/documentation/request-admission.md +7 -0
  45. package/documentation/storage-and-credentials.md +54 -0
  46. package/ethos.md +132 -0
  47. package/examples/node/README.md +53 -0
  48. package/examples/node/src/index.ts +73 -0
  49. package/examples/worker/README.md +160 -0
  50. package/examples/worker/src/cloudflare-kv.ts +43 -0
  51. package/examples/worker/src/d1-activity-row.ts +100 -0
  52. package/examples/worker/src/d1-activity.ts +144 -0
  53. package/examples/worker/src/index.ts +136 -0
  54. package/examples/worker/wrangler.jsonc +26 -0
  55. package/package.json +11 -1
  56. package/src/catalog-service.ts +177 -15
  57. package/src/catalog.ts +143 -12
  58. package/src/execute.ts +372 -96
  59. package/src/invocation.ts +1 -8
  60. package/src/meta-tools.ts +504 -11
  61. package/src/skills.ts +1 -1
  62. package/src/tool-safety.ts +15 -0
  63. package/src/version.ts +1 -1
  64. package/templates/node/.env.example +5 -0
  65. package/templates/node/AGENTS.md +19 -0
  66. package/templates/node/README.md +33 -0
  67. package/templates/node/package.json +23 -0
  68. package/templates/node/src/index.ts +43 -0
  69. package/templates/node/tsconfig.json +12 -0
@@ -0,0 +1,7 @@
1
+ # Connector guides
2
+
3
+ > **Stub.** The old manual was retired in the phase-1 docs restructure. This
4
+ > document will be rewritten as an agent-facing guide — what the subsystem is
5
+ > for, how to work on it, and what it must never do — once the ideas in
6
+ > [ethos.md](../ethos.md) settle. The prior text lives in git history as
7
+ > `docs/connector-guides.md`.
@@ -0,0 +1,63 @@
1
+ # Connectors
2
+
3
+ Connectors are the boundary between Connecta's fixed meta-tool surface and
4
+ downstream capabilities. `api()` defines a deliberate HTTP API surface;
5
+ `remoteMcp()` aggregates another MCP endpoint. Both publish the same tool
6
+ definitions and pass through the same catalog, read-only admission, invocation,
7
+ result-size, and activity paths.
8
+
9
+ Connector instances are deployment configuration. They are not registered or
10
+ reconfigured at runtime. Request-local clients, transports, abort signals, and
11
+ catalogs must be released with the request that created them.
12
+
13
+ ## MCP version skew
14
+
15
+ Connecta deliberately sits between protocol generations
16
+ ([full revision inventory](./mcp-2026-07-28.md)):
17
+
18
+ - **Inbound:** `/mcp` serves both the 2026-07-28 revision and legacy 2025
19
+ clients. Modern clients negotiate with `server/discover` and do not send
20
+ `initialize`; legacy clients retain their initialize flow. The endpoint
21
+ remains stateless in both cases.
22
+ - **Outbound:** `remoteMcp()` probes modern downstreams and falls back to the
23
+ byte-compatible legacy flow. Legacy downstreams are normal supported
24
+ deployments, not a temporary exception.
25
+ - **Legacy sessions:** Connecta's own endpoint creates no protocol session, but
26
+ a stateful legacy downstream can still issue `Mcp-Session-Id`. Closing a
27
+ request scope explicitly sends the legacy DELETE before closing its transport.
28
+ SDK v2 `Client.close()` does not do that on Connecta's behalf, so
29
+ `terminateSession` remains required and tested.
30
+ - **Modern cache hints:** `tools/list` is deployment-fixed and returns a
31
+ one-hour private cache hint. Downstream hints do not alter Connecta's existing
32
+ five-minute fingerprinted catalog cache; that remains gated in
33
+ [#206](https://github.com/zackbart/connecta/issues/206).
34
+ - **Multi-round-trip results:** a downstream `input_required` result becomes a
35
+ non-retryable `input_required_unsupported` failure. `call_tool`, the
36
+ `execute_code` host bridge, and classic's `batch_call` all preserve the
37
+ structured code. Relaying the
38
+ opaque `requestState` is architecturally possible but gated until real hosts
39
+ and downstreams adopt it.
40
+
41
+ The compatibility policy has no automatic sunset. Dropping a revision,
42
+ session cleanup, or cursor tolerance requires an explicit design decision and
43
+ replacement evidence.
44
+
45
+ ## Catalog contract
46
+
47
+ A downstream catalog is complete or it is a failure. Follow every page until
48
+ the cursor ends, preserve schemas and annotations, and never cache or serve a
49
+ partial walk. The fixed TTL is paired with a schema fingerprint so a changed
50
+ catalog invalidates persisted results even within the time window.
51
+
52
+ Tool calls must use the shared invocation path. That keeps direct calls, batch
53
+ children, and code-mode host calls aligned on safety, retries, admission,
54
+ timeouts, validation, result guards, and typed failures.
55
+
56
+ ## Authentication
57
+
58
+ OAuth-backed MCP connectors persist their registration and tokens through the
59
+ connector-scoped storage context. Those values are bound to the authorization
60
+ server issuer discovered and validated by the SDK; see
61
+ [storage and credentials](./storage-and-credentials.md#downstream-oauth).
62
+ The callback route validates `state` before passing the complete callback query
63
+ to the SDK so RFC 9207 `iss` validation is not lost.
@@ -0,0 +1,46 @@
1
+ # MCP 2026-07-28 revision inventory
2
+
3
+ This is Connecta's disposition of every change in the 2026-07-28 MCP revision.
4
+ It records what SDK v2 owns, what Connecta implements, and what remains
5
+ deliberately gated. The migration is tracked in
6
+ [#176](https://github.com/zackbart/connecta/issues/176).
7
+
8
+ | # | Change | Verdict | Connecta action |
9
+ | --- | --- | --- | --- |
10
+ | 1 | Sessions and `Mcp-Session-Id` removed | adopt inbound; retain outbound legacy support | `/mcp` stays stateless. Keep `mcp-session-id` CORS compatibility and explicit DELETE for stateful legacy downstreams; v2 `Client.close()` does not terminate those sessions. |
11
+ | 2 | `initialize` removed; version and capabilities move to per-request `_meta` | adopt via SDK | Modern clients use the v2 envelope; legacy initialize remains served indefinitely. |
12
+ | 3 | `server/discover` required | adopt via SDK | The fetch-native handler serves discovery and the outbound client uses it for negotiation. |
13
+ | 4 | `subscriptions/listen` server push | decline | Connecta emits no notifications and remains request-scoped. |
14
+ | 5 | Multi-round-trip results on Connecta's own surface | decline | Credential recovery remains the accepted `auth_required` tool-result flow. |
15
+ | 6 | Downstream `resultType: "input_required"` | handle; gate passthrough | Return the structured non-retryable `input_required_unsupported` failure through direct, batch, and code-mode invocation. |
16
+ | 7 | `resultType` required on results | adopt via SDK | Modern results are stamped `complete`; legacy results without the field remain accepted. |
17
+ | 8 | `ttlMs` and `cacheScope` on cacheable lists | adopt | `tools/list` returns `ttlMs: 3_600_000` and `cacheScope: "private"`. |
18
+ | 9 | Honor downstream list cache hints | gated | Keep the fixed five-minute TTL plus fingerprint until refresh-churn evidence warrants complexity ([#206](https://github.com/zackbart/connecta/issues/206)). |
19
+ | 10 | Native Tasks extension | refuse | Tasks address duration, while `get_result` addresses response size. Polling plus paging would add round trips without replacing the stash. |
20
+ | 11 | Extensions capability framework | decline | No client consumes a Connecta extension declaration. |
21
+ | 12 | `Mcp-Method` and `Mcp-Name` POST headers | adopt | SDK v2 handles the headers; CORS allows `mcp-method, mcp-name`. |
22
+ | 13 | RFC 9207 `iss` validation and `application_type` | adopt via SDK | The callback preserves all query parameters for SDK validation. |
23
+ | 14 | Credentials keyed to issuer; re-register when AS changes | adopt | Version-2 credential envelopes store issuer; mismatch advances the generation epoch and invalidates registration and tokens. |
24
+ | 15 | Client ID Metadata Documents replace DCR | gated | DCR is grandfathered. Publishing deployment-specific CIMD remains [#207](https://github.com/zackbart/connecta/issues/207). |
25
+ | 16 | Full JSON Schema 2020-12 and arbitrary `structuredContent` | verify | Exotic-keyword schemas pass through compact discovery and the 2020-12 input validator. |
26
+ | 17 | Protocol error-code updates | adopt via SDK | Connecta has no source-level dependency on protocol error numbers. |
27
+ | 18 | Roots, Sampling, Logging, and HTTP+SSE deprecated | no-op | Connecta does not aggregate or initiate these surfaces. |
28
+
29
+ ## Compatibility decisions
30
+
31
+ The inbound handler serves both revisions from one endpoint. The outbound
32
+ client automatically negotiates modern service and falls back to legacy
33
+ initialize. The legacy session termination test proves that explicit
34
+ `terminateSession` remains necessary: SDK v2 closes the client transport but
35
+ does not send the downstream's session DELETE.
36
+
37
+ MRTR passthrough is gated rather than permanently refused. A stateless relay
38
+ could carry `requestState`, but Connecta has no host contract for exposing the
39
+ embedded input request, no downstream adoption evidence, and no annotation
40
+ model for the resumed call. Until those arrive, a loud typed failure is safer
41
+ than discarding the intermediate result or hanging.
42
+
43
+ Native Tasks and downstream cache hints do not replace existing mechanisms.
44
+ `get_result` pages oversized completed data; Tasks poll unfinished work.
45
+ Connecta's fingerprinted catalog cache already produces roughly 3 ms reads, so
46
+ downstream hint handling must earn its complexity with measured churn.
@@ -0,0 +1,167 @@
1
+ # Meta-tools
2
+
3
+ Connecta keeps one small tool surface in model context and resolves downstream
4
+ tools behind it. `search_tools` finds addresses, the call tools enforce safety
5
+ annotations, and `get_result` pages bounded results.
6
+
7
+ ## Which surface a deployment serves
8
+
9
+ The `executor` decides it, and there is nothing else to configure
10
+ ([#224](https://github.com/zackbart/connecta/issues/224)):
11
+
12
+ | | `tools/list` | Discovery breadth and batching |
13
+ | --- | --- | --- |
14
+ | **executor configured** | seven: `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`, `authorize_connector`, `get_result`, `skills` | `connecta.search`, `connecta.describe`, `connecta.batch` inside a program |
15
+ | **no executor** | nine: the above minus `execute_code`, plus `list_connectors`, `describe_tools`, `batch_call` | those three top-level tools |
16
+
17
+ Code-first is what a model sees. Four overlapping ways to reach one connector
18
+ became two: `search_tools` then `call_tool` for a single cold read — measurably
19
+ cheaper direct than through a program — and `execute_code` for everything wider.
20
+ The fold is worth 19.6% of the serialized tool definitions measured against the
21
+ ten-tool shape an executor-backed deployment used to serve — 10,675B to 8,587B —
22
+ and, more durably, one fewer routing decision a model makes before doing any
23
+ work. Note which baseline that is: the executor-free nine serialize to 7,207B,
24
+ so the seven-tool surface is *larger* than the row below it in that table. It
25
+ buys the program with those bytes. The [guest API contract](./code-mode.md) is
26
+ what a program is promised.
27
+
28
+ `execute_code` accepts optional `diagnostics: true` when a caller is measuring
29
+ a workflow. It adds only compact request-local timing and serialized-size
30
+ aggregates; normal calls carry no diagnostics block or response-context cost.
31
+ The measurements never contain program source, arguments, values, addresses,
32
+ credentials, logs, or raw error text.
33
+
34
+ Classic is the compatibility surface: what an executor-free deployment
35
+ necessarily serves, since the program surface the fold depends on is not there.
36
+ It is supported and tested, not an equal citizen in the docs. `surface:
37
+ "classic"` beside an executor is the only override; it produces the ten-tool
38
+ shape the [eval gate](../eval/code-first-gate/README.md)'s *incremental* arm
39
+ measures. That gate's control arm is executor-free classic and needs no
40
+ override.
41
+
42
+ Nothing became unreachable. `connecta.describe` takes the same addresses and
43
+ formats as `describe_tools`, `connecta.batch` runs the same 1–10 parallel
44
+ read-only calls as `batch_call` and returns the same typed outcomes, and an
45
+ unfiltered `connecta.search({})` browses every catalog a program can reach —
46
+ the part of `list_connectors` a model used. Live connector probing was the rest
47
+ of it, and that is an operator concern: the operator pages and `/health` own it.
48
+
49
+ ## Discovery context
50
+
51
+ Start an unknown-address lookup with two to four distinctive action/object
52
+ terms, not the full request, and omit `limit` so the default eight-result page
53
+ stays small. Set `safety: "readOnly"` when the result is headed to `call_tool`
54
+ or generated code; `safety: "approvalRequired"` finds the complementary set
55
+ that must cross `call_destructive_tool`. Omitting `safety`, or setting it to
56
+ `"all"`, preserves the complete configured catalog. This is only a discovery
57
+ filter: it neither grants authority nor changes invocation admission.
58
+ `includeSchemas: "compact"` adds each match's input and any
59
+ declared output shape; matches also carry declared behavior annotations. When
60
+ that shape is sufficient, call the returned address directly. Reserve schema
61
+ expansion — `connecta.describe` in a program, `describe_tools` on the classic
62
+ surface — for a search without schemas, an ambiguous compact shape, or exact
63
+ constraints that require `format: "json"`.
64
+
65
+ Compact search is deliberately a routing view, not a second copy of connector
66
+ documentation. Tool purposes are capped at 160 characters, connector
67
+ descriptions and property prose are omitted, required input fields render
68
+ before optional ones, and each input or output shape is capped at 1,024 UTF-8
69
+ bytes. A capped object becomes a valid required-first shape with `unknown`
70
+ types; other shapes become `unknown /* truncated */`. The match also carries
71
+ `inputSchemaTruncated` or `outputSchemaTruncated`; repeat the search with
72
+ `includeSchemas: "json"` or use the existing describe path when exact
73
+ constraints matter.
74
+
75
+ ## Result representation
76
+
77
+ For object results, `structuredContent` is the canonical full-fidelity value.
78
+ `content` carries the same complete value as compact JSON for clients that only
79
+ consume text. Keeping both follows MCP's backwards-compatibility guidance;
80
+ removing or summarizing the text copy is deferred until host-forwarding
81
+ measurements demonstrate that supported clients do not need it.
82
+
83
+ Plain-text guidance and errors remain text-only. A downstream MCP tool's native
84
+ content blocks also pass through unchanged when `call_tool` uses MCP result
85
+ mode; they are not a duplicated Connecta object result. Newly stashed JSON and
86
+ downstream content envelopes use compact serialization, so `get_result` byte
87
+ offsets and totals refer to that exact compact text.
88
+
89
+ `fields` keeps its historical flat `{ "<path>": value }` result when every
90
+ requested dot-path resolves, except that an exact downstream `$connecta` field
91
+ is always escaped under `data`. If any path misses—or that reserved name is
92
+ selected—the result carries matches under `data` and reserves `$connecta` for a
93
+ `type: "field_projection"` recovery record naming each `unmatchedFields`
94
+ entry. The discriminator means downstream fields named `data`, `projection`,
95
+ or `$connecta` remain ordinary values nested under `data`, never apparent
96
+ metadata. A declared output schema contributes a bounded `availableFields`
97
+ list and a `schemaCoverage` verdict. Only a completely analyzed, closed schema
98
+ can label paths `invalidFields`; open, patterned, tuple, unresolvable, cyclic,
99
+ `$ref`-sibling, or traversal-limited shapes stay `partial`. Traversal bounds
100
+ depth, nodes, path count, individual path characters/bytes, and cumulative path
101
+ characters/bytes before sorting or rendering. Without a schema, Connecta
102
+ reports only observed misses and does not pretend it knows the complete runtime
103
+ shape. API values and JSON-parseable downstream MCP text blocks follow the same
104
+ rule.
105
+
106
+ ## Lexical discovery
107
+
108
+ `search_tools` tokenizes tool names and descriptions at punctuation and
109
+ camel-case boundaries. Exact whole-token matches carry the most weight; a small
110
+ set of inflectional variants preserves singular/plural and verb-form recall
111
+ without allowing arbitrary mid-word substring matches. Ranking weights each
112
+ query term by its document frequency across the available catalogs in that
113
+ search, so a rare domain term outranks a ubiquitous action while action terms
114
+ still distinguish `get`, `list`, `search`, and write operations. If no tool
115
+ covers every non-conversational term, the same scorer falls back to any-term
116
+ matching and marks the result `matchMode: "partial"`.
117
+
118
+ Every partial or no-match lexical search also returns bounded `queryAnalysis`;
119
+ an all-term result needs no recovery advice. `representedTerms` occur in the
120
+ current page, `otherResultTerms` occur only in another result, and
121
+ `unmatchedTerms` have no lexical match in the catalogs that answered. Partial
122
+ results explain that no single tool covered every term and recommend splitting
123
+ distinct intents. A true negative says that no matching capability is
124
+ configured and recommends refining, connector-scoping, or browsing; when a
125
+ connector catalog was unavailable, the response includes
126
+ `unavailableConnectorCount` instead of making that stronger claim. Analysis
127
+ from a connector-filtered search includes `connectorScope` and speaks only
128
+ about that connector; `unknownConnector` distinguishes an unconfigured ID from
129
+ a known connector with no match. Analysis covers at most eight distinct terms
130
+ of at most 64 displayed characters each, marks longer input `truncated`, and
131
+ never changes lexical ranking.
132
+
133
+ ## Authorization recovery
134
+
135
+ Every typed `auth_required` call failure uses the same envelope:
136
+
137
+ ```json
138
+ {
139
+ "code": "auth_required",
140
+ "message": "...",
141
+ "retryable": false,
142
+ "connector": "service",
143
+ "operation": "service.read",
144
+ "recovery": "oauth",
145
+ "nextAction": {
146
+ "tool": "authorize_connector",
147
+ "arguments": { "connector": "service" },
148
+ "operatorHandoff": "Give the URL and instructions it returns to the operator."
149
+ },
150
+ "retry": "Retry service.read after the operator completes recovery."
151
+ }
152
+ ```
153
+
154
+ `recovery` is `oauth`, `operator_config`, or `unavailable`. Call
155
+ `authorize_connector` only after this error. It returns the class-specific
156
+ handoff:
157
+
158
+ - `oauth`: an `authorizationUrl` and consent instructions;
159
+ - `operator_config`: an `operatorUrl` ending in `/credentials`, plus the
160
+ declared credential label and field names/guidance; or
161
+ - `unavailable`: an honest deployment/configuration message.
162
+
163
+ The tool accepts no secret. `force` applies only to OAuth and may discard its
164
+ stored grant before restarting consent. Static credential values are written
165
+ only through the same-origin, Clerk-operator credential route. After OAuth
166
+ consent or an operator update, retry the original operation; a static update is
167
+ read from the vault on the next call and needs no redeploy.
@@ -0,0 +1,7 @@
1
+ # Operations
2
+
3
+ > **Stub.** The old manual was retired in the phase-1 docs restructure. This
4
+ > document will be rewritten as an agent-facing guide — what the subsystem is
5
+ > for, how to work on it, and what it must never do — once the ideas in
6
+ > [ethos.md](../ethos.md) settle. The prior text lives in git history as
7
+ > `docs/operations.md`.
@@ -0,0 +1,7 @@
1
+ # Operator UI
2
+
3
+ > **Stub.** The old manual was retired in the phase-1 docs restructure. This
4
+ > document will be rewritten as an agent-facing guide — what the subsystem is
5
+ > for, how to work on it, and what it must never do — once the ideas in
6
+ > [ethos.md](../ethos.md) settle. The prior text lives in git history as
7
+ > `docs/operator-ui.md`.
@@ -0,0 +1,7 @@
1
+ # Request admission
2
+
3
+ > **Stub.** The old manual was retired in the phase-1 docs restructure. This
4
+ > document will be rewritten as an agent-facing guide — what the subsystem is
5
+ > for, how to work on it, and what it must never do — once the ideas in
6
+ > [ethos.md](../ethos.md) settle. The prior text lives in git history as
7
+ > `docs/request-admission.md`.
@@ -0,0 +1,54 @@
1
+ # Storage and credentials
2
+
3
+ The core `KVStorage` seam supports `get`, `set`, and `delete`; adapters may also
4
+ implement `list(prefix)`. Named access tokens require listing because every
5
+ token is an independent record rather than one shared, race-prone manifest.
6
+ The built-in memory and file adapters implement it, as does the Cloudflare KV
7
+ example.
8
+
9
+ Connectors may declare an operator-managed `credential` slot. When
10
+ `credentials.encryptionKey` is configured, Connecta encrypts values in the
11
+ deployment storage and exposes read-only access only through that connector's
12
+ `ctx.credential`. Values, masked values, call arguments, and raw errors never
13
+ enter model-facing recovery responses or activity records.
14
+
15
+ Proactive credential liveness probing was **removed in 0.9** by ethos decision
16
+ ([#179](https://github.com/zackbart/connecta/issues/179)). The vault, local
17
+ credential-shape drift detection, and operator-triggered credential tests remain.
18
+
19
+ Credentials fail at use. A typed `auth_required` response directs the agent to
20
+ `authorize_connector`, which returns one of the recovery modes documented in
21
+ [meta-tools](./meta-tools.md#authorization-recovery). A declared slot with a
22
+ configured vault returns a secret-free `/credentials` handoff. A missing vault
23
+ returns `recovery: "unavailable"` and names `credentials.encryptionKey`;
24
+ Connecta also warns at startup.
25
+
26
+ Credential mutation is intentionally narrower than MCP access:
27
+
28
+ - a static bearer may call tools and receive the operator handoff, but it
29
+ cannot write credentials;
30
+ - only an admitted Clerk user may use the same-origin credential mutation
31
+ routes; and
32
+ - saving, replacing, testing, or removing a value never returns that value.
33
+
34
+ The vault is read for each call. Once an operator saves a replacement,
35
+ the agent can retry immediately without restarting or redeploying Connecta.
36
+
37
+ ## Downstream OAuth
38
+
39
+ `remoteMcp()` stores dynamic client registration, tokens, PKCE material, state,
40
+ and the pending authorization URL in the connector's storage namespace.
41
+ Registration and token envelopes are bound to the validated authorization
42
+ server `issuer`. An unbound pre-0.9 envelope is upgraded in place on its first
43
+ issuer-aware read, preserving the existing grant.
44
+
45
+ If later discovery resolves a different issuer, Connecta does not send the old
46
+ client identifier or tokens to it. The provider publishes a new generation
47
+ epoch, makes every older credential namespace unreadable, cleans up the retired
48
+ values, and lets the SDK begin registration and authorization again. The same
49
+ epoch fence prevents an older isolate or late token exchange from resurrecting
50
+ the retired grant.
51
+
52
+ The OAuth callback verifies the one-shot `state` first, then hands the complete
53
+ query string—including RFC 9207 `iss`—to the SDK transport. One-shot state,
54
+ verifier, and pending URL are cleared only after a successful exchange.
package/ethos.md ADDED
@@ -0,0 +1,132 @@
1
+ # connecta — ethos
2
+
3
+ What connecta is, what it refuses to be, and the invariants every change must
4
+ preserve. This file is deliberately terse. When a proposed change contradicts a
5
+ line here, either the change is wrong or this file needs amending — in that
6
+ order, and amending it is a design decision, not a drive-by edit.
7
+
8
+ ## What this is
9
+
10
+ - **One MCP endpoint, one programmable surface.** Every integration you've
11
+ deliberately chosen sits behind a capability catalog that agents reach by
12
+ writing ordinary JavaScript, ringed by a few explicit tools — for the
13
+ boundaries code must not cross, and for the jobs a program is the wrong
14
+ shape for.
15
+ - **A deployment is a small config-as-code file.** Changing what agents can
16
+ reach is an edit and a redeploy. One deployment, one tenant, one audience —
17
+ more audiences means more deployments.
18
+ - **Two equal ways in.** `remoteMcp()` proxies a downstream MCP server;
19
+ `api()` hand-writes a deliberate tool surface over a plain HTTP API. Both
20
+ come out identical: same addresses, same catalog, same safety rules.
21
+ - **Every meta-tool earns its keep**, and the bar has gone up. The default
22
+ answer to "agents need X" is the program surface: a capability has to be
23
+ shown inexpressible through it before it earns a top-level tool, and one
24
+ that isn't worth its context cost still goes. Seven tools counting
25
+ `execute_code` where a deployment has an executor, which is what a model
26
+ sees by default; nine where it doesn't
27
+ ([#224](https://github.com/zackbart/connecta/issues/224)).
28
+ - **Safe by default.** Only tools explicitly annotated read-only are callable
29
+ without crossing the destructive boundary — directly or from generated code;
30
+ everything else goes through `call_destructive_tool`, where the MCP host can
31
+ put the question to a human. Approval is the host's job; connecta makes the
32
+ question visible.
33
+ - **One fetch-native core, two runtimes.** The same code runs unchanged on
34
+ Cloudflare Workers and in Node — a Worker or a Docker stack, your pick. Web
35
+ APIs only in the core; Node touches live behind explicit subpaths.
36
+ - **An executor is the assumed posture.** The primary surface is a program, so
37
+ the deployment connecta is written toward has one — a Dynamic Worker on
38
+ Cloudflare, QuickJS behind its optional-peer subpath on Node. Configuring one
39
+ is what selects the code-first surface; executor-free stays supported as
40
+ compatibility, not as an equal citizen. Packaging is unchanged: assumed is
41
+ about defaults, never about dependencies.
42
+ - **Observable, never administrable.** Operator pages show connector status,
43
+ masked credentials, and payload-free activity. They can rotate a secret;
44
+ they cannot add a connector, change policy, or alter what an agent can call.
45
+
46
+ ## What this isn't
47
+
48
+ - **Not a platform.** No runtime connector registration, no admin UI that
49
+ changes behavior, no policy engine, no approvals, no pauses.
50
+ - **Not a schema ingester.** No OpenAPI or GraphQL → tools. Generated tool
51
+ sprawl is the disease the meta-tools treat, not a feature to add.
52
+ - **Not multi-tenant.** No accounts dimension, no per-user credential store,
53
+ no org hierarchy. Two accounts on one service are two connector instances.
54
+ - **Not stateful.** No protocol sessions, no server push. Scope resolves per
55
+ request — which is also where the MCP spec itself has now arrived.
56
+ - **Not a nanny.** Credentials are stored safely and fail loudly at use;
57
+ connecta doesn't probe them behind your back.
58
+ - **Not a promise to strangers — yet.** Built for its author's deployments
59
+ first, published openly. Breaking changes are cheap and the version number
60
+ signals change, not stability.
61
+
62
+ ## Decisions
63
+
64
+ The record of shapes considered and refused. Proposing one again is allowed;
65
+ proposing one without a new argument is not.
66
+
67
+ | Decision | Verdict | Why |
68
+ | --- | --- | --- |
69
+ | OpenAPI / GraphQL ingestion | refused | generated tools are the disease; hand-write `api()` |
70
+ | Multi-tenancy / account model | refused | one deployment per tenant; deploy again instead |
71
+ | Policy engine, approvals, pauses | refused | the host asks the human; connecta only annotates |
72
+ | Runtime connector registration | refused | config-as-code is the security model |
73
+ | Protocol sessions & server push | refused | stateless per request |
74
+ | Resources & prompts aggregation | refused | tools only |
75
+ | Elicitation passthrough | refused | no route through a stateless aggregator |
76
+ | Repository formatter | refused | style is authored, not enforced |
77
+ | Toolkits (scoped views) | removed | never earned its keep; deploy per audience ([#178](https://github.com/zackbart/connecta/issues/178)) |
78
+ | Proactive credential liveness | removed | fail-at-use is enough ([#179](https://github.com/zackbart/connecta/issues/179)) |
79
+ | Agent credential recovery | accepted | one `auth_required` route through `authorize_connector`; only an operator handles secrets ([#192](https://github.com/zackbart/connecta/issues/192)) |
80
+ | Operator-issued MCP access tokens | accepted | named, revocable authentication gives header-capable clients a small alternative to OAuth; tokens identify callers but never scope tools or become operator credentials |
81
+ | Structured result surface | accepted | canonical `structuredContent` plus complete compact `content`; summary-only text is gated on host-forwarding evidence ([#191](https://github.com/zackbart/connecta/issues/191)) |
82
+ | Code mode (`execute_code`) | accepted | the primary read, discovery, and composition surface: smaller serialized definitions (the exploration estimated ~32%, the shipped fold measures 19.6% — see the consolidation row), far smaller results once composition and projection happen before the model sees them, and a cold-start model that read the interface without help ([exploration](./documentation/code-first-exploration.md)) |
83
+ | Code-first as the user-facing default | accepted | owner decision, 2026-07-30: "we don't need a deploy time flip, I'm the only one who uses it… We already decided it's good let's just do it." One operator, one deployment, and the exploration plus the smoke runs were enough judgment for him; the gate below was written for a user base connecta does not have ([#224](https://github.com/zackbart/connecta/issues/224)) |
84
+ | The repeated per-model eval as the flip's gate | removed | it decided a question the owner has now decided himself; [`eval/code-first-gate`](./eval/code-first-gate/README.md) remains as measurement — the arms are real deployment shapes and it still reports which surface performs — but nothing waits on its verdict ([#222](https://github.com/zackbart/connecta/issues/222)) |
85
+ | Surface consolidation to seven tools | accepted | folding `list_connectors`, `describe_tools`, and `batch_call` into the program surface deletes the overlapping routing choice between direct calls, batches, discovery, and execution; `call_tool` stays because a simple call is not cheaper through code. Measured at 19.6% fewer serialized definition bytes than the ten-tool shape the same deployment used to serve (10,675B → 8,587B), correcting the exploration's ~32% ([#224](https://github.com/zackbart/connecta/issues/224)) |
86
+ | Executor-assumed posture | accepted | the primary surface is a program, so a deployment without an executor can only ever be the compatibility shape; packaging invariants are untouched ([#224](https://github.com/zackbart/connecta/issues/224)) |
87
+ | Classic surface retention | accepted | what an executor-free deployment necessarily serves, the rollback path, and the eval's control arm; `surface: "classic"` alongside an executor is the only knob, and whether classic is ever removed is a separate future decision ([#224](https://github.com/zackbart/connecta/issues/224)) |
88
+ | Connector shortcut namespaces in programs | accepted | sugar over canonical addressing, kept but frozen — every expansion invents a collision class `<connectorId>.<toolName>` already solved ([#223](https://github.com/zackbart/connecta/issues/223)) |
89
+ | Automatic host-side projection of program results | refused | the measured win was program-authored projection; a host heuristic drops fields a program chose to return and is invisible in the transcript ([#223](https://github.com/zackbart/connecta/issues/223)) |
90
+ | Caller-visible execution diagnostics | accepted | optional request-local timing and size aggregates make catalog, connector, and executor costs distinguishable without persisting payloads, adding a tool, or charging normal responses context ([#247](https://github.com/zackbart/connecta/issues/247)) |
91
+ | `get_result` paging for program results | refused | paging rewards the unprojected return code mode exists to remove; a program can shrink anything ([#223](https://github.com/zackbart/connecta/issues/223)) |
92
+ | Stabilized workflows (programs → versioned scripts/skills) | gated | earns a surface only once real traffic shows programs that actually recur ([#225](https://github.com/zackbart/connecta/issues/225)) |
93
+ | Semantic tool search | gated | keyword search has not been shown to be the thing failing; earns its way in through [#222](https://github.com/zackbart/connecta/issues/222)'s harness ([#27](https://github.com/zackbart/connecta/issues/27)) |
94
+ | MRTR / `input_required` passthrough | gated | statelessly relayable via `requestState`, but no host or downstream emits it yet; fails loudly until adoption evidence ([#176](https://github.com/zackbart/connecta/issues/176)) |
95
+ | Native Tasks for oversized results | refused | tasks solve duration, `get_result` solves size; paging on a polling extension adds round trips for nothing ([#176](https://github.com/zackbart/connecta/issues/176)) |
96
+ | Downstream `ttlMs` cache hints | gated | fixed TTL + fingerprint is battle-tested and catalog reads are ~3 ms; earns its way in with refresh-churn evidence ([#176](https://github.com/zackbart/connecta/issues/176)) |
97
+
98
+ ## Invariants
99
+
100
+ One line each; the enforcing tests live beside the subsystem documentation.
101
+ Breaking one is not a bug fix — it is a design change wearing a disguise.
102
+
103
+ - **Fail-closed read-only.** A missing, false, or contradictory annotation
104
+ never gets the benefit of the doubt.
105
+ - **Generated code cannot mint capabilities.** Admission, credentials, and
106
+ read-only classification are enforced below the sandbox; nothing a program
107
+ does widens what it can reach.
108
+ - **Only explicitly read-only work runs inside the sandbox.** Unannotated,
109
+ write-capable, and destructive tools cross `call_destructive_tool`, where the
110
+ host can ask a human.
111
+ - **Nothing request-bound survives a request.** No transport, stream, abort
112
+ state, or later-awaited promise outlives the request that made it.
113
+ - **A downstream catalog is complete or it is a failure.** A partial catalog
114
+ is never cached, persisted, or served as if it were small.
115
+ - **Activity is payload-free by construction.** The event type has nowhere to
116
+ put arguments, results, code, or raw error text.
117
+ - **Credentials never leave the host.** Encrypted at rest, readable only by
118
+ the owning connector, never rendered by any surface.
119
+ - **Import-graph purity.** Nothing reachable from the root entry imports a
120
+ `node:` builtin.
121
+ - **The published surface is a boundary.** Heavyweight or platform-bound code
122
+ goes behind an optional-peer subpath, never into core.
123
+ - **No runtime admin.** If a browser could change what an agent can reach,
124
+ that feature is a non-goal wearing a disguise.
125
+ - **Structural mistakes throw at construction.** A deployment that boots into
126
+ the wrong shape is worse than one that refuses to boot.
127
+
128
+ ---
129
+
130
+ Connecta began as a radical simplification of
131
+ [executor](https://github.com/UsefulSoftwareCo/executor). The table above is
132
+ the record of that simplification holding.
@@ -0,0 +1,53 @@
1
+ # connecta — Node repository example
2
+
3
+ This example runs against the current package source. For an independently
4
+ installable deployment, use the root initializer instead:
5
+
6
+ ```sh
7
+ npx @zackbart/connecta init my-connecta
8
+ ```
9
+
10
+ From the package repository, run this example with:
11
+
12
+ ```sh
13
+ npm install
14
+ CONNECTA_TOKEN=dev-token npx tsx examples/node/src/index.ts
15
+ ```
16
+
17
+ It serves the prescribed seven-tool code-first surface: `execute_code` in a
18
+ bounded QuickJS child plus the six explicit boundary tools.
19
+
20
+ - MCP endpoint: `http://localhost:8787/mcp`, with
21
+ `Authorization: Bearer dev-token`
22
+ - Operator pages: Connections at `http://localhost:8787/`, Credentials at
23
+ `/credentials`, and Activity at `/activity` (paste the same token; this
24
+ bearer-only example cannot manage credentials)
25
+ - Health: `http://localhost:8787/health`
26
+
27
+ `PORT` defaults to `8787`; `CONNECTA_TOKEN` is required. Use a long random
28
+ value outside this local example.
29
+
30
+ ## The deployment contract
31
+
32
+ Keep this deployment small:
33
+
34
+ - Edit `src/index.ts` to change connectors, auth, storage, and the public URL.
35
+ - Keep `executor: quickJsExecutor()` unless you deliberately want the classic
36
+ compatibility surface.
37
+ - Keep secrets in environment variables or an external secret store. Never put
38
+ tokens in `src/index.ts`, connector guides, or committed JSON.
39
+ - Add application code only when implementing a deliberate `api()` connector.
40
+ Do not copy Connecta internals into the deployment.
41
+ - Run the repository's `npm run check` after a package change.
42
+
43
+ `fileStorage("./.connecta-state.json")` persists downstream OAuth tokens and
44
+ tool catalogs across restarts. `memoryStorage()` is sufficient only when the
45
+ deployment needs neither. Add `remoteMcp(...)` entries for downstream MCP
46
+ servers or `api(...)` connectors for HTTP APIs you deliberately expose.
47
+ Downstream OAuth also requires `publicUrl` to be an origin the browser can
48
+ reach.
49
+
50
+ Docker packaging of the same code-first shape is
51
+ [repository-only](https://github.com/zackbart/connecta/tree/main/examples/docker).
52
+ The standalone template used by the initializer is in
53
+ [`../../templates/node/`](../../templates/node/).
@@ -0,0 +1,73 @@
1
+ /**
2
+ * connecta on Node.
3
+ *
4
+ * One MCP endpoint aggregating two in-code HTTP API connectors behind the
5
+ * seven-tool code-first surface (execute_code in a QuickJS/WASM sandbox plus the
6
+ * six explicit tools), guarded by a static bearer token, with OAuth/cache state
7
+ * on disk.
8
+ *
9
+ * Run:
10
+ * CONNECTA_TOKEN=dev-token npx tsx examples/node/src/index.ts
11
+ * # then point an MCP client at http://localhost:8787/mcp with
12
+ * # Authorization: Bearer dev-token
13
+ */
14
+ import { api, bearerToken, createConnecta } from "@zackbart/connecta";
15
+ import { fileStorage, listen } from "@zackbart/connecta/node";
16
+ import { quickJsExecutor } from "@zackbart/connecta/quickjs";
17
+
18
+ const token = process.env.CONNECTA_TOKEN;
19
+ if (!token) {
20
+ throw new Error(
21
+ "CONNECTA_TOKEN is required. Refusing to start without inbound auth.",
22
+ );
23
+ }
24
+ const port = Number(process.env.PORT ?? 8787);
25
+
26
+ const connecta = createConnecta({
27
+ // fileStorage persists downstream-OAuth/cache state across restarts.
28
+ // Swap for memoryStorage() if you don't need persistence.
29
+ storage: fileStorage("./.connecta-state.json"),
30
+ auth: bearerToken(token, { subjectId: "operator" }),
31
+ // Downstream OAuth callbacks use this deployment origin.
32
+ publicUrl: `http://localhost:${port}`,
33
+ // Code mode: QuickJS runs model-written JS in a bounded disposable child.
34
+ // This line is also what selects the seven-tool code-first surface; remove it
35
+ // to serve the nine classic meta-tools instead.
36
+ executor: quickJsExecutor(),
37
+ connectors: [
38
+ api("time", {
39
+ description: "Time — current timestamp",
40
+ tools: [
41
+ {
42
+ name: "get_now",
43
+ description: "Return the current time as an ISO 8601 timestamp.",
44
+ inputSchema: { type: "object", properties: {} },
45
+ annotations: { readOnlyHint: true },
46
+ handler: async () => ({ now: new Date().toISOString() }),
47
+ },
48
+ ],
49
+ }),
50
+ api("text", {
51
+ description: "Text — string utilities",
52
+ tools: [
53
+ {
54
+ name: "upper",
55
+ description: "Uppercase the given text.",
56
+ inputSchema: {
57
+ type: "object",
58
+ properties: { text: { type: "string" } },
59
+ required: ["text"],
60
+ },
61
+ annotations: { readOnlyHint: true },
62
+ handler: async ({ text }: { text: string }) => ({
63
+ text: text.toUpperCase(),
64
+ }),
65
+ },
66
+ ],
67
+ }),
68
+ ],
69
+ });
70
+
71
+ listen(connecta, port);
72
+
73
+ console.log(`connecta listening on http://localhost:${port}/mcp`);