@zackbart/connecta 0.10.6 → 0.11.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 (80) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +5 -4
  4. package/bin/connecta.mjs +0 -7
  5. package/dist/activity.d.ts.map +1 -1
  6. package/dist/activity.js.map +1 -1
  7. package/dist/catalog-service.d.ts +2 -17
  8. package/dist/catalog-service.d.ts.map +1 -1
  9. package/dist/catalog-service.js +4 -6
  10. package/dist/catalog-service.js.map +1 -1
  11. package/dist/connectors/api.d.ts +2 -2
  12. package/dist/connectors/remote-mcp.d.ts +1 -1
  13. package/dist/errors.d.ts +1 -3
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +1 -1
  16. package/dist/errors.js.map +1 -1
  17. package/dist/execute.d.ts +1 -3
  18. package/dist/execute.d.ts.map +1 -1
  19. package/dist/execute.js +7 -28
  20. package/dist/execute.js.map +1 -1
  21. package/dist/index.d.ts +15 -31
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +23 -37
  24. package/dist/index.js.map +1 -1
  25. package/dist/invocation.js +2 -2
  26. package/dist/invocation.js.map +1 -1
  27. package/dist/meta-tools.d.ts +19 -58
  28. package/dist/meta-tools.d.ts.map +1 -1
  29. package/dist/meta-tools.js +34 -431
  30. package/dist/meta-tools.js.map +1 -1
  31. package/dist/registry.d.ts +1 -10
  32. package/dist/registry.d.ts.map +1 -1
  33. package/dist/registry.js +3 -15
  34. package/dist/registry.js.map +1 -1
  35. package/dist/routes/mcp.d.ts.map +1 -1
  36. package/dist/routes/mcp.js +19 -30
  37. package/dist/routes/mcp.js.map +1 -1
  38. package/dist/routes/shared.d.ts +5 -11
  39. package/dist/routes/shared.d.ts.map +1 -1
  40. package/dist/routes/shared.js.map +1 -1
  41. package/dist/server.js +5 -4
  42. package/dist/server.js.map +1 -1
  43. package/dist/skills.d.ts +8 -18
  44. package/dist/skills.d.ts.map +1 -1
  45. package/dist/skills.js +13 -60
  46. package/dist/skills.js.map +1 -1
  47. package/dist/types.d.ts +6 -20
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. package/documentation/code-first-exploration.md +16 -16
  52. package/documentation/code-mode.md +21 -35
  53. package/documentation/connectors.md +1 -1
  54. package/documentation/meta-tools.md +30 -43
  55. package/documentation/rich-output-design.md +4 -4
  56. package/examples/node/README.md +1 -2
  57. package/examples/node/src/index.ts +1 -3
  58. package/examples/worker/README.md +8 -13
  59. package/examples/worker/src/index.ts +6 -14
  60. package/examples/worker/wrangler.jsonc +3 -6
  61. package/package.json +1 -1
  62. package/src/activity.ts +5 -0
  63. package/src/catalog-service.ts +6 -26
  64. package/src/connectors/api.ts +2 -2
  65. package/src/connectors/remote-mcp.ts +1 -1
  66. package/src/errors.ts +2 -2
  67. package/src/execute.ts +7 -36
  68. package/src/index.ts +40 -69
  69. package/src/invocation.ts +2 -2
  70. package/src/meta-tools.ts +38 -565
  71. package/src/registry.ts +2 -33
  72. package/src/routes/mcp.ts +19 -30
  73. package/src/routes/shared.ts +4 -11
  74. package/src/server.ts +7 -7
  75. package/src/skills.ts +11 -74
  76. package/src/types.ts +6 -21
  77. package/src/version.ts +1 -1
  78. package/templates/node/README.md +2 -1
  79. package/templates/node/package.json +1 -1
  80. package/templates/node/src/index.ts +1 -1
