@zackbart/connecta 0.15.1 → 0.16.0

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