@zackbart/connecta 0.31.0 → 0.33.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 (169) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +30 -20
  3. package/bin/connecta.mjs +25 -6
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.d.ts +167 -23
  6. package/dist/access-tokens.js +676 -140
  7. package/dist/agent-identity.js +47 -0
  8. package/dist/auth/cloudflare-access.js +13 -5
  9. package/dist/auth-elicitation.js +1 -1
  10. package/dist/call-admission-context.js +15 -0
  11. package/dist/call-admission.js +30 -43
  12. package/dist/call-recovery.js +6 -2
  13. package/dist/catalog-service.js +7 -2
  14. package/dist/config-value-sources.js +14 -9
  15. package/dist/config.d.ts +58 -78
  16. package/dist/config.js +143 -47
  17. package/dist/connector-access.d.ts +4 -33
  18. package/dist/connector-access.js +15 -99
  19. package/dist/connector-caller.d.ts +7 -1
  20. package/dist/connector-caller.js +3 -2
  21. package/dist/connectors/api-connector.d.ts +27 -4
  22. package/dist/connectors/api-connector.js +2 -0
  23. package/dist/connectors/guarded-fetch.d.ts +9 -0
  24. package/dist/connectors/guarded-fetch.js +7 -0
  25. package/dist/connectors/remote-mcp.js +2 -0
  26. package/dist/describe-config.d.ts +32 -7
  27. package/dist/describe-config.js +9 -18
  28. package/dist/errors.d.ts +1 -1
  29. package/dist/errors.js +1 -0
  30. package/dist/execute.js +16 -14
  31. package/dist/index.d.ts +4 -3
  32. package/dist/index.js +30 -10
  33. package/dist/invocation.js +12 -7
  34. package/dist/membership.d.ts +51 -0
  35. package/dist/membership.js +121 -0
  36. package/dist/meta-tools.js +17 -19
  37. package/dist/module-contracts.d.ts +23 -6
  38. package/dist/operator-record.d.ts +2 -2
  39. package/dist/operator-record.js +2 -2
  40. package/dist/operator-ui/agents.d.ts +74 -0
  41. package/dist/operator-ui/agents.js +104 -0
  42. package/dist/operator-ui/contract.d.ts +151 -29
  43. package/dist/operator-ui/endpoints.js +192 -0
  44. package/dist/operator-ui/fix-prompts.js +4 -4
  45. package/dist/operator-ui/generated.js +5 -4
  46. package/dist/operator-ui/member-page.js +112 -0
  47. package/dist/operator-ui/model.d.ts +15 -7
  48. package/dist/operator-ui/view.d.ts +84 -19
  49. package/dist/operator-ui/view.js +83 -21
  50. package/dist/page-styles.js +1 -1
  51. package/dist/program-writes.js +2 -2
  52. package/dist/provider.d.ts +63 -11
  53. package/dist/provider.js +46 -7
  54. package/dist/providers/_shared/google/workspace.d.ts +53 -16
  55. package/dist/providers/_shared/google/workspace.js +31 -9
  56. package/dist/providers/_shared/rest/tools.js +2 -2
  57. package/dist/providers/_shared/rest/value-safety.d.ts +50 -0
  58. package/dist/providers/_shared/rest/value-safety.js +699 -7
  59. package/dist/providers/breeze/skill.generated.js +2 -2
  60. package/dist/providers/ccb/index.js +1 -1
  61. package/dist/providers/cloudflare/index.js +3 -3
  62. package/dist/providers/cloudflare/rest.js +25 -3
  63. package/dist/providers/cloudflare/skill.generated.js +2 -2
  64. package/dist/providers/cloudflare/value-safety.js +32 -0
  65. package/dist/providers/docs/index.d.ts +1 -1
  66. package/dist/providers/docs/index.js +5 -5
  67. package/dist/providers/docs/skill.generated.js +2 -2
  68. package/dist/providers/drive/index.d.ts +1 -1
  69. package/dist/providers/drive/index.js +4 -4
  70. package/dist/providers/drive/skill.generated.js +2 -2
  71. package/dist/providers/forms/index.d.ts +1 -1
  72. package/dist/providers/forms/index.js +5 -5
  73. package/dist/providers/forms/skill.generated.js +2 -2
  74. package/dist/providers/github/skill.generated.js +2 -2
  75. package/dist/providers/gmail/index.d.ts +1 -1
  76. package/dist/providers/gmail/index.js +279 -26
  77. package/dist/providers/gmail/skill.generated.js +2 -2
  78. package/dist/providers/linear/index.js +1 -1
  79. package/dist/providers/mixpanel/index.d.ts +27 -12
  80. package/dist/providers/mixpanel/index.js +58 -6
  81. package/dist/providers/mixpanel/operations.js +20 -0
  82. package/dist/providers/mixpanel/rest.js +603 -0
  83. package/dist/providers/mixpanel/skill.generated.js +3 -2
  84. package/dist/providers/mixpanel/value-safety.js +16 -0
  85. package/dist/providers/notion/index.d.ts +7 -0
  86. package/dist/providers/notion/index.js +116 -11
  87. package/dist/providers/notion/rest.js +183 -24
  88. package/dist/providers/notion/skill.generated.js +4 -4
  89. package/dist/providers/notion/value-safety.js +30 -5
  90. package/dist/providers/overflow/index.js +1 -1
  91. package/dist/providers/planning-center/index.d.ts +27 -0
  92. package/dist/providers/planning-center/index.js +287 -25
  93. package/dist/providers/planning-center/skill.generated.js +24 -4
  94. package/dist/providers/sheets/index.d.ts +4 -1
  95. package/dist/providers/sheets/index.js +9 -6
  96. package/dist/providers/sheets/skill.generated.js +2 -2
  97. package/dist/providers/slides/index.d.ts +1 -1
  98. package/dist/providers/slides/index.js +6 -6
  99. package/dist/providers/slides/skill.generated.js +2 -2
  100. package/dist/providers/stripe/rest.js +9 -2
  101. package/dist/providers/stripe/skill.generated.js +2 -2
  102. package/dist/providers/stripe/value-safety.js +22 -0
  103. package/dist/providers/vercel/index.js +2 -2
  104. package/dist/providers/vercel/rest.js +20 -2
  105. package/dist/providers/vercel/skill.generated.js +2 -2
  106. package/dist/providers/vercel/value-safety.js +22 -0
  107. package/dist/providers/wispr-flow/index.d.ts +14 -0
  108. package/dist/providers/wispr-flow/index.js +117 -0
  109. package/dist/providers/wispr-flow/skill.generated.js +8 -0
  110. package/dist/registry.d.ts +2 -3
  111. package/dist/registry.js +8 -9
  112. package/dist/routes/access-tokens.js +82 -75
  113. package/dist/routes/activity-disclosure.js +17 -18
  114. package/dist/routes/activity.js +34 -54
  115. package/dist/routes/actor-labels.js +33 -4
  116. package/dist/routes/agent-path.js +43 -0
  117. package/dist/routes/connect.js +16 -12
  118. package/dist/routes/credentials.js +16 -40
  119. package/dist/routes/mcp.js +155 -85
  120. package/dist/routes/oauth-management.js +11 -5
  121. package/dist/routes/oauth.js +3 -9
  122. package/dist/routes/operator.js +25 -17
  123. package/dist/routes/shared.d.ts +60 -20
  124. package/dist/routes/shared.js +187 -96
  125. package/dist/routes/ui-config.js +437 -97
  126. package/dist/routes/ui-data.js +2 -2
  127. package/dist/routes/ui.js +9 -16
  128. package/dist/runtime/call-admission.js +9 -0
  129. package/dist/server.js +8 -0
  130. package/dist/skills.js +5 -4
  131. package/dist/storage/keys.js +9 -1
  132. package/dist/tool-safety.js +13 -5
  133. package/dist/types.d.ts +12 -1
  134. package/dist/ui.d.ts +2 -2
  135. package/dist/ui.js +17 -15
  136. package/dist/usage-guide.js +3 -3
  137. package/dist/version.d.ts +1 -1
  138. package/dist/version.js +1 -1
  139. package/dist/workspace-address.js +9 -0
  140. package/dist/workspace-subject.js +23 -0
  141. package/documentation/README.md +46 -22
  142. package/documentation/architecture.md +378 -58
  143. package/documentation/auth.md +806 -215
  144. package/documentation/code-mode.md +93 -81
  145. package/documentation/deploying.md +128 -26
  146. package/documentation/integrating.md +145 -22
  147. package/documentation/meta-tools.md +58 -46
  148. package/documentation/migration-0.33.md +344 -0
  149. package/documentation/operating.md +14 -9
  150. package/documentation/operator-tests.md +117 -31
  151. package/documentation/operator-ui.md +326 -55
  152. package/documentation/provider-auth-migration.md +42 -27
  153. package/documentation/provider-migration-0.29.md +3 -3
  154. package/examples/worker/AGENTS.md +35 -8
  155. package/examples/worker/README.md +92 -53
  156. package/examples/worker/src/connecta.config.ts +41 -20
  157. package/examples/worker/wrangler.jsonc +3 -0
  158. package/package.json +7 -2
  159. package/templates/node/.env.example +3 -0
  160. package/templates/node/AGENTS.md +33 -10
  161. package/templates/node/Dockerfile +2 -2
  162. package/templates/node/README.md +71 -44
  163. package/templates/node/docker-compose.yml +3 -2
  164. package/templates/node/package.json +4 -3
  165. package/templates/node/src/adopt-token.ts +23 -0
  166. package/templates/node/src/connecta.config.ts +33 -12
  167. package/templates/node/src/rotate-token.ts +17 -0
  168. package/dist/tool-safety.d.ts +0 -9
  169. package/templates/node/src/provision-token.ts +0 -17
