borgmcp 4.1.0 → 4.2.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/dist/index.js CHANGED
@@ -257,8 +257,15 @@ export async function main() {
257
257
  isError: true,
258
258
  };
259
259
  }
260
+ const described = {
261
+ name: def.name,
262
+ description: def.description,
263
+ inputSchema: def.inputSchema,
264
+ outputSchema: def.outputSchema ?? null,
265
+ };
260
266
  return {
261
- content: [{ type: 'text', text: JSON.stringify({ name: def.name, description: def.description, inputSchema: def.inputSchema }, null, 2) }],
267
+ content: [{ type: 'text', text: JSON.stringify(described, null, 2) }],
268
+ structuredContent: described,
262
269
  };
263
270
  }
264
271
  // gh#899: borg_tool dispatcher — unwrap to the inner tool and fall through
@@ -306,6 +313,7 @@ export async function main() {
306
313
  text: 'Not connected to a cube. Use `borg_assimilate cube_name="<name>"` to join one.',
307
314
  },
308
315
  ],
316
+ structuredContent: { connected: false },
309
317
  };
310
318
  }
311
319
  seedDisplayIdentity(active);
@@ -358,10 +366,12 @@ export async function main() {
358
366
  : '';
359
367
  // gh#285: version-mismatch nudge when on-disk package is newer.
360
368
  let versionHeader = '';
369
+ let onDiskVersion = null;
361
370
  try {
362
371
  const running = getPackageVersion();
363
372
  const onDisk = getOnDiskVersion();
364
373
  if (running !== 'unknown' && onDisk !== 'unknown' && onDisk !== running) {
374
+ onDiskVersion = onDisk;
365
375
  const [rMaj, rMin, rPat] = running.split('.').map(Number);
366
376
  const [dMaj, dMin, dPat] = onDisk.split('.').map(Number);
367
377
  const onDiskNewer = dMaj > rMaj || (dMaj === rMaj && dMin > rMin) || (dMaj === rMaj && dMin === rMin && dPat > rPat);
@@ -371,7 +381,21 @@ export async function main() {
371
381
  }
372
382
  }
373
383
  catch { /* never break regen */ }
374
- return { content: [{ type: 'text', text: versionHeader + prefix + formatRegenMarkdown(displayedResult, { mode }) }] };
384
+ return {
385
+ content: [{ type: 'text', text: versionHeader + prefix + formatRegenMarkdown(displayedResult, { mode }) }],
386
+ structuredContent: {
387
+ connected: true,
388
+ mode,
389
+ cube: displayedResult.cube,
390
+ drone: displayedResult.drone,
391
+ role: displayedResult.role,
392
+ behind_by: typeof displayedResult.behind_by === 'number' ? displayedResult.behind_by : null,
393
+ decision_topics: (displayedResult.decisions ?? []).map((d) => d.topic),
394
+ running_version: getPackageVersion(),
395
+ on_disk_version: onDiskVersion,
396
+ wake_path_healthy: inboxMonitorHealthy,
397
+ },
398
+ };
375
399
  }
376
400
  case 'borg_assimilate': {
377
401
  const cubeName = args?.cube_name;
@@ -417,6 +441,11 @@ export async function main() {
417
441
  ].join('\n');
418
442
  return {
419
443
  content: [{ type: 'text', text: header + formatRegenMarkdown(displayedResult, { mode: 'full' }) }],
444
+ structuredContent: {
445
+ reattached: true,
446
+ cube_name: displayIdentity.cubeName,
447
+ drone_label: displayIdentity.droneLabel,
448
+ },
420
449
  };
421
450
  }
422
451
  catch (err) {
@@ -426,7 +455,10 @@ export async function main() {
426
455
  }
427
456
  }
428
457
  case 'borg_version': {
429
- return { content: [{ type: 'text', text: `borgmcp ${getPackageVersion()}` }] };
458
+ return {
459
+ content: [{ type: 'text', text: `borgmcp ${getPackageVersion()}` }],
460
+ structuredContent: { version: getPackageVersion() },
461
+ };
430
462
  }
431
463
  case 'borg_playbook': {
432
464
  // gh#912: serve the on-demand operating-playbook chapter (static
@@ -446,7 +478,14 @@ export async function main() {
446
478
  : topic
447
479
  ? `No exact match for "${topic}". Full Borg MCP docs index — WebFetch the URL you need:`
448
480
  : `Borg MCP docs index — WebFetch the URL of the section you need:`;
449
- return { content: [{ type: 'text', text: `${header}\n\n${formatDocsIndex(sections)}` }] };
481
+ return {
482
+ content: [{ type: 'text', text: `${header}\n\n${formatDocsIndex(sections)}` }],
483
+ structuredContent: {
484
+ topic: topic || null,
485
+ matched: matched.length > 0,
486
+ sections: sections.map((s) => ({ slug: s.slug, title: s.title, url: s.url, summary: s.summary })),
487
+ },
488
+ };
450
489
  }
451
490
  case 'borg_whoami': {
452
491
  const active = await requireActiveCube();
@@ -476,7 +515,10 @@ export async function main() {
476
515
  // The invocation-local server truth remains authoritative even if
477
516
  // the display-only persistence refresh cannot be written.
478
517
  }
479
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
518
+ return {
519
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
520
+ structuredContent: { ...result },
521
+ };
480
522
  }
481
523
  case 'borg_cube': {
482
524
  // No-cache invariant (T1.2 — 0.7.0): both `getCubeInfo` and
@@ -529,7 +571,10 @@ export async function main() {
529
571
  }
530
572
  lines.push('');
531
573
  lines.push(getDronePlaybook());
532
- return { content: [{ type: 'text', text: lines.join('\n') }] };
574
+ return {
575
+ content: [{ type: 'text', text: lines.join('\n') }],
576
+ structuredContent: { cube, roles },
577
+ };
533
578
  }
534
579
  case 'borg_role': {
535
580
  // No-cache invariant (T1.2 — 0.7.0): role reads MUST fetch
@@ -544,7 +589,7 @@ export async function main() {
544
589
  ``,
545
590
  role.detailed_description || '_(no detailed description set)_',
546
591
  ].join('\n');
547
- return { content: [{ type: 'text', text }] };
592
+ return { content: [{ type: 'text', text }], structuredContent: { role } };
548
593
  }
549
594
  const { role } = await getRoleInfo(active.sessionToken, active.apiUrl, active.serverTrustIdentity);
550
595
  const text = [
@@ -552,7 +597,7 @@ export async function main() {
552
597
  ``,
553
598
  role.detailed_description || '_(no detailed description set)_',
554
599
  ].join('\n');
555
- return { content: [{ type: 'text', text }] };
600
+ return { content: [{ type: 'text', text }], structuredContent: { role } };
556
601
  }
557
602
  case 'borg_role-rationale': {
558
603
  const active = await requireActiveCube();
@@ -564,7 +609,10 @@ export async function main() {
564
609
  '',
565
610
  result.body || '_(empty)_',
566
611
  ].join('\n');
567
- return { content: [{ type: 'text', text }] };
612
+ return {
613
+ content: [{ type: 'text', text }],
614
+ structuredContent: { role: result.role, section: result.section, body: result.body },
615
+ };
568
616
  }
569
617
  case 'borg_roster': {
570
618
  const active = await requireActiveCube();
@@ -577,7 +625,10 @@ export async function main() {
577
625
  resolvedSince: resolvedSince ?? null,
578
626
  humanAgo,
579
627
  });
580
- return { content: [{ type: 'text', text }] };
628
+ return {
629
+ content: [{ type: 'text', text }],
630
+ structuredContent: { cube_name: active.name, drones, roles, since: resolvedSince ?? null },
631
+ };
581
632
  }
582
633
  case 'borg_stream-status': {
583
634
  // Probe the in-process SSE consumer state. Does NOT require
@@ -616,7 +667,18 @@ export async function main() {
616
667
  cubeName: active?.name ?? null,
617
668
  humanAgo,
618
669
  });
619
- return { content: [{ type: 'text', text: silentInertWarning + text }] };
670
+ return {
671
+ content: [{ type: 'text', text: silentInertWarning + text }],
672
+ structuredContent: {
673
+ status,
674
+ wake_path: wakePath,
675
+ inbox_monitor_healthy: inboxMonitorHealthy,
676
+ inbox_path: inboxPath,
677
+ monitor_state_root: monitorStateRoot,
678
+ drone_label: active?.droneLabel ?? null,
679
+ cube_name: active?.name ?? null,
680
+ },
681
+ };
620
682
  }
