@zackbart/connecta 0.19.0 → 0.21.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +3 -1
  3. package/bin/connecta.mjs +23 -6
  4. package/dist/apps-shell.d.ts +10 -12
  5. package/dist/apps-shell.js +29 -220
  6. package/dist/auth/bearer.d.ts +2 -2
  7. package/dist/auth/bearer.js +2 -2
  8. package/dist/auth/clerk.js +1 -0
  9. package/dist/auth/cloudflare-access.d.ts +8 -0
  10. package/dist/auth/cloudflare-access.js +66 -0
  11. package/dist/execute.d.ts +0 -7
  12. package/dist/execute.js +20 -123
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +110 -69
  15. package/dist/invocation.d.ts +1 -1
  16. package/dist/meta-tools.d.ts +0 -1
  17. package/dist/meta-tools.js +10 -495
  18. package/dist/operator-ui/generated.d.ts +2 -2
  19. package/dist/operator-ui/generated.js +1 -1
  20. package/dist/operator-ui/model.d.ts +3 -3
  21. package/dist/operator-ui/view.d.ts +1 -1
  22. package/dist/operator-ui/view.js +6 -3
  23. package/dist/routes/access-tokens.d.ts +1 -1
  24. package/dist/routes/access-tokens.js +2 -2
  25. package/dist/routes/activity.js +2 -2
  26. package/dist/routes/credentials.js +1 -1
  27. package/dist/routes/mcp.js +1 -1
  28. package/dist/routes/oauth.js +1 -1
  29. package/dist/routes/shared.d.ts +4 -4
  30. package/dist/routes/shared.js +10 -10
  31. package/dist/routes/ui.js +12 -9
  32. package/dist/skills.d.ts +1 -1
  33. package/dist/skills.js +11 -8
  34. package/dist/types.d.ts +37 -22
  35. package/dist/ui.d.ts +1 -1
  36. package/dist/ui.js +3 -3
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/documentation/architecture.md +7 -4
  40. package/documentation/auth.md +71 -7
  41. package/documentation/code-mode.md +23 -23
  42. package/documentation/meta-tools.md +26 -50
  43. package/documentation/operations.md +36 -21
  44. package/documentation/operator-ui.md +21 -5
  45. package/documentation/provider-conventions.md +3 -4
  46. package/documentation/upgrading.md +106 -8
  47. package/ethos.md +3 -4
  48. package/examples/worker/README.md +52 -32
  49. package/examples/worker/src/index.ts +32 -38
  50. package/examples/worker/wrangler.jsonc +12 -4
  51. package/package.json +5 -1
  52. package/templates/node/package.json +1 -1
@@ -94,480 +94,6 @@ export function alignEndToCharBoundary(bytes, offset, end, total) {
94
94
  }
95
95
  return e;
96
96
  }
