@zackbart/connecta 0.40.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 +148 -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 +32 -5
- package/dist/activity.js +32 -6
- package/dist/advertised-schema.js +15 -0
- package/dist/agent-observation.js +24 -2
- package/dist/agent-routes.js +33 -44
- package/dist/auth/clerk.d.ts +6 -1
- package/dist/auth/clerk.js +16 -0
- package/dist/auth/cloudflare-access.js +49 -7
- package/dist/auth/downstream-oauth/consent.js +79 -29
- package/dist/auth/downstream-oauth/grant-store.js +58 -7
- package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
- package/dist/auth/downstream-oauth/refresh-records.js +6 -5
- package/dist/auth/downstream-oauth.js +15 -4
- package/dist/auth/oauth-request-not-sent.js +4 -0
- package/dist/auth/static-oauth.js +77 -39
- package/dist/auth-elicitation.js +118 -61
- package/dist/authorization-handoff.js +36 -3
- package/dist/catalog-cache.js +352 -43
- package/dist/catalog-freshness.js +25 -0
- package/dist/catalog-service.js +408 -78
- package/dist/catalog.js +216 -62
- package/dist/config-defaults.d.ts +2 -1
- package/dist/config-defaults.js +4 -1
- package/dist/config.d.ts +53 -21
- package/dist/config.js +56 -17
- package/dist/connectors/remote-mcp/call.js +1 -1
- package/dist/connectors/remote-mcp/catalog.js +52 -28
- package/dist/connectors/remote-mcp/connection.js +113 -22
- package/dist/connectors/remote-mcp/oauth.js +2 -2
- package/dist/connectors/remote-mcp/resources.js +5 -2
- package/dist/connectors/remote-mcp/transport.js +24 -6
- package/dist/connectors/remote-mcp.js +14 -5
- package/dist/credential-contract.d.ts +3 -1
- package/dist/describe-config.d.ts +4 -2
- package/dist/describe-config.js +1 -0
- package/dist/discovery-health.js +26 -11
- package/dist/downstream-input.js +11 -2
- package/dist/errors.d.ts +60 -4
- package/dist/errors.js +88 -3
- package/dist/execute.js +491 -202
- package/dist/executor-admission.js +2 -0
- package/dist/executor-contract.js +6 -0
- package/dist/executor-result.js +3 -3
- package/dist/executors/quickjs-child.js +14 -5
- package/dist/executors/quickjs-runtime.d.ts +2 -0
- package/dist/executors/quickjs-runtime.js +37 -20
- package/dist/executors/quickjs.js +24 -7
- package/dist/guarded-transport.js +2 -0
- 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 +14 -2
- package/dist/http/errors.js +9 -4
- package/dist/http/fields.js +17 -5
- package/dist/http/hatch.js +11 -3
- package/dist/http/node-transport.js +110 -4
- package/dist/http/oauth.js +17 -6
- 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 +15 -21
- package/dist/invocation-failure.js +28 -0
- package/dist/invocation.js +167 -64
- package/dist/membership.d.ts +1 -1
- 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 +286 -73
- package/dist/module-contracts.d.ts +3 -1
- package/dist/native-recovery.js +4 -1
- package/dist/native-tools.js +177 -55
- package/dist/node.d.ts +7 -0
- package/dist/node.js +85 -4
- package/dist/oauth-handoff.js +53 -43
- package/dist/operation-activity.js +104 -0
- package/dist/operation-vocabulary.js +6 -0
- package/dist/operator-record.d.ts +9 -1
- package/dist/operator-record.js +16 -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 +1 -14
- package/dist/provider.js +0 -3
- package/dist/providers/_shared/google/kit.js +2 -2
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/breeze/api.js +2 -2
- package/dist/providers/breeze/index.js +1 -1
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/ccb/api.js +2 -2
- package/dist/providers/ccb/index.js +1 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/cloudflare/api.js +2 -2
- package/dist/providers/cloudflare/index.js +3 -1
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/index.js +1 -1
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/drive/index.js +1 -1
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/forms/index.js +2 -1
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/github/api.js +98 -6
- package/dist/providers/github/index.js +8 -3
- 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 +2 -1
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/granola/index.js +1 -1
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/index.js +3 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/planning-center/index.js +12 -6
- 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/api.js +28 -25
- package/dist/providers/quickbooks/index.js +3 -6
- package/dist/providers/quickbooks/lib/writes.js +55 -0
- package/dist/providers/quickbooks/safety.js +17 -5
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/index.js +2 -1
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/slides/index.js +3 -3
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/stripe/index.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/index.js +2 -1
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/vercel/api.js +3 -2
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/registry.d.ts +36 -4
- package/dist/registry.js +142 -30
- package/dist/resource-read.js +21 -2
- package/dist/result-preview.js +210 -0
- package/dist/result-seal.js +168 -0
- package/dist/route-names.js +53 -0
- package/dist/routes/activity-context.js +1 -0
- package/dist/routes/activity-disclosure.js +26 -4
- package/dist/routes/activity.js +2 -1
- package/dist/routes/connect.js +21 -13
- package/dist/routes/mcp.js +83 -35
- package/dist/routes/shared.d.ts +6 -0
- package/dist/routes/ui-config.js +1 -1
- package/dist/routes/ui-data.js +3 -2
- package/dist/runtime/call.js +15 -10
- package/dist/runtime/native-storage.js +12 -0
- package/dist/runtime/request-reads.js +66 -14
- package/dist/runtime/request-scope.js +38 -2
- package/dist/runtime/run.js +4 -1
- package/dist/sent-secrets.d.ts +20 -0
- package/dist/sent-secrets.js +49 -1
- package/dist/skill-origin.js +16 -0
- package/dist/skills.js +213 -57
- package/dist/storage/d1-driver.js +33 -5
- package/dist/storage/keys.js +16 -7
- package/dist/storage/sql-native.js +60 -3
- package/dist/storage/sql.js +6 -0
- package/dist/timing-trace.js +84 -0
- package/dist/tool-safety.d.ts +33 -0
- package/dist/tool-safety.js +51 -15
- package/dist/types.d.ts +36 -0
- package/dist/url-safety.js +38 -20
- package/dist/usage-guide.js +14 -13
- package/dist/utility-recovery.js +33 -0
- package/dist/validate.js +29 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +60 -15
- package/documentation/architecture.md +152 -43
- package/documentation/auth.md +160 -53
- package/documentation/code-mode.md +396 -119
- package/documentation/deploying.md +35 -10
- package/documentation/hosted-mcp-migration.md +9 -3
- package/documentation/integrating.md +27 -5
- package/documentation/meta-tools.md +761 -157
- package/documentation/operating.md +91 -4
- package/documentation/operator-ui.md +51 -2
- package/documentation/providers.md +34 -20
- 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 +10 -8
- 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,39 +188,98 @@ 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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
193
|
+
// Only immutable token strings and numeric positions cross requests. The key
|
|
194
|
+
// holds exactly what buildIndex reads, in order, so schemas cost nothing here;
|
|
195
|
+
// it avoids hash collisions and charges its UTF-16 storage as well.
|
|
196
|
+
const retainedIndexes = new Map();
|
|
197
|
+
const INDEX_BYTES = 8 * 1024 * 1024;
|
|
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
|
+
}
|
|
204
|
+
function buildIndex(tools) {
|
|
205
|
+
const postings = ZONES.map(() => new Map());
|
|
206
|
+
const documents = tools.map((tool, position) => {
|
|
207
|
+
const description = tool.description ?? "";
|
|
208
|
+
const nameTokens = lexicalTokens(tool.name);
|
|
209
|
+
const summary = new Set(lexicalTokens(discoverySummary(description).text));
|
|
210
|
+
const doc = {
|
|
211
|
+
name: nameTokens.join(" "),
|
|
212
|
+
nameTokens,
|
|
213
|
+
titleTokens: [...new Set(lexicalTokens(toolTitle(tool)))],
|
|
214
|
+
summaryTokens: [...summary],
|
|
215
|
+
tailTokens: [...new Set(lexicalTokens(description))].filter((token) => !summary.has(token)),
|
|
178
216
|
};
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
name: nameTokens.join(" "),
|
|
187
|
-
nameTokens,
|
|
188
|
-
descriptionTokens,
|
|
189
|
-
};
|
|
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
|
+
}
|
|
190
224
|
});
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
225
|
+
return Object.freeze(doc);
|
|
226
|
+
});
|
|
227
|
+
for (const map of postings)
|
|
228
|
+
for (const positions of map.values())
|
|
229
|
+
Object.freeze(positions);
|
|
230
|
+
// Maps remain private to this module and are never mutated after construction.
|
|
231
|
+
return Object.freeze({ documents: Object.freeze(documents), postings: Object.freeze(postings) });
|
|
232
|
+
}
|
|
233
|
+
function bindIndex(tools, index) {
|
|
234
|
+
const view = { index, tools: new Map(tools.map((tool, position) => [position, tool])) };
|
|
235
|
+
searchIndexes.set(tools, view);
|
|
236
|
+
return view;
|
|
237
|
+
}
|
|
238
|
+
/** Only call after complete catalog acceptance, redaction and classification. */
|
|
239
|
+
export function prepareLexicalCatalog(tools, partition) {
|
|
240
|
+
if (searchIndexes.has(tools))
|
|
241
|
+
return;
|
|
242
|
+
const key = JSON.stringify([partition, tools.map((tool) => [tool.name, tool.description ?? "", toolTitle(tool)])]);
|
|
243
|
+
const retained = retainedIndexes.get(key);
|
|
244
|
+
if (retained) {
|
|
245
|
+
bindIndex(tools, retained.index);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const index = buildIndex(tools);
|
|
249
|
+
bindIndex(tools, index);
|
|
250
|
+
let bytes = key.length * 2 + 128;
|
|
251
|
+
for (const doc of index.documents) {
|
|
252
|
+
bytes += 128 + doc.name.length * 2;
|
|
253
|
+
for (const zone of ZONES)
|
|
254
|
+
for (const token of doc[zone])
|
|
255
|
+
bytes += 32 + token.length * 2;
|
|
256
|
+
}
|
|
257
|
+
for (const map of index.postings)
|
|
258
|
+
for (const [token, positions] of map)
|
|
259
|
+
bytes += 96 + token.length * 2 + positions.length * 8;
|
|
260
|
+
if (bytes > INDEX_BYTES)
|
|
261
|
+
return;
|
|
262
|
+
while (retainedIndexes.size >= 4 || retainedBytes + bytes > INDEX_BYTES) {
|
|
263
|
+
const oldest = retainedIndexes.keys().next().value;
|
|
264
|
+
retainedBytes -= retainedIndexes.get(oldest).bytes;
|
|
265
|
+
retainedIndexes.delete(oldest);
|
|
197
266
|
}
|
|
198
|
-
|
|
267
|
+
retainedIndexes.set(key, { index, bytes });
|
|
268
|
+
retainedBytes += bytes;
|
|
269
|
+
}
|
|
270
|
+
/** Project immutable positions onto this request's currently visible objects. */
|
|
271
|
+
export function carryLexicalCatalog(source, visible) {
|
|
272
|
+
const view = searchIndexes.get(source);
|
|
273
|
+
if (!view || source === visible)
|
|
274
|
+
return;
|
|
275
|
+
const allowed = new Set(visible);
|
|
276
|
+
searchIndexes.set(visible, {
|
|
277
|
+
index: view.index,
|
|
278
|
+
tools: new Map([...view.tools].filter(([, tool]) => allowed.has(tool))),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function indexFor(tools) {
|
|
282
|
+
return searchIndexes.get(tools) ?? bindIndex(tools, buildIndex(tools));
|
|
199
283
|
}
|
|
200
284
|
/**
|
|
201
285
|
* Whole-token equality is the ordinary lexical match. A deliberately narrow
|
|
@@ -212,6 +296,24 @@ function inflectionVariants(base) {
|
|
|
212
296
|
...(base.endsWith("y") ? [`${base.slice(0, -1)}ies`, `${base.slice(0, -1)}ied`] : []),
|
|
213
297
|
];
|
|
214
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
|
+
]));
|
|
215
317
|
function matchingTokenCandidates(term) {
|
|
216
318
|
const candidates = new Set([term, ...inflectionVariants(term)]);
|
|
217
319
|
const possibleBases = [
|
|
@@ -227,6 +329,12 @@ function matchingTokenCandidates(term) {
|
|
|
227
329
|
candidates.add(base);
|
|
228
330
|
}
|
|
229
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
|
+
}
|
|
230
338
|
return candidates;
|
|
231
339
|
}
|
|
232
340
|
/**
|
|
@@ -279,26 +387,38 @@ export function lexicalCorpusStatistics(toolSets, query, synonymsFor) {
|
|
|
279
387
|
documentFrequency: new Map(),
|
|
280
388
|
nameMatches: new Map(),
|
|
281
389
|
descriptionMatches: new Map(),
|
|
390
|
+
headMatches: new Map(),
|
|
282
391
|
};
|
|
283
392
|
}
|
|
284
393
|
const nameMatches = new Map();
|
|
285
394
|
const descriptionMatches = new Map();
|
|
395
|
+
const headMatches = new Map();
|
|
286
396
|
for (const term of terms) {
|
|
287
397
|
const termNameMatches = new Set();
|
|
288
398
|
const termDescriptionMatches = new Set();
|
|
399
|
+
const termHeadMatches = new Set();
|
|
289
400
|
for (const tools of toolSets) {
|
|
290
401
|
const index = indexFor(tools);
|
|
291
402
|
for (const candidate of synonymCandidates(term, synonymsFor?.(tools))) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
termDescriptionMatches
|
|
297
|
-
|
|
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
|
+
});
|
|
298
417
|
}
|
|
299
418
|
}
|
|
300
419
|
nameMatches.set(term, termNameMatches);
|
|
301
420
|
descriptionMatches.set(term, termDescriptionMatches);
|
|
421
|
+
headMatches.set(term, termHeadMatches);
|
|
302
422
|
}
|
|
303
423
|
const documentFrequency = new Map(terms.map((term) => [
|
|
304
424
|
term,
|
|
@@ -309,15 +429,20 @@ export function lexicalCorpusStatistics(toolSets, query, synonymsFor) {
|
|
|
309
429
|
documentFrequency,
|
|
310
430
|
nameMatches,
|
|
311
431
|
descriptionMatches,
|
|
432
|
+
headMatches,
|
|
312
433
|
};
|
|
313
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
|
+
}
|
|
314
439
|
function inverseDocumentFrequency(term, statistics) {
|
|
315
440
|
const frequency = statistics.documentFrequency.get(term) ?? 0;
|
|
316
441
|
return Math.log(1 + (statistics.documentCount - frequency + 0.5) / (frequency + 0.5));
|
|
317
442
|
}
|
|
318
443
|
function scoreDocument(doc, phrase, terms, mode, statistics) {
|
|
319
444
|
if (!phrase)
|
|
320
|
-
return { score: 0, matchedTermCount: 0 };
|
|
445
|
+
return { score: 0, matchedTermCount: 0, headTerms: [], nameTerms: [], namesDistinctiveTerm: false };
|
|
321
446
|
const matchedTerms = terms.filter((term) => statistics.nameMatches.get(term)?.has(doc.tool) || statistics.descriptionMatches.get(term)?.has(doc.tool));
|
|
322
447
|
if (mode === "all" && matchedTerms.length !== terms.length)
|
|
323
448
|
return null;
|
|
@@ -325,8 +450,10 @@ function scoreDocument(doc, phrase, terms, mode, statistics) {
|
|
|
325
450
|
return null;
|
|
326
451
|
// Coverage remains meaningful in partial mode, but is IDF-weighted rather
|
|
327
452
|
// than a raw term count: one rare domain term can beat several ubiquitous
|
|
328
|
-
// catalog verbs.
|
|
329
|
-
|
|
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);
|
|
330
457
|
const phraseWeight = terms.reduce((total, term) => total + inverseDocumentFrequency(term, statistics), 0);
|
|
331
458
|
if (doc.name === phrase)
|
|
332
459
|
score += 40 * phraseWeight;
|
|
@@ -342,13 +469,31 @@ function scoreDocument(doc, phrase, terms, mode, statistics) {
|
|
|
342
469
|
else if (statistics.nameMatches.get(term)?.has(doc.tool)) {
|
|
343
470
|
score += 8 * weight;
|
|
344
471
|
}
|
|
345
|
-
|
|
472
|
+
// A term reaching a zone only through an inflection or synonym earns half.
|
|
473
|
+
if (doc.summaryTokens.includes(term))
|
|
346
474
|
score += 3 * weight;
|
|
347
|
-
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))
|
|
348
480
|
score += 1.5 * weight;
|
|
349
|
-
|
|
481
|
+
else if (statistics.descriptionMatches.get(term)?.has(doc.tool))
|
|
482
|
+
score += 0.25 * weight;
|
|
350
483
|
}
|
|
351
|
-
|
|
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
|
+
};
|
|
352
497
|
}
|
|
353
498
|
function queryContainsExactName(doc, phrase) {
|
|
354
499
|
if (!doc.name || !phrase)
|
|
@@ -365,15 +510,24 @@ export function rankTools(tools, query, mode = "all", statistics = lexicalCorpus
|
|
|
365
510
|
const exactNamePhrase = normalized(exactNameQuery);
|
|
366
511
|
const terms = [...new Set(phrase.split(/\s+/).filter(Boolean))];
|
|
367
512
|
const ranked = [];
|
|
368
|
-
indexFor(tools)
|
|
369
|
-
|
|
513
|
+
const view = indexFor(tools);
|
|
514
|
+
let order = 0;
|
|
515
|
+
view.index.documents.forEach((doc, position) => {
|
|
516
|
+
const tool = view.tools.get(position);
|
|
517
|
+
if (!tool)
|
|
518
|
+
return;
|
|
519
|
+
const visibleOrder = order++;
|
|
520
|
+
const scored = scoreDocument({ ...doc, tool }, phrase, terms, mode, statistics);
|
|
370
521
|
if (scored !== null) {
|
|
371
522
|
ranked.push({
|
|
372
|
-
tool
|
|
523
|
+
tool,
|
|
373
524
|
score: scored.score,
|
|
374
|
-
order,
|
|
525
|
+
order: visibleOrder,
|
|
375
526
|
exactName: queryContainsExactName(doc, exactNamePhrase),
|
|
376
527
|
matchedTermCount: scored.matchedTermCount,
|
|
528
|
+
headTerms: scored.headTerms,
|
|
529
|
+
nameTerms: scored.nameTerms,
|
|
530
|
+
namesDistinctiveTerm: scored.namesDistinctiveTerm,
|
|
377
531
|
});
|
|
378
532
|
}
|
|
379
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. */
|
|
@@ -211,7 +222,7 @@ declare const pool: {
|
|
|
211
222
|
* What this pool is for, in plain words for the model: appended to the MCP
|
|
212
223
|
* `initialize` instructions at `/mcp/<name>` and `/agent/<name>`, so a
|
|
213
224
|
* caller learns its scope ("Team tier: shared tools only."). Trimmed, 1 to
|
|
214
|
-
* 500
|
|
225
|
+
* 500 UTF-16 code units, no line breaks or control characters. It grants and hides nothing; plain
|
|
215
226
|
* `/mcp` never shows it.
|
|
216
227
|
*/
|
|
217
228
|
description: import("./config-schema.js").Field<string, string | undefined, true>;
|
|
@@ -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
|
|
@@ -283,8 +297,9 @@ declare const connectaConfig: {
|
|
|
283
297
|
*/
|
|
284
298
|
storage: import("./config-schema.js").Field<KVStorage, KVStorage, true>;
|
|
285
299
|
/**
|
|
286
|
-
* Public base URL. Defaults to the request origin per-request
|
|
287
|
-
*
|
|
300
|
+
* Public base URL. Defaults to the request origin per-request, except that
|
|
301
|
+
* clerkAuth requires it here or as its own `publicUrl`. Configuring an HTTPS
|
|
302
|
+
* URL also redirects matching inbound HTTP requests to HTTPS.
|
|
288
303
|
*/
|
|
289
304
|
publicUrl: import("./config-schema.js").Field<string, string | undefined, true>;
|
|
290
305
|
/**
|
|
@@ -294,7 +309,10 @@ declare const connectaConfig: {
|
|
|
294
309
|
allowedOrigins: import("./config-schema.js").Field<readonly string[] | "*", readonly string[] | "*" | undefined, true>;
|
|
295
310
|
/** Optional recorder and reader, created by activityHistory() from /activity. */
|
|
296
311
|
activity: import("./config-schema.js").Field<ActivityModule, ActivityModule | undefined, true>;
|
|
297
|
-
/**
|
|
312
|
+
/**
|
|
313
|
+
* Replaceable owner-partitioned credential storage. Omit for config-owned
|
|
314
|
+
* secrets. With any OAuth connector it must implement `seal` and `open`.
|
|
315
|
+
*/
|
|
298
316
|
vault: import("./config-schema.js").Field<CredentialVault, CredentialVault | undefined, true>;
|
|
299
317
|
/** Optional connection UI, created by operatorUi() from /ui. */
|
|
300
318
|
ui: import("./config-schema.js").Field<OperatorSurface, OperatorSurface | undefined, true>;
|
|
@@ -351,7 +369,8 @@ declare const connectaConfig: {
|
|
|
351
369
|
/**
|
|
352
370
|
* Deadline (ms) for `call_tool`/`call_destructive_tool` calls that pass no
|
|
353
371
|
* `timeoutMs`. An explicit per-call value wins. Unset by default, when those
|
|
354
|
-
* calls fall back to `execute.hostCallTimeoutMs`.
|
|
372
|
+
* calls fall back to `execute.hostCallTimeoutMs`. `execute.watchdogMs` caps
|
|
373
|
+
* every one of them.
|
|
355
374
|
*
|
|
356
375
|
* Each call makes one attempt. `connecta.call` inside `execute_code` uses
|
|
357
376
|
* it too, ahead of `execute.hostCallTimeoutMs`; `connecta.result`,
|
|
@@ -390,6 +409,7 @@ declare const connectaConfig: {
|
|
|
390
409
|
watchdogMs?: number | undefined;
|
|
391
410
|
maxWrites?: number | undefined;
|
|
392
411
|
maxResultChars?: number | undefined;
|
|
412
|
+
readRoute?: boolean | undefined;
|
|
393
413
|
}, {
|
|
394
414
|
readonly maxEmittedBytes: number;
|
|
395
415
|
readonly maxEmittedBlocks: number;
|
|
@@ -398,6 +418,7 @@ declare const connectaConfig: {
|
|
|
398
418
|
readonly watchdogMs: number;
|
|
399
419
|
readonly maxWrites: number;
|
|
400
420
|
readonly maxResultChars: number;
|
|
421
|
+
readonly readRoute: boolean;
|
|
401
422
|
}, true> & {
|
|
402
423
|
readonly shape: {
|
|
403
424
|
/**
|
|
@@ -415,16 +436,19 @@ declare const connectaConfig: {
|
|
|
415
436
|
* `connecta.read`, in milliseconds. Default 15_000. Discovery uses
|
|
416
437
|
* `discovery.probeTimeoutMs`. `connecta.call` and the direct call tools use it only when
|
|
417
438
|
* `calls.defaultTimeoutMs` is unset, and an explicit per-call `timeoutMs`
|
|
418
|
-
*
|
|
419
|
-
* such as analytics queries.
|
|
439
|
+
* wins, up to `execute.watchdogMs`. Raise it for providers whose legitimate
|
|
440
|
+
* calls run longer, such as analytics queries.
|
|
420
441
|
*/
|
|
421
442
|
hostCallTimeoutMs: import("./config-schema.js").Field<number, number, true>;
|
|
422
443
|
/**
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
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.
|
|
428
452
|
*/
|
|
429
453
|
watchdogMs: import("./config-schema.js").Field<number, number, true>;
|
|
430
454
|
/**
|
|
@@ -440,6 +464,12 @@ declare const connectaConfig: {
|
|
|
440
464
|
* model step. From 4_000 to 1_000_000.
|
|
441
465
|
*/
|
|
442
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>;
|
|
443
473
|
};
|
|
444
474
|
};
|
|
445
475
|
/**
|
|
@@ -542,6 +572,8 @@ declare const connectaConfig: {
|
|
|
542
572
|
};
|
|
543
573
|
};
|
|
544
574
|
};
|
|
575
|
+
/** Payload-free request timing logs; 0 disables tracing, 1 samples every MCP request. */
|
|
576
|
+
timingTraceSampleRate: import("./config-schema.js").Field<number, number, true>;
|
|
545
577
|
/** Diagnostic output. Use "silent" to disable all diagnostic logging. */
|
|
546
578
|
logger: import("./config-schema.js").Field<Logger | "silent", Logger, true>;
|
|
547
579
|
serverInfo: import("./config-schema.js").Field<{
|