@zackbart/connecta 0.10.1 → 0.10.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/AGENTS.md +113 -0
- package/CHANGELOG.md +70 -0
- package/README.md +53 -9
- package/bin/connecta.mjs +272 -0
- package/dist/catalog-service.d.ts +40 -1
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +137 -12
- package/dist/catalog-service.js.map +1 -1
- package/dist/catalog.d.ts +17 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +113 -13
- package/dist/catalog.js.map +1 -1
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +40 -0
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +45 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +265 -68
- package/dist/execute.js.map +1 -1
- package/dist/invocation.d.ts.map +1 -1
- package/dist/invocation.js +34 -6
- package/dist/invocation.js.map +1 -1
- package/dist/meta-tools.d.ts +1 -0
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +412 -12
- package/dist/meta-tools.js.map +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +1 -1
- package/dist/tool-safety.d.ts +10 -0
- package/dist/tool-safety.d.ts.map +1 -0
- package/dist/tool-safety.js +12 -0
- package/dist/tool-safety.js.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +100 -1
- package/dist/validate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +7 -0
- package/documentation/auth.md +58 -0
- package/documentation/call-admission.md +7 -0
- package/documentation/code-first-exploration.md +292 -0
- package/documentation/code-mode.md +696 -0
- package/documentation/connector-guides.md +7 -0
- package/documentation/connectors.md +69 -0
- package/documentation/mcp-2026-07-28.md +46 -0
- package/documentation/meta-tools.md +185 -0
- package/documentation/operations.md +7 -0
- package/documentation/operator-ui.md +7 -0
- package/documentation/request-admission.md +7 -0
- package/documentation/storage-and-credentials.md +54 -0
- package/ethos.md +132 -0
- package/examples/node/README.md +53 -0
- package/examples/node/src/index.ts +73 -0
- package/examples/worker/README.md +160 -0
- package/examples/worker/src/cloudflare-kv.ts +43 -0
- package/examples/worker/src/d1-activity-row.ts +100 -0
- package/examples/worker/src/d1-activity.ts +144 -0
- package/examples/worker/src/index.ts +136 -0
- package/examples/worker/wrangler.jsonc +26 -0
- package/package.json +11 -1
- package/src/catalog-service.ts +181 -16
- package/src/catalog.ts +143 -12
- package/src/errors.ts +88 -1
- package/src/execute.ts +372 -96
- package/src/invocation.ts +45 -8
- package/src/meta-tools.ts +506 -11
- package/src/skills.ts +1 -1
- package/src/tool-safety.ts +15 -0
- package/src/validate.ts +128 -0
- package/src/version.ts +1 -1
- package/templates/node/.env.example +5 -0
- package/templates/node/AGENTS.md +19 -0
- package/templates/node/README.md +33 -0
- package/templates/node/package.json +23 -0
- package/templates/node/src/index.ts +43 -0
- 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,69 @@
|
|
|
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
|
+
For remote MCP tools, that path checks the catalog's advertised `inputSchema`
|
|
57
|
+
before provider dispatch. Supported mismatches become bounded, payload-free
|
|
58
|
+
`invalid_args` findings; a schema the local validator cannot evaluate passes
|
|
59
|
+
through unchanged. Connecta does not parse provider error prose to invent a
|
|
60
|
+
validation classification.
|
|
61
|
+
|
|
62
|
+
## Authentication
|
|
63
|
+
|
|
64
|
+
OAuth-backed MCP connectors persist their registration and tokens through the
|
|
65
|
+
connector-scoped storage context. Those values are bound to the authorization
|
|
66
|
+
server issuer discovered and validated by the SDK; see
|
|
67
|
+
[storage and credentials](./storage-and-credentials.md#downstream-oauth).
|
|
68
|
+
The callback route validates `state` before passing the complete callback query
|
|
69
|
+
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,185 @@
|
|
|
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.
|
|
168
|
+
|
|
169
|
+
## Argument recovery
|
|
170
|
+
|
|
171
|
+
A remote MCP tool's advertised `inputSchema` is checked in the shared
|
|
172
|
+
invocation path before admission and provider dispatch. A mismatch is the
|
|
173
|
+
non-retryable `invalid_args`, consistently across `call_tool`,
|
|
174
|
+
`call_destructive_tool`, batch outcomes, and generated-code failures. The error
|
|
175
|
+
names the connector and operation and carries bounded `validation.issues`:
|
|
176
|
+
JSON Pointer `path`, schema-keyword `code`, and expected shape. Submitted
|
|
177
|
+
values are never copied into those findings.
|
|
178
|
+
|
|
179
|
+
At most three findings are returned; `validation.truncated` says when more
|
|
180
|
+
exist. `nextAction` points to `search_tools` scoped to the same connector and
|
|
181
|
+
tool name when the compact schema is needed, while `retry` says to correct the
|
|
182
|
+
listed arguments and reissue the original operation. A schema the local
|
|
183
|
+
validator cannot evaluate passes through to the provider. Provider error prose
|
|
184
|
+
is not parsed or guessed, so an unknown format remains
|
|
185
|
+
`connector_call_failed`.
|
|
@@ -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/).
|