97
- /** Resolve a dot-path and retain misses below every `[]` boundary. */
98
- function resolvePath(value, segments) {
99
- const seg = segments[0];
100
- if (seg === undefined) {
101
- return value === undefined
102
- ? { status: "unmatched" }
103
- : { status: "matched", value };
104
- }
105
- const rest = segments.slice(1);
106
- const isArr = seg.endsWith("[]");
107
- const key = isArr ? seg.slice(0, -2) : seg;
108
- let next = value;
109
- if (key !== "") {
110
- if (value === null || typeof value !== "object") {
111
- return { status: "unmatched" };
112
- }
113
- next = value[key];
114
- }
115
- if (isArr) {
116
- if (!Array.isArray(next))
117
- return { status: "unmatched" };
118
- if (next.length === 0)
119
- return { status: "matched", value: [] };
120
- const elements = next.map((el) => resolvePath(el, rest));
121
- const matched = elements.some((element) => element.status !== "unmatched");
122
- const missed = elements.some((element) => element.status !== "matched");
123
- if (!matched)
124
- return { status: "unmatched" };
125
- return {
126
- status: missed ? "partial" : "matched",
127
- // Undefined placeholders retain the historical array positions in the
128
- // partial value. JSON renders them as null; partialFields says they are
129
- // unresolved rather than genuine downstream nulls.
130
- value: elements.map((element) => element.status === "unmatched" ? undefined : element.value),
131
- };
132
- }
133
- return resolvePath(next, rest);
134
- }
135
- const MAX_PROJECTION_AVAILABLE_FIELDS = 20;
136
- const MAX_PROJECTION_SCHEMA_DEPTH = 12;
137
- const MAX_PROJECTION_SCHEMA_NODES = 200;
138
- const MAX_PROJECTION_SCHEMA_PATHS = 100;
139
- const MAX_PROJECTION_PATH_CHARS = 256;
140
- const MAX_PROJECTION_PATH_BYTES = 512;
141
- const MAX_PROJECTION_TOTAL_PATH_CHARS = 512;
142
- const MAX_PROJECTION_TOTAL_PATH_BYTES = 768;
143
- const JSON_SCHEMA_TYPES = new Set([
144
- "array",
145
- "boolean",
146
- "integer",
147
- "null",
148
- "number",
149
- "object",
150
- "string",
151
- ]);
152
- const NON_SEMANTIC_REF_SIBLINGS = new Set([
153
- "$anchor",
154
- "$comment",
155
- "$defs",
156
- "$id",
157
- "$ref",
158
- "$schema",
159
- "default",
160
- "definitions",
161
- "deprecated",
162
- "description",
163
- "examples",
164
- "readOnly",
165
- "title",
166
- "writeOnly",
167
- ]);
168
- function localSchemaRef(root, ref) {
169
- if (ref === "#")
170
- return root;
171
- if (!ref.startsWith("#/") ||
172
- ref.length > 2_048 ||
173
- /~(?![01])/.test(ref)) {
174
- return undefined;
175
- }
176
- const segments = ref.slice(2).split("/");
177
- if (segments.length > MAX_PROJECTION_SCHEMA_DEPTH)
178
- return undefined;
179
- let current = root;
180
- for (const encoded of segments) {
181
- if (current === null || typeof current !== "object")
182
- return undefined;
183
- const key = encoded.replaceAll("~1", "/").replaceAll("~0", "~");
184
- if (!Object.prototype.hasOwnProperty.call(current, key))
185
- return undefined;
186
- current = current[key];
187
- }
188
- return current;
189
- }
190
- function hasSchemaType(schema, wanted) {
191
- return (schema.type === wanted ||
192
- (Array.isArray(schema.type) && schema.type.includes(wanted)));
193
- }
194
- function selectableFieldName(name) {
195
- return name.length > 0 && !name.includes(".") && !name.endsWith("[]");
196
- }
197
- /**
198
- * Collect selectable output paths without trusting a schema more than JSON
199
- * Schema permits. Traversal is iterative and budgeted before sorting or
200
- * rendering, so a cyclic, extremely deep, or extremely broad downstream
201
- * schema cannot turn projection feedback into unbounded host work.
202
- */
203
- function analyzeSchemaFields(root) {
204
- const pending = [
205
- { schema: root, prefix: "", depth: 0, ancestors: new Set() },
206
- ];
207
- const paths = new Set();
208
- let nodes = 0;
209
- let totalPathChars = 0;
210
- let totalPathBytes = 0;
211
- let complete = true;
212
- let truncated = false;
213
- const addPath = (path) => {
214
- if (paths.has(path))
215
- return true;
216
- // Check UTF-16 length before encoding, so a hostile multi-megabyte key
217
- // never causes a same-sized temporary allocation merely to reject it.
218
- if (path.length > MAX_PROJECTION_PATH_CHARS ||
219
- totalPathChars + path.length > MAX_PROJECTION_TOTAL_PATH_CHARS) {
220
- complete = false;
221
- truncated = true;
222
- return false;
223
- }
224
- const pathBytes = enc.encode(path).length;
225
- if (pathBytes > MAX_PROJECTION_PATH_BYTES ||
226
- totalPathBytes + pathBytes > MAX_PROJECTION_TOTAL_PATH_BYTES ||
227
- paths.size >= MAX_PROJECTION_SCHEMA_PATHS) {
228
- complete = false;
229
- truncated = true;
230
- return false;
231
- }
232
- paths.add(path);
233
- totalPathChars += path.length;
234
- totalPathBytes += pathBytes;
235
- return true;
236
- };
237
- const enqueue = (item) => {
238
- if (nodes + pending.length >= MAX_PROJECTION_SCHEMA_NODES) {
239
- complete = false;
240
- truncated = true;
241
- return false;
242
- }
243
- pending.push(item);
244
- return true;
245
- };
246
- while (pending.length > 0) {
247
- const item = pending.pop();
248
- if (item.depth > MAX_PROJECTION_SCHEMA_DEPTH) {
249
- complete = false;
250
- truncated = true;
251
- continue;
252
- }
253
- if (++nodes > MAX_PROJECTION_SCHEMA_NODES) {
254
- complete = false;
255
- truncated = true;
256
- break;
257
- }
258
- if (item.schema === false)
259
- continue;
260
- if (item.schema === true ||
261
- item.schema === null ||
262
- typeof item.schema !== "object" ||
263
- item.ancestors.has(item.schema)) {
264
- complete = false;
265
- continue;
266
- }
267
- const schema = item.schema;
268
- const ancestors = new Set(item.ancestors).add(item.schema);
269
- let recognized = false;
270
- if (schema.type !== undefined &&
271
- !((typeof schema.type === "string" &&
272
- JSON_SCHEMA_TYPES.has(schema.type)) ||
273
- (Array.isArray(schema.type) &&
274
- schema.type.length > 0 &&
275
- schema.type.every((type) => typeof type === "string" && JSON_SCHEMA_TYPES.has(type))))) {
276
- complete = false;
277
- }
278
- if (schema.$ref !== undefined) {
279
- recognized = true;
280
- let hasSemanticSiblings = false;
281
- for (const key in schema) {
282
- if (Object.prototype.hasOwnProperty.call(schema, key) &&
283
- !NON_SEMANTIC_REF_SIBLINGS.has(key)) {
284
- hasSemanticSiblings = true;
285
- break;
286
- }
287
- }
288
- if (hasSemanticSiblings) {
289
- // Modern JSON Schema applies $ref siblings as an intersection. A
290
- // compact field walker cannot prove that intersection's selectable
291
- // paths, so do not publish paths from either half as available.
292
- complete = false;
293
- continue;
294
- }
295
- const target = typeof schema.$ref === "string"
296
- ? localSchemaRef(root, schema.$ref)
297
- : undefined;
298
- if (target === undefined)
299
- complete = false;
300
- else {
301
- enqueue({
302
- schema: target,
303
- prefix: item.prefix,
304
- depth: item.depth + 1,
305
- ancestors,
306
- });
307
- }
308
- }
309
- for (const keyword of ["allOf", "anyOf", "oneOf"]) {
310
- const variants = schema[keyword];
311
- if (!Array.isArray(variants))
312
- continue;
313
- recognized = true;
314
- // Combining schemas can close or conditionally expose fields in ways
315
- // this compact recovery walker intentionally does not prove.
316
- complete = false;
317
- for (const variant of variants) {
318
- if (!enqueue({
319
- schema: variant,
320
- prefix: item.prefix,
321
- depth: item.depth + 1,
322
- ancestors,
323
- })) {
324
- break;
325
- }
326
- }
327
- }
328
- if (truncated)
329
- break;
330
- const properties = schema.properties;
331
- const propertyRecord = properties !== null &&
332
- typeof properties === "object" &&
333
- !Array.isArray(properties)
334
- ? properties
335
- : undefined;
336
- if (properties !== undefined && propertyRecord === undefined) {
337
- complete = false;
338
- }
339
- const objectShape = hasSchemaType(schema, "object") || propertyRecord !== undefined;
340
- if (objectShape) {
341
- recognized = true;
342
- const patterns = schema.patternProperties;
343
- let hasPatterns = false;
344
- if (patterns !== undefined &&
345
- (patterns === null ||
346
- typeof patterns !== "object" ||
347
- Array.isArray(patterns))) {
348
- complete = false;
349
- }
350
- else if (patterns !== undefined) {
351
- for (const key in patterns) {
352
- if (Object.prototype.hasOwnProperty.call(patterns, key)) {
353
- hasPatterns = true;
354
- break;
355
- }
356
- }
357
- }
358
- if (schema.additionalProperties !== false || hasPatterns) {
359
- complete = false;
360
- }
361
- if (propertyRecord) {
362
- for (const key in propertyRecord) {
363
- if (!Object.prototype.hasOwnProperty.call(propertyRecord, key)) {
364
- continue;
365
- }
366
- if (++nodes > MAX_PROJECTION_SCHEMA_NODES) {
367
- complete = false;
368
- truncated = true;
369
- break;
370
- }
371
- if (key.length > MAX_PROJECTION_PATH_CHARS) {
372
- complete = false;
373
- truncated = true;
374
- break;
375
- }
376
- if (!selectableFieldName(key)) {
377
- complete = false;
378
- continue;
379
- }
380
- const child = propertyRecord[key];
381
- // A false property schema forbids the property; advertising its name
382
- // as selectable would turn an impossible value into a valid hint.
383
- if (child === false)
384
- continue;
385
- const path = item.prefix ? `${item.prefix}.${key}` : key;
386
- if (!addPath(path))
387
- break;
388
- if (!enqueue({
389
- schema: child,
390
- prefix: path,
391
- depth: item.depth + 1,
392
- ancestors,
393
- })) {
394
- break;
395
- }
396
- }
397
- }
398
- }
399
- if (truncated)
400
- break;
401
- const arrayShape = hasSchemaType(schema, "array") ||
402
- schema.items !== undefined ||
403
- schema.prefixItems !== undefined;
404
- if (arrayShape) {
405
- recognized = true;
406
- const arrayPath = `${item.prefix}[]`;
407
- if (addPath(arrayPath)) {
408
- if (Array.isArray(schema.prefixItems)) {
409
- complete = false;
410
- for (const child of schema.prefixItems) {
411
- if (!enqueue({
412
- schema: child,
413
- prefix: arrayPath,
414
- depth: item.depth + 1,
415
- ancestors,
416
- })) {
417
- break;
418
- }
419
- }
420
- }
421
- if (schema.items === undefined) {
422
- if (!Array.isArray(schema.prefixItems))
423
- complete = false;
424
- }
425
- else if (schema.items === true || Array.isArray(schema.items)) {
426
- complete = false;
427
- const children = Array.isArray(schema.items)
428
- ? schema.items
429
- : [];
430
- for (const child of children) {
431
- if (!enqueue({
432
- schema: child,
433
- prefix: arrayPath,
434
- depth: item.depth + 1,
435
- ancestors,
436
- })) {
437
- break;
438
- }
439
- }
440
- }
441
- else if (schema.items !== false) {
442
- enqueue({
443
- schema: schema.items,
444
- prefix: arrayPath,
445
- depth: item.depth + 1,
446
- ancestors,
447
- });
448
- }
449
- }
450
- }
451
- const types = Array.isArray(schema.type)
452
- ? schema.type
453
- : schema.type === undefined
454
- ? []
455
- : [schema.type];
456
- const primitiveOnly = types.length > 0 &&
457
- types.every((type) => type === "string" ||
458
- type === "number" ||
459
- type === "integer" ||
460
- type === "boolean" ||
461
- type === "null");
462
- if (!recognized && !primitiveOnly)
463
- complete = false;
464
- if (truncated)
465
- break;
466
- }
467
- return {
468
- paths: [...paths].sort(),
469
- complete,
470
- truncated,
471
- };
472
- }
473
- function schemaProjectionFeedback(outputSchema, unmatchedFields) {
474
- const analysis = analyzeSchemaFields(outputSchema);
475
- const available = new Set(analysis.paths);
476
- const invalidFields = analysis.complete
477
- ? unmatchedFields.filter((field) => !available.has(field))
478
- : [];
479
- const missingArrayMarker = unmatchedFields.some((field) => analysis.paths.some((availableField) => availableField.includes("[]") &&
480
- availableField.replaceAll("[]", "") === field));
481
- return {
482
- ...(missingArrayMarker
483
- ? {
484
- hint: 'Traverse arrays with [] after the array field name, for example "results[].id".',
485
- }
486
- : {}),
487
- schemaDeclared: true,
488
- schemaCoverage: analysis.complete ? "complete" : "partial",
489
- ...(invalidFields.length > 0 ? { invalidFields } : {}),
490
- ...(analysis.paths.length > 0
491
- ? {
492
- availableFields: analysis.paths.slice(0, MAX_PROJECTION_AVAILABLE_FIELDS),
493
- }
494
- : {}),
495
- ...(analysis.truncated ||
496
- analysis.paths.length > MAX_PROJECTION_AVAILABLE_FIELDS
497
- ? { availableFieldsTruncated: true }
498
- : {}),
499
- };
500
- }
501
- /** Select the given dot-paths, retaining both matches and exact misses. */
502
- function applyFields(value, fields) {
503
- const out = {};
504
- const unmatchedFields = [];
505
- const partialFields = [];
506
- for (const path of fields) {
507
- const resolved = resolvePath(value, path.split("."));
508
- if (resolved.status === "unmatched") {
509
- unmatchedFields.push(path);
510
- }
511
- else {
512
- out[path] = resolved.value;
513
- if (resolved.status === "partial")
514
- partialFields.push(path);
515
- }
516
- }
517
- return { data: out, unmatchedFields, partialFields };
518
- }
519
- /**
520
- * Keep the historical flat projection when every path resolves. A miss gets a
521
- * wrapper with a reserved discriminator so neither `{}` nor downstream fields
522
- * named `data` / `projection` can be mistaken for projection feedback.
523
- */
524
- function projectionValue(value, fields, outputSchema) {
525
- const projected = applyFields(value, fields);
526
- // `$connecta` is reserved at the top level of a projection. Even a fully
527
- // matched downstream field with that exact name is escaped below `data`, so
528
- // no user-controlled value can impersonate Connecta's discriminator.
529
- const reservedCollision = Object.prototype.hasOwnProperty.call(projected.data, "$connecta");
530
- if (projected.unmatchedFields.length === 0 &&
531
- projected.partialFields.length === 0 &&
532
- !reservedCollision) {
533
- return projected.data;
534
- }
535
- const problemFields = [
536
- ...projected.unmatchedFields,
537
- ...projected.partialFields,
538
- ];
539
- return {
540
- data: projected.data,
541
- $connecta: {
542
- type: "field_projection",
543
- unmatchedFields: projected.unmatchedFields,
544
- ...(projected.partialFields.length > 0
545
- ? { partialFields: projected.partialFields }
546
- : {}),
547
- ...(outputSchema
548
- ? schemaProjectionFeedback(outputSchema, problemFields)
549
- : {}),
550
- },
551
- };
552
- }
553
- /** Apply fields to each JSON-parseable text block; non-JSON blocks pass through. */
554
- function applyFieldsToContent(content, fields, outputSchema) {
555
- return content.map((b) => {
556
- if (b.type !== "text")
557
- return b;
558
- let parsed;
559
- try {
560
- parsed = JSON.parse(b.text);
561
- }
562
- catch {
563
- return b;
564
- }
565
- return {
566
- ...b,
567
- text: JSON.stringify(projectionValue(parsed, fields, outputSchema)),
568
- };
569
- });
570
- }
571
97
  // --- result-size guard + get_result (feature 1) ---------------------------
