@zackbart/connecta 0.38.0 → 0.39.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 (177) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +3 -2
  3. package/bin/version.mjs +1 -1
  4. package/dist/agent-routes.js +231 -0
  5. package/dist/auth/downstream-oauth/consent.js +519 -0
  6. package/dist/auth/downstream-oauth/grant-store.js +439 -0
  7. package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
  8. package/dist/auth/downstream-oauth/refresh-records.js +447 -0
  9. package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
  10. package/dist/auth/downstream-oauth.js +66 -2382
  11. package/dist/auth/native.js +27 -0
  12. package/dist/auth/oauth-grant.js +13 -0
  13. package/dist/auth/static-oauth.js +153 -131
  14. package/dist/auth-elicitation.js +7 -1
  15. package/dist/authorization-handoff.js +11 -5
  16. package/dist/catalog-cache.js +130 -43
  17. package/dist/catalog-policy.js +18 -0
  18. package/dist/catalog-service.js +284 -47
  19. package/dist/catalog.js +18 -2
  20. package/dist/config.d.ts +16 -11
  21. package/dist/config.js +9 -6
  22. package/dist/connector-caller.d.ts +1 -1
  23. package/dist/connector-health.js +59 -0
  24. package/dist/connector-status.js +117 -0
  25. package/dist/connectors/api-connector.d.ts +7 -2
  26. package/dist/connectors/api-connector.js +7 -4
  27. package/dist/connectors/negotiation-cache.js +22 -4
  28. package/dist/connectors/remote-mcp/call.js +164 -0
  29. package/dist/connectors/remote-mcp/catalog.js +278 -0
  30. package/dist/connectors/remote-mcp/config.js +263 -0
  31. package/dist/connectors/remote-mcp/connection.js +671 -0
  32. package/dist/connectors/remote-mcp/errors.js +488 -0
  33. package/dist/connectors/remote-mcp/oauth.js +469 -0
  34. package/dist/connectors/remote-mcp/resources.js +473 -0
  35. package/dist/connectors/remote-mcp/runtime.js +1 -0
  36. package/dist/connectors/remote-mcp/transport.js +544 -0
  37. package/dist/connectors/remote-mcp.js +59 -2863
  38. package/dist/credential-contract.d.ts +23 -0
  39. package/dist/credential-reads.js +17 -0
  40. package/dist/credential-verification.js +53 -0
  41. package/dist/credentials.d.ts +7 -0
  42. package/dist/credentials.js +60 -25
  43. package/dist/describe-config.d.ts +12 -2
  44. package/dist/describe-config.js +2 -0
  45. package/dist/discovery-health.js +199 -0
  46. package/dist/downstream-input.js +2 -0
  47. package/dist/errors.d.ts +55 -17
  48. package/dist/errors.js +95 -41
  49. package/dist/execute.js +152 -114
  50. package/dist/executor-result.js +3 -1
  51. package/dist/guest-functions.js +83 -0
  52. package/dist/guest-runtime.js +60 -19
  53. package/dist/guest-types.d.ts +19 -5
  54. package/dist/http/argument-problems.js +35 -0
  55. package/dist/http/errors.js +5 -4
  56. package/dist/http/fields.js +192 -13
  57. package/dist/http/hatch-response.js +73 -0
  58. package/dist/http/hatch.js +88 -23
  59. package/dist/http/oauth.js +3 -2
  60. package/dist/http/provider.js +32 -4
  61. package/dist/http/safety.js +8 -0
  62. package/dist/http/tools.js +44 -23
  63. package/dist/index.d.ts +1 -1
  64. package/dist/index.js +9 -2
  65. package/dist/invocation.js +21 -6
  66. package/dist/membership.d.ts +13 -5
  67. package/dist/membership.js +34 -2
  68. package/dist/meta-tools.js +23 -8
  69. package/dist/native-recovery.js +4 -16
  70. package/dist/native-tools.js +4 -1
  71. package/dist/oauth-sealing.js +10 -0
  72. package/dist/operation-vocabulary.js +135 -13
  73. package/dist/operator-record.d.ts +2 -2
  74. package/dist/operator-record.js +1 -0
  75. package/dist/operator-ui/contract.d.ts +10 -0
  76. package/dist/operator-ui/generated.js +2 -2
  77. package/dist/operator-ui/model.d.ts +22 -1
  78. package/dist/providers/_shared/rest/operation-index.js +48 -50
  79. package/dist/providers/_shared/rest/value-safety.js +80 -0
  80. package/dist/providers/breeze/api.js +7 -1
  81. package/dist/providers/breeze/skill.generated.js +1 -1
  82. package/dist/providers/breeze/tools.js +4 -3
  83. package/dist/providers/ccb/api.js +2 -1
  84. package/dist/providers/ccb/index.js +5 -0
  85. package/dist/providers/ccb/skill.generated.js +1 -1
  86. package/dist/providers/cloudflare/api.js +8 -1
  87. package/dist/providers/cloudflare/skill.generated.js +1 -1
  88. package/dist/providers/docs/lib/schemas.js +24 -22
  89. package/dist/providers/docs/safety.js +2 -0
  90. package/dist/providers/docs/skill.generated.js +1 -1
  91. package/dist/providers/docs/tools.js +2 -4
  92. package/dist/providers/drive/api.js +1 -0
  93. package/dist/providers/drive/lib/contracts.js +8 -5
  94. package/dist/providers/drive/skill.generated.js +1 -1
  95. package/dist/providers/drive/tools.js +11 -16
  96. package/dist/providers/forms/api.js +1 -0
  97. package/dist/providers/forms/skill.generated.js +1 -1
  98. package/dist/providers/forms/tools.js +10 -7
  99. package/dist/providers/github/api.js +1 -0
  100. package/dist/providers/github/lib/discovery.js +1 -1
  101. package/dist/providers/github/skill.generated.js +1 -1
  102. package/dist/providers/github/tools.js +61 -17
  103. package/dist/providers/gmail/lib/schemas.js +60 -50
  104. package/dist/providers/gmail/skill.generated.js +1 -1
  105. package/dist/providers/gmail/tools.js +7 -5
  106. package/dist/providers/granola/index.js +2 -0
  107. package/dist/providers/granola/skill.generated.js +1 -1
  108. package/dist/providers/overflow/api.js +7 -1
  109. package/dist/providers/overflow/skill.generated.js +1 -1
  110. package/dist/providers/planning-center/api.js +3 -1
  111. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  112. package/dist/providers/planning-center/skill.generated.js +1 -1
  113. package/dist/providers/quickbooks/safety.js +80 -39
  114. package/dist/providers/quickbooks/skill.generated.js +1 -1
  115. package/dist/providers/sheets/api.js +1 -0
  116. package/dist/providers/sheets/skill.generated.js +1 -1
  117. package/dist/providers/sheets/tools.js +13 -14
  118. package/dist/providers/slides/skill.generated.js +1 -1
  119. package/dist/providers/slides/tools.js +15 -13
  120. package/dist/providers/stripe/skill.generated.js +1 -1
  121. package/dist/providers/tithely/api.js +3 -1
  122. package/dist/providers/tithely/skill.generated.js +1 -1
  123. package/dist/providers/vercel/api.js +1 -1
  124. package/dist/providers/vercel/skill.generated.js +1 -1
  125. package/dist/providers/vercel/tools.js +4 -3
  126. package/dist/registry.d.ts +10 -4
  127. package/dist/registry.js +41 -13
  128. package/dist/routes/credentials.js +129 -60
  129. package/dist/routes/dispatch.js +5 -1
  130. package/dist/routes/mcp.js +30 -14
  131. package/dist/routes/operator.js +2 -2
  132. package/dist/routes/shared.d.ts +6 -1
  133. package/dist/routes/shared.js +7 -1
  134. package/dist/routes/ui-config.js +2 -2
  135. package/dist/routes/ui-data.js +126 -20
  136. package/dist/routes/ui.js +16 -0
  137. package/dist/runtime/call.js +7 -2
  138. package/dist/runtime/native-storage.js +9 -0
  139. package/dist/runtime/request-reads.js +182 -0
  140. package/dist/runtime/request-scope.js +16 -0
  141. package/dist/runtime/storage.js +12 -0
  142. package/dist/skills.js +37 -20
  143. package/dist/storage/keys.js +53 -3
  144. package/dist/storage/memory.js +8 -2
  145. package/dist/storage/sql-native.js +27 -0
  146. package/dist/storage/sql.js +12 -2
  147. package/dist/storage-digest.js +32 -0
  148. package/dist/timeout.js +8 -0
  149. package/dist/types.d.ts +33 -0
  150. package/dist/usage-guide.js +16 -35
  151. package/dist/validate.d.ts +3 -0
  152. package/dist/validate.js +220 -158
  153. package/dist/version.d.ts +1 -1
  154. package/dist/version.js +1 -1
  155. package/dist/write-outcome.js +0 -3
  156. package/documentation/README.md +13 -13
  157. package/documentation/architecture.md +350 -19
  158. package/documentation/auth.md +217 -63
  159. package/documentation/code-mode.md +122 -39
  160. package/documentation/deploying.md +6 -3
  161. package/documentation/integrating.md +15 -1
  162. package/documentation/meta-tools.md +220 -46
  163. package/documentation/migration-0.33.md +5 -1
  164. package/documentation/migration-0.34.md +3 -1
  165. package/documentation/operator-tests.md +1 -1
  166. package/documentation/operator-ui.md +105 -12
  167. package/documentation/pool-membership-upgrade.md +44 -0
  168. package/documentation/providers.md +105 -0
  169. package/examples/worker/AGENTS.md +10 -10
  170. package/examples/worker/README.md +4 -3
  171. package/examples/worker/src/connecta.config.ts +1 -0
  172. package/guides/connecting-agents.md +2 -1
  173. package/guides/how-it-works.md +4 -2
  174. package/package.json +1 -1
  175. package/templates/node/AGENTS.md +13 -12
  176. package/templates/node/package.json +1 -1
  177. package/templates/node/src/connecta.config.ts +1 -0
