@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.
- package/AGENTS.md +8 -6
- package/CHANGELOG.md +40 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +0 -7
- package/dist/activity.d.ts.map +1 -1
- package/dist/activity.js.map +1 -1
- package/dist/catalog-service.d.ts +2 -17
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +4 -6
- package/dist/catalog-service.js.map +1 -1
- package/dist/connectors/api.d.ts +2 -2
- package/dist/connectors/remote-mcp.d.ts +1 -1
- package/dist/errors.d.ts +1 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +1 -3
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +7 -28
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +15 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -37
- package/dist/index.js.map +1 -1
- package/dist/invocation.js +2 -2
- package/dist/invocation.js.map +1 -1
- package/dist/meta-tools.d.ts +19 -58
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +34 -431
- package/dist/meta-tools.js.map +1 -1
- package/dist/registry.d.ts +1 -10
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +3 -15
- package/dist/registry.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +19 -30
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/shared.d.ts +5 -11
- package/dist/routes/shared.d.ts.map +1 -1
- package/dist/routes/shared.js.map +1 -1
- package/dist/server.js +5 -4
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +8 -18
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +13 -60
- package/dist/skills.js.map +1 -1
- package/dist/types.d.ts +6 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/code-first-exploration.md +16 -16
- package/documentation/code-mode.md +21 -35
- package/documentation/connectors.md +1 -1
- package/documentation/meta-tools.md +30 -43
- package/documentation/rich-output-design.md +4 -4
- package/examples/node/README.md +1 -2
- package/examples/node/src/index.ts +1 -3
- package/examples/worker/README.md +8 -13
- package/examples/worker/src/index.ts +6 -14
- package/examples/worker/wrangler.jsonc +3 -6
- package/package.json +1 -1
- package/src/activity.ts +5 -0
- package/src/catalog-service.ts +6 -26
- package/src/connectors/api.ts +2 -2
- package/src/connectors/remote-mcp.ts +1 -1
- package/src/errors.ts +2 -2
- package/src/execute.ts +7 -36
- package/src/index.ts +40 -69
- package/src/invocation.ts +2 -2
- package/src/meta-tools.ts +38 -565
- package/src/registry.ts +2 -33
- package/src/routes/mcp.ts +19 -30
- package/src/routes/shared.ts +4 -11
- package/src/server.ts +7 -7
- package/src/skills.ts +11 -74
- package/src/types.ts +6 -21
- package/src/version.ts +1 -1
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- 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
|
|
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
|
-
##
|
|
7
|
+
## The deployment surface
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
44
|
-
read-only calls
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
65
|
-
|
|
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`
|
|
93
|
-
`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
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
|
|
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.
|
|
107
|
-
|
|
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
|
|
package/examples/node/README.md
CHANGED
|
@@ -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()`
|
|
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
|
-
//
|
|
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.
|
|
6
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
The Worker Loader binding is
|
|
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`
|
|
70
|
-
|
|
71
|
-
|
|
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.
|
|
7
|
-
* seven-tool
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
22
|
-
|
|
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.
|
|
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
|
|
package/src/catalog-service.ts
CHANGED
|
@@ -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
|
-
*
|
|
64
|
-
*
|
|
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
|
|
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
|
|
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,
|
|
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) => {
|
package/src/connectors/api.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
) =>
|
|
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
|
|
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(
|
|
1039
|
+
description: executeDescription(emitBudgets),
|
|
1069
1040
|
inputSchema: z.object({
|
|
1070
1041
|
code: z
|
|
1071
1042
|
.string()
|