572
98
  /**
573
99
  * The one serialization every result guard measures, stashes, and pages: JSON
@@ -595,7 +121,7 @@ async function stashResult(text, results, totalBytes) {
595
121
  truncated: true,
596
122
  resultId: id,
597
123
  totalBytes,
598
- hint: "use get_result {id, offset} to page, or re-call with fields to select less",
124
+ hint: "use get_result {id, offset} to page the complete direct-call result",
599
125
  nextAction: {
600
126
  tool: "get_result",
601
127
  arguments: { id, offset: 0 },
@@ -734,7 +260,6 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
734
260
  /** MCP adapter: shared invocation semantics plus MCP-only result shaping. */
735
261
  async function runCall(call, source, options = {}) {
736
262
  const results = registry.resultsStorage();
737
- const fields = call.fields && call.fields.length > 0 ? call.fields : null;
738
263
  const timeoutMs = normalizeTimeoutMs(call.timeoutMs) ?? defaultToolTimeoutMs;
739
264
  const outcome = await invocation.invoke(call.address, call.args ?? {}, {
740
265
  source,
@@ -763,25 +288,18 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
763
288
  ...(truncated ? { friction: "result_too_large" } : {}),
764
289
  });
765
290
  if (call.resultMode === "value") {
766
- let value = fields
767
- ? projectionValue(result, fields, resolved.definition.outputSchema)
768
- : result;
291
+ let value = result;
769
292
  const guarded = await guardValue(value, results, cap);
770
293
  value = guarded.result;
771
294
  return processed(jsonResult({ ok: true, data: value }), guarded.truncated, { value });
772
295
  }
773
296
  if (resolved.connector.kind === "mcp") {
774
297
  const mcpResult = result;
775
- let content = mcpResult?.content ?? [];
776
- if (fields) {
777
- content = applyFieldsToContent(content, fields, resolved.definition.outputSchema);
778
- }
298
+ const content = mcpResult?.content ?? [];
779
299
  const guarded = await guardContent(content, results, cap);
780
300
  return processed(guarded.result, guarded.truncated);
781
301
  }
782
- const value = fields
783
- ? projectionValue(result, fields, resolved.definition.outputSchema)
784
- : result;
302
+ const value = result;
785
303
  const guarded = await guardText(serializeResultText(value), results, cap);
786
304
  return processed(guarded.result, guarded.truncated, { value });
787
305
  },