@@ -49,7 +49,12 @@ write tools from `search_tools` results and describe, and refuses every write
49
49
  call. Connector `readOnly` applies the same restriction on every endpoint; effective read-only is endpoint OR connector. Neither setting changes connector visibility or read grants. Guides
50
50
  (`queryAnalysis.guideSummary`, `skills`, `connecta.skill`, skill
51
51
  `resources/read`) are documentation and may name tools the endpoint refuses. The four tools with structured-only success results
52
- advertise `outputSchema`. The two direct-call tools omit it because their default
52
+ advertise `outputSchema`. `search_tools`'s schema defines its typed failure,
53
+ including every authorization-handoff field, once under `$defs.failure`;
54
+ `catalogErrors[]` references it through `allOf` with `connector` required, and
55
+ `queryAnalysis.catalogError` references it directly. The schema declares no
56
+ `$schema`, and validators for drafts 4, 7, 2019-09 and 2020-12 accept the same
57
+ pages. The two direct-call tools omit it because their default
53
58
  mode returns native `content`. Default-mode successes declare
54
59
  `_meta["dev.connecta/format"]: "json" | "text"`; value mode places the value in
55
60
  `data` and declares `format` in its complete result envelope.
@@ -134,16 +139,25 @@ Live connector probing is not a fourth: the operator pages and `/health` own it.
134
139
 
135
140
  ## Discovery context
136
141
 
