@zackbart/connecta 0.16.1 → 0.18.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/CHANGELOG.md +180 -0
- package/README.md +4 -0
- package/dist/catalog-service.d.ts +10 -0
- package/dist/catalog-service.js +77 -5
- package/dist/catalog.js +114 -12
- package/dist/errors.d.ts +4 -6
- package/dist/execute.d.ts +7 -0
- package/dist/execute.js +262 -168
- package/dist/invocation.js +3 -1
- package/dist/meta-tools.d.ts +4 -0
- package/dist/meta-tools.js +55 -23
- package/dist/operator-ui/generated.d.ts +1 -1
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +3 -1
- package/dist/providers/mixpanel.d.ts +3 -5
- package/dist/providers/mixpanel.js +73 -5
- package/dist/providers/stripe.d.ts +25 -24
- package/dist/providers/stripe.js +64 -35
- package/dist/registry.d.ts +32 -9
- package/dist/registry.js +217 -33
- package/dist/routes/mcp.js +6 -0
- package/dist/routes/ui.js +1 -1
- package/dist/skills.d.ts +5 -1
- package/dist/skills.js +206 -30
- package/dist/types.d.ts +14 -2
- package/dist/ui.js +4 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +8 -5
- package/documentation/code-mode.md +68 -68
- package/documentation/connector-guides.md +29 -27
- package/documentation/connectors.md +13 -1
- package/documentation/meta-tools.md +53 -19
- package/documentation/mixpanel.md +20 -0
- package/documentation/notion.md +17 -0
- package/documentation/operations.md +24 -21
- package/documentation/operator-ui.md +12 -2
- package/documentation/provider-audit.md +15 -7
- package/documentation/provider-conventions.md +26 -13
- package/documentation/stripe.md +66 -59
- package/documentation/upgrading.md +46 -4
- package/ethos.md +7 -7
- package/examples/worker/README.md +4 -3
- package/package.json +2 -2
- package/templates/node/README.md +7 -0
- package/templates/node/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,186 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.18.0 — 2026-08-13
|
|
6
|
+
|
|
7
|
+
This minor release spends fewer tokens before the first call and answers
|
|
8
|
+
better when a call goes wrong. The always-loaded MCP instructions and tool
|
|
9
|
+
definitions are 44.6% lighter, the `execute_code` description now names the
|
|
10
|
+
deployment's configured connector IDs before the first catalog search, and a
|
|
11
|
+
failed `connecta.describe` entry carries the same typed recovery as a failed
|
|
12
|
+
call. The compatibility change is the floor: Node 22 is now the minimum
|
|
13
|
+
supported release, matching the shipped Docker template, and CI runs one job
|
|
14
|
+
per pull request against it; the Cloudflare Worker target is unchanged. Stripe
|
|
15
|
+
OAuth deployments must drop the connector-wide `mode` — each returned account
|
|
16
|
+
now carries its own. An evaluation of erasable TypeScript syntax in
|
|
17
|
+
`execute_code` programs ended in a recorded refusal, so portable programs
|
|
18
|
+
remain plain JavaScript. A deployment already on Node 22 that lets accounts
|
|
19
|
+
carry their Stripe mode needs no configuration change.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **`execute_code` now names the deployment's connectors up front.** Its
|
|
24
|
+
description carries a connector inventory derived only from the configured
|
|
25
|
+
registry: canonical IDs in registry order, the executable shortcut when
|
|
26
|
+
sanitization changes the ID, bounded to 256 serialized bytes with an exact
|
|
27
|
+
omitted count. A cold model can scope its first catalog search without
|
|
28
|
+
guessing, and rendering the inventory performs no catalog request and no
|
|
29
|
+
credential access (#416).
|
|
30
|
+
|
|
31
|
+
- **Failed `connecta.describe` entries are now as repairable as failed calls.**
|
|
32
|
+
Each failed entry carries `errorDetails` beside the preserved human text: the
|
|
33
|
+
invocation path's stable `code` and `retryable`, a route-aware `nextAction`,
|
|
34
|
+
and up to three deterministic nearby canonical addresses when the connector
|
|
35
|
+
is known. Caller-authored addresses are clamped everywhere they echo, so
|
|
36
|
+
hostile input can no longer displace partial results (#417).
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **The always-loaded surface got 44.6% lighter.** The MCP instructions and the
|
|
41
|
+
seven tool definitions now carry route selection, the fail-closed read-only
|
|
42
|
+
boundary, and the minimum guest syntax — 6,850 bytes instead of 12,358.
|
|
43
|
+
Selection discipline, runtime differences, worked examples, direct-call
|
|
44
|
+
options, `get_result` rules, the `connecta.ui` reads-binding shape, and
|
|
45
|
+
repair guidance moved into the on-demand `usage` skill, which grew to a
|
|
46
|
+
deliberate 9,000-byte cap and stays byte-identical across deployments.
|
|
47
|
+
Clients that never fetch the skill keep correct routing from the compact
|
|
48
|
+
definitions alone (#418).
|
|
49
|
+
|
|
50
|
+
- **Node 22 is now the minimum supported Node release.** CI and the published
|
|
51
|
+
engine range now match the Node 22 runtime used by the shipped deployment
|
|
52
|
+
template. The Worker test project continues to cover the other deployment
|
|
53
|
+
target (#422).
|
|
54
|
+
|
|
55
|
+
- **Stripe OAuth mode now belongs to each returned account.** OAuth-backed
|
|
56
|
+
connectors no longer accept a connector-wide `mode`. Their neutral metadata
|
|
57
|
+
and guide support mixed live and sandbox accounts, require
|
|
58
|
+
`list_available_accounts_or_orgs`, and carry its exact `stripe_context` and
|
|
59
|
+
`livemode` into each account-scoped call. They use the stricter sandbox
|
|
60
|
+
admission ceiling. Header credentials still require one fixed mode, retain
|
|
61
|
+
key-prefix contradiction checks, and keep Stripe Connect behavior (#414).
|
|
62
|
+
|
|
63
|
+
- **The reviewed Notion writes stay narrow.** Newly published workspace-private
|
|
64
|
+
creation, templates, placement, richer media, locking, and irreversible
|
|
65
|
+
content erasure do not join the maintained surface. They are distinct
|
|
66
|
+
ownership, asynchronous, ordering, file, coordination, or deletion workflows,
|
|
67
|
+
not extra fields on `create_page` or `update_page_properties`. The existing
|
|
68
|
+
request subsets remain valid, and `trash_page` stays isolated and reversible
|
|
69
|
+
(#408, #409).
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- **The Node template now declares its one reviewed install script.** npm 11 no
|
|
74
|
+
longer warns that esbuild's install is unreviewed, and npm 12 will not block
|
|
75
|
+
it. The approval is pinned to `esbuild@0.28.2`; the package smoke reads the
|
|
76
|
+
generated lockfile and fails if any resolved dependency has an unapproved
|
|
77
|
+
install script, including a future esbuild version. Local and container
|
|
78
|
+
installs keep the same `tsx` runtime path (#375).
|
|
79
|
+
|
|
80
|
+
- The documentation gate now ignores `.claude`, whose nested agent worktrees
|
|
81
|
+
are separate historical checkouts rather than source in the current tree.
|
|
82
|
+
|
|
83
|
+
## 0.17.0 — 2026-08-13
|
|
84
|
+
|
|
85
|
+
This minor release makes catalog discovery faster and its answers more exact.
|
|
86
|
+
Agent reads can use a verified stale catalog while one bounded refresh runs,
|
|
87
|
+
guide summaries now read Markdown as prose, compact schemas retain more declared
|
|
88
|
+
constraints, array projection distinguishes misses from genuine nulls, and
|
|
89
|
+
Mixpanel carries the conditional rules its live tools enforce. One construction
|
|
90
|
+
contract tightens: an explicit `usageGuide.summary` over 120 characters now
|
|
91
|
+
refuses to boot. Deployments whose summaries fit, or which let Connecta derive
|
|
92
|
+
them, need no configuration change. Operator catalog reads retain their blocking
|
|
93
|
+
freshness behavior. This release also makes the two code sandboxes' runtime
|
|
94
|
+
differences explicit. The shipped
|
|
95
|
+
Worker example is already loader-only; deployments that added executor
|
|
96
|
+
`bindings`, `modules`, or `globalOutbound` must remove them. Existing portable
|
|
97
|
+
`execute_code` programs keep their behavior. Programs can now classify a caught
|
|
98
|
+
Connecta failure without parsing its message.
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
|
|
102
|
+
- An explicit `usageGuide.summary` longer than 120 characters after whitespace
|
|
103
|
+
normalization now throws during registry construction. Exactly 120 remains
|
|
104
|
+
valid, and a blank explicit summary still falls back to derivation (#392).
|
|
105
|
+
|
|
106
|
+
- **Agent catalog reads now serve a verified stale entry while they refresh it.**
|
|
107
|
+
Search, describe, and code-mode calls no longer await a downstream listing
|
|
108
|
+
when the runtime already holds a complete catalog inside its stale window.
|
|
109
|
+
The inbound request still causes the refresh; there is no timer, warmup, or
|
|
110
|
+
credential probe. One bounded refresh per connector owns and closes a fresh
|
|
111
|
+
scope, while operator status stays blocking and shows whether the last agent
|
|
112
|
+
read in this runtime was fresh or stale (#396).
|
|
113
|
+
|
|
114
|
+
### Fixed
|
|
115
|
+
|
|
116
|
+
- **Caught `execute_code` failures now keep their machine-readable type.**
|
|
117
|
+
Calls, connector shortcuts, discovery, emitted-output and UI validation,
|
|
118
|
+
batch validation, and host-call budgets still throw with the same human
|
|
119
|
+
message, but now expose `code`, `retryable`, and full `details`. Batch entries
|
|
120
|
+
use the same codes. A per-run authenticated frame prevents connector prose or
|
|
121
|
+
guest code from forging the host transport on either executor (#393).
|
|
122
|
+
|
|
123
|
+
- **`execute_code` now tells the truth about each shipped sandbox.** QuickJS
|
|
124
|
+
has no `fetch`, `process`, timers, `crypto`, or `WebSocket`, and blocks
|
|
125
|
+
imports. Loader-only Dynamic Workers deny outbound fetch, WebSocket,
|
|
126
|
+
`node:net`, and `node:tls`; leave DNS unresolved; expose no environment
|
|
127
|
+
bindings or filesystem/HTTP builtins; but retain local `data:` fetch,
|
|
128
|
+
runtime globals, and a non-contract builtin set that can drift. The example
|
|
129
|
+
pins the required loader-only construction, and agent guidance tells portable
|
|
130
|
+
programs to use none of that Dynamic-only authority (#390).
|
|
131
|
+
|
|
132
|
+
- **Clerk-authenticated operator pages now wait for ClerkJS before booting.**
|
|
133
|
+
The loader runs before the later inline operator bundle instead of deferring
|
|
134
|
+
until after parsing, so a fresh page no longer mistakes normal script order
|
|
135
|
+
for a network failure. Clerk's major-to-pinned version redirect remains
|
|
136
|
+
supported, and a real loader failure keeps the existing clear error (#403).
|
|
137
|
+
|
|
138
|
+
- **Stripe's guide now treats connector identity as routing intent, not account
|
|
139
|
+
proof.** One OAuth session may cover several accounts in one organization,
|
|
140
|
+
so agents resolve the intended account through the live tool schema and stop
|
|
141
|
+
when the target or selector is ambiguous. The guide also keeps organization
|
|
142
|
+
accounts separate from the restricted-key-only Stripe Connect path (#404).
|
|
143
|
+
|
|
144
|
+
- **The no-account-model constitution now matches provider-owned sessions.**
|
|
145
|
+
Connecta still has no account dimension: credentials, storage, admission,
|
|
146
|
+
and health remain connector-scoped. A provider may expose its own account
|
|
147
|
+
scope only through its live schema; metadata never proves identity, and an
|
|
148
|
+
ambiguous target or selector stops instead of becoming a guess (#410).
|
|
149
|
+
|
|
150
|
+
- **The reviewed Notion page contracts are current again.** Notion added
|
|
151
|
+
create-page template and placement options plus update-page locking,
|
|
152
|
+
template, and erase options. The existing parent, properties, Markdown,
|
|
153
|
+
children, emoji, and trash request subsets remain valid, so this release
|
|
154
|
+
records the two changed endpoint digests without adding the new capabilities.
|
|
155
|
+
Their product decisions remain in #408 and #409.
|
|
156
|
+
|
|
157
|
+
- **Array field misses now report what happened.** A path that misses every
|
|
158
|
+
element appears in `unmatchedFields` instead of returning a clean array of
|
|
159
|
+
false nulls. A heterogeneous array keeps its positional result and names the
|
|
160
|
+
path in `partialFields`, so genuine downstream nulls remain distinguishable.
|
|
161
|
+
Schema-backed misses keep the same bounded guidance through nested arrays;
|
|
162
|
+
schema-free projections still report their observed misses (#394).
|
|
163
|
+
- Derived guide summaries now join a hard-wrapped opening paragraph before
|
|
164
|
+
selecting a complete sentence or shortening at a clause or word boundary.
|
|
165
|
+
Frontmatter, fences, headings, rules, tables, and description fallbacks keep
|
|
166
|
+
their prior roles; multi-line HTML comments are now skipped whole (#392).
|
|
167
|
+
|
|
168
|
+
- **Compact schemas now carry declared numeric and string constraints.** Search
|
|
169
|
+
and compact describe show numeric bounds, multiples, string length bounds,
|
|
170
|
+
patterns, and formats beside the affected type. Search keeps its 1,024-byte
|
|
171
|
+
schema ceiling and 256-byte node budget: a constraint that does not fit is
|
|
172
|
+
dropped whole, and the existing truncation flag sends the caller to describe
|
|
173
|
+
for the complete shape (#391).
|
|
174
|
+
|
|
175
|
+
- **Carry Mixpanel's three enforced conditional-input rules in its maintained
|
|
176
|
+
guide.** A live read-only audit confirmed that `Get-Business-Context`,
|
|
177
|
+
`Get-Property-Values`, and `List-Properties` accept shapes in their advertised
|
|
178
|
+
schemas that their implementations reject. Connecta still preserves the
|
|
179
|
+
hosted schemas unchanged; the guide now prevents those rejected calls, the
|
|
180
|
+
vetted manifest records schema digests for all 63 tools, and the provider
|
|
181
|
+
defect is tracked upstream. The maintainer drift check also frames
|
|
182
|
+
Mixpanel's service account as its documented `Bearer Basic` value instead of
|
|
183
|
+
ordinary HTTP Basic (#395).
|
|
184
|
+
|
|
5
185
|
## 0.16.1 — 2026-08-13
|
|
6
186
|
|
|
7
187
|
This is the cleanup that follows 0.16.0 out the door: the packaging housekeeping
|
package/README.md
CHANGED
|
@@ -110,6 +110,10 @@ connector tools are reachable. Unannotated or write-capable calls stay
|
|
|
110
110
|
individual and cross `call_destructive_tool`, where the MCP host can ask the
|
|
111
111
|
operator for approval.
|
|
112
112
|
|
|
113
|
+
The Node template also pins its one approved dependency install script:
|
|
114
|
+
esbuild, which `tsx` needs to run the deployment source. A dependency update
|
|
115
|
+
that adds or changes an install script fails the package smoke until reviewed.
|
|
116
|
+
|
|
113
117
|
There are two deployment shapes and no others:
|
|
114
118
|
|
|
115
119
|
- [Node, local or Docker](./templates/node/) — what `init` copies
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CallErrorDetails } from "./errors.js";
|
|
2
2
|
import type { ConnectorOperationOptions, RegistryView } from "./registry.js";
|
|
3
|
+
import type { DeferredWork } from "./connector-scope.js";
|
|
3
4
|
import type { Connector, ToolDef } from "./types.js";
|
|
4
5
|
export declare const DEFAULT_SEARCH_LIMIT = 8;
|
|
5
6
|
export declare const MAX_SEARCH_LIMIT = 100;
|
|
@@ -71,6 +72,11 @@ interface CatalogFailureDetail {
|
|
|
71
72
|
retryable: boolean;
|
|
72
73
|
retryAfterMs?: number;
|
|
73
74
|
}
|
|
75
|
+
interface CatalogDescriptionFailureDetail extends CatalogFailureDetail {
|
|
76
|
+
nextAction?: NonNullable<CallErrorDetails["nextAction"]>;
|
|
77
|
+
/** Nearby canonical addresses, ranked deterministically by tool name. */
|
|
78
|
+
suggestions?: string[];
|
|
79
|
+
}
|
|
74
80
|
export interface CatalogSearchPage {
|
|
75
81
|
entries: CatalogSearchEntry[];
|
|
76
82
|
total: number;
|
|
@@ -108,6 +114,7 @@ export interface CatalogDescription {
|
|
|
108
114
|
outputSchema?: unknown;
|
|
109
115
|
annotations?: ToolDef["annotations"];
|
|
110
116
|
error?: string;
|
|
117
|
+
errorDetails?: CatalogDescriptionFailureDetail;
|
|
111
118
|
}
|
|
112
119
|
export interface ResolvedCatalogTool {
|
|
113
120
|
connector: Connector;
|
|
@@ -138,6 +145,7 @@ export declare class CatalogService {
|
|
|
138
145
|
private readonly probeTimeoutMs;
|
|
139
146
|
private readonly concurrency;
|
|
140
147
|
private readonly searchRoute;
|
|
148
|
+
private readonly readOptions;
|
|
141
149
|
private readonly loaded;
|
|
142
150
|
private readonly loading;
|
|
143
151
|
constructor(registry: RegistryView, baseUrl: string, options?: {
|
|
@@ -146,6 +154,8 @@ export declare class CatalogService {
|
|
|
146
154
|
concurrency?: number;
|
|
147
155
|
/** The discovery route recovery records name. Default `search_tools`. */
|
|
148
156
|
searchRoute?: SearchRoute;
|
|
157
|
+
/** Runtime-owned tail for stale-while-revalidate catalog reads. */
|
|
158
|
+
defer?: DeferredWork;
|
|
149
159
|
});
|
|
150
160
|
/**
|
|
151
161
|
* Send a caller back to discovery through the surface it can actually reach.
|
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);
|
|
@@ -167,6 +201,7 @@ export class CatalogService {
|
|
|
167
201
|
probeTimeoutMs;
|
|
168
202
|
concurrency;
|
|
169
203
|
searchRoute;
|
|
204
|
+
readOptions;
|
|
170
205
|
loaded = new Map();
|
|
171
206
|
loading = new Map();
|
|
172
207
|
constructor(registry, baseUrl, options = {}) {
|
|
@@ -177,6 +212,12 @@ export class CatalogService {
|
|
|
177
212
|
normalizeTimeoutMs(options.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
178
213
|
this.concurrency = resolveDiscoveryConcurrency(options.concurrency);
|
|
179
214
|
this.searchRoute = options.searchRoute ?? "search_tools";
|
|
215
|
+
this.readOptions = options.defer
|
|
216
|
+
? {
|
|
217
|
+
defer: options.defer,
|
|
218
|
+
refreshTimeoutMs: this.probeTimeoutMs,
|
|
219
|
+
}
|
|
220
|
+
: undefined;
|
|
180
221
|
}
|
|
181
222
|
/**
|
|
182
223
|
* Send a caller back to discovery through the surface it can actually reach.
|
|
@@ -207,7 +248,7 @@ export class CatalogService {
|
|
|
207
248
|
if (inFlight)
|
|
208
249
|
return inFlight;
|
|
209
250
|
const loading = this.registry
|
|
210
|
-
.getTools(id, this.baseUrl, this.requestScope, callOptions)
|
|
251
|
+
.getTools(id, this.baseUrl, this.requestScope, callOptions, this.readOptions)
|
|
211
252
|
.then((tools) => {
|
|
212
253
|
this.loaded.set(id, tools);
|
|
213
254
|
return tools;
|
|
@@ -727,17 +768,48 @@ export class CatalogService {
|
|
|
727
768
|
});
|
|
728
769
|
return resolved.map(({ address, resolved: addressResolution }) => {
|
|
729
770
|
if (!addressResolution) {
|
|
730
|
-
|
|
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
|
+
};
|
|
731
780
|
}
|
|
732
781
|
const catalog = catalogs.get(addressResolution.connector.id);
|
|
733
782
|
if (catalog instanceof Error) {
|
|
734
|
-
|
|
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
|
+
};
|
|
735
797
|
}
|
|
736
798
|
const tool = catalog?.find((item) => item.name === addressResolution.toolName);
|
|
737
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 ?? []);
|
|
738
802
|
return {
|
|
739
|
-
address,
|
|
740
|
-
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
|
+
},
|
|
741
813
|
};
|
|
742
814
|
}
|
|
743
815
|
const input = tool.inputSchema ?? { type: "object" };
|
package/dist/catalog.js
CHANGED
|
@@ -2,6 +2,7 @@ const DEFAULT_DESCRIPTION_LENGTH = 240;
|
|
|
2
2
|
const DISCOVERY_DESCRIPTION_LENGTH = 160;
|
|
3
3
|
export const MAX_COMPACT_DISCOVERY_SCHEMA_BYTES = 1_024;
|
|
4
4
|
const MAX_COMPACT_DISCOVERY_ENUM_BYTES = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES / 4;
|
|
5
|
+
const MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES / 4;
|
|
5
6
|
const schemaEncoder = new TextEncoder();
|
|
6
7
|
const COMPACT_DISCOVERY_TRUNCATION = " /* truncated */";
|
|
7
8
|
export function summarizeDescription(text, full) {
|
|
@@ -323,7 +324,8 @@ function declaresShape(s) {
|
|
|
323
324
|
s.const !== undefined ||
|
|
324
325
|
s.items !== undefined ||
|
|
325
326
|
s.properties !== undefined ||
|
|
326
|
-
s.type !== undefined
|
|
327
|
+
s.type !== undefined ||
|
|
328
|
+
constraintEntries(s).length > 0);
|
|
327
329
|
}
|
|
328
330
|
/**
|
|
329
331
|
* Parenthesize a top-level union so it doesn't read as part of a surrounding
|
|
@@ -366,6 +368,56 @@ function renderEnum(values, byteLimit, onTruncated) {
|
|
|
366
368
|
}
|
|
367
369
|
return rendered;
|
|
368
370
|
}
|
|
371
|
+
function safeConstraintValue(value) {
|
|
372
|
+
return JSON.stringify(value).replaceAll("*/", "*\\/");
|
|
373
|
+
}
|
|
374
|
+
function constraintEntries(schema) {
|
|
375
|
+
const entries = [];
|
|
376
|
+
const number = (keyword, label) => {
|
|
377
|
+
const value = schema[keyword];
|
|
378
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
379
|
+
entries.push(`${label} ${value}`);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
const integer = (keyword, label) => {
|
|
383
|
+
const value = schema[keyword];
|
|
384
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
|
|
385
|
+
entries.push(`${label} ${value}`);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
number("minimum", ">=");
|
|
389
|
+
number("exclusiveMinimum", ">");
|
|
390
|
+
number("maximum", "<=");
|
|
391
|
+
number("exclusiveMaximum", "<");
|
|
392
|
+
number("multipleOf", "multiple of");
|
|
393
|
+
integer("minLength", "length >=");
|
|
394
|
+
integer("maxLength", "length <=");
|
|
395
|
+
if (typeof schema.format === "string") {
|
|
396
|
+
entries.push(`format ${safeConstraintValue(schema.format)}`);
|
|
397
|
+
}
|
|
398
|
+
if (typeof schema.pattern === "string") {
|
|
399
|
+
entries.push(`pattern ${safeConstraintValue(schema.pattern)}`);
|
|
400
|
+
}
|
|
401
|
+
return entries;
|
|
402
|
+
}
|
|
403
|
+
function renderConstraints(base, schema, byteLimit, onTruncated) {
|
|
404
|
+
const entries = constraintEntries(schema);
|
|
405
|
+
if (entries.length === 0)
|
|
406
|
+
return base;
|
|
407
|
+
const kept = [];
|
|
408
|
+
for (const entry of entries) {
|
|
409
|
+
const candidate = ` /* ${[...kept, entry].join("; ")} */`;
|
|
410
|
+
if (byteLimit !== undefined &&
|
|
411
|
+
schemaEncoder.encode(candidate).length > byteLimit) {
|
|
412
|
+
onTruncated?.();
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
kept.push(entry);
|
|
416
|
+
}
|
|
417
|
+
return kept.length === 0
|
|
418
|
+
? base
|
|
419
|
+
: `${grouped(base)} /* ${kept.join("; ")} */`;
|
|
420
|
+
}
|
|
369
421
|
function renderSchema(schema, defs, seen, depth, options) {
|
|
370
422
|
if (depth > 4)
|
|
371
423
|
return "…";
|
|
@@ -405,24 +457,36 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
405
457
|
seen.add(name);
|
|
406
458
|
const rendered = renderSchema(target, defs, seen, depth, options);
|
|
407
459
|
seen.delete(name);
|
|
408
|
-
return
|
|
460
|
+
return options.renderConstraints
|
|
461
|
+
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
462
|
+
: rendered;
|
|
409
463
|
}
|
|
410
464
|
const union = (s.oneOf ?? s.anyOf);
|
|
411
465
|
if (Array.isArray(union)) {
|
|
412
|
-
|
|
466
|
+
const rendered = union
|
|
413
467
|
.map((u) => renderSchema(u, defs, seen, depth + 1, options))
|
|
414
468
|
.join(" | ") ||
|
|
415
|
-
"unknown"
|
|
469
|
+
"unknown";
|
|
470
|
+
return options.renderConstraints
|
|
471
|
+
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
472
|
+
: rendered;
|
|
416
473
|
}
|
|
417
474
|
if (Array.isArray(s.enum)) {
|
|
418
|
-
|
|
475
|
+
const rendered = renderEnum(s.enum, options.enumByteLimit, options.onEnumTruncated);
|
|
476
|
+
return options.renderConstraints
|
|
477
|
+
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
478
|
+
: rendered;
|
|
419
479
|
}
|
|
420
480
|
// Checked before type/properties so a discriminator like
|
|
421
481
|
// { type: "string", const: "emoji" } renders as "emoji" rather than string.
|
|
422
482
|
// JSON.stringify(undefined) returns undefined (not a string), so an explicit
|
|
423
483
|
// `const: undefined` must fall through to the regular type rendering.
|
|
424
|
-
if (s.const !== undefined)
|
|
425
|
-
|
|
484
|
+
if (s.const !== undefined) {
|
|
485
|
+
const rendered = JSON.stringify(s.const);
|
|
486
|
+
return options.renderConstraints
|
|
487
|
+
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
488
|
+
: rendered;
|
|
489
|
+
}
|
|
426
490
|
const type = s.type;
|
|
427
491
|
if (type === "array" || s.items) {
|
|
428
492
|
const items = s.items
|
|
@@ -454,10 +518,20 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
454
518
|
})
|
|
455
519
|
.join(", ")} }`;
|
|
456
520
|
}
|
|
457
|
-
if (typeof type === "string")
|
|
458
|
-
return
|
|
459
|
-
|
|
460
|
-
|
|
521
|
+
if (typeof type === "string") {
|
|
522
|
+
return options.renderConstraints
|
|
523
|
+
? renderConstraints(type, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
524
|
+
: type;
|
|
525
|
+
}
|
|
526
|
+
if (Array.isArray(type)) {
|
|
527
|
+
const rendered = type.join(" | ");
|
|
528
|
+
return options.renderConstraints
|
|
529
|
+
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
530
|
+
: rendered;
|
|
531
|
+
}
|
|
532
|
+
if (options.renderConstraints && constraintEntries(s).length > 0) {
|
|
533
|
+
return renderConstraints("unknown", s, options.constraintByteLimit, options.onConstraintTruncated);
|
|
534
|
+
}
|
|
461
535
|
return JSON.stringify(schema);
|
|
462
536
|
}
|
|
463
537
|
const compactSchemas = new WeakMap();
|
|
@@ -475,6 +549,7 @@ export function compactSchema(schema) {
|
|
|
475
549
|
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
476
550
|
propertyDescriptions: true,
|
|
477
551
|
requiredFirst: false,
|
|
552
|
+
renderConstraints: true,
|
|
478
553
|
});
|
|
479
554
|
}
|
|
480
555
|
catch {
|
|
@@ -534,6 +609,7 @@ export function compactDiscoverySchema(schema) {
|
|
|
534
609
|
};
|
|
535
610
|
let rendered;
|
|
536
611
|
let enumTruncated = false;
|
|
612
|
+
let constraintTruncated = false;
|
|
537
613
|
try {
|
|
538
614
|
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
539
615
|
propertyDescriptions: false,
|
|
@@ -545,15 +621,41 @@ export function compactDiscoverySchema(schema) {
|
|
|
545
621
|
onEnumTruncated: () => {
|
|
546
622
|
enumTruncated = true;
|
|
547
623
|
},
|
|
624
|
+
renderConstraints: true,
|
|
625
|
+
constraintByteLimit: MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES,
|
|
626
|
+
onConstraintTruncated: () => {
|
|
627
|
+
constraintTruncated = true;
|
|
628
|
+
},
|
|
548
629
|
});
|
|
549
630
|
}
|
|
550
631
|
catch {
|
|
551
632
|
rendered = JSON.stringify(schema);
|
|
552
633
|
}
|
|
634
|
+
if (schemaEncoder.encode(rendered).length >
|
|
635
|
+
MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
|
|
636
|
+
try {
|
|
637
|
+
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
638
|
+
propertyDescriptions: false,
|
|
639
|
+
requiredFirst: true,
|
|
640
|
+
enumByteLimit: MAX_COMPACT_DISCOVERY_ENUM_BYTES,
|
|
641
|
+
onEnumTruncated: () => {
|
|
642
|
+
enumTruncated = true;
|
|
643
|
+
},
|
|
644
|
+
renderConstraints: false,
|
|
645
|
+
});
|
|
646
|
+
constraintTruncated = true;
|
|
647
|
+
}
|
|
648
|
+
catch {
|
|
649
|
+
rendered = JSON.stringify(schema);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
553
652
|
const bytes = schemaEncoder.encode(rendered);
|
|
554
653
|
let result;
|
|
555
654
|
if (bytes.length <= MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
|
|
556
|
-
result = {
|
|
655
|
+
result = {
|
|
656
|
+
text: rendered,
|
|
657
|
+
truncated: enumTruncated || constraintTruncated,
|
|
658
|
+
};
|
|
557
659
|
}
|
|
558
660
|
else {
|
|
559
661
|
result = {
|
package/dist/errors.d.ts
CHANGED
|
@@ -11,12 +11,10 @@ export type ConnectorCallErrorCode = "timeout" | "auth_required" | "rate_limited
|
|
|
11
11
|
* Its own code because the next move is none of the others': not a retry,
|
|
12
12
|
* not `authorize_connector`, not a reshaped argument object, but
|
|
13
13
|
* re-addressing — look the identifier up again, or accept the absence and
|
|
14
|
-
* carry on. Inside `execute_code` a program reads that difference
|
|
15
|
-
* `connecta.batch` entry's `errorDetails.code` —
|
|
16
|
-
* abort on `connector_call_failed
|
|
17
|
-
*
|
|
18
|
-
* bridge reduces a rejected host call to `new Error(message)` and drops
|
|
19
|
-
* every own property, and message prose cannot be classified.
|
|
14
|
+
* carry on. Inside `execute_code` a program reads that difference from a
|
|
15
|
+
* caught error's `code` or a `connecta.batch` entry's `errorDetails.code` —
|
|
16
|
+
* continue past this one, abort on `connector_call_failed`. Message prose
|
|
17
|
+
* cannot be classified.
|
|
20
18
|
*
|
|
21
19
|
* Use it only where the provider distinguishes absence from a permission
|
|
22
20
|
* gap. A status that means both "it is not there" and "you cannot see it" —
|
package/dist/execute.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
import type { ActivityRequestContext } from "./activity.js";
|
|
3
|
+
import type { DeferredWork } from "./connector-scope.js";
|
|
3
4
|
import { type ToolResult } from "./meta-tools.js";
|
|
4
5
|
import { InvocationFailure } from "./invocation.js";
|
|
5
6
|
import type { RegistryView } from "./registry.js";
|
|
6
7
|
import type { Executor, ExecutorProvider, Logger } from "./types.js";
|
|
7
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;
|
|
8
11
|
/**
|
|
9
12
|
* Default budgets for `connecta.emit`. The byte budget is a transport bound,
|
|
10
13
|
* not a context bound — emitted image/audio blocks reach the model as media,
|
|
@@ -157,6 +160,8 @@ export declare function buildSandboxProviders(registry: RegistryView, baseUrl: s
|
|
|
157
160
|
* blocks nobody will ever return.
|
|
158
161
|
*/
|
|
159
162
|
emitCollector?: EmitCollector;
|
|
163
|
+
/** Runtime-owned tail for stale catalog refreshes. */
|
|
164
|
+
defer?: DeferredWork;
|
|
160
165
|
}): Promise<ExecutorProvider[]>;
|
|
161
166
|
/** The execute_code handler. Exported for direct testing. */
|
|
162
167
|
export declare function createExecuteTool(registry: RegistryView, baseUrl: string, executor: Executor, logger: Logger, activity?: ActivityRequestContext, config?: {
|
|
@@ -164,6 +169,7 @@ export declare function createExecuteTool(registry: RegistryView, baseUrl: strin
|
|
|
164
169
|
probeTimeoutMs?: number;
|
|
165
170
|
maxEmittedBytes?: number;
|
|
166
171
|
maxEmittedBlocks?: number;
|
|
172
|
+
defer?: DeferredWork;
|
|
167
173
|
}): ({ code, diagnostics: diagnosticsRequested }: {
|
|
168
174
|
code: string;
|
|
169
175
|
diagnostics?: boolean;
|
|
@@ -189,5 +195,6 @@ export declare function registerExecuteTool(server: McpServer, registry: Registr
|
|
|
189
195
|
maxEmittedBytes?: number;
|
|
190
196
|
/** Block-count budget for connecta.emit. Default 32. */
|
|
191
197
|
maxEmittedBlocks?: number;
|
|
198
|
+
defer?: DeferredWork;
|
|
192
199
|
}): void;
|
|
193
200
|
export {};
|