@zackbart/connecta 0.31.0 → 0.32.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 (95) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +15 -13
  3. package/bin/connecta.mjs +1 -0
  4. package/bin/version.mjs +1 -1
  5. package/dist/auth-elicitation.js +1 -1
  6. package/dist/catalog-service.js +7 -2
  7. package/dist/config-value-sources.js +6 -3
  8. package/dist/config.d.ts +26 -8
  9. package/dist/config.js +38 -16
  10. package/dist/connector-access.d.ts +2 -1
  11. package/dist/connectors/guarded-fetch.d.ts +9 -0
  12. package/dist/connectors/guarded-fetch.js +7 -0
  13. package/dist/describe-config.d.ts +6 -3
  14. package/dist/describe-config.js +3 -2
  15. package/dist/errors.d.ts +1 -1
  16. package/dist/errors.js +1 -0
  17. package/dist/execute.js +16 -14
  18. package/dist/index.js +4 -3
  19. package/dist/invocation.js +7 -4
  20. package/dist/meta-tools.js +17 -19
  21. package/dist/operator-record.d.ts +2 -2
  22. package/dist/operator-record.js +2 -0
  23. package/dist/operator-ui/contract.d.ts +5 -4
  24. package/dist/operator-ui/generated.js +2 -2
  25. package/dist/operator-ui/model.d.ts +1 -1
  26. package/dist/operator-ui/view.js +1 -0
  27. package/dist/program-writes.js +2 -2
  28. package/dist/provider.d.ts +2 -3
  29. package/dist/providers/_shared/google/workspace.js +1 -1
  30. package/dist/providers/breeze/skill.generated.js +2 -2
  31. package/dist/providers/cloudflare/skill.generated.js +1 -1
  32. package/dist/providers/docs/index.d.ts +1 -1
  33. package/dist/providers/docs/index.js +2 -2
  34. package/dist/providers/drive/index.d.ts +1 -1
  35. package/dist/providers/drive/index.js +1 -1
  36. package/dist/providers/forms/index.d.ts +1 -1
  37. package/dist/providers/forms/index.js +2 -2
  38. package/dist/providers/github/skill.generated.js +2 -2
  39. package/dist/providers/gmail/index.d.ts +1 -1
  40. package/dist/providers/gmail/index.js +274 -23
  41. package/dist/providers/gmail/skill.generated.js +2 -2
  42. package/dist/providers/linear/index.js +1 -1
  43. package/dist/providers/mixpanel/index.d.ts +27 -12
  44. package/dist/providers/mixpanel/index.js +58 -6
  45. package/dist/providers/mixpanel/operations.js +20 -0
  46. package/dist/providers/mixpanel/rest.js +603 -0
  47. package/dist/providers/mixpanel/skill.generated.js +3 -2
  48. package/dist/providers/mixpanel/value-safety.js +16 -0
  49. package/dist/providers/notion/index.js +16 -0
  50. package/dist/providers/notion/rest.js +138 -19
  51. package/dist/providers/notion/skill.generated.js +4 -4
  52. package/dist/providers/planning-center/index.d.ts +27 -0
  53. package/dist/providers/planning-center/index.js +286 -24
  54. package/dist/providers/planning-center/skill.generated.js +24 -4
  55. package/dist/providers/sheets/index.d.ts +1 -1
  56. package/dist/providers/sheets/index.js +3 -3
  57. package/dist/providers/slides/index.d.ts +1 -1
  58. package/dist/providers/slides/index.js +3 -3
  59. package/dist/providers/stripe/skill.generated.js +2 -2
  60. package/dist/providers/vercel/index.js +2 -2
  61. package/dist/providers/vercel/skill.generated.js +2 -2
  62. package/dist/providers/wispr-flow/index.d.ts +14 -0
  63. package/dist/providers/wispr-flow/index.js +117 -0
  64. package/dist/providers/wispr-flow/skill.generated.js +8 -0
  65. package/dist/routes/mcp.js +30 -10
  66. package/dist/routes/operator.js +14 -7
  67. package/dist/routes/ui-config.js +2 -2
  68. package/dist/server.js +3 -0
  69. package/dist/skills.js +5 -4
  70. package/dist/tool-safety.js +13 -5
  71. package/dist/types.d.ts +1 -1
  72. package/dist/ui.d.ts +1 -1
  73. package/dist/ui.js +1 -1
  74. package/dist/usage-guide.js +3 -3
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/documentation/architecture.md +38 -23
  78. package/documentation/auth.md +86 -27
  79. package/documentation/code-mode.md +81 -72
  80. package/documentation/deploying.md +10 -5
  81. package/documentation/integrating.md +56 -4
  82. package/documentation/meta-tools.md +44 -37
  83. package/documentation/operating.md +11 -8
  84. package/documentation/operator-tests.md +2 -3
  85. package/documentation/operator-ui.md +9 -3
  86. package/documentation/provider-auth-migration.md +5 -4
  87. package/documentation/provider-migration-0.29.md +1 -1
  88. package/examples/worker/AGENTS.md +5 -0
  89. package/examples/worker/src/connecta.config.ts +5 -0
  90. package/package.json +5 -1
  91. package/templates/node/AGENTS.md +6 -4
  92. package/templates/node/README.md +4 -2
  93. package/templates/node/package.json +1 -1
  94. package/templates/node/src/connecta.config.ts +5 -0
  95. package/dist/tool-safety.d.ts +0 -9
