@zackbart/connecta 0.16.0 → 0.17.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 +12 -5
- package/CHANGELOG.md +289 -0
- package/README.md +6 -1
- package/dist/catalog-service.d.ts +4 -0
- package/dist/catalog-service.js +49 -5
- package/dist/catalog.d.ts +11 -0
- package/dist/catalog.js +134 -12
- package/dist/errors.d.ts +28 -2
- package/dist/errors.js +1 -0
- package/dist/execute.d.ts +5 -0
- package/dist/execute.js +229 -161
- package/dist/invocation.js +3 -1
- package/dist/meta-tools.d.ts +4 -0
- package/dist/meta-tools.js +46 -14
- 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/cloudflare.js +13 -25
- package/dist/providers/mixpanel.d.ts +3 -5
- package/dist/providers/mixpanel.js +73 -5
- package/dist/providers/stripe.d.ts +2 -2
- package/dist/providers/stripe.js +13 -11
- package/dist/registry.d.ts +32 -9
- package/dist/registry.js +217 -33
- package/dist/routes/mcp.js +6 -0
- package/dist/skills.d.ts +4 -0
- package/dist/skills.js +157 -18
- 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 +7 -4
- package/documentation/cloudflare.md +40 -8
- package/documentation/code-first-exploration.md +2 -2
- package/documentation/code-mode.md +45 -53
- package/documentation/connector-guides.md +24 -19
- package/documentation/connectors.md +13 -1
- package/documentation/meta-tools.md +33 -18
- package/documentation/mixpanel.md +20 -0
- package/documentation/notion.md +7 -2
- package/documentation/operations.md +74 -29
- package/documentation/operator-ui.md +12 -2
- package/documentation/provider-audit.md +4 -4
- package/documentation/provider-conventions.md +68 -19
- package/documentation/stripe.md +45 -14
- package/documentation/upgrading.md +478 -0
- package/ethos.md +4 -4
- package/examples/worker/README.md +13 -6
- package/package.json +7 -2
- package/templates/node/AGENTS.md +5 -0
- package/templates/node/package.json +1 -1
package/dist/routes/mcp.js
CHANGED
|
@@ -234,6 +234,9 @@ async function serveMcp(request, opts, baseUrl, actor, registry, runtimeContext)
|
|
|
234
234
|
? { discoveryConcurrency: opts.discoveryConcurrency }
|
|
235
235
|
: {}),
|
|
236
236
|
requestSignal: request.signal,
|
|
237
|
+
...(runtimeContext?.waitUntil
|
|
238
|
+
? { defer: runtimeContext.waitUntil.bind(runtimeContext) }
|
|
239
|
+
: {}),
|
|
237
240
|
});
|
|
238
241
|
registerExecuteTool(server, registry, {
|
|
239
242
|
baseUrl,
|
|
@@ -241,6 +244,9 @@ async function serveMcp(request, opts, baseUrl, actor, registry, runtimeContext)
|
|
|
241
244
|
logger: opts.logger,
|
|
242
245
|
...(activity ? { activity } : {}),
|
|
243
246
|
requestSignal: request.signal,
|
|
247
|
+
...(runtimeContext?.waitUntil
|
|
248
|
+
? { defer: runtimeContext.waitUntil.bind(runtimeContext) }
|
|
249
|
+
: {}),
|
|
244
250
|
...(opts.discoveryConcurrency !== undefined
|
|
245
251
|
? { discoveryConcurrency: opts.discoveryConcurrency }
|
|
246
252
|
: {}),
|
package/dist/skills.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare function hasConnectorGuides(connectors: readonly Connector[]): bo
|
|
|
10
10
|
export declare function connectorSkillName(connectorId: string): string;
|
|
11
11
|
/** The connector's guide, or undefined when it declares none (or a blank one). */
|
|
12
12
|
export declare function connectorGuide(connector: Connector): string | undefined;
|
|
13
|
+
/** Discovery budget for one connector-guide summary, including an ellipsis. */
|
|
14
|
+
export declare const GUIDE_SUMMARY_LENGTH = 120;
|
|
15
|
+
/** Normalize authored and derived summaries under one construction contract. */
|
|
16
|
+
export declare function normalizeGuideSummary(summary: string): string | undefined;
|
|
13
17
|
/** Bounded, decision-useful discovery summary for a connector guide. */
|
|
14
18
|
export declare function connectorGuideSummary(connector: Connector): string | undefined;
|
|
15
19
|
/** Whether correct use always depends on conventions outside the tool schema. */
|
package/dist/skills.js
CHANGED
|
@@ -17,24 +17,24 @@ Use exact addresses from discovery; never invent one. Search 2–4 distinctive a
|
|
|
17
17
|
|
|
18
18
|
## Inside a program
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Portable code uses only connector globals (\`<connectorId>.<toolName>(args)\`), \`connecta\`, and \`console.*\`. QuickJS blocks imports and lacks fetch/process/timers/crypto/WebSocket. Dynamic Workers require only \`{ loader }\`; bindings/modules/globalOutbound violate it. Then env maps are empty; node:fs/http/https absent; outbound fetch/WebSocket/node:net/tls denied; DNS unresolved. Runtime builtins remain through import() and process.getBuiltinModule(), including node:path and cloudflare:workers; the set can drift. Timers/process/crypto/WebSocket and data: fetch remain. Avoid them; QuickJS fails.
|
|
21
21
|
|
|
22
22
|
- \`connecta.search({})\` loads all catalogs; pass \`connector: "<id>"\` when obvious to load one. \`safety: "readOnly"\` keeps executable calls. Neither grants authority. Matches carry \`address\` and annotations.
|
|
23
23
|
- Exact schemas: \`connecta.describe({ address: "connector.tool" })\` for one, \`{ addresses: [...] }\` for many; \`format: "json"\` only for exact constraints.
|
|
24
|
-
-
|
|
25
|
-
- Search inside the run
|
|
24
|
+
- Caught Connecta errors have \`message\`, \`code\`, \`retryable\`, and \`details\`; branch on fields. For 2–10 independent calls, \`connecta.batch([...])\` returns success data or an \`errorDetails\` whose code and retryable flag match the throw.
|
|
25
|
+
- Search inside the run; return only the reduction the answer needs, never raw payloads.
|
|
26
26
|
- Only tools annotated \`readOnlyHint: true\` are reachable; the gate, credentials, and admission are enforced below the sandbox — nothing a program does widens its reach.
|
|
27
27
|
|
|
28
28
|
## Rendering a view
|
|
29
29
|
|
|
30
|
-
\`connecta.ui(html)\` renders
|
|
30
|
+
\`connecta.ui(html)\` renders one success-only display view, never for the model. Fetch and check the shape first. On empty or missing data, return a trimmed first record instead of rendering. Otherwise render returned variables; the model reads the return value, not the view.
|
|
31
31
|
|
|
32
32
|
`;
|
|
33
33
|
/** Deployment-scoped guide routing appended to the shared usage guide. */
|
|
34
34
|
const CONNECTOR_GUIDES_SECTION = `
|
|
35
35
|
## Per-connector guides
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Connector guides appear in \`skills({})\` and discovery with an exact \`guide\` name and bounded \`guideSummary\`. Fetch only a listed or carried name with \`skills({ name: <guide> })\`; never infer one from a connector id. \`guideRequired: true\` is a hard stop. \`guideRequiredReasons\` says why: \`connector_required\` and \`approval_required\` stand after schema expansion; \`schema_truncated\` clears after exact describe. Otherwise fetch for a relevant sequence, unit, pagination rule, alias, or API convention. A read-only call with a complete compact schema may skip an irrelevant guide. Connector guides never apply to another deployment.
|
|
38
38
|
`;
|
|
39
39
|
/** Shared Connecta routing guidance, byte-identical across deployments. */
|
|
40
40
|
export const USAGE_SKILL = USAGE_SKILL_BASE + CONNECTOR_GUIDES_SECTION;
|
|
@@ -80,17 +80,63 @@ export function connectorGuide(connector) {
|
|
|
80
80
|
const content = typeof guide === "string" ? guide : guide?.content;
|
|
81
81
|
return content && content.trim() !== "" ? content : undefined;
|
|
82
82
|
}
|
|
83
|
-
|
|
83
|
+
/** Discovery budget for one connector-guide summary, including an ellipsis. */
|
|
84
|
+
export const GUIDE_SUMMARY_LENGTH = 120;
|
|
84
85
|
/** A `---`/`***`/`___` rule, which also opens and closes YAML frontmatter. */
|
|
85
86
|
const RULE_RE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
|
|
86
87
|
/** A fenced code block's delimiter. */
|
|
87
88
|
const FENCE_RE = /^\s*(?:```|~~~)/;
|
|
89
|
+
/** Markdown blocks that end a paragraph without a blank physical line. */
|
|
90
|
+
const HEADING_RE = /^\s*#{1,6}/;
|
|
91
|
+
const LIST_ITEM_RE = /^\s*(?:[-*+]\s+|\d+[.)]\s+)/;
|
|
92
|
+
const SETEXT_UNDERLINE_RE = /^\s*=+\s*$/;
|
|
93
|
+
const TABLE_DELIMITER_RE = /^\s*\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?\s*$/;
|
|
94
|
+
const OPENS_CLAUSE_RE = /^(?:The|A|An)\b/u;
|
|
95
|
+
const ARTICLE_RE = /^(?:the|a|an)\b/u;
|
|
88
96
|
/**
|
|
89
97
|
* Markup that carries no summary text of its own: horizontal rules, HTML
|
|
90
98
|
* comments, and table rows. Skipped so a guide that opens with one is
|
|
91
99
|
* summarized by its first real line instead of by punctuation.
|
|
92
100
|
*/
|
|
93
101
|
const NOT_SUMMARY_RE = /^\s*(?:<!--|\|)|^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
|
|
102
|
+
/** A standard Markdown table starts with a pipe-bearing row and delimiter. */
|
|
103
|
+
function startsTable(lines, index) {
|
|
104
|
+
const header = lines[index] ?? "";
|
|
105
|
+
const delimiter = lines[index + 1] ?? "";
|
|
106
|
+
return header.includes("|") && TABLE_DELIMITER_RE.test(delimiter);
|
|
107
|
+
}
|
|
108
|
+
/** True when a sentence-looking period belongs to an abbreviation. */
|
|
109
|
+
function isAbbreviation(text, end) {
|
|
110
|
+
const token = text.slice(0, end).match(/\S+$/u)?.[0] ?? "";
|
|
111
|
+
// These introduce an example or restatement even before a capitalized word.
|
|
112
|
+
if (/^(?:e\.g|i\.e)\.$/iu.test(token))
|
|
113
|
+
return true;
|
|
114
|
+
// An initial or title belongs to the proper name that follows it.
|
|
115
|
+
if (/^[A-Z]\.$/u.test(token))
|
|
116
|
+
return true;
|
|
117
|
+
if (/^(?:Mr|Mrs|Ms|Dr|Prof|Sr|Jr|St)\.$/iu.test(token))
|
|
118
|
+
return true;
|
|
119
|
+
if (!/^(?:[A-Za-z]\.){2,}$/u.test(token) &&
|
|
120
|
+
!/^(?:vs|etc|approx|dept|fig|no)\.$/iu.test(token)) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
// Initialisms can end a sentence or extend a name ("U.S. East region").
|
|
124
|
+
// The mistakes are asymmetric: a false ending presents a fragment as a
|
|
125
|
+
// complete thought, while a missed ending gets an honest ellipsis. Count
|
|
126
|
+
// the period only with narrow evidence of a new clause: an article in one
|
|
127
|
+
// of its first two words. This is grammar evidence, not a starter-word list.
|
|
128
|
+
const following = text
|
|
129
|
+
.slice(end)
|
|
130
|
+
.match(/^[)\]}'"”’]*\s+(\S+)(?:\s+(\S+))?/u);
|
|
131
|
+
if (!following)
|
|
132
|
+
return false;
|
|
133
|
+
const [, nextWord, afterNext] = following;
|
|
134
|
+
const startsClause = nextWord !== undefined &&
|
|
135
|
+
/^\p{Lu}/u.test(nextWord) &&
|
|
136
|
+
(OPENS_CLAUSE_RE.test(nextWord) ||
|
|
137
|
+
(afterNext !== undefined && ARTICLE_RE.test(afterNext)));
|
|
138
|
+
return !startsClause;
|
|
139
|
+
}
|
|
94
140
|
/** Drop a leading YAML frontmatter block — metadata, not summary text. */
|
|
95
141
|
function withoutFrontmatter(lines) {
|
|
96
142
|
let start = 0;
|
|
@@ -102,33 +148,107 @@ function withoutFrontmatter(lines) {
|
|
|
102
148
|
const close = lines.findIndex((line, i) => i > start && RULE_RE.test(line));
|
|
103
149
|
return close === -1 ? lines : lines.slice(close + 1);
|
|
104
150
|
}
|
|
151
|
+
/** Normalize authored and derived summaries under one construction contract. */
|
|
152
|
+
export function normalizeGuideSummary(summary) {
|
|
153
|
+
const normalized = summary.replace(/\s+/g, " ").trim();
|
|
154
|
+
return normalized === "" ? undefined : normalized;
|
|
155
|
+
}
|
|
105
156
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* markup.
|
|
157
|
+
* Shorten a normalized summary at the strongest readable boundary available.
|
|
158
|
+
* A complete sentence needs no ellipsis; clause and word cuts do, so discovery
|
|
159
|
+
* never presents an unfinished fragment as the guide's complete thought.
|
|
110
160
|
*/
|
|
111
161
|
function boundedSummary(summary) {
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
162
|
+
const normalized = normalizeGuideSummary(summary);
|
|
163
|
+
if (!normalized)
|
|
114
164
|
return undefined;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
165
|
+
if (normalized.length <= GUIDE_SUMMARY_LENGTH)
|
|
166
|
+
return normalized;
|
|
167
|
+
const contentBudget = GUIDE_SUMMARY_LENGTH - 1;
|
|
168
|
+
let sentenceEnd = 0;
|
|
169
|
+
const sentenceBoundary = /[.!?…。!?](?:[)\]}'"”’]+)?(?=\s|$)/gu;
|
|
170
|
+
for (const match of normalized.matchAll(sentenceBoundary)) {
|
|
171
|
+
const end = (match.index ?? 0) + match[0].length;
|
|
172
|
+
if (end > GUIDE_SUMMARY_LENGTH)
|
|
173
|
+
break;
|
|
174
|
+
const punctuationEnd = (match.index ?? 0) + 1;
|
|
175
|
+
if (match[0].startsWith(".") &&
|
|
176
|
+
isAbbreviation(normalized, punctuationEnd)) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
// Do not mistake another short fragment for a useful complete thought.
|
|
180
|
+
if (end >= 24)
|
|
181
|
+
sentenceEnd = end;
|
|
182
|
+
}
|
|
183
|
+
if (sentenceEnd > 0)
|
|
184
|
+
return normalized.slice(0, sentenceEnd);
|
|
185
|
+
const available = normalized.slice(0, contentBudget);
|
|
186
|
+
let clauseEnd = 0;
|
|
187
|
+
const clauseBoundary = /[,;:](?=\s)|\s[—–-](?=\s)/g;
|
|
188
|
+
for (const match of available.matchAll(clauseBoundary)) {
|
|
189
|
+
const end = match.index ?? 0;
|
|
190
|
+
// Prefer a clause only when it retains most of the discovery budget.
|
|
191
|
+
if (end >= 80)
|
|
192
|
+
clauseEnd = end;
|
|
193
|
+
}
|
|
194
|
+
if (clauseEnd > 0) {
|
|
195
|
+
return `${available.slice(0, clauseEnd).trimEnd()}…`;
|
|
196
|
+
}
|
|
197
|
+
const wordEnd = available.search(/\s+\S*$/);
|
|
198
|
+
if (wordEnd > 0) {
|
|
199
|
+
const prefix = available
|
|
200
|
+
.slice(0, wordEnd)
|
|
201
|
+
.trimEnd()
|
|
202
|
+
.replace(/[,;:([{—–-]+$/u, "")
|
|
203
|
+
.trimEnd();
|
|
204
|
+
if (prefix !== "")
|
|
205
|
+
return `${prefix}…`;
|
|
206
|
+
}
|
|
207
|
+
let hardEnd = contentBudget;
|
|
208
|
+
const code = normalized.charCodeAt(hardEnd - 1);
|
|
209
|
+
if (code >= 0xd800 && code <= 0xdbff)
|
|
210
|
+
hardEnd--;
|
|
211
|
+
return `${normalized.slice(0, hardEnd)}…`;
|
|
118
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* One thought describing a guide for the cheap list view: the first meaningful
|
|
215
|
+
* paragraph, joined across physical lines, with headings and the connector
|
|
216
|
+
* description as fallbacks when the guide opens with markup alone.
|
|
217
|
+
*/
|
|
119
218
|
function summarizeGuide(connector, guide) {
|
|
219
|
+
const lines = withoutFrontmatter(guide.split("\n"));
|
|
120
220
|
let inFence = false;
|
|
221
|
+
let inComment = false;
|
|
121
222
|
let headingFallback;
|
|
122
|
-
for (
|
|
223
|
+
for (let index = 0; index < lines.length; index++) {
|
|
224
|
+
const raw = lines[index] ?? "";
|
|
225
|
+
if (inComment) {
|
|
226
|
+
if (raw.includes("-->"))
|
|
227
|
+
inComment = false;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
123
230
|
if (FENCE_RE.test(raw)) {
|
|
124
231
|
inFence = !inFence;
|
|
125
232
|
continue;
|
|
126
233
|
}
|
|
127
234
|
if (inFence)
|
|
128
235
|
continue;
|
|
236
|
+
if (raw.trimStart().startsWith("<!--")) {
|
|
237
|
+
if (!raw.includes("-->"))
|
|
238
|
+
inComment = true;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (startsTable(lines, index)) {
|
|
242
|
+
index++;
|
|
243
|
+
while (index + 1 < lines.length &&
|
|
244
|
+
(lines[index + 1] ?? "").includes("|")) {
|
|
245
|
+
index++;
|
|
246
|
+
}
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
129
249
|
if (raw.trim() === "" || NOT_SUMMARY_RE.test(raw))
|
|
130
250
|
continue;
|
|
131
|
-
const heading =
|
|
251
|
+
const heading = HEADING_RE.test(raw);
|
|
132
252
|
const line = raw
|
|
133
253
|
// `\s*` (not `\s+`) so a bare `#` strips to nothing and is skipped, and
|
|
134
254
|
// an unspaced `#Heading` is still read as a heading.
|
|
@@ -142,7 +262,24 @@ function summarizeGuide(connector, guide) {
|
|
|
142
262
|
headingFallback ??= boundedSummary(line);
|
|
143
263
|
continue;
|
|
144
264
|
}
|
|
145
|
-
|
|
265
|
+
const paragraph = [line];
|
|
266
|
+
while (index + 1 < lines.length) {
|
|
267
|
+
const next = lines[index + 1] ?? "";
|
|
268
|
+
if (next.trim() === "" ||
|
|
269
|
+
startsTable(lines, index + 1) ||
|
|
270
|
+
FENCE_RE.test(next) ||
|
|
271
|
+
HEADING_RE.test(next) ||
|
|
272
|
+
LIST_ITEM_RE.test(next) ||
|
|
273
|
+
SETEXT_UNDERLINE_RE.test(next) ||
|
|
274
|
+
NOT_SUMMARY_RE.test(next)) {
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
paragraph.push(next.trim());
|
|
278
|
+
index++;
|
|
279
|
+
if (paragraph.join(" ").length > GUIDE_SUMMARY_LENGTH)
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
return boundedSummary(paragraph.join(" ")) ?? line;
|
|
146
283
|
}
|
|
147
284
|
if (headingFallback)
|
|
148
285
|
return headingFallback;
|
|
@@ -155,6 +292,8 @@ export function connectorGuideSummary(connector) {
|
|
|
155
292
|
if (!guide)
|
|
156
293
|
return undefined;
|
|
157
294
|
const configured = typeof connector.usageGuide === "object"
|
|
295
|
+
// Registry construction rejects over-budget configured summaries. Keep
|
|
296
|
+
// normalization here so direct Connector callers see the same text.
|
|
158
297
|
? boundedSummary(connector.usageGuide.summary ?? "")
|
|
159
298
|
: undefined;
|
|
160
299
|
return configured ?? summarizeGuide(connector, guide);
|
package/dist/types.d.ts
CHANGED
|
@@ -180,6 +180,12 @@ export interface CatalogDriftReport extends CatalogDriftCounts {
|
|
|
180
180
|
/** When the observation was taken; never when a probe was scheduled. */
|
|
181
181
|
observedAt: string;
|
|
182
182
|
}
|
|
183
|
+
/** The last agent-facing catalog read this runtime served for one connector. */
|
|
184
|
+
export interface CatalogAccessObservation {
|
|
185
|
+
/** Fresh means the read used a live or unexpired catalog; stale means SWR. */
|
|
186
|
+
state: "fresh" | "stale";
|
|
187
|
+
observedAt: string;
|
|
188
|
+
}
|
|
183
189
|
export interface ConnectorStatus {
|
|
184
190
|
state: ConnectorStatusState;
|
|
185
191
|
/** When state === "auth_required", the URL the operator should open. */
|
|
@@ -193,6 +199,11 @@ export interface ConnectorStatus {
|
|
|
193
199
|
* isolate or process has seen nothing, not that nothing drifted.
|
|
194
200
|
*/
|
|
195
201
|
catalogDrift?: CatalogDriftReport;
|
|
202
|
+
/**
|
|
203
|
+
* The last agent-facing catalog read in this runtime. This payload-free
|
|
204
|
+
* observation is not persisted and operator reads do not replace it.
|
|
205
|
+
*/
|
|
206
|
+
catalogAccess?: CatalogAccessObservation;
|
|
196
207
|
}
|
|
197
208
|
/** The whole plugin contract — the one open seam. */
|
|
198
209
|
export interface Connector {
|
|
@@ -313,8 +324,9 @@ export interface ConnectorUsageGuide {
|
|
|
313
324
|
/** Markdown returned verbatim by `skills({ name: "connector:<id>" })`. */
|
|
314
325
|
content: string;
|
|
315
326
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
327
|
+
* Discovery hint describing the conventions the guide covers. Whitespace is
|
|
328
|
+
* normalized, and a value over 120 characters throws at construction. When
|
|
329
|
+
* omitted, Connecta derives a bounded summary from the first body paragraph.
|
|
318
330
|
*/
|
|
319
331
|
summary?: string;
|
|
320
332
|
/**
|
package/dist/ui.js
CHANGED
|
@@ -374,6 +374,9 @@ export async function buildUiData(registry, baseUrl, serverInfo, credentialVault
|
|
|
374
374
|
// `boundedCatalogDrift`, so what lands here cannot carry a name or a
|
|
375
375
|
// schema even if the plugin seam returned one.
|
|
376
376
|
...(status.catalogDrift ? { catalogDrift: status.catalogDrift } : {}),
|
|
377
|
+
...(status.catalogAccess
|
|
378
|
+
? { catalogAccess: status.catalogAccess }
|
|
379
|
+
: {}),
|
|
377
380
|
...(c.disconnectAuth && c.startAuth ? { oauth: true } : {}),
|
|
378
381
|
...(credential ? { credential } : {}),
|
|
379
382
|
};
|
|
@@ -461,7 +464,7 @@ export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding, nonce, page = "c
|
|
|
461
464
|
: `<span class="product">${escapeHtmlAttr(brand.productName)}</span>`
|
|
462
465
|
: "";
|
|
463
466
|
const clerkScript = clerk && clerkScriptOrigin
|
|
464
|
-
? `<script${nonceAttr}
|
|
467
|
+
? `<script${nonceAttr} crossorigin="anonymous" data-clerk-publishable-key="${escapeHtmlAttr(clerk.publishableKey)}" src="${escapeHtmlAttr(clerkScriptOrigin)}/npm/@clerk/clerk-js@6/dist/clerk.browser.js"></script>`
|
|
465
468
|
: "";
|
|
466
469
|
return `<!doctype html>
|
|
467
470
|
<html lang="en">
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -26,7 +26,10 @@ ethos invariant, not a style preference: a client retained across requests on
|
|
|
26
26
|
Workers is a cross-request capability leak, and a promise awaited after the
|
|
27
27
|
response is work the runtime may have already torn down. Deferred work has one
|
|
28
28
|
sanctioned channel — `ctx.waitUntil`, threaded through `fetch(request, env,
|
|
29
|
-
ctx)
|
|
29
|
+
ctx)`. Best-effort activity writes use it. An agent read that already demanded
|
|
30
|
+
an expired catalog refresh may also use it while serving a complete catalog
|
|
31
|
+
inside its stale window. That refresh owns a new scope and deadline; it never
|
|
32
|
+
carries the inbound scope or signal past the request.
|
|
30
33
|
|
|
31
34
|
The registry is deliberately on the long side of that line and the MCP server
|
|
32
35
|
deliberately on the short side. A fresh `McpServer` per request is what makes
|
|
@@ -87,8 +90,8 @@ owns or hands out, and a change usually belongs in exactly one of them:
|
|
|
87
90
|
|
|
88
91
|
| Module | Owns |
|
|
89
92
|
| --- | --- |
|
|
90
|
-
| `src/registry.ts` | The connector set,
|
|
91
|
-
| `src/catalog-service.ts` |
|
|
93
|
+
| `src/registry.ts` | The connector set, address resolution, catalog TTL/persistence/completeness, shared refresh single-flight, connector health, per-connector call limiters, and drift. Construction-time refusals live here. |
|
|
94
|
+
| `src/catalog-service.ts` | Request-local tool listing, search, and describe. It coalesces reads inside one request and opts agent reads into the runtime's deferred catalog channel when one exists. |
|
|
92
95
|
| `src/invocation.ts` | One tool call: argument validation, call admission, per-attempt timeout, retry with the connector's own `Retry-After` honoured exactly or declined, result unwrapping, size capping, and the activity record. |
|
|
93
96
|
| `src/catalog.ts` | Ranking, description summarizing, and the compact schema renderer discovery shows. |
|
|
94
97
|
|
|
@@ -137,7 +140,7 @@ src/
|
|
|
137
140
|
apps-shell.ts the one build-time MCP Apps template
|
|
138
141
|
skills.ts MCP instructions, the usage skill, connector guides
|
|
139
142
|
registry.ts connector set, addresses, health, call limiters
|
|
140
|
-
catalog-service.ts catalog
|
|
143
|
+
catalog-service.ts request-local catalog access, search, and describe
|
|
141
144
|
catalog.ts ranking, summaries, compact schema rendering
|
|
142
145
|
invocation.ts one tool call, end to end
|
|
143
146
|
catalog-drift.ts vetted manifests and the counts a refresh produces
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Import `cloudflare()` independently from
|
|
4
4
|
`@zackbart/connecta/providers/cloudflare`. It is a deliberate, hand-written
|
|
5
|
-
surface over Cloudflare's v4 REST API. Fifty-
|
|
5
|
+
surface over Cloudflare's v4 REST API. Fifty-one tools combine ergonomic,
|
|
6
6
|
fully described operations for common work with three guarded escape hatches
|
|
7
7
|
for the rest of Cloudflare's fast-moving control plane. Reads, JSON mutations,
|
|
8
8
|
and raw/multipart uploads remain separate so safety routing does not depend on
|
|
@@ -140,7 +140,7 @@ projections:
|
|
|
140
140
|
|
|
141
141
|
| Area | Reads | Writes |
|
|
142
142
|
| --- | --- | --- |
|
|
143
|
-
| Zones | discovery, details,
|
|
143
|
+
| Zones | discovery, details, one setting at a time, rulesets | update a setting |
|
|
144
144
|
| DNS/cache | list and get records | create, update, delete, targeted/full purge |
|
|
145
145
|
| Workers | scripts, settings, deployments | delete a script |
|
|
146
146
|
| KV | namespaces, keys, bulk values | create/rename/delete namespace, bulk write/delete |
|
|
@@ -158,9 +158,10 @@ asserts those properties rather than leaving them as a claim.
|
|
|
158
158
|
A named tool is a permanent line item in every deployment's catalog, so the
|
|
159
159
|
surface was measured against the escape hatches rather than assumed to beat
|
|
160
160
|
them ([#350](https://github.com/zackbart/connecta/issues/350), evidence in
|
|
161
|
-
[`eval/current-version/results/issue-350-evidence.md`](
|
|
162
|
-
Two named tools lost that comparison,
|
|
163
|
-
surface to keep a policy pair on one route
|
|
161
|
+
[`eval/current-version/results/issue-350-evidence.md`](https://github.com/zackbart/connecta/blob/main/eval/current-version/results/issue-350-evidence.md)).
|
|
162
|
+
Two named tools lost that comparison, a third followed one of them off the
|
|
163
|
+
surface to keep a policy pair on one route, and a fourth left because
|
|
164
|
+
Cloudflare deprecated the only endpoint it could call:
|
|
164
165
|
|
|
165
166
|
- **R2 CORS writes.** `set_r2_cors` declared its rule list as free-form objects
|
|
166
167
|
— the untyped body this connection refuses everywhere else — so its schema
|
|
@@ -181,8 +182,31 @@ surface to keep a policy pair on one route:
|
|
|
181
182
|
path, and returned the response untouched — `cloudflare_api_get` at
|
|
182
183
|
`/accounts/{accountId}/r2/metrics` does the same thing without a permanent
|
|
183
184
|
catalog line.
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
- **The bulk zone-settings read.** `list_zone_settings` called
|
|
186
|
+
`GET /zones/{zoneId}/settings`, which Cloudflare's published document now
|
|
187
|
+
marks `deprecated: true` along with its `PATCH` sibling
|
|
188
|
+
([#361](https://github.com/zackbart/connecta/issues/361)). There is no bulk
|
|
189
|
+
replacement to repoint it at: the supported operations are the per-setting
|
|
190
|
+
`GET` and `PATCH /zones/{zoneId}/settings/{settingId}` that `get_zone_setting`
|
|
191
|
+
and `update_zone_setting` already call. Keeping the tool would have meant
|
|
192
|
+
maintaining a name over a contract Cloudflare has announced it intends to
|
|
193
|
+
stop honouring — and #350 had already measured this one as the sharpest
|
|
194
|
+
passthrough on the surface: it took a zone id, projected nothing, and *grew*
|
|
195
|
+
the payload 22.7% by wrapping Cloudflare's settings array in a page object an
|
|
196
|
+
unpaginated endpoint never filled. Read one setting with `get_zone_setting`.
|
|
197
|
+
Where the whole set is genuinely wanted — an audit, a config diff — an
|
|
198
|
+
operator can still ask for it explicitly through `cloudflare_api_get` at
|
|
199
|
+
`/zones/{zoneId}/settings`, which is the honest place for a call whose
|
|
200
|
+
endpoint is on its way out: named by the caller, at the caller's risk, rather
|
|
201
|
+
than promised by connecta's catalog.
|
|
202
|
+
|
|
203
|
+
Because `scripts/drift/cloudflare-endpoints.json` records the endpoints this
|
|
204
|
+
connection *calls*, dropping the tool drops the row — so
|
|
205
|
+
`npm run drift:check -- --specs` is quiet about zone settings by construction
|
|
206
|
+
rather than by a recorded exception. A path reached only through a hatch is
|
|
207
|
+
named by the caller, so it was never a touched endpoint.
|
|
208
|
+
|
|
209
|
+
The surviving 47 named tools all refuse malformed arguments locally, which is
|
|
186
210
|
the one thing no escape hatch can do: a hatch's path is an opaque string, so it
|
|
187
211
|
can only check that a path is a path.
|
|
188
212
|
|
|
@@ -324,9 +348,17 @@ the failure message so the provider's own code number survives to the agent.
|
|
|
324
348
|
| 401 or 403 | `auth_required`, not retryable | Stops and reports which permission is missing |
|
|
325
349
|
| 400 with a credential-shaped code (1001, 6003, 6111, 9103, 9106, 9107) | `auth_required`, not retryable | Stops; the header or key is malformed, not the arguments |
|
|
326
350
|
| 400, 409, 422 | `invalid_args`, not retryable | Repairs the arguments |
|
|
327
|
-
| 404 | `
|
|
351
|
+
| 404 | `not_found`, not retryable | Re-runs discovery for the id |
|
|
328
352
|
| 5xx or a transport error | `unavailable`, retryable | Retries |
|
|
329
353
|
|
|
354
|
+
The 404 row is the unambiguous half of the `not_found` rule
|
|
355
|
+
([H11](./provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next)).
|
|
356
|
+
Cloudflare refuses a token that may not touch a resource with 401 or 403, so a
|
|
357
|
+
404 is an absence rather than a permission gap wearing a miss, and an agent can
|
|
358
|
+
act on it: confirm the zone or account id with `list_zones` or `list_accounts`
|
|
359
|
+
and address the right one. Notion's 404, which proves neither, stays
|
|
360
|
+
`connector_call_failed`.
|
|
361
|
+
|
|
330
362
|
The six credential-shaped codes deserve a caveat: Cloudflare publishes no
|
|
331
363
|
official table mapping error codes to causes, so that set is assembled from
|
|
332
364
|
community reports and probing, not from documentation. The same goes for the
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
> constraint below was dropped.** The owner decided the default directly on
|
|
14
14
|
> 2026-07-30 and [#224](https://github.com/zackbart/connecta/issues/224)
|
|
15
15
|
> shipped it; the ethos records the eval-as-gate as `removed` and
|
|
16
|
-
> [`eval/code-first-gate`](
|
|
16
|
+
> [`eval/code-first-gate`](https://github.com/zackbart/connecta/blob/main/eval/code-first-gate/README.md) continues as
|
|
17
17
|
> measurement that nothing waits on.
|
|
18
18
|
|
|
19
19
|
## Executive summary
|
|
@@ -227,7 +227,7 @@ changing the default surface. Run at least 20 independent samples per task and
|
|
|
227
227
|
model, with prompt variation.
|
|
228
228
|
|
|
229
229
|
That suite was built under
|
|
230
|
-
[`eval/code-first-gate`](
|
|
230
|
+
[`eval/code-first-gate`](https://github.com/zackbart/connecta/blob/main/eval/code-first-gate/README.md). Its recorded results
|
|
231
231
|
remain as measurement history; the runnable comparison was retired when #273
|
|
232
232
|
removed the alternate deployment shapes.
|
|
233
233
|
|