621
683
  case 'borg_read-log': {
622
684
  const active = await requireActiveCube();
@@ -658,7 +720,16 @@ export async function main() {
658
720
  lines.push('');
659
721
  lines.push(`⚠ behind_by: ${behind_by} more unread ${behind_by === 1 ? 'entry' : 'entries'} addressed to you — call \`borg_read-log unread_only=true\` again until behind_by=0 so you don't skip messages.`);
660
722
  }
661
- return { content: [{ type: 'text', text: lines.join('\n') }] };
723
+ return {
724
+ content: [{ type: 'text', text: lines.join('\n') }],
725
+ structuredContent: {
726
+ entries,
727
+ drones,
728
+ roles,
729
+ behind_by: typeof behind_by === 'number' ? behind_by : null,
730
+ has_more: has_more === true,
731
+ },
732
+ };
662
733
  }
663
734
  case 'borg_read-entry': {
664
735
  const active = await requireActiveCube();
@@ -670,17 +741,26 @@ export async function main() {
670
741
  '',
671
742
  formatLogEntryMarkdown(entry, droneById, roleById),
672
743
  ].join('\n');
673
- return { content: [{ type: 'text', text }] };
744
+ return {
745
+ content: [{ type: 'text', text }],
746
+ structuredContent: { entry, drones, roles },
747
+ };
674
748
  }