@@ -48,7 +48,7 @@ QuickJS subpath:
48
48
  import { createConnecta } from "@zackbart/connecta";
49
49
  import { quickJsExecutor } from "@zackbart/connecta/quickjs";
50
50
 
51
- createConnecta({ executor: quickJsExecutor() /* connectors, auth, storage… */ });
51
+ createConnecta({ executor: quickJsExecutor(), readOnly: false /* connectors, auth, storage… */ });
52
52
  ```
53
53
 
54
54
  `quickJsExecutor()` runs each program in a disposable child process, with CPU,
@@ -61,7 +61,7 @@ On Workers the Worker Loader is the sandbox; Dynamic Workers require the Paid pl
61
61
  ```ts
62
62
  import { workerExecutor } from "@zackbart/connecta/worker";
63
63
 
64
- createConnecta({ executor: workerExecutor({ loader: env.LOADER }) });
64
+ createConnecta({ executor: workerExecutor({ loader: env.LOADER }), readOnly: false /* … */ });
65
65
  ```
66
66
 
67
67
  The adapter uses only `loader` and an optional `timeout`. Its admission defaults
@@ -283,7 +283,10 @@ signature is byte-identical to the one `search_tools` returns.
283
283
  integration is ambiguous. `safety: "readOnly"` selects stored read verdicts;
284
284
  `"approvalRequired"` selects stored write verdicts; omitted or `"all"` selects
285
285
  both. Search and describe rows carry `classification`. These filters grant no
286
- authority and change no pool trust or call admission decision.
286
+ authority and change no call admission decision. On a read-only endpoint,
287
+ search and describe omit write tools (`W12`): a write is absent from every
288
+ filter, and describing one is `unknown_tool`. `connecta.skill` guides are not
289
+ filtered and may name such a write; calling it is still refused.
287
290
 
288
291
  **S2.** A requested object schema carries `inputKeys`, `requiredInputKeys`
289
292
  (declared properties only), and `outputKeys`: the names the rendered schema
@@ -394,10 +397,10 @@ and a 32 KiB bridge ceiling. `page` is zero-based and mutually exclusive with
394
397
  are at least four bytes wide and align both edges to UTF-8 boundaries.
395
398
  Follow `nextOffset`, reassemble inside the program, and return a reduced value.
396
399
  Before returning a page, the host rechecks current authentication, connector/tool
397
- grants, pool membership, and endpoint trust against both the original and current
398
- tool classification. A read-only endpoint cannot page a write result, so a
399
- direct write there returns an inline truncation notice without stashing the
400
- result or advertising a paging action. Binding
400
+ grants, pool membership, and whether the endpoint is read-only against both the
401
+ original and current tool classification. A read-only endpoint cannot page a
402
+ write result; it never runs a write, so any write result it is asked for came
403
+ from a writable endpoint. Binding
401
404
  mismatches, revoked access, and old entries without bindings are `not_found`, as
402
405
  are unknown or expired ids; a storage failure is `unavailable`. IDs are random UUIDs.
403
406
  The `get_result` meta-tool is removed.
@@ -477,7 +480,7 @@ Connecta passes an admitted decoded URI as an opaque MCP `resources/read`
477
480
  argument to the configured endpoint. It never fetches that
478
481
  URI or creates a connector from it.
479
482
 
480
- Resources are protocol reads in both `read-only` and `trusted` pools. A read
483
+ Resources are protocol reads on read-only and writable endpoints alike. A read
481
484
  requires a grant to the whole connector through the endpoint/pool and identity
482
485
  intersection. Exact-tool grants, including guarded grants, authorize no
483
486
  resources. An absent, hidden, tool-restricted or resource-incapable connector
@@ -553,27 +556,27 @@ URL-elicitation hosts retain their existing flow.
553
556
  may do. A provider maps each downstream failure to the code that tells the
554
557
  caller what to do next, and never invents a cause it was not told.
555
558
 
556
- | Code | Raised when | `retryable` |
557
- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
558
- | `unknown_address` | no visible connector owns the tool address or grants the resource reader | false |
559
- | `unknown_tool` | the connector has no such tool | false |
560
- | `destructive_tool_requires_approval` | a program in a read-only pool attempted a write (`E4`) | false |
561
- | `auth_required` | the credential is missing, expired, or rejected | false |
562
- | `invalid_args` | arguments or discovery bounds were rejected | false |
563
- | `not_found` | the downstream answered and the resource is not there — the one code that says skip this id rather than stop, raised only where the provider tells absence from a permission gap | false |
564
- | `conflict` | the write named a base version someone else already moved past; nothing changed. `details.current` says where things stand (at most 20 whole-number entries) — re-read, reapply, retry with that base | false |
565
- | `input_required_unsupported` | a downstream asked for input; use `nextAction` for the equivalent direct call | false |
566
- | `rate_limited` | the downstream reported a rate limit | true |
567
- | `unavailable` | the downstream is down or unreachable; optional sanitized `details.host` and `details.code` describe the transport failure without paths, queries, credentials, or provider prose | true |
568
- | `timeout` | a call or sandbox deadline expired; details name operation, stage, elapsedMs, and deadlineMs | per operation |
569
- | `program_error` | guest JavaScript failed; `details.name`, `line`, and a fixed repair hint describe it | false |
570
- | `cancelled` | the run ended while this call was in flight (`E5`) | false |
571
- | `connector_call_failed` | anything else the connector threw | per message |
572
- | `catalog_lookup_failed` | the connector's catalog could not be loaded | per cause |
573
- | `result_processing_failed` | the result could not be prepared | per message |
574
- | `result_too_large` | a discovery response exceeded its byte bound | false |
575
- | `budget_exceeded` | the run exhausted a host-call, write, or emitted-output budget | false |
576
- | `write_outcome_unknown` | a trusted-pool write was sent and no answer came back; it is never sent again (`W9`) | false |
559
+ | Code | Raised when | `retryable` |
560
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
561
+ | `unknown_address` | no visible connector owns the tool address or grants the resource reader | false |
562
+ | `unknown_tool` | the connector has no such tool | false |
563
+ | `read_only_endpoint` | a program on a read-only endpoint attempted a write (`E4`) | false |
564
+ | `auth_required` | the credential is missing, expired, or rejected | false |
565
+ | `invalid_args` | arguments or discovery bounds were rejected | false |
566
+ | `not_found` | the downstream answered and the resource is not there — the one code that says skip this id rather than stop, raised only where the provider tells absence from a permission gap | false |
567
+ | `conflict` | the write named a base version someone else already moved past; nothing changed. `details.current` says where things stand (at most 20 whole-number entries) — re-read, reapply, retry with that base | false |
568
+ | `input_required_unsupported` | a downstream asked for input; use `nextAction` for the equivalent direct call | false |
569
+ | `rate_limited` | the downstream reported a rate limit | true |
570
+ | `unavailable` | the downstream is down or unreachable; optional sanitized `details.host` and `details.code` describe the transport failure without paths, queries, credentials, or provider prose | true |
571
+ | `timeout` | a call or sandbox deadline expired; details name operation, stage, elapsedMs, and deadlineMs | per operation |
572
+ | `program_error` | guest JavaScript failed; `details.name`, `line`, and a fixed repair hint describe it | false |
573
+ | `cancelled` | the run ended while this call was in flight (`E5`) | false |
574
+ | `connector_call_failed` | anything else the connector threw | per message |
575
+ | `catalog_lookup_failed` | the connector's catalog could not be loaded | per cause |
576
+ | `result_processing_failed` | the result could not be prepared | per message |
577
+ | `result_too_large` | a discovery response exceeded its byte bound | false |
578
+ | `budget_exceeded` | the run exhausted a host-call, write, or emitted-output budget | false |
579
+ | `write_outcome_unknown` | a program write was sent and no answer came back; it is never sent again (`W9`) | false |
577
580
 
578
581
  **E3.** `auth_required` carries the same recovery envelope as `call_tool`:
579
582
  `connector`, `operation`, `recovery` (`oauth`, `operator_config`, or
@@ -581,16 +584,13 @@ caller what to do next, and never invents a cause it was not told.
581
584
  sentence. A program cannot recover credentials — only an operator can — so stop
582
585
  and let the failure reach the model.
583
586
 
584
- **E4.** A tool classified as a write never runs in a
585
- program unless the pool is trusted (`W12`). It is refused with
586
- `destructive_tool_requires_approval` before validation and before anything is
587
- sent, `nextAction` carrying its canonical address to `call_destructive_tool`
588
- and a schema-filtered snapshot within the 512-byte echo budget. `argsRedacted`
589
- marks partial or withheld arguments; use the original arguments if this flag is
590
- set or `args` is absent. Snapshots are never clipped. The host's prompt on that
591
- call is the approval, and the only one: generated code can neither mint the
592
- capability nor approve its own write, and the model's short `reason` grants no
593
- authority and never goes downstream.
587
+ **E4.** A tool classified as a write never runs on a read-only endpoint
588
+ (`W12`). A program's call is refused with `read_only_endpoint` before
589
+ validation and before anything is sent. The refusal carries no `nextAction` and
590
+ echoes no arguments: no route on that endpoint writes, so the program reports
591
+ it rather than retrying. Generated code can neither mint the capability nor
592
+ change the endpoint; on a writable endpoint, approval of the program as a write
593
+ belongs to the client.
594
594
 
595
595
  **E5.** Failures of the _execution_, not of a call, never appear inside the
596
596
  guest: admission rejection (`executor_overloaded`, retryable, with
@@ -631,7 +631,7 @@ Program diagnostic fields are bounded before transport, including escaped text;
631
631
  a large custom error name cannot turn a failure into a truncated success.
632
632
 
633
633
  **E7.** `retryable` for `unknown_address`, `unknown_tool`, and
634
- `destructive_tool_requires_approval` is pinned false, never inferred from an
634
+ `read_only_endpoint` is pinned false, never inferred from an
635
635
  address containing `503`, `429`, or `temporar`. The first two carry
636
636
  `nextAction: { function: "connecta.search", arguments: { query, connector?, includeSchemas: "compact" } }`
637
637
  — scoped discovery keyed to the surface the caller has, since a program cannot
@@ -686,7 +686,7 @@ Caught call failures retain that connector visibility when catalog loading or
686
686
  tool resolution fails. Recovery messages listing configured connectors or
687
687
  local guides retain visibility of those contributors too.
688
688
  Each page rechecks current auth,
689
- grants, pool membership, and trust. Reading a stash inside a program carries
689
+ grants, pool membership, and the endpoint's `readOnly`. Reading a stash inside a program carries
690
690
  its dependencies forward to any new return stash. The page cap is the smallest
691
691
  of the deployment cap and contributing connectors' effective `maxResultBytes`.
692
692
  Read-only endpoints cannot page write results. A UUID is a handle, never a grant.
@@ -882,17 +882,17 @@ deadline past the ceiling means raising the ceiling too.
882
882
  **L4.** Per-execution bounds that are contract, identical in both executors
883
883
  because connecta enforces them above the sandbox:
884
884
 
885
- | Bound | Value |
886
- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
887
- | Host calls per execution, shared by `search`, `describe`, `call`, and `read` | 20 by default, `execute.maxHostCalls` |
888
- | Trusted-pool writes per run, on top of the host calls they also spend (`W10`) | 10 by default, `execute.maxWrites` |
889
- | Deadline per host call | 15 s, `execute.hostCallTimeoutMs`; one deadline covers catalog resolution, admission, and the connector call |
890
- | Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
891
- | `describe` addresses | ≤ 100 |
892
- | `describe` nearby suggestions | ≤ 3 canonical addresses per failed entry |
893
- | Caller text echoed by `describe` recovery | ≤ 512 UTF-8 bytes per field, plus `…` |
894
- | Result | 24,000 serialized characters |
895
- | Logs presented to the model | 4,000 characters |
885
+ | Bound | Value |
886
+ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
887
+ | Host calls per execution, shared by `search`, `describe`, `call`, and `read` | 20 by default, `execute.maxHostCalls` |
888
+ | Writes per run on a writable endpoint, on top of the host calls they also spend (`W10`) | 10 by default, `execute.maxWrites` |
889
+ | Deadline per host call | 15 s, `execute.hostCallTimeoutMs`; one deadline covers catalog resolution, admission, and the connector call |
890
+ | Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
891
+ | `describe` addresses | ≤ 100 |
892
+ | `describe` nearby suggestions | ≤ 3 canonical addresses per failed entry |
893
+ | Caller text echoed by `describe` recovery | ≤ 512 UTF-8 bytes per field, plus `…` |
894
+ | Result | 24,000 serialized characters |
895
+ | Logs presented to the model | 4,000 characters |
896
896
 
897
897
  Every `call`, `read`, `search`, and `describe` spends one host call on entry, before
898
898
  resolution, validation, or dispatch. Catching a local refusal refunds nothing.
@@ -901,7 +901,7 @@ The first call beyond the budget ends the host run with one non-retryable
901
901
  `try/catch` cannot swallow it. Later host access, including `emit`, stops; pending
902
902
  replies are withheld. The refusal reaches no connector, returns no partial result,
903
903
  and discards accepted emits (`M4`). Calls still resolving or awaiting admission
904
- are cancelled, including trusted-pool writes past their gate. Dispatched trusted-pool writes
904
+ are cancelled, including writes past their gate. Dispatched writes
905
905
  drain under `W9`; unknown outcomes retain precedence. The lease then releases:
906
906
  QuickJS terminates its child, and the Worker adapter disposes RPC and loader
907
907
  handles independently of the guest deadline. Workers guest computation may
@@ -1003,22 +1003,22 @@ payload-free _by construction_ means the event has nowhere to put a payload.
1003
1003
  distinctive output is the program source — exactly what a payload-free history
1004
1004
  must never keep.
1005
1005
 
1006
- **V5.** A trusted-pool write a program issues after it has already returned is not
1006
+ **V5.** A write a program issues after it has already returned is not
1007
1007
  sent and records nothing: it was never an attempt (`W9`).
1008
1008
 
1009
1009
  ## Writes
1010
1010
 
1011
- Pool trust belongs to deployment code. The default `/mcp` endpoint uses
1012
- `trust: "read-only"`; each named pool has its own `trust`, also defaulting to
1013
- `"read-only"`. A trusted default endpoint does not make named pools trusted.
1014
- The default preserves refusal of ordinary program writes. A deployment must opt into program writes explicitly.
1011
+ Whether an endpoint writes belongs to deployment code. Every deployment sets
1012
+ `readOnly` for `/mcp` explicitly; construction fails without it. Each named pool inherits
1013
+ the deployment's `readOnly` unless it sets its own, in either direction. The
1014
+ client owns approval; Connecta answers one question per endpoint: can it write.
1015
1015
 
1016
1016
  ```ts