@@ -1,16 +1,62 @@
1
1
  # Integrating services
2
2
 
3
- Use this guide to add a connector to deployment configuration. To implement a
4
- new maintained provider in Connecta itself, follow the repository's
5
- [add-provider skill](https://github.com/zackbart/connecta/blob/main/.claude/skills/add-provider/SKILL.md)
6
- and [AGENTS.md](https://github.com/zackbart/connecta/blob/main/AGENTS.md).
3
+ Use this guide to change connectors in deployment configuration.
7
4
  [All agent routes](./README.md) are in the index.
8
5
 
6
+ ## Add or remove a connector
7
+
8
+ Connectors are code-owned. Edit the deployment's `src/connecta.config.ts`;
9
+ there is no install button or runtime registration API.
10
+
11
+ To add one:
12
+
13
+ 1. Choose a maintained provider, `remoteMcp()`, or `api()` using the sections
14
+ below. Give it a unique, stable id such as `crm`; renaming an id selects
15
+ different stored credentials and changes every tool address.
16
+ 2. Put the constructed connector in `connectors`. Keep secrets in environment
17
+ variables or the encrypted vault. Choose shared or personal auth explicitly
18
+ for the intended audience; agents cannot see personal-auth connectors.
19
+ 3. Add the id (all its tools) or exact `id.tool` addresses to the intended
20
+ pools' `tools`. Maintainers at plain `/mcp` already see the whole catalog.
21
+ Follow [the agent pool layout](./auth.md#organizing-an-agents-pools) for an
22
+ agent: shared connectors go in its base constant, per-user connectors only
23
+ in that user's pool.
24
+ 4. Run the deployment's `npm run typecheck` (Worker copies: `tsc --noEmit`),
25
+ restart Node or redeploy the Worker, and
26
+ [connect its credential](./auth.md#connect-a-shared-credential) if needed.
27
+ Run [doctor](./deploying.md#verify-and-diagnose), discover its tools from
28
+ the intended pool, and perform a bounded read. Check an excluded pool too.
29
+
30
+ For example, merge this fragment into the existing configuration, retaining
31
+ its storage, executor, auth, and `readOnly` settings:
32
+
33
+ ```ts
34
+ import { remoteMcp } from "@zackbart/connecta";
35
+
36
+ const crm = remoteMcp("crm", {
37
+ url: "https://crm.example.com/mcp",
38
+ authScope: "shared",
39
+ auth: { type: "credential", credential: { label: "CRM API token" } },
40
+ });
41
+ const assistant = { tools: ["crm", "tickets"], members: ["agent:assistant"] };
42
+ // connectors: [crm, tickets], agents: ["assistant"], pools: { assistant }
43
+ ```
44
+
45
+ To remove `crm`, first disconnect its OAuth grant or delete its saved
46
+ credential while the connector still exists, using a signed-in maintainer
47
+ for shared auth. Each person disconnects their own personal account. Remove
48
+ `crm` and all `crm.*` addresses from every pool (including base constants),
49
+ then remove its entry from `connectors`, unused imports, and unused secret
50
+ configuration. Remove any pools left with no tools (empty `tools` fails
51
+ construction), and update or retire clients pointing at those pools. Typecheck, restart/redeploy, run doctor, and confirm discovery
52
+ and calls no longer find it. Removing configuration alone does not revoke a
53
+ credential at the downstream service; revoke unused credentials there too.
54
+
9
55
  ## Choose a connector
10
56
 
11
57
  | Need | Configuration path | Check before using |
12
58
  | -------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------- |
13
- | A maintained integration | Its `@zackbart/connecta/providers/<name>` factory | Provider `SKILL.md`, options, auth ownership, supported tools |
59
+ | A maintained integration | Its `@zackbart/connecta/providers/<name>` factory | Published factory declarations, auth ownership, supported tools |
14
60
  | Another hosted remote MCP server | `remoteMcp()` from the root package | Endpoint, authentication, current catalog, and read/write annotations |
15
61
  | A selected HTTP API capability | `api()` from the root package | Input/output schemas, explicit read/write hints, bounded handler, credential handling |
16
62
 
@@ -19,6 +65,14 @@ registry enforcement, caller grants, result bounds, and activity paths. There
19
65
  is no runtime installation or registration. Keep connector IDs unique and
20
66
  stable because they prefix canonical `<connectorId>.<toolName>` addresses.
21
67
 
68
+ `callAdmission` is optional on `remoteMcp()`, `api()`, custom connectors, and
69
+ provider variants that expose an override. Only omission or `undefined` uses
70
+ the connector's documented default, which may be no admission. Every defined
71
+ value is validated at construction. Malformed policies, including `false`,
72
+ `null`, `0`, and `""`, throw before serving requests; they never disable
73
+ admission or select a default. Custom connector policies are validated when
74
+ the deployment constructs its registry.
75
+
22
76
  ## Maintained integrations
23
77
 
24
78
  Import a provider individually. For example, Linear requires an explicit
@@ -35,20 +89,30 @@ const tracker = linear("tracker", {
35
89
  ```
36
90
 
37
91
  Linear defaults to OAuth; its read-only endpoint requests only the downstream
38
- read scope. That is separate from Connecta pool trust. Read the
39
- [Linear skill](https://github.com/zackbart/connecta/blob/main/src/providers/linear/SKILL.md)
40
- and [factory options](https://github.com/zackbart/connecta/blob/main/src/providers/linear/index.ts)
41
- for API-key framing, workspace instructions, and admission choices.
92
+ read scope. That is separate from a Connecta `readOnly` endpoint. Read the installed `dist/providers/linear/index.d.ts` for API-key framing,
93
+ workspace instructions, and admission choices.
42
94
 
43
95
  The [human README](../README.md#what-you-can-do-with-it) lists available
44
- integrations. Each [provider folder](https://github.com/zackbart/connecta/tree/main/src/providers)
45
- contains its scoped guide, options, tests, fixtures, and review data. Prefer
46
- that guide to general assumptions about a service's API. Google Workspace
96
+ integrations. In the installed package, `dist/providers/<name>/index.d.ts`
97
+ and its imported declarations describe each factory's options. After adding
98
+ a connector, use `search_tools` to discover its current tools and `connecta.skill("connector:<id>")` in `execute_code`
99
+ to read its runtime guide; provider source skills are not installed files.
100
+ Google Workspace
47
101
  providers require administrator-managed domain-wide delegation and a deployment
48
- mapping from admitted identity to the downstream account; a request must not
49
- choose its own account. Gmail never sends mail, and Drive never permanently
102
+ mapping from admitted identity to the downstream account; an agent acts as its
103
+ pool's `owner` instead ([auth guide](auth.md#pool-owners-for-google-workspace)).
104
+ A request must not choose its own account. Gmail never sends mail, and Drive never permanently
50
105
  deletes files.
51
106
 
107
+ Wispr Flow uses OAuth only through `wisprFlow()` from
108
+ `@zackbart/connecta/providers/wispr-flow`. It reaches the authorized account's
109
+ Notetaker content, with personal and work accounts holding separate data.
110
+ Its runtime guide, available through `connecta.skill("connector:<id>")` in `execute_code`, describes account access,
111
+ search limits, transcript permissions and paging. The
112
+ reviewed classification includes task writes listed in Claude's directory even
113
+ though Wispr's public help describes read-only access; discover the account's
114
+ actual tools before assuming those writes exist.
115
+
52
116
  Stripe requires `auth`: `{ type: "oauth" }` reaches Stripe's hosted MCP
53
117
  server, and `{ type: "apiKey" }` with `mode` reaches Connecta's REST connector
54
118
  with an operator-managed key. Notion requires `auth` the same way:
@@ -66,6 +130,58 @@ MCP clients to its hosted server, an operational gate outside Connecta. See the
66
130
  To use both implementations of one provider, configure two connector ids with
67
131
  independent credentials.
68
132
 
133
+ Both Notion auth variants accept optional `callAdmission`. For a token
134
+ connector it replaces the whole per-runtime default of 180 calls per minute
135
+ and 3 concurrent. Notion's [request limits](https://developers.notion.com/reference/request-limits)
136
+ allow 600 requests per minute per connection on Business and Enterprise and
137
+ 180 on other plans. A separate workspace-wide limit is shared by all
138
+ connections. Size an override for other runtime traffic and tools that make
139
+ multiple HTTP requests. Only an omitted or `undefined` policy selects the
140
+ default; malformed policies, including `false`, `null`, `0`, and `""`, throw
141
+ at construction on both auth variants.
142
+
143
+ Planning Center's `excludeApps` removes whole products from a connection, for
144
+ example `planningCenter("pco", { purpose, excludeApps: ["giving"] })` for a
145
+ deployment that keeps agents away from donor and financial data. Excluded
146
+ products lose their named tools, guide text, and version pins, and the
147
+ `pco_api_get` and `pco_api_mutate` hatches refuse their paths before sending.
148
+ The check reads the exact URL the transport would fetch, and while any product
149
+ is excluded a hatch path must be literal: dot segments, empty segments,
150
+ backslashes, whitespace, and percent-encoded `/`, `\`, `?`, `#`, or `%` are
151
+ refused rather than normalized.
152
+
153
+ Exclusion also closes the routes through which the excluded product's data is
154
+ reachable through another product. While any product is excluded the connection removes
155
+ `list_lists`, `list_list_people`, and `run_list` and refuses every People list,
156
+ list result, rule, and condition, by path or `include`, because a list's
157
+ conditions can draw on any product, such as a donor list built from Giving
158
+ donations, and lists can depend on other lists. Planning Center publishes a
159
+ list's conditions, but a reliable check would have to follow every nested list,
160
+ so the connection does not attempt one. It refuses webhook event and delivery
161
+ payloads, since a payload can carry any product's records, and every change to
162
+ an existing webhook subscription by id, since the id does not name its product.
163
+ A new subscription must name a reachable product's event. People cannot be
164
+ excluded.
165
+
166
+ What exclusion guarantees is that agents reach none of the excluded product's
167
+ endpoints, no live cross-product query such as a People list, and no webhook
168
+ payload or change to an existing subscription. It does not hide records that the church's own
169
+ automations or staff have copied into a reachable product. A
170
+ [Giving automation](https://help.planningcenter.com/en/144355-automations-in-giving.html)
171
+ can add first-time donors to a People workflow, and those workflow cards stay
172
+ readable, as do donor details recorded in notes, custom fields, or form
173
+ answers. Those are People data once written, so the connection does not refuse
174
+ them by guessing at their origin.
175
+
176
+ For a hard boundary, give the connection a personal access token belonging to a
177
+ Planning Center user with No Access in the excluded product. Planning Center
178
+ sets permissions per product for each user, does not grant Giving to
179
+ organization administrators automatically, and lets People workflows be shared
180
+ per workflow; a token carries its user's permissions, so Planning Center itself
181
+ refuses what that user cannot see. `excludeApps` is defense in depth on top of
182
+ that boundary: it narrows what Connecta exposes, not what the token can do
183
+ elsewhere.
184
+
69
185
  ## Another remote MCP server
70
186
 
71
187
  Declare its endpoint and intended auth mode. This fragment uses a server-stored
@@ -79,8 +195,8 @@ const service = remoteMcp("service", {
79
195
  description: "Our team's hosted service",
80
196
  auth: { type: "credential" },
81
197
  });
82
- // Add service to connectors. Configure the vault and permitted operators
83
- // before supplying its credential through the operator UI.
198
+ // Add service to connectors. Configure the vault; a signed-in maintainer
199
+ // supplies its credential through the operator UI.
84
200
  ```
85
201
 
86
202
  Credential auth defaults to an `Authorization: Bearer` header. Choose framing
@@ -137,19 +253,20 @@ which binds the grant to configured API origins and manages authentication.
137
253
  [Downstream OAuth on api()](./auth.md#downstream-oauth-on-api) owns a concrete
138
254
  HTTP example and the client setup contract.
139
255
 
140
- Mark every side-effecting tool `readOnlyHint: false`. On read-only endpoints
141
- writes use `call_destructive_tool`; trusted programs may write within their
142
- grants. Host approval is independent of tool definition. Use declared output
256
+ Mark every side-effecting tool `readOnlyHint: false`. On a writable endpoint
257
+ writes use `call_destructive_tool` or a program, within the caller's grants; a
258
+ `readOnly` endpoint refuses them and omits them from search. Client approval is independent of
259
+ tool definition. Use declared output
143
260
  schemas when known; runtime observations cannot replace them.
144
261
 
145
262
  ## Separate ownership from visibility
146
263
 
147
264
  `authScope: "shared"` uses deployment-owned downstream auth; `"personal"` uses
148
265
  the admitted principal's partition. Visibility is a different decision,
149
- controlled by identity and pool grants. A personal connector must not close
266
+ controlled by maintainers and pool membership. A personal connector must not close
150
267
  over a shared secret or derive its owner from arguments. Remote MCP refuses
151
268
  literal header credentials with personal scope. Review
152
- [principals and grants](./auth.md#principals-visibility-and-operators) and
269
+ [maintainers and members](./auth.md#principals-visibility-and-operators) and
153
270
  [management permissions](./auth.md#management-permissions) before exposing a
154
271
  new integration or its authentication controls.
155
272
 
@@ -158,7 +275,13 @@ perform a bounded read. Add negative checks for target restrictions and writes
158
275
  when authoring a handler or provider. Follow repository verification policy
159
276
  when changing package code.
160
277
 
161
- ## Implementation and evidence
278
+ ## Contributor reference
279
+
280
+ ### Implementation and evidence
281
+
282
+ To implement a maintained provider in Connecta itself, follow the repository's
283
+ [add-provider skill](https://github.com/zackbart/connecta/blob/main/.claude/skills/add-provider/SKILL.md)
284
+ and [AGENTS.md](https://github.com/zackbart/connecta/blob/main/AGENTS.md).
162
285
 
163
286
  Factory contracts live in [remote MCP](https://github.com/zackbart/connecta/blob/main/src/connectors/remote-mcp.ts),
164
287
  [API connector](https://github.com/zackbart/connecta/blob/main/src/connectors/api-connector.ts),
@@ -2,7 +2,7 @@
2
2
 
3
3
  Connecta keeps one small tool surface in model context and resolves downstream
4
4
  tools behind it. `search_tools` finds addresses, the call tools enforce the
5
- stored classification, `execute_code` runs programs under pool trust, and `connecta.result` inside programs
5
+ stored classification, `execute_code` runs programs on a writable or read-only endpoint, and `connecta.result` inside programs
6
6
  pages bounded results.
7
7
 
8
8
  This guide is the contract an MCP client sees. The in-program `connecta.*` API
@@ -31,10 +31,13 @@ configuration adds a seventh or removes one, so a client's cached tool list
31
31
  never depends on the storage behind the deployment. There was an eighth,
32
32
  `resume_execution`, while programs paused at writes; it left with pausing
33
33
  ([#672](https://github.com/zackbart/connecta/issues/672)). `execute_code`
34
- is annotated `readOnlyHint: true, destructiveHint: false` on a `read-only`
35
- endpoint, and `readOnlyHint: false, destructiveHint: true` on a `trusted`
36
- endpoint. Approval belongs to the host. Pool trust never changes connector
37
- visibility or tool grants. The four tools with structured-only success results
34
+ is annotated `readOnlyHint: true, destructiveHint: false` on a read-only
35
+ endpoint, and `readOnlyHint: false, destructiveHint: true` on a writable one.
36
+ Approval belongs to the client. A read-only endpoint keeps all six tools, omits
37
+ write tools from `search_tools` results and describe, and refuses every write
38
+ call; `readOnly` never changes connector visibility or read grants. Guides
39
+ (`queryAnalysis.guideSummary`, `skills`, `connecta.skill`, skill
40
+ `resources/read`) are documentation and may name tools the endpoint refuses. The four tools with structured-only success results
38
41
  advertise `outputSchema`. The two direct-call tools omit it because their default
39
42
  mode returns native `content`. Default-mode successes declare
40
43
  `_meta["dev.connecta/format"]: "json" | "text"`; value mode places the value in
@@ -70,9 +73,11 @@ credentials, logs, or raw error text.
70
73
 
71
74
  Connecta's discovery tools carry read-only hints. `authorize_connector`
72
75
  requests interactive authentication changes; `call_destructive_tool` sends
73
- writes and is annotated destructive. `execute_code` reports the selected pool's
74
- trust in its description and annotations. Its host may approve the whole
75
- program on a trusted endpoint. `call_destructive_tool.reason` is dropped before
76
+ writes and is annotated destructive. `execute_code` states whether the endpoint
77
+ is read-only or writable in its description and annotations. On a writable
78
+ endpoint the client may approve the whole program as a write. On a read-only
79
+ endpoint, the `call_tool`, `call_destructive_tool`, and `search_tools`
80
+ descriptions say so too; `call_destructive_tool` still runs reads there. `call_destructive_tool.reason` is dropped before
76
81
  anything runs.
77
82
 
78
83
  ## Routing between the call surfaces
@@ -93,12 +98,16 @@ page the completed direct-call result or oversized program return with
93
98
  `connecta.result` after the write runs once. Sampling or slicing a write's output
94
99
  inside the program can still discard the answer.
95
100
 
96
- A `trusted` pool lets the same program discover and dispatch writes.
97
- A `read-only` pool refuses writes before validation or dispatch with
98
- `destructive_tool_requires_approval` and a `nextAction` naming
99
- `call_destructive_tool` and the canonical address. Each write then uses its
100
- own top-level call. Connecta never pauses, replays, or approves a program.
101
- Top-level discovery remains available for catalog inspection and write routing.
101
+ On a writable endpoint the same program discovers and dispatches writes, capped
102
+ by `execute.maxWrites`, and `call_destructive_tool` sends one known write. On a
103
+ read-only endpoint nothing writes: `search_tools`, `connecta.search`, and
104
+ `connecta.describe` omit write tools as if the connector lacked them, and
105
+ every write call through `call_tool`, `call_destructive_tool`, or a program is
106
+ refused before validation or dispatch with `read_only_endpoint`,
107
+ `retryable: false`, and no `nextAction`. Connector guides are not filtered and
108
+ may still name such a write. No route on that endpoint writes; the agent
109
+ reports it. Connecta
110
+ never pauses, replays, or approves a program.
102
111
 
103
112
  Both search routes return the flat page specified under
104
113
  [lexical discovery](#lexical-discovery), using the catalog's shared output schema.
@@ -129,14 +138,14 @@ Read-only lookup belongs in `connecta.search` inside the program; top-level
129
138
  `search_tools` stays for explicit catalog inspection and approval-required
130
139
  discovery. Both take the same arguments.
131
140
 
132
- | Argument | What it does |
133
- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
134
- | `query` | two to four action/object terms; empty or whitespace-only browses |
135
- | `connector` | scopes to one id, loading that catalog alone instead of fanning out across every configured connector. Set it when the integration is obvious, omit it when the right one is genuinely ambiguous |
136
- | `safety` | `"readOnly"` selects stored `read` verdicts; `"approvalRequired"` selects stored `write` verdicts; omitted or `"all"` selects both. Each row carries `classification: "read" \| "write"`. These legacy filter names do not describe host approval policy or pool trust. |
137
- | `limit` / `offset` | page the ranked results; omit `limit` initially so the default eight-result page stays small |
138
- | `includeSchemas` | `"compact"` for the rendered routing view, `"json"` for the exact schema, `"typescript"` for a function signature ([below](#typescript-signatures)) |
139
- | `fullDescriptions` | unabridged tool purposes, at the obvious cost |
141
+ | Argument | What it does |
142
+ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
143
+ | `query` | two to four action/object terms; empty or whitespace-only browses |
144
+ | `connector` | scopes to one id, loading that catalog alone instead of fanning out across every configured connector. Set it when the integration is obvious, omit it when the right one is genuinely ambiguous |
145
+ | `safety` | `"readOnly"` selects stored `read` verdicts; `"approvalRequired"` selects stored `write` verdicts; omitted or `"all"` selects both. Each row carries `classification: "read" \| "write"`. These legacy filter names do not describe client approval policy; a read-only endpoint has no write rows. |
146
+ | `limit` / `offset` | page the ranked results; omit `limit` initially so the default eight-result page stays small |
147
+ | `includeSchemas` | `"compact"` for the rendered routing view, `"json"` for the exact schema, `"typescript"` for a function signature ([below](#typescript-signatures)) |
148
+ | `fullDescriptions` | unabridged tool purposes, at the obvious cost |
140
149
 
141
150
  Neither `connector` nor `safety` grants authority or changes invocation
142
151
  admission; they select what discovery shows and nothing else.
@@ -185,8 +194,7 @@ discovery made the same bet).
185
194
  ```
186
195
 
187
196
  It is something to read, not something that runs: programs stay JavaScript,
188
- and `execute_code` accepts no erasable TypeScript
189
- ([decision history](https://github.com/zackbart/connecta/blob/main/decisions/0001-ethos-verdict-table.md)). The output half is the provider's declared
197
+ and `execute_code` accepts no erasable TypeScript. The output half is the provider's declared
190
198
  schema, else an `S9` observation that opens with the
191
199
  `/* observed, not declared */` marker and keeps the `outputSchemaSource`
192
200
  field, else `unknown` — runtime evidence never reads as a contract, even when
@@ -273,7 +281,7 @@ is its slug; other IDs use `connector-` followed by a stable SHA-256 prefix.
273
281
  The suffix makes each
274
282
  document a valid Agent Skills file; the roots `skill://connecta/usage` and
275
283
  `skill://connecta/connectors/<connector-id>` also resolve. Provider guides are
276
- built from the generated `src/providers/*/SKILL.md` content with the existing
284
+ built from maintained provider guides with the existing
277
285
  connection context and deployment instructions. Local manifests contain a
278
286
  SHA-256 digest and UTF-8 byte size of the complete document. `usage` and
279
287
  `investigate` remain compatibility names; `connector:<id>` remains a
@@ -381,7 +389,7 @@ caught catalog-load errors, unknown-tool failures, and recovery messages that
381
389
  list visible connectors or local guides. Returns with no downstream calls still
382
390
  require matching identity, origin and endpoint, current auth, and pool membership. A page must
383
391
  match those bindings and pass current auth, connector/tool grants, pool membership,
384
- and trust checks. Old entries lacking bindings fail closed. A random UUID is a
392
+ and read-only checks. Old entries lacking bindings fail closed. A random UUID is a
385
393
  handle, never an access grant.
386
394
 
387
395
  New entries store UTF-8 bytes in a base64 envelope split across storage keys,
@@ -512,13 +520,10 @@ format: "text", text }`. The page size is clamped to the original call's inline
512
520
  cap and a 32 KiB program bridge ceiling, with UTF-8 alignment and forward progress. Reassemble inside the sandbox;
513
521
  returning pages unchanged can hit the program result cap. The admitted subject
514
522
  and principal, endpoint/pool, request origin, connector, and tool bindings must
515
- match. The host rechecks current access and trust before returning each page; a
516
- read-only endpoint cannot read a write stash. Caller arguments select no partition.
517
-
518
- A write on a read-only endpoint returns an inline truncation notice and any
519
- usable bounded preview. It says paging is unavailable and the write already
520
- ran; it carries no result handle or recovery action and stores no unreachable
521
- stash. Write paging remains available on trusted endpoints.
523
+ match. The host rechecks current access and whether the endpoint is read-only
524
+ before returning each page; a read-only endpoint cannot read a write stash.
525
+ Caller arguments select no partition. A read-only endpoint never runs a write,
526
+ so every write result it could page came from a writable endpoint.
522
527
 
523
528
  A refused stash write cannot undo a downstream success. Direct calls and program returns produce
524
529
  a paging-unavailable notice, without a handle or recovery action. A write's
@@ -592,8 +597,8 @@ same when no visible connector ID or title names that service. This service-name
592
597
  vocabulary is query interpretation, not an inventory of configured providers.
593
598
  For other service names, use the explicit `connector` scope. An unavailable
594
599
  catalog is a catalog failure, never an absence. Absence records and catalog
595
- errors use only the endpoint/pool and grant intersection; a connector hidden by
596
- that intersection is indistinguishable from an unconfigured one.
600
+ errors use only the endpoint's tools; a connector the endpoint does not serve
601
+ is indistinguishable from an unconfigured one.
597
602
 
598
603
  If no tool covers every search term, wider any-term fallback remains available
599
604
  for ordinary action/object searches and the page carries `matchMode: "partial"`.
@@ -820,10 +825,11 @@ attempted tool name; an unknown tool scopes the same query to the connector that
820
825
  answered. The suggested route follows the route the caller took:
821
826
  `tool: "search_tools"` for a top-level call, `function: "connecta.search"` with
822
827
  the same arguments when the miss happened inside `execute_code`, which has no
823
- way to call a tool. `call_tool` reaching an unannotated, write-capable, or
824
- destructive tool returns `nextAction` for `call_destructive_tool` with the
825
- canonical address, and so does the same call inside a program, unless config
826
- marks that pool trusted. Nothing is executed by these records.
828
+ way to call a tool. On a writable endpoint, `call_tool` reaching an unannotated,
829
+ write-capable, or destructive tool returns `destructive_tool_requires_approval`
830
+ with `nextAction` for `call_destructive_tool` and the canonical address. On a
831
+ read-only endpoint every write path returns `read_only_endpoint` with no
832
+ `nextAction`. Nothing is executed by these records.
827
833
 
828
834
  `connecta.describe` keeps its failures inline instead, so one miss cannot
829
835
  discard the other schemas; each failed entry carries a human `error`, typed
@@ -926,18 +932,22 @@ list connectors from another pool or identity's grants.
926
932
  A write that times out after dispatch returns `write_outcome_unknown` with
927
933
  `retryable: false` and an agent-only `uncertainCall: { address, args?, argsOmitted?, argsRedacted?, recovery? }`.
928
934
  No write is automatically replayed. `recovery` carries facts the connector
929
- recorded before dispatch (`src/call-recovery.ts`), such as the `idempotencyKey`
930
- a REST connector generated; a deliberate retry that reuses it with the exact
931
- original arguments cannot repeat the write while the vendor retains the key
935
+ recorded before dispatch, such as a REST connector's
936
+ generated or caller-supplied `idempotencyKey`. Recovery accepts keys up to 255
937
+ characters, matching the REST write schema. Other recovery values remain capped
938
+ at 128 characters, with at most four facts per call and camelCase names of at
939
+ most 32 characters. Values outside these bounds are dropped whole. A deliberate
940
+ retry that reuses the key with the exact original arguments cannot repeat the
941
+ write while the vendor retains the key
932
942
  (Stripe may prune v1 keys after 24 hours). After that, look the object up before
933
943
  retrying. When a connector recorded such facts, any other dispatched write whose
934
944
  outcome is unknown (a lost response body, a 5xx) also carries `uncertainCall`
935
- with them under its own code, and trusted-program write accounting keeps it. `args` may be partial reconciliation context
945
+ with them under its own code, and program write accounting keeps it. `args` may be partial reconciliation context
936
946
  with `argsRedacted: true`. A withheld echo or arguments over the 512-byte budget
937
947
  carry `argsOmitted: true`; sensitive or unresolved schemas also carry
938
948
  `argsRedacted: true`. When `args` is absent or redacted, use the original
939
- arguments if reconciliation requires another call. A timeout before dispatch remains `timeout`. Trusted
940
- program write accounting keeps this detail even if the guest catches the
949
+ arguments if reconciliation requires another call. A timeout before dispatch remains `timeout`.
950
+ Program write accounting keeps this detail even if the guest catches the
941
951
  failure or returns early. Multiple uncertain writes use `uncertainCalls`,
942
952
  bounded to ten calls with `uncertainCallsTruncated` when more exist.
943
953
 
@@ -947,7 +957,9 @@ SDK timeout classes, and runtime network codes. Untyped prose is non-retryable:
947
957
  name, or URL never changes the verdict. JSON-RPC `ProtocolError.data` is dropped
948
958
  at the downstream boundary, even when the agent may see the error's message.
949
959
 
950
- ## Source and tests
960
+ ## Contributor reference
961
+
962
+ ### Source and tests
951
963
 
952
964
  Handlers and schemas live in [meta-tools.ts](https://github.com/zackbart/connecta/blob/main/src/meta-tools.ts).
953
965
  [Catalog service](https://github.com/zackbart/connecta/blob/main/src/catalog-service.ts)