675
749
  case 'borg_put-document': {
676
750
  const active = await requireActiveCube();
677
751
  const result = await putDocument(active.sessionToken, active.apiUrl, args ?? {}, active.serverTrustIdentity);
678
- return { content: [{ type: 'text', text: `Created cube document.\n\n${formatDocument(result.document)}` }] };
752
+ return {
753
+ content: [{ type: 'text', text: `Created cube document.\n\n${formatDocument(result.document)}` }],
754
+ structuredContent: { document: result.document },
755
+ };
679
756
  }
680
757
  case 'borg_get-document': {
681
758
  const active = await requireActiveCube();
682
759
  const result = await getDocument(active.sessionToken, active.apiUrl, args ?? {}, active.serverTrustIdentity);
683
- return { content: [{ type: 'text', text: formatDocument(result.document) }] };
760
+ return {
761
+ content: [{ type: 'text', text: formatDocument(result.document) }],
762
+ structuredContent: { document: result.document },
763
+ };
684
764
  }
685
765
  case 'borg_list-documents': {
686
766
  const active = await requireActiveCube();
@@ -688,12 +768,18 @@ export async function main() {
688
768
  const text = result.documents.length === 0
689
769
  ? `No active or superseded documents in cube "${active.name}".`
690
770
  : result.documents.map(formatDocumentMetadata).join('\n\n');
691
- return { content: [{ type: 'text', text }] };
771
+ return {
772
+ content: [{ type: 'text', text }],
773
+ structuredContent: { documents: result.documents },
774
+ };
692
775
  }
693
776
  case 'borg_remove-document': {
694
777
  const active = await requireActiveCube();
695
778
  const result = await removeDocument(active.sessionToken, active.apiUrl, args ?? {}, active.serverTrustIdentity);
696
- return { content: [{ type: 'text', text: `Removed cube document.\n\n${formatDocumentMetadata(result.document)}` }] };
779
+ return {
780
+ content: [{ type: 'text', text: `Removed cube document.\n\n${formatDocumentMetadata(result.document)}` }],
781
+ structuredContent: { document: result.document },
782
+ };
697
783
  }
698
784
  case 'borg_log': {
699
785
  const message = args?.message;
@@ -719,6 +805,13 @@ export async function main() {
719
805
  text: `Suppressed duplicate ${decision.signal?.toUpperCase()} lifecycle log for ${displayIdentity.droneLabel}; recent cube log already contains this signal.`,
720
806
  },
721
807
  ],
808
+ structuredContent: {
809
+ suppressed: true,
810
+ entry: null,
811
+ recipients: [],
812
+ unreachable_recipients: [],
813
+ advisory: null,
814
+ },
722
815
  };