@@ -867,7 +385,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
867
385
  },
868
386
  async callDestructiveTool(args) {
869
387
  // `reason` is read by the host's approval view and stops there — runCall
870
- // forwards only the call fields, so it never reaches the connector.
388
+ // forwards only the call arguments, so it never reaches the connector.
871
389
  return (await runCall(args, "call_destructive_tool", { allowDestructive: true })).toolResult;
872
390
  },
873
391
  async getResult(args) {
@@ -997,12 +515,12 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
997
515
  },
998
516
  };
999
517
  }
1000
- const SEARCH_DESC = `Use top-level search for one unknown-address read before call_tool, or for approval-required work before call_destructive_tool. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" finds direct or program calls; "approvalRequired" finds the fail-closed complement. These filters grant no authority. For multiple, dependent, or reduced read-only calls, use one execute_code program instead. Empty query browses.`;
1001
- const CALL_DESC = 'Call one tool explicitly annotated readOnlyHint: true. Use execute_code for multiple, dependent, or reduced read-only calls. Unannotated or write-capable tools fail closed to call_destructive_tool. fields projects JSON dot-paths; use [] through arrays. A truncated result carries a get_result action.';
518
+ const SEARCH_DESC = `Use top-level search for catalog inspection or approval-required work before call_destructive_tool. Unknown-address read-only work belongs in one execute_code program that searches, calls, and returns the answer. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" finds direct or program calls; "approvalRequired" finds the fail-closed complement. These filters grant no authority. Empty query browses.`;
519
+ const CALL_DESC = 'Call one known-address tool explicitly annotated readOnlyHint: true. Use execute_code for unknown-address, multiple, dependent, or reduced read-only work. Unannotated or write-capable tools fail closed to call_destructive_tool. A truncated result carries a get_result action.';
1002
520
  const CALL_DESTRUCTIVE_DESC = "Call any tool not explicitly annotated readOnlyHint: true. Include a short reason for the human reviewer after checking the schema and consequences. The reason grants no authority and is not sent downstream.";
