borgmcp 4.1.0 → 4.2.1

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
@@ -112,6 +112,17 @@ export function formatUpdatedRoleResult(role, advisory) {
112
112
  const tag = tags ? ` (${tags})` : '';
113
113
  return appendServerAdvisory(`Updated role **${role.name}**${tag} (id: ${role.id}).`, advisory);
114
114
  }
115
+ // gh#496: the unread drain runs on every wake, so its structured payload must
116
+ // stay proportional to the entries it returns. Rosters are deliberately NOT
117
+ // included — entries already carry drone_label/role_name, and borg_roster
118
+ // serves identity mapping when a consumer needs it.
119
+ export function buildReadLogStructuredContent(input) {
120
+ return {
121
+ entries: input.entries,
122
+ behind_by: typeof input.behind_by === 'number' ? input.behind_by : null,
123
+ has_more: input.has_more === true,
124
+ };
125
+ }
115
126
  export function formatPatchedRoleSectionResult(action, heading, role, advisory) {
116
127
  const verb = action === 'replace' ? 'Replaced' : action === 'insert' ? 'Inserted' : 'Deleted';
117
128
  return appendServerAdvisory(`${verb} section **${heading}** in role **${role.name}** (id: ${role.id}).`, advisory);
@@ -257,8 +268,15 @@ export async function main() {
257
268
  isError: true,
258
269
  };
259
270
  }
271
+ const described = {
272
+ name: def.name,
273
+ description: def.description,
274
+ inputSchema: def.inputSchema,
275
+ outputSchema: def.outputSchema ?? null,
276
+ };
260
277
  return {
261
- content: [{ type: 'text', text: JSON.stringify({ name: def.name, description: def.description, inputSchema: def.inputSchema }, null, 2) }],
278
+ content: [{ type: 'text', text: JSON.stringify(described, null, 2) }],
279
+ structuredContent: described,
262
280
  };
263
281
  }
264
282
  // gh#899: borg_tool dispatcher — unwrap to the inner tool and fall through
@@ -306,6 +324,7 @@ export async function main() {
306
324
  text: 'Not connected to a cube. Use `borg_assimilate cube_name="<name>"` to join one.',
307
325
  },
308
326
  ],
327
+ structuredContent: { connected: false },
309
328
  };
310
329
  }
311
330
  seedDisplayIdentity(active);
@@ -358,10 +377,12 @@ export async function main() {
358
377
  : '';
359
378
  // gh#285: version-mismatch nudge when on-disk package is newer.
360
379
  let versionHeader = '';
380
+ let onDiskVersion = null;
361
381
  try {
362
382
  const running = getPackageVersion();
363
383
  const onDisk = getOnDiskVersion();
364
384
  if (running !== 'unknown' && onDisk !== 'unknown' && onDisk !== running) {
385
+ onDiskVersion = onDisk;
365
386
  const [rMaj, rMin, rPat] = running.split('.').map(Number);
366
387
  const [dMaj, dMin, dPat] = onDisk.split('.').map(Number);
367
388
  const onDiskNewer = dMaj > rMaj || (dMaj === rMaj && dMin > rMin) || (dMaj === rMaj && dMin === rMin && dPat > rPat);
@@ -371,7 +392,21 @@ export async function main() {
371
392
  }
372
393
  }
373
394
  catch { /* never break regen */ }
374
- return { content: [{ type: 'text', text: versionHeader + prefix + formatRegenMarkdown(displayedResult, { mode }) }] };
395
+ return {
396
+ content: [{ type: 'text', text: versionHeader + prefix + formatRegenMarkdown(displayedResult, { mode }) }],
397
+ structuredContent: {
398
+ connected: true,
399
+ mode,
400
+ cube: displayedResult.cube,
401
+ drone: displayedResult.drone,
402
+ role: displayedResult.role,
403
+ behind_by: typeof displayedResult.behind_by === 'number' ? displayedResult.behind_by : null,
404
+ decision_topics: (displayedResult.decisions ?? []).map((d) => d.topic),
405
+ running_version: getPackageVersion(),
406
+ on_disk_version: onDiskVersion,
407
+ wake_path_healthy: inboxMonitorHealthy,
408
+ },
409
+ };
375
410
  }
376
411
  case 'borg_assimilate': {
377
412
  const cubeName = args?.cube_name;
@@ -417,6 +452,11 @@ export async function main() {
417
452
  ].join('\n');
418
453
  return {
419
454
  content: [{ type: 'text', text: header + formatRegenMarkdown(displayedResult, { mode: 'full' }) }],
455
+ structuredContent: {
456
+ reattached: true,
457
+ cube_name: displayIdentity.cubeName,
458
+ drone_label: displayIdentity.droneLabel,
459
+ },
420
460
  };
421
461
  }
422
462
  catch (err) {
@@ -426,7 +466,10 @@ export async function main() {
426
466
  }
427
467
  }
428
468
  case 'borg_version': {
429
- return { content: [{ type: 'text', text: `borgmcp ${getPackageVersion()}` }] };
469
+ return {
470
+ content: [{ type: 'text', text: `borgmcp ${getPackageVersion()}` }],
471
+ structuredContent: { version: getPackageVersion() },
472
+ };
430
473
  }
431
474
  case 'borg_playbook': {
432
475
  // gh#912: serve the on-demand operating-playbook chapter (static
@@ -446,7 +489,14 @@ export async function main() {
446
489
  : topic
447
490
  ? `No exact match for "${topic}". Full Borg MCP docs index — WebFetch the URL you need:`
448
491
  : `Borg MCP docs index — WebFetch the URL of the section you need:`;
449
- return { content: [{ type: 'text', text: `${header}\n\n${formatDocsIndex(sections)}` }] };
492
+ return {
493
+ content: [{ type: 'text', text: `${header}\n\n${formatDocsIndex(sections)}` }],
494
+ structuredContent: {
495
+ topic: topic || null,
496
+ matched: matched.length > 0,
497
+ sections: sections.map((s) => ({ slug: s.slug, title: s.title, url: s.url, summary: s.summary })),
498
+ },
499
+ };
450
500
  }
451
501
  case 'borg_whoami': {
452
502
  const active = await requireActiveCube();
@@ -476,7 +526,10 @@ export async function main() {
476
526
  // The invocation-local server truth remains authoritative even if
477
527
  // the display-only persistence refresh cannot be written.
478
528
  }
479
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
529
+ return {
530
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
531
+ structuredContent: { ...result },
532
+ };
480
533
  }
481
534
  case 'borg_cube': {
482
535
  // No-cache invariant (T1.2 — 0.7.0): both `getCubeInfo` and
@@ -529,7 +582,10 @@ export async function main() {
529
582
  }
530
583
  lines.push('');
531
584
  lines.push(getDronePlaybook());
532
- return { content: [{ type: 'text', text: lines.join('\n') }] };
585
+ return {
586
+ content: [{ type: 'text', text: lines.join('\n') }],
587
+ structuredContent: { cube, roles },
588
+ };
533
589
  }
534
590
  case 'borg_role': {
535
591
  // No-cache invariant (T1.2 — 0.7.0): role reads MUST fetch
@@ -544,7 +600,7 @@ export async function main() {
544
600
  ``,
545
601
  role.detailed_description || '_(no detailed description set)_',
546
602
  ].join('\n');
547
- return { content: [{ type: 'text', text }] };
603
+ return { content: [{ type: 'text', text }], structuredContent: { role } };
548
604
  }
549
605
  const { role } = await getRoleInfo(active.sessionToken, active.apiUrl, active.serverTrustIdentity);
550
606
  const text = [
@@ -552,7 +608,7 @@ export async function main() {
552
608
  ``,
553
609
  role.detailed_description || '_(no detailed description set)_',
554
610
  ].join('\n');
555
- return { content: [{ type: 'text', text }] };
611
+ return { content: [{ type: 'text', text }], structuredContent: { role } };
556
612
  }
557
613
  case 'borg_role-rationale': {
558
614
  const active = await requireActiveCube();
@@ -564,7 +620,10 @@ export async function main() {
564
620
  '',
565
621
  result.body || '_(empty)_',
566
622
  ].join('\n');
567
- return { content: [{ type: 'text', text }] };
623
+ return {
624
+ content: [{ type: 'text', text }],
625
+ structuredContent: { role: result.role, section: result.section, body: result.body },
626
+ };
568
627
  }
569
628
  case 'borg_roster': {
570
629
  const active = await requireActiveCube();
@@ -577,7 +636,10 @@ export async function main() {
577
636
  resolvedSince: resolvedSince ?? null,
578
637
  humanAgo,
579
638
  });
580
- return { content: [{ type: 'text', text }] };
639
+ return {
640
+ content: [{ type: 'text', text }],
641
+ structuredContent: { cube_name: active.name, drones, roles, since: resolvedSince ?? null },
642
+ };
581
643
  }
582
644
  case 'borg_stream-status': {
583
645
  // Probe the in-process SSE consumer state. Does NOT require
@@ -616,7 +678,18 @@ export async function main() {
616
678
  cubeName: active?.name ?? null,
617
679
  humanAgo,
618
680
  });
619
- return { content: [{ type: 'text', text: silentInertWarning + text }] };
681
+ return {
682
+ content: [{ type: 'text', text: silentInertWarning + text }],
683
+ structuredContent: {
684
+ status,
685
+ wake_path: wakePath,
686
+ inbox_monitor_healthy: inboxMonitorHealthy,
687
+ inbox_path: inboxPath,
688
+ monitor_state_root: monitorStateRoot,
689
+ drone_label: active?.droneLabel ?? null,
690
+ cube_name: active?.name ?? null,
691
+ },
692
+ };
620
693
  }
621
694
  case 'borg_read-log': {
622
695
  const active = await requireActiveCube();
@@ -658,7 +731,10 @@ export async function main() {
658
731
  lines.push('');
659
732
  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
733
  }
661
- return { content: [{ type: 'text', text: lines.join('\n') }] };
734
+ return {
735
+ content: [{ type: 'text', text: lines.join('\n') }],
736
+ structuredContent: buildReadLogStructuredContent({ entries, behind_by, has_more }),
737
+ };
662
738
  }
663
739
  case 'borg_read-entry': {
664
740
  const active = await requireActiveCube();
@@ -670,17 +746,26 @@ export async function main() {
670
746
  '',
671
747
  formatLogEntryMarkdown(entry, droneById, roleById),
672
748
  ].join('\n');
673
- return { content: [{ type: 'text', text }] };
749
+ return {
750
+ content: [{ type: 'text', text }],
751
+ structuredContent: { entry, drones, roles },
752
+ };
674
753
  }
675
754
  case 'borg_put-document': {
676
755
  const active = await requireActiveCube();
677
756
  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)}` }] };
757
+ return {
758
+ content: [{ type: 'text', text: `Created cube document.\n\n${formatDocument(result.document)}` }],
759
+ structuredContent: { document: result.document },
760
+ };
679
761
  }
680
762
  case 'borg_get-document': {
681
763
  const active = await requireActiveCube();
682
764
  const result = await getDocument(active.sessionToken, active.apiUrl, args ?? {}, active.serverTrustIdentity);
683
- return { content: [{ type: 'text', text: formatDocument(result.document) }] };
765
+ return {
766
+ content: [{ type: 'text', text: formatDocument(result.document) }],
767
+ structuredContent: { document: result.document },
768
+ };
684
769
  }
685
770
  case 'borg_list-documents': {
686
771
  const active = await requireActiveCube();
@@ -688,12 +773,18 @@ export async function main() {
688
773
  const text = result.documents.length === 0
689
774
  ? `No active or superseded documents in cube "${active.name}".`
690
775
  : result.documents.map(formatDocumentMetadata).join('\n\n');
691
- return { content: [{ type: 'text', text }] };
776
+ return {
777
+ content: [{ type: 'text', text }],
778
+ structuredContent: { documents: result.documents },
779
+ };
692
780
  }
693
781
  case 'borg_remove-document': {
694
782
  const active = await requireActiveCube();
695
783
  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)}` }] };
784
+ return {
785
+ content: [{ type: 'text', text: `Removed cube document.\n\n${formatDocumentMetadata(result.document)}` }],
786
+ structuredContent: { document: result.document },
787
+ };
697
788
  }
698
789
  case 'borg_log': {
699
790
  const message = args?.message;
@@ -719,6 +810,13 @@ export async function main() {
719
810
  text: `Suppressed duplicate ${decision.signal?.toUpperCase()} lifecycle log for ${displayIdentity.droneLabel}; recent cube log already contains this signal.`,
720
811
  },
721
812
  ],
813
+ structuredContent: {
814
+ suppressed: true,
815
+ entry: null,
816
+ recipients: [],
817
+ unreachable_recipients: [],
818
+ advisory: null,
819
+ },
722
820
  };
723
821
  }
724
822
  }
@@ -765,7 +863,16 @@ export async function main() {
765
863
  ? `\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
864
  : '';
767
865
  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 }] };
866
+ return {
867
+ content: [{ type: 'text', text }],
868
+ structuredContent: {
869
+ suppressed: false,
870
+ entry: result.entry,
871
+ recipients: routedRecipients,
872
+ unreachable_recipients: result.unreachableRecipients ?? [],
873
+ advisory: result.advisory ?? null,
874
+ },
875
+ };
769
876
  }
770
877
  case 'borg_ack': {
771
878
  const entryId = args?.entry_id;
@@ -787,12 +894,16 @@ export async function main() {
787
894
  : `Acked entry ${entryId} in cube "${active.name}".`,
788
895
  },
789
896
  ],
897
+ structuredContent: { entry_id: entryId, kind, cube_name: active.name },
790
898
  };
791
899
  }
792
900
  case 'borg_ack-status': {
793
901
  const active = await requireActiveCube();
794
902
  const result = await getAckStatus(active.sessionToken, active.apiUrl, args ?? {}, active.serverTrustIdentity);
795
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
903
+ return {
904
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
905
+ structuredContent: { ...result },
906
+ };
796
907
  }
797
908
  case 'borg_decide': {
798
909
  const topic = args?.topic;
@@ -816,6 +927,7 @@ export async function main() {
816
927
  text: `Recorded ratified decision on "${topic}" in cube "${active.name}"${superseded}. Cite it via borg_decisions; it surfaces in borg_regen.`,
817
928
  },
818
929
  ],
930
+ structuredContent: { decision: row, superseded: Boolean(row?.supersedes), cube_name: active.name },
819
931
  };
820
932
  }
821
933
  case 'borg_decisions': {
@@ -829,7 +941,10 @@ export async function main() {
829
941
  : decisions
830
942
  .map((d) => `**${d.topic}:** ${d.decision}${d.rationale ? ` — ${d.rationale}` : ''}`)
831
943
  .join('\n');
832
- return { content: [{ type: 'text', text }] };
944
+ return {
945
+ content: [{ type: 'text', text }],
946
+ structuredContent: { decisions },
947
+ };
833
948
  }
834
949
  case 'borg_remove-decision': {
835
950
  const topic = typeof args?.topic === 'string' ? args.topic : undefined;
@@ -845,15 +960,22 @@ export async function main() {
845
960
  type: 'text',
846
961
  text: `Removed the active ratified decision on "${decision.topic}" from cube "${active.name}".`,
847
962
  }],
963
+ structuredContent: { decision, cube_name: active.name },
848
964
  };
849
965
  }
850
966
  case 'borg_list-cubes': {
851
967
  const { cubes } = await listCubes();
852
968
  if (!cubes.length) {
853
- return { content: [{ type: 'text', text: 'No cubes yet. Use borg_create-cube to make your first one.' }] };
969
+ return {
970
+ content: [{ type: 'text', text: 'No cubes yet. Use borg_create-cube to make your first one.' }],
971
+ structuredContent: { cubes: [] },
972
+ };
854
973
  }
855
974
  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')}` }] };
975
+ return {
976
+ content: [{ type: 'text', text: `Your cubes (${cubes.length}):\n\n${lines.join('\n\n')}` }],
977
+ structuredContent: { cubes },
978
+ };
857
979
  }
858
980
  case 'borg_create-cube': {
859
981
  const name = args?.name;
@@ -892,10 +1014,21 @@ export async function main() {
892
1014
  ? ' Template cube directive applied (operator passed empty).'
893
1015
  : '';
894
1016
  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 }] };
1017
+ return {
1018
+ content: [{ type: 'text', text }],
1019
+ structuredContent: {
1020
+ cube,
1021
+ template: templateName,
1022
+ roles_created: summary.created,
1023
+ roles_updated: summary.updated,
1024
+ },
1025
+ };
896
1026
  }
897
1027
  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 }] };
1028
+ return {
1029
+ content: [{ type: 'text', text }],
1030
+ structuredContent: { cube, template: null, roles_created: null, roles_updated: null },
1031
+ };
899
1032
  }
900
1033
  case 'borg_update-cube': {
901
1034
  const cubeId = args?.cube_id;
@@ -911,7 +1044,10 @@ export async function main() {
911
1044
  if (Object.keys(updates).length === 0)
912
1045
  throw new Error('Pass at least one of: cube_directive, message_taxonomy.');
913
1046
  const { cube, advisory } = await updateCube(cubeId, updates);
914
- return { content: [{ type: 'text', text: formatUpdatedCubeResult(cube, advisory) }] };
1047
+ return {
1048
+ content: [{ type: 'text', text: formatUpdatedCubeResult(cube, advisory) }],
1049
+ structuredContent: { cube, advisory: advisory ?? null },
1050
+ };
915
1051
  }
916
1052
  case 'borg_patch-taxonomy-class': {
917
1053
  const cubeId = args?.cube_id;
@@ -939,7 +1075,10 @@ export async function main() {
939
1075
  label = String(classDef.class ?? '');
940
1076
  }
941
1077
  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}).` }] };
1078
+ return {
1079
+ content: [{ type: 'text', text: `${verb} taxonomy class **${label}** in cube **${cube.name}** (id: ${cube.id}).` }],
1080
+ structuredContent: { action, class: label, cube },
1081
+ };
943
1082
  }
944
1083
  case 'borg_delete-cube': {
945
1084
  const cubeId = args?.cube_id;
@@ -950,7 +1089,10 @@ export async function main() {
950
1089
  throw new CubeDeletionConfirmationError(cubeId, confirmCubeId);
951
1090
  }
952
1091
  await deleteCube(cubeId, confirmCubeId);
953
- return { content: [{ type: 'text', text: `Deleted cube ${cubeId} (and all its roles, drones, log entries).` }] };
1092
+ return {
1093
+ content: [{ type: 'text', text: `Deleted cube ${cubeId} (and all its roles, drones, log entries).` }],
1094
+ structuredContent: { cube_id: cubeId, deleted: true },
1095
+ };
954
1096
  }
955
1097
  case 'borg_create-role': {
956
1098
  const cubeId = args?.cube_id;
@@ -988,7 +1130,10 @@ export async function main() {
988
1130
  role.is_mandatory ? 'mandatory' : null,
989
1131
  ].filter(Boolean).join(', ');
990
1132
  const tag = tags ? ` (${tags})` : '';
991
- return { content: [{ type: 'text', text: `Created role **${role.name}**${tag} (id: ${role.id}) in cube ${cubeId}.` }] };
1133
+ return {
1134
+ content: [{ type: 'text', text: `Created role **${role.name}**${tag} (id: ${role.id}) in cube ${cubeId}.` }],
1135
+ structuredContent: { role, cube_id: cubeId },
1136
+ };
992
1137
  }
993
1138
  case 'borg_update-role': {
994
1139
  const roleId = args?.role_id;
@@ -1016,7 +1161,10 @@ export async function main() {
1016
1161
  if (Object.keys(updates).length === 0)
1017
1162
  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
1163
  const { role, advisory } = await updateRole(roleId, updates);
1019
- return { content: [{ type: 'text', text: formatUpdatedRoleResult(role, advisory) }] };
1164
+ return {
1165
+ content: [{ type: 'text', text: formatUpdatedRoleResult(role, advisory) }],
1166
+ structuredContent: { role, advisory: advisory ?? null },
1167
+ };
1020
1168
  }
1021
1169
  case 'borg_patch-role-section': {
1022
1170
  const roleId = args?.role_id;
@@ -1047,26 +1195,45 @@ export async function main() {
1047
1195
  ({ role, advisory } = await patchRoleSection(roleId, { action, heading, body }));
1048
1196
  }
1049
1197
  }
1050
- return { content: [{ type: 'text', text: formatPatchedRoleSectionResult(action, heading, role, advisory) }] };
1198
+ return {
1199
+ content: [{ type: 'text', text: formatPatchedRoleSectionResult(action, heading, role, advisory) }],
1200
+ structuredContent: { action, heading, role, advisory: advisory ?? null },
1201
+ };
1051
1202
  }
1052
1203
  case 'borg_delete-role': {
1053
1204
  const roleId = args?.role_id;
1054
1205
  if (!roleId)
1055
1206
  throw new Error('role_id is required');
1056
1207
  await deleteRole(roleId);
1057
- return { content: [{ type: 'text', text: `Deleted role ${roleId}.` }] };
1208
+ return {
1209
+ content: [{ type: 'text', text: `Deleted role ${roleId}.` }],
1210
+ structuredContent: { role_id: roleId, deleted: true },
1211
+ };
1058
1212
  }
1059
1213
  case 'borg_reassign-drone': {
1214
+ const reassigned = await runReassignDroneTool({
1215
+ droneId: args?.drone_id,
1216
+ roleId: args?.role_id,
1217
+ });
1060
1218
  return {
1061
- content: [{ type: 'text', text: await runReassignDroneTool({
1062
- droneId: args?.drone_id,
1063
- roleId: args?.role_id,
1064
- }) }],
1219
+ content: [{ type: 'text', text: reassigned.text }],
1220
+ structuredContent: {
1221
+ drone: reassigned.drone,
1222
+ role_name: reassigned.roleName,
1223
+ cube_name: reassigned.cubeName,
1224
+ },
1065
1225
  };
1066
1226
  }
1067
1227
  case 'borg_evict-drone': {
1228
+ const evicted = await runEvictDroneTool(args);
1068
1229
  return {
1069
- content: [{ type: 'text', text: await runEvictDroneTool(args) }],
1230
+ content: [{ type: 'text', text: evicted.text }],
1231
+ structuredContent: {
1232
+ drone_id: evicted.droneId,
1233
+ label: evicted.label,
1234
+ cube_name: evicted.cubeName,
1235
+ evicted: true,
1236
+ },
1070
1237
  };
1071
1238
  }
1072
1239
  case 'borg_list-drones': {
@@ -1075,7 +1242,10 @@ export async function main() {
1075
1242
  throw new Error('cube_id is required');
1076
1243
  const { drones, roles } = await getCube(cubeId);
1077
1244
  if (!drones.length) {
1078
- return { content: [{ type: 'text', text: 'No drones in this cube yet.' }] };
1245
+ return {
1246
+ content: [{ type: 'text', text: 'No drones in this cube yet.' }],
1247
+ structuredContent: { cube_id: cubeId, drones: [], roles },
1248
+ };
1079
1249
  }
1080
1250
  const rolesById = new Map(roles.map((r) => [r.id, r]));
1081
1251
  const lines = drones.map((d) => {
@@ -1088,7 +1258,10 @@ export async function main() {
1088
1258
  ...renderRuntimeMetadataLines(d),
1089
1259
  ].join('\n');
1090
1260
  });
1091
- return { content: [{ type: 'text', text: `Drones in cube ${cubeId} (${drones.length}):\n\n_${RUNTIME_METADATA_ADVISORY}_\n\n${lines.join('\n')}` }] };
1261
+ return {
1262
+ content: [{ type: 'text', text: `Drones in cube ${cubeId} (${drones.length}):\n\n_${RUNTIME_METADATA_ADVISORY}_\n\n${lines.join('\n')}` }],
1263
+ structuredContent: { cube_id: cubeId, drones, roles },
1264
+ };
1092
1265
  }
1093
1266
  case 'borg_list-roles': {
1094
1267
  // Sprint 6 / gh#153: surface role IDs to Coordinator-class drones
@@ -1103,7 +1276,10 @@ export async function main() {
1103
1276
  if (!cubeId)
1104
1277
  throw new Error('cube_id is required');
1105
1278
  const roles = await listRoles(cubeId);
1106
- return { content: [{ type: 'text', text: renderRoleList(roles, cubeId) }] };
1279
+ return {
1280
+ content: [{ type: 'text', text: renderRoleList(roles, cubeId) }],
1281
+ structuredContent: { cube_id: cubeId, roles },
1282
+ };
1107
1283
  }
1108
1284
  case 'borg_list-templates': {
1109
1285
  const names = listTemplateNames();
@@ -1111,7 +1287,12 @@ export async function main() {
1111
1287
  const t = getTemplate(n);
1112
1288
  return `- **${n}**: ${t.description}`;
1113
1289
  });
1114
- return { content: [{ type: 'text', text: `Available templates:\n\n${lines.join('\n')}` }] };
1290
+ return {
1291
+ content: [{ type: 'text', text: `Available templates:\n\n${lines.join('\n')}` }],
1292
+ structuredContent: {
1293
+ templates: names.map((n) => ({ name: n, description: getTemplate(n).description })),
1294
+ },
1295
+ };
1115
1296
  }
1116
1297
  case 'borg_sync-roles': {
1117
1298
  const cubeId = args?.cube_id;
@@ -1127,7 +1308,10 @@ export async function main() {
1127
1308
  if (!cubeId)
1128
1309
  throw new Error('cube_id is required');
1129
1310
  const result = await syncRoles(cubeId, templateName, apply, decisions);
1130
- return { content: [{ type: 'text', text: renderSyncRolesResult(result, templateName) }] };
1311
+ return {
1312
+ content: [{ type: 'text', text: renderSyncRolesResult(result, templateName) }],
1313
+ structuredContent: { cube_id: cubeId, template: templateName, apply, result },
1314
+ };
1131
1315
  }
1132
1316
  case 'borg_apply-template': {
1133
1317
  const cubeId = args?.cube_id;
@@ -1147,7 +1331,16 @@ export async function main() {
1147
1331
  noMutation: 'No template fragments were changed.',
1148
1332
  });
1149
1333
  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}` }] };
1334
+ return {
1335
+ content: [{ type: 'text', text: `Applied template **${templateName}** to cube ${cubeId} — ${summary.created} role(s) created, ${summary.updated} updated.${cubeDirectiveNote}` }],
1336
+ structuredContent: {
1337
+ cube_id: cubeId,
1338
+ template: templateName,
1339
+ roles_created: summary.created,
1340
+ roles_updated: summary.updated,
1341
+ cube_directive_applied: cubeDirectiveNote !== '',
1342
+ },
1343
+ };
1151
1344
  }
1152
1345
  default:
1153
1346
  throw new Error(`Unknown tool: ${name}`);