@zackbart/connecta 0.29.0 → 0.30.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 (69) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +123 -134
  3. package/bin/connecta.mjs +13 -2
  4. package/bin/version.mjs +1 -1
  5. package/dist/argument-redaction.js +230 -0
  6. package/dist/auth-elicitation.js +14 -9
  7. package/dist/authorization-handoff.js +111 -0
  8. package/dist/catalog-cache.js +2 -2
  9. package/dist/catalog-service.js +103 -63
  10. package/dist/config.d.ts +2 -0
  11. package/dist/config.js +12 -7
  12. package/dist/connectors/guarded-fetch.js +3 -3
  13. package/dist/connectors/negotiation-cache.js +1 -1
  14. package/dist/downstream-input-context.js +9 -0
  15. package/dist/downstream-input.js +13 -11
  16. package/dist/errors.d.ts +36 -12
  17. package/dist/errors.js +19 -9
  18. package/dist/execute.js +375 -43
  19. package/dist/executor-result.js +11 -9
  20. package/dist/executors/quickjs-child.js +12 -5
  21. package/dist/executors/quickjs-protocol.js +5 -6
  22. package/dist/executors/quickjs-runtime.js +6 -1
  23. package/dist/executors/quickjs.js +20 -4
  24. package/dist/guest-runtime.js +52 -2
  25. package/dist/guest-types.d.ts +47 -5
  26. package/dist/index.d.ts +1 -1
  27. package/dist/invocation.js +40 -14
  28. package/dist/meta-output.js +0 -15
  29. package/dist/meta-tools.js +166 -95
  30. package/dist/oauth-handoff.js +88 -11
  31. package/dist/program-result.js +5 -0
  32. package/dist/program-writes.js +5 -1
  33. package/dist/provider.d.ts +1 -1
  34. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  35. package/dist/providers/_shared/request-token.js +83 -0
  36. package/dist/providers/infisical/index.d.ts +7 -0
  37. package/dist/providers/infisical/index.js +713 -0
  38. package/dist/providers/infisical/skill.generated.js +8 -0
  39. package/dist/registry.d.ts +8 -6
  40. package/dist/registry.js +134 -61
  41. package/dist/resource-read.js +4 -1
  42. package/dist/routes/mcp.js +11 -2
  43. package/dist/sent-secrets.d.ts +54 -13
  44. package/dist/sent-secrets.js +770 -231
  45. package/dist/skills.js +26 -16
  46. package/dist/sqlite.js +21 -6
  47. package/dist/storage/keys.js +20 -5
  48. package/dist/storage/memory.js +9 -4
  49. package/dist/storage/sql.js +18 -9
  50. package/dist/types.d.ts +17 -3
  51. package/dist/usage-guide.js +49 -40
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/worker.js +2 -1
  55. package/documentation/README.md +63 -0
  56. package/documentation/architecture.md +121 -89
  57. package/documentation/auth.md +138 -5
  58. package/documentation/code-mode.md +224 -107
  59. package/documentation/deploying.md +146 -0
  60. package/documentation/integrating.md +159 -0
  61. package/documentation/meta-tools.md +135 -30
  62. package/documentation/operating.md +135 -0
  63. package/documentation/operator-tests.md +4 -0
  64. package/documentation/operator-ui.md +132 -0
  65. package/documentation/provider-migration-0.29.md +5 -0
  66. package/examples/worker/README.md +7 -0
  67. package/package.json +7 -1
  68. package/templates/node/README.md +7 -0
  69. package/templates/node/package.json +1 -1