1003
521
  const GET_RESULT_DESC = "Page a truncated direct-call result by id and byte offset. A program result is never paged; reduce it inside execute_code. Returns text, offset, nextOffset when more remains, and totalBytes.";
1004
522
  const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or reports required deployment configuration. force=true restarts OAuth only; this tool never accepts credentials.";
1005
- const SKILLS_DESC = 'List or fetch on-demand guidance. Fetch usage once per task for program syntax, selection, repair, examples, and runtime details.';
523
+ const SKILLS_DESC = 'List or fetch on-demand guidance. Fetch usage only when the always-loaded instructions are insufficient or a program needs repair.';
1006
524
  /**
1007
525
  * Sentences appended to a meta-tool description only when this connection
1008
526
  * actually has connector guides. Tool descriptions are always-loaded context,
@@ -1043,7 +561,6 @@ const READ_ONLY_LOCAL = {
1043
561
  const CALL_INPUT_SCHEMA = {
1044
562
  address: z.string(),
1045
563
  args: z.record(z.string(), z.unknown()).optional(),
1046
- fields: z.array(z.string()).optional(),
1047
564
  resultMode: z.enum(["mcp", "value"]).optional(),
1048
565
  timeoutMs: z.number().int().positive().optional(),
1049
566
  maxRetries: z.number().int().min(0).max(2).optional(),
@@ -1094,10 +611,8 @@ export function registerMetaTools(server, registry, ctx) {
1094
611
  // call_tool admits only tools that are themselves explicitly read-only;
1095
612
  // anything else is refused and routed to call_destructive_tool.
1096
613
  annotations: READ_ONLY_REMOTE,
1097
- // The trusted program-view shell delegates bounded named reads here.
1098
- // It is already one of the seven model tools; app visibility adds no
1099
- // tool and this handler repeats ordinary fail-closed read admission.
1100
- _meta: { ui: { visibility: ["model", "app"] } },
614
+ // Omission defaults to model + app. Display-only views may call no tool.
615
+ _meta: { ui: { visibility: ["model"] } },
1101
616
  }, async (args) => mt.callTool(args));
1102
617
  server.registerTool("call_destructive_tool", {
1103
618
  description: describedFor(registry, CALL_DESTRUCTIVE_DESC, "destructive"),