@zackbart/connecta 0.17.0 → 0.18.1
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/CHANGELOG.md +133 -0
- package/README.md +79 -114
- package/dist/catalog-service.d.ts +6 -0
- package/dist/catalog-service.js +69 -4
- package/dist/execute.d.ts +2 -0
- package/dist/execute.js +43 -17
- package/dist/meta-tools.js +9 -9
- package/dist/providers/mixpanel.js +4 -1
- package/dist/providers/revenuecat.d.ts +77 -0
- package/dist/providers/revenuecat.js +314 -0
- package/dist/providers/stripe.d.ts +23 -22
- package/dist/providers/stripe.js +63 -32
- package/dist/routes/ui.js +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +53 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +1 -1
- package/documentation/code-mode.md +23 -15
- package/documentation/connector-guides.md +7 -10
- package/documentation/connectors.md +1 -0
- package/documentation/meta-tools.md +27 -2
- package/documentation/notion.md +17 -0
- package/documentation/operations.md +11 -10
- package/documentation/provider-audit.md +45 -7
- package/documentation/provider-conventions.md +24 -15
- package/documentation/revenuecat.md +279 -0
- package/documentation/stripe.md +53 -67
- package/documentation/upgrading.md +32 -4
- package/ethos.md +4 -4
- package/package.json +6 -2
- package/templates/node/README.md +7 -0
- package/templates/node/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,139 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.18.1 — 2026-08-18
|
|
6
|
+
|
|
7
|
+
This patch is the response to one long investigation run against a live
|
|
8
|
+
deployment: a Mixpanel-and-Stripe user-journey reconstruction that rated the
|
|
9
|
+
connectors trustworthy and the ceremony too high. Nothing breaks and no
|
|
10
|
+
constructor changes. The two maintained guides now say the things that
|
|
11
|
+
investigation had to discover the hard way — which questions Insights cannot
|
|
12
|
+
answer, when Mixpanel's `false` is an absent property, which Stripe reads must
|
|
13
|
+
be projected inside the sandbox and why that is a privacy boundary as much as
|
|
14
|
+
a size one, and which fields Stripe search will not filter on. Beside them, a
|
|
15
|
+
new maintained RevenueCat connection lands, with the fact that decides its
|
|
16
|
+
deployment shape stated up front: a RevenueCat secret key reaches exactly one
|
|
17
|
+
project, so several projects are several connectors. A deployment that
|
|
18
|
+
constructs none of the three connections can ignore this release entirely.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`revenuecat()` — a maintained RevenueCat hosted-MCP connection** at
|
|
23
|
+
`@zackbart/connecta/providers/revenuecat`. OAuth by default (account-scoped:
|
|
24
|
+
the guide requires `list-projects` first and carries `project_id` into every
|
|
25
|
+
project-scoped call, stopping on ambiguity); an API v2 secret key as the
|
|
26
|
+
headless alternative, which binds the connector to one project — the guide's
|
|
27
|
+
first line names it, and a second project is a second connector with its own
|
|
28
|
+
key. All 95 documented tools are classified from RevenueCat's tools reference
|
|
29
|
+
(50 read-only, 15 additive, 29 destructive); `render-paywall-screenshot`
|
|
30
|
+
carries no access column there and fails closed. Borderline verdicts are
|
|
31
|
+
argued beside the rows they decide: `create-webhook-integration`,
|
|
32
|
+
`grant-customer-entitlement`, and `assign-customer-offering` are destructive
|
|
33
|
+
on consequence, not on verb. RevenueCat publishes six per-domain rate limits
|
|
34
|
+
and one admission rule cannot honestly cover 95 tools, so `callAdmission`
|
|
35
|
+
stays with the operator and the guide states the numbers. The maintainer-run
|
|
36
|
+
drift check accepts `revenuecat` behind `CONNECTA_DRIFT_REVENUECAT_KEY`; no
|
|
37
|
+
schema digests are recorded until a maintainer runs `--record` against a live
|
|
38
|
+
project (#433).
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **The Mixpanel guide says what Insights cannot answer.** One analysis is one
|
|
43
|
+
`execute_code` program with one `Get-Query-Schema` fetch; per-user event
|
|
44
|
+
timelines and sequence questions are out of reach through `Run-Query`
|
|
45
|
+
(`Get-User-Replays-Data` covers one user only where session replay is on);
|
|
46
|
+
`false` on a boolean property may be an absent one and must be confirmed with
|
|
47
|
+
`List-Properties` before it becomes a signal; breakdown responses are
|
|
48
|
+
flattened one row per combination inside the program with `$overall`
|
|
49
|
+
dropped. Convention P7 gains the rendering-rule clause this rests on (#430).
|
|
50
|
+
|
|
51
|
+
- **The Stripe guide names the reads that must be projected and the fields
|
|
52
|
+
search cannot filter.** Any `stripe_api_read` list or `stripe_api_search`
|
|
53
|
+
that returns full objects belongs inside `execute_code`, projected before
|
|
54
|
+
`return` — the reduction keeps customer PII out of the transcript, not only
|
|
55
|
+
bytes. Charges search takes a documented field set with no `payment_intent`;
|
|
56
|
+
follow the PaymentIntent's `latest_charge` instead. The account → search →
|
|
57
|
+
details → read sequence is one program, and decline outcomes are `outcome`,
|
|
58
|
+
`failure_code`, `failure_message` on the charge (#431).
|
|
59
|
+
|
|
60
|
+
## 0.18.0 — 2026-08-13
|
|
61
|
+
|
|
62
|
+
This minor release spends fewer tokens before the first call and answers
|
|
63
|
+
better when a call goes wrong. The always-loaded MCP instructions and tool
|
|
64
|
+
definitions are 44.6% lighter, the `execute_code` description now names the
|
|
65
|
+
deployment's configured connector IDs before the first catalog search, and a
|
|
66
|
+
failed `connecta.describe` entry carries the same typed recovery as a failed
|
|
67
|
+
call. The compatibility change is the floor: Node 22 is now the minimum
|
|
68
|
+
supported release, matching the shipped Docker template, and CI runs one job
|
|
69
|
+
per pull request against it; the Cloudflare Worker target is unchanged. Stripe
|
|
70
|
+
OAuth deployments must drop the connector-wide `mode` — each returned account
|
|
71
|
+
now carries its own. An evaluation of erasable TypeScript syntax in
|
|
72
|
+
`execute_code` programs ended in a recorded refusal, so portable programs
|
|
73
|
+
remain plain JavaScript. A deployment already on Node 22 that lets accounts
|
|
74
|
+
carry their Stripe mode needs no configuration change.
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- **`execute_code` now names the deployment's connectors up front.** Its
|
|
79
|
+
description carries a connector inventory derived only from the configured
|
|
80
|
+
registry: canonical IDs in registry order, the executable shortcut when
|
|
81
|
+
sanitization changes the ID, bounded to 256 serialized bytes with an exact
|
|
82
|
+
omitted count. A cold model can scope its first catalog search without
|
|
83
|
+
guessing, and rendering the inventory performs no catalog request and no
|
|
84
|
+
credential access (#416).
|
|
85
|
+
|
|
86
|
+
- **Failed `connecta.describe` entries are now as repairable as failed calls.**
|
|
87
|
+
Each failed entry carries `errorDetails` beside the preserved human text: the
|
|
88
|
+
invocation path's stable `code` and `retryable`, a route-aware `nextAction`,
|
|
89
|
+
and up to three deterministic nearby canonical addresses when the connector
|
|
90
|
+
is known. Caller-authored addresses are clamped everywhere they echo, so
|
|
91
|
+
hostile input can no longer displace partial results (#417).
|
|
92
|
+
|
|
93
|
+
### Changed
|
|
94
|
+
|
|
95
|
+
- **The always-loaded surface got 44.6% lighter.** The MCP instructions and the
|
|
96
|
+
seven tool definitions now carry route selection, the fail-closed read-only
|
|
97
|
+
boundary, and the minimum guest syntax — 6,850 bytes instead of 12,358.
|
|
98
|
+
Selection discipline, runtime differences, worked examples, direct-call
|
|
99
|
+
options, `get_result` rules, the `connecta.ui` reads-binding shape, and
|
|
100
|
+
repair guidance moved into the on-demand `usage` skill, which grew to a
|
|
101
|
+
deliberate 9,000-byte cap and stays byte-identical across deployments.
|
|
102
|
+
Clients that never fetch the skill keep correct routing from the compact
|
|
103
|
+
definitions alone (#418).
|
|
104
|
+
|
|
105
|
+
- **Node 22 is now the minimum supported Node release.** CI and the published
|
|
106
|
+
engine range now match the Node 22 runtime used by the shipped deployment
|
|
107
|
+
template. The Worker test project continues to cover the other deployment
|
|
108
|
+
target (#422).
|
|
109
|
+
|
|
110
|
+
- **Stripe OAuth mode now belongs to each returned account.** OAuth-backed
|
|
111
|
+
connectors no longer accept a connector-wide `mode`. Their neutral metadata
|
|
112
|
+
and guide support mixed live and sandbox accounts, require
|
|
113
|
+
`list_available_accounts_or_orgs`, and carry its exact `stripe_context` and
|
|
114
|
+
`livemode` into each account-scoped call. They use the stricter sandbox
|
|
115
|
+
admission ceiling. Header credentials still require one fixed mode, retain
|
|
116
|
+
key-prefix contradiction checks, and keep Stripe Connect behavior (#414).
|
|
117
|
+
|
|
118
|
+
- **The reviewed Notion writes stay narrow.** Newly published workspace-private
|
|
119
|
+
creation, templates, placement, richer media, locking, and irreversible
|
|
120
|
+
content erasure do not join the maintained surface. They are distinct
|
|
121
|
+
ownership, asynchronous, ordering, file, coordination, or deletion workflows,
|
|
122
|
+
not extra fields on `create_page` or `update_page_properties`. The existing
|
|
123
|
+
request subsets remain valid, and `trash_page` stays isolated and reversible
|
|
124
|
+
(#408, #409).
|
|
125
|
+
|
|
126
|
+
### Fixed
|
|
127
|
+
|
|
128
|
+
- **The Node template now declares its one reviewed install script.** npm 11 no
|
|
129
|
+
longer warns that esbuild's install is unreviewed, and npm 12 will not block
|
|
130
|
+
it. The approval is pinned to `esbuild@0.28.2`; the package smoke reads the
|
|
131
|
+
generated lockfile and fails if any resolved dependency has an unapproved
|
|
132
|
+
install script, including a future esbuild version. Local and container
|
|
133
|
+
installs keep the same `tsx` runtime path (#375).
|
|
134
|
+
|
|
135
|
+
- The documentation gate now ignores `.claude`, whose nested agent worktrees
|
|
136
|
+
are separate historical checkouts rather than source in the current tree.
|
|
137
|
+
|
|
5
138
|
## 0.17.0 — 2026-08-13
|
|
6
139
|
|
|
7
140
|
This minor release makes catalog discovery faster and its answers more exact.
|
package/README.md
CHANGED
|
@@ -2,129 +2,94 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
One
|
|
5
|
+
One MCP endpoint. The integrations you chose. Your agent reaches them by
|
|
6
|
+
writing code instead of loading a thousand tool definitions.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
every service separately. You decide which integrations are available and
|
|
9
|
-
Connecta holds their credentials, and the agent mostly works by writing
|
|
10
|
-
ordinary JavaScript that Connecta runs in a sandbox next to them — with a few
|
|
11
|
-
explicit tools for the jobs a program is the wrong shape for, destructive calls
|
|
12
|
-
among them.
|
|
8
|
+
## The mental model
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Agent["AI agent"]
|
|
17
|
-
Integrations["The integrations you chose"]
|
|
18
|
-
|
|
19
|
-
subgraph Connecta["Connecta: one MCP endpoint; credentials stay here"]
|
|
20
|
-
Sandbox["execute_code<br/>server-side sandbox"]
|
|
21
|
-
Explicit["Explicit tools<br/>destructive calls, search, auth"]
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
Agent -->|"writes a program"| Sandbox
|
|
25
|
-
Agent -->|"one deliberate call"| Explicit
|
|
26
|
-
Sandbox --> Integrations
|
|
27
|
-
Explicit --> Integrations
|
|
28
|
-
```
|
|
10
|
+
You ask your agent a question that touches a service — Linear, Stripe, an
|
|
11
|
+
internal API, anything you have connected. Here is what happens:
|
|
29
12
|
|
|
30
|
-
|
|
13
|
+
1. The agent talks to one endpoint, yours, and sees seven tools. Always seven,
|
|
14
|
+
no matter how many services sit behind it.
|
|
15
|
+
2. It writes a short JavaScript program. Connecta runs it in a sandbox next to
|
|
16
|
+
your integrations. The program can search for tools, call them, chain the
|
|
17
|
+
calls, and shape the result.
|
|
18
|
+
3. Only the answer comes back into the agent's context — not raw pages of
|
|
19
|
+
API output.
|
|
20
|
+
4. If the agent wants to change something — create, update, delete — it
|
|
21
|
+
cannot do that from a program. It makes one explicit call, and your MCP
|
|
22
|
+
client can put that call in front of you first.
|
|
31
23
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
calls, and trim the results before the agent ever sees them — so nothing has
|
|
35
|
-
to be loaded up front.
|
|
36
|
-
- **Safer access.** Credentials stay server-side — the program never sees them
|
|
37
|
-
— and consequential actions remain explicit and individual.
|
|
38
|
-
- **Named client access.** A Clerk operator can issue and revoke one-time,
|
|
39
|
-
hashed Bearer tokens for MCP clients that support header authentication.
|
|
40
|
-
- **Your deployment.** Connecta runs on Node, Docker, or Cloudflare Workers,
|
|
41
|
-
with configuration you can review and version.
|
|
24
|
+
Credentials never leave the server. The program never sees them, and neither
|
|
25
|
+
does the agent.
|
|
42
26
|
|
|
43
|
-
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart TB
|
|
29
|
+
Client["Your MCP client<br/>Claude, Cursor, …"]
|
|
44
30
|
|
|
45
|
-
|
|
31
|
+
subgraph Connecta["Connecta — one endpoint, seven tools, your credentials"]
|
|
32
|
+
Sandbox["execute_code<br/>the agent's program runs here<br/>read-only tools only"]
|
|
33
|
+
Explicit["call_destructive_tool<br/>one visible call per write<br/>your client can ask you first"]
|
|
34
|
+
end
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
npx @zackbart/connecta init my-connecta
|
|
49
|
-
cd my-connecta
|
|
50
|
-
npm install
|
|
51
|
-
CONNECTA_TOKEN=dev-token npm start
|
|
52
|
-
```
|
|
36
|
+
Integrations["The integrations you chose<br/>Linear · Stripe · Notion · your HTTP API · any MCP server"]
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
my-connecta/
|
|
59
|
-
├── src/index.ts # connectors, auth, storage, public URL
|
|
60
|
-
├── src/file-activity.ts # deployment-owned activity store, wired on request
|
|
61
|
-
├── package.json # exact Connecta and QuickJS versions
|
|
62
|
-
├── tsconfig.json
|
|
63
|
-
├── Dockerfile # the same source, containerized
|
|
64
|
-
├── docker-compose.yml # one service, state on a named volume
|
|
65
|
-
├── .dockerignore
|
|
66
|
-
├── .env.example
|
|
67
|
-
├── .gitignore
|
|
68
|
-
├── AGENTS.md
|
|
69
|
-
├── CLAUDE.md -> AGENTS.md
|
|
70
|
-
└── README.md
|
|
38
|
+
Client -->|"one connection"| Sandbox
|
|
39
|
+
Client --> Explicit
|
|
40
|
+
Sandbox -->|"reads"| Integrations
|
|
41
|
+
Explicit -->|"writes"| Integrations
|
|
71
42
|
```
|
|
72
43
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
|
|
44
|
+
This is the kind of thing the agent writes, not you:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
async () => {
|
|
48
|
+
const { nodes } = await tracker.list_issues({ state: "started" });
|
|
49
|
+
const byOwner = {};
|
|
50
|
+
for (const issue of nodes) {
|
|
51
|
+
(byOwner[issue.assignee?.name ?? "unassigned"] ??= []).push(issue.identifier);
|
|
52
|
+
}
|
|
53
|
+
return byOwner;
|
|
54
|
+
}
|
|
78
55
|
```
|
|
79
56
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Every deployment configures a sandbox: QuickJS on Node or a Dynamic Worker on
|
|
120
|
-
Cloudflare. Construction fails with an actionable error when the executor is
|
|
121
|
-
missing, so the model-facing interface is always the same seven tools.
|
|
122
|
-
|
|
123
|
-
## Project status
|
|
124
|
-
|
|
125
|
-
Connecta is built for its author's deployments first and is still evolving.
|
|
126
|
-
Breaking changes are expected before 1.0.
|
|
127
|
-
|
|
128
|
-
Read the [ethos](./ethos.md) for the product's principles, the
|
|
129
|
-
[changelog](./CHANGELOG.md) for releases, and [security policy](./SECURITY.md)
|
|
130
|
-
for vulnerability reporting.
|
|
57
|
+
Fifty issues in, one small object out. Your context window notices.
|
|
58
|
+
|
|
59
|
+
## What you can do with it
|
|
60
|
+
|
|
61
|
+
- **Put every MCP server you use behind one connection.** Add or remove
|
|
62
|
+
services in a config file; your client never changes.
|
|
63
|
+
- **Wrap any HTTP API by hand.** A few lines per tool. No OpenAPI conversion —
|
|
64
|
+
generated tool sprawl is the problem, not the fix.
|
|
65
|
+
- **Use maintained connections** for Cloudflare, Linear, Mixpanel, Notion,
|
|
66
|
+
RevenueCat, and Stripe — known endpoints, auth defaults, and vetted read/write
|
|
67
|
+
classifications, imported one at a time.
|
|
68
|
+
- **Let the agent work in code.** Search, chain, filter, join, and reduce
|
|
69
|
+
inside the sandbox instead of round-tripping every call through the model.
|
|
70
|
+
- **Keep writes deliberate.** Only tools marked read-only run in a program.
|
|
71
|
+
Everything else is a separate, visible call your client can gate.
|
|
72
|
+
- **Run it where you like.** Node, a Docker container, or a Cloudflare Worker,
|
|
73
|
+
from the same small deployment file.
|
|
74
|
+
|
|
75
|
+
There is also an operator surface, off until you turn it on: sign-in, an
|
|
76
|
+
encrypted credential vault with rotation, revocable per-client tokens, and a
|
|
77
|
+
payload-free activity log.
|
|
78
|
+
|
|
79
|
+
Connecta is not a platform, a marketplace, a policy engine, or a multi-tenant
|
|
80
|
+
service. Those are decisions, and the [ethos](./ethos.md) records each one
|
|
81
|
+
and why.
|
|
82
|
+
|
|
83
|
+
## Getting started
|
|
84
|
+
|
|
85
|
+
Setup is written for an agent. Point yours at [`AGENTS.md`](./AGENTS.md) and
|
|
86
|
+
ask it to set up a Connecta deployment; the
|
|
87
|
+
[documentation](./documentation/) covers every subsystem if you want to go
|
|
88
|
+
deeper, and [upgrading](./documentation/upgrading.md) an existing deployment
|
|
89
|
+
is its own runbook.
|
|
90
|
+
|
|
91
|
+
## Status
|
|
92
|
+
|
|
93
|
+
Built for its author's deployments first and published openly. Breaking
|
|
94
|
+
changes are expected before 1.0. See the [changelog](./CHANGELOG.md) and
|
|
95
|
+
[security policy](./SECURITY.md).
|
|
@@ -72,6 +72,11 @@ interface CatalogFailureDetail {
|
|
|
72
72
|
retryable: boolean;
|
|
73
73
|
retryAfterMs?: number;
|
|
74
74
|
}
|
|
75
|
+
interface CatalogDescriptionFailureDetail extends CatalogFailureDetail {
|
|
76
|
+
nextAction?: NonNullable<CallErrorDetails["nextAction"]>;
|
|
77
|
+
/** Nearby canonical addresses, ranked deterministically by tool name. */
|
|
78
|
+
suggestions?: string[];
|
|
79
|
+
}
|
|
75
80
|
export interface CatalogSearchPage {
|
|
76
81
|
entries: CatalogSearchEntry[];
|
|
77
82
|
total: number;
|
|
@@ -109,6 +114,7 @@ export interface CatalogDescription {
|
|
|
109
114
|
outputSchema?: unknown;
|
|
110
115
|
annotations?: ToolDef["annotations"];
|
|
111
116
|
error?: string;
|
|
117
|
+
errorDetails?: CatalogDescriptionFailureDetail;
|
|
112
118
|
}
|
|
113
119
|
export interface ResolvedCatalogTool {
|
|
114
120
|
connector: Connector;
|
package/dist/catalog-service.js
CHANGED
|
@@ -16,6 +16,7 @@ const MAX_QUERY_TERM_LENGTH = 64;
|
|
|
16
16
|
* of the deployment.
|
|
17
17
|
*/
|
|
18
18
|
const MAX_IDENTITY_CONNECTORS = 3;
|
|
19
|
+
const MAX_DESCRIBE_SUGGESTIONS = 3;
|
|
19
20
|
const encoder = new TextEncoder();
|
|
20
21
|
/** Clip one echoed query term without splitting a non-BMP code point. */
|
|
21
22
|
function boundedQueryTerm(term) {
|
|
@@ -80,6 +81,39 @@ function recoveryQuery(address) {
|
|
|
80
81
|
const candidate = separator >= 0 ? address.slice(separator + 1) : address;
|
|
81
82
|
return boundedEchoText(candidate.replaceAll(/[._-]+/g, " ").trim() || address);
|
|
82
83
|
}
|
|
84
|
+
function editDistance(left, right) {
|
|
85
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
86
|
+
for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
|
|
87
|
+
const current = [leftIndex + 1];
|
|
88
|
+
for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
|
|
89
|
+
current.push(Math.min(previous[rightIndex + 1] + 1, current[rightIndex] + 1, previous[rightIndex] +
|
|
90
|
+
(left[leftIndex] === right[rightIndex] ? 0 : 1)));
|
|
91
|
+
}
|
|
92
|
+
previous = current;
|
|
93
|
+
}
|
|
94
|
+
return previous[right.length];
|
|
95
|
+
}
|
|
96
|
+
/** Nearby names only; descriptions never influence describe-miss recovery. */
|
|
97
|
+
function describeSuggestions(connectorId, attemptedName, tools) {
|
|
98
|
+
const attempted = attemptedName.toLowerCase();
|
|
99
|
+
return tools
|
|
100
|
+
.map((tool, order) => {
|
|
101
|
+
const name = tool.name.toLowerCase();
|
|
102
|
+
return { tool, order, distance: editDistance(attempted, name) };
|
|
103
|
+
})
|
|
104
|
+
.filter(({ tool, distance }) => {
|
|
105
|
+
const longest = Math.max(attempted.length, tool.name.length);
|
|
106
|
+
return (attempted.includes(tool.name.toLowerCase()) ||
|
|
107
|
+
tool.name.toLowerCase().includes(attempted) ||
|
|
108
|
+
distance <= Math.max(2, Math.floor(longest * 0.4)));
|
|
109
|
+
})
|
|
110
|
+
.sort((left, right) => left.distance - right.distance || left.order - right.order)
|
|
111
|
+
.map(({ tool }) => `${connectorId}.${tool.name}`)
|
|
112
|
+
// A clipped address would no longer be canonical. Omit an implausibly
|
|
113
|
+
// large catalog name instead of letting one suggestion erase the page.
|
|
114
|
+
.filter((address) => boundedEchoText(address) === address)
|
|
115
|
+
.slice(0, MAX_DESCRIBE_SUGGESTIONS);
|
|
116
|
+
}
|
|
83
117
|
/** Serialize once and count the exact bytes the MCP adapter would emit. */
|
|
84
118
|
export function boundedDiscoveryText(value, hint) {
|
|
85
119
|
const text = JSON.stringify(value);
|
|
@@ -734,17 +768,48 @@ export class CatalogService {
|
|
|
734
768
|
});
|
|
735
769
|
return resolved.map(({ address, resolved: addressResolution }) => {
|
|
736
770
|
if (!addressResolution) {
|
|
737
|
-
|
|
771
|
+
const message = `Unknown address "${boundedEchoText(address)}"`;
|
|
772
|
+
return {
|
|
773
|
+
address: boundedEchoText(address),
|
|
774
|
+
error: message,
|
|
775
|
+
errorDetails: {
|
|
776
|
+
...framingError("unknown_address", message),
|
|
777
|
+
nextAction: this.searchRecovery({ query: recoveryQuery(address) }, "Find the configured canonical address before retrying."),
|
|
778
|
+
},
|
|
779
|
+
};
|
|
738
780
|
}
|
|
739
781
|
const catalog = catalogs.get(addressResolution.connector.id);
|
|
740
782
|
if (catalog instanceof Error) {
|
|
741
|
-
|
|
783
|
+
const classified = classifyCallError(catalog, "catalog_lookup_failed");
|
|
784
|
+
const message = boundedEchoText(classified.message);
|
|
785
|
+
return {
|
|
786
|
+
address: boundedEchoText(address),
|
|
787
|
+
error: message,
|
|
788
|
+
errorDetails: {
|
|
789
|
+
code: classified.code,
|
|
790
|
+
message,
|
|
791
|
+
retryable: classified.retryable,
|
|
792
|
+
...(classified.retryAfterMs === undefined
|
|
793
|
+
? {}
|
|
794
|
+
: { retryAfterMs: classified.retryAfterMs }),
|
|
795
|
+
},
|
|
796
|
+
};
|
|
742
797
|
}
|
|
743
798
|
const tool = catalog?.find((item) => item.name === addressResolution.toolName);
|
|
744
799
|
if (!tool) {
|
|
800
|
+
const message = `Unknown tool "${boundedEchoText(addressResolution.toolName)}" on connector "${addressResolution.connector.id}"`;
|
|
801
|
+
const suggestions = describeSuggestions(addressResolution.connector.id, addressResolution.toolName, catalog ?? []);
|
|
745
802
|
return {
|
|
746
|
-
address,
|
|
747
|
-
error:
|
|
803
|
+
address: boundedEchoText(address),
|
|
804
|
+
error: message,
|
|
805
|
+
errorDetails: {
|
|
806
|
+
...framingError("unknown_tool", message),
|
|
807
|
+
nextAction: this.searchRecovery({
|
|
808
|
+
query: recoveryQuery(addressResolution.toolName),
|
|
809
|
+
connector: addressResolution.connector.id,
|
|
810
|
+
}, "Find the connector's current canonical tool address."),
|
|
811
|
+
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
812
|
+
},
|
|
748
813
|
};
|
|
749
814
|
}
|
|
750
815
|
const input = tool.inputSchema ?? { type: "object" };
|
package/dist/execute.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { InvocationFailure } from "./invocation.js";
|
|
|
6
6
|
import type { RegistryView } from "./registry.js";
|
|
7
7
|
import type { Executor, ExecutorProvider, Logger } from "./types.js";
|
|
8
8
|
export declare const EXECUTE_MAX_BATCH_CALLS = 10;
|
|
9
|
+
/** Complete entries plus an exact omission count, all inside this byte cap. */
|
|
10
|
+
export declare const CONNECTOR_INVENTORY_MAX_BYTES = 256;
|
|
9
11
|
/**
|
|
10
12
|
* Default budgets for `connecta.emit`. The byte budget is a transport bound,
|
|
11
13
|
* not a context bound — emitted image/audio blocks reach the model as media,
|
package/dist/execute.js
CHANGED
|
@@ -4,7 +4,7 @@ import { boundedDiscoveryText, CatalogService, DiscoveryPolicyError, flatSearchR
|
|
|
4
4
|
import { errorResult, jsonResult } from "./meta-tools.js";
|
|
5
5
|
import { guardExecuteResultValue, MAX_EXECUTE_LOG_CHARS, truncateExecuteText, } from "./executor-result.js";
|
|
6
6
|
import { ExecutorAdmissionError, ExecutorExecutionError, isAdmittingExecutor, } from "./executor-admission.js";
|
|
7
|
-
import { classifyCallError } from "./errors.js";
|
|
7
|
+
import { boundedEchoText, classifyCallError } from "./errors.js";
|
|
8
8
|
import { InvocationFailure, InvocationService, } from "./invocation.js";
|
|
9
9
|
import { hasConnectorGuides } from "./skills.js";
|
|
10
10
|
import { isExplicitlyReadOnly } from "./tool-safety.js";
|
|
@@ -12,6 +12,8 @@ import { isExplicitlyReadOnly } from "./tool-safety.js";
|
|
|
12
12
|
const EXECUTE_MAX_HOST_CALLS = 20;
|
|
13
13
|
export const EXECUTE_MAX_BATCH_CALLS = 10;
|
|
14
14
|
const EXECUTE_HOST_CALL_TIMEOUT_MS = 15_000;
|
|
15
|
+
/** Complete entries plus an exact omission count, all inside this byte cap. */
|
|
16
|
+
export const CONNECTOR_INVENTORY_MAX_BYTES = 256;
|
|
15
17
|
/**
|
|
16
18
|
* Default budgets for `connecta.emit`. The byte budget is a transport bound,
|
|
17
19
|
* not a context bound — emitted image/audio blocks reach the model as media,
|
|
@@ -996,24 +998,47 @@ function discardedEmitsText(emitted) {
|
|
|
996
998
|
];
|
|
997
999
|
return lines.length > 0 ? `\n\n${lines.join("\n")}` : "";
|
|
998
1000
|
}
|
|
999
|
-
|
|
1001
|
+
function connectorInventory(connectors) {
|
|
1002
|
+
const prefix = "Connectors: ";
|
|
1003
|
+
if (connectors.length === 0)
|
|
1004
|
+
return `${prefix}none.`;
|
|
1005
|
+
const entries = connectors.map((connector) => {
|
|
1006
|
+
const shortcut = sanitizeIdentifier(connector.id);
|
|
1007
|
+
return shortcut === connector.id
|
|
1008
|
+
? connector.id
|
|
1009
|
+
: `${connector.id} (shortcut ${shortcut})`;
|
|
1010
|
+
});
|
|
1011
|
+
const shown = [];
|
|
1012
|
+
for (let index = 0; index < entries.length; index++) {
|
|
1013
|
+
const entry = entries[index];
|
|
1014
|
+
if (entry === undefined)
|
|
1015
|
+
break;
|
|
1016
|
+
const candidate = [...shown, entry].join(", ");
|
|
1017
|
+
const omitted = entries.length - index - 1;
|
|
1018
|
+
const suffix = omitted > 0 ? `; +${omitted} more.` : ".";
|
|
1019
|
+
const serialized = prefix + candidate + suffix;
|
|
1020
|
+
if (boundedEchoText(serialized, CONNECTOR_INVENTORY_MAX_BYTES) !== serialized) {
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
shown.push(entry);
|
|
1024
|
+
}
|
|
1025
|
+
const omitted = entries.length - shown.length;
|
|
1026
|
+
if (omitted === 0)
|
|
1027
|
+
return `${prefix}${shown.join(", ")}.`;
|
|
1028
|
+
return `${prefix}${shown.join(", ")}${shown.length > 0 ? "; " : ""}+${omitted} more.`;
|
|
1029
|
+
}
|
|
1030
|
+
const executeDescription = (emitBudgets, connectorGuides, connectors) => `Choose the route before discovery. Exactly one unknown-address read uses top-level search_tools then call_tool. execute_code is the primary surface for everything wider: make exactly one execute_code call that searches, selects, calls, and reduces. A discovery-only program wastes its round trip: finish here, don't return catalog matches for a later call. Only readOnlyHint: true tools are available. Limits: ${EXECUTE_MAX_HOST_CALLS} host calls per run, ${EXECUTE_MAX_BATCH_CALLS} per batch, ${EXECUTE_HOST_CALL_TIMEOUT_MS / 1_000}-second host deadline.
|
|
1031
|
+
|
|
1032
|
+
${connectorInventory(connectors)}
|
|
1000
1033
|
|
|
1001
|
-
Write
|
|
1002
|
-
-
|
|
1003
|
-
- connecta.
|
|
1004
|
-
- top-level search_tools returns { connectors: [{ id, tools }], total, offset, limit, hasMore }; connecta.search returns { tools, total, offset, limit, hasMore }; connecta.describe returns { tools }.
|
|
1005
|
-
- connecta.search(args) loads catalogs and must be followed by selection and calls in this program; set connector to the obvious id to load one, otherwise it loads all. For distinct operations, make separate short searches here. Check address, description, requiredInputKeys, truncation, safety, and outputs; never take the first lexical or merely input-compatible match. Select by fit; do not require it to be the only match. Missing outputKeys means inspect outputSchema, not discard the candidate. Every required key needs task or prior-result data; do not prefer zero required keys. Put every requiredInputKey in call args. For dependencies, match an earlier outputKey to the later requiredInputKey. [] means no required keys, not permission to invent args. Describe only a truncated/insufficient compact shape. Reducers use declared outputKeys, never guessed items/results roots. Describe one address or an addresses array. Use safety: "readOnly" to avoid advertising calls this sandbox cannot execute.${connectorGuides ? " guideRequired: true = stop. Describe clears only schema_truncated; otherwise return its exact guide, fetch with top-level skills, then write the informed call." : ""}
|
|
1034
|
+
Write one plain-JavaScript async arrow function. Use only:
|
|
1035
|
+
- <connectorId>.<toolName>(args) for a sanitized shortcut, or connecta.call(address, args) for a canonical address.
|
|
1036
|
+
- connecta.search(args), connecta.describe(args), and connecta.batch(calls) for discovery and independent read-only calls.
|
|
1006
1037
|
- connecta.emit(block) — { type: "text", text } or { type: "image" | "audio", data (base64), mimeType }. Success-only; ${emitBudgets.maxBlocks} blocks/${emitBudgets.maxBytes} bytes; invalid/over-budget throws.
|
|
1007
|
-
- connecta.ui(html, options?)
|
|
1038
|
+
- connecta.ui(html, options?) for one success-only view; return the same initial summary the HTML renders.
|
|
1008
1039
|
- console.log(...) — captured.
|
|
1009
1040
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
Dependent example (only when the second call requires a value returned by the first): async () => { const { tools } = await connecta.search({ query: "pipeline run job logs", safety: "readOnly", includeSchemas: "compact" }); const pick = (suffix) => { const match = tools.find((t) => t.address.endsWith(suffix)); if (!match) throw new Error("no tool for " + suffix); return match.address; }; const run = await connecta.call(pick(".get_run"), { runId: 42 }); const logs = await connecta.call(pick(".get_job_logs"), { jobId: run.failedJobId }); return [run, logs]; }
|
|
1013
|
-
|
|
1014
|
-
A caught Connecta failure keeps its message and exposes code, retryable, and details; batch uses the same fields. Branch on fields, never prose. Never retry retryable: false, or rate_limited immediately; portable code cannot wait. Return JSON; reduce large results before they truncate.
|
|
1015
|
-
|
|
1016
|
-
Plain JS. Compact schemas are TypeScript-like: write the property names they display; never guess positions or aliases.`;
|
|
1041
|
+
Programs have no portable ambient capabilities. Return JSON and reduce large results before they truncate. Fetch skills({ name: "usage" }) once for selection rules, exact result shapes, repair, examples, guide handling${connectorGuides ? ", connector-guide rules" : ""}, and runtime differences.`;
|
|
1017
1042
|
/** Register the execute_code meta-tool. Only called when an executor is configured. */
|
|
1018
1043
|
export function registerExecuteTool(server, registry, ctx) {
|
|
1019
1044
|
// Resolved once so the description and the collector cannot disagree about
|
|
@@ -1022,6 +1047,7 @@ export function registerExecuteTool(server, registry, ctx) {
|
|
|
1022
1047
|
maxBytes: resolveEmitBudget(ctx.maxEmittedBytes, EXECUTE_MAX_EMITTED_BYTES),
|
|
1023
1048
|
maxBlocks: resolveEmitBudget(ctx.maxEmittedBlocks, EXECUTE_MAX_EMITTED_BLOCKS),
|
|
1024
1049
|
};
|
|
1050
|
+
const connectors = registry.listConnectors();
|
|
1025
1051
|
const handler = createExecuteTool(registry, ctx.baseUrl, ctx.executor, ctx.logger, ctx.activity, {
|
|
1026
1052
|
...(ctx.discoveryConcurrency !== undefined
|
|
1027
1053
|
? { discoveryConcurrency: ctx.discoveryConcurrency }
|
|
@@ -1034,11 +1060,11 @@ export function registerExecuteTool(server, registry, ctx) {
|
|
|
1034
1060
|
...(ctx.defer !== undefined ? { defer: ctx.defer } : {}),
|
|
1035
1061
|
});
|
|
1036
1062
|
server.registerTool("execute_code", {
|
|
1037
|
-
description: executeDescription(emitBudgets, hasConnectorGuides(
|
|
1063
|
+
description: executeDescription(emitBudgets, hasConnectorGuides(connectors), connectors),
|
|
1038
1064
|
inputSchema: z.object({
|
|
1039
1065
|
code: z
|
|
1040
1066
|
.string()
|
|
1041
|
-
.describe("One complete JavaScript async arrow function
|
|
1067
|
+
.describe("One complete JavaScript async arrow function that discovers, calls, and returns the reduced answer."),
|
|
1042
1068
|
diagnostics: z
|
|
1043
1069
|
.boolean()
|
|
1044
1070
|
.optional()
|