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.
@@ -9,6 +9,19 @@
9
9
  */
10
10
  import { DECISION_TEXT_MAX_BYTES, DOCUMENT_CONTENT_TYPES } from 'borgmcp-shared/protocol';
11
11
 
12
+ /**
13
+ * gh#492: JSON Schema contract for a tool's `structuredContent`. Success
14
+ * results conform to it; errors stay text-first. `additionalProperties` is
15
+ * left open everywhere so additive server fields never invalidate a
16
+ * conforming result.
17
+ */
18
+ export interface OutputSchema {
19
+ type: 'object';
20
+ description?: string;
21
+ properties: Record<string, any>;
22
+ required?: string[];
23
+ }
24
+
12
25
  export interface ToolManifestEntry {
13
26
  name: string;
14
27
  description: string;
@@ -18,9 +31,10 @@ export interface ToolManifestEntry {
18
31
  required?: string[];
19
32
  oneOf?: Array<{ required: string[] }>;
20
33
  };
34
+ outputSchema?: OutputSchema;
21
35
  }
22
36
 
23
- export const TOOL_MANIFEST: ToolManifestEntry[] = [
37
+ const BASE_TOOL_MANIFEST: ToolManifestEntry[] = [
24
38
  {
25
39
  name: 'borg_regen',
26
40
  description:
@@ -698,3 +712,534 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
698
712
  },
699
713
  },
700
714
  ];