723
816
  }
724
817
  }
@@ -765,7 +858,16 @@ export async function main() {
765
858
  ? `\nAdvisory: this message exceeded ${result.advisory.threshold_bytes} UTF-8 bytes. Store durable detail with borg_put-document, then cite its full id in borg_log.documents with an explicit borg_log.to audience.`
766
859
  : '';
767
860
  const text = `Logged to cube "${displayIdentity.cubeName}" as ${displayIdentity.droneLabel}. (entry id: ${result.entry.id})${routed}${unreachable}${citations}${advisory}`;
768
- return { content: [{ type: 'text', text }] };
861
+ return {
862
+ content: [{ type: 'text', text }],
863
+ structuredContent: {
864
+ suppressed: false,
865
+ entry: result.entry,
866
+ recipients: routedRecipients,
867
+ unreachable_recipients: result.unreachableRecipients ?? [],
868
+ advisory: result.advisory ?? null,
869
+ },
870
+ };
769
871
  }
770
872
  case 'borg_ack': {
771
873
  const entryId = args?.entry_id;
@@ -787,12 +889,16 @@ export async function main() {
787
889
  : `Acked entry ${entryId} in cube "${active.name}".`,
788
890
  },
789
891
  ],
892
+ structuredContent: { entry_id: entryId, kind, cube_name: active.name },
790
893
  };
791
894
  }
792
895
  case 'borg_ack-status': {
793
896
  const active = await requireActiveCube();
794
897
  const result = await getAckStatus(active.sessionToken, active.apiUrl, args ?? {}, active.serverTrustIdentity);
795
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
898
+ return {
899
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
900
+ structuredContent: { ...result },
901
+ };
796
902
  }
797
903
  case 'borg_decide': {
798
904
  const topic = args?.topic;
@@ -816,6 +922,7 @@ export async function main() {
816
922
  text: `Recorded ratified decision on "${topic}" in cube "${active.name}"${superseded}. Cite it via borg_decisions; it surfaces in borg_regen.`,
817
923
  },
818
924
  ],
925
+ structuredContent: { decision: row, superseded: Boolean(row?.supersedes), cube_name: active.name },
819
926
  };
820
927
  }
821
928
  case 'borg_decisions': {
@@ -829,7 +936,10 @@ export async function main() {
829
936
  : decisions
830
937
  .map((d) => `**${d.topic}:** ${d.decision}${d.rationale ? ` — ${d.rationale}` : ''}`)
831
938
  .join('\n');
832
- return { content: [{ type: 'text', text }] };
939
+ return {
940
+ content: [{ type: 'text', text }],
941
+ structuredContent: { decisions },
942
+ };
833
943
  }
834
944
  case 'borg_remove-decision': {
835
945
  const topic = typeof args?.topic === 'string' ? args.topic : undefined;
@@ -845,15 +955,22 @@ export async function main() {
845
955
  type: 'text',
846
956
  text: `Removed the active ratified decision on "${decision.topic}" from cube "${active.name}".`,
847
957
  }],
958
+ structuredContent: { decision, cube_name: active.name },
848
959
  };
849
960
  }
850
961
  case 'borg_list-cubes': {
851
962
  const { cubes } = await listCubes();
852
963
  if (!cubes.length) {
853
- return { content: [{ type: 'text', text: 'No cubes yet. Use borg_create-cube to make your first one.' }] };
964
+ return {
965
+ content: [{ type: 'text', text: 'No cubes yet. Use borg_create-cube to make your first one.' }],
966
+ structuredContent: { cubes: [] },
967
+ };
854
968
  }
855
969
  const lines = cubes.map((c) => `- **${c.name}** (id: ${c.id})\n ${(c.cube_directive || '_(no directive set)_').split('\n')[0].slice(0, 120)}`);
856
- return { content: [{ type: 'text', text: `Your cubes (${cubes.length}):\n\n${lines.join('\n\n')}` }] };
970
+ return {
971
+ content: [{ type: 'text', text: `Your cubes (${cubes.length}):\n\n${lines.join('\n\n')}` }],
972
+ structuredContent: { cubes },
973
+ };
857
974
  }