137
- The deployment-derived `execute_code` description carries a live connector
138
- inventory before any catalog search — registry order, each canonical id, and a
139
- distinct configured title, with no second name minted for programs. Titles
140
- normalize whitespace and cap at 48 UTF-8 bytes, the complete line caps at 256,
141
- entries stay whole, and a truncated line ends with the exact `+N more` count, so
142
- account and environment hints cannot eat the inventory. It reads only the
143
- configured registry: no catalog load, no credential probe, no capability, and no
144
- replacement for canonical discovery or addressing. Program search results carry
145
- the same bounded `connectorTitle` per tool, so choosing an account or
146
- environment costs no provider read. The inventory does not probe live access.
142
+ The deployment-derived `execute_code` description carries a connector
143
+ inventory before any catalog search — registry order and each canonical id,
144
+ with no second name minted for programs. An entry adds the configured title
145
+ only when it says more than the id: a title whose words the id already spells,
146
+ such as `Google Calendar` for `google_calendar`, is dropped. Titles normalize
147
+ whitespace and cap at 48 UTF-8 bytes. A connector with a guide says how to
148
+ fetch it: `read first: skills({name:"connector:<id>"})` for a required guide,
149
+ `guide: skills({name:"connector:<id>"})` otherwise. For example:
150
+ `Connectors: crm (Customer CRM; read first: skills({name:"connector:crm"})), tickets.`
151
+
152
+ The complete line caps at 1,024 UTF-8 bytes, which names every connector of a
153
+ typical deployment. Past the cap, detail degrades before names do: titles are
154
+ dropped first, then optional guide pointers. Only when bare ids and required
155
+ guide pointers still exceed it do whole entries give way, in registry order, to
156
+ an exact `+N more` count. The inventory reads only the configured registry: no
157
+ catalog load, no credential probe, no live status, no capability, and no
158
+ replacement for canonical discovery or addressing. Search pages carry the same
159
+ bounded title once per connector in `connectors[<id>].title`, so choosing an
160
+ account or environment costs no provider read.
147
161
 
148
162
  Read-only lookup belongs in `connecta.search` inside the program; top-level
149
163
  `search_tools` stays for explicit catalog inspection and approval-required
@@ -171,6 +185,56 @@ read/write filter. On `approvalRequired` searches the returned discovery tool
171
185
  is still a read. Hidden tools, missing grants and read-only restrictions remain
172
186
  in force. Browsing does not consult operation vocabulary.
173
187
 
188
+ When a matched operation's call is fully determined, the first page also
189
+ carries it in `readyCalls`, so a program can call it without operation search or
190
+ details:
191
+
192
+ ```json
193
+ {
194
+ "readyCalls": [
195
+ {
196
+ "address": "notes.notes_api_read",
197
+ "args": { "path": "/v1/notes" },
198
+ "classification": "read",
199
+ "summary": "List notes"
200
+ }
201
+ ]
202
+ }
203
+ ```
204
+
205
+ Pass `args` unchanged to `connecta.call(address, args)` or `call_tool`. An
206
+ operation is fully determined only when it is a reviewed read served by the
207
+ hatch's `_api_read` tool itself (no named tool), uses GET, the read tool's
208
+ default method, and declares no path template, required parameter or required
209
+ body. Anything needing an id, a method choice or an argument the agent must
210
+ fill in stays behind `_api_search` and `_api_details`. Ready calls pass the same
211
+ endpoint, connector read-only, safety, hidden-tool and grant checks as the
212
+ vocabulary match, through a visible read tool in the caller's view. They are
213
+ computed offline from the construction-time index, read no account data, and
214
+ start no authorization.
215
+
216
+ A ready call must also be what the query names. Each path segment, and the
217
+ operationId without its action and version words, is one whole resource name:
218
+ `event_destinations` is the name "event destination", never "event". Every
219
+ query word other than an action (`list`, `get`, `count`, `show`, `create`,
220
+ `update`, `delete` and their vocabulary aliases) must belong to a resource name
221
+ whose words the query states in order, with singular/plural normalization and
222
+ the provider's declared synonyms. Version segments such as `v1` never count, and
223
+ a summary may supply only actions. A query with no object word gets no ready
224
+ call. Ready calls are omitted entirely when the top result is an exact tool name
225
+ or address, or a complete match on an authored tool, not the hatch's own search
226
+ or read tool, whose name adds nothing to the query: each of its name words is an
227
+ action or a query word, up to plural/singular and declared synonyms. For
228
+ example, `list teams` returns `list_teams` and no ready call, even when other
229
+ operations mention teams in their summaries, while `list projects` still offers
230
+ `/v10/projects` beside `list_project_env_vars`, whose name adds `env` and `vars`.
231
+ Each hatch offers only its top-ranked operation: a last path segment naming the
232
+ resource first, then the fewest resource words the query did not ask for, then
233
+ the shortest path. A connector offers at most one ready call and a page at most
234
+ three, in connector order. Later pages carry none. A ready
235
+ call keeps its read tool's `guideRequired` and `guideRequiredReasons`, and its
236
+ connector appears in `connectors`.
237
+
174
238
  Vocabulary is bounded: queries at most 200 characters and eight terms; each
175
239
  hatch caches at most 8,192 rows and 2,097,152 characters, with at most 1,024
176
240
  characters per row and 128 query intersections. Longer queries and vocabulary
@@ -281,9 +345,11 @@ guide's first meaningful body paragraph. `required: true` is reserved for
281
345
  generic API wrappers and cross-operation conventions a complete downstream
282
346
  schema cannot express.
283
347
 
284
- Search and describe results carry a `guide: "connector:<id>"` pointer and a
285
- `guideSummary`. A matching tool also carries `guideRequired: true` and
286
- `guideRequiredReasons` when Connecta can prove review is necessary:
348
+ Describe results carry a `guide: "connector:<id>"` pointer and a
349
+ `guideSummary`; a search page carries both once per connector in
350
+ `connectors[<id>]`. A matching tool row or description also carries
351
+ `guideRequired: true` and `guideRequiredReasons` when Connecta can prove review
352
+ is necessary:
287
353
 
288
354
  | Reason | Raised by | Survives exact schema expansion |
289
355
  | -------------------- | ------------------------------------ | -------------------------------------------------------- |
@@ -297,8 +363,17 @@ is merely told to fetch the guide first, for any reason listed. Otherwise the
297
363
  bounded summary decides — connector-specific sequencing, units, pagination,
298
364
  aliases, and generic API conventions still need the guide when they affect the
299
365
  task, while a complete and unambiguous one-read schema proceeds directly. Guide
300
- lookup always uses an exact name returned by `skills({})`, search, or describe;
301
- callers never manufacture `connector:<id>` from an unmarked connector.
366
+ lookup uses an exact name returned by `skills({})`, search, describe, or the
367
+ `execute_code` inventory; callers never manufacture `connector:<id>` from an
368
+ unmarked connector. A bare connector id, such as `skills({ name: "crm" })` or
369
+ `connecta.skill("crm")`, also serves that connector's guide. Built-in skill
370
+ names stay reserved: `usage` and `investigate` always fetch the built-in skill,
371
+ even when a connector has that id, whose guide stays at `connector:usage` or
372
+ `connector:investigate`. A bare id naming a connector without a guide fails with
373
+ `Connector "<id>" has no usage guide.`; any other unknown name fails as an
374
+ unknown skill. Names containing `:`, including URIs, never fall back to a bare
375
+ id. MCP `skills/get` resolves names the same way; `resources/read` accepts only
376
+ document URIs.
302
377
 