1017
1017
  createConnecta({
1018
1018
  connectors, executor,
1019
- trust: "read-only",
1019
+ readOnly: false, // required: false writes, true refuses every write call
1020
1020
  pools: {
1021
- automation: { tools: ["posthog"], trust: "trusted", grant },
1021
+ reporting: { tools: ["posthog"], readOnly: true, grant },
1022
1022
  },
1023
1023
  classification: {
1024
1024
  posthog: { exec: "read" }, // only for a deployment with a read-only exec contract
@@ -1027,6 +1027,9 @@ createConnecta({
1027
1027
  });
1028
1028
  ```
1029
1029
 
1030
+ The removed `trust` option, on the deployment or a pool, fails construction
1031
+ with a message naming `readOnly` (`INV-11`).
1032
+
1030
1033
  The registry computes one `classifyTool` verdict on each request-scoped
1031
1034
  published catalog entry. It caches and persists only raw downstream facts.
1032
1035
  Every caller consumes that stored verdict. Config cannot classify an individual
@@ -1044,7 +1047,7 @@ fail construction; unknown remote tool keys fail publication of the whole
1044
1047
  catalog. Overrides are exact tool names, including names containing dots.
1045
1048
  They are never inferred from descriptions or arguments.
1046
1049
 
1047
- **W9.** Programs in trusted pools may write. A dispatched write is awaited even
1050
+ **W9.** Programs on a writable endpoint may write. A dispatched write is awaited even
1048
1051
  if the program does not await it, bounded by its host-call deadline. A write
1049
1052
  that reaches accounting after the program settles is not sent. An unanswered
1050
1053
  write makes the run fail `write_outcome_unknown` with
@@ -1054,7 +1057,7 @@ write is automatically retried, including an ambiguous timeout. A downstream
1054
1057
  refusal or a tool's `isError` can establish failure; a timeout or an unanswered
1055
1058
  transport failure leaves the outcome unknown.
1056
1059
 
1057
- **W10.** `execute.maxWrites`, default 10, bounds writes in trusted programs in
1060
+ **W10.** `execute.maxWrites`, default 10, bounds a program's writes in
1058
1061
  addition to the host-call budget. It is checked after argument validation and
1059
1062
  before admission, so an over-budget write spends no permit.
1060
1063
 
@@ -1067,14 +1070,20 @@ reconciliation. Any new call must use the original arguments when its echo is
1067
1070
  partial or absent. A returned `writes` count cannot make a run safe to repeat.
1068
1071
  See [argument echo rules](./meta-tools.md#echo-budgets) for supported schema shapes.
1069
1072
 
1070
- **W12.** `trusted` endpoints annotate `execute_code` as a write and permit
1071
- program writes. `read-only` endpoints annotate it as read-only and refuse
1072
- program writes before validation, with `call_destructive_tool` as the recovery.
1073
- `call_tool` accepts only reads in either tier; `call_destructive_tool` may send
1074
- writes in either tier. Approval belongs to the host. Trust grants no tool or
1075
- connector access and cannot be set by guest code, request arguments, or a
1076
- downstream catalog. `execute.approval`, `Connector.approval`, and per-tool
1077
- approval exemptions are removed. `W1`–`W8` and `W11` remain retired.
1073
+ **W12.** Writable endpoints annotate `execute_code` as a write
1074
+ (`readOnlyHint: false, destructiveHint: true`) and permit program writes and
1075
+ `call_destructive_tool`; `call_tool` still accepts only reads and refuses a write
1076
+ with `destructive_tool_requires_approval`. Read-only endpoints annotate
1077
+ `execute_code` as read-only (`readOnlyHint: true, destructiveHint: false`),
1078
+ omit write tools from `search_tools`, `connecta.search`, and
1079
+ `connecta.describe`, and refuse every write call through `call_tool`,
1080
+ `call_destructive_tool`, and programs with `read_only_endpoint` before
1081
+ validation or dispatch. Guides and skills are documentation and may still name
1082
+ those tools. `tools/list` is the same six tools on both. Approval belongs to the client.
1083
+ `readOnly` grants no tool or connector access and cannot be set by guest code,
1084
+ request arguments, or a downstream catalog. `execute.approval`,
1085
+ `Connector.approval`, per-tool approval exemptions, and pool `trust` are
1086
+ removed. `W1`–`W8` and `W11` remain retired.
1078
1087
 
1079
1088
  ## Executor exceptions
1080
1089
 
@@ -1225,8 +1234,8 @@ rejection, and branded adapter acceptance across module copies.
1225
1234
  | `L5`, `L7`, `X2` | `test/quickjs-executor.node.test.ts` (CPU, heap), `test/execute.test.ts` and `test/executor-admission.test.ts` (bounded admission and queue) |
1226
1235
  | `L6`, `X10` | `test/guest-api-contract-quickjs.node.test.ts` and `test/guest-api-contract.test.ts` (whole oversized reads and one-write pageable handles), `test/sqlite-storage.node.test.ts` and `test/d1-storage.node.test.ts` (shared stash transfers), `test/quickjs-executor.node.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message), `test/quickjs-child-stderr.node.test.ts` (outer reply serialization failure settles the call) |
1227
1236
  | `V1`–`V4` | `test/guest-api-contract.test.ts` (dispatched calls, every refusal class including an address no connector owns, the friction each derives, no event for the execution itself), `test/activity.test.ts` (the shared code → friction table, the identity clamp, the one-attempt floor), `test/operator-view.test.ts`, `test/sql-storage-contract.ts`, run by `test/d1-storage.node.test.ts` and `test/sqlite-storage.node.test.ts` (historical pause and approval rows still render and round-trip) |
1228
- | `V5`, `W9` | `test/program-writes.test.ts` (an unawaited trusted-pool write finished and recorded, its unknown outcome reported, counts on a failed program, the classification table), `test/invocation-pipeline.test.ts` (the gate after validation, an unrecorded refusal) |
1229
- | `W10`, `W12` | `test/program-writes.test.ts` (a trusted-pool write runs and every other write keeps `E4`, the write budget, pool trust and override precedence, `call_tool` still refusing, search and describe verdicts, construction refusals), `test/operator-ui-model.test.ts`, `test/browser/operator-ui.spec.ts` (the badge) |
1237
+ | `V5`, `W9` | `test/program-writes.test.ts` (an unawaited program write finished and recorded, its unknown outcome reported, counts on a failed program, the classification table), `test/invocation-pipeline.test.ts` (the gate after validation, an unrecorded refusal) |
1238
+ | `W10`, `W12` | `test/program-writes.test.ts` (a writable endpoint's program write runs, a read-only endpoint omits writes from search and describe and refuses every write call on every path, including a write its guide names, a required deployment `readOnly`, the write budget, `readOnly` inheritance and overrides, `call_tool` still refusing, search and describe verdicts, construction refusals including retired `trust`), `test/operator-ui-model.test.ts`, `test/browser/operator-ui.spec.ts` (the badge) |
1230
1239
  | `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (rejected shapes, bounded native snapshots, paging before metadata traversal, and cyclic presentation refusal) |
1231
1240
  | `M2`, `M3` | `test/guest-api-contract.test.ts` (delivery order, truncated return plus delivered blocks), `test/execute-emit.test.ts` (envelope, `structuredContent`, byte-for-byte no-emit path) |
1232
1241
  | `M4` | `test/guest-api-contract.test.ts` (discard is visible), `test/execute-emit.test.ts` (structured and plain paths), `test/quickjs-executor.node.test.ts` (mid-run shutdown) |
@@ -64,7 +64,7 @@ through to `fetch`. The example owns those details and
64
64
 
65
65
  ## Compose capabilities and access
66
66
 
67
- Both configurations use `defineConfig((env) => …)` and a required executor.
67
+ Both configurations use `defineConfig((env) => …)`, a required executor, and a required `readOnly` choice.
68
68
  [Integrating services](./integrating.md) explains the `connectors` array.
69
69
  Configuration selects optional factories through explicit slots:
70
70
 
@@ -96,10 +96,15 @@ personal connection, token management, and activity access explicit.
96
96
  [Clerk setup](./auth.md#clerk-oauth-tokens-and-operator-sessions) owns resource-bound
97
97
  tokens and `aud_claim_enabled`. A person's operator session is not an MCP token.
98
98
 
99
- Root and named pools default to `read-only`. A `trusted` pool allows writes
100
- inside programs and marks `execute_code` as a write. Host approval remains the
101
- host's decision. Pool membership intersects the caller's grants; it never
102
- widens them. See [pools](./auth.md#pools) before changing trust.
99
+ Every deployment must set `readOnly` in its configuration; startup fails without
100
+ it, and the template and Worker example set `readOnly: false`. Each named pool
101
+ inherits that value unless it sets its own. A writable endpoint (`false`) allows
102
+ writes through `call_destructive_tool` and programs and marks `execute_code` as
103
+ a write; the client decides what to approve. A read-only endpoint (`true`) omits
104
+ write tools from search and describe and refuses every write call; provider
105
+ guides may still name those tools. Pool membership intersects
106
+ the caller's grants; it never widens them. See [pools](./auth.md#pools) before
107
+ changing `readOnly`.
103
108
 
104
109
  ## Verify and diagnose
105
110
 
@@ -35,7 +35,7 @@ const tracker = linear("tracker", {
35
35
  ```
36
36
 
37
37
  Linear defaults to OAuth; its read-only endpoint requests only the downstream
38
- read scope. That is separate from Connecta pool trust. Read the
38
+ read scope. That is separate from a Connecta `readOnly` endpoint. Read the
39
39
  [Linear skill](https://github.com/zackbart/connecta/blob/main/src/providers/linear/SKILL.md)
40
40
  and [factory options](https://github.com/zackbart/connecta/blob/main/src/providers/linear/index.ts)
41
41
  for API-key framing, workspace instructions, and admission choices.
@@ -49,6 +49,15 @@ mapping from admitted identity to the downstream account; a request must not
49
49
  choose its own account. Gmail never sends mail, and Drive never permanently
50
50
  deletes files.
51
51
 
52
+ Wispr Flow uses OAuth only through `wisprFlow()` from
53
+ `@zackbart/connecta/providers/wispr-flow`. It reaches the authorized account's
54
+ Notetaker content, with personal and work accounts holding separate data.
55
+ Its [provider guide](https://github.com/zackbart/connecta/blob/main/src/providers/wispr-flow/SKILL.md)
56
+ owns account access, search limits, transcript permissions and paging. The
57
+ reviewed classification includes task writes listed in Claude's directory even
58
+ though Wispr's public help describes read-only access; discover the account's
59
+ actual tools before assuming those writes exist.
60
+
52
61
  Stripe requires `auth`: `{ type: "oauth" }` reaches Stripe's hosted MCP
53
62
  server, and `{ type: "apiKey" }` with `mode` reaches Connecta's REST connector
54
63
  with an operator-managed key. Notion requires `auth` the same way:
@@ -66,6 +75,48 @@ MCP clients to its hosted server, an operational gate outside Connecta. See the
66
75
  To use both implementations of one provider, configure two connector ids with
67
76
  independent credentials.
68
77
 
78
+ Planning Center's `excludeApps` removes whole products from a connection, for
79
+ example `planningCenter("pco", { purpose, excludeApps: ["giving"] })` for a
80
+ deployment that keeps agents away from donor and financial data. Excluded
81
+ products lose their named tools, guide text, and version pins, and the
82
+ `pco_api_get` and `pco_api_mutate` hatches refuse their paths before sending.
83
+ The check reads the exact URL the transport would fetch, and while any product
84
+ is excluded a hatch path must be literal: dot segments, empty segments,
85
+ backslashes, whitespace, and percent-encoded `/`, `\`, `?`, `#`, or `%` are
86
+ refused rather than normalized.
87
+
88
+ Exclusion also closes the routes through which the excluded product's data is
89
+ reachable through another product. While any product is excluded the connection removes
90
+ `list_lists`, `list_list_people`, and `run_list` and refuses every People list,
91
+ list result, rule, and condition, by path or `include`, because a list's
92
+ conditions can draw on any product, such as a donor list built from Giving
93
+ donations, and lists can depend on other lists. Planning Center publishes a
94
+ list's conditions, but a reliable check would have to follow every nested list,
95
+ so the connection does not attempt one. It refuses webhook event and delivery
96
+ payloads, since a payload can carry any product's records, and every change to
97
+ an existing webhook subscription by id, since the id does not name its product.
98
+ A new subscription must name a reachable product's event. People cannot be
99
+ excluded.
100
+
101
+ What exclusion guarantees is that agents reach none of the excluded product's
102
+ endpoints, no live cross-product query such as a People list, and no webhook
103
+ payload or change to an existing subscription. It does not hide records that the church's own
104
+ automations or staff have copied into a reachable product. A
105
+ [Giving automation](https://help.planningcenter.com/en/144355-automations-in-giving.html)
106
+ can add first-time donors to a People workflow, and those workflow cards stay
107
+ readable, as do donor details recorded in notes, custom fields, or form
108
+ answers. Those are People data once written, so the connection does not refuse
109
+ them by guessing at their origin.
110
+
111
+ For a hard boundary, give the connection a personal access token belonging to a
112
+ Planning Center user with No Access in the excluded product. Planning Center
113
+ sets permissions per product for each user, does not grant Giving to
114
+ organization administrators automatically, and lets People workflows be shared
115
+ per workflow; a token carries its user's permissions, so Planning Center itself
116
+ refuses what that user cannot see. `excludeApps` is defense in depth on top of
117
+ that boundary: it narrows what Connecta exposes, not what the token can do
118
+ elsewhere.
119
+
69
120
  ## Another remote MCP server
70
121
 
71
122
  Declare its endpoint and intended auth mode. This fragment uses a server-stored
@@ -137,9 +188,10 @@ which binds the grant to configured API origins and manages authentication.
137
188
  [Downstream OAuth on api()](./auth.md#downstream-oauth-on-api) owns a concrete
138
189
  HTTP example and the client setup contract.
139
190
 
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
191
+ Mark every side-effecting tool `readOnlyHint: false`. On a writable endpoint
192
+ writes use `call_destructive_tool` or a program, within the caller's grants; a
193
+ `readOnly` endpoint refuses them and omits them from search. Client approval is independent of
194
+ tool definition. Use declared output
143
195
  schemas when known; runtime observations cannot replace them.
144
196
 
145
197
  ## Separate ownership from visibility
@@ -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.
@@ -381,7 +390,7 @@ caught catalog-load errors, unknown-tool failures, and recovery messages that
381
390
  list visible connectors or local guides. Returns with no downstream calls still
382
391
  require matching identity, origin and endpoint, current auth, and pool membership. A page must
383
392
  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
393
+ and read-only checks. Old entries lacking bindings fail closed. A random UUID is a
385
394
  handle, never an access grant.
386
395
 
387
396
  New entries store UTF-8 bytes in a base64 envelope split across storage keys,
@@ -512,13 +521,10 @@ format: "text", text }`. The page size is clamped to the original call's inline
512
521
  cap and a 32 KiB program bridge ceiling, with UTF-8 alignment and forward progress. Reassemble inside the sandbox;
513
522
  returning pages unchanged can hit the program result cap. The admitted subject
514
523
  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.
524
+ match. The host rechecks current access and whether the endpoint is read-only
525
+ before returning each page; a read-only endpoint cannot read a write stash.
526
+ Caller arguments select no partition. A read-only endpoint never runs a write,
527
+ so every write result it could page came from a writable endpoint.
522
528
 
523
529
  A refused stash write cannot undo a downstream success. Direct calls and program returns produce
524
530
  a paging-unavailable notice, without a handle or recovery action. A write's
@@ -820,10 +826,11 @@ attempted tool name; an unknown tool scopes the same query to the connector that
820
826
  answered. The suggested route follows the route the caller took:
821
827
  `tool: "search_tools"` for a top-level call, `function: "connecta.search"` with
822
828
  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.
829
+ way to call a tool. On a writable endpoint, `call_tool` reaching an unannotated,
830
+ write-capable, or destructive tool returns `destructive_tool_requires_approval`
831
+ with `nextAction` for `call_destructive_tool` and the canonical address. On a
832
+ read-only endpoint every write path returns `read_only_endpoint` with no
833
+ `nextAction`. Nothing is executed by these records.
827
834
 
828
835
  `connecta.describe` keeps its failures inline instead, so one miss cannot
829
836
  discard the other schemas; each failed entry carries a human `error`, typed
@@ -932,12 +939,12 @@ original arguments cannot repeat the write while the vendor retains the key
932
939
  (Stripe may prune v1 keys after 24 hours). After that, look the object up before
933
940
  retrying. When a connector recorded such facts, any other dispatched write whose
934
941
  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
942
+ with them under its own code, and program write accounting keeps it. `args` may be partial reconciliation context
936
943
  with `argsRedacted: true`. A withheld echo or arguments over the 512-byte budget
937
944
  carry `argsOmitted: true`; sensitive or unresolved schemas also carry
938
945
  `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
946
+ arguments if reconciliation requires another call. A timeout before dispatch remains `timeout`.
947
+ Program write accounting keeps this detail even if the guest catches the
941
948
  failure or returns early. Multiple uncertain writes use `uncertainCalls`,
942
949
  bounded to ten calls with `uncertainCallsTruncated` when more exist.
943
950