@zackbart/connecta 0.10.0 → 0.10.2

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.
Files changed (123) hide show
  1. package/AGENTS.md +113 -0
  2. package/CHANGELOG.md +83 -0
  3. package/README.md +62 -12
  4. package/bin/connecta.mjs +272 -0
  5. package/dist/access-tokens.d.ts +32 -0
  6. package/dist/access-tokens.d.ts.map +1 -0
  7. package/dist/access-tokens.js +225 -0
  8. package/dist/access-tokens.js.map +1 -0
  9. package/dist/catalog-service.d.ts +39 -1
  10. package/dist/catalog-service.d.ts.map +1 -1
  11. package/dist/catalog-service.js +133 -11
  12. package/dist/catalog-service.js.map +1 -1
  13. package/dist/catalog.d.ts +17 -0
  14. package/dist/catalog.d.ts.map +1 -1
  15. package/dist/catalog.js +113 -13
  16. package/dist/catalog.js.map +1 -1
  17. package/dist/execute.d.ts +45 -1
  18. package/dist/execute.d.ts.map +1 -1
  19. package/dist/execute.js +265 -68
  20. package/dist/execute.js.map +1 -1
  21. package/dist/index.d.ts +11 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +12 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/invocation.d.ts.map +1 -1
  26. package/dist/invocation.js +1 -5
  27. package/dist/invocation.js.map +1 -1
  28. package/dist/meta-tools.d.ts +1 -0
  29. package/dist/meta-tools.d.ts.map +1 -1
  30. package/dist/meta-tools.js +410 -12
  31. package/dist/meta-tools.js.map +1 -1
  32. package/dist/operator-ui/generated.d.ts +2 -2
  33. package/dist/operator-ui/generated.d.ts.map +1 -1
  34. package/dist/operator-ui/generated.js +2 -2
  35. package/dist/operator-ui/generated.js.map +1 -1
  36. package/dist/operator-ui/model.d.ts +2 -0
  37. package/dist/operator-ui/model.d.ts.map +1 -1
  38. package/dist/operator-ui/model.js.map +1 -1
  39. package/dist/routes/access-tokens.d.ts +7 -0
  40. package/dist/routes/access-tokens.d.ts.map +1 -0
  41. package/dist/routes/access-tokens.js +84 -0
  42. package/dist/routes/access-tokens.js.map +1 -0
  43. package/dist/routes/shared.d.ts +3 -0
  44. package/dist/routes/shared.d.ts.map +1 -1
  45. package/dist/routes/shared.js.map +1 -1
  46. package/dist/routes/ui.d.ts.map +1 -1
  47. package/dist/routes/ui.js +9 -1
  48. package/dist/routes/ui.js.map +1 -1
  49. package/dist/server.d.ts.map +1 -1
  50. package/dist/server.js +5 -0
  51. package/dist/server.js.map +1 -1
  52. package/dist/skills.d.ts +1 -1
  53. package/dist/skills.d.ts.map +1 -1
  54. package/dist/skills.js +1 -1
  55. package/dist/storage/file.d.ts.map +1 -1
  56. package/dist/storage/file.js +5 -0
  57. package/dist/storage/file.js.map +1 -1
  58. package/dist/storage/memory.d.ts.map +1 -1
  59. package/dist/storage/memory.js +8 -0
  60. package/dist/storage/memory.js.map +1 -1
  61. package/dist/tool-safety.d.ts +10 -0
  62. package/dist/tool-safety.d.ts.map +1 -0
  63. package/dist/tool-safety.js +12 -0
  64. package/dist/tool-safety.js.map +1 -0
  65. package/dist/types.d.ts +5 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/ui.d.ts +4 -4
  68. package/dist/ui.d.ts.map +1 -1
  69. package/dist/ui.js +44 -1
  70. package/dist/ui.js.map +1 -1
  71. package/dist/version.d.ts +1 -1
  72. package/dist/version.js +1 -1
  73. package/documentation/architecture.md +7 -0
  74. package/documentation/auth.md +58 -0
  75. package/documentation/call-admission.md +7 -0
  76. package/documentation/code-first-exploration.md +292 -0
  77. package/documentation/code-mode.md +697 -0
  78. package/documentation/connector-guides.md +7 -0
  79. package/documentation/connectors.md +63 -0
  80. package/documentation/mcp-2026-07-28.md +46 -0
  81. package/documentation/meta-tools.md +167 -0
  82. package/documentation/operations.md +7 -0
  83. package/documentation/operator-ui.md +7 -0
  84. package/documentation/request-admission.md +7 -0
  85. package/documentation/storage-and-credentials.md +54 -0
  86. package/ethos.md +132 -0
  87. package/examples/node/README.md +53 -0
  88. package/examples/node/src/index.ts +73 -0
  89. package/examples/worker/README.md +160 -0
  90. package/examples/worker/src/cloudflare-kv.ts +43 -0
  91. package/examples/worker/src/d1-activity-row.ts +100 -0
  92. package/examples/worker/src/d1-activity.ts +144 -0
  93. package/examples/worker/src/index.ts +136 -0
  94. package/examples/worker/wrangler.jsonc +26 -0
  95. package/package.json +11 -1
  96. package/src/access-tokens.ts +289 -0
  97. package/src/catalog-service.ts +177 -15
  98. package/src/catalog.ts +143 -12
  99. package/src/execute.ts +372 -96
  100. package/src/index.ts +33 -1
  101. package/src/invocation.ts +1 -8
  102. package/src/meta-tools.ts +504 -11
  103. package/src/operator-ui/browser.css +63 -0
  104. package/src/operator-ui/browser.ts +288 -2
  105. package/src/operator-ui/generated.ts +2 -2
  106. package/src/operator-ui/model.ts +6 -0
  107. package/src/routes/access-tokens.ts +115 -0
  108. package/src/routes/shared.ts +3 -0
  109. package/src/routes/ui.ts +9 -0
  110. package/src/server.ts +5 -0
  111. package/src/skills.ts +1 -1
  112. package/src/storage/file.ts +5 -0
  113. package/src/storage/memory.ts +8 -0
  114. package/src/tool-safety.ts +15 -0
  115. package/src/types.ts +5 -0
  116. package/src/ui.ts +50 -1
  117. package/src/version.ts +1 -1
  118. package/templates/node/.env.example +5 -0
  119. package/templates/node/AGENTS.md +19 -0
  120. package/templates/node/README.md +33 -0
  121. package/templates/node/package.json +23 -0
  122. package/templates/node/src/index.ts +43 -0
  123. package/templates/node/tsconfig.json +12 -0