858
975
  case 'borg_create-cube': {
859
976
  const name = args?.name;
@@ -892,10 +1009,21 @@ export async function main() {
892
1009
  ? ' Template cube directive applied (operator passed empty).'
893
1010
  : '';
894
1011
  const text = `Created cube **${cube.name}** (id: ${cube.id}) with template **${templateName}** applied — ${summary.created} role(s) created, ${summary.updated} updated.${cubeDirectiveNote} Use borg_assimilate ${cube.name} to join as a drone.`;
895
- return { content: [{ type: 'text', text }] };
1012
+ return {
1013
+ content: [{ type: 'text', text }],
1014
+ structuredContent: {
1015
+ cube,
1016
+ template: templateName,
1017
+ roles_created: summary.created,
1018
+ roles_updated: summary.updated,
1019
+ },
1020
+ };
896
1021
  }
897
1022
  const text = `Created cube **${cube.name}** (id: ${cube.id}). A default "Drone" role was seeded — rename or replace it via borg_update-role / borg_create-role / borg_delete-role. Use borg_assimilate ${cube.name} to join as a drone.`;
898
- return { content: [{ type: 'text', text }] };
1023
+ return {
1024
+ content: [{ type: 'text', text }],
1025
+ structuredContent: { cube, template: null, roles_created: null, roles_updated: null },
1026
+ };
899
1027
  }