@@ -33,7 +33,7 @@ Connecta deliberately sits between protocol generations
33
33
  [#206](https://github.com/zackbart/connecta/issues/206).
34
34
  - **Multi-round-trip results:** a downstream `input_required` result becomes a
35
35
  non-retryable `input_required_unsupported` failure. `call_tool`, the
36
- `execute_code` host bridge, and classic's `batch_call` all preserve the
36
+ `execute_code` host bridge and internal batch path both preserve the
37
37
  structured code. Relaying the
38
38
  opaque `requestState` is architecturally possible but gated until real hosts
39
39
  and downstreams adopt it.
@@ -4,26 +4,20 @@ 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 safety
5
5
  annotations, and `get_result` pages bounded results.
6
6
 
7
- ## Which surface a deployment serves
7
+ ## The deployment surface
8
8
 
9
- The `executor` decides it, and there is nothing else to configure
10
- ([#224](https://github.com/zackbart/connecta/issues/224)):
11
-
12
- | | `tools/list` | Discovery breadth and batching |
13
- | --- | --- | --- |
14
- | **executor configured** | seven: `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`, `authorize_connector`, `get_result`, `skills` | `connecta.search`, `connecta.describe`, `connecta.batch` inside a program |
15
- | **no executor** | nine: the above minus `execute_code`, plus `list_connectors`, `describe_tools`, `batch_call` | those three top-level tools |
9
+ Every deployment requires an executor and `tools/list` is exactly seven:
10
+ `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`,
11
+ `authorize_connector`, `get_result`, and `skills`. Discovery breadth and
12
+ batching live in `connecta.search`, `connecta.describe`, and `connecta.batch`
13
+ inside a program ([#273](https://github.com/zackbart/connecta/issues/273)).
16
14
 
17
15
  Code-first is what a model sees. Four overlapping ways to reach one connector
18
16
  became two: `search_tools` then `call_tool` for a single cold read — measurably
19
17
  cheaper direct than through a program — and `execute_code` for everything wider.
20
- The fold is worth 19.6% of the serialized tool definitions measured against the
21
- ten-tool shape an executor-backed deployment used to serve 10,675B to 8,587B —
22
- and, more durably, one fewer routing decision a model makes before doing any
23
- work. Note which baseline that is: the executor-free nine serialize to 7,207B,
24
- so the seven-tool surface is *larger* than the row below it in that table. It
25
- buys the program with those bytes. The [guest API contract](./code-mode.md) is
26
- what a program is promised.
18
+ The consolidation removed overlapping routing choices while preserving the
19
+ cheaper direct path for one cold call. The [guest API contract](./code-mode.md)
20
+ is what a program is promised.
27
21
 
28
22
  `execute_code` accepts optional `diagnostics: true` when a caller is measuring
29
23
  a workflow. It adds only compact request-local timing and serialized-size
@@ -31,20 +25,11 @@ aggregates; normal calls carry no diagnostics block or response-context cost.
31
25
  The measurements never contain program source, arguments, values, addresses,
32
26
  credentials, logs, or raw error text.
33
27
 
34
- Classic is the compatibility surface: what an executor-free deployment
35
- necessarily serves, since the program surface the fold depends on is not there.
36
- It is supported and tested, not an equal citizen in the docs. `surface:
37
- "classic"` beside an executor is the only override; it produces the ten-tool
38
- shape the [eval gate](../eval/code-first-gate/README.md)'s *incremental* arm
39
- measures. That gate's control arm is executor-free classic and needs no
40
- override.
41
-
42
28
  Nothing became unreachable. `connecta.describe` takes the same addresses and
43
- formats as `describe_tools`, `connecta.batch` runs the same 1–10 parallel
44
- read-only calls as `batch_call` and returns the same typed outcomes, and an
45
- unfiltered `connecta.search({})` browses every catalog a program can reach
46
- the part of `list_connectors` a model used. Live connector probing was the rest
47
- of it, and that is an operator concern: the operator pages and `/health` own it.
29
+ formats as the internal catalog service, `connecta.batch` runs 1–10 parallel
30
+ read-only calls and returns typed outcomes, and an unfiltered
31
+ `connecta.search({})` browses every catalog a program can reach. Live connector
32
+ probing is an operator concern: the operator pages and `/health` own it.
48
33
 
49
34
  ## Discovery context
50
35
 
@@ -61,8 +46,8 @@ shape. Bounded plain-object schemas also expose `inputKeys`,
61
46
  list rather than repeating a large partial inventory. Matches carry declared
62
47
  behavior annotations. When
63
48
  that shape is sufficient, call the returned address directly. Reserve schema
64
- expansion `connecta.describe` in a program, `describe_tools` on the classic
65
- surface — for a search without schemas, an ambiguous compact shape, or exact
49
+ expansion through `connecta.describe` for a search without schemas, an
50
+ ambiguous compact shape, or exact
66
51
  constraints that require `format: "json"`.
67
52
 
68
53
  Compact search is deliberately a routing view, not a second copy of connector
@@ -89,12 +74,11 @@ mode; they are not a duplicated Connecta object result. Newly stashed JSON and
89
74
  downstream content envelopes use compact serialization, so `get_result` byte
90
75
  offsets and totals refer to that exact compact text.
91
76
 
92
- A `call_tool` or `batch_call` truncation notice carries both the historical
93
- `resultId` and an exact
94
- `nextAction: { tool: "get_result", arguments: { id, offset: 0 } }`. The handle
95
- is therefore directly actionable without copying an identifier out of prose;
96
- re-calling with `fields` remains the smaller alternative when projection is
97
- possible. Program results and oversized discovery responses carry no such
77
+ A `call_tool` truncation notice carries both the historical `resultId` and an
78
+ exact `nextAction: { tool: "get_result", arguments: { id, offset: 0 } }`. The
79
+ handle is therefore
80
+ directly actionable without copying an identifier out of prose; re-calling with
81
+ `fields` remains the smaller alternative when projection is possible. Program results and oversized discovery responses carry no such
98
82
  route — paging a program's return value is a refused shape, because a program
99
83
  can shrink anything before it returns.
100
84
 
@@ -186,7 +170,7 @@ read from the vault on the next call and needs no redeploy.
186
170
  Predictable local refusals carry structured recovery on both result modes.
187
171
  An unknown connector suggests an unscoped discovery query derived from the
188
172
  attempted tool name; an unknown tool scopes the same query to the connector that
189
- answered. The suggested route follows the caller's own surface: `tool:
173
+ answered. The suggested route follows the route the caller took: `tool:
190
174
  "search_tools"` for a top-level call, `function: "connecta.search"` with the same
191
175
  arguments when the miss happened inside `execute_code`, which has no way to call
192
176
  a tool. A read path that reaches an unannotated, write-capable, or destructive
@@ -197,9 +181,10 @@ That route echoes the caller's own arguments back only while they fit a
197
181
  512-byte budget, and then whole — never clipped. An error envelope is not
198
182
  size-guarded the way a result is, so an unbounded echo would let a large
199
183
  argument object produce a refusal many times the deployment's result cap, on
200
- both `call_tool` and the `batch_call` envelope. Over budget, `args` is absent
201
- and the `purpose` says to re-send what was just sent: the agent already holds
202
- its own arguments, and half of them would describe a call nobody made.
184
+ both `call_tool` and calls a program routes through `connecta.call` or
185
+ `connecta.batch`. Over budget, `args` is absent and the `purpose` says to
186
+ re-send what was just sent: the agent already holds its own arguments, and half
187
+ of them would describe a call nobody made.
203
188
 
204
189
  The address gets the same budget and the opposite rule: 512 bytes, clamped
205
190
  with a trailing `…` rather than dropped. It is caller-authored too — an
@@ -221,9 +206,11 @@ reason rather than as a reason to refuse the call.
221
206
  Activity carries an optional coarse `friction` class: `tool_not_found`,
222
207
  `schema_retry`, `destructive_reroute`, `auth_required`, or `result_too_large`.
223
208
  It is derived from the typed error code, except on the one call that has no
224
- error code to derive from: a `call_tool` result or a `batch_call` child's
225
- too large to return inline is friction for the agent while remaining
226
- `outcome: "success"`. That is the only source of `result_too_large` friction.
209
+ error code to derive from: a result too large to return inline is friction for
210
+ the agent while remaining `outcome: "success"`. That applies to a `call_tool`
211
+ result, the only source of `result_too_large` friction. (Activity stored by
212
+ older releases may still carry the retired `batch_call` source; nothing writes
213
+ it today.)
227
214
  An oversized *discovery* response and an oversized program return are shaped
228
215
  differently and produce none, and an `errorCode` is written only when the call
229
216
  actually failed. The category adds no arguments, results, search text,
@@ -20,13 +20,13 @@ program cannot "return less" of a screenshot — so any rich block a downstream
20
20
  tool produces dies at the exit guard, converted to a truncation envelope whose
21
21
  preview is the head of a base64 string, which is of use to no one.
22
22
 
23
- The asymmetry is already visible in the classic surface. `call_tool` forwards
23
+ The asymmetry is already visible in the direct-call surface. `call_tool` forwards
24
24
  non-text downstream blocks to the client untouched when they fit the result cap
25
25
  (the fix for issue #43 made the guard measure every block, then pass them
26
26
  through in original order). And on the intake side, a host call **inside** a
27
27
  program is not size-capped at all: `unwrapMcpResult` hands mixed content
28
28
  through as a raw object, base64 and all. So today a downstream image survives
29
- the trip *into* the sandbox and survives the classic surface entirely — the one
29
+ the trip *into* the sandbox and survives the explicit tool boundary — the one
30
30
  place it cannot survive is the exit of the surface connecta calls primary.
31
31
 
32
32
  ## The shape: `connecta.emit(block)`
@@ -103,8 +103,8 @@ uncapped) and emits it. The host attaches no attribution.
103
103
  **M7.** `emit` does not spend the host-call budget (`L4`). Its bounds are
104
104
  `M5`'s and only `M5`'s.
105
105
 
106
- **M8.** `ExecuteResult` is unchanged. No executor — QuickJS, Dynamic Worker,
107
- or third-party needs modification, and the parity suite runs the same
106
+ **M8.** `ExecuteResult` is unchanged. Neither QuickJS, Dynamic Worker, nor a
107
+ third-party executor needs modification, and the parity suite runs the same
108
108
  emitting program through both vitest projects and asserts identical delivered
109
109
  content.
110
110
 
@@ -32,8 +32,7 @@ value outside this local example.
32
32
  Keep this deployment small:
33
33
 
34
34
  - Edit `src/index.ts` to change connectors, auth, storage, and the public URL.
35
- - Keep `executor: quickJsExecutor()` unless you deliberately want the classic
36
- compatibility surface.
35
+ - Keep the required `executor: quickJsExecutor()` configuration.
37
36
  - Keep secrets in environment variables or an external secret store. Never put
38
37
  tokens in `src/index.ts`, connector guides, or committed JSON.
39
38
  - Add application code only when implementing a deliberate `api()` connector.
@@ -30,9 +30,7 @@ const connecta = createConnecta({
30
30
  auth: bearerToken(token, { subjectId: "operator" }),
31
31
  // Downstream OAuth callbacks use this deployment origin.
32
32
  publicUrl: `http://localhost:${port}`,
33
- // Code mode: QuickJS runs model-written JS in a bounded disposable child.
34
- // This line is also what selects the seven-tool code-first surface; remove it
35
- // to serve the nine classic meta-tools instead.
33
+ // Required: QuickJS runs model-written JS in a bounded disposable child.
36
34
  executor: quickJsExecutor(),
37
35
  connectors: [
38
36
  api("time", {
@@ -2,9 +2,8 @@
2
2
 
3
3
  A deployable Worker that aggregates a downstream remote MCP and an in-code HTTP
4
4
  API connector, guarded by Clerk OAuth *and* a static bearer token, with state in
5
- a KV namespace. One Wrangler binding turns on paid code mode, which is also what
6
- selects the seven-tool code-first surface; the checked-in configuration deploys
7
- without it, serving the nine classic meta-tools on the Workers Free plan.
5
+ a KV namespace. Its required Worker Loader binding backs the seven-tool surface
6
+ and requires the Workers Paid plan.
8
7
 
9
8
  This is also the **starting template for a deployment**: a real deployment
10
9
  should be its own repository that pins an exact `@zackbart/connecta` version and
@@ -56,21 +55,17 @@ Connections. Credentials is at `/credentials`, named MCP access tokens are at
56
55
 
57
56
  ## Code mode
58
57
 
59
- Code mode is a deploy-time opt-in because its Dynamic Worker sandbox requires
60
- the [Workers Paid plan](https://developers.cloudflare.com/dynamic-workers/pricing/).
61
- The Worker Loader binding is the switch; no TypeScript change or separate
62
- environment variable is needed. Add this block to `wrangler.jsonc` (and a comma
63
- after the preceding property):
58
+ The Dynamic Worker sandbox requires the
59
+ [Workers Paid plan](https://developers.cloudflare.com/dynamic-workers/pricing/).
60
+ The required Worker Loader binding is checked into `wrangler.jsonc`:
64
61
 
65
62
  ```jsonc
66
63
  "worker_loaders": [{ "binding": "LOADER" }]
67
64
  ```
68
65
 
69
- `src/index.ts` detects `env.LOADER`, constructs `DynamicWorkerExecutor`, and
70
- serves the seven-tool code-first surface. Leave the binding absent as it is in
71
- the checked-in config — to deploy the same source on the Workers Free plan with
72
- the nine classic meta-tools. A deployment copied into its own repository must
73
- also install the executor package before enabling the binding:
66
+ `src/index.ts` constructs `DynamicWorkerExecutor` from `env.LOADER` and serves
67
+ the seven-tool surface. A deployment copied into its own repository must also
68
+ install the executor package:
74
69
 
75
70
  ```sh
76
71
  npm install @cloudflare/codemode
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * One MCP endpoint aggregating a downstream remote MCP and an HTTP API, guarded
5
5
  * by Clerk OAuth *and* a static bearer token, with OAuth/cache state in a KV
6
- * namespace. Add the optional Worker Loader binding in wrangler.jsonc for the
7
- * seven-tool code-first surface; without it this serves the nine classic tools.
6
+ * namespace. The required Worker Loader binding in wrangler.jsonc backs the
7
+ * seven-tool surface.
8
8
  *
9
9
  * Setup (this example has no package.json of its own — it self-references the
10
10
  * installed `@zackbart/connecta` package):
@@ -19,8 +19,7 @@
19
19
  * and CLERK_PUBLISHABLE_KEY + PUBLIC_URL as plain vars in wrangler.jsonc.
20
20
  * 4. Enable Dynamic Client Registration in the Clerk dashboard
21
21
  * (OAuth Applications -> DCR toggle) so Claude/Cursor can self-register.
22
- * 5. Optional paid code mode: add the documented `worker_loaders` binding to
23
- * wrangler.jsonc. Binding presence enables execute_code automatically.
22
+ * 5. Use the Workers Paid plan required by the `worker_loaders` binding.
24
23
  * 6. `wrangler deploy` from this folder (examples/worker), where wrangler.jsonc
25
24
  * lives. Point your MCP client at `<PUBLIC_URL>/mcp`.
26
25
  */
@@ -46,23 +45,16 @@ interface Env {
46
45
  PUBLIC_URL: string;
47
46
  /**
48
47
  * Worker Loader binding (wrangler.jsonc `worker_loaders`) powering
49
- * execute_code and, with it, the code-first surface. Dynamic Workers require
50
- * the Workers Paid plan; leave the binding absent for the nine classic
51
- * meta-tools on either plan.
48
+ * execute_code. Dynamic Workers require the Workers Paid plan.
52
49
  */
53
- LOADER?: WorkerLoader;
50
+ LOADER: WorkerLoader;
54
51
  }
55
52
 
56
53
  function build(env: Env) {
57
54
  return createConnecta({
58
55
  publicUrl: env.PUBLIC_URL,
59
56
  storage: cloudflareKvStorage(env.CONNECTA_KV),
60
- // Binding-as-switch: adding worker_loaders in wrangler.jsonc enables code
61
- // mode and the seven-tool code-first surface with it; leaving it absent
62
- // keeps this deployment free-tier compatible on the classic surface.
63
- ...(env.LOADER
64
- ? { executor: new DynamicWorkerExecutor({ loader: env.LOADER }) }
65
- : {}),
57
+ executor: new DynamicWorkerExecutor({ loader: env.LOADER }),
66
58
  auth: [
67
59
  // Multiple credentials may identify callers in one deployment. Every
68
60
  // admitted caller reaches this deployment's deliberate connector set.
@@ -16,11 +16,8 @@
16
16
  // Create with `wrangler kv namespace create CONNECTA_KV` and paste the id.
17
17
  "kv_namespaces": [
18
18
  { "binding": "CONNECTA_KV", "id": "replace-with-kv-namespace-id" }
19
- ]
19
+ ],
20
20
 
21
- // Optional paid code mode: add a comma above, then uncomment this binding.
22
- // Its presence is the entire switch — src/index.ts detects env.LOADER and
23
- // serves the seven-tool code-first surface. Leave it absent for a free-tier
24
- // deployment with the nine classic meta-tools.
25
- // "worker_loaders": [{ "binding": "LOADER" }]
21
+ // Required by execute_code; Dynamic Workers use the Workers Paid plan.
22
+ "worker_loaders": [{ "binding": "LOADER" }]
26
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.10.6",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "One MCP to rule them all — a single MCP endpoint aggregating many downstream connectors behind a code-first surface of seven meta-tools.",
package/src/activity.ts CHANGED
@@ -23,6 +23,11 @@ const MAX_ACTIVITY_ADDRESS_BYTES = MAX_ACTIVITY_NAME_BYTES * 2 + 1;
23
23
  export type ActivityCallSource =
24
24
  | "call_tool"
25
25
  | "call_destructive_tool"
26
+ // Read-only history. Nothing emits `batch_call` since issue #273 removed the
27
+ // tool, but activity storage is append-only: rows written by older
28
+ // deployments are still read back — the D1 example maps a stored row straight
29
+ // into this type — and an operator's timeline should not have to lie about
30
+ // where a call came from. Never widen this member back into a live source.
26
31
  | "batch_call"
27
32
  | "execute_code";
28
33
 
@@ -48,23 +48,10 @@ const MAX_QUERY_ANALYSIS_TERM_LENGTH = 64;
48
48
 
49
49
  const encoder = new TextEncoder();
50
50
 
51
- /**
52
- * The tool a describe-path error should name when it tells a caller to retry.
53
- * This is the route the *caller* took, not the deployment's advertised surface:
54
- * a classic deployment with an executor serves `describe_tools` at top level
55
- * while every in-program describe still arrives through `connecta.describe`, so
56
- * one CatalogService cannot infer the answer from `surface` alone. Callers pass
57
- * the route they own.
58
- */
59
- export type DescribeRoute = "describe_tools" | "connecta.describe";
60
-
61
51
  /**
62
52
  * The discovery route a routing failure should send a caller back through. Same
63
- * rule as {@link DescribeRoute} the route the *caller* took, not the
64
- * deployment's advertised surface with one difference worth keeping the two
65
- * options separate for: `search_tools` exists on both advertised surfaces, so a
66
- * top-level handler never has to derive this one, while an in-program caller
67
- * still has to be told about `connecta.search` because it cannot call a tool.
53
+ * catalog logic serves both the top-level `search_tools` path and the
54
+ * in-program `connecta.search` path, so callers pass the route they own.
68
55
  */
69
56
  export type SearchRoute = "search_tools" | "connecta.search";
70
57
 
@@ -96,10 +83,7 @@ function discoverySearchLimit(value: unknown): number {
96
83
  }
97
84
 
98
85
  /** Normalize the single-address convenience form, then validate the bounded list. */
99
- function discoveryAddresses(
100
- args: CatalogDescribeArgs,
101
- describeRoute: DescribeRoute,
102
- ): unknown[] {
86
+ function discoveryAddresses(args: CatalogDescribeArgs): unknown[] {
103
87
  if (args.address !== undefined && args.addresses !== undefined) {
104
88
  throw new DiscoveryPolicyError(
105
89
  "invalid_args",
@@ -121,7 +105,7 @@ function discoveryAddresses(
121
105
  if (value.length > MAX_DESCRIBE_ADDRESSES) {
122
106
  throw new DiscoveryPolicyError(
123
107
  "invalid_args",
124
- `addresses must contain at most ${MAX_DESCRIBE_ADDRESSES} entries. Split a larger list across ${describeRoute} calls.`,
108
+ `addresses must contain at most ${MAX_DESCRIBE_ADDRESSES} entries. Split a larger list across connecta.describe calls.`,
125
109
  );
126
110
  }
127
111
  return value;
@@ -327,7 +311,6 @@ export class CatalogService {
327
311
  readonly requestScope: object;
328
312
  private readonly probeTimeoutMs: number;
329
313
  private readonly concurrency: number;
330
- private readonly describeRoute: DescribeRoute;
331
314
  private readonly searchRoute: SearchRoute;
332
315
  private readonly loaded = new Map<string, ToolDef[]>();
333
316
  private readonly loading = new Map<string, Promise<ToolDef[]>>();
@@ -339,8 +322,6 @@ export class CatalogService {
339
322
  requestScope?: object;
340
323
  probeTimeoutMs?: number;
341
324
  concurrency?: number;
342
- /** The tool describe-path errors name. Default `describe_tools`. */
343
- describeRoute?: DescribeRoute;
344
325
  /** The discovery route recovery records name. Default `search_tools`. */
345
326
  searchRoute?: SearchRoute;
346
327
  } = {},
@@ -349,7 +330,6 @@ export class CatalogService {
349
330
  this.probeTimeoutMs =
350
331
  normalizeTimeoutMs(options.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
351
332
  this.concurrency = resolveDiscoveryConcurrency(options.concurrency);
352
- this.describeRoute = options.describeRoute ?? "describe_tools";
353
333
  this.searchRoute = options.searchRoute ?? "search_tools";
354
334
  }
355
335
 
@@ -817,7 +797,7 @@ export class CatalogService {
817
797
  }
818
798
 
819
799
  async describe(args: CatalogDescribeArgs): Promise<CatalogDescription[]> {
820
- const addresses = discoveryAddresses(args, this.describeRoute);
800
+ const addresses = discoveryAddresses(args);
821
801
  const format = args.format ?? "compact";
822
802
  const resolved = addresses.map((rawAddress) => {
823
803
  const address = String(rawAddress);
@@ -834,7 +814,7 @@ export class CatalogService {
834
814
  connectorIds,
835
815
  this.concurrency,
836
816
  (id) =>
837
- this.loadForDiscovery(id, `${this.describeRoute} probe of "${id}"`),
817
+ this.loadForDiscovery(id, `connecta.describe probe of "${id}"`),
838
818
  );
839
819
  const catalogs = new Map<string, ToolDef[] | Error>();
840
820
  loaded.forEach((result, index) => {
@@ -20,7 +20,7 @@ export interface ApiTool {
20
20
  outputSchema?: JsonSchema;
21
21
  /**
22
22
  * Standard MCP-style behavior hints. Only an explicit readOnlyHint: true
23
- * admits the tool to call_tool, batch_call, and execute_code.
23
+ * admits the tool to call_tool and execute_code.
24
24
  */
25
25
  annotations?: ToolAnnotations;
26
26
  handler: (args: any, ctx: ConnectorContext) => Promise<unknown> | unknown;
@@ -32,7 +32,7 @@ export interface ApiOptions {
32
32
  description?: string;
33
33
  /**
34
34
  * Max inline result size (bytes) for this connector's tools before
35
- * call_tool/batch_call truncate and stash the full text for get_result
35
+ * call_tool truncates and stashes the full text for get_result
36
36
  * paging. Overrides the deployment's `calls.maxResultBytes`; omit to inherit
37
37
  * it. Must be a whole number of bytes >= 1; anything else warns at startup
38
38
  * and is ignored.
@@ -38,7 +38,7 @@ export interface RemoteMcpOptions {
38
38
  description?: string;
39
39
  /**
40
40
  * Max inline result size (bytes) for this connector's tools before
41
- * call_tool/batch_call truncate and stash the full text for get_result
41
+ * call_tool truncates and stashes the full text for get_result
42
42
  * paging. Overrides the deployment's `calls.maxResultBytes`; omit to inherit
43
43
  * it. Must be a whole number of bytes >= 1; anything else warns at startup
44
44
  * and is ignored.
package/src/errors.ts CHANGED
@@ -221,7 +221,7 @@ export class ConnectorCallError extends Error {
221
221
  }
222
222
  }
223
223
 
224
- /** The `error` object surfaced in call_tool/batch_call value-mode results. */
224
+ /** The `error` object surfaced in value-mode call results and connecta.batch entries. */
225
225
  export interface CallErrorDetails {
226
226
  code: string;
227
227
  message: string;
@@ -322,7 +322,7 @@ const RETRYABLE_MESSAGE_RE =
322
322
  const TIMEOUT_MESSAGE_RE = /timed out|timeout/i;
323
323
 
324
324
  /** Message-text fallback used when an error carries no typed classification. */
325
- export function messageLooksRetryable(message: string): boolean {
325
+ function messageLooksRetryable(message: string): boolean {
326
326
  return RETRYABLE_MESSAGE_RE.test(message);
327
327
  }
328
328
 
package/src/execute.ts CHANGED
@@ -24,7 +24,6 @@ import {
24
24
  } from "./invocation.js";
25
25
  import type { RegistryView } from "./registry.js";
26
26
  import type {
27
- ConnectaSurface,
28
27
  Executor,
29
28
  ExecutorProvider,
30
29
  Logger,
@@ -425,12 +424,7 @@ export async function buildSandboxProviders(
425
424
  const requestScope = {};
426
425
  const catalog = new CatalogService(registry, baseUrl, {
427
426
  requestScope,
428
- // Every describe reaching this catalog came from inside a program, on a
429
- // code-first and a classic-with-executor deployment alike, so the retry
430
- // advice names connecta.describe regardless of what this server advertises.
431
- describeRoute: "connecta.describe",
432
- // Same reasoning for the discovery route a routing failure hands back: the
433
- // program that just missed an address cannot call search_tools.
427
+ // A program that just missed an address cannot call search_tools.
434
428
  searchRoute: "connecta.search",
435
429
  ...(limits.discoveryConcurrency !== undefined
436
430
  ? { concurrency: limits.discoveryConcurrency }
@@ -588,8 +582,8 @@ export async function buildSandboxProviders(
588
582
  ),
589
583
  };
590
584
  } catch (err) {
591
- // Same failure shape batch_call reports: the message a program
592
- // can log, plus the typed details it must classify by. A
585
+ // The failure shape connecta.batch reports: the message a
586
+ // program can log, plus the typed details it must classify by. A
593
587
  // thrown host error crosses the sandbox bridge as a bare
594
588
  // message string in every executor, so this is the one place a
595
589
  // program can tell a policy refusal from a transient failure.
@@ -857,7 +851,7 @@ export function createExecuteTool(
857
851
  // Executor bridges necessarily reduce thrown host errors to strings.
858
852
  // Match that terminal string back to the request-local typed failure so
859
853
  // an unhandled tool failure keeps the same structured contract as
860
- // call_tool and batch_call. Failures caught by model code never reach
854
+ // call_tool. Failures caught by model code never reach
861
855
  // outcome.error and therefore remain under that code's control.
862
856
  //
863
857
  // An error the program let through unchanged matches exactly, and an
@@ -973,33 +967,12 @@ function discardedEmitsText(emitted: EmitCollector): string {
973
967
  : "";
974
968
  }
975
969
 
976
- /**
977
- * How the tool opens, and where a program's argument schemas come from. Both
978
- * differ by surface: on the classic surface `execute_code` is the tool of last
979
- * resort and its neighbours (`batch_call`, `describe_tools`) own the simpler
980
- * jobs, while on the code-first surface those tools are gone and the program is
981
- * where all of that work happens. Everything after these two phrases is
982
- * identical, so the shared body below has one source of truth.
983
- */
984
- const EXECUTE_ROUTING = {
985
- classic:
986
- "Use for dependent multi-step calls, loops, joins, branching, or reducing large results in a sandbox. Never use execute_code for search-only discovery or one downstream call: use search_tools, then call_tool when needed. For 2–10 independent calls use batch_call.",
987
- "code-first":
988
- "The primary surface. Use for discovery beyond one lookup, two or more calls, dependent steps, loops, joins, branching, or reducing large results before they reach the model — connecta.search and connecta.describe browse and expand catalogs in the run, and connecta.batch replaces a separate batch tool. The exception is a single call at an address already in hand: search_tools then one call_tool is cheaper than a program.",
989
- } as const;
990
-
991
- const EXECUTE_SCHEMA_SOURCE = {
992
- classic: "describe_tools",
993
- "code-first": "connecta.describe",
994
- } as const;
995
-
996
970
  const executeDescription = (
997
- surface: ConnectaSurface,
998
971
  emitBudgets: { maxBytes: number; maxBlocks: number },
999
- ) => `${EXECUTE_ROUTING[surface]} Only tools explicitly annotated readOnlyHint: true are available. Each run is limited to ${EXECUTE_MAX_HOST_CALLS} host calls; connecta.batch accepts at most ${EXECUTE_MAX_BATCH_CALLS}; each host call has a ${EXECUTE_HOST_CALL_TIMEOUT_MS / 1_000}-second deadline.
972
+ ) => `The primary surface. Use for discovery beyond one lookup, two or more calls, dependent steps, loops, joins, branching, or reducing large results before they reach the model — connecta.search and connecta.describe browse and expand catalogs in the run, and connecta.batch handles independent calls. The exception is a single call at an address already in hand: search_tools then one call_tool is cheaper than a program. Only tools explicitly annotated readOnlyHint: true are available. Each run is limited to ${EXECUTE_MAX_HOST_CALLS} host calls; connecta.batch accepts at most ${EXECUTE_MAX_BATCH_CALLS}; each host call has a ${EXECUTE_HOST_CALL_TIMEOUT_MS / 1_000}-second deadline.
1000
973
 
1001
974
  Write an async arrow function. It runs with NO network, filesystem, timers, or imports — the only capabilities are:
1002
- - One global per connector: every address <connectorId>.<toolName> from search_tools is callable as <connectorId>.<toolName>(args) with a single args object matching the schema from ${EXECUTE_SCHEMA_SOURCE[surface]}. Names are sanitized to JS identifiers: characters outside [A-Za-z0-9_$] become "_" (e.g. my-service.get.thing → my_service.get_thing), leading digits get "_" prefixed, reserved words get "_" appended.
975
+ - One global per connector: every address <connectorId>.<toolName> from search_tools is callable as <connectorId>.<toolName>(args) with a single args object matching the schema from connecta.describe. Names are sanitized to JS identifiers: characters outside [A-Za-z0-9_$] become "_" (e.g. my-service.get.thing → my_service.get_thing), leading digits get "_" prefixed, reserved words get "_" appended.
1003
976
  - connecta.call(address, args) and connecta.batch(calls) — call raw addresses.
1004
977
  - connecta.search(args), connecta.describe({ address: "<connectorId>.<toolName>" }), and connecta.describe({ addresses: [...] }) — load and inspect request-local catalogs on demand. Use safety: "readOnly" to avoid advertising calls this sandbox cannot execute; the filter changes results, not authority. Matches carrying schemas also list inputKeys, requiredInputKeys, and outputKeys — the same names the schema shows, ready to check against before building args. They are absent when a schema is not a plain object shape, so read the schema itself rather than assuming a missing list means no fields.
1005
978
  - connecta.emit(block) — deliver rich MCP content alongside the JSON return: exactly { type: "text", text } or { type: "image" | "audio", data (base64), mimeType }, no other fields. Blocks are appended to the result on success only, spend no host calls, and are budgeted per run (${emitBudgets.maxBlocks} blocks, ${emitBudgets.maxBytes} serialized bytes); an over-budget or invalid emit throws catchably and accepts nothing.
@@ -1032,8 +1005,6 @@ export function registerExecuteTool(
1032
1005
  maxEmittedBytes?: number;
1033
1006
  /** Block-count budget for connecta.emit. Default 32. */
1034
1007
  maxEmittedBlocks?: number;
1035
- /** The advertised surface, which decides this tool's routing copy. */
1036
- surface?: ConnectaSurface;
1037
1008
  },
1038
1009
  ): void {
1039
1010
  // Resolved once so the description and the collector cannot disagree about
@@ -1065,7 +1036,7 @@ export function registerExecuteTool(
1065
1036
  server.registerTool(
1066
1037
  "execute_code",
1067
1038
  {
1068
- description: executeDescription(ctx.surface ?? "classic", emitBudgets),
1039
+ description: executeDescription(emitBudgets),
1069
1040
  inputSchema: z.object({
1070
1041
  code: z
1071
1042
  .string()