@zackbart/connecta 0.41.0 → 0.42.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 +61 -0
- package/bin/connecta.mjs +26 -17
- package/bin/meta-tool-names.d.mts +3 -1
- package/bin/meta-tool-names.mjs +4 -1
- package/bin/version.mjs +1 -1
- package/dist/activity.d.ts +2 -1
- package/dist/activity.js +5 -5
- package/dist/advertised-schema.js +15 -0
- package/dist/agent-routes.js +31 -5
- package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
- package/dist/auth/downstream-oauth.js +3 -0
- package/dist/auth/static-oauth.js +26 -18
- package/dist/auth-elicitation.js +118 -61
- package/dist/authorization-handoff.js +36 -3
- package/dist/catalog-cache.js +292 -24
- package/dist/catalog-freshness.js +12 -0
- package/dist/catalog-service.js +373 -70
- package/dist/catalog.js +147 -58
- package/dist/config-defaults.d.ts +2 -1
- package/dist/config-defaults.js +4 -1
- package/dist/config.d.ts +43 -17
- package/dist/config.js +26 -10
- package/dist/connectors/remote-mcp/catalog.js +52 -28
- package/dist/connectors/remote-mcp/connection.js +62 -5
- package/dist/connectors/remote-mcp.js +10 -1
- package/dist/describe-config.d.ts +3 -2
- package/dist/discovery-health.js +26 -11
- package/dist/errors.d.ts +52 -3
- package/dist/errors.js +86 -3
- package/dist/execute.js +476 -200
- package/dist/executor-admission.js +2 -0
- package/dist/executor-contract.js +6 -0
- package/dist/executor-result.js +2 -2
- package/dist/executors/quickjs-child.js +13 -4
- package/dist/executors/quickjs-runtime.d.ts +2 -0
- package/dist/executors/quickjs-runtime.js +36 -19
- package/dist/executors/quickjs.js +23 -6
- package/dist/guest-functions.js +5 -4
- package/dist/guest-runtime.js +37 -1
- package/dist/guest-types.d.ts +18 -0
- package/dist/http/client.js +7 -2
- package/dist/http/errors.js +9 -4
- package/dist/http/fields.js +17 -5
- package/dist/http/hatch.js +9 -3
- package/dist/http/paging.js +1 -1
- package/dist/http/provider.js +2 -6
- package/dist/http/vendor.js +6 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -0
- package/dist/invocation.js +98 -34
- package/dist/meta-output.js +24 -18
- package/dist/meta-tool-names.d.ts +1 -1
- package/dist/meta-tool-names.js +1 -1
- package/dist/meta-tools.js +259 -60
- package/dist/native-recovery.js +4 -1
- package/dist/native-tools.js +166 -51
- package/dist/node.js +3 -1
- package/dist/oauth-handoff.js +53 -43
- package/dist/operation-activity.js +2 -1
- package/dist/operation-vocabulary.js +6 -0
- package/dist/operator-record.d.ts +1 -1
- package/dist/operator-record.js +1 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/program-result.js +2 -0
- package/dist/program-source.js +1 -1
- package/dist/program-writes.js +112 -19
- package/dist/provider.d.ts +0 -13
- package/dist/provider.js +0 -3
- package/dist/providers/_shared/google/kit.js +2 -2
- package/dist/providers/breeze/api.js +2 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/ccb/api.js +2 -2
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/cloudflare/api.js +2 -2
- package/dist/providers/cloudflare/index.js +2 -0
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/forms/index.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/github/api.js +98 -6
- package/dist/providers/github/index.js +7 -2
- package/dist/providers/github/lib/auth.d.ts +3 -1
- package/dist/providers/github/lib/auth.js +43 -12
- package/dist/providers/github/lib/discovery.js +11 -1
- package/dist/providers/github/lib/operations.js +8 -0
- package/dist/providers/github/safety.js +39 -0
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +12 -5
- package/dist/providers/gmail/index.js +1 -0
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/index.js +2 -0
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/planning-center/index.js +11 -5
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/quickbooks/index.js +2 -5
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/index.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/slides/index.js +2 -2
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/index.js +1 -0
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/vercel/api.js +2 -2
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/registry.d.ts +36 -4
- package/dist/registry.js +127 -27
- package/dist/result-preview.js +210 -0
- package/dist/result-seal.js +168 -0
- package/dist/route-names.js +13 -4
- package/dist/routes/connect.js +21 -13
- package/dist/routes/mcp.js +55 -26
- package/dist/routes/shared.d.ts +6 -0
- package/dist/runtime/call.js +15 -10
- package/dist/runtime/request-scope.js +36 -1
- package/dist/runtime/run.js +4 -1
- package/dist/sent-secrets.d.ts +20 -0
- package/dist/sent-secrets.js +49 -1
- package/dist/skills.js +100 -33
- package/dist/storage/keys.js +9 -6
- package/dist/storage/sql-native.js +1 -1
- package/dist/tool-safety.d.ts +33 -0
- package/dist/tool-safety.js +51 -15
- package/dist/types.d.ts +31 -0
- package/dist/usage-guide.js +14 -13
- package/dist/validate.js +29 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +28 -8
- package/documentation/architecture.md +104 -42
- package/documentation/auth.md +46 -19
- package/documentation/code-mode.md +378 -116
- package/documentation/deploying.md +10 -9
- package/documentation/hosted-mcp-migration.md +9 -3
- package/documentation/integrating.md +27 -5
- package/documentation/meta-tools.md +655 -153
- package/documentation/operating.md +5 -3
- package/documentation/providers.md +18 -6
- package/examples/worker/README.md +3 -3
- package/guides/how-it-works.md +2 -1
- package/guides/safety-and-trust.md +1 -3
- package/package.json +1 -1
- package/templates/node/AGENTS.md +3 -3
- package/templates/node/README.md +1 -1
- package/templates/node/package.json +1 -1
package/dist/catalog.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const DEFAULT_DESCRIPTION_LENGTH = 240;
|
|
2
1
|
const DISCOVERY_DESCRIPTION_LENGTH = 160;
|
|
2
|
+
const DISCOVERY_DESCRIPTION_MINIMUM = 40;
|
|
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
5
|
const MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES / 4;
|
|
@@ -75,21 +75,46 @@ class SchemaWork {
|
|
|
75
75
|
}));
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
78
|
+
/** A sentence's end, or the paragraph break `discoverySummary` marks with a newline. */
|
|
79
|
+
const SENTENCE_END = /[.!?]["'`)\]]*(?= |\n|$)|\n/g;
|
|
80
|
+
/** A period that follows an initial or a common abbreviation ends no sentence. */
|
|
81
|
+
const ABBREVIATED = /(?:^|[\s(.])(?:e\.g|i\.e|etc|vs|cf|approx|[a-z])\.$/i;
|
|
82
|
+
/**
|
|
83
|
+
* What search shows of a description: the first complete sentence, joined to
|
|
84
|
+
* the next ones until it reaches 40 characters, within 160. Text with no such
|
|
85
|
+
* boundary is cut at a word. `truncated` says text was left out.
|
|
86
|
+
*/
|
|
87
|
+
export function discoverySummary(text) {
|
|
88
|
+
const marked = text
|
|
89
|
+
.trim()
|
|
90
|
+
.split(/\n\s*\n/)
|
|
91
|
+
.map((paragraph) => paragraph.replace(/\s+/g, " ").trim())
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.join("\n");
|
|
94
|
+
const whole = marked.replaceAll("\n", " ");
|
|
95
|
+
let end;
|
|
96
|
+
for (const match of marked.matchAll(SENTENCE_END)) {
|
|
97
|
+
const candidate = match[0] === "\n" ? match.index : match.index + match[0].length;
|
|
98
|
+
if (candidate < DISCOVERY_DESCRIPTION_MINIMUM)
|
|
99
|
+
continue;
|
|
100
|
+
if (candidate > DISCOVERY_DESCRIPTION_LENGTH)
|
|
101
|
+
break;
|
|
102
|
+
if (ABBREVIATED.test(marked.slice(Math.max(0, candidate - 8), candidate)))
|
|
103
|
+
continue;
|
|
104
|
+
end = candidate;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
if (end !== undefined)
|
|
108
|
+
return { text: whole.slice(0, end), truncated: end < whole.length };
|
|
109
|
+
if (whole.length <= DISCOVERY_DESCRIPTION_LENGTH)
|
|
110
|
+
return { text: whole, truncated: false };
|
|
111
|
+
let cut = DISCOVERY_DESCRIPTION_LENGTH - 1;
|
|
112
|
+
const space = whole.lastIndexOf(" ", cut);
|
|
113
|
+
if (space >= DISCOVERY_DESCRIPTION_MINIMUM)
|
|
114
|
+
cut = space;
|
|
115
|
+
else if (/[\uD800-\uDBFF]/.test(whole.charAt(cut - 1)))
|
|
116
|
+
cut -= 1;
|
|
117
|
+
return { text: `${whole.slice(0, cut).trimEnd()}…`, truncated: true };
|
|
93
118
|
}
|
|
94
119
|
function lexicalTokens(text) {
|
|
95
120
|
return text
|
|
@@ -163,6 +188,7 @@ export function lexicalSearchContent(query) {
|
|
|
163
188
|
const contentTerms = terms.filter((term) => !CONVERSATIONAL_QUERY_WORDS.has(term));
|
|
164
189
|
return contentTerms.join(" ");
|
|
165
190
|
}
|
|
191
|
+
const ZONES = ["nameTokens", "titleTokens", "summaryTokens", "tailTokens"];
|
|
166
192
|
const searchIndexes = new WeakMap();
|
|
167
193
|
// Only immutable token strings and numeric positions cross requests. The key
|
|
168
194
|
// holds exactly what buildIndex reads, in order, so schemas cost nothing here;
|
|
@@ -170,36 +196,39 @@ const searchIndexes = new WeakMap();
|
|
|
170
196
|
const retainedIndexes = new Map();
|
|
171
197
|
const INDEX_BYTES = 8 * 1024 * 1024;
|
|
172
198
|
let retainedBytes = 0;
|
|
199
|
+
/** A tool's display title; MCP carries it on the tool or on its annotations. */
|
|
200
|
+
function toolTitle(tool) {
|
|
201
|
+
const title = tool.title ?? tool.annotations?.title;
|
|
202
|
+
return typeof title === "string" ? title : "";
|
|
203
|
+
}
|
|
173
204
|
function buildIndex(tools) {
|
|
174
|
-
const
|
|
175
|
-
const descriptionTokenDocuments = new Map();
|
|
176
|
-
const addTokens = (target, tokens, position) => {
|
|
177
|
-
for (const token of new Set(tokens)) {
|
|
178
|
-
const documents = target.get(token) ?? [];
|
|
179
|
-
documents.push(position);
|
|
180
|
-
target.set(token, documents);
|
|
181
|
-
}
|
|
182
|
-
};
|
|
205
|
+
const postings = ZONES.map(() => new Map());
|
|
183
206
|
const documents = tools.map((tool, position) => {
|
|
207
|
+
const description = tool.description ?? "";
|
|
184
208
|
const nameTokens = lexicalTokens(tool.name);
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
addTokens(descriptionTokenDocuments, descriptionTokens, position);
|
|
188
|
-
return Object.freeze({
|
|
209
|
+
const summary = new Set(lexicalTokens(discoverySummary(description).text));
|
|
210
|
+
const doc = {
|
|
189
211
|
name: nameTokens.join(" "),
|
|
190
|
-
nameTokens
|
|
191
|
-
|
|
212
|
+
nameTokens,
|
|
213
|
+
titleTokens: [...new Set(lexicalTokens(toolTitle(tool)))],
|
|
214
|
+
summaryTokens: [...summary],
|
|
215
|
+
tailTokens: [...new Set(lexicalTokens(description))].filter((token) => !summary.has(token)),
|
|
216
|
+
};
|
|
217
|
+
ZONES.forEach((zone, index) => {
|
|
218
|
+
Object.freeze(doc[zone]);
|
|
219
|
+
for (const token of new Set(doc[zone])) {
|
|
220
|
+
const positions = postings[index].get(token) ?? [];
|
|
221
|
+
positions.push(position);
|
|
222
|
+
postings[index].set(token, positions);
|
|
223
|
+
}
|
|
192
224
|
});
|
|
225
|
+
return Object.freeze(doc);
|
|
193
226
|
});
|
|
194
|
-
for (const map of
|
|
227
|
+
for (const map of postings)
|
|
195
228
|
for (const positions of map.values())
|
|
196
229
|
Object.freeze(positions);
|
|
197
230
|
// Maps remain private to this module and are never mutated after construction.
|
|
198
|
-
return Object.freeze({
|
|
199
|
-
documents: Object.freeze(documents),
|
|
200
|
-
nameTokenDocuments,
|
|
201
|
-
descriptionTokenDocuments,
|
|
202
|
-
});
|
|
231
|
+
return Object.freeze({ documents: Object.freeze(documents), postings: Object.freeze(postings) });
|
|
203
232
|
}
|
|
204
233
|
function bindIndex(tools, index) {
|
|
205
234
|
const view = { index, tools: new Map(tools.map((tool, position) => [position, tool])) };
|
|
@@ -210,7 +239,7 @@ function bindIndex(tools, index) {
|
|
|
210
239
|
export function prepareLexicalCatalog(tools, partition) {
|
|
211
240
|
if (searchIndexes.has(tools))
|
|
212
241
|
return;
|
|
213
|
-
const key = JSON.stringify([partition, tools.map((tool) => [tool.name, tool.description ?? ""])]);
|
|
242
|
+
const key = JSON.stringify([partition, tools.map((tool) => [tool.name, tool.description ?? "", toolTitle(tool)])]);
|
|
214
243
|
const retained = retainedIndexes.get(key);
|
|
215
244
|
if (retained) {
|
|
216
245
|
bindIndex(tools, retained.index);
|
|
@@ -221,11 +250,11 @@ export function prepareLexicalCatalog(tools, partition) {
|
|
|
221
250
|
let bytes = key.length * 2 + 128;
|
|
222
251
|
for (const doc of index.documents) {
|
|
223
252
|
bytes += 128 + doc.name.length * 2;
|
|
224
|
-
for (const
|
|
225
|
-
for (const token of
|
|
253
|
+
for (const zone of ZONES)
|
|
254
|
+
for (const token of doc[zone])
|
|
226
255
|
bytes += 32 + token.length * 2;
|
|
227
256
|
}
|
|
228
|
-
for (const map of
|
|
257
|
+
for (const map of index.postings)
|
|
229
258
|
for (const [token, positions] of map)
|
|
230
259
|
bytes += 96 + token.length * 2 + positions.length * 8;
|
|
231
260
|
if (bytes > INDEX_BYTES)
|
|
@@ -267,6 +296,24 @@ function inflectionVariants(base) {
|
|
|
267
296
|
...(base.endsWith("y") ? [`${base.slice(0, -1)}ies`, `${base.slice(0, -1)}ied`] : []),
|
|
268
297
|
];
|
|
269
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Shortenings API names use for words people write out. Only unambiguous
|
|
301
|
+
* pairs: `auth` could mean either authentication or authorization.
|
|
302
|
+
*/
|
|
303
|
+
const ABBREVIATIONS = new Map([
|
|
304
|
+
"config configuration",
|
|
305
|
+
"db database",
|
|
306
|
+
"doc document",
|
|
307
|
+
"env environment",
|
|
308
|
+
"org organization",
|
|
309
|
+
"repo repository",
|
|
310
|
+
"var variable",
|
|
311
|
+
]
|
|
312
|
+
.map((pair) => pair.split(" "))
|
|
313
|
+
.flatMap(([short, long]) => [
|
|
314
|
+
[short, long],
|
|
315
|
+
[long, short],
|
|
316
|
+
]));
|
|
270
317
|
function matchingTokenCandidates(term) {
|
|
271
318
|
const candidates = new Set([term, ...inflectionVariants(term)]);
|
|
272
319
|
const possibleBases = [
|
|
@@ -282,6 +329,12 @@ function matchingTokenCandidates(term) {
|
|
|
282
329
|
candidates.add(base);
|
|
283
330
|
}
|
|
284
331
|
}
|
|
332
|
+
for (const candidate of candidates) {
|
|
333
|
+
const other = ABBREVIATIONS.get(candidate);
|
|
334
|
+
if (other)
|
|
335
|
+
for (const variant of [other, ...inflectionVariants(other)])
|
|
336
|
+
candidates.add(variant);
|
|
337
|
+
}
|
|
285
338
|
return candidates;
|
|
286
339
|
}
|
|
287
340
|
/**
|
|
@@ -334,30 +387,38 @@ export function lexicalCorpusStatistics(toolSets, query, synonymsFor) {
|
|
|
334
387
|
documentFrequency: new Map(),
|
|
335
388
|
nameMatches: new Map(),
|
|
336
389
|
descriptionMatches: new Map(),
|
|
390
|
+
headMatches: new Map(),
|
|
337
391
|
};
|
|
338
392
|
}
|
|
339
393
|
const nameMatches = new Map();
|
|
340
394
|
const descriptionMatches = new Map();
|
|
395
|
+
const headMatches = new Map();
|
|
341
396
|
for (const term of terms) {
|
|
342
397
|
const termNameMatches = new Set();
|
|
343
398
|
const termDescriptionMatches = new Set();
|
|
399
|
+
const termHeadMatches = new Set();
|
|
344
400
|
for (const tools of toolSets) {
|
|
345
401
|
const index = indexFor(tools);
|
|
346
402
|
for (const candidate of synonymCandidates(term, synonymsFor?.(tools))) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
403
|
+
// Name and title, summary and tail: only the tail is outside the head.
|
|
404
|
+
[
|
|
405
|
+
[termNameMatches, termHeadMatches],
|
|
406
|
+
[termDescriptionMatches, termHeadMatches],
|
|
407
|
+
[termDescriptionMatches, termHeadMatches],
|
|
408
|
+
[termDescriptionMatches],
|
|
409
|
+
].forEach((targets, zone) => {
|
|
410
|
+
for (const position of index.index.postings[zone].get(candidate) ?? []) {
|
|
411
|
+
const tool = index.tools.get(position);
|
|
412
|
+
if (tool)
|
|
413
|
+
for (const target of targets)
|
|
414
|
+
target.add(tool);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
357
417
|
}
|
|
358
418
|
}
|
|
359
419
|
nameMatches.set(term, termNameMatches);
|
|
360
420
|
descriptionMatches.set(term, termDescriptionMatches);
|
|
421
|
+
headMatches.set(term, termHeadMatches);
|
|
361
422
|
}
|
|
362
423
|
const documentFrequency = new Map(terms.map((term) => [
|
|
363
424
|
term,
|
|
@@ -368,15 +429,20 @@ export function lexicalCorpusStatistics(toolSets, query, synonymsFor) {
|
|
|
368
429
|
documentFrequency,
|
|
369
430
|
nameMatches,
|
|
370
431
|
descriptionMatches,
|
|
432
|
+
headMatches,
|
|
371
433
|
};
|
|
372
434
|
}
|
|
435
|
+
/** A hatch's standing when one operation states every term: coverage, without a named tool's name evidence. */
|
|
436
|
+
export function vocabularyScore(terms, statistics) {
|
|
437
|
+
return terms.reduce((total, term) => total + 5 * inverseDocumentFrequency(term, statistics), 0);
|
|
438
|
+
}
|
|
373
439
|
function inverseDocumentFrequency(term, statistics) {
|
|
374
440
|
const frequency = statistics.documentFrequency.get(term) ?? 0;
|
|
375
441
|
return Math.log(1 + (statistics.documentCount - frequency + 0.5) / (frequency + 0.5));
|
|
376
442
|
}
|
|
377
443
|
function scoreDocument(doc, phrase, terms, mode, statistics) {
|
|
378
444
|
if (!phrase)
|
|
379
|
-
return { score: 0, matchedTermCount: 0 };
|
|
445
|
+
return { score: 0, matchedTermCount: 0, headTerms: [], nameTerms: [], namesDistinctiveTerm: false };
|
|
380
446
|
const matchedTerms = terms.filter((term) => statistics.nameMatches.get(term)?.has(doc.tool) || statistics.descriptionMatches.get(term)?.has(doc.tool));
|
|
381
447
|
if (mode === "all" && matchedTerms.length !== terms.length)
|
|
382
448
|
return null;
|
|
@@ -384,8 +450,10 @@ function scoreDocument(doc, phrase, terms, mode, statistics) {
|
|
|
384
450
|
return null;
|
|
385
451
|
// Coverage remains meaningful in partial mode, but is IDF-weighted rather
|
|
386
452
|
// than a raw term count: one rare domain term can beat several ubiquitous
|
|
387
|
-
// catalog verbs.
|
|
388
|
-
|
|
453
|
+
// catalog verbs. A term met only in the description's tail is weak coverage,
|
|
454
|
+
// so a long description cannot outrank a tool that names what was asked.
|
|
455
|
+
const inHead = (term) => statistics.headMatches.get(term)?.has(doc.tool) === true;
|
|
456
|
+
let score = matchedTerms.reduce((total, term) => total + (inHead(term) ? 4 : 1) * inverseDocumentFrequency(term, statistics), 0);
|
|
389
457
|
const phraseWeight = terms.reduce((total, term) => total + inverseDocumentFrequency(term, statistics), 0);
|
|
390
458
|
if (doc.name === phrase)
|
|
391
459
|
score += 40 * phraseWeight;
|
|
@@ -401,13 +469,31 @@ function scoreDocument(doc, phrase, terms, mode, statistics) {
|
|
|
401
469
|
else if (statistics.nameMatches.get(term)?.has(doc.tool)) {
|
|
402
470
|
score += 8 * weight;
|
|
403
471
|
}
|
|
404
|
-
|
|
472
|
+
// A term reaching a zone only through an inflection or synonym earns half.
|
|
473
|
+
if (doc.summaryTokens.includes(term))
|
|
405
474
|
score += 3 * weight;
|
|
406
|
-
else if (
|
|
475
|
+
else if (doc.titleTokens.includes(term))
|
|
476
|
+
score += 2 * weight;
|
|
477
|
+
else if (doc.tailTokens.includes(term))
|
|
478
|
+
score += 0.5 * weight;
|
|
479
|
+
else if (inHead(term))
|
|
407
480
|
score += 1.5 * weight;
|
|
408
|
-
|
|
481
|
+
else if (statistics.descriptionMatches.get(term)?.has(doc.tool))
|
|
482
|
+
score += 0.25 * weight;
|
|
409
483
|
}
|
|
410
|
-
|
|
484
|
+
// Partial evidence earns only its squared share of the query's rarity, wherever
|
|
485
|
+
// it appears, so a common verb in a name cannot outweigh rare words in a tail.
|
|
486
|
+
const covered = matchedTerms.reduce((total, term) => total + inverseDocumentFrequency(term, statistics), 0);
|
|
487
|
+
score *= (covered / phraseWeight) ** 2;
|
|
488
|
+
const averageWeight = phraseWeight / terms.length;
|
|
489
|
+
const nameTerms = matchedTerms.filter((term) => statistics.nameMatches.get(term)?.has(doc.tool));
|
|
490
|
+
return {
|
|
491
|
+
score,
|
|
492
|
+
matchedTermCount: matchedTerms.length,
|
|
493
|
+
headTerms: matchedTerms.filter(inHead),
|
|
494
|
+
nameTerms,
|
|
495
|
+
namesDistinctiveTerm: nameTerms.some((term) => inverseDocumentFrequency(term, statistics) >= averageWeight),
|
|
496
|
+
};
|
|
411
497
|
}
|
|
412
498
|
function queryContainsExactName(doc, phrase) {
|
|
413
499
|
if (!doc.name || !phrase)
|
|
@@ -439,6 +525,9 @@ export function rankTools(tools, query, mode = "all", statistics = lexicalCorpus
|
|
|
439
525
|
order: visibleOrder,
|
|
440
526
|
exactName: queryContainsExactName(doc, exactNamePhrase),
|
|
441
527
|
matchedTermCount: scored.matchedTermCount,
|
|
528
|
+
headTerms: scored.headTerms,
|
|
529
|
+
nameTerms: scored.nameTerms,
|
|
530
|
+
namesDistinctiveTerm: scored.namesDistinctiveTerm,
|
|
442
531
|
});
|
|
443
532
|
}
|
|
444
533
|
});
|
|
@@ -18,9 +18,10 @@ export declare const CONFIG_DEFAULTS: {
|
|
|
18
18
|
readonly maxEmittedBlocks: 32;
|
|
19
19
|
readonly maxHostCalls: 20;
|
|
20
20
|
readonly hostCallTimeoutMs: 15000;
|
|
21
|
-
readonly watchdogMs:
|
|
21
|
+
readonly watchdogMs: 55000;
|
|
22
22
|
readonly maxWrites: 10;
|
|
23
23
|
readonly maxResultChars: 24000;
|
|
24
|
+
readonly readRoute: true;
|
|
24
25
|
};
|
|
25
26
|
readonly admission: {
|
|
26
27
|
readonly requests: {
|
package/dist/config-defaults.js
CHANGED
|
@@ -25,9 +25,12 @@ export const CONFIG_DEFAULTS = {
|
|
|
25
25
|
maxEmittedBlocks: 32,
|
|
26
26
|
maxHostCalls: 20,
|
|
27
27
|
hostCallTimeoutMs: 15_000,
|
|
28
|
-
|
|
28
|
+
// Below the 60 s MCP clients give one request, leaving time to send a typed
|
|
29
|
+
// result. See documentation/code-mode.md#cancellation-and-limits.
|
|
30
|
+
watchdogMs: 55_000,
|
|
29
31
|
maxWrites: 10,
|
|
30
32
|
maxResultChars: 24_000,
|
|
33
|
+
readRoute: true,
|
|
31
34
|
},
|
|
32
35
|
admission: {
|
|
33
36
|
requests: {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ClassificationOverride } from "./tool-safety.js";
|
|
1
2
|
import type { CredentialVault } from "./credential-contract.js";
|
|
2
3
|
import { type ConfigInput, type ConfigOutput } from "./config-schema.js";
|
|
3
4
|
import type { MemberRef } from "./membership.js";
|
|
@@ -31,7 +32,8 @@ declare const calls: {
|
|
|
31
32
|
/**
|
|
32
33
|
* Deadline (ms) for `call_tool`/`call_destructive_tool` calls that pass no
|
|
33
34
|
* `timeoutMs`. An explicit per-call value wins. Unset by default, when those
|
|
34
|
-
* calls fall back to `execute.hostCallTimeoutMs`.
|
|
35
|
+
* calls fall back to `execute.hostCallTimeoutMs`. `execute.watchdogMs` caps
|
|
36
|
+
* every one of them.
|
|
35
37
|
*
|
|
36
38
|
* Each call makes one attempt. `connecta.call` inside `execute_code` uses
|
|
37
39
|
* it too, ahead of `execute.hostCallTimeoutMs`; `connecta.result`,
|
|
@@ -67,16 +69,19 @@ declare const execute: {
|
|
|
67
69
|
* `connecta.read`, in milliseconds. Default 15_000. Discovery uses
|
|
68
70
|
* `discovery.probeTimeoutMs`. `connecta.call` and the direct call tools use it only when
|
|
69
71
|
* `calls.defaultTimeoutMs` is unset, and an explicit per-call `timeoutMs`
|
|
70
|
-
*
|
|
71
|
-
* such as analytics queries.
|
|
72
|
+
* wins, up to `execute.watchdogMs`. Raise it for providers whose legitimate
|
|
73
|
+
* calls run longer, such as analytics queries.
|
|
72
74
|
*/
|
|
73
75
|
hostCallTimeoutMs: import("./config-schema.js").Field<number, number, true>;
|
|
74
76
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
77
|
+
* Ceiling on one `execute_code` or direct call response, in milliseconds,
|
|
78
|
+
* counted from the request's arrival and enforced outside the sandbox.
|
|
79
|
+
* Default 55_000: MCP clients commonly abandon a request at 60_000, and only
|
|
80
|
+
* a response sent before that reaches the agent as a typed result. It sits
|
|
81
|
+
* above both executors' own deadlines (QuickJS 30_000, the Dynamic Worker
|
|
82
|
+
* 50_000), so a run ends here only if its executor never settled, or if
|
|
83
|
+
* queueing spent its time. An explicit `timeoutMs` above it is cut to it. Raise it,
|
|
84
|
+
* and any executor deadline with it, only for clients whose own limit is raised.
|
|
80
85
|
*/
|
|
81
86
|
watchdogMs: import("./config-schema.js").Field<number, number, true>;
|
|
82
87
|
/**
|
|
@@ -92,6 +97,12 @@ declare const execute: {
|
|
|
92
97
|
* model step. From 4_000 to 1_000_000.
|
|
93
98
|
*/
|
|
94
99
|
maxResultChars: import("./config-schema.js").Field<number, number, true>;
|
|
100
|
+
/**
|
|
101
|
+
* `false` stops listing `execute_read_code` on writable human endpoints. A
|
|
102
|
+
* read program runs unprompted on hosts that auto-approve reads, so a deployment
|
|
103
|
+
* that wants a person to see every program can turn it off. Default true.
|
|
104
|
+
*/
|
|
105
|
+
readRoute: import("./config-schema.js").Field<boolean, boolean, true>;
|
|
95
106
|
};
|
|
96
107
|
declare const codeAdmission: {
|
|
97
108
|
/** Simultaneous work admitted to this pool. */
|
|
@@ -271,8 +282,11 @@ declare const connectaConfig: {
|
|
|
271
282
|
readonly executeCode: boolean | undefined;
|
|
272
283
|
}>> | undefined, true>;
|
|
273
284
|
/** Exact connector id -> tool name -> verdict. Hidden tools cannot be discovered or called.
|
|
285
|
+
* `"read"` applies only where the source is silent, and asserts the call is safe to repeat; a
|
|
286
|
+
* source's declared write stays a write and is reported. `"read-repeatable"` also serves a
|
|
287
|
+
* declared write as a read: the deployment has checked it and accepts that hosts may repeat it.
|
|
274
288
|
* Unknown static names fail construction; absent runtime names stay dormant and are reported. */
|
|
275
|
-
classification: import("./config-schema.js").Field<Record<string, Record<string,
|
|
289
|
+
classification: import("./config-schema.js").Field<Record<string, Record<string, ClassificationOverride>>, Readonly<Record<string, Readonly<Record<string, ClassificationOverride>>>> | undefined, true>;
|
|
276
290
|
/**
|
|
277
291
|
* The deployment's one store: `d1Storage(env.CONNECTA_DB)` from
|
|
278
292
|
* `@zackbart/connecta/d1` on Workers, `sqliteStorage(path)` from
|
|
@@ -355,7 +369,8 @@ declare const connectaConfig: {
|
|
|
355
369
|
/**
|
|
356
370
|
* Deadline (ms) for `call_tool`/`call_destructive_tool` calls that pass no
|
|
357
371
|
* `timeoutMs`. An explicit per-call value wins. Unset by default, when those
|
|
358
|
-
* calls fall back to `execute.hostCallTimeoutMs`.
|
|
372
|
+
* calls fall back to `execute.hostCallTimeoutMs`. `execute.watchdogMs` caps
|
|
373
|
+
* every one of them.
|
|
359
374
|
*
|
|
360
375
|
* Each call makes one attempt. `connecta.call` inside `execute_code` uses
|
|
361
376
|
* it too, ahead of `execute.hostCallTimeoutMs`; `connecta.result`,
|
|
@@ -394,6 +409,7 @@ declare const connectaConfig: {
|
|
|
394
409
|
watchdogMs?: number | undefined;
|
|
395
410
|
maxWrites?: number | undefined;
|
|
396
411
|
maxResultChars?: number | undefined;
|
|
412
|
+
readRoute?: boolean | undefined;
|
|
397
413
|
}, {
|
|
398
414
|
readonly maxEmittedBytes: number;
|
|
399
415
|
readonly maxEmittedBlocks: number;
|
|
@@ -402,6 +418,7 @@ declare const connectaConfig: {
|
|
|
402
418
|
readonly watchdogMs: number;
|
|
403
419
|
readonly maxWrites: number;
|
|
404
420
|
readonly maxResultChars: number;
|
|
421
|
+
readonly readRoute: boolean;
|
|
405
422
|
}, true> & {
|
|
406
423
|
readonly shape: {
|
|
407
424
|
/**
|
|
@@ -419,16 +436,19 @@ declare const connectaConfig: {
|
|
|
419
436
|
* `connecta.read`, in milliseconds. Default 15_000. Discovery uses
|
|
420
437
|
* `discovery.probeTimeoutMs`. `connecta.call` and the direct call tools use it only when
|
|
421
438
|
* `calls.defaultTimeoutMs` is unset, and an explicit per-call `timeoutMs`
|
|
422
|
-
*
|
|
423
|
-
* such as analytics queries.
|
|
439
|
+
* wins, up to `execute.watchdogMs`. Raise it for providers whose legitimate
|
|
440
|
+
* calls run longer, such as analytics queries.
|
|
424
441
|
*/
|
|
425
442
|
hostCallTimeoutMs: import("./config-schema.js").Field<number, number, true>;
|
|
426
443
|
/**
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
444
|
+
* Ceiling on one `execute_code` or direct call response, in milliseconds,
|
|
445
|
+
* counted from the request's arrival and enforced outside the sandbox.
|
|
446
|
+
* Default 55_000: MCP clients commonly abandon a request at 60_000, and only
|
|
447
|
+
* a response sent before that reaches the agent as a typed result. It sits
|
|
448
|
+
* above both executors' own deadlines (QuickJS 30_000, the Dynamic Worker
|
|
449
|
+
* 50_000), so a run ends here only if its executor never settled, or if
|
|
450
|
+
* queueing spent its time. An explicit `timeoutMs` above it is cut to it. Raise it,
|
|
451
|
+
* and any executor deadline with it, only for clients whose own limit is raised.
|
|
432
452
|
*/
|
|
433
453
|
watchdogMs: import("./config-schema.js").Field<number, number, true>;
|
|
434
454
|
/**
|
|
@@ -444,6 +464,12 @@ declare const connectaConfig: {
|
|
|
444
464
|
* model step. From 4_000 to 1_000_000.
|
|
445
465
|
*/
|
|
446
466
|
maxResultChars: import("./config-schema.js").Field<number, number, true>;
|
|
467
|
+
/**
|
|
468
|
+
* `false` stops listing `execute_read_code` on writable human endpoints. A
|
|
469
|
+
* read program runs unprompted on hosts that auto-approve reads, so a deployment
|
|
470
|
+
* that wants a person to see every program can turn it off. Default true.
|
|
471
|
+
*/
|
|
472
|
+
readRoute: import("./config-schema.js").Field<boolean, boolean, true>;
|
|
447
473
|
};
|
|
448
474
|
};
|
|
449
475
|
/**
|
package/dist/config.js
CHANGED
|
@@ -82,7 +82,8 @@ const calls = {
|
|
|
82
82
|
/**
|
|
83
83
|
* Deadline (ms) for `call_tool`/`call_destructive_tool` calls that pass no
|
|
84
84
|
* `timeoutMs`. An explicit per-call value wins. Unset by default, when those
|
|
85
|
-
* calls fall back to `execute.hostCallTimeoutMs`.
|
|
85
|
+
* calls fall back to `execute.hostCallTimeoutMs`. `execute.watchdogMs` caps
|
|
86
|
+
* every one of them.
|
|
86
87
|
*
|
|
87
88
|
* Each call makes one attempt. `connecta.call` inside `execute_code` uses
|
|
88
89
|
* it too, ahead of `execute.hostCallTimeoutMs`; `connecta.result`,
|
|
@@ -118,16 +119,19 @@ const execute = {
|
|
|
118
119
|
* `connecta.read`, in milliseconds. Default 15_000. Discovery uses
|
|
119
120
|
* `discovery.probeTimeoutMs`. `connecta.call` and the direct call tools use it only when
|
|
120
121
|
* `calls.defaultTimeoutMs` is unset, and an explicit per-call `timeoutMs`
|
|
121
|
-
*
|
|
122
|
-
* such as analytics queries.
|
|
122
|
+
* wins, up to `execute.watchdogMs`. Raise it for providers whose legitimate
|
|
123
|
+
* calls run longer, such as analytics queries.
|
|
123
124
|
*/
|
|
124
125
|
hostCallTimeoutMs: whole({ min: 1, default: D.execute.hostCallTimeoutMs }),
|
|
125
126
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
127
|
+
* Ceiling on one `execute_code` or direct call response, in milliseconds,
|
|
128
|
+
* counted from the request's arrival and enforced outside the sandbox.
|
|
129
|
+
* Default 55_000: MCP clients commonly abandon a request at 60_000, and only
|
|
130
|
+
* a response sent before that reaches the agent as a typed result. It sits
|
|
131
|
+
* above both executors' own deadlines (QuickJS 30_000, the Dynamic Worker
|
|
132
|
+
* 50_000), so a run ends here only if its executor never settled, or if
|
|
133
|
+
* queueing spent its time. An explicit `timeoutMs` above it is cut to it. Raise it,
|
|
134
|
+
* and any executor deadline with it, only for clients whose own limit is raised.
|
|
131
135
|
*/
|
|
132
136
|
watchdogMs: whole({ min: 1, default: D.execute.watchdogMs }),
|
|
133
137
|
/**
|
|
@@ -143,6 +147,15 @@ const execute = {
|
|
|
143
147
|
* model step. From 4_000 to 1_000_000.
|
|
144
148
|
*/
|
|
145
149
|
maxResultChars: whole({ min: 4_000, max: 1_000_000, default: D.execute.maxResultChars }),
|
|
150
|
+
/**
|
|
151
|
+
* `false` stops listing `execute_read_code` on writable human endpoints. A
|
|
152
|
+
* read program runs unprompted on hosts that auto-approve reads, so a deployment
|
|
153
|
+
* that wants a person to see every program can turn it off. Default true.
|
|
154
|
+
*/
|
|
155
|
+
readRoute: opaque({
|
|
156
|
+
check: assertFlag,
|
|
157
|
+
resolve: (value) => value ?? D.execute.readRoute,
|
|
158
|
+
}),
|
|
146
159
|
};
|
|
147
160
|
function admissionPool(defaults) {
|
|
148
161
|
return {
|
|
@@ -359,11 +372,14 @@ const connectaConfig = {
|
|
|
359
372
|
/** Named tool pools, each served at `/mcp/<name>` to its members only. */
|
|
360
373
|
pools: record(object(pool)),
|
|
361
374
|
/** Exact connector id -> tool name -> verdict. Hidden tools cannot be discovered or called.
|
|
375
|
+
* `"read"` applies only where the source is silent, and asserts the call is safe to repeat; a
|
|
376
|
+
* source's declared write stays a write and is reported. `"read-repeatable"` also serves a
|
|
377
|
+
* declared write as a read: the deployment has checked it and accepts that hosts may repeat it.
|
|
362
378
|
* Unknown static names fail construction; absent runtime names stay dormant and are reported. */
|
|
363
379
|
classification: record(record(opaque({
|
|
364
380
|
check: (value, path) => {
|
|
365
|
-
if (value !== "read" && value !== "write" && value !== "hidden") {
|
|
366
|
-
throw new ConfigError(`${path} must be "read", "write", or "hidden".`);
|
|
381
|
+
if (value !== "read" && value !== "read-repeatable" && value !== "write" && value !== "hidden") {
|
|
382
|
+
throw new ConfigError(`${path} must be "read", "read-repeatable", "write", or "hidden".`);
|
|
367
383
|
}
|
|
368
384
|
},
|
|
369
385
|
}))),
|