@zackbart/connecta 0.24.2 → 0.24.4
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 +18 -20
- package/CHANGELOG.md +205 -1
- package/README.md +5 -6
- package/dist/auth/bearer.js +2 -0
- package/dist/auth/downstream-oauth.d.ts +12 -1
- package/dist/auth/downstream-oauth.js +147 -35
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.d.ts +4 -0
- package/dist/call-admission.js +26 -0
- package/dist/catalog-drift.js +9 -4
- package/dist/catalog-service.d.ts +2 -0
- package/dist/catalog-service.js +25 -8
- package/dist/catalog.d.ts +2 -0
- package/dist/catalog.js +246 -121
- package/dist/connectors/api.d.ts +1 -1
- package/dist/connectors/api.js +21 -3
- package/dist/connectors/guarded-fetch.d.ts +6 -2
- package/dist/connectors/guarded-fetch.js +61 -24
- package/dist/connectors/remote-mcp.js +92 -57
- package/dist/errors.d.ts +28 -3
- package/dist/errors.js +60 -1
- package/dist/execute.js +85 -23
- package/dist/executor-result.js +3 -1
- package/dist/executors/quickjs-child.js +5 -1
- package/dist/executors/quickjs-protocol.d.ts +4 -0
- package/dist/executors/quickjs-runtime.d.ts +1 -1
- package/dist/executors/quickjs-runtime.js +38 -21
- package/dist/executors/quickjs.js +68 -27
- package/dist/index.d.ts +15 -1
- package/dist/index.js +36 -4
- package/dist/invocation.js +134 -93
- package/dist/mcp-result.js +3 -2
- package/dist/meta-tools.js +191 -36
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +38 -1
- package/dist/operator-ui/view.js +71 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +83 -10
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +42 -4
- package/dist/providers/stripe.d.ts +7 -1
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +11 -1
- package/dist/registry.d.ts +22 -2
- package/dist/registry.js +101 -13
- package/dist/routes/mcp.d.ts +4 -1
- package/dist/routes/mcp.js +84 -13
- package/dist/routes/oauth.js +4 -0
- package/dist/routes/shared.d.ts +1 -0
- package/dist/routes/shared.js +4 -4
- package/dist/server.js +15 -3
- package/dist/skills.js +6 -5
- package/dist/storage/file.d.ts +6 -2
- package/dist/storage/file.js +312 -34
- package/dist/storage/memory.js +12 -1
- package/dist/types.d.ts +37 -0
- package/dist/ui.js +18 -10
- package/dist/validate.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +193 -165
- package/documentation/auth.md +199 -145
- package/documentation/code-mode.md +433 -316
- package/documentation/meta-tools.md +363 -355
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +12 -10
- package/examples/worker/src/index.ts +12 -15
- package/package.json +1 -2
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/documentation/call-admission.md +0 -142
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -389
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -722
- package/documentation/request-admission.md +0 -170
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -201
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -754
- package/documentation/vercel.md +0 -241
|
@@ -1,722 +0,0 @@
|
|
|
1
|
-
# Provider conventions
|
|
2
|
-
|
|
3
|
-
The seven maintained prebuilt connections grew one at a time, and until now
|
|
4
|
-
"excellent provider" meant whatever the last author thought. This document
|
|
5
|
-
writes the judgment down so it can be argued with, audited, and reused.
|
|
6
|
-
|
|
7
|
-
There are two genuinely different provider shapes, and one convention set
|
|
8
|
-
cannot honestly cover both:
|
|
9
|
-
|
|
10
|
-
- **Hand-written HTTP providers**: `api()` surfaces where Connecta owns every
|
|
11
|
-
tool name, schema, projection, and error. Today: Cloudflare, Notion, and the
|
|
12
|
-
Vercel API surface.
|
|
13
|
-
- **Hosted-MCP proxies**: `remoteMcp()` wrappers around a server somebody else
|
|
14
|
-
operates, where the names, schemas, results, and error prose arrive as they
|
|
15
|
-
are. Today: Cloudflare, Linear, Stripe, Mixpanel, Notion, RevenueCat, and
|
|
16
|
-
Vercel MCP interfaces.
|
|
17
|
-
|
|
18
|
-
The governing principle for every convention below is the same: **keep the
|
|
19
|
-
model that interacts with connecta as efficient as possible.** A convention
|
|
20
|
-
earns its place by reducing what an agent spends discovering, selecting,
|
|
21
|
-
calling, and reading. It does not earn its place by being tidy.
|
|
22
|
-
|
|
23
|
-
Nothing here overrides [ethos.md](../ethos.md). In particular, prebuilt
|
|
24
|
-
connections remain ordinary `Connector` instances with no extra privileges,
|
|
25
|
-
their annotations fill in downstream silence rather than replacing it, and no
|
|
26
|
-
tool is ever generated from a schema document.
|
|
27
|
-
|
|
28
|
-
## How to read a convention
|
|
29
|
-
|
|
30
|
-
Every convention is one rule, one reason, and one cost. The cost is drawn from
|
|
31
|
-
a fixed vocabulary of four, because those are the four things an agent actually
|
|
32
|
-
pays:
|
|
33
|
-
|
|
34
|
-
| Cost | What it means |
|
|
35
|
-
| --- | --- |
|
|
36
|
-
| **discovery tokens** | bytes the agent reads before it can call anything |
|
|
37
|
-
| **wrong-tool selection** | it picked the wrong tool, connector, or mode |
|
|
38
|
-
| **argument retries** | the call was made and rejected, so it must be made again |
|
|
39
|
-
| **result size** | bytes the agent reads back, and the round trips paging them |
|
|
40
|
-
|
|
41
|
-
A convention that cannot name one of those four is a preference, not a
|
|
42
|
-
convention, and does not belong in this document.
|
|
43
|
-
|
|
44
|
-
## What discovery actually shows
|
|
45
|
-
|
|
46
|
-
Several conventions are budgets, and the budgets are not arbitrary — they are
|
|
47
|
-
the points at which Connecta's own surface starts dropping characters on the
|
|
48
|
-
floor. From `src/catalog.ts` and `src/catalog-service.ts`:
|
|
49
|
-
|
|
50
|
-
- **A tool description is cut to 160 characters in `search_tools`** and to 240
|
|
51
|
-
in the describe path, both with a trailing `…`, unless the caller passes
|
|
52
|
-
`fullDescriptions: true`. Prose past those points reaches an agent only when
|
|
53
|
-
it pays for the expansion.
|
|
54
|
-
- **A compact schema renders into at most 1,024 UTF-8 bytes**, and any single
|
|
55
|
-
enum or constraint annotation into at most 256. Numeric bounds, string
|
|
56
|
-
length bounds, patterns, and formats ride beside their TypeScript-like type.
|
|
57
|
-
Past a cap the renderer keeps what fits and degrades the rest — a prefix of
|
|
58
|
-
the enum plus `unknown`, a shape without the constraints that did not fit, a
|
|
59
|
-
required-first object with `unknown` types, or
|
|
60
|
-
`unknown /* truncated */` — and flags the match, which costs a describe
|
|
61
|
-
round trip to recover.
|
|
62
|
-
- **`inputKeys`, `requiredInputKeys`, and `outputKeys` come only from bounded
|
|
63
|
-
plain-object schemas.** A top-level `anyOf` has no keys to list, so a caller
|
|
64
|
-
learns nothing about the arguments without expanding the schema.
|
|
65
|
-
- **A guide summary is bounded at 120 characters.** A configured value past
|
|
66
|
-
the bound refuses construction. An omitted one defaults to the guide's first
|
|
67
|
-
meaningful body paragraph, joined across physical line wraps and shortened
|
|
68
|
-
at a readable boundary.
|
|
69
|
-
- **Search returns a connector's `id`, `title`, `guide`, and `guideSummary` —
|
|
70
|
-
never its `description`.** The description reaches an agent only as the
|
|
71
|
-
fallback summary for a guide with no usable body paragraph. Routing facts belong
|
|
72
|
-
in the title and the guide's opening paragraph; a routing fact that lives only in
|
|
73
|
-
the connector description has been written into a field the model does not
|
|
74
|
-
read. Neither the `id` nor the `title` is a lexical document, so a term drawn
|
|
75
|
-
from one of them is not a search hit — it is a no-match whose guidance names
|
|
76
|
-
the connector and points at a scoped browse.
|
|
77
|
-
|
|
78
|
-
Two construction-time checks enforce the floor beneath all of this. The hard
|
|
79
|
-
one is `api()` itself: since
|
|
80
|
-
[#340](https://github.com/zackbart/connecta/issues/340) a hand-written tool
|
|
81
|
-
throws unless it carries a non-empty description, an explicit boolean
|
|
82
|
-
`annotations.readOnlyHint`, and an `inputSchema` — where it declares one — the
|
|
83
|
-
validator can compile. The soft one is `Registry.checkConventions()`, which
|
|
84
|
-
warns about a connector with no description, and about a static tool from any
|
|
85
|
-
other source with no description or no `inputSchema`.
|
|
86
|
-
|
|
87
|
-
## Hand-written HTTP providers
|
|
88
|
-
|
|
89
|
-
Connecta owns the whole surface here, which means every miss is ours. These
|
|
90
|
-
apply to `api()`-based prebuilt connections (Cloudflare, Notion, and Vercel's
|
|
91
|
-
API surface) and are the bar any future one is written to.
|
|
92
|
-
|
|
93
|
-
None of them asks an author to re-derive transport safety. URL confinement,
|
|
94
|
-
query and body construction, `ctx.signal`, redirect refusal, credential
|
|
95
|
-
shadowing, bounded response reads, and network-failure normalization are the
|
|
96
|
-
[guarded fetch transport](./connectors.md#the-guarded-fetch-transport)'s job
|
|
97
|
-
([#341](https://github.com/zackbart/connecta/issues/341)). What the conventions
|
|
98
|
-
below still demand — H11's error mapping and H12's credential test above all —
|
|
99
|
-
is the provider knowledge no shared helper can hold.
|
|
100
|
-
|
|
101
|
-
### H1 — Identity is deployment-owned; the provider supplies everything else
|
|
102
|
-
|
|
103
|
-
The constructor takes an `id`, a required `purpose`, an optional `title`, and
|
|
104
|
-
optional `instructions` appended to — never replacing — the maintained guide.
|
|
105
|
-
A blank `purpose` throws at construction.
|
|
106
|
-
|
|
107
|
-
*Why:* an agent choosing between two instances of the same provider has only
|
|
108
|
-
the title and the guide summary to choose from. *Cost:* wrong-tool selection.
|
|
109
|
-
|
|
110
|
-
### H2 — Names are `verb_object`, and the verb is the safety class
|
|
111
|
-
|
|
112
|
-
`snake_case`, a leading verb from the small set the surface already uses
|
|
113
|
-
(`list_`, `get_`, `search_`, `create_`, `update_`, `delete_`, plus a provider's
|
|
114
|
-
own vocabulary such as `purge_` or `trash_`), and a noun that matches the
|
|
115
|
-
provider's own word for the thing. A read never opens with a write verb, and a
|
|
116
|
-
write never hides behind a neutral one. Escape hatches are named
|
|
117
|
-
`<provider>_api_<class>` so they sort together and read as generic.
|
|
118
|
-
|
|
119
|
-
*Why:* the name is the only thing lexical search indexes besides the
|
|
120
|
-
description, and it is what an agent skims first. A connector's `id` and
|
|
121
|
-
`title` are displayed, not indexed — a search for one of them matches no tool,
|
|
122
|
-
and the miss is answered by guidance naming that connector rather than by a
|
|
123
|
-
ranked result. *Cost:* wrong-tool selection.
|
|
124
|
-
|
|
125
|
-
### H3 — The selection sentence fits in 160 characters; the whole description in 240
|
|
126
|
-
|
|
127
|
-
Sentence one says what the tool returns or does, in the imperative, and is
|
|
128
|
-
complete inside 160 characters (roughly 40 tokens). Everything else — the
|
|
129
|
-
constraint, the disqualifier, the handoff — fits in the remaining 80 characters
|
|
130
|
-
(240 total, roughly 60 tokens). Detail that does not fit belongs in a property
|
|
131
|
-
description or the usage guide, both of which are fetched only when needed.
|
|
132
|
-
|
|
133
|
-
*Why:* search cuts at 160 and describe at 240 unless the caller passes
|
|
134
|
-
`fullDescriptions: true`, so anything past the budget is delivered only to an
|
|
135
|
-
agent that spends a second, larger read to get it. *Cost:* discovery tokens.
|
|
136
|
-
|
|
137
|
-
### H4 — The description names the disqualifier, not the pitch
|
|
138
|
-
|
|
139
|
-
Say what the tool will not do when an agent is likely to assume it does:
|
|
140
|
-
`search` "finds pages and data sources by **title**. Never searches content."
|
|
141
|
-
One clause of honest negative space outperforms three of capability.
|
|
142
|
-
|
|
143
|
-
*Why:* the cheapest wrong call is the one never made. *Cost:*
|
|
144
|
-
wrong-tool selection.
|
|
145
|
-
|
|
146
|
-
### H5 — Input schemas are complete, closed, and bounded
|
|
147
|
-
|
|
148
|
-
Every tool carries a hand-written `inputSchema`: a plain object at the top
|
|
149
|
-
level, `additionalProperties: false`, an accurate `required` list, an `enum` on
|
|
150
|
-
every constrained field, explicit numeric bounds on every page size and count,
|
|
151
|
-
explicit string bounds where length or shape is constrained, and a description
|
|
152
|
-
on every property — nested objects and array items included,
|
|
153
|
-
because a caller composing an array element is reading that element's fields,
|
|
154
|
-
not the parent's prose. `api()` enforces the enforceability half for free since
|
|
155
|
-
[#340](https://github.com/zackbart/connecta/issues/340): a schema the validator
|
|
156
|
-
cannot compile throws at construction, and one that only reveals itself on
|
|
157
|
-
first use fails the call rather than silently admitting unvalidated input — in
|
|
158
|
-
a surface we wrote ourselves, an unenforceable schema is our bug.
|
|
159
|
-
|
|
160
|
-
*Why:* a complete schema is the difference between one call and a call, a
|
|
161
|
-
rejection, and a repair. *Cost:* argument retries.
|
|
162
|
-
|
|
163
|
-
### H6 — A local bound says whose bound it is
|
|
164
|
-
|
|
165
|
-
When a schema's bound is the provider's, the description says so; when the
|
|
166
|
-
bound is narrower than the provider's, the description says that too. A local
|
|
167
|
-
cap that an agent is told about beats a page size that fails somewhere inside
|
|
168
|
-
the provider — but only if the agent is told.
|
|
169
|
-
|
|
170
|
-
*Why:* an unexplained refusal reads as a bug and gets retried. *Cost:*
|
|
171
|
-
argument retries.
|
|
172
|
-
|
|
173
|
-
### H7 — Schemas fit the compact renderer, or selection does not depend on the part that is cut
|
|
174
|
-
|
|
175
|
-
Keep the common path's compact input and output shapes inside 1,024 bytes and
|
|
176
|
-
each enum or constraint annotation inside 256. Numeric and string constraints
|
|
177
|
-
render when they fit. Search drops complete constraints that do not fit and
|
|
178
|
-
sets the existing truncation flag; compact describe keeps them. Where a
|
|
179
|
-
legitimate enum genuinely cannot fit — 21 DNS record types — the truncation is
|
|
180
|
-
acceptable only if the tool's name and description already carry enough for
|
|
181
|
-
selection, so the caller expands the schema to *call*, not to *choose*.
|
|
182
|
-
|
|
183
|
-
*Why:* a truncated compact shape costs a describe round trip. *Cost:* discovery
|
|
184
|
-
tokens.
|
|
185
|
-
|
|
186
|
-
### H8 — Every tool declares an `outputSchema`
|
|
187
|
-
|
|
188
|
-
Declared outputs produce `outputKeys` and let a program reduce a result without
|
|
189
|
-
first fetching one to look at. Connecta measured *declared* output schemas at 0
|
|
190
|
-
of 30 tools on one real deployment and 3 of 30 on another
|
|
191
|
-
([#282](https://github.com/zackbart/connecta/issues/282)) — nearly every tool an
|
|
192
|
-
agent meets is a shape it can only learn by calling. A maintained provider has
|
|
193
|
-
no excuse to join that majority.
|
|
194
|
-
|
|
195
|
-
*Why:* an agent that knows the shape projects before it reads. *Cost:* result
|
|
196
|
-
size.
|
|
197
|
-
|
|
198
|
-
### H9 — Every read projects, and says what it dropped
|
|
199
|
-
|
|
200
|
-
Reads return the provider's payload flattened and renamed, with plan,
|
|
201
|
-
permission, and presentation noise removed. Where the dropped detail can
|
|
202
|
-
matter, the tool takes `raw: true` and returns the untouched response; where
|
|
203
|
-
the provider itself truncated something, the projection surfaces that fact and
|
|
204
|
-
the id needed to fetch the rest, rather than handing back a confident partial.
|
|
205
|
-
The argument and result vocabulary is consistent within a connector, and the
|
|
206
|
-
mapping from the provider's own names is either identity or one mechanical rule
|
|
207
|
-
stated in the guide.
|
|
208
|
-
|
|
209
|
-
*Why:* projection is the largest single lever on what an agent reads, and a
|
|
210
|
-
silent truncation is worse than a large result. *Cost:* result size.
|
|
211
|
-
|
|
212
|
-
### H10 — Pagination is one convention per connector, with one field to branch on
|
|
213
|
-
|
|
214
|
-
List tools take an explicit page argument and a cursor, default to a page size
|
|
215
|
-
smaller than the provider's maximum because a first read should be cheap, and
|
|
216
|
-
return exactly one branchable signal — `hasMore` beside a cursor. Cursors are
|
|
217
|
-
opaque: passed back verbatim, never parsed or constructed. Where an endpoint
|
|
218
|
-
paginates differently from the rest of the connector, the schema and the guide
|
|
219
|
-
both say so instead of letting an agent discover it.
|
|
220
|
-
|
|
221
|
-
*Why:* the loop condition should not be a research project. *Cost:* result size.
|
|
222
|
-
|
|
223
|
-
### H11 — Errors are mapped to what the caller does next
|
|
224
|
-
|
|
225
|
-
The typed failure code is chosen by the caller's next move, not by the
|
|
226
|
-
provider's name for what happened: an authorization gap an operator must fix is
|
|
227
|
-
not `auth_required` if `authorize_connector` cannot fix it; a retryable failure
|
|
228
|
-
carries `retryAfterMs` when the provider says how long; an ambiguous provider
|
|
229
|
-
code gets a message that states the ambiguity rather than picking the
|
|
230
|
-
convenient reading. A call that can only fail is refused locally as
|
|
231
|
-
`invalid_args` before the round trip. Provider error prose is never parsed to
|
|
232
|
-
invent a classification.
|
|
233
|
-
|
|
234
|
-
**A downstream 404 is `not_found` — when the provider means it.** The code
|
|
235
|
-
exists because the next move is none of the others': you do not wait, you do
|
|
236
|
-
not send the agent to `authorize_connector`, you do not repair the argument
|
|
237
|
-
object. You re-address — look the id up again, or accept the absence and carry
|
|
238
|
-
on — and a program looping over ids inside `execute_code` can continue past
|
|
239
|
-
`not_found` where `connector_call_failed` would have to abort the run, reading
|
|
240
|
-
the code from a caught error or a rejected promise. The guest bridge preserves
|
|
241
|
-
the typed classification. That
|
|
242
|
-
control-flow difference is the H11 test being met; it is not a label for the
|
|
243
|
-
cause.
|
|
244
|
-
|
|
245
|
-
The qualifier is the whole rule. Map a status to `not_found` only where the
|
|
246
|
-
provider distinguishes absence from a permission gap. Where it does not —
|
|
247
|
-
Notion returns `object_not_found` both for an object that is gone and for one
|
|
248
|
-
that was never shared with the integration, and will not say which — the honest
|
|
249
|
-
code stays `connector_call_failed` (or `auth_required`, where a credential
|
|
250
|
-
really is the fix) and the message states the ambiguity, exactly as the
|
|
251
|
-
paragraph above requires. Cloudflare is the other side of the pair: a token
|
|
252
|
-
that may not touch a resource is refused with 401 or 403, so its 404 is an
|
|
253
|
-
absence and maps to `not_found`. Neither connector's mapping changed shape when
|
|
254
|
-
the code arrived; one of them changed codes.
|
|
255
|
-
|
|
256
|
-
Two boundaries. `not_found` is about a resource the *downstream* owns: an
|
|
257
|
-
address connecta cannot resolve is already framed as `unknown_address` or
|
|
258
|
-
`unknown_tool` and never reaches a connector. And it never appears on the
|
|
259
|
-
hosted-MCP proxy path, because `P1` forbids re-shaping downstream framing and
|
|
260
|
-
prose is never parsed to invent a classification — a proxied server's own
|
|
261
|
-
missing-resource error arrives as that server wrote it. The two paths do not
|
|
262
|
-
diverge on the rule; they diverge on who is entitled to apply it, which is the
|
|
263
|
-
same split every other code already has.
|
|
264
|
-
|
|
265
|
-
*Why:* a misrouted error sends an agent down a recovery path that cannot
|
|
266
|
-
succeed. *Cost:* argument retries.
|
|
267
|
-
|
|
268
|
-
### H12 — One operator credential, one cheap test, no probing
|
|
269
|
-
|
|
270
|
-
The connection declares its credential slot with a labeled field per secret and
|
|
271
|
-
implements `testCredential`/`testCredentials` with the cheapest call that proves
|
|
272
|
-
the secret is live, reporting the identity or workspace it authenticated as.
|
|
273
|
-
Connecta does not check credentials behind an operator's back; the test runs
|
|
274
|
-
when a human asks, and everything else fails loudly at use.
|
|
275
|
-
|
|
276
|
-
*Why:* "which account is this?" answered once at configuration time is a
|
|
277
|
-
question no agent has to answer by calling something. *Cost:* wrong-tool
|
|
278
|
-
selection.
|
|
279
|
-
|
|
280
|
-
### H13 — The guide carries only what a schema cannot
|
|
281
|
-
|
|
282
|
-
`usageGuide` uses the structured form: `content`, an explicit `summary`, and
|
|
283
|
-
`required: true` only when correct use depends on a sequence or convention no
|
|
284
|
-
complete schema can express. Imperative bullets, decision first, exact tool and
|
|
285
|
-
argument names, constraints with their numbers. The first content line is the
|
|
286
|
-
routing fact, because it is the summary fallback. Nothing in the guide restates
|
|
287
|
-
a schema.
|
|
288
|
-
|
|
289
|
-
*Why:* the guide is fetched into a live context window, so every line that
|
|
290
|
-
repeats a schema is paid for twice. *Cost:* discovery tokens.
|
|
291
|
-
|
|
292
|
-
### H14 — A named tool must beat the escape hatch, and the escape hatch splits by safety
|
|
293
|
-
|
|
294
|
-
Guarded raw access is an accepted shape, not a required one — a small provider
|
|
295
|
-
whose surface is genuinely finite may deliberately have none, and say so. Where
|
|
296
|
-
a provider is large and fast-moving enough to need one, it is split by safety
|
|
297
|
-
class: a GET-only tool that is explicitly read-only, a JSON mutation tool that
|
|
298
|
-
is always destructive, and an upload tool that is always destructive. The split
|
|
299
|
-
is Connecta's, never an agent-supplied HTTP method. Paths are provider-relative
|
|
300
|
-
and confined; the connector owns authentication, host, content type, and
|
|
301
|
-
framing. This is not schema ingestion — a fixed handful of stable tools, never
|
|
302
|
-
one per operation — and a *named* tool earns its place only by beating the
|
|
303
|
-
hatch on schema, projection, or safety routing.
|
|
304
|
-
|
|
305
|
-
*Why:* every named tool costs catalog bytes forever, and a thin wrapper around
|
|
306
|
-
a call the hatch already makes costs them for nothing. *Cost:* discovery
|
|
307
|
-
tokens.
|
|
308
|
-
|
|
309
|
-
## Hosted-MCP proxies
|
|
310
|
-
|
|
311
|
-
Here the downstream owns the tool names, descriptions, input schemas, result
|
|
312
|
-
shapes, pagination, and error prose. Conventions that legislate those things
|
|
313
|
-
would be fiction. What Connecta owns is the endpoint, the credential, the
|
|
314
|
-
classification, the connector's own identity, the guide, and the budget — so
|
|
315
|
-
that is what these conventions cover.
|
|
316
|
-
|
|
317
|
-
### P1 — Normalize by adding, never by rewriting
|
|
318
|
-
|
|
319
|
-
A proxy may add annotations, a title, a guide, and an admission policy. It does
|
|
320
|
-
not rewrite a downstream tool's name, description, or schema, and it does not
|
|
321
|
-
re-shape a downstream result. A rewritten description drifts silently away from
|
|
322
|
-
the schema it describes, and the agent believes the description.
|
|
323
|
-
|
|
324
|
-
*Why:* the catalog must stay a true report of what the downstream will accept.
|
|
325
|
-
*Cost:* argument retries.
|
|
326
|
-
|
|
327
|
-
### P2 — Identity is deployment-owned
|
|
328
|
-
|
|
329
|
-
Identical to H1: `id`, required `purpose`, optional `title`, and `instructions`
|
|
330
|
-
appended to the maintained guide, never replacing it, and never able to change
|
|
331
|
-
the safety classification.
|
|
332
|
-
|
|
333
|
-
*Why:* two instances of the same provider are told apart only by title and
|
|
334
|
-
guide summary. *Cost:* wrong-tool selection.
|
|
335
|
-
|
|
336
|
-
### P3 — The fact that decides routing goes in the title and the guide's first line
|
|
337
|
-
|
|
338
|
-
Whichever variant an agent must not get wrong — production versus sandbox,
|
|
339
|
-
read-only versus read-write, region, account — appears in the default `title`
|
|
340
|
-
and as the first content line of the guide. It may also appear in the
|
|
341
|
-
`description`; it may never appear *only* there, because search never returns
|
|
342
|
-
the description.
|
|
343
|
-
|
|
344
|
-
*Why:* the model reads title and guide summary at browse time and nothing else.
|
|
345
|
-
*Cost:* wrong-tool selection.
|
|
346
|
-
|
|
347
|
-
### P4 — Endpoint selection is a constructor option with the safest honest default
|
|
348
|
-
|
|
349
|
-
Where the provider publishes more than one endpoint, the option selects between
|
|
350
|
-
them and the default is the safe one. Where the provider publishes one endpoint
|
|
351
|
-
and the environment rides the credential, the mode is required with no default,
|
|
352
|
-
and construction throws when a recognizable credential contradicts the declared
|
|
353
|
-
mode. Deprecated transports stay unreachable.
|
|
354
|
-
|
|
355
|
-
*Why:* a provider-enforced scope limit is a stronger guarantee than any
|
|
356
|
-
annotation Connecta applies, and a wrong-mode write is not recoverable by
|
|
357
|
-
retrying. *Cost:* wrong-tool selection.
|
|
358
|
-
|
|
359
|
-
### P5 — Classification is a reviewed allowlist that fails closed
|
|
360
|
-
|
|
361
|
-
Reads are listed by name, writes are listed by name with their destructive
|
|
362
|
-
verdict, and anything unlisted is not read-only. The lists are supersets:
|
|
363
|
-
hosted catalogs vary by plan and feature flags, so a classified name a
|
|
364
|
-
workspace never returns costs nothing while an unclassified new one fails
|
|
365
|
-
closed onto `call_destructive_tool`. The classification fills in downstream
|
|
366
|
-
silence and otherwise preserves explicit annotations, with the single
|
|
367
|
-
fail-closed exception the [ethos](../ethos.md) accepted-prebuilt row records
|
|
368
|
-
([#315](https://github.com/zackbart/connecta/issues/315)) — a release-reviewed
|
|
369
|
-
destructive verdict outranks a contradictory `readOnlyHint: true`, because that
|
|
370
|
-
release independently established that the tool mutates existing state. An
|
|
371
|
-
additive write leaves `destructiveHint` unset.
|
|
372
|
-
|
|
373
|
-
*Why:* the fail-closed read-only invariant is not negotiable, and inflated
|
|
374
|
-
destructive copy trains humans to approve without reading. *Cost:* wrong-tool
|
|
375
|
-
selection.
|
|
376
|
-
|
|
377
|
-
### P6 — The guide says the catalog is not a fixed set
|
|
378
|
-
|
|
379
|
-
The guide tells the agent to search this connector for what the workspace
|
|
380
|
-
actually exposes rather than assuming a documented tool exists, and names the
|
|
381
|
-
plan- or beta-gated areas where absence is expected.
|
|
382
|
-
|
|
383
|
-
*Why:* a hosted catalog varies per account, and an agent that assumes ours is
|
|
384
|
-
complete spends calls proving it is not. *Cost:* wrong-tool selection.
|
|
385
|
-
|
|
386
|
-
### P7 — The guide carries the reduction advice the schemas cannot
|
|
387
|
-
|
|
388
|
-
A proxy cannot project a downstream result, so the guide tells the agent to
|
|
389
|
-
page with the cursor rather than raising the page size, and to reduce inside
|
|
390
|
-
`execute_code` before returning anything — and, where a value's rendering is
|
|
391
|
-
the provider's rule rather than the schema's, what that value means: Mixpanel
|
|
392
|
-
renders an absent boolean property as `false` in a breakdown, so the guide
|
|
393
|
-
says to confirm presence before reading `false` as a signal
|
|
394
|
-
([#430](https://github.com/zackbart/connecta/issues/430)). Structured form,
|
|
395
|
-
explicit `summary`, `required: true` only for a genuine cross-tool sequence or
|
|
396
|
-
a generic wrapper.
|
|
397
|
-
|
|
398
|
-
*Why:* the only projection available is the one the program writes, and a
|
|
399
|
-
value the schema types correctly can still mislead without the provider's
|
|
400
|
-
rendering rule beside it — the agent then re-queries to explain a signal that
|
|
401
|
-
was never there. *Cost:* result size.
|
|
402
|
-
|
|
403
|
-
### P8 — Identity resolution comes before action
|
|
404
|
-
|
|
405
|
-
Where a downstream's write arguments take ids, the guide names the read tools
|
|
406
|
-
that produce them and says not to guess. Where the provider has a
|
|
407
|
-
human-readable identifier alongside a UUID, the guide says which is which.
|
|
408
|
-
|
|
409
|
-
*Why:* a guessed id is a call, a rejection, and a repair. *Cost:* argument
|
|
410
|
-
retries.
|
|
411
|
-
|
|
412
|
-
### P9 — Authentication defaults to OAuth, with a documented headless alternative
|
|
413
|
-
|
|
414
|
-
OAuth per connector instance, stored in connector-scoped storage, is the
|
|
415
|
-
default. The provider's own headless credential — a personal API key, a
|
|
416
|
-
restricted key, a service account — is supported two ways: explicit `headers`
|
|
417
|
-
auth, documented as a secret rather than configuration, and `{ type:
|
|
418
|
-
"credential" }`, which declares an operator slot and takes the same secret from
|
|
419
|
-
the connection UI at `/` instead. Either way it is paired with the narrowest mode the
|
|
420
|
-
deployment can use, and the framing matches the provider's *published* contract
|
|
421
|
-
for the MCP endpoint — not a convention borrowed from that provider's other
|
|
422
|
-
APIs, and not this repository's earlier example, which is the same claim wearing
|
|
423
|
-
a circle. `requireHttps` is set. Recovery from an expired authorization is the
|
|
424
|
-
ordinary `auth_required` → `authorize_connector` route, which returns the
|
|
425
|
-
consent URL for permitted OAuth starts and a connection UI handoff at `/` for
|
|
426
|
-
a declared slot when UI and vault are mounted. Missing modules yield
|
|
427
|
-
`unavailable`; never advertise a route the deployment cannot serve.
|
|
428
|
-
|
|
429
|
-
*Why:* one route back from an expired credential is what keeps a failed call
|
|
430
|
-
from becoming an abandoned task. *Cost:* wrong-tool selection.
|
|
431
|
-
|
|
432
|
-
### P10 — Nothing probes a credential unasked; a declared slot may be tested on request
|
|
433
|
-
|
|
434
|
-
A proxy declares an operator credential slot exactly when its auth is `{ type:
|
|
435
|
-
"credential" }`, and then it inherits H12 whole
|
|
436
|
-
([#439](https://github.com/zackbart/connecta/issues/439)). The other two shapes
|
|
437
|
-
declare no slot and hold nothing for the credentials page: OAuth lives in
|
|
438
|
-
connector-scoped storage and is exercised by the authorization flow itself,
|
|
439
|
-
while a `headers` key arrives as deployment configuration. H12 is owed in every
|
|
440
|
-
shape, and a proxy pays it in two places that do not depend on a slot:
|
|
441
|
-
construction throws when a recognizable credential contradicts the declared mode
|
|
442
|
-
(P4) — a check a vault-managed key cannot get, because there is nothing in the
|
|
443
|
-
deployment file to read — and a dead, revoked, or absent credential fails loudly
|
|
444
|
-
at use as `auth_required` with the `authorize_connector` route attached (P9).
|
|
445
|
-
|
|
446
|
-
`testCredential` exists only behind the operator-pressed Test action on
|
|
447
|
-
the connection UI at `/`, and only for a declared slot. It connects with the stored value
|
|
448
|
-
and reports how many tools the downstream served, which is the whole honest
|
|
449
|
-
check for a proxy: which account, project, or mode a key reaches is the
|
|
450
|
-
provider's answer, not Connecta's. That is not the shape
|
|
451
|
-
[#179](https://github.com/zackbart/connecta/issues/179) removed. What was
|
|
452
|
-
removed is the *unasked* probe — a liveness call every deployment pays on a
|
|
453
|
-
schedule or at startup to answer a question only a misconfigured one has. A
|
|
454
|
-
human clicking Test has asked, `api()` has had that button since the vault
|
|
455
|
-
existed, and nothing here probes on its own: no timer, no warmup, no check on
|
|
456
|
-
the read path.
|
|
457
|
-
|
|
458
|
-
*Why:* an unasked-for liveness probe spends a call on every deployment to answer
|
|
459
|
-
a question only a misconfigured one has; a requested one spends a call the
|
|
460
|
-
person requesting it chose. *Cost:* result size.
|
|
461
|
-
|
|
462
|
-
### P11 — Connecta classifies the transport; the downstream owns the tool error
|
|
463
|
-
|
|
464
|
-
Connecta maps what it can see from outside the tool: an authorization failure to
|
|
465
|
-
`auth_required`, a session or scope teardown, a timeout, and a capability the
|
|
466
|
-
proxy will not relay (`input_required`, task-required execution) to an explicit
|
|
467
|
-
refusal that says so. A tool-level failure the downstream returns — a validation
|
|
468
|
-
complaint, a not-found, a plan restriction — is passed back as it arrived. The
|
|
469
|
-
proxy does not read downstream error prose to invent a Connecta classification,
|
|
470
|
-
and does not repackage a downstream error as `invalid_args`, because it has no
|
|
471
|
-
schema of its own to have validated against. Where a downstream reliably reports
|
|
472
|
-
a retryable condition, the guide says how to recognize it rather than the code
|
|
473
|
-
guessing.
|
|
474
|
-
|
|
475
|
-
*Why:* a transport failure and a rejected argument need different next moves,
|
|
476
|
-
and prose-sniffing routes the second one down the first one's path. *Cost:*
|
|
477
|
-
argument retries.
|
|
478
|
-
|
|
479
|
-
### P12 — Declare an admission budget only when the provider documents a number
|
|
480
|
-
|
|
481
|
-
Where the provider publishes a rate limit, transcribe it as a rolling-window
|
|
482
|
-
budget and say in the guide that it is a per-runtime approximation, not an
|
|
483
|
-
enforcement. Where the provider documents nothing, or documents a limit metered
|
|
484
|
-
per user in a way a per-runtime counter cannot approximate, declare no budget
|
|
485
|
-
and leave the number to the operator who knows the account — with a documented
|
|
486
|
-
example of how to supply one. A `maxConcurrency` beside a budget is a choice
|
|
487
|
-
Connecta made and is labeled as one.
|
|
488
|
-
|
|
489
|
-
*Why:* a hardcoded ceiling either throttles a healthy deployment or fails to
|
|
490
|
-
protect a busy one, and both look like the provider being flaky. *Cost:*
|
|
491
|
-
argument retries.
|
|
492
|
-
|
|
493
|
-
### P13 — A drifting downstream must be visible, not absorbed
|
|
494
|
-
|
|
495
|
-
The classification lists name what a release reviewed, and they are the
|
|
496
|
-
manifest the runtime drift check compares against — one structure per provider,
|
|
497
|
-
built once by `vettedCatalog()` and used both to classify and to compare, so
|
|
498
|
-
the annotation a caller gets and the verdict a check reads can never disagree.
|
|
499
|
-
When the downstream changes underneath them, the correct outcome is a loud
|
|
500
|
-
unclassified tool on the approval path and a maintained record of the drift —
|
|
501
|
-
never a quiet re-guess. The runtime half is
|
|
502
|
-
[the runtime drift policy](#the-runtime-drift-policy) below; the release-time
|
|
503
|
-
half is [the maintainer-run drift check](#the-maintainer-run-drift-check).
|
|
504
|
-
|
|
505
|
-
*Why:* an allowlist nobody can tell is stale is an allowlist that is wrong.
|
|
506
|
-
*Cost:* wrong-tool selection.
|
|
507
|
-
|
|
508
|
-
## The runtime drift policy
|
|
509
|
-
|
|
510
|
-
Detection rides a refresh; it never causes one
|
|
511
|
-
([#343](https://github.com/zackbart/connecta/issues/343)). The comparison
|
|
512
|
-
happens inside the wrapper's `listTools`, on the listing the downstream just
|
|
513
|
-
returned to serve a request the deployment already made, before the
|
|
514
|
-
classification is applied — so what it reads is the downstream's own word, not
|
|
515
|
-
connecta's fill-in. There is no scheduled job, no background request, no
|
|
516
|
-
credential probe, and no automatic issue filing. Proactive credential liveness
|
|
517
|
-
stays removed ([#179](https://github.com/zackbart/connecta/issues/179)); this
|
|
518
|
-
is the shape that does not become it.
|
|
519
|
-
|
|
520
|
-
**What a manifest holds.** Every tool name a release reviewed and its verdict
|
|
521
|
-
(`read-only`, `additive`, or `destructive`). A manifest may also carry a schema
|
|
522
|
-
digest from a prior review, but that digest is runtime drift evidence, not a
|
|
523
|
-
schema Connecta serves. The credential-free provider check neither requires nor
|
|
524
|
-
updates schema digests. The live `tools/list` definition always remains the
|
|
525
|
-
agent-facing contract.
|
|
526
|
-
|
|
527
|
-
**What it counts.** Four categories, and only counts:
|
|
528
|
-
|
|
529
|
-
| Category | What it means |
|
|
530
|
-
| --- | --- |
|
|
531
|
-
| unclassified additions | the downstream serves a tool no release classified; it already fails closed onto `call_destructive_tool` |
|
|
532
|
-
| names no longer served | a classified name is absent from this catalog |
|
|
533
|
-
| annotation conflicts | the downstream *explicitly* contradicts a vetted verdict — `readOnlyHint: false` or `destructiveHint: true` on a vetted read, `readOnlyHint: true` on a vetted write |
|
|
534
|
-
| schema changes | a recorded digest no longer matches the schemas that arrived |
|
|
535
|
-
|
|
536
|
-
Silence is never a conflict: filling it in is what the classification is for.
|
|
537
|
-
A non-zero "no longer served" count is the expected reading on a plan-gated
|
|
538
|
-
workspace, because P5's lists are deliberate supersets — it is triage input,
|
|
539
|
-
not an alarm.
|
|
540
|
-
|
|
541
|
-
**Where it surfaces.** Connector status carries the counts and the time they
|
|
542
|
-
were observed; `/health` carries the same per connector, which is where
|
|
543
|
-
`connecta doctor` reads them, and doctor reports drift without failing on it.
|
|
544
|
-
Both reads are projections — four counts and a bounded timestamp, rebuilt from
|
|
545
|
-
whatever the connector seam returned, because `/health` is unauthenticated and
|
|
546
|
-
`Connector.catalogDrift()` is third-party code.
|
|
547
|
-
One activity event per *change* in the counts — an identical report every TTL
|
|
548
|
-
is a heartbeat, not news — carrying the connector id and four integers. The
|
|
549
|
-
event type has nowhere to put a tool name, a schema, an argument, a result, or
|
|
550
|
-
downstream error prose, which is the same construction guarantee the tool-call
|
|
551
|
-
event makes. Which account-specific tool drifted is deliberately absent from
|
|
552
|
-
this payload-free runtime report. The public check can name documented drift;
|
|
553
|
-
it cannot recover a plan-gated name the provider does not publish.
|
|
554
|
-
|
|
555
|
-
**How far an observation reaches.** One runtime, and no further. The
|
|
556
|
-
observation lives in the isolate or process that served the refresh; unlike the
|
|
557
|
-
catalog, it is not persisted, so nothing carries it across a Workers isolate, a
|
|
558
|
-
restart, or a second Node process. Status and `/health` therefore answer for
|
|
559
|
-
the instance that took the request: on Workers a `connecta doctor` run will
|
|
560
|
-
usually land on an isolate that has served no refresh and print nothing, and
|
|
561
|
-
behind more than one process it is a coin flip. Read an empty report as *this
|
|
562
|
-
runtime has observed nothing*, never as *nothing drifted* — the durable record
|
|
563
|
-
of a finding is the activity event a sink already stored. The public check can
|
|
564
|
-
name a documented change. Naming an unpublished, account-only tool is outside a
|
|
565
|
-
credential-free checker.
|
|
566
|
-
|
|
567
|
-
**What a finding obliges.** A contradicted vetted verdict — the downstream
|
|
568
|
-
calling a release-reviewed destructive tool `readOnlyHint: true`, or a vetted
|
|
569
|
-
read `destructiveHint: true` — blocks that provider's next release until a
|
|
570
|
-
human has re-reviewed the tool. Everything else enters ordinary issue triage.
|
|
571
|
-
No finding changes what a caller may reach: an unclassified tool fails closed
|
|
572
|
-
whether or not anybody noticed it arrived.
|
|
573
|
-
|
|
574
|
-
## The maintainer-run drift check
|
|
575
|
-
|
|
576
|
-
`npm run providers:check` is the other half
|
|
577
|
-
([#351](https://github.com/zackbart/connecta/issues/351)): a human at a laptop,
|
|
578
|
-
before a release, using only published specifications and documentation. It
|
|
579
|
-
lives in
|
|
580
|
-
[`scripts/drift-check.mjs`](https://github.com/zackbart/connecta/blob/main/scripts/drift-check.mjs) and ships nowhere —
|
|
581
|
-
`scripts/` is outside the package, no runtime module imports it, and nothing it
|
|
582
|
-
reads becomes a runtime input.
|
|
583
|
-
|
|
584
|
-
**Published MCP references.** `--docs` checks
|
|
585
|
-
the official setup page for each maintained hosted MCP connection, including
|
|
586
|
-
the endpoint and OAuth support. Cloudflare, Stripe, Mixpanel, Notion,
|
|
587
|
-
RevenueCat, and Vercel also publish structured tool inventories. The checker
|
|
588
|
-
compares their documented names with the same vetted manifests the wrappers
|
|
589
|
-
use. Linear's setup page does not enumerate tools, so its result says that only
|
|
590
|
-
setup metadata was checked.
|
|
591
|
-
|
|
592
|
-
A documented addition with no classification is a finding. A classified tool
|
|
593
|
-
missing from public docs is printed but does not fail the run, because account
|
|
594
|
-
scope, staged rollout, and documentation lag can all hide a tool that an
|
|
595
|
-
earlier release reviewed. RevenueCat's `render-paywall-screenshot` is the odd
|
|
596
|
-
one out: the official inventory names it but leaves its Access column blank.
|
|
597
|
-
The checker reports the reviewed exception and Connecta keeps it fail-closed.
|
|
598
|
-
|
|
599
|
-
`npm run providers:check` runs this documentation check for every maintained
|
|
600
|
-
hosted MCP connection and the touched-endpoint OpenAPI check for every
|
|
601
|
-
hand-written HTTP connection. It never reads a provider credential. The network
|
|
602
|
-
keeps it outside `npm run check`; a provider outage must not make the
|
|
603
|
-
deterministic test suite flaky.
|
|
604
|
-
|
|
605
|
-
MCP schema handling needs a different assertion. Connecta does not vendor or
|
|
606
|
-
reconstruct those schemas, so there is no static MCP schema snapshot for this
|
|
607
|
-
command to compare. The live `tools/list` response is passed through at runtime,
|
|
608
|
-
and the deterministic `catalog-drift.test.ts` suite pins that the vetted wrapper
|
|
609
|
-
changes safety annotations only while preserving the provider input schema,
|
|
610
|
-
output schema, and description. A parameter table in a docs page is never
|
|
611
|
-
promoted into a runtime schema. The report states this directly instead of
|
|
612
|
-
printing a misleading authenticated-schema requirement.
|
|
613
|
-
|
|
614
|
-
**Touched endpoints.** A hand-written provider is written against a published
|
|
615
|
-
OpenAPI document and calls a few dozen of its operations, so
|
|
616
|
-
[`scripts/drift/`](https://github.com/zackbart/connecta/tree/main/scripts/drift) commits exactly those: method, path, the
|
|
617
|
-
specification revision a release reviewed the endpoint at, whether the
|
|
618
|
-
operation was deprecated at that revision, and a digest of that endpoint's
|
|
619
|
-
contract. `--specs` fetches each provider's published document and reports four
|
|
620
|
-
things per touched endpoint — the path is gone, the method is gone, the
|
|
621
|
-
operation's deprecation changed, or its contract changed since the recorded
|
|
622
|
-
revision. Everything else in the document is ignored, which is the point: a
|
|
623
|
-
Cloudflare release that rewrites 2,000 operations connecta never calls is not
|
|
624
|
-
news, and a revision bump that left the touched contracts alone reports
|
|
625
|
-
nothing.
|
|
626
|
-
|
|
627
|
-
Deprecation is reported as a *transition*, not a state: a deprecation a
|
|
628
|
-
maintainer has read and recorded stops being news, and an operation that comes
|
|
629
|
-
back off the deprecation list is its own finding. Without that, a single
|
|
630
|
-
reviewed deprecation would fail every release forever, and the check could
|
|
631
|
-
never reach the "no drift" state its exit code is for.
|
|
632
|
-
|
|
633
|
-
A contract digest covers the parameters, the request body, and the success
|
|
634
|
-
responses, with local `$ref`s inlined so a change inside a shared component is
|
|
635
|
-
visible, and with descriptions, examples, and `x-` extensions stripped so a
|
|
636
|
-
reworded document is not a finding. Inlining runs before a response's `content`
|
|
637
|
-
is read, because a whole response object is often a reference itself —
|
|
638
|
-
Cloudflare writes several of connecta's touched responses that way — and
|
|
639
|
-
reading through the reference would digest the response contract as nothing at
|
|
640
|
-
all. Two bounds are deliberate: a `$ref` cycle stays a reference rather than an
|
|
641
|
-
infinite walk, and failure responses are excluded because an error body is
|
|
642
|
-
H11's business, mapped from the status. `--record` rewrites the manifests from
|
|
643
|
-
the documents on hand; run it when a finding has been reviewed, and read the
|
|
644
|
-
diff before committing it.
|
|
645
|
-
|
|
646
|
-
Narrowing is checked against the part being run: `--specs --provider linear`
|
|
647
|
-
and `--specs --provider stripe` exit 2 rather than checking nothing and
|
|
648
|
-
reporting no drift. `npm run providers:check -- --provider vercel` checks the
|
|
649
|
-
REST OpenAPI contract and public MCP documentation without credentials.
|
|
650
|
-
`npm run drift:check` is the lower-level equivalent with the same
|
|
651
|
-
credential-free default plus fixture and recording flags.
|
|
652
|
-
|
|
653
|
-
**What it never does.** No provider credential is read at all. There is no
|
|
654
|
-
scheduled job, background traffic in a deployment, or automatic issue filing.
|
|
655
|
-
Public docs do not generate tools or replace the live MCP catalog. A finding is
|
|
656
|
-
read by a human and becomes a GitHub issue they wrote, because the decision a
|
|
657
|
-
finding needs — the provider moved this endpoint, or connecta has to stop
|
|
658
|
-
calling it — is not one a diff can make. Published specifications remain drift
|
|
659
|
-
evidence and nothing else: no tool is generated from one, which is the
|
|
660
|
-
[ethos](../ethos.md)'s refusal, not a detail of this script.
|
|
661
|
-
|
|
662
|
-
## What the audit checks
|
|
663
|
-
|
|
664
|
-
The provider audit ([#342](https://github.com/zackbart/connecta/issues/342))
|
|
665
|
-
runs this document against each of the seven providers and returns a verdict per
|
|
666
|
-
convention: **meets**, **misses** (with the fix), or **not applicable** (with
|
|
667
|
-
the reason). A convention is never quietly skipped, and an accepted miss is
|
|
668
|
-
recorded as a provider-specific exception with its argument, not left blank.
|
|
669
|
-
Its seven reports live in [provider-audit.md](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md), and the
|
|
670
|
-
mechanically checkable half of the hand-written bar runs on every test run in
|
|
671
|
-
[`test/provider-conventions.test.ts`](https://github.com/zackbart/connecta/blob/main/test/provider-conventions.test.ts) —
|
|
672
|
-
so a convention that was met once stays met, or fails loudly.
|
|
673
|
-
|
|
674
|
-
Hand-written providers are audited against H1–H14; hosted-MCP proxies against
|
|
675
|
-
P1–P13. Applying a hand-written convention to a proxy is a category error, not
|
|
676
|
-
a finding.
|
|
677
|
-
|
|
678
|
-
Most of the bar is mechanically checkable against the shipped surface rather
|
|
679
|
-
than by reading:
|
|
680
|
-
|
|
681
|
-
| Convention | Mechanical check |
|
|
682
|
-
| --- | --- |
|
|
683
|
-
| H1, P2 | constructor throws on a blank `purpose`; `instructions` appear appended to the guide |
|
|
684
|
-
| H2 | every tool name matches `^[a-z][a-z0-9_]*$` and opens with a verb from the connector's own set |
|
|
685
|
-
| H3 | first sentence ≤ 160 characters; whole description ≤ 240 |
|
|
686
|
-
| H5 | every tool has an `inputSchema` that is a closed plain object with a `required` list, and every property at every depth — nested objects and array items included — carries a description or sits on a recorded exception list; `api()` refuses to construct one it cannot enforce |
|
|
687
|
-
| H7 | every compact input and output render stays inside 1,024 bytes, or the tool is on a recorded exception list |
|
|
688
|
-
| H8 | every tool declares an `outputSchema` |
|
|
689
|
-
| H9 | every read either projects or documents why it does not; `raw: true` exists wherever the projection drops something recoverable |
|
|
690
|
-
| H10 | every list tool has bounded page arguments, a default below the provider maximum, and exactly one `hasMore`-shaped signal |
|
|
691
|
-
| H11 | every mapped provider status has a test asserting the resulting code and retryability |
|
|
692
|
-
| H12 | `credential` declared with labeled fields, and `testCredential`/`testCredentials` implemented |
|
|
693
|
-
| H13, P7 | `usageGuide` uses the structured form with an explicit `summary`; `required` is set only with a stated reason |
|
|
694
|
-
| H14 | escape hatches split GET / JSON-mutate / upload, and the GET tool is annotated read-only |
|
|
695
|
-
| P1 | the wrapper's `listTools` changes annotations only |
|
|
696
|
-
| P3 | the routing fact appears in the default `title` and the guide's first content line |
|
|
697
|
-
| P4 | endpoint or mode option exists, with the documented default (or no default, where none is safe) |
|
|
698
|
-
| P5 | reads and writes are named lists; an unlisted tool resolves to not-read-only; a reviewed destructive name beats a contradictory `readOnlyHint: true` |
|
|
699
|
-
| P6, P8 | the guide contains the catalog-varies note and the id-resolution rule |
|
|
700
|
-
| P9 | `auth` defaults to OAuth and `requireHttps` is set; a credential-auth shape frames the key the way the provider's MCP documentation does |
|
|
701
|
-
| 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 |
|
|
702
|
-
| P11 | an authorization failure surfaces as `auth_required`; a downstream tool error is returned unchanged, with no code chosen from its prose |
|
|
703
|
-
| P12 | a declared budget matches a citable documented limit, or the absence is justified in the guide |
|
|
704
|
-
| 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 |
|
|
705
|
-
|
|
706
|
-
The remainder — H4, H6, and the judgment in H14 about whether a named tool
|
|
707
|
-
beats the escape hatch — is a reading, and the audit reports it as one. The
|
|
708
|
-
Cloudflare keep/prune half of that judgment was made in
|
|
709
|
-
[#350](https://github.com/zackbart/connecta/issues/350): 30 keep, 18 improve,
|
|
710
|
-
3 prune, measured per tool in
|
|
711
|
-
[`eval/current-version/results/issue-350-evidence.md`](https://github.com/zackbart/connecta/blob/main/eval/current-version/results/issue-350-evidence.md).
|
|
712
|
-
Its eighteen `improve` rows were H8 and H9 misses on tools that clearly earned
|
|
713
|
-
their place. Issue #488 resolved them with useful output keys and maintained
|
|
714
|
-
projections; the measurements remain the reason those tools stayed rather than
|
|
715
|
-
a second removal argument.
|
|
716
|
-
|
|
717
|
-
Each provider's own guide ([Cloudflare](./cloudflare.md),
|
|
718
|
-
[Linear](./linear.md), [Mixpanel](./mixpanel.md), [Notion](./notion.md),
|
|
719
|
-
[RevenueCat](./revenuecat.md), [Stripe](./stripe.md)) is part of the audited
|
|
720
|
-
surface: documentation moves with
|
|
721
|
-
the work, and a guide describing a surface that shipped differently is itself a
|
|
722
|
-
miss.
|