@@ -152,18 +152,405 @@ function resolvePath(value, segments) {
152
152
  }
153
153
  return resolvePath(next, rest);
154
154
  }
155
- /** Select the given dot-paths from a value → `{ "<path>": value }` (omitting misses). */
155
+ const MAX_PROJECTION_AVAILABLE_FIELDS = 20;
156
+ const MAX_PROJECTION_SCHEMA_DEPTH = 12;
157
+ const MAX_PROJECTION_SCHEMA_NODES = 200;
158
+ const MAX_PROJECTION_SCHEMA_PATHS = 100;
159
+ const MAX_PROJECTION_PATH_CHARS = 256;
160
+ const MAX_PROJECTION_PATH_BYTES = 512;
161
+ const MAX_PROJECTION_TOTAL_PATH_CHARS = 512;
162
+ const MAX_PROJECTION_TOTAL_PATH_BYTES = 768;
163
+ const JSON_SCHEMA_TYPES = new Set([
164
+ "array",
165
+ "boolean",
166
+ "integer",
167
+ "null",
168
+ "number",
169
+ "object",
170
+ "string",
171
+ ]);
172
+ const NON_SEMANTIC_REF_SIBLINGS = new Set([
173
+ "$anchor",
174
+ "$comment",
175
+ "$defs",
176
+ "$id",
177
+ "$ref",
178
+ "$schema",
179
+ "default",
180
+ "definitions",
181
+ "deprecated",
182
+ "description",
183
+ "examples",
184
+ "readOnly",
185
+ "title",
186
+ "writeOnly",
187
+ ]);
188
+ function localSchemaRef(root, ref) {
189
+ if (ref === "#")
190
+ return root;
191
+ if (!ref.startsWith("#/") ||
192
+ ref.length > 2_048 ||
193
+ /~(?![01])/.test(ref)) {
194
+ return undefined;
195
+ }
196
+ const segments = ref.slice(2).split("/");
197
+ if (segments.length > MAX_PROJECTION_SCHEMA_DEPTH)
198
+ return undefined;
199
+ let current = root;
200
+ for (const encoded of segments) {
201
+ if (current === null || typeof current !== "object")
202
+ return undefined;
203
+ const key = encoded.replaceAll("~1", "/").replaceAll("~0", "~");
204
+ if (!Object.prototype.hasOwnProperty.call(current, key))
205
+ return undefined;
206
+ current = current[key];
207
+ }
208
+ return current;
209
+ }
210
+ function hasSchemaType(schema, wanted) {
211
+ return (schema.type === wanted ||
212
+ (Array.isArray(schema.type) && schema.type.includes(wanted)));
213
+ }
214
+ function selectableFieldName(name) {
215
+ return name.length > 0 && !name.includes(".") && !name.endsWith("[]");
216
+ }
217
+ /**
218
+ * Collect selectable output paths without trusting a schema more than JSON
219
+ * Schema permits. Traversal is iterative and budgeted before sorting or
220
+ * rendering, so a cyclic, extremely deep, or extremely broad downstream
221
+ * schema cannot turn projection feedback into unbounded host work.
222
+ */
223
+ function analyzeSchemaFields(root) {
224
+ const pending = [
225
+ { schema: root, prefix: "", depth: 0, ancestors: new Set() },
226
+ ];
227
+ const paths = new Set();
228
+ let nodes = 0;
229
+ let totalPathChars = 0;
230
+ let totalPathBytes = 0;
231
+ let complete = true;
232
+ let truncated = false;
233
+ const addPath = (path) => {
234
+ if (paths.has(path))
235
+ return true;
236
+ // Check UTF-16 length before encoding, so a hostile multi-megabyte key
237
+ // never causes a same-sized temporary allocation merely to reject it.
238
+ if (path.length > MAX_PROJECTION_PATH_CHARS ||
239
+ totalPathChars + path.length > MAX_PROJECTION_TOTAL_PATH_CHARS) {
240
+ complete = false;
241
+ truncated = true;
242
+ return false;
243
+ }
244
+ const pathBytes = enc.encode(path).length;
245
+ if (pathBytes > MAX_PROJECTION_PATH_BYTES ||
246
+ totalPathBytes + pathBytes > MAX_PROJECTION_TOTAL_PATH_BYTES ||
247
+ paths.size >= MAX_PROJECTION_SCHEMA_PATHS) {
248
+ complete = false;
249
+ truncated = true;
250
+ return false;
251
+ }
252
+ paths.add(path);
253
+ totalPathChars += path.length;
254
+ totalPathBytes += pathBytes;
255
+ return true;
256
+ };
257
+ const enqueue = (item) => {
258
+ if (nodes + pending.length >= MAX_PROJECTION_SCHEMA_NODES) {
259
+ complete = false;
260
+ truncated = true;
261
+ return false;
262
+ }
263
+ pending.push(item);
264
+ return true;
265
+ };
266
+ while (pending.length > 0) {
267
+ const item = pending.pop();
268
+ if (item.depth > MAX_PROJECTION_SCHEMA_DEPTH) {
269
+ complete = false;
270
+ truncated = true;
271
+ continue;
272
+ }
273
+ if (++nodes > MAX_PROJECTION_SCHEMA_NODES) {
274
+ complete = false;
275
+ truncated = true;
276
+ break;
277
+ }
278
+ if (item.schema === false)
279
+ continue;
280
+ if (item.schema === true ||
281
+ item.schema === null ||
282
+ typeof item.schema !== "object" ||
283
+ item.ancestors.has(item.schema)) {
284
+ complete = false;
285
+ continue;
286
+ }
287
+ const schema = item.schema;
288
+ const ancestors = new Set(item.ancestors).add(item.schema);
289
+ let recognized = false;
290
+ if (schema.type !== undefined &&
291
+ !((typeof schema.type === "string" &&
292
+ JSON_SCHEMA_TYPES.has(schema.type)) ||
293
+ (Array.isArray(schema.type) &&
294
+ schema.type.length > 0 &&
295
+ schema.type.every((type) => typeof type === "string" && JSON_SCHEMA_TYPES.has(type))))) {
296
+ complete = false;
297
+ }
298
+ if (schema.$ref !== undefined) {
299
+ recognized = true;
300
+ let hasSemanticSiblings = false;
301
+ for (const key in schema) {
302
+ if (Object.prototype.hasOwnProperty.call(schema, key) &&
303
+ !NON_SEMANTIC_REF_SIBLINGS.has(key)) {
304
+ hasSemanticSiblings = true;
305
+ break;
306
+ }
307
+ }
308
+ if (hasSemanticSiblings) {
309
+ // Modern JSON Schema applies $ref siblings as an intersection. A
310
+ // compact field walker cannot prove that intersection's selectable
311
+ // paths, so do not publish paths from either half as available.
312
+ complete = false;
313
+ continue;
314
+ }
315
+ const target = typeof schema.$ref === "string"
316
+ ? localSchemaRef(root, schema.$ref)
317
+ : undefined;
318
+ if (target === undefined)
319
+ complete = false;
320
+ else {
321
+ enqueue({
322
+ schema: target,
323
+ prefix: item.prefix,
324
+ depth: item.depth + 1,
325
+ ancestors,
326
+ });
327
+ }
328
+ }
329
+ for (const keyword of ["allOf", "anyOf", "oneOf"]) {
330
+ const variants = schema[keyword];
331
+ if (!Array.isArray(variants))
332
+ continue;
333
+ recognized = true;
334
+ // Combining schemas can close or conditionally expose fields in ways
335
+ // this compact recovery walker intentionally does not prove.
336
+ complete = false;
337
+ for (const variant of variants) {
338
+ if (!enqueue({
339
+ schema: variant,
340
+ prefix: item.prefix,
341
+ depth: item.depth + 1,
342
+ ancestors,
343
+ })) {
344
+ break;
345
+ }
346
+ }
347
+ }
348
+ if (truncated)
349
+ break;
350
+ const properties = schema.properties;
351
+ const propertyRecord = properties !== null &&
352
+ typeof properties === "object" &&
353
+ !Array.isArray(properties)
354
+ ? properties
355
+ : undefined;
356
+ if (properties !== undefined && propertyRecord === undefined) {
357
+ complete = false;
358
+ }
359
+ const objectShape = hasSchemaType(schema, "object") || propertyRecord !== undefined;
360
+ if (objectShape) {
361
+ recognized = true;
362
+ const patterns = schema.patternProperties;
363
+ let hasPatterns = false;
364
+ if (patterns !== undefined &&
365
+ (patterns === null ||
366
+ typeof patterns !== "object" ||
367
+ Array.isArray(patterns))) {
368
+ complete = false;
369
+ }
370
+ else if (patterns !== undefined) {
371
+ for (const key in patterns) {
372
+ if (Object.prototype.hasOwnProperty.call(patterns, key)) {
373
+ hasPatterns = true;
374
+ break;
375
+ }
376
+ }
377
+ }
378
+ if (schema.additionalProperties !== false || hasPatterns) {
379
+ complete = false;
380
+ }
381
+ if (propertyRecord) {
382
+ for (const key in propertyRecord) {
383
+ if (!Object.prototype.hasOwnProperty.call(propertyRecord, key)) {
384
+ continue;
385
+ }
386
+ if (++nodes > MAX_PROJECTION_SCHEMA_NODES) {
387
+ complete = false;
388
+ truncated = true;
389
+ break;
390
+ }
391
+ if (key.length > MAX_PROJECTION_PATH_CHARS) {
392
+ complete = false;
393
+ truncated = true;
394
+ break;
395
+ }
396
+ if (!selectableFieldName(key)) {
397
+ complete = false;
398
+ continue;
399
+ }
400
+ const child = propertyRecord[key];
401
+ // A false property schema forbids the property; advertising its name
402
+ // as selectable would turn an impossible value into a valid hint.
403
+ if (child === false)
404
+ continue;
405
+ const path = item.prefix ? `${item.prefix}.${key}` : key;
406
+ if (!addPath(path))
407
+ break;
408
+ if (!enqueue({
409
+ schema: child,
410
+ prefix: path,
411
+ depth: item.depth + 1,
412
+ ancestors,
413
+ })) {
414
+ break;
415
+ }
416
+ }
417
+ }
418
+ }
419
+ if (truncated)
420
+ break;
421
+ const arrayShape = hasSchemaType(schema, "array") ||
422
+ schema.items !== undefined ||
423
+ schema.prefixItems !== undefined;
424
+ if (arrayShape) {
425
+ recognized = true;
426
+ const arrayPath = `${item.prefix}[]`;
427
+ if (addPath(arrayPath)) {
428
+ if (Array.isArray(schema.prefixItems)) {
429
+ complete = false;
430
+ for (const child of schema.prefixItems) {
431
+ if (!enqueue({
432
+ schema: child,
433
+ prefix: arrayPath,
434
+ depth: item.depth + 1,
435
+ ancestors,
436
+ })) {
437
+ break;
438
+ }
439
+ }
440
+ }
441
+ if (schema.items === undefined) {
442
+ if (!Array.isArray(schema.prefixItems))
443
+ complete = false;
444
+ }
445
+ else if (schema.items === true || Array.isArray(schema.items)) {
446
+ complete = false;
447
+ const children = Array.isArray(schema.items)
448
+ ? schema.items
449
+ : [];
450
+ for (const child of children) {
451
+ if (!enqueue({
452
+ schema: child,
453
+ prefix: arrayPath,
454
+ depth: item.depth + 1,
455
+ ancestors,
456
+ })) {
457
+ break;
458
+ }
459
+ }
460
+ }
461
+ else if (schema.items !== false) {
462
+ enqueue({
463
+ schema: schema.items,
464
+ prefix: arrayPath,
465
+ depth: item.depth + 1,
466
+ ancestors,
467
+ });
468
+ }
469
+ }
470
+ }
471
+ const types = Array.isArray(schema.type)
472
+ ? schema.type
473
+ : schema.type === undefined
474
+ ? []
475
+ : [schema.type];
476
+ const primitiveOnly = types.length > 0 &&
477
+ types.every((type) => type === "string" ||
478
+ type === "number" ||
479
+ type === "integer" ||
480
+ type === "boolean" ||
481
+ type === "null");
482
+ if (!recognized && !primitiveOnly)
483
+ complete = false;
484
+ if (truncated)
485
+ break;
486
+ }
487
+ return {
488
+ paths: [...paths].sort(),
489
+ complete,
490
+ truncated,
491
+ };
492
+ }
493
+ function schemaProjectionFeedback(outputSchema, unmatchedFields) {
494
+ const analysis = analyzeSchemaFields(outputSchema);
495
+ const available = new Set(analysis.paths);
496
+ const invalidFields = analysis.complete
497
+ ? unmatchedFields.filter((field) => !available.has(field))
498
+ : [];
499
+ return {
500
+ schemaDeclared: true,
501
+ schemaCoverage: analysis.complete ? "complete" : "partial",
502
+ ...(invalidFields.length > 0 ? { invalidFields } : {}),
503
+ ...(analysis.paths.length > 0
504
+ ? {
505
+ availableFields: analysis.paths.slice(0, MAX_PROJECTION_AVAILABLE_FIELDS),
506
+ }
507
+ : {}),
508
+ ...(analysis.truncated ||
509
+ analysis.paths.length > MAX_PROJECTION_AVAILABLE_FIELDS
510
+ ? { availableFieldsTruncated: true }
511
+ : {}),
512
+ };
513
+ }
514
+ /** Select the given dot-paths, retaining both matches and exact misses. */
156
515
  function applyFields(value, fields) {
157
516
  const out = {};
517
+ const unmatchedFields = [];
158
518
  for (const path of fields) {
159
519
  const resolved = resolvePath(value, path.split("."));
160
- if (resolved !== undefined)
520
+ if (resolved === undefined)
521
+ unmatchedFields.push(path);
522
+ else
161
523
  out[path] = resolved;
162
524
  }
163
- return out;
525
+ return { data: out, unmatchedFields };
526
+ }
527
+ /**
528
+ * Keep the historical flat projection when every path resolves. A miss gets a
529
+ * wrapper with a reserved discriminator so neither `{}` nor downstream fields
530
+ * named `data` / `projection` can be mistaken for projection feedback.
531
+ */
532
+ function projectionValue(value, fields, outputSchema) {
533
+ const projected = applyFields(value, fields);
534
+ // `$connecta` is reserved at the top level of a projection. Even a fully
535
+ // matched downstream field with that exact name is escaped below `data`, so
536
+ // no user-controlled value can impersonate Connecta's discriminator.
537
+ const reservedCollision = Object.prototype.hasOwnProperty.call(projected.data, "$connecta");
538
+ if (projected.unmatchedFields.length === 0 && !reservedCollision) {
539
+ return projected.data;
540
+ }
541
+ return {
542
+ data: projected.data,
543
+ $connecta: {
544
+ type: "field_projection",
545
+ unmatchedFields: projected.unmatchedFields,
546
+ ...(outputSchema
547
+ ? schemaProjectionFeedback(outputSchema, projected.unmatchedFields)
548
+ : {}),
549
+ },
550
+ };
164
551
  }