@@ -0,0 +1,146 @@
1
+ # Deploying Connecta
2
+
3
+ Use this guide when creating or maintaining a deployment. For changes to the
4
+ Connecta package itself, follow [repository policy](https://github.com/zackbart/connecta/blob/main/AGENTS.md).
5
+ For work through an already connected endpoint, use [Operating an endpoint](./operating.md).
6
+ [All agent routes](./README.md) are in the index.
7
+
8
+ ## Choose a deployment
9
+
10
+ | Platform | Runnable instructions | Required runtime and state |
11
+ | ------------------ | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
12
+ | Node | [Node template](../templates/node/README.md) | Node 22.13 or later, QuickJS peer, one SQLite file |
13
+ | Docker | [Container setup in the same Node template](../templates/node/README.md#run-it-in-docker) | Same source and config, persistent named volume |
14
+ | Cloudflare Workers | [Worker example](../examples/worker/README.md#deploy) | Workers Paid plan, Worker Loader binding, one D1 database, Cloudflare Access |
15
+
16
+ These are the two deployment shapes. Docker packages the Node project.
17
+ Templates and their configuration files own the full runnable steps and
18
+ environment-variable names; follow them rather than rebuilding their setup
19
+ from the contract reference.
20
+
21
+ ## Start on Node
22
+
23
+ Create a new deployment directory with the CLI. `init` refuses an existing
24
+ path and pins the generated project's Connecta dependency to the CLI version:
25
+
26
+ ```sh
27
+ npx @zackbart/connecta init my-connecta
28
+ cd my-connecta
29
+ npm install
30
+ npm run --silent provision-token -- "local-machine"
31
+ # Save the returned cta_ token privately. It is shown only once.
32
+ npm start
33
+ ```
34
+
35
+ The client connects to `http://localhost:8787/mcp` with
36
+ `Authorization: Bearer <returned-cta-token>`. The generated project always
37
+ installs `accessTokens(storage)`. An empty database admits no MCP caller even
38
+ though `/health` works. `CONNECTA_TOKEN` is only a client/doctor variable;
39
+ it never configures server authentication. The local commands read the process
40
+ environment and do not load `.env`; Docker Compose does.
41
+
42
+ Edit `src/connecta.config.ts` for connectors, public URL, auth, identity rules,
43
+ and optional modules. `src/index.ts` only constructs and starts the deployment.
44
+ Follow the [template README](../templates/node/README.md) for storage paths,
45
+ backups, Docker provisioning, secrets, and optional modules. Run the deployment's
46
+ `npm run typecheck` after configuration changes.
47
+
48
+ ## Start on Workers
49
+
50
+ Follow the [Worker example](../examples/worker/README.md#deploy) to create D1,
51
+ set secrets, configure `PUBLIC_URL` and `LOADER`, and deploy with Wrangler.
52
+ When copying it into a separate repository, follow
53
+ [the separate-project installation](../examples/worker/README.md#copied-into-its-own-repository)
54
+ and pin Connecta's exact version. The optional `@cloudflare/codemode` peer
55
+ must be installed for the Worker executor.
56
+
57
+ Attach Cloudflare Access to the Worker destination. Interactive MCP clients
58
+ need Managed OAuth, Dynamic Client Registration, and the documented redirect
59
+ allowlist. Machines need Access service headers at the edge plus a stored
60
+ `cta_` bearer inside Connecta; service identity alone is refused. Keep
61
+ `enable_request_signal`, construct Connecta once per isolate, and pass `ctx`
62
+ through to `fetch`. The example owns those details and
63
+ [the machine-token procedure](../examples/worker/README.md#machine-tokens).
64
+
65
+ ## Compose capabilities and access
66
+
67
+ Both configurations use `defineConfig((env) => …)` and a required executor.
68
+ [Integrating services](./integrating.md) explains the `connectors` array.
69
+ Configuration selects optional factories through explicit slots:
70
+
71
+ | Slot | Factory/import | Purpose |
72
+ | -------------- | -------------------------------------------------------------------- | -------------------------------------------------- |
73
+ | `ui` | `operatorUi()` from `@zackbart/connecta/ui` | Operator pages and scoped config inspection |
74
+ | `vault` | `encryptedCredentialVault()` from `@zackbart/connecta/credentials` | Encrypted operator-managed credentials |
75
+ | `activity` | `activityHistory()` from `@zackbart/connecta/activity` | Payload-free history over the platform's SQL store |
76
+ | `auth` | Clerk or Cloudflare Access from `@zackbart/connecta/auth/*` | Interactive inbound authentication |
77
+ | `accessTokens` | `accessTokens(storage)` from `@zackbart/connecta/auth/access-tokens` | Stored machine-token authentication |
78
+
79
+ Omitting an optional module leaves its implementation inactive. Invalid or
80
+ unknown configuration refuses construction; do not catch it and silently
81
+ fall back. [Configuration](./architecture.md#configuration) owns validation
82
+ and `describeConfig()`, and [optional modules](./architecture.md#optional-deployment-modules)
83
+ owns their composition contracts.
84
+
85
+ Enable `encryptedCredentialVault(storage, key)` before persisting downstream
86
+ OAuth credentials. Without a sealing vault, OAuth grants are stored as plaintext.
87
+ The platform templates describe the base64 32-byte encryption key. Keep it
88
+ outside the database and source control, and preserve it across upgrades.
89
+ [OAuth state at rest](./auth.md#downstream-oauth-state-at-rest) owns sealing and
90
+ existing-record migration.
91
+
92
+ One deployment serves one tenant and may admit several people. Node uses Clerk
93
+ for human auth; Access is Workers-only. Keep credential administration,
94
+ personal connection, token management, and activity access explicit.
95
+ [Auth](./auth.md#principals-visibility-and-operators) owns identity and grants;
96
+ [Clerk setup](./auth.md#clerk-oauth-tokens-and-operator-sessions) owns resource-bound
97
+ tokens and `aud_claim_enabled`. A person's operator session is not an MCP token.
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.
103
+
104
+ ## Verify and diagnose
105
+
106
+ With the deployment running, replace the placeholder with its saved client
107
+ token and run doctor:
108
+
109
+ ```sh
110
+ CONNECTA_TOKEN='<stored-cta-token>' npx connecta doctor --url http://localhost:8787
111
+ ```
112
+
113
+ Doctor checks health, the required executor, and the current six-tool set.
114
+ For a Worker protected by Access, follow the example's doctor credentials
115
+ and service-header setup. If the operator UI is enabled, authenticated
116
+ `connecta doctor --config --url <public-url>` prints the caller-scoped
117
+ configuration snapshot as JSON. It runs no diagnostic program and prints no
118
+ raw HTTP failure body. [Operator UI](./operator-ui.md) describes the returned
119
+ configuration, live facts, and permission rules.
120
+
121
+ Health success alone does not prove a client can authenticate or reach an
122
+ integration. Check client connection and a bounded read through a chosen
123
+ connector; resolve credentials using [authorization recovery](./meta-tools.md#authorization-recovery).
124
+
125
+ ## Upgrade an existing deployment
126
+
127
+ Bump the exact package pin, read the [changelog](../CHANGELOG.md) for every
128
+ intervening release, and apply the relevant migration before startup. `init`
129
+ does not merge changes into an existing deployment.
130
+
131
+ - [Managed tokens from v0.23](./auth.md#managed-client-tokens-and-upgrading-from-v023) keep their secrets when storage, namespaces, and grants are retained. Replace the old boolean config with `accessTokens(storage)`; machine tokens gain no operator authority.
132
+ - [Node storage from 0.28](../templates/node/README.md#storage) uses `connecta migrate-state` to copy the JSON state into SQLite. Activity JSONL is not imported.
133
+ - [Worker storage from 0.28](../examples/worker/README.md#upgrading-from-028) copies KV into D1 with a one-shot migration.
134
+ - [Notion, Vercel, and Cloudflare for 0.29](./provider-migration-0.29.md) change defaults and retained API tool names.
135
+ - [Executor wiring](./code-mode.md#deploy-time-capability) requires branded shipped executors or a self-managed custom executor.
136
+
137
+ ## Implementation and evidence
138
+
139
+ Check the [CLI](https://github.com/zackbart/connecta/blob/main/bin/connecta.mjs),
140
+ [Node configuration](../templates/node/src/connecta.config.ts), and
141
+ [Worker configuration](../examples/worker/src/connecta.config.ts) when changing
142
+ setup. [Deployment-shape tests](https://github.com/zackbart/connecta/blob/main/test/deployment-shapes.node.test.ts),
143
+ [config tests](https://github.com/zackbart/connecta/blob/main/test/config.test.ts),
144
+ and [package smoke](https://github.com/zackbart/connecta/blob/main/scripts/check-package.mjs)
145
+ check the deployment and published package boundaries. Repository verification
146
+ requirements remain in AGENTS.md.
@@ -0,0 +1,159 @@
1
+ # Integrating services
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).
7
+ [All agent routes](./README.md) are in the index.
8
+
9
+ ## Choose a connector
10
+
11
+ | Need | Configuration path | Check before using |
12
+ | -------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------- |
13
+ | A maintained integration | Its `@zackbart/connecta/providers/<name>` factory | Provider `SKILL.md`, options, auth ownership, supported tools |
14
+ | Another hosted remote MCP server | `remoteMcp()` from the root package | Endpoint, authentication, current catalog, and read/write annotations |
15
+ | A selected HTTP API capability | `api()` from the root package | Input/output schemas, explicit read/write hints, bounded handler, credential handling |
16
+
17
+ All produce connectors in the deployment's `connectors` array and use the same
18
+ registry enforcement, caller grants, result bounds, and activity paths. There
19
+ is no runtime installation or registration. Keep connector IDs unique and
20
+ stable because they prefix canonical `<connectorId>.<toolName>` addresses.
21
+
22
+ ## Maintained integrations
23
+
24
+ Import a provider individually. For example, Linear requires an explicit
25
+ choice of its downstream endpoint:
26
+
27
+ ```ts
28
+ import { linear } from "@zackbart/connecta/providers/linear";
29
+
30
+ const tracker = linear("tracker", {
31
+ purpose: "Engineering issues in our workspace",
32
+ access: "read-only",
33
+ });
34
+ // Add tracker to the deployment's connectors array.
35
+ ```
36
+
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
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.
42
+
43
+ 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
47
+ 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
50
+ deletes files.
51
+
52
+ Notion, Vercel, and Cloudflare default to hosted MCP. Their explicit
53
+ `surface: "api"` choices retain selected REST capabilities, with Notion's
54
+ internal-integration identity preserved. To use both, configure distinct IDs
55
+ and independent credentials. Read the [0.29 migration guide](./provider-migration-0.29.md)
56
+ and its provider reconciliation tables for retained and removed names.
57
+
58
+ ## Another remote MCP server
59
+
60
+ Declare its endpoint and intended auth mode. This fragment uses a server-stored
61
+ credential slot; replace the illustrative URL with the real endpoint:
62
+
63
+ ```ts
64
+ import { remoteMcp } from "@zackbart/connecta";
65
+
66
+ const service = remoteMcp("service", {
67
+ url: "https://mcp.example.com/mcp",
68
+ description: "Our team's hosted service",
69
+ auth: { type: "credential" },
70
+ });
71
+ // Add service to connectors. Configure the vault and permitted operators
72
+ // before supplying its credential through the operator UI.
73
+ ```
74
+
75
+ Credential auth defaults to an `Authorization: Bearer` header. Choose framing
76
+ from the downstream contract, not from its HTTP API's convention. Remote auth
77
+ also supports OAuth, static headers, and request-local token resolution. Use
78
+ [shared and personal auth](./auth.md#shared-and-personal-auth),
79
+ [client registration](./auth.md#downstream-oauth-client-registration), and
80
+ [request-local tokens](./auth.md#request-local-downstream-bearer-tokens)
81
+ for those cases. Secrets belong in deployment secret storage or the vault,
82
+ not source literals.
83
+
84
+ Remote catalogs evolve. Discovery, direct calls, and program calls all enforce
85
+ classification against the current catalog. Missing or contradictory read hints
86
+ fail closed as writes. `classify` can supply a reviewed per-tool verdict;
87
+ schema changes can invalidate a reviewed read. Exact deployment classification
88
+ overrides take precedence. See
89
+ [reviewed classification](./architecture.md#providers-and-reviewed-classification)
90
+ for precedence, schema digests, and `unlisted: "hide"`. Review the behavior
91
+ before declaring a read, and do not use classification to bypass provider target
92
+ scopes or caller grants.
93
+
94
+ ## A hand-written HTTP capability
95
+
96
+ Define only the operations the deployment needs. Every `api()` tool needs a
97
+ nonempty description and explicit `readOnlyHint`. This local time tool matches
98
+ the shape used by the deployment templates:
99
+
100
+ ```ts
101
+ import { api } from "@zackbart/connecta";
102
+
103
+ const time = api("time", {
104
+ description: "Time, current timestamp",
105
+ tools: [{
106
+ name: "get_now",
107
+ description: "Return the current time as an ISO 8601 timestamp.",
108
+ inputSchema: { type: "object", properties: {} },
109
+ outputSchema: {
110
+ type: "object",
111
+ properties: { now: { type: "string" } },
112
+ required: ["now"],
113
+ },
114
+ annotations: { readOnlyHint: true },
115
+ handler: async () => ({ now: new Date().toISOString() }),
116
+ }],
117
+ });
118
+ ```
119
+
120
+ For an HTTP handler, use its supplied `ctx.fetch` to register outgoing secrets
121
+ for redaction, pass the request signal, and bound any pagination or fanout.
122
+ Read declared credential slots through `ctx.credential`; do not return their
123
+ values. With a declared static OAuth configuration, use `ctx.oauth.fetch`,
124
+ which binds the grant to configured API origins and manages authentication.
125
+ `api()` refuses simultaneous `oauth` and `credential` declarations.
126
+ [Downstream OAuth on api()](./auth.md#downstream-oauth-on-api) owns a concrete
127
+ HTTP example and the client setup contract.
128
+
129
+ Mark every side-effecting tool `readOnlyHint: false`. On read-only endpoints
130
+ writes use `call_destructive_tool`; trusted programs may write within their
131
+ grants. Host approval is independent of tool definition. Use declared output
132
+ schemas when known; runtime observations cannot replace them.
133
+
134
+ ## Separate ownership from visibility
135
+
136
+ `authScope: "shared"` uses deployment-owned downstream auth; `"personal"` uses
137
+ the admitted principal's partition. Visibility is a different decision,
138
+ controlled by identity and pool grants. A personal connector must not close
139
+ over a shared secret or derive its owner from arguments. Remote MCP refuses
140
+ literal header credentials with personal scope. Review
141
+ [principals and grants](./auth.md#principals-visibility-and-operators) and
142
+ [management permissions](./auth.md#management-permissions) before exposing a
143
+ new integration or its authentication controls.
144
+
145
+ After editing configuration, run the deployment's typecheck and doctor, then
146
+ perform a bounded read. Add negative checks for target restrictions and writes
147
+ when authoring a handler or provider. Follow repository verification policy
148
+ when changing package code.
149
+
150
+ ## Implementation and evidence
151
+
152
+ Factory contracts live in [remote MCP](https://github.com/zackbart/connecta/blob/main/src/connectors/remote-mcp.ts),
153
+ [API connector](https://github.com/zackbart/connecta/blob/main/src/connectors/api-connector.ts),
154
+ and [provider construction](https://github.com/zackbart/connecta/blob/main/src/provider.ts).
155
+ [Remote tests](https://github.com/zackbart/connecta/blob/main/test/remote-mcp.test.ts),
156
+ [API tests](https://github.com/zackbart/connecta/blob/main/test/api-connector.test.ts),
157
+ [provider contracts](https://github.com/zackbart/connecta/blob/main/test/provider-contracts.test.ts),
158
+ and [classification tests](https://github.com/zackbart/connecta/blob/main/test/classified-decorators.test.ts)
159
+ check shared behavior. Provider-specific tests remain beside each provider.
@@ -9,6 +9,21 @@ This guide is the contract an MCP client sees. The in-program `connecta.*` API
9
9
  those tools imply belongs to [code mode](./code-mode.md); inbound identity and
10
10
  credential administration belong to [auth](./auth.md).
11
11
 
12
+ ## Task routes
13
+
14
+ Start with [Operating an endpoint](./operating.md) for a workflow, or
15
+ [the agent index](./README.md) for setup and repository tasks. This reference
16
+ owns the top-level MCP contract; it does not require reading the core internals.
17
+
18
+ | Task | Sections |
19
+ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
20
+ | Choose a tool or construct its arguments | [Six-tool table](#the-six-tools), [call routing](#routing-between-the-call-surfaces), [downstream input](#downstream-input-on-direct-calls) |
21
+ | Find an operation and its schema | [Discovery context](#discovery-context), [TypeScript signatures](#typescript-signatures), [lexical discovery](#lexical-discovery) |
22
+ | Load provider instructions or native skills | [Connector guides](#connector-guides-and-skills), [native extension](#native-skills-extension) |
23
+ | Interpret a direct response or page a large result | [Representation](#result-representation), [truncation](#truncated-direct-call-results), [paging](#paging-with-connectaresult) |
24
+ | Recover a refused or invalid call | [Authorization](#authorization-recovery), [routing](#routing-recovery), [arguments](#argument-recovery), [echo budgets](#echo-budgets) |
25
+ | Change the contract | [Source and tests](#source-and-tests), [guest API](./code-mode.md#verification) |
26
+
12
27
  ## The six tools
13
28
 
14
29
  Every deployment requires an executor, so `tools/list` is exactly six. No
@@ -19,9 +34,11 @@ never depends on the storage behind the deployment. There was an eighth,
19
34
  is annotated `readOnlyHint: true, destructiveHint: false` on a `read-only`
20
35
  endpoint, and `readOnlyHint: false, destructiveHint: true` on a `trusted`
21
36
  endpoint. Approval belongs to the host. Pool trust never changes connector
22
- visibility or tool grants. Every meta-tool advertises `outputSchema` for its
23
- structured content; raw downstream MCP content stays in `content`. Direct
24
- results declare `format: "json" | "text"`, and value mode places the value in `data`.
37
+ visibility or tool grants. The four tools with structured-only success results
38
+ advertise `outputSchema`. The two direct-call tools omit it because their default
39
+ mode returns native `content`. Default-mode successes declare
40
+ `_meta["dev.connecta/format"]: "json" | "text"`; value mode places the value in
41
+ `data` and declares `format` in its complete result envelope.
25
42
 
26
43
  | Tool | Arguments | Returns |
27
44
  | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
@@ -72,9 +89,9 @@ a small sample for inspection before continuing in another call, which avoids
72
89
  repeated guesses at text formats and collection roots without restoring a
73
90
  mandatory discovery-only round trip. A one-time write can return the only copy
74
91
  of its result. Inspect and reduce the full value before a program returns, or
75
- use a direct `call_destructive_tool` result and page it with `connecta.result` after
76
- the write runs once. A program return has no page handle; sampling or slicing
77
- a write's output there can discard the answer.
92
+ page the completed direct-call result or oversized program return with
93
+ `connecta.result` after the write runs once. Sampling or slicing a write's output
94
+ inside the program can still discard the answer.
78
95
 
79
96
  A `trusted` pool lets the same program discover and dispatch writes.
80
97
  A `read-only` pool refuses writes before validation or dispatch with
@@ -302,16 +319,25 @@ and [2026-07-28 Resources](https://modelcontextprotocol.io/specification/2026-07
302
319
 
303
320
  ## Result representation
304
321
 
305
- For object results, `structuredContent` is the canonical full-fidelity value and
322
+ For structured envelopes, `structuredContent` is the canonical full-fidelity value and
306
323
  `content` carries the same complete value as compact JSON for clients that only
307
324
  consume text. Keeping both follows MCP's backwards-compatibility guidance;
308
325
  dropping the text copy waits on host-forwarding measurements showing supported
309
326
  clients do not need it. Direct-call failures set `isError: true` and carry the
310
327
  same `{ ok: false, error, durationMs, attempts }` envelope in both forms, whether
311
328
  `resultMode` is omitted, `"mcp"`, or `"value"`. Plain-text guidance stays
312
- text-only. A downstream MCP tool's successful native content blocks pass through
313
- in MCP result mode.
314
- When no text block exists and `structuredContent` is present, Connecta appends a
329
+ text-only. Successful direct calls with `resultMode` omitted or `"mcp"` return
330
+ their data only in `content`, with the downstream format in
331
+ `_meta["dev.connecta/format"]`. They omit `structuredContent`, including on
332
+ truncated results, so clients that prefer it cannot replace data or a preview
333
+ with metadata. API results keep their serialized value; downstream MCP native
334
+ content blocks pass through. Value-mode successes retain
335
+ `{ ok: true, data, format, durationMs, attempts }` in both forms, including when
336
+ `data` is a truncation notice. Direct-call tools advertise no `outputSchema`:
337
+ MCP requires a tool declaring one to supply conforming structured results, which
338
+ default native content does not promise.
339
+ When downstream MCP content has no text block and downstream `structuredContent`
340
+ is present, Connecta appends a
315
341
  text block carrying its compact JSON and then applies the same content size
316
342
  guard, which preserves structured-only results including `null`, arrays, and
317
343
  scalars. An existing text mirror stays unchanged; Connecta adds no second copy.
@@ -346,7 +372,14 @@ when it has one and `connecta:auth:<provider kind>` otherwise. Keep subject ids
346
372
  distinct within that namespace. An explicit principal is the fallback subject
347
373
  when neither id is supplied, and open deployments and auth providers that supply
348
374
  no identity share one partition. Each new stash also binds the principal,
349
- endpoint/pool, request origin, connector, tool, and classification. A page must
375
+ endpoint/pool, request origin, connector, tool, and classification. Program-return
376
+ stashes bind every contributing call, resource or skill read, and catalog lookup,
377
+ including dependencies inherited from pages read during the run. Downstream skill and
378
+ resource content requires a whole-connector grant on every page. Local guides and catalog
379
+ metadata require the contributing connector or tool to remain visible, including
380
+ caught catalog-load errors, unknown-tool failures, and recovery messages that
381
+ list visible connectors or local guides. Returns with no downstream calls still
382
+ require matching identity, origin and endpoint, current auth, and pool membership. A page must
350
383
  match those bindings and pass current auth, connector/tool grants, pool membership,
351
384
  and trust checks. Old entries lacking bindings fail closed. A random UUID is a
352
385
  handle, never an access grant.
@@ -361,6 +394,14 @@ including pre-upgrade single-key envelopes and raw-text entries. Offsets and `to
361
394
  original UTF-8 text, not the envelope. A supplied offset inside a character
362
395
  moves back to its start; page ends also align to character boundaries, and a
363
396
  page smaller than one character widens just enough to make progress.
397
+ Decoding preserves every payload code point, including U+FEFF at the start of
398
+ the result or any page, so joining pages reproduces the original UTF-8 text.
399
+ Oversized raw text containing unpaired UTF-16 surrogates cannot use this UTF-8
400
+ page/offset contract. It returns a successful truncation notice explaining that
401
+ the text cannot be paged and suggesting a JSON value request for recovery,
402
+ without a stash id, paging action, or altered preview.
403
+ Completed writes remain successful and must not be repeated. JSON values still
404
+ round-trip such strings through their escaped JSON representation.
364
405
 
365
406
  A per-call `timeoutMs` covers catalog resolution, admission, and connector
366
407
  execution under one deadline. The admission queue's own timeout may expire
@@ -373,7 +414,8 @@ retryable timeout while Connecta prepares its response.
373
414
  A `call_tool` or `call_destructive_tool` result over its cap — the connector's
374
415
  `maxResultBytes`, else `calls.maxResultBytes`, else 24,000 bytes — comes back
375
416
  as one text block. Its first line is the truncation notice, one line of compact
376
- JSON; everything after the first newline is the preview:
417
+ JSON; everything after the first newline is the preview. `structuredContent`
418
+ is absent, so the notice cannot hide the preview in clients that prefer it:
377
419
 
378
420
  ```text
379
421
  {"truncated":true,"resultId":"…","totalBytes":161420,"hint":"This write already ran: do not call it again to see its result. Bytes 0-24000 of 161420 follow; page the rest with connecta.result using nextAction.","nextOffset":24000,"nextAction":{"tool":"execute_code","arguments":{"code":"async () => await connecta.result(\"…\", { offset: 24000 })"}}}
@@ -437,13 +479,19 @@ When paging is available, `nextAction` is the page handle for either kind of cal
437
479
  the one next step connecta can spell out exactly, while a reduction is a
438
480
  program the agent has to write. `resultId` stays beside the exact `nextAction`,
439
481
  so the handle is actionable without copying an identifier out of prose.
440
- Program results and oversized discovery responses carry no such route: paging a
441
- program's return value is a refused shape, because a program can shrink
442
- anything before it returns.
482
+ Oversized program returns carry the same route, with a bounded preview after
483
+ its notice fields. They stash the compact JSON return and page from offset 0;
484
+ see [program returns](./code-mode.md#results-and-projection). A run that completed
485
+ any write gets the same no-repeat warning. Oversized discovery responses carry
486
+ no paging route.
443
487
 
444
488
  ### Paging with connecta.result
445
489
 
446
- Paging is a guest operation, never another top-level tool. A direct call's
490
+ Paging is a guest operation, never another top-level tool. Program calls also
491
+ return handles for oversized values; see [program result limits](./code-mode.md#cancellation-and-limits).
492
+ `connecta.result(handle, { page: 0 })` accepts those handles alongside direct
493
+ result IDs. Numbered pages are zero-based and mutually exclusive with offset.
494
+ Keep maxBytes fixed while incrementing pages. A direct call's
447
495
  notice carries a `resultId`, `nextOffset`, and an executable `execute_code`
448
496
  recovery action. Read and reduce the result in one program:
449
497
 
@@ -461,7 +509,7 @@ async () => {
461
509
 
462
510
  A page is `{ resultId, offset, bytes, totalBytes, hasMore, nextOffset?,
463
511
  format: "text", text }`. The page size is clamped to the original call's inline
464
- cap, with UTF-8 alignment and forward progress. Reassemble inside the sandbox;
512
+ cap and a 32 KiB program bridge ceiling, with UTF-8 alignment and forward progress. Reassemble inside the sandbox;
465
513
  returning pages unchanged can hit the program result cap. The admitted subject
466
514
  and principal, endpoint/pool, request origin, connector, and tool bindings must
467
515
  match. The host rechecks current access and trust before returning each page; a
@@ -472,7 +520,7 @@ usable bounded preview. It says paging is unavailable and the write already
472
520
  ran; it carries no result handle or recovery action and stores no unreachable
473
521
  stash. Write paging remains available on trusted endpoints.
474
522
 
475
- A refused stash write cannot undo a downstream success. Both call tools return
523
+ A refused stash write cannot undo a downstream success. Direct calls and program returns produce
476
524
  a paging-unavailable notice, without a handle or recovery action. A write's
477
525
  notice still says it already ran. Activity records success; operator logs receive
478
526
  a fixed warning without storage error prose. Never repeat a write to recover output.
@@ -709,6 +757,8 @@ An OAuth call failure uses this envelope:
709
757
  "connector": "service",
710
758
  "operation": "service.read",
711
759
  "recovery": "oauth",
760
+ "authorizationUrl": "https://connecta.example/connect/service?h=...",
761
+ "instructions": "Copy authorizationUrl exactly as returned and sign in as the initiating user.",
712
762
  "nextAction": {
713
763
  "tool": "authorize_connector",
714
764
  "arguments": { "connector": "service" },
@@ -718,9 +768,13 @@ An OAuth call failure uses this envelope:
718
768
  }
719
769
  ```
720
770
 
721
- `recovery` is `oauth`, `operator_config`, or `unavailable`. Call
722
- `authorize_connector` only after this error. It returns the class-specific
723
- handoff:
771
+ `recovery` is `oauth`, `operator_config`, or `unavailable`. On hosts without URL
772
+ elicitation, auth errors carry the same handoff fields that `authorize_connector`
773
+ returns for the admitted user, connector, and pool. Direct calls include them in
774
+ `error`; program `connecta.call` failures include them in `error.data` and
775
+ `error.details`. Search `catalogErrors` and scoped `queryAnalysis.catalogError`,
776
+ and describe `errorDetails`, carry them too. When an auth error includes a
777
+ handoff, give it to the user or operator. The class-specific handoff is:
724
778
 
725
779
  - `oauth`: an `authorizationUrl` to connecta's `/connect/<connector>?h=...` route
726
780
  and instructions to sign in as the initiating user;
@@ -733,6 +787,13 @@ The class follows what the connector declares, not how it was authored: a
733
787
  and no OAuth flow, so it uses `operator_config` when both vault and UI are
734
788
  configured. Without either it returns `unavailable`, never a dead UI link.
735
789
 
790
+ Errors mint at most one handoff per connector per direct call or program run,
791
+ including concurrent and repeated call or catalog errors. The request reuses the
792
+ same signed URL; a later request can issue a fresh one. Issuing a link is
793
+ stateless and starts no consent. Errors never request `force`; use an explicit
794
+ `authorize_connector` call for a restart or a fresh expired link. URL-elicitation
795
+ hosts keep the existing prompt and retry flow.
796
+
736
797
  The tool accepts no secret and starts no downstream consent. `force` applies
737
798
  only to OAuth and requests a restart when the verified browser opens the signed
738
799
  link. That visit may discard the stored grant before restarting consent. Static credential values are written
@@ -740,9 +801,12 @@ only through the same-origin interactive-user credential route, and only for a
740
801
  connector visible to that user with the relevant shared or personal management
741
802
  permission; Issuing an OAuth link, `force` included, requires that permission too. The
742
803
  `/connect` visit and callback both verify the same initiating user under Clerk or
743
- Cloudflare Access, including for shared connectors. The URL expires after fifteen
744
- minutes and carries no browser authentication. A signing credential vault is
745
- required; deployments without either interactive provider return `unavailable`
804
+ Cloudflare Access, including for shared connectors.
805
+
806
+ Copy `authorizationUrl` exactly as returned. Do not decode or re-encode its opaque,
807
+ unpadded base64url handoff token. If the link is invalid or expired, request a fresh link with
808
+ `authorize_connector`. The URL expires after fifteen minutes and carries no browser authentication.
809
+ A signing credential vault is required; deployments without either interactive provider return `unavailable`
746
810
  with a clear configuration message. Status reads stay passive and never expose
747
811
  a downstream authorization-server URL. These routes work without the UI. After OAuth
748
812
  consent or a human update, retry the original operation; a static update is read
@@ -786,13 +850,36 @@ what happens over budget differs by field.
786
850
  | unknown `connecta.result(id)`, `authorize_connector.connector`, `skills.name` | clamped the same way |
787
851
  | `search_tools.connector` | rejected with `invalid_args` before catalog lookup |
788
852
 
789
- Arguments go all or nothing because the agent already holds what it sent, and
790
- half of it would describe a call nobody made. The address gets the opposite rule
853
+ Arguments are schema-filtered before budgeting. `writeOnly: true` values are
854
+ omitted at any depth, including array items and prefixes, local `$ref`/`$defs`,
855
+ and `allOf`. `oneOf`/`anyOf` alternatives must agree on sensitivity. Unresolved
856
+ or remote references, sensitive `patternProperties`/`additionalProperties`
857
+ schemas, unsupported sensitivity rules, and traversal limits omit the entire
858
+ echo. A private array element omits its containing array to keep indices intact.
859
+ `argsRedacted: true` marks partial or withheld echoes. Use them only to identify
860
+ the target for reconciliation; any new call requires the original arguments.
861
+ The filtered snapshot is never clipped: over-budget arguments are dropped whole.
862
+ The address gets the opposite rule
791
863
  because it is the thing being corrected: a clipped one still identifies the
792
864
  mistake, and a short one — every real one — comes back exact and untagged. A
793
865
  scope is rejected outright because a clipped one could select a different
794
866
  connector. A failed result-storage read returns typed `unavailable` without
795
867
  exposing backend error text.
868
+ Submitted `writeOnly` values also enter the call's sent-secret output redaction
869
+ set, including property names inside private object subtrees. Downstream
870
+ result and error text quoting them, in raw or credential-supported encoded
871
+ forms, is redacted before direct-call output, program returns, logs, and emits,
872
+ or result stash persistence; paging in a later request reads already-redacted
873
+ bytes. A program's `connecta.read` resources, connector usage guides, and
874
+ downstream `connecta.skill` files take the same rules before the program sees
875
+ them. JSON stays valid: a matching number or boolean becomes `"[redacted]"`.
876
+ Empty private strings are exempt. A private value shorter than eight characters
877
+ redacts only structured fields that equal it, so identifiers and approval state
878
+ remain, and withholds prose that contains it, including error messages, while
879
+ typed classification, retry facts, and write outcome remain.
880
+ Work limits withhold the affected text without failing the call. See
881
+ [output redaction](./auth.md#what-a-servers-errors-may-say)
882
+ for the shared encoding, budget, and argument-filter contract.
796
883
 
797
884
  Activity records each of these refusals with the coarse `friction` class derived
798
885
  from its typed error code — `tool_not_found`, `schema_retry`,
@@ -837,10 +924,12 @@ scoped registry, including an empty list when none are accessible. They never
837
924
  list connectors from another pool or identity's grants.
838
925
 
839
926
  A write that times out after dispatch returns `write_outcome_unknown` with
840
- `retryable: false` and an agent-only `uncertainCall: { address, args }`. No write
841
- is automatically replayed. Arguments over the 512-byte echo budget are omitted
842
- whole with `argsOmitted: true`; use the exact arguments already sent when
843
- checking the target. A timeout before dispatch remains `timeout`. Trusted
927
+ `retryable: false` and an agent-only `uncertainCall: { address, args?, argsOmitted?, argsRedacted? }`.
928
+ No write is automatically replayed. `args` may be partial reconciliation context
929
+ with `argsRedacted: true`. A withheld echo or arguments over the 512-byte budget
930
+ carry `argsOmitted: true`; sensitive or unresolved schemas also carry
931
+ `argsRedacted: true`. When `args` is absent or redacted, use the original
932
+ arguments if reconciliation requires another call. A timeout before dispatch remains `timeout`. Trusted
844
933
  program write accounting keeps this detail even if the guest catches the
845
934
  failure or returns early. Multiple uncertain writes use `uncertainCalls`,
846
935
  bounded to ten calls with `uncertainCallsTruncated` when more exist.
@@ -850,3 +939,19 @@ SDK timeout classes, and runtime network codes. Untyped prose is non-retryable:
850
939
  "timeout", "temporarily unavailable", "rate limit", or "503" in a body, tool
851
940
  name, or URL never changes the verdict. JSON-RPC `ProtocolError.data` is dropped
852
941
  at the downstream boundary, even when the agent may see the error's message.
942
+
943
+ ## Source and tests
944
+
945
+ Handlers and schemas live in [meta-tools.ts](https://github.com/zackbart/connecta/blob/main/src/meta-tools.ts).
946
+ [Catalog service](https://github.com/zackbart/connecta/blob/main/src/catalog-service.ts)
947
+ and [invocation](https://github.com/zackbart/connecta/blob/main/src/invocation.ts)
948
+ serve both direct tools and programs. [Skills](https://github.com/zackbart/connecta/blob/main/src/skills.ts)
949
+ owns the supplied usage instructions and connector guides.
950
+
951
+ [Schema tests](https://github.com/zackbart/connecta/blob/main/test/meta-tool-schemas.test.ts),
952
+ [search tests](https://github.com/zackbart/connecta/blob/main/test/meta-tools-search.test.ts),
953
+ [call tests](https://github.com/zackbart/connecta/blob/main/test/meta-tools-call.test.ts),
954
+ [skills tests](https://github.com/zackbart/connecta/blob/main/test/skills-extension.test.ts),
955
+ and [surface tests](https://github.com/zackbart/connecta/blob/main/test/code-first-surface.test.ts)
956
+ check the published contract. Guest parity and clause evidence are in
957
+ [code-mode verification](./code-mode.md#verification).