900
1028
  case 'borg_update-cube': {
901
1029
  const cubeId = args?.cube_id;
@@ -911,7 +1039,10 @@ export async function main() {
911
1039
  if (Object.keys(updates).length === 0)
912
1040
  throw new Error('Pass at least one of: cube_directive, message_taxonomy.');
913
1041
  const { cube, advisory } = await updateCube(cubeId, updates);
914
- return { content: [{ type: 'text', text: formatUpdatedCubeResult(cube, advisory) }] };
1042
+ return {
1043
+ content: [{ type: 'text', text: formatUpdatedCubeResult(cube, advisory) }],
1044
+ structuredContent: { cube, advisory: advisory ?? null },
1045
+ };
915
1046
  }
916
1047
  case 'borg_patch-taxonomy-class': {
917
1048
  const cubeId = args?.cube_id;
@@ -939,7 +1070,10 @@ export async function main() {
939
1070
  label = String(classDef.class ?? '');
940
1071
  }
941
1072
  const verb = action === 'add' ? 'Added' : action === 'replace' ? 'Replaced' : 'Removed';
942
- return { content: [{ type: 'text', text: `${verb} taxonomy class **${label}** in cube **${cube.name}** (id: ${cube.id}).` }] };
1073
+ return {
1074
+ content: [{ type: 'text', text: `${verb} taxonomy class **${label}** in cube **${cube.name}** (id: ${cube.id}).` }],
1075
+ structuredContent: { action, class: label, cube },
1076
+ };
943
1077
  }
944
1078
  case 'borg_delete-cube': {
945
1079
  const cubeId = args?.cube_id;
@@ -950,7 +1084,10 @@ export async function main() {
950
1084
  throw new CubeDeletionConfirmationError(cubeId, confirmCubeId);
951
1085
  }
952
1086
  await deleteCube(cubeId, confirmCubeId);
953
- return { content: [{ type: 'text', text: `Deleted cube ${cubeId} (and all its roles, drones, log entries).` }] };
1087
+ return {
1088
+ content: [{ type: 'text', text: `Deleted cube ${cubeId} (and all its roles, drones, log entries).` }],
1089
+ structuredContent: { cube_id: cubeId, deleted: true },
1090
+ };
954
1091
  }
955
1092
  case 'borg_create-role': {
956
1093
  const cubeId = args?.cube_id;
@@ -988,7 +1125,10 @@ export async function main() {
988
1125
  role.is_mandatory ? 'mandatory' : null,
989
1126
  ].filter(Boolean).join(', ');
990
1127
  const tag = tags ? ` (${tags})` : '';
991
- return { content: [{ type: 'text', text: `Created role **${role.name}**${tag} (id: ${role.id}) in cube ${cubeId}.` }] };
1128
+ return {
1129
+ content: [{ type: 'text', text: `Created role **${role.name}**${tag} (id: ${role.id}) in cube ${cubeId}.` }],
1130
+ structuredContent: { role, cube_id: cubeId },
1131
+ };
992
1132
  }
993
1133
  case 'borg_update-role': {
994
1134
  const roleId = args?.role_id;
@@ -1016,7 +1156,10 @@ export async function main() {
1016
1156
  if (Object.keys(updates).length === 0)
1017
1157
  throw new Error('Pass at least one of: name, short_description, detailed_description, is_default, is_mandatory, is_human_seat, can_broadcast, receives_all_direct.');
1018
1158
  const { role, advisory } = await updateRole(roleId, updates);
1019
- return { content: [{ type: 'text', text: formatUpdatedRoleResult(role, advisory) }] };
1159
+ return {
1160
+ content: [{ type: 'text', text: formatUpdatedRoleResult(role, advisory) }],
1161
+ structuredContent: { role, advisory: advisory ?? null },
1162
+ };
1020
1163
  }
1021
1164
  case 'borg_patch-role-section': {
1022
1165
  const roleId = args?.role_id;
@@ -1047,26 +1190,45 @@ export async function main() {
1047
1190
  ({ role, advisory } = await patchRoleSection(roleId, { action, heading, body }));
1048
1191
  }
1049
1192
  }
1050
- return { content: [{ type: 'text', text: formatPatchedRoleSectionResult(action, heading, role, advisory) }] };
1193
+ return {
1194
+ content: [{ type: 'text', text: formatPatchedRoleSectionResult(action, heading, role, advisory) }],
1195
+ structuredContent: { action, heading, role, advisory: advisory ?? null },
1196
+ };
1051
1197
  }
1052
1198
  case 'borg_delete-role': {
1053
1199
  const roleId = args?.role_id;
1054
1200
  if (!roleId)
1055
1201
  throw new Error('role_id is required');
1056
1202
  await deleteRole(roleId);
1057
- return { content: [{ type: 'text', text: `Deleted role ${roleId}.` }] };
1203
+ return {
1204
+ content: [{ type: 'text', text: `Deleted role ${roleId}.` }],
1205
+ structuredContent: { role_id: roleId, deleted: true },
1206
+ };
1058
1207
  }
1059
1208
  case 'borg_reassign-drone': {
1209
+ const reassigned = await runReassignDroneTool({
1210
+ droneId: args?.drone_id,
1211
+ roleId: args?.role_id,
1212
+ });
1060
1213
  return {
1061
- content: [{ type: 'text', text: await runReassignDroneTool({
1062
- droneId: args?.drone_id,
1063
- roleId: args?.role_id,
1064
- }) }],
1214
+ content: [{ type: 'text', text: reassigned.text }],
1215
+ structuredContent: {
1216
+ drone: reassigned.drone,
1217
+ role_name: reassigned.roleName,
1218
+ cube_name: reassigned.cubeName,
1219
+ },
1065
1220
  };
1066
1221
  }
1067
1222
  case 'borg_evict-drone': {
1223
+ const evicted = await runEvictDroneTool(args);
1068
1224
  return {
1069
- content: [{ type: 'text', text: await runEvictDroneTool(args) }],
1225
+ content: [{ type: 'text', text: evicted.text }],
1226
+ structuredContent: {
1227
+ drone_id: evicted.droneId,
1228
+ label: evicted.label,
1229
+ cube_name: evicted.cubeName,
1230
+ evicted: true,
1231
+ },
1070
1232
  };
1071
1233
  }
1072
1234
  case 'borg_list-drones': {
@@ -1075,7 +1237,10 @@ export async function main() {
1075
1237
  throw new Error('cube_id is required');
1076
1238
  const { drones, roles } = await getCube(cubeId);
1077
1239
  if (!drones.length) {
1078
- return { content: [{ type: 'text', text: 'No drones in this cube yet.' }] };
1240
+ return {
1241
+ content: [{ type: 'text', text: 'No drones in this cube yet.' }],
1242
+ structuredContent: { cube_id: cubeId, drones: [], roles },
1243
+ };
1079
1244
  }
1080
1245
  const rolesById = new Map(roles.map((r) => [r.id, r]));
1081
1246
  const lines = drones.map((d) => {
@@ -1088,7 +1253,10 @@ export async function main() {
1088
1253
  ...renderRuntimeMetadataLines(d),
1089
1254
  ].join('\n');
1090
1255
  });
1091
- return { content: [{ type: 'text', text: `Drones in cube ${cubeId} (${drones.length}):\n\n_${RUNTIME_METADATA_ADVISORY}_\n\n${lines.join('\n')}` }] };
1256
+ return {
1257
+ content: [{ type: 'text', text: `Drones in cube ${cubeId} (${drones.length}):\n\n_${RUNTIME_METADATA_ADVISORY}_\n\n${lines.join('\n')}` }],
1258
+ structuredContent: { cube_id: cubeId, drones, roles },
1259
+ };
1092
1260
  }
1093
1261
  case 'borg_list-roles': {
1094
1262
  // Sprint 6 / gh#153: surface role IDs to Coordinator-class drones
@@ -1103,7 +1271,10 @@ export async function main() {
1103
1271
  if (!cubeId)
1104
1272
  throw new Error('cube_id is required');
1105
1273
  const roles = await listRoles(cubeId);
1106
- return { content: [{ type: 'text', text: renderRoleList(roles, cubeId) }] };
1274
+ return {
1275
+ content: [{ type: 'text', text: renderRoleList(roles, cubeId) }],
1276
+ structuredContent: { cube_id: cubeId, roles },
1277
+ };
1107
1278
  }
1108
1279
  case 'borg_list-templates': {
1109
1280
  const names = listTemplateNames();
@@ -1111,7 +1282,12 @@ export async function main() {
1111
1282
  const t = getTemplate(n);
1112
1283
  return `- **${n}**: ${t.description}`;
1113
1284
  });
1114
- return { content: [{ type: 'text', text: `Available templates:\n\n${lines.join('\n')}` }] };
1285
+ return {
1286
+ content: [{ type: 'text', text: `Available templates:\n\n${lines.join('\n')}` }],
1287
+ structuredContent: {
1288
+ templates: names.map((n) => ({ name: n, description: getTemplate(n).description })),
1289
+ },
1290
+ };
1115
1291
  }
1116
1292
  case 'borg_sync-roles': {
1117
1293
  const cubeId = args?.cube_id;
@@ -1127,7 +1303,10 @@ export async function main() {
1127
1303
  if (!cubeId)
1128
1304
  throw new Error('cube_id is required');
1129
1305
  const result = await syncRoles(cubeId, templateName, apply, decisions);
1130
- return { content: [{ type: 'text', text: renderSyncRolesResult(result, templateName) }] };
1306
+ return {
1307
+ content: [{ type: 'text', text: renderSyncRolesResult(result, templateName) }],
1308
+ structuredContent: { cube_id: cubeId, template: templateName, apply, result },
1309
+ };
1131
1310
  }
1132
1311
  case 'borg_apply-template': {
1133
1312
  const cubeId = args?.cube_id;
@@ -1147,7 +1326,16 @@ export async function main() {
1147
1326
  noMutation: 'No template fragments were changed.',
1148
1327
  });
1149
1328
  const { summary, cubeDirectiveNote } = await runApplyTemplateTool(cubeId, template, authority);
1150
- return { content: [{ type: 'text', text: `Applied template **${templateName}** to cube ${cubeId} — ${summary.created} role(s) created, ${summary.updated} updated.${cubeDirectiveNote}` }] };
1329
+ return {
1330
+ content: [{ type: 'text', text: `Applied template **${templateName}** to cube ${cubeId} — ${summary.created} role(s) created, ${summary.updated} updated.${cubeDirectiveNote}` }],
1331
+ structuredContent: {
1332
+ cube_id: cubeId,
1333
+ template: templateName,
1334
+ roles_created: summary.created,
1335
+ roles_updated: summary.updated,
1336
+ cube_directive_applied: cubeDirectiveNote !== '',
1337
+ },
1338
+ };
1151
1339
  }
1152
1340
  default:
1153
1341
  throw new Error(`Unknown tool: ${name}`);