303
378
  A connector-scoped lexical miss keeps that connector's guide metadata under
304
379
  `queryAnalysis`. This matters for generic wrappers whose broad tool name
@@ -307,12 +382,29 @@ disappearing with the zero-tool page.
307
382
 
308
383
  Two built-in skills are byte-identical across deployments. `usage` is fetched
309
384
  only for missing guidance or program repair, and owns program selection detail, examples, runtime
310
- differences, and repair guidance; `investigate` is on-demand guidance for
311
- purchase verification, experiment checks, and customer or deployment
312
- investigations — shared guidance, not a saved workflow and not a source of
313
- deployment-specific ids. The always-loaded instructions and tool definitions keep
385
+ differences, and repair guidance; `investigate` is domain-neutral on-demand
386
+ guidance for an investigation that spans several services — shared guidance,
387
+ not a saved workflow and not a source of deployment-specific ids. `skills`
388
+ lists it; the `execute_code` description does not advertise it. The always-loaded instructions and tool definitions keep
314
389
  route selection, the fail-closed boundary, and the minimum guest syntax, so a
315
390
  client that never fetches a skill can still write a valid first program.
391
+
392
+ The server instructions state the complete routing rule once. Some clients
393
+ drop server instructions, so each tool description stays sufficient for its own
394
+ correct use without restating that rule: `call_tool` names its scope (one
395
+ known-address read) and points other read work to `execute_code`;
396
+ `search_tools` names its scope (catalog inspection, or finding a write) and
397
+ points read work into a program; `execute_code` opens with the terse
398
+ three-route lead (`One known read: call_tool. One known write:
399
+ call_destructive_tool. Everything else: execute_code.`, or the read-only
400
+ two-route form). The `usage` skill points to those two sources instead of
401
+ repeating them.
402
+
403
+ Tool descriptions and output schemas carry deployment configuration only: the
404
+ connector inventory, budgets, the endpoint's read-only setting, and whether any
405
+ visible connector has a guide. No catalog outcome, call, authorization state,
406
+ or other live status reaches `tools/list` or the instructions, so the
407
+ always-loaded surface stays byte-stable for a client's prompt cache.
316
408
  The instructions are the same on every endpoint except one addition: at
317
409
  `/mcp/<pool>` and `/agent/<pool>`, a pool that sets a