165
552
  /** Apply fields to each JSON-parseable text block; non-JSON blocks pass through. */
166
- function applyFieldsToContent(content, fields) {
553
+ function applyFieldsToContent(content, fields, outputSchema) {
167
554
  return content.map((b) => {
168
555
  if (b.type !== "text")
169
556
  return b;
@@ -174,7 +561,10 @@ function applyFieldsToContent(content, fields) {
174
561
  catch {
175
562
  return b;
176
563
  }
177
- return { ...b, text: JSON.stringify(applyFields(parsed, fields)) };
564
+ return {
565
+ ...b,
566
+ text: JSON.stringify(projectionValue(parsed, fields, outputSchema)),
567
+ };
178
568
  });
179
569
  }
180
570
  // --- result-size guard + get_result (feature 1) ---------------------------
@@ -354,7 +744,9 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
354
744
  // here, so the connector simply inherits `globalCap`.
355
745
  const cap = resolveMaxResultBytes(resolved.connector.maxResultBytes, globalCap);
356
746
  if (call.resultMode === "value") {
357
- let value = fields ? applyFields(result, fields) : result;
747
+ let value = fields
748
+ ? projectionValue(result, fields, resolved.definition.outputSchema)
749
+ : result;
358
750
  value = await guardValue(value, results, cap);
359
751
  return {
360
752
  toolResult: jsonResult({ ok: true, data: value }),
@@ -364,11 +756,14 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
364
756
  if (resolved.connector.kind === "mcp") {
365
757
  const mcpResult = result;
366
758
  let content = mcpResult?.content ?? [];
367
- if (fields)
368
- content = applyFieldsToContent(content, fields);
759
+ if (fields) {
760
+ content = applyFieldsToContent(content, fields, resolved.definition.outputSchema);
761
+ }
369
762
  return { toolResult: await guardContent(content, results, cap) };
370
763
  }
371
- const value = fields ? applyFields(result, fields) : result;
764
+ const value = fields
765
+ ? projectionValue(result, fields, resolved.definition.outputSchema)
766
+ : result;
372
767
  return {
373
768
  toolResult: await guardText(serializeResultText(value), results, cap),
374
769
  value,
@@ -806,9 +1201,9 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
806
1201
  };
807
1202
  }
808
1203
  const LIST_DESC = "List connectors with status, cached tool count, and recent real-call health. Use probe=false for a fast inventory; use probe=true (default) only to diagnose live health or authorization.";
809
- const SEARCH_DESC = `Unknown address: use 2–4 distinctive action/object terms, not the full request; omit limit initially (default ${DEFAULT_SEARCH_LIMIT}) and page only if needed, up to ${MAX_SEARCH_LIMIT}. includeSchemas="compact" adds the input and any declared output shape; matches also carry declared annotations. Call directly when sufficient. Empty query browses all.`;
1204
+ const SEARCH_DESC = `Unknown address: use 2–4 distinctive action/object terms, not the full request; omit limit initially (default ${DEFAULT_SEARCH_LIMIT}) and page only if needed, up to ${MAX_SEARCH_LIMIT}. Partial and no-match searches report term coverage and next-step guidance. safety="readOnly" returns only calls available to call_tool and generated code; "approvalRequired" returns everything else; omitted or "all" preserves the complete catalog. This filters results, not authority. includeSchemas="compact" adds the input and any declared output shape, each bounded; inputSchemaTruncated/outputSchemaTruncated mark shapes that need exact retrieval; matches also carry declared annotations. Call directly when sufficient. Empty query browses all.`;
810
1205
  const DESCRIBE_DESC = `Only when search_tools omitted schemas, a compact shape is ambiguous, or exact JSON constraints are needed. Inspects up to ${MAX_DESCRIBE_ADDRESSES} addresses with schemas and annotations; "compact" is default, while "json" preserves exact constraints.`;
811
- const CALL_DESC = 'Use for one tool explicitly annotated readOnlyHint: true. For 2–10 independent read-only calls use batch_call; for dependent steps or data reduction use execute_code when available. Unannotated, write-capable, and destructive tools are refused and require call_destructive_tool. fields selects JSON dot-paths, resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
1206
+ const CALL_DESC = 'Use for one tool explicitly annotated readOnlyHint: true. For 2–10 independent read-only calls use batch_call; for dependent steps or data reduction use execute_code when available. Unannotated, write-capable, and destructive tools are refused and require call_destructive_tool. fields selects JSON dot-paths; any misses return data plus `$connecta` field-projection feedback. resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
812
1207
  const CALL_DESTRUCTIVE_DESC = "Invoke any tool that is not explicitly annotated readOnlyHint: true, including unannotated, write-capable, or destructive tools. The MCP destructiveHint on this meta-tool lets the host request human approval before execution. Use only after reviewing the downstream tool schema and consequences.";
813
1208
  const GET_RESULT_DESC = "Page a truncated result stashed by call_tool/batch_call. Input { id, offset?, maxBytes? } → { text, offset, nextOffset?, totalBytes } sliced by byte offset. maxBytes is a whole number of bytes >= 1 (omit for the deployment default) and offset a whole number of bytes >= 0; an offset inside a multi-byte character is moved back to that character's first byte and the offset served is returned. Unknown/expired id is an error.";
814
1209
  const BATCH_DESC = "Use for 2–10 independent tools explicitly annotated readOnlyHint: true. Calls run in parallel with shared request-scoped clients; use execute_code when available instead for dependencies or in-sandbox reduction. Unannotated, write-capable, and destructive tools are refused. Batch timeout, safe retry, result mode, and diagnostics defaults may be overridden per call. An oversized final envelope returns ordered outcome summaries plus a get_result page handle.";
@@ -826,7 +1221,7 @@ const SKILLS_DESC = 'List or fetch concise guidance for choosing among Connecta
826
1221
  * change what that control measures.
827
1222
  */
828
1223
  const CODE_FIRST_SEARCH_DESC = `${SEARCH_DESC} Expand an ambiguous compact shape, or read exact JSON constraints, with connecta.describe inside execute_code.`;
829
- const CODE_FIRST_CALL_DESC = 'Use for ONE tool explicitly annotated readOnlyHint: true — the cheapest path for a single cold call. For two or more calls, dependent steps, loops, joins, or data reduction use execute_code, whose connecta.call and connecta.batch reach the same tools. Unannotated, write-capable, and destructive tools are refused and require call_destructive_tool. fields selects JSON dot-paths, resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
1224
+ const CODE_FIRST_CALL_DESC = 'Use for ONE tool explicitly annotated readOnlyHint: true — the cheapest path for a single cold call. For two or more calls, dependent steps, loops, joins, or data reduction use execute_code, whose connecta.call and connecta.batch reach the same tools. Unannotated, write-capable, and destructive tools are refused and require call_destructive_tool. fields selects JSON dot-paths; any misses return data plus `$connecta` field-projection feedback. resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
830
1225
  const CODE_FIRST_GET_RESULT_DESC = "Page a truncated result stashed by call_tool or call_destructive_tool; a program's oversized return is not paged, so reduce it in code instead. Input { id, offset?, maxBytes? } → { text, offset, nextOffset?, totalBytes } sliced by byte offset. maxBytes is a whole number of bytes >= 1 (omit for the deployment default) and offset a whole number of bytes >= 0; an offset inside a multi-byte character is moved back to that character's first byte and the offset served is returned. Unknown/expired id is an error.";
831
1226
  /**
832
1227
  * Sentences appended to a meta-tool description only when this connection
@@ -926,6 +1321,9 @@ export function registerMetaTools(server, registry, ctx) {
926
1321
  inputSchema: z.object({
927
1322
  query: z.string().optional(),
928
1323
  connector: z.string().optional(),
1324
+ safety: z
1325
+ .enum(["readOnly", "approvalRequired", "all"])
1326
+ .optional(),
929
1327
  limit: z.number().int().positive().max(MAX_SEARCH_LIMIT).optional(),
930
1328
  offset: z.number().int().nonnegative().optional(),
931
1329
  fullDescriptions: z.boolean().optional(),