715
+
716
+ // ---------------------------------------------------------------------------
717
+ // gh#492: outputSchema per typed tool. Shapes mirror the borgmcp-shared
718
+ // protocol result contracts (protocol/coordination, protocol/types,
719
+ // protocol/documents); the client builds structuredContent from the same
720
+ // source objects its text renderer consumes. `borg_tool` declares no schema
721
+ // (its output is the selected inner tool's result) and `borg_playbook` is a
722
+ // deliberate text-only prose chapter.
723
+ // ---------------------------------------------------------------------------
724
+
725
+ const DOCUMENT_CITATION_OUTPUT = {
726
+ type: 'object',
727
+ properties: {
728
+ id: { type: 'string' },
729
+ title: { type: 'string' },
730
+ size_bytes: { type: 'number' },
731
+ state: { type: 'string' },
732
+ },
733
+ required: ['id', 'title'],
734
+ };
735
+
736
+ const DOCUMENT_METADATA_OUTPUT = {
737
+ type: 'object',
738
+ properties: {
739
+ id: { type: 'string' },
740
+ title: { type: 'string' },
741
+ size_bytes: { type: 'number' },
742
+ state: { type: 'string' },
743
+ content_type: { type: 'string' },
744
+ supersedes: { type: ['string', 'null'] },
745
+ superseded_by: { type: ['string', 'null'] },
746
+ author: { type: 'object' },
747
+ created_at: { type: 'string' },
748
+ removed_by: { type: ['object', 'null'] },
749
+ removed_at: { type: ['string', 'null'] },
750
+ },
751
+ required: ['id', 'title', 'state'],
752
+ };
753
+
754
+ const DOCUMENT_OUTPUT = {
755
+ ...DOCUMENT_METADATA_OUTPUT,
756
+ properties: { ...DOCUMENT_METADATA_OUTPUT.properties, content: { type: 'string' } },
757
+ };
758
+
759
+ const LOG_ENTRY_OUTPUT = {
760
+ type: 'object',
761
+ description: 'Enriched activity-log entry.',
762
+ properties: {
763
+ id: { type: 'string' },
764
+ cube_id: { type: 'string' },
765
+ drone_id: { type: ['string', 'null'] },
766
+ message: { type: 'string' },
767
+ visibility: { type: 'string', enum: ['broadcast', 'direct'] },
768
+ created_at: { type: 'string' },
769
+ drone_label: { type: ['string', 'null'] },
770
+ role_name: { type: ['string', 'null'] },
771
+ recipient_drone_ids: { type: 'array', items: { type: 'string' } },
772
+ documents: { type: 'array', items: DOCUMENT_CITATION_OUTPUT },
773
+ },
774
+ required: ['id', 'message', 'visibility', 'created_at'],
775
+ };
776
+
777
+ const DRONE_OUTPUT = {
778
+ type: 'object',
779
+ properties: {
780
+ id: { type: 'string' },
781
+ cube_id: { type: 'string' },
782
+ role_id: { type: 'string' },
783
+ label: { type: 'string' },
784
+ last_seen: { type: 'string' },
785
+ hostname: { type: ['string', 'null'] },
786
+ },
787
+ required: ['id', 'label'],
788
+ };
789
+
790
+ const ROLE_OUTPUT = {
791
+ type: 'object',
792
+ properties: {
793
+ id: { type: 'string' },
794
+ cube_id: { type: 'string' },
795
+ name: { type: 'string' },
796
+ short_description: { type: 'string' },
797
+ is_default: { type: 'boolean' },
798
+ is_human_seat: { type: 'boolean' },
799
+ created_at: { type: 'string' },
800
+ },
801
+ required: ['id', 'name'],
802
+ };
803
+
804
+ const CUBE_OUTPUT = {
805
+ type: 'object',
806
+ properties: {
807
+ id: { type: 'string' },
808
+ name: { type: 'string' },
809
+ cube_directive: { type: 'string' },
810
+ created_at: { type: 'string' },
811
+ updated_at: { type: 'string' },
812
+ },
813
+ required: ['id', 'name'],
814
+ };
815
+
816
+ const DECISION_OUTPUT = {
817
+ type: 'object',
818
+ properties: {
819
+ id: { type: 'string' },
820
+ cube_id: { type: 'string' },
821
+ topic: { type: 'string' },
822
+ decision: { type: 'string' },
823
+ rationale: { type: ['string', 'null'] },
824
+ supersedes: { type: ['string', 'null'] },
825
+ created_at: { type: 'string' },
826
+ },
827
+ required: ['topic', 'decision'],
828
+ };
829
+
830
+ // The server's contextAdvisory for cube update, role update, and role-section
831
+ // patch is a SENTENCE (string) when present — never an object. Only the
832
+ // log-append advisory is an object; borg_log declares that shape separately.
833
+ const ADVISORY_OUTPUT = {
834
+ description: 'Server advisory sentence attached to the mutation result; null when the server sent none.',
835
+ type: ['string', 'null'],
836
+ };
837
+
838
+ export const TOOL_OUTPUT_SCHEMAS: Record<string, OutputSchema> = {
839
+ // --- Machine-state queries and discovery ---
840
+ 'borg_version': {
841
+ type: 'object',
842
+ properties: { version: { type: 'string' } },
843
+ required: ['version'],
844
+ },
845
+ 'borg_whoami': {
846
+ type: 'object',
847
+ properties: {
848
+ cube_id: { type: 'string' },
849
+ cube_name: { type: 'string' },
850
+ drone_id: { type: 'string' },
851
+ drone_label: { type: 'string' },
852
+ role_id: { type: 'string' },
853
+ role_name: { type: 'string' },
854
+ runtime_metadata: { type: 'object' },
855
+ runtime_metadata_reported: { type: 'boolean' },
856
+ },
857
+ required: ['cube_id', 'cube_name', 'drone_id', 'drone_label', 'role_id', 'role_name'],
858
+ },
859
+ 'borg_roster': {
860
+ type: 'object',
861
+ properties: {
862
+ cube_name: { type: 'string' },
863
+ drones: { type: 'array', items: DRONE_OUTPUT },
864
+ roles: { type: 'array', items: ROLE_OUTPUT },
865
+ since: { type: ['string', 'null'], description: 'Resolved since-cursor, or null for the default window.' },
866
+ },
867
+ required: ['cube_name', 'drones', 'roles', 'since'],
868
+ },
869
+ 'borg_stream-status': {
870
+ type: 'object',
871
+ properties: {
872
+ status: {
873
+ type: 'object',
874
+ description: 'In-process SSE consumer snapshot.',
875
+ properties: {
876
+ connected: { type: 'boolean' },
877
+ reconnectAttempts: { type: 'number' },
878
+ runLoopHealth: { type: 'string' },
879
+ },
880
+ },
881
+ wake_path: { type: 'object', description: 'Runtime-specific wake-path inspection.' },
882
+ inbox_monitor_healthy: { type: 'boolean' },
883
+ inbox_path: { type: ['string', 'null'] },
884
+ monitor_state_root: { type: ['string', 'null'] },
885
+ drone_label: { type: ['string', 'null'] },
886
+ cube_name: { type: ['string', 'null'] },
887
+ },
888
+ required: ['status', 'inbox_monitor_healthy'],
889
+ },
890
+ 'borg_read-log': {
891
+ type: 'object',
892
+ properties: {
893
+ // gh#496: no roster block — the per-wake drain payload stays
894
+ // proportional to its entry count; entries carry drone_label/role_name.
895
+ entries: { type: 'array', items: LOG_ENTRY_OUTPUT },
896
+ behind_by: { type: ['number', 'null'], description: 'Visible entries still unread after this read; null when the server did not report it.' },
897
+ has_more: { type: 'boolean' },
898
+ },
899
+ required: ['entries', 'behind_by', 'has_more'],
900
+ },
901
+ 'borg_read-entry': {
902
+ type: 'object',
903
+ properties: {
904
+ entry: LOG_ENTRY_OUTPUT,
905
+ drones: { type: 'array', items: DRONE_OUTPUT },
906
+ roles: { type: 'array', items: ROLE_OUTPUT },
907
+ },
908
+ required: ['entry'],
909
+ },
910
+ 'borg_ack-status': {
911
+ type: 'object',
912
+ properties: {
913
+ entry_id: { type: 'string' },
914
+ visibility: { type: 'string', enum: ['broadcast', 'direct'] },
915
+ recipients: {
916
+ type: 'array',
917
+ items: {
918
+ type: 'object',
919
+ properties: {
920
+ drone_id: { type: 'string' },
921
+ drone_label: { type: ['string', 'null'] },
922
+ drone_role: { type: ['string', 'null'] },
923
+ acknowledged_at: { type: ['string', 'null'] },
924
+ },
925
+ required: ['drone_id', 'acknowledged_at'],
926
+ },
927
+ },
928
+ claims: {
929
+ type: 'array',
930
+ items: {
931
+ type: 'object',
932
+ properties: {
933
+ drone_id: { type: 'string' },
934
+ drone_label: { type: ['string', 'null'] },
935
+ drone_role: { type: ['string', 'null'] },
936
+ claimed_at: { type: 'string' },
937
+ },
938
+ required: ['drone_id', 'claimed_at'],
939
+ },
940
+ },
941
+ },
942
+ required: ['entry_id', 'visibility', 'recipients', 'claims'],
943
+ },
944
+ 'borg_decisions': {
945
+ type: 'object',
946
+ properties: { decisions: { type: 'array', items: DECISION_OUTPUT } },
947
+ required: ['decisions'],
948
+ },
949
+ 'borg_get-document': {
950
+ type: 'object',
951
+ properties: { document: DOCUMENT_OUTPUT },
952
+ required: ['document'],
953
+ },
954
+ 'borg_list-documents': {
955
+ type: 'object',
956
+ properties: { documents: { type: 'array', items: DOCUMENT_METADATA_OUTPUT } },
957
+ required: ['documents'],
958
+ },
959
+ 'borg_list-cubes': {
960
+ type: 'object',
961
+ properties: { cubes: { type: 'array', items: CUBE_OUTPUT } },
962
+ required: ['cubes'],
963
+ },
964
+ 'borg_list-drones': {
965
+ type: 'object',
966
+ properties: {
967
+ cube_id: { type: 'string' },
968
+ drones: { type: 'array', items: DRONE_OUTPUT },
969
+ roles: { type: 'array', items: ROLE_OUTPUT },
970
+ },
971
+ required: ['cube_id', 'drones'],
972
+ },
973
+ 'borg_list-roles': {
974
+ type: 'object',
975
+ properties: {
976
+ cube_id: { type: 'string' },
977
+ roles: { type: 'array', items: ROLE_OUTPUT },
978
+ },
979
+ required: ['cube_id', 'roles'],
980
+ },
981
+ 'borg_list-templates': {
982
+ type: 'object',
983
+ properties: {
984
+ templates: {
985
+ type: 'array',
986
+ items: {
987
+ type: 'object',
988
+ properties: { name: { type: 'string' }, description: { type: 'string' } },
989
+ required: ['name', 'description'],
990
+ },
991
+ },
992
+ },
993
+ required: ['templates'],
994
+ },
995
+ 'borg_describe-tool': {
996
+ type: 'object',
997
+ properties: {
998
+ name: { type: 'string' },
999
+ description: { type: 'string' },
1000
+ inputSchema: { type: 'object' },
1001
+ outputSchema: { type: ['object', 'null'], description: 'The described tool\'s structuredContent contract, or null for text-only and dynamic tools.' },
1002
+ },
1003
+ required: ['name', 'description', 'inputSchema', 'outputSchema'],
1004
+ },
1005
+ // --- Mutation receipts ---
1006
+ 'borg_ack': {
1007
+ type: 'object',
1008
+ properties: {
1009
+ entry_id: { type: 'string' },
1010
+ kind: { type: 'string', enum: ['ack', 'claim'] },
1011
+ cube_name: { type: 'string' },
1012
+ },
1013
+ required: ['entry_id', 'kind', 'cube_name'],
1014
+ },
1015
+ 'borg_decide': {
1016
+ type: 'object',
1017
+ properties: {
1018
+ decision: DECISION_OUTPUT,
1019
+ superseded: { type: 'boolean', description: 'True when this ratification superseded a prior decision on the topic.' },
1020
+ cube_name: { type: 'string' },
1021
+ },
1022
+ required: ['decision', 'superseded', 'cube_name'],
1023
+ },
1024
+ 'borg_remove-decision': {
1025
+ type: 'object',
1026
+ properties: { decision: DECISION_OUTPUT, cube_name: { type: 'string' } },
1027
+ required: ['decision', 'cube_name'],
1028
+ },
1029
+ 'borg_put-document': {
1030
+ type: 'object',
1031
+ properties: { document: DOCUMENT_OUTPUT },
1032
+ required: ['document'],
1033
+ },
1034
+ 'borg_remove-document': {
1035
+ type: 'object',
1036
+ properties: { document: DOCUMENT_METADATA_OUTPUT },
1037
+ required: ['document'],
1038
+ },
1039
+ 'borg_log': {
1040
+ type: 'object',
1041
+ properties: {
1042
+ suppressed: { type: 'boolean', description: 'True when a duplicate lifecycle signal was suppressed instead of persisted.' },
1043
+ entry: { ...LOG_ENTRY_OUTPUT, type: ['object', 'null'], description: 'The persisted entry; null when suppressed.' },
1044
+ recipients: { type: 'array', items: { type: 'string' }, description: 'Resolved directed-recipient display labels.' },
1045
+ unreachable_recipients: {
1046
+ type: 'array',
1047
+ items: {
1048
+ type: 'object',
1049
+ properties: { id: { type: 'string' }, label: { type: 'string' } },
1050
+ required: ['id', 'label'],
1051
+ },
1052
+ },
1053
+ advisory: {
1054
+ type: ['object', 'null'],
1055
+ properties: { code: { type: 'string' }, threshold_bytes: { type: 'number' } },
1056
+ },
1057
+ },
1058
+ required: ['suppressed', 'entry', 'recipients', 'unreachable_recipients', 'advisory'],
1059
+ },
1060
+ 'borg_create-cube': {
1061
+ type: 'object',
1062
+ properties: {
1063
+ cube: CUBE_OUTPUT,
1064
+ template: { type: ['string', 'null'] },
1065
+ roles_created: { type: ['number', 'null'] },
1066
+ roles_updated: { type: ['number', 'null'] },
1067
+ },
1068
+ required: ['cube', 'template'],
1069
+ },
1070
+ 'borg_update-cube': {
1071
+ type: 'object',
1072
+ properties: { cube: CUBE_OUTPUT, advisory: ADVISORY_OUTPUT },
1073
+ required: ['cube'],
1074
+ },
1075
+ 'borg_patch-taxonomy-class': {
1076
+ type: 'object',
1077
+ properties: {
1078
+ action: { type: 'string', enum: ['add', 'replace', 'remove'] },
1079
+ class: { type: 'string' },
1080
+ cube: CUBE_OUTPUT,
1081
+ },
1082
+ required: ['action', 'class', 'cube'],
1083
+ },
1084
+ 'borg_delete-cube': {
1085
+ type: 'object',
1086
+ properties: { cube_id: { type: 'string' }, deleted: { type: 'boolean' } },
1087
+ required: ['cube_id', 'deleted'],
1088
+ },
1089
+ 'borg_create-role': {
1090
+ type: 'object',
1091
+ properties: { role: ROLE_OUTPUT, cube_id: { type: 'string' } },
1092
+ required: ['role', 'cube_id'],
1093
+ },
1094
+ 'borg_update-role': {
1095
+ type: 'object',
1096
+ properties: { role: ROLE_OUTPUT, advisory: ADVISORY_OUTPUT },
1097
+ required: ['role'],
1098
+ },
1099
+ 'borg_patch-role-section': {
1100
+ type: 'object',
1101
+ properties: {
1102
+ action: { type: 'string', enum: ['replace', 'insert', 'delete'] },
1103
+ heading: { type: 'string' },
1104
+ role: ROLE_OUTPUT,
1105
+ advisory: ADVISORY_OUTPUT,
1106
+ },
1107
+ required: ['action', 'heading', 'role'],
1108
+ },
1109
+ 'borg_delete-role': {
1110
+ type: 'object',
1111
+ properties: { role_id: { type: 'string' }, deleted: { type: 'boolean' } },
1112
+ required: ['role_id', 'deleted'],
1113
+ },
1114
+ 'borg_reassign-drone': {
1115
+ type: 'object',
1116
+ properties: {
1117
+ drone: {
1118
+ type: 'object',
1119
+ properties: {
1120
+ id: { type: 'string' },
1121
+ cube_id: { type: 'string' },
1122
+ role_id: { type: 'string' },
1123
+ label: { type: 'string' },
1124
+ },
1125
+ required: ['id', 'cube_id', 'role_id', 'label'],
1126
+ },
1127
+ role_name: { type: 'string' },
1128
+ cube_name: { type: 'string' },
1129
+ },
1130
+ required: ['drone', 'role_name', 'cube_name'],
1131
+ },
1132
+ 'borg_evict-drone': {
1133
+ type: 'object',
1134
+ properties: {
1135
+ drone_id: { type: 'string' },
1136
+ label: { type: 'string' },
1137
+ cube_name: { type: 'string' },
1138
+ evicted: { type: 'boolean' },
1139
+ },
1140
+ required: ['drone_id', 'evicted'],
1141
+ },
1142
+ 'borg_apply-template': {
1143
+ type: 'object',
1144
+ properties: {
1145
+ cube_id: { type: 'string' },
1146
+ template: { type: 'string' },
1147
+ roles_created: { type: 'number' },
1148
+ roles_updated: { type: 'number' },
1149
+ cube_directive_applied: { type: 'boolean' },
1150
+ },
1151
+ required: ['cube_id', 'template', 'roles_created', 'roles_updated'],
1152
+ },
1153
+ 'borg_sync-roles': {
1154
+ type: 'object',
1155
+ properties: {
1156
+ cube_id: { type: 'string' },
1157
+ template: { type: 'string' },
1158
+ apply: { type: 'boolean' },
1159
+ result: { type: 'object', description: 'The sync plan or apply summary exactly as the server returned it.' },
1160
+ },
1161
+ required: ['cube_id', 'template', 'apply', 'result'],
1162
+ },
1163
+ // --- Context/domain results ---
1164
+ 'borg_regen': {
1165
+ type: 'object',
1166
+ properties: {
1167
+ connected: { type: 'boolean', description: 'False when no cube is active; other fields are then absent.' },
1168
+ mode: { type: 'string', enum: ['full', 'lite'] },
1169
+ cube: CUBE_OUTPUT,
1170
+ drone: DRONE_OUTPUT,
1171
+ role: ROLE_OUTPUT,
1172
+ behind_by: { type: ['number', 'null'], description: 'Unread-entry count, null when the server did not report it.' },
1173
+ decision_topics: { type: 'array', items: { type: 'string' } },
1174
+ running_version: { type: 'string' },
1175
+ on_disk_version: { type: ['string', 'null'] },
1176
+ wake_path_healthy: { type: 'boolean' },
1177
+ },
1178
+ required: ['connected'],
1179
+ },
1180
+ 'borg_assimilate': {
1181
+ type: 'object',
1182
+ properties: {
1183
+ reattached: { type: 'boolean' },
1184
+ cube_name: { type: 'string' },
1185
+ drone_label: { type: 'string' },
1186
+ },
1187
+ required: ['reattached', 'cube_name', 'drone_label'],
1188
+ },
1189
+ 'borg_cube': {
1190
+ type: 'object',
1191
+ properties: {
1192
+ cube: {
1193
+ ...CUBE_OUTPUT,
1194
+ properties: { ...CUBE_OUTPUT.properties, message_taxonomy: { type: ['array', 'null'] } },
1195
+ },
1196
+ roles: { type: 'array', items: ROLE_OUTPUT },
1197
+ },
1198
+ required: ['cube', 'roles'],
1199
+ },
1200
+ 'borg_role': {
1201
+ type: 'object',
1202
+ properties: {
1203
+ role: {
1204
+ ...ROLE_OUTPUT,
1205
+ properties: { ...ROLE_OUTPUT.properties, detailed_description: { type: 'string' } },
1206
+ },
1207
+ },
1208
+ required: ['role'],
1209
+ },
1210
+ 'borg_docs': {
1211
+ type: 'object',
1212
+ properties: {
1213
+ topic: { type: ['string', 'null'] },
1214
+ matched: { type: 'boolean', description: 'True when the topic matched specific sections; false when the full index is returned.' },
1215
+ sections: {
1216
+ type: 'array',
1217
+ items: {
1218
+ type: 'object',
1219
+ properties: {
1220
+ slug: { type: 'string' },
1221
+ title: { type: 'string' },
1222
+ url: { type: 'string' },
1223
+ summary: { type: 'string' },
1224
+ },
1225
+ required: ['slug', 'title', 'url', 'summary'],
1226
+ },
1227
+ },
1228
+ },
1229
+ required: ['topic', 'matched', 'sections'],
1230
+ },
1231
+ 'borg_role-rationale': {
1232
+ type: 'object',
1233
+ properties: {
1234
+ role: { type: 'string' },
1235
+ section: { type: 'string' },
1236
+ body: { type: 'string' },
1237
+ },
1238
+ required: ['role', 'section', 'body'],
1239
+ },
1240
+ };
1241
+
1242
+ export const TOOL_MANIFEST: ToolManifestEntry[] = BASE_TOOL_MANIFEST.map((entry) => {
1243
+ const outputSchema = TOOL_OUTPUT_SCHEMAS[entry.name];
1244
+ return outputSchema ? { ...entry, outputSchema } : entry;
1245
+ });