318
410
  [`description`](./auth.md#pools) has it appended after `This endpoint: `, so
@@ -618,7 +710,7 @@ a fixed warning without storage error prose. Never repeat a write to recover out
618
710
  page. Select tool rows from `page.tools`.
619
711
  With the same schema format, schema-key option, and scoped registry, both paths
620
712
  return identical data. Top-level search omits schemas unless requested; programs
621
- default to JSON schemas and schema-key metadata.
713
+ default to TypeScript signatures and schema-key metadata.
622
714
 
623
715
  ```json
624
716
  {
@@ -627,12 +719,12 @@ default to JSON schemas and schema-key metadata.
627
719
  {
628
720
  "name": "get_ticket",
629
721
  "address": "tickets.get_ticket",
630
- "connectorTitle": "Tickets",
631
722
  "classification": "read",
632
723
  "schemaFormat": "json",
633
724
  "inputSchema": { "type": "object", "properties": { "id": { "type": "string" } }, "required": ["id"] }
634
725
  }
635
726
  ],
727
+ "connectors": { "tickets": { "title": "Tickets", "guide": "connector:tickets", "guideSummary": "Ticket ids and states." } },
636
728
  "total": 1,
637
729
  "offset": 0,
638
730
  "limit": 8,
@@ -652,8 +744,26 @@ and connector id, never connector-supplied error text. Other failures retain
652
744
  `retryAfterMs`. A scoped search also keeps its bounded failure subset under
653
745
  `queryAnalysis.catalogError` for clients that already use it.
654
746
 
655
- Tool rows carry the address, configured display title when present, guide and
656
- guide summary when available, and stored classification. Requested schemas have
747
+ An unscoped search gives each connector at most 5 seconds (or
748
+ `discovery.probeTimeoutMs` when shorter) and ends within a 9-second discovery
749
+ budget. A connector that recently timed out or failed slowly is skipped for up
750
+ to 60 seconds: its entry keeps the recorded code with `retryable: true`,
751
+ `retryAfterMs`, and the message `Connector "X" was skipped because its catalog
752
+ lookup failed recently (CODE). Search with connector "X" to try it now.` A
753
+ scoped search always probes. Records are kept per owner, admitted endpoint and
754
+ described connector configuration. A healthy connector whose catalog always
755
+ takes more than 5 seconds can stay out of unscoped search; search it with
756
+ `connector`, or let any longer-deadline operation warm its complete catalog.
757
+ See [Discovery health](./architecture.md#storage-credentials-and-connectors).
758
+
759
+ Tool rows carry the address, stored classification, and tool-specific
760
+ `guideRequired` and `guideRequiredReasons`. Facts shared by every row of one
761
+ connector appear once per page in `connectors`, keyed by the connector id that
762
+ prefixes each row's address: the configured display `title` when present and
763
+ `guide` and `guideSummary` when the connector has a guide. The map names only
764
+ connectors with a row or ready call on this page and is omitted when none has
765
+ such facts.
766
+ Requested schemas have
657
767
  `schemaFormat: "json"` for JSON Schema values or `schemaFormat: "text"` for
658
768
  compact schema strings and TypeScript signatures. Compact schemas are text;
659
769
  `inputSchema.properties` is meaningful only in JSON mode. Pagination counts
@@ -666,7 +776,19 @@ rare domain terms above ubiquitous action terms. Exact tool names and canonical
666
776
  addresses rank first, so `get_issue` beats `get_issue_status` even when a
667
777
  connector is named `issue`. Exact configured connector IDs, full display titles,
668
778
  and recognized service names rank next. Complete lexical matches then precede
669
- partial matches, with score and catalog order breaking ties.
779
+ partial matches. Within a tier, a query whose first word outside connector names
780
+ is `list`, `get`, `find`, `search`, `show`, `read`, `fetch`, or `count` ranks
781
+ read rows before write rows; writes stay on the page. Score and catalog order
782
+ break the remaining ties.
783
+
784
+ A maintained provider may declare `searchSynonyms`: groups of single lowercase
785
+ words its users and its vendor use for the same record, such as `household`
786
+ and `family` for Church Community Builder. A query word then matches that
787
+ provider's tools and operation vocabulary through any word in its group, as an
788
+ ordinary whole-token match ranked below the word itself. Synonyms never widen
789
+ another connector's matches. Church Community Builder declares
790
+ `person`/`individual` and `household`/`family`; Granola declares
791
+ `meeting`/`note`.
670
792
  An exact connector ID or title query browses that connector's tools, even when
671
793
  individual tool names or descriptions mention that identity. Connector identity
672
794
  terms that never occur in its tools need not be repeated in every tool description.
@@ -1027,28 +1149,77 @@ A remote MCP tool's advertised `inputSchema` is checked in the shared invocation
1027
1149
  path before admission and provider dispatch, so a mismatch is the non-retryable
1028
1150
  `invalid_args` identically on `call_tool`, `call_destructive_tool`,
1029
1151
  generated-code failures, and rejected promises. The error names the connector and
1030
- operation and carries `validation.issues`: JSON Pointer `path`, schema-keyword
1031
- `code`, and expected shape, with submitted values never copied into a finding. At
1032
- most three are returned, and `validation.truncated` says when more exist — the
1033
- same three-item bound describe's nearby-address list uses.
1152
+ operation and carries one recovery envelope, `validation`:
1153
+
1154
+ ```json
1155
+ {
1156
+ "issues": [
1157
+ { "path": "/limit", "expected": "at least 5", "received": "4" },
1158
+ { "path": "/mode", "expected": "one of \"fast\", \"safe\"", "received": "string" },
1159
+ { "path": "/<key>", "expected": "one of the accepted keys: mode, limit" }
1160
+ ],
1161
+ "acceptedKeys": ["mode", "limit"],
1162
+ "example": { "mode": "fast", "limit": 5 }
1163
+ }
1164
+ ```
1165
+
1166
+ Every finding is listed, and the message repeats them, so one corrected call
1167
+ fixes them all.
1168
+
1169
+ A refusal contains only names and literals the published schema declares at
1170
+ non-sensitive positions, numbers allowed by the bound rule below, and fixed
1171
+ placeholder text. It never contains a submitted string or object key, in the
1172
+ message, a finding, `acceptedKeys` or the example. A position is sensitive when
1173
+ it or an ancestor is `writeOnly`; a named tool's input schema marks each request
1174
+ secret that way. API hatch findings name only the operation contract's
1175
+ properties and enum members, as `*_api_details` publishes them.
1176
+
1177
+ - `path` is a JSON Pointer naming declared properties and array indices. The
1178
+ first key the schema does not declare (a misspelled argument, a map key)
1179
+ reads `<key>` and ends the path, e.g. `/headers/<key>`.
1180
+ - `expected` states the constraint that failed, then every other constraint
1181
+ the position declares: bounds, property and item counts, `multipleOf`,
1182
+ `pattern`, `format`, `uniqueItems`, enum members, `dependentRequired`
1183
+ (`string (required with tags)`). A sensitive position never renders an enum,
1184
+ `const` or pattern literal. Each position gets one finding.
1185
+ - `received` is the submitted JSON type, `missing`, or, for a numeric bound at
1186
+ a non-sensitive position of a named tool, the submitted number.
1187
+
1188
+ At most eight findings are returned, each field clipped (path and expected 256
1189
+ characters, received 64), and `truncated: true` says something was omitted or
1190
+ clipped.
1191
+
1192
+ `example` is one valid call synthesized from the published schema and checked
1193
+ against the same validator. It never copies a submitted value, and a sensitive
1194
+ position never renders a schema literal (`enum`, `const`, `default`,
1195
+ `examples`). Complex or unsatisfiable schemas get `exampleUnavailable` instead.
1196
+ Synthesis detects recursive references and shares a 128-node work budget.
1197
+ Invocation uses only this schema example, never a connector's copy of the
1198
+ call, unless the connector states `exampleUnavailable`. API hatches
1199
+ (`*_api_read`, `*_api_write`) always do: their generic schema says nothing
1200
+ about an operation's arguments, so the reason points to the operation's
1201
+ `*_api_details`.
1202
+
1203
+ `acceptedKeys` names the tool's top-level arguments. `conditionalRequirements`
1204
+ lists known `dependentRequired` and `dependentSchemas` requirements by
1205
+ property name; `if`/`then`/`else` conditions are schema subtrees and are not
1206
+ rendered. The envelope has a 4 KiB budget:
1207
+ optional detail, then the example, then trailing findings are dropped whole,
1208
+ with `truncated: true`. A `repair` object a custom connector passes contributes
1209
+ its received types and schema facts. A custom connector's own findings must
1210
+ follow the same rule. Operator records contain only checked failure facts,
1211
+ never findings or examples.
1034
1212
 
1035
1213
  `nextAction` points to discovery scoped to the same connector and tool name when
1036
1214
  the compact schema is needed, routed like any other miss: a program to
1037
1215
  `connecta.search`, a top-level call to `search_tools`. `retry` says to correct
1038
- the listed arguments and reissue the original operation. Which keyword a finding
1039
- names, and what the local validator declines to evaluate, is
1040
- [code mode](./code-mode.md#errors)'s `E8`.
1041
-
1042
- `repair` adds agent-only detail from that tool's published schema: accepted
1043
- keys, received JSON types, enum values, numeric/length/item bounds, and known
1044
- conditional requirements such as `dependentRequired` and `if`/`then`/`else`.
1045
- Its `example` is synthesized without submitted values and checked against the
1046
- same validator. Complex or unsatisfiable schemas get `exampleUnavailable`
1047
- rather than an invalid example. Synthesis detects recursive references and
1048
- shares a 128-node work budget. Alternative branches do not emit dependency
1049
- advice unless the branch condition is known. Repair detail has a 4 KiB budget; oversized
1050
- detail is withheld whole with `truncated: true`. Operator records still contain
1051
- only checked failure facts, never findings or examples.
1216
+ the listed arguments and reissue the original operation. What the local
1217
+ validator declines to evaluate is [code mode](./code-mode.md#errors)'s `E8`.
1218
+
1219
+ Earlier releases returned `validation.issues[].code` (the schema keyword)
1220
+ and a separate `repair` object. Both are gone from responses; a custom
1221
+ connector may still pass `code` and `repair` to `ConnectorCallError`, which
1222
+ folds `repair` into `validation`.
1052
1223
 
1053
1224
  Unknown-address errors list `configuredConnectors` from the current endpoint's
1054
1225
  scoped registry, including an empty list when none are accessible. They never
@@ -1146,7 +1317,9 @@ its code and message for diagnostics but is `retryable: false` with
1146
1317
  a program's `writes.unknown`. An HTTP refusal of the call's own write stays
1147
1318
  known only under the refusal rules above and a complete ledger proving the
1148
1319
  refusal covers the whole call; its recovery advice and program accounting both
1149
- report failure. Partial or unknown ledgers remain conservative. An auth failure after entering a write with
1320
+ report failure. A `remoteMcp()` write answered with a 4xx (other than 401, 403,
1321
+ 408 or 429) whose body is not a JSON-RPC error is unknown, never a refusal: a
1322
+ gateway may have answered after the server acted. Partial or unknown ledgers remain conservative. An auth failure after entering a write with
1150
1323
  no call-owned dispatch reports an unknown outcome and requires reconciliation,
1151
1324
  including credential refusals, static-OAuth refresh failures, and `remoteMcp()`
1152
1325
  connect or initialize 401s before `tools/call`. A non-auth refusal before
@@ -1311,7 +1484,7 @@ window, a native `tools/list` carries a 300-second private cache hint, against
1311
1484
  an hour on meta endpoints. This hint does not guarantee that a host refreshes
1312
1485
  its cached annotations; the stale-approval race is accepted on writable pools.
1313
1486
 
1314
- **Listing.** `tools/list` is one page with no cursor (Eve 0.86 reads only the
1487
+ **Listing.** `tools/list` is one page with no cursor (Eve 0.90 reads only the
1315
1488
  first page). A pool listing more than 1,000 tools fails `tools/list` with a
1316
1489
  JSON-RPC error asking the operator to narrow the pool; nothing is silently
1317
1490
  dropped. A connector whose catalog cannot load is left out of that listing
@@ -1328,7 +1501,8 @@ never repeat a write; page a truncated result as its notice says. A pool
1328
1501
  **Recovery.** Repair and validation fields retain the exact constraint details.
1329
1502
  Discovery `nextAction` points to the host's tool search (`host: "search"`), with
1330
1503
  only a `query` argument. Retry actions and retry text name the native tool,
1331
- never a meta wrapper. Auth actions carry `authorizationUrl` or `operatorUrl`;
1504
+ never a meta wrapper; a program on a native pool sees the same native wording
1505
+ in a failure it catches. An unknown name is reported as sent. Auth actions carry `authorizationUrl` or `operatorUrl`;
1332
1506
  give the returned URL and instructions to the user or operator. Capable modern
1333
1507
  clients may resume native auth and downstream form/URL input with the same
1334
1508
  wire name, arguments and returned `requestState`. Changed names, targets or
@@ -40,6 +40,10 @@ the sections in order, then [verify](#verify).
40
40
  | `DELETE /ui/credentials/<id>` on an empty slot answering 204 | 409 `{ "problem": "credential_required" }` |
41
41
  | The `agent_not_maintainer` denial reason | Removed; agents are refused at human endpoints before admission |
42
42
 
43
+ This guide records the 0.33 migration. Current versions require every pool to
44
+ list only people or only agents. If a 0.33 deployment combined them, follow
45
+ [Split mixed pools](./pool-membership-upgrade.md) as well.
46
+
43
47
  ## Maintainers and pool members
44
48
 
45
49
  0.32 combined an identity resolver, a top-level `grant`, and pool `grant`
@@ -349,4 +353,4 @@ pools: {
349
353
  absent: search finds none of their tools and a call fails
350
354
  `unknown_address`. The same holds in any pool without an owner, even one
351
355
  that lists a Workspace connector with a subject mapping.
352
- 6. Confirm each `<agent>-<user>` pool lists no other person in `members`.
356
+ 6. Confirm each `<agent>-<user>` pool lists only the agent in `members` (the current construction rule).
@@ -39,7 +39,9 @@ If an agent needs to act as Alice, put that access in her personal pool and set
39
39
  to use. Confirm the pool's tools and members before adding an owner. Follow
40
40
  [the agent pool layout](./auth.md#organizing-an-agents-pools) for the full
41
41
  configuration. People in the same pool, agents in owned pools and connectors
42
- with a fixed `subject` address keep their behavior.
42
+ with a fixed `subject` address keep their behavior. Current versions also
43
+ require each pool to list only people or only agents; see
44
+ [Split mixed pools](./pool-membership-upgrade.md).
43
45
 
44
46
  ## Optional settings and development tooling
45
47
 
@@ -24,7 +24,7 @@ committed through the deployment's own store, so the contract's `agents` and
24
24
  last seen are fixed. The restricted state is the member fixture from
25
25
  `test/fixtures/operator-contract-v2.ts`, and the endpoint and matrix variants
26
26
  use its maintainer fixture, which covers every runs-as kind and agent pools,
27
- with `test/fixtures/operator-console.ts` crowding the team pool so its avatar
27
+ with `test/fixtures/operator-console.ts` crowding the people-only team pool so its avatar
28
28
  stack collapses to "+3".
29
29
  Collections use their corresponding browser types. Real contract serialization,
30
30
  member projection and secret withholding are exercised through
@@ -113,6 +113,12 @@ Pool endpoint rows show the effective surface (`meta` or `native`) and whether
113
113
  code is on. These are read-only facts from deployment configuration; change
114
114
  `surface` and `executeCode` in deployment code.
115
115
 
116
+ Plain `/mcp` is routed by sign-in: the `/mcp` row and matrix column show the
117
+ whole catalog maintainers receive, and a pool row shows what its people
118
+ receive at both `/mcp/<pool>` and, when they belong to no other pool, plain
119
+ `/mcp`. A note above the rows says so. People in several pools appear in
120
+ `config.peopleInSeveralPools`; plain `/mcp` refuses them.
121
+
116
122
  One row each for `/mcp` and every pool: the path, a read-only pill on a
117
123
  read-only endpoint, a copyable URL, who can use it, its connectors as colored
118
124
  monograms (four, then "+N"), its tool count over a read and write bar, and a
@@ -121,7 +127,9 @@ Setup button.
121
127
  - Who can use it is `root.maintainers` on `/mcp` and the pool's `members`
122
128
  elsewhere, as an avatar stack named on hover and to screen readers (label,
123
129
  raw id when the label is null, or agent name). Four show, then a "+N"
124
- button. A pool with agent members labels its two URLs People and Agents.
130
+ button. A pool lists only people or only agents. An agent pool's row
131
+ still shows two URLs labeled People and Agents; the People URL
132
+ (`/mcp/<pool>`) admits no one, because the pool has no people.
125
133
  - Expanding a row lists everyone by name, then each connector with what it
126
134
  runs as and its tools, write tools amber with a pencil. Runs-as comes from
127
135
  the endpoint's matrix cell: "Shared · saved <date>", "Shared · connected
@@ -194,8 +202,9 @@ tabs:
194
202
  annotation badges (Destructive, Open world), filtered by search and All,
195
203
  Read and Write. A tool opens its schema in a side drawer.
196
204
  - **Connection.** An Authentication card (mode, OAuth client mechanism, who
197
- manages it, OAuth actions, and the credential card with masked last four,
198
- field states, and Replace, Test and Remove); a Diagnostics card of tiles
205
+ manages it, the last verification, OAuth actions, and the credential card
206
+ with masked last four, field states, and Replace, Test and Remove); a
207
+ Diagnostics card of tiles
199
208
  (catalog age and last call) with a fix prompt beside each
200
209
  problem; and a Configuration card. Its badges name the kind (Remote MCP,
201
210
  HTTP API or Custom), provider, auth scope, endpoint host and usage guide.
@@ -340,10 +349,12 @@ connections**, has no tabs; its heading shows the signed-in email when the
340
349
  sign-in exposes one (Clerk's primary email), and its top bar shows branding and
341
350
  an **Account** button with Sign out or Change token. It lists:
342
351
 
343
- - **Your endpoints**: one card per admitted `/mcp/<pool>` URL with a copy button
344
- and **Client setup**. For example, Bob in pool `team` copies
345
- `https://connecta.example.com/mcp/team` into an MCP client and authenticates
346
- as Bob.
352
+ - **Your endpoints**: one card per address with a copy button and **Client
353
+ setup**. A member of exactly one pool sees one card for plain `/mcp`, named
354
+ after the pool it serves; a member of several pools sees one
355
+ `/mcp/<pool>` card per pool. For example, Bob, only in pool `team`, copies
356
+ `https://connecta.example.com/mcp` into an MCP client and authenticates as
357
+ Bob.
347
358
  - **Your accounts**: the personal connectors in their pools, each with a
348
359
  monogram, tool count, one status badge in an aligned column, and only the
349
360
  controls its `manage.actions` lists. A saved credential that no longer fits
@@ -415,7 +426,8 @@ without this capability contribute an unconfirmed count of zero. For example,
415
426
  The maintainer view carries:
416
427
 
417
428
  - `config`, the construction-time `describeConfig()` snapshot, including
418
- `maintainers` and each pool's `members`, and `configSources`, which labels
429
+ `maintainers`, each pool's `members`, `peopleInSeveralPools` (non-maintainer
430
+ people plain `/mcp` refuses because they are in several pools), and `configSources`, which labels
419
431
  each serialized leaf as `default` or `config` from option presence rather
420
432
  than equality with the default. Factory descriptions carry relative
421
433
  `optionSources` paths so resolved module and transport defaults retain their
@@ -488,7 +500,9 @@ The maintainer view carries:
488
500
  The member view is exactly that member's projection: `config`,
489
501
  `configSources` and `root` are `null`, `live.connectors`, `agents` and
490
502
  `matrix` are empty, and `live.activity` is `"forbidden"`. `pools` lists only
491
- their pools, each with `owner: null` and `members: []`. `connectors` lists
503
+ their pools, each with `owner: null` and `members: []`. With exactly one pool,
504
+ its `humanPath` is `/mcp`, the address that serves them that pool, and the
505
+ member's catalog reads carry that pool as plain `/mcp` would. `connectors` lists
492
506
  each connector in those pools with its title, the member's pools that serve
493
507
  it, its auth scope, the tools those pools grant with their classification,
494
508
  `connection` (their own partition's state and problem code for a personal
@@ -556,6 +570,58 @@ the config snapshot, and never treats a `per_caller` overlay as an error.
556
570
  complete classified tools with input/output schemas, registration path, catalog
557
571
  age, and activity-derived last-call facts under the reader's disclosure policy.
558
572
  A failed or timed-out connector releases its own scope; siblings finish independently.
573
+ The status and catalog observation has its own deadline: 8 seconds, or
574
+ `discovery.probeTimeoutMs` when that is shorter, in which case the whole row
575
+ uses that one deadline. The 8-second value is fixed, not configured; agent
576
+ discovery keeps the full `discovery.probeTimeoutMs`. When the deadline ends
577
+ first, the downstream read is aborted and the row returns `status: "error"`
578
+ with `problem: "connector_timeout"` in the row and its overlay. The row still
579
+ carries its credential card and auth permissions, so a slow connector's slot
580
+ stays manageable. Any other failure keeps `connector_unavailable`. The
581
+ connector renders as **Not responding**, with fixed copy, a fix prompt and
582
+ Refresh.
583
+ **Stored status.** Each live `GET /ui/connectors/:id` records one status
584
+ observation for that connector and viewer partition before it answers: its
585
+ state, problem code, tool count and observation time. Authored static catalogs
586
+ such as `api()` connectors are recorded too, since their status still checks
587
+ auth. The record lives in the `connector-health:v1:` family under the same
588
+ owner partition as the catalog cache (principal, subject, caller identity and
589
+ pool, endpoint, described configuration and package version), so a personal
590
+ connector's observation is one person's. It also carries the catalog
591
+ generation read before the probe, through the request's read memo that the
592
+ catalog listing reuses. It holds no tools, schemas, status prose or
593
+ credential facts. It expires 120 seconds after it was observed, and lapses at
594
+ once when a credential change, OAuth connect or disconnect, or invalidation
595
+ rotates the generation. A read that ends at the row's own deadline, including
596
+ a configured `discovery.probeTimeoutMs` shorter than 8 seconds, records
597
+ `connector_timeout` before it answers. An observation is written only while
598
+ the caller is still waiting: a request its caller has already left starts no
599
+ write. A write that has started is never raced against a deadline or
600
+ detached, because a storage write cannot be withdrawn once sent. The request,
601
+ whether it times out or its caller leaves meanwhile, settles only after that
602
+ write does, bounded by the storage layer like every other awaited write, so
603
+ nothing commits after the request ends.
604
+
605
+ `GET /ui/connectors/:id?observation=stored` answers from a current observation
606
+ without probing the downstream. The row and overlay carry `observedAgeMs`, the
607
+ stored `status`, `problem` and `toolCount`, and `tools: []`; the tools stay in
608
+ the catalog cache. The credential card, drift, permissions and any
609
+ authorization link are computed fresh on every request. Without a current
610
+ observation the route does the live read and records it, with no
611
+ `observedAgeMs`. Nothing is refreshed after the response: no deferred work and
612
+ no `ctx.waitUntil`.
613
+
614
+ The browser asks each connector for its stored observation on the page load
615
+ that follows admission, and fixes the observation time once as each stored
616
+ row lands. The list card and the connector page mark a stored row the same
617
+ way, "Stored status, checked … ago", with an age that keeps counting, and
618
+ show its tools as awaiting the live read rather than as none. The browser then
619
+ sends one plain live read per stored row, through the same queue of eight
620
+ detail reads. A stored `connector_timeout` row is not read again on its own;
621
+ its mark says to Refresh, and it waits for Refresh like a live timeout.
622
+ Refresh, a single connector's read, recovery retries and OAuth polls always
623
+ read live.
624
+
559
625
  The summary and each detail carry `viewerKey` for the principal admitted to that
560
626
  request. The browser publishes the detail and overlay atomically only when that
561
627
  key matches admission. A changed viewer key, including an Access switch between
@@ -568,7 +634,7 @@ screen. The first answer for the new viewer triggers readmission, which clears
568
634
  everything retained.
569
635
  Config observations never
570
636
  overwrite a completed detail row. Each connector opens at most one live observation
571
- per page load; the summary `/ui/data` performs no downstream probes.
637
+ per page load, after its stored read; the summary `/ui/data` performs no downstream probes.
572
638
 
573
639
  `GET /ui/api/config?connector=<id>` narrows the same contract to one connector
574
640
  before status and catalog probes. For a maintainer, config, provenance, live
@@ -628,8 +694,23 @@ bounded at 2, 2, 4 and 8 seconds; a later focus return can try again after backo
628
694
 
629
695
  Connector Refresh, focus recovery and mutation follow-ups read only affected
630
696
  connectors. Focus probes unhealthy rows, never healthy siblings. All detail reads
631
- share a four-request limit and coalesce per connector. Failures retry only their
697
+ in one tab share an eight-request limit and coalesce per connector, so a page of
698
+ up to eight connectors loads in one round. The limit is fixed, not configured.
699
+ It bounds how many connectors one tab waits on at once; a tab still reads each
700
+ connector once per load, so the limit adds no requests to any one connector.
701
+ Detail reads do not consume `admission.requests` permits, which govern `/mcp`
702
+ only. They still share isolate CPU and memory, D1, and downstream account quotas
703
+ with agent traffic; each tab adds up to eight concurrent downstream reads, and
704
+ several connectors on one vendor account share that vendor's rate limit. Over HTTP/1.1 the browser itself allows about six
705
+ connections per origin.
706
+ Failures retry only their
632
707
  connector with the same bounded backoff; recovery never replays a mutation.
708
+ A `connector_timeout` row is parked instead. It spends its automatic retry
709
+ budget, and focus and visibility returns skip it. Only Refresh, a full refresh
710
+ or a mutation follow-up reads it again, so an outage holds a detail slot once
711
+ per explicit request, never repeatedly. An OAuth start's consent-return poll is
712
+ the exception: a timeout during its 30-second window keeps polling, and success
713
+ still stops it.
633
714
  Mutations cancel affected reads and advance their publication fence both at
634
715
  dispatch and on settlement, including failure. Follow-ups require an observation
635
716
  started after settlement, so a Refresh or retry started during the write cannot
@@ -638,9 +719,21 @@ while identity and full refresh revisions fence the complete view. An explicit
638
719
  OAuth start polls only that connector every two seconds for up to 30 seconds.
639
720
  Concurrent authorizations retain independent cancellation and publication. Success,
640
721
  failed reads, disconnect, navigation, unmount and identity reset stop the matching
641
- poll. Recovery never starts authorization. A scoped result cannot confirm identity,
722
+ poll; a `connector_timeout` result does not. Recovery never starts authorization. A scoped result cannot confirm identity,
642
723
  role, token slots or any other deployment-wide admission fact.
643
724
 
725
+ A credential connector's `/ui/data` and `/ui/connectors/<id>` row carries
726
+ `verification: { outcome, observedAt }` when a record of the stored save
727
+ exists (see [last verification](./auth.md#test-then-save-and-last-verification)).
728
+ The auth facts show it as **Last verified**: Verified, Saved without a test,
729
+ Rejected, Missing permission, Service unreachable, Test timed out or Test
730
+ failed, with its time. A maintainer's credential form on a testable connector
731
+ saves through **Test and save** (`verify: "test"`). A failed test leaves the
732
+ form open with fixed copy for its failure class, and adds **Save without
733
+ testing**, a save without `verify`. Test shows the same per-class copy and reloads
734
+ the connector so its last verification updates. The member page's
735
+ credential form saves without a test and offers Test once a value is stored.
736
+
644
737
  Unreadable credential metadata uses `configured: null`, including named fields,
645
738
  and renders as unavailable with a retry prompt. It does not claim that the slot
646
739
  is empty. Overview, Endpoints, Connectors, connector