@reveldigital/mcp-graphql-proxy 1.16.0 → 1.18.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
@@ -19,6 +19,7 @@ import { GraphQLClient } from './core/graphql-client.js';
19
19
  import { ResponseCache } from './core/cache.js';
20
20
  import { QueryBuilder } from './core/query-builder.js';
21
21
  import { ResponseOptimizer } from './core/response-optimizer.js';
22
+ import { generateSystemPrompt, generateCompactPrompt } from './core/system-prompt.js';
22
23
  import { defaultGraphQLProxyConfig, } from './types/graphql-proxy.js';
23
24
  // ============================================================================
24
25
  // Server Configuration
@@ -121,6 +122,8 @@ server.tool('build_query', 'Build an optimized GraphQL query with field selectio
121
122
  'alert',
122
123
  // Audit & Compliance
123
124
  'auditEvent',
125
+ // Data Tables
126
+ 'dataTables', 'dataTable',
124
127
  // Device Commands & Permissions
125
128
  'displayCommands', 'permissions',
126
129
  // Analytics (aggregated metrics - optimized for AI context windows)
@@ -696,15 +699,42 @@ server.tool('add_playlist_source', 'Add a content source to an existing playlist
696
699
  source: z.object({
697
700
  name: z.string().optional().describe('Display name for the source'),
698
701
  type: z.enum([
699
- 'IMAGE', 'VIDEO', 'AUDIO', 'PDF', 'SVG',
700
- 'TEMPLATE', 'PLAYLIST', 'URL', 'WEBPAGE', 'RSS', 'TEXT', 'YOUTUBE',
701
- ]).describe('The content type'),
702
- mediaId: z.string().optional().describe('Media file ID (for Image, Video, Audio, Pdf, Svg types)'),
703
- templateId: z.string().optional().describe('Template ID (for Template type)'),
704
- playlistId: z.string().optional().describe('Embedded playlist ID (for Playlist type)'),
705
- value: z.string().optional().describe('URL or text value (for Url, WebPage, Rss, Text, YouTube types)'),
702
+ 'AUDIO', 'COMMAND', 'FLASH', 'GADGET', 'IMAGE', 'PDF',
703
+ 'PLACE_EXCHANGE', 'PLAYLIST', 'POWER_POINT', 'RSS', 'SVG',
704
+ 'TEMPLATE', 'TEXT', 'TWITTER', 'URL', 'VIDEO',
705
+ 'VISTAR_MEDIA', 'WEB_PAGE', 'YOU_TUBE', 'VISTAR_MEDIA_EX',
706
+ ]).describe('The content type (e.g., IMAGE, VIDEO, TEMPLATE, PLAYLIST, URL, WEB_PAGE, YOU_TUBE)'),
707
+ mediaId: z.string().optional().describe('Media file ID (for IMAGE, VIDEO, AUDIO, PDF, SVG types)'),
708
+ templateId: z.string().optional().describe('Template ID (for TEMPLATE type)'),
709
+ playlistId: z.string().optional().describe('Embedded playlist ID (for PLAYLIST type)'),
710
+ value: z.string().optional().describe('URL or text value (for URL, WEB_PAGE, RSS, TEXT, YOU_TUBE types)'),
706
711
  interval: z.number().optional().describe('Duration in seconds'),
707
- loopPolicyType: z.enum(['LOOP', 'PLAY_ONCE', 'PLAY_THROUGH']).optional().describe('Loop policy for fixed-duration playlists'),
712
+ loopPolicyType: z.enum([
713
+ 'DEFAULT', 'OVER_SATURATE2X', 'OVER_SATURATE3X', 'OVER_SATURATE4X', 'OVER_SATURATE5X',
714
+ 'RANDOM', 'UNDER_SATURATE2X', 'UNDER_SATURATE3X', 'UNDER_SATURATE4X', 'UNDER_SATURATE5X',
715
+ 'PREFILL', 'POSTFILL', 'SPREADFILL',
716
+ ]).optional().describe('Loop policy (DEFAULT=1x per loop, OVER_SATURATE2X=2x per loop, UNDER_SATURATE2X=1 per 2 loops, RANDOM, PREFILL, POSTFILL, SPREADFILL)'),
717
+ conditions: z.array(z.object({
718
+ type: z.enum([
719
+ 'AFTER_DATE', 'AFTER_TIME', 'ALWAYS', 'BEACON_ACTIVE', 'BEFORE_DATE', 'BEFORE_TIME',
720
+ 'COMMAND', 'DATE_RANGE', 'DAY_OF_MONTH', 'DAYS_OF_WEEK',
721
+ 'DEVICE_BY_GROUP', 'DEVICE_BY_NESTED_GROUP', 'DEVICE_BY_NAME', 'DEVICE_BY_TAG', 'DEVICE_BY_ORG',
722
+ 'EVERYWHERE', 'GEO_LOCATION', 'GPS_WITHIN_RADIUS', 'KEY_EVENT',
723
+ 'MONTH_OF_YEAR', 'MOTION', 'NEVER', 'NOWHERE',
724
+ 'PCT_ADULT', 'PCT_CHILD', 'PCT_FEMALE', 'PCT_MALE', 'PCT_SENIOR', 'PCT_YOUNG', 'PCT_YOUNG_ADULT',
725
+ 'PLAYLIST_BY_NAME', 'PLAYLIST_BY_TAG', 'SEND_COMMAND',
726
+ 'SPECIFIC_DEVICE', 'SPECIFIC_PLAYLIST', 'SPECIFIC_TEMPLATE', 'SPECIFIC_ZONE',
727
+ 'TEMPLATE_BY_NAME', 'TEMPLATE_BY_TAG',
728
+ 'TIME_RANGE', 'TOPIC_SET', 'TOTAL_VIEWERS_GREATER_THAN', 'TOTAL_VIEWERS_LESS_THAN',
729
+ 'TOUCH_EVENT', 'WEEK_OF_MONTH', 'WEEK_OF_YEAR',
730
+ ]).describe('Condition type (e.g., DATE_RANGE, TIME_RANGE, DAYS_OF_WEEK, SPECIFIC_DEVICE)'),
731
+ operator: z.enum(['AND', 'OR', 'AND_NOT', 'OR_NOT']).optional()
732
+ .describe('Logical operator for combining with previous condition (ignored for first condition)'),
733
+ value1: z.string().optional().describe('Value 1 - meaning depends on condition type'),
734
+ value2: z.string().optional().describe('Value 2 - meaning depends on condition type'),
735
+ value3: z.string().optional().describe('Value 3 - meaning depends on condition type'),
736
+ value4: z.string().optional().describe('Value 4 - meaning depends on condition type'),
737
+ })).optional().describe('Conditions controlling when/where this source plays (e.g., date range, time range, device targeting). If omitted, plays unconditionally.'),
708
738
  }).describe('The source to add'),
709
739
  position: z.number().optional().describe('Optional position (0-based index). If omitted, appends to end.'),
710
740
  }, async (args) => {
@@ -762,15 +792,42 @@ server.tool('update_playlist_source', 'Update a source within a playlist. Only p
762
792
  source: z.object({
763
793
  name: z.string().optional().describe('New display name'),
764
794
  type: z.enum([
765
- 'IMAGE', 'VIDEO', 'AUDIO', 'PDF', 'SVG',
766
- 'TEMPLATE', 'PLAYLIST', 'URL', 'WEBPAGE', 'RSS', 'TEXT', 'YOUTUBE',
795
+ 'AUDIO', 'COMMAND', 'FLASH', 'GADGET', 'IMAGE', 'PDF',
796
+ 'PLACE_EXCHANGE', 'PLAYLIST', 'POWER_POINT', 'RSS', 'SVG',
797
+ 'TEMPLATE', 'TEXT', 'TWITTER', 'URL', 'VIDEO',
798
+ 'VISTAR_MEDIA', 'WEB_PAGE', 'YOU_TUBE', 'VISTAR_MEDIA_EX',
767
799
  ]).optional().describe('New content type'),
768
800
  mediaId: z.string().optional().describe('New media file ID'),
769
801
  templateId: z.string().optional().describe('New template ID'),
770
802
  playlistId: z.string().optional().describe('New embedded playlist ID'),
771
803
  value: z.string().optional().describe('New URL or text value'),
772
804
  interval: z.number().optional().describe('New duration in seconds'),
773
- loopPolicyType: z.enum(['LOOP', 'PLAY_ONCE', 'PLAY_THROUGH']).optional().describe('New loop policy'),
805
+ loopPolicyType: z.enum([
806
+ 'DEFAULT', 'OVER_SATURATE2X', 'OVER_SATURATE3X', 'OVER_SATURATE4X', 'OVER_SATURATE5X',
807
+ 'RANDOM', 'UNDER_SATURATE2X', 'UNDER_SATURATE3X', 'UNDER_SATURATE4X', 'UNDER_SATURATE5X',
808
+ 'PREFILL', 'POSTFILL', 'SPREADFILL',
809
+ ]).optional().describe('New loop policy'),
810
+ conditions: z.array(z.object({
811
+ type: z.enum([
812
+ 'AFTER_DATE', 'AFTER_TIME', 'ALWAYS', 'BEACON_ACTIVE', 'BEFORE_DATE', 'BEFORE_TIME',
813
+ 'COMMAND', 'DATE_RANGE', 'DAY_OF_MONTH', 'DAYS_OF_WEEK',
814
+ 'DEVICE_BY_GROUP', 'DEVICE_BY_NESTED_GROUP', 'DEVICE_BY_NAME', 'DEVICE_BY_TAG', 'DEVICE_BY_ORG',
815
+ 'EVERYWHERE', 'GEO_LOCATION', 'GPS_WITHIN_RADIUS', 'KEY_EVENT',
816
+ 'MONTH_OF_YEAR', 'MOTION', 'NEVER', 'NOWHERE',
817
+ 'PCT_ADULT', 'PCT_CHILD', 'PCT_FEMALE', 'PCT_MALE', 'PCT_SENIOR', 'PCT_YOUNG', 'PCT_YOUNG_ADULT',
818
+ 'PLAYLIST_BY_NAME', 'PLAYLIST_BY_TAG', 'SEND_COMMAND',
819
+ 'SPECIFIC_DEVICE', 'SPECIFIC_PLAYLIST', 'SPECIFIC_TEMPLATE', 'SPECIFIC_ZONE',
820
+ 'TEMPLATE_BY_NAME', 'TEMPLATE_BY_TAG',
821
+ 'TIME_RANGE', 'TOPIC_SET', 'TOTAL_VIEWERS_GREATER_THAN', 'TOTAL_VIEWERS_LESS_THAN',
822
+ 'TOUCH_EVENT', 'WEEK_OF_MONTH', 'WEEK_OF_YEAR',
823
+ ]).describe('Condition type (e.g., DATE_RANGE, TIME_RANGE, DAYS_OF_WEEK, SPECIFIC_DEVICE)'),
824
+ operator: z.enum(['AND', 'OR', 'AND_NOT', 'OR_NOT']).optional()
825
+ .describe('Logical operator for combining with previous condition (ignored for first condition)'),
826
+ value1: z.string().optional().describe('Value 1 - meaning depends on condition type'),
827
+ value2: z.string().optional().describe('Value 2 - meaning depends on condition type'),
828
+ value3: z.string().optional().describe('Value 3 - meaning depends on condition type'),
829
+ value4: z.string().optional().describe('Value 4 - meaning depends on condition type'),
830
+ })).optional().describe('Conditions controlling when/where this source plays. Replaces existing conditions. If omitted, conditions are unchanged.'),
774
831
  }).describe('Updated source properties (only provided fields are changed)'),
775
832
  }, async (args) => {
776
833
  try {
@@ -919,6 +976,916 @@ mutation ReorderPlaylistSources($input: ReorderPlaylistSourcesInput) {
919
976
  }
920
977
  });
921
978
  // ============================================================================
979
+ // Tool: List Data Tables
980
+ // ============================================================================
981
+ server.tool('list_data_tables', 'List data tables in the account. Returns table summaries with pagination. Use to discover available tables before querying rows.', {
982
+ groupId: z.string().optional().describe('Optional group ID to filter by'),
983
+ pageSize: z.number().optional().default(50).describe('Number of tables per page (default 50)'),
984
+ continuationToken: z.string().optional().describe('Token for fetching the next page'),
985
+ }, async (args) => {
986
+ try {
987
+ const query = `
988
+ query DataTables($groupId: String, $pageSize: Int! = 50, $continuationToken: String) {
989
+ dataTables(groupId: $groupId, pageSize: $pageSize, continuationToken: $continuationToken) {
990
+ data {
991
+ id
992
+ name
993
+ description
994
+ columnCount
995
+ rowCount
996
+ groupId
997
+ updatedAt
998
+ }
999
+ continuationToken
1000
+ }
1001
+ }`;
1002
+ const variables = { pageSize: args.pageSize };
1003
+ if (args.groupId)
1004
+ variables.groupId = args.groupId;
1005
+ if (args.continuationToken)
1006
+ variables.continuationToken = args.continuationToken;
1007
+ const response = await graphqlClient.query(query, variables);
1008
+ if (response.errors && response.errors.length > 0) {
1009
+ const result = {
1010
+ success: false,
1011
+ error: response.errors.map((e) => e.message).join('; '),
1012
+ };
1013
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1014
+ }
1015
+ const result = {
1016
+ success: true,
1017
+ data: response.data,
1018
+ };
1019
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1020
+ }
1021
+ catch (error) {
1022
+ const result = {
1023
+ success: false,
1024
+ error: error instanceof Error ? error.message : 'Unknown error',
1025
+ };
1026
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1027
+ }
1028
+ });
1029
+ // ============================================================================
1030
+ // Tool: Get Data Table
1031
+ // ============================================================================
1032
+ server.tool('get_data_table', 'Get a single data table definition by ID, including its full column schema. Use this to understand table structure before querying or modifying rows.', {
1033
+ tableId: z.string().describe('The data table ID'),
1034
+ }, async (args) => {
1035
+ try {
1036
+ const query = `
1037
+ query DataTable($tableId: String) {
1038
+ dataTable(tableId: $tableId) {
1039
+ id
1040
+ name
1041
+ description
1042
+ groupId
1043
+ rowCount
1044
+ cacheTtlSeconds
1045
+ createdAt
1046
+ updatedAt
1047
+ columns {
1048
+ id
1049
+ name
1050
+ key
1051
+ type
1052
+ required
1053
+ sortable
1054
+ options
1055
+ default
1056
+ }
1057
+ }
1058
+ }`;
1059
+ const response = await graphqlClient.query(query, { tableId: args.tableId });
1060
+ if (response.errors && response.errors.length > 0) {
1061
+ const result = {
1062
+ success: false,
1063
+ error: response.errors.map((e) => e.message).join('; '),
1064
+ };
1065
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1066
+ }
1067
+ const result = {
1068
+ success: true,
1069
+ data: response.data,
1070
+ };
1071
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1072
+ }
1073
+ catch (error) {
1074
+ const result = {
1075
+ success: false,
1076
+ error: error instanceof Error ? error.message : 'Unknown error',
1077
+ };
1078
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1079
+ }
1080
+ });
1081
+ // ============================================================================
1082
+ // Tool: List Data Table Rows
1083
+ // ============================================================================
1084
+ server.tool('list_data_table_rows', 'List rows in a data table with optional filtering, sorting, field selection, and pagination.', {
1085
+ tableId: z.string().describe('The data table ID'),
1086
+ filter: z.string().optional().describe('Optional filter as a JSON string (e.g., {"status":"active"})'),
1087
+ sort: z.string().optional().describe('Column key to sort by'),
1088
+ sortDir: z.enum(['asc', 'desc']).optional().default('asc').describe('Sort direction'),
1089
+ pageSize: z.number().optional().default(50).describe('Number of rows per page (default 50)'),
1090
+ continuationToken: z.string().optional().describe('Token for fetching the next page'),
1091
+ fields: z.string().optional().describe('Comma-separated list of column keys to return'),
1092
+ }, async (args) => {
1093
+ try {
1094
+ const query = `
1095
+ query DataTableRows($tableId: String, $filter: String, $sort: String, $sortDir: String = "asc", $pageSize: Int! = 50, $continuationToken: String, $fields: String) {
1096
+ dataTableRows(tableId: $tableId, filter: $filter, sort: $sort, sortDir: $sortDir, pageSize: $pageSize, continuationToken: $continuationToken, fields: $fields) {
1097
+ data {
1098
+ id
1099
+ sortOrder
1100
+ data
1101
+ updatedAt
1102
+ }
1103
+ totalCount
1104
+ continuationToken
1105
+ }
1106
+ }`;
1107
+ const variables = {
1108
+ tableId: args.tableId,
1109
+ pageSize: args.pageSize,
1110
+ sortDir: args.sortDir,
1111
+ };
1112
+ if (args.filter)
1113
+ variables.filter = args.filter;
1114
+ if (args.sort)
1115
+ variables.sort = args.sort;
1116
+ if (args.continuationToken)
1117
+ variables.continuationToken = args.continuationToken;
1118
+ if (args.fields)
1119
+ variables.fields = args.fields;
1120
+ const response = await graphqlClient.query(query, variables);
1121
+ if (response.errors && response.errors.length > 0) {
1122
+ const result = {
1123
+ success: false,
1124
+ error: response.errors.map((e) => e.message).join('; '),
1125
+ };
1126
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1127
+ }
1128
+ const result = {
1129
+ success: true,
1130
+ data: response.data,
1131
+ };
1132
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1133
+ }
1134
+ catch (error) {
1135
+ const result = {
1136
+ success: false,
1137
+ error: error instanceof Error ? error.message : 'Unknown error',
1138
+ };
1139
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1140
+ }
1141
+ });
1142
+ // ============================================================================
1143
+ // Tool: Get Data Table Row
1144
+ // ============================================================================
1145
+ server.tool('get_data_table_row', 'Get a single row from a data table by row ID.', {
1146
+ tableId: z.string().describe('The data table ID'),
1147
+ rowId: z.string().describe('The row ID'),
1148
+ }, async (args) => {
1149
+ try {
1150
+ const query = `
1151
+ query DataTableRow($tableId: String, $rowId: String) {
1152
+ dataTableRow(tableId: $tableId, rowId: $rowId) {
1153
+ id
1154
+ sortOrder
1155
+ data
1156
+ updatedAt
1157
+ }
1158
+ }`;
1159
+ const response = await graphqlClient.query(query, {
1160
+ tableId: args.tableId,
1161
+ rowId: args.rowId,
1162
+ });
1163
+ if (response.errors && response.errors.length > 0) {
1164
+ const result = {
1165
+ success: false,
1166
+ error: response.errors.map((e) => e.message).join('; '),
1167
+ };
1168
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1169
+ }
1170
+ const result = {
1171
+ success: true,
1172
+ data: response.data,
1173
+ };
1174
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1175
+ }
1176
+ catch (error) {
1177
+ const result = {
1178
+ success: false,
1179
+ error: error instanceof Error ? error.message : 'Unknown error',
1180
+ };
1181
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1182
+ }
1183
+ });
1184
+ // ============================================================================
1185
+ // Tool: Export Data Table Rows
1186
+ // ============================================================================
1187
+ server.tool('export_data_table_rows', 'Export all rows from a data table as a CSV string. Useful for bulk data extraction or backup.', {
1188
+ tableId: z.string().describe('The data table ID'),
1189
+ }, async (args) => {
1190
+ try {
1191
+ const query = `
1192
+ query ExportDataTableRows($tableId: String) {
1193
+ exportDataTableRows(tableId: $tableId)
1194
+ }`;
1195
+ const response = await graphqlClient.query(query, { tableId: args.tableId });
1196
+ if (response.errors && response.errors.length > 0) {
1197
+ const result = {
1198
+ success: false,
1199
+ error: response.errors.map((e) => e.message).join('; '),
1200
+ };
1201
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1202
+ }
1203
+ const result = {
1204
+ success: true,
1205
+ data: response.data,
1206
+ };
1207
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1208
+ }
1209
+ catch (error) {
1210
+ const result = {
1211
+ success: false,
1212
+ error: error instanceof Error ? error.message : 'Unknown error',
1213
+ };
1214
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1215
+ }
1216
+ });
1217
+ // ============================================================================
1218
+ // Tool: Get Data Table Row Versions
1219
+ // ============================================================================
1220
+ server.tool('get_data_table_row_versions', 'List version history for a data table row (newest first). Use to audit changes or before rolling back.', {
1221
+ tableId: z.string().describe('The data table ID'),
1222
+ rowId: z.string().describe('The row ID'),
1223
+ pageSize: z.number().optional().default(25).describe('Number of versions per page (default 25)'),
1224
+ continuationToken: z.string().optional().describe('Token for fetching the next page'),
1225
+ }, async (args) => {
1226
+ try {
1227
+ const query = `
1228
+ query DataTableRowVersions($tableId: String, $rowId: String, $pageSize: Int! = 25, $continuationToken: String) {
1229
+ dataTableRowVersions(tableId: $tableId, rowId: $rowId, pageSize: $pageSize, continuationToken: $continuationToken) {
1230
+ data {
1231
+ id
1232
+ rowId
1233
+ version
1234
+ action
1235
+ data
1236
+ changedFields
1237
+ previousValues
1238
+ changedBy
1239
+ changedAt
1240
+ }
1241
+ totalCount
1242
+ continuationToken
1243
+ }
1244
+ }`;
1245
+ const variables = {
1246
+ tableId: args.tableId,
1247
+ rowId: args.rowId,
1248
+ pageSize: args.pageSize,
1249
+ };
1250
+ if (args.continuationToken)
1251
+ variables.continuationToken = args.continuationToken;
1252
+ const response = await graphqlClient.query(query, variables);
1253
+ if (response.errors && response.errors.length > 0) {
1254
+ const result = {
1255
+ success: false,
1256
+ error: response.errors.map((e) => e.message).join('; '),
1257
+ };
1258
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1259
+ }
1260
+ const result = {
1261
+ success: true,
1262
+ data: response.data,
1263
+ };
1264
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1265
+ }
1266
+ catch (error) {
1267
+ const result = {
1268
+ success: false,
1269
+ error: error instanceof Error ? error.message : 'Unknown error',
1270
+ };
1271
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1272
+ }
1273
+ });
1274
+ // ============================================================================
1275
+ // Tool: Get Data Table Row Version
1276
+ // ============================================================================
1277
+ server.tool('get_data_table_row_version', 'Get a specific version snapshot of a data table row. Use to inspect historical state before rollback.', {
1278
+ tableId: z.string().describe('The data table ID'),
1279
+ rowId: z.string().describe('The row ID'),
1280
+ version: z.number().describe('The version number to retrieve'),
1281
+ }, async (args) => {
1282
+ try {
1283
+ const query = `
1284
+ query DataTableRowVersion($tableId: String, $rowId: String, $version: Int!) {
1285
+ dataTableRowVersion(tableId: $tableId, rowId: $rowId, version: $version) {
1286
+ id
1287
+ rowId
1288
+ version
1289
+ action
1290
+ data
1291
+ changedFields
1292
+ previousValues
1293
+ changedBy
1294
+ changedAt
1295
+ }
1296
+ }`;
1297
+ const response = await graphqlClient.query(query, {
1298
+ tableId: args.tableId,
1299
+ rowId: args.rowId,
1300
+ version: args.version,
1301
+ });
1302
+ if (response.errors && response.errors.length > 0) {
1303
+ const result = {
1304
+ success: false,
1305
+ error: response.errors.map((e) => e.message).join('; '),
1306
+ };
1307
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1308
+ }
1309
+ const result = {
1310
+ success: true,
1311
+ data: response.data,
1312
+ };
1313
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1314
+ }
1315
+ catch (error) {
1316
+ const result = {
1317
+ success: false,
1318
+ error: error instanceof Error ? error.message : 'Unknown error',
1319
+ };
1320
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1321
+ }
1322
+ });
1323
+ // ============================================================================
1324
+ // Tool: Create Data Table
1325
+ // ============================================================================
1326
+ server.tool('create_data_table', 'Create a new data table with the specified column schema. Example: Create a menu board table with columns for item name, price, description, and image.', {
1327
+ name: z.string().describe('Table name (max 100 characters)'),
1328
+ description: z.string().optional().describe('Optional table description (max 500 characters)'),
1329
+ groupId: z.string().optional().describe('Optional group ID for organizing tables'),
1330
+ columns: z.array(z.object({
1331
+ name: z.string().describe('Display name for the column'),
1332
+ key: z.string().describe('Unique key used in row data objects'),
1333
+ type: z.enum(['STRING', 'NUMBER', 'BOOLEAN', 'DATE', 'SELECT', 'MEDIA', 'URL', 'RICH_TEXT', 'TIME', 'HIDDEN'])
1334
+ .describe('Data type of the column'),
1335
+ required: z.boolean().describe('Whether a value is required'),
1336
+ sortable: z.boolean().describe('Whether the column supports sorting'),
1337
+ options: z.array(z.string()).optional().describe('Allowed values for Select-type columns'),
1338
+ default: z.unknown().optional().describe('Default value for the column'),
1339
+ })).describe('Column definitions (at least one required)'),
1340
+ cacheTtlSeconds: z.number().optional().describe('Optional cache TTL in seconds for gadget/player reads'),
1341
+ }, async (args) => {
1342
+ try {
1343
+ const mutation = `
1344
+ mutation CreateDataTable($input: CreateDataTableInput) {
1345
+ createDataTable(input: $input) {
1346
+ success
1347
+ table {
1348
+ id
1349
+ name
1350
+ description
1351
+ columns {
1352
+ id
1353
+ name
1354
+ key
1355
+ type
1356
+ required
1357
+ }
1358
+ rowCount
1359
+ createdAt
1360
+ }
1361
+ error
1362
+ }
1363
+ }`;
1364
+ const input = {
1365
+ name: args.name,
1366
+ columns: args.columns,
1367
+ };
1368
+ if (args.description !== undefined)
1369
+ input.description = args.description;
1370
+ if (args.groupId !== undefined)
1371
+ input.groupId = args.groupId;
1372
+ if (args.cacheTtlSeconds !== undefined)
1373
+ input.cacheTtlSeconds = args.cacheTtlSeconds;
1374
+ const response = await graphqlClient.query(mutation, { input });
1375
+ if (response.errors && response.errors.length > 0) {
1376
+ const result = {
1377
+ success: false,
1378
+ error: response.errors.map((e) => e.message).join('; '),
1379
+ };
1380
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1381
+ }
1382
+ const result = {
1383
+ success: true,
1384
+ data: response.data,
1385
+ };
1386
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1387
+ }
1388
+ catch (error) {
1389
+ const result = {
1390
+ success: false,
1391
+ error: error instanceof Error ? error.message : 'Unknown error',
1392
+ };
1393
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1394
+ }
1395
+ });
1396
+ // ============================================================================
1397
+ // Tool: Update Data Table
1398
+ // ============================================================================
1399
+ server.tool('update_data_table', 'Update an existing data table definition. Only provided fields are changed. Example: Add a new "calories" column to a menu board table.', {
1400
+ tableId: z.string().describe('The table ID to update'),
1401
+ name: z.string().optional().describe('New table name (null leaves unchanged)'),
1402
+ description: z.string().optional().describe('New description (null leaves unchanged)'),
1403
+ groupId: z.string().optional().describe('New group ID (null leaves unchanged)'),
1404
+ columns: z.array(z.object({
1405
+ id: z.string().optional().describe('Column ID (only needed when updating existing columns)'),
1406
+ name: z.string().optional().describe('Display name for the column'),
1407
+ key: z.string().optional().describe('Unique key used in row data objects'),
1408
+ type: z.enum(['STRING', 'NUMBER', 'BOOLEAN', 'DATE', 'SELECT', 'MEDIA', 'URL', 'RICH_TEXT', 'TIME', 'HIDDEN'])
1409
+ .describe('Data type of the column'),
1410
+ required: z.boolean().describe('Whether a value is required'),
1411
+ sortable: z.boolean().describe('Whether the column supports sorting'),
1412
+ options: z.array(z.string()).optional().describe('Allowed values for Select-type columns'),
1413
+ default: z.unknown().optional().describe('Default value for the column'),
1414
+ })).optional().describe('Updated column definitions (null leaves unchanged)'),
1415
+ cacheTtlSeconds: z.number().optional().describe('New cache TTL in seconds (null leaves unchanged)'),
1416
+ }, async (args) => {
1417
+ try {
1418
+ const mutation = `
1419
+ mutation UpdateDataTable($input: UpdateDataTableInput) {
1420
+ updateDataTable(input: $input) {
1421
+ success
1422
+ table {
1423
+ id
1424
+ name
1425
+ description
1426
+ columns {
1427
+ id
1428
+ name
1429
+ key
1430
+ type
1431
+ required
1432
+ }
1433
+ rowCount
1434
+ updatedAt
1435
+ }
1436
+ error
1437
+ }
1438
+ }`;
1439
+ const input = { tableId: args.tableId };
1440
+ if (args.name !== undefined)
1441
+ input.name = args.name;
1442
+ if (args.description !== undefined)
1443
+ input.description = args.description;
1444
+ if (args.groupId !== undefined)
1445
+ input.groupId = args.groupId;
1446
+ if (args.columns !== undefined)
1447
+ input.columns = args.columns;
1448
+ if (args.cacheTtlSeconds !== undefined)
1449
+ input.cacheTtlSeconds = args.cacheTtlSeconds;
1450
+ const response = await graphqlClient.query(mutation, { input });
1451
+ if (response.errors && response.errors.length > 0) {
1452
+ const result = {
1453
+ success: false,
1454
+ error: response.errors.map((e) => e.message).join('; '),
1455
+ };
1456
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1457
+ }
1458
+ const result = {
1459
+ success: true,
1460
+ data: response.data,
1461
+ };
1462
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1463
+ }
1464
+ catch (error) {
1465
+ const result = {
1466
+ success: false,
1467
+ error: error instanceof Error ? error.message : 'Unknown error',
1468
+ };
1469
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1470
+ }
1471
+ });
1472
+ // ============================================================================
1473
+ // Tool: Delete Data Table
1474
+ // ============================================================================
1475
+ server.tool('delete_data_table', 'Delete a data table and all its rows. This action is irreversible.', {
1476
+ tableId: z.string().describe('The data table ID to delete'),
1477
+ }, async (args) => {
1478
+ try {
1479
+ const mutation = `
1480
+ mutation DeleteDataTable($tableId: String) {
1481
+ deleteDataTable(tableId: $tableId) {
1482
+ success
1483
+ error
1484
+ }
1485
+ }`;
1486
+ const response = await graphqlClient.query(mutation, { tableId: args.tableId });
1487
+ if (response.errors && response.errors.length > 0) {
1488
+ const result = {
1489
+ success: false,
1490
+ error: response.errors.map((e) => e.message).join('; '),
1491
+ };
1492
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1493
+ }
1494
+ const result = {
1495
+ success: true,
1496
+ data: response.data,
1497
+ };
1498
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1499
+ }
1500
+ catch (error) {
1501
+ const result = {
1502
+ success: false,
1503
+ error: error instanceof Error ? error.message : 'Unknown error',
1504
+ };
1505
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1506
+ }
1507
+ });
1508
+ // ============================================================================
1509
+ // Tool: Create Data Table Row
1510
+ // ============================================================================
1511
+ server.tool('create_data_table_row', 'Create a new row in a data table. Row data keys must match column keys defined in the table schema. Example: Add a new menu item to a menu board table.', {
1512
+ tableId: z.string().describe('The table ID to add the row to'),
1513
+ data: z.array(z.unknown()).describe('Row data as a JSON array. Keys must match column keys defined in the table schema.'),
1514
+ }, async (args) => {
1515
+ try {
1516
+ const mutation = `
1517
+ mutation CreateDataTableRow($input: CreateDataTableRowInput) {
1518
+ createDataTableRow(input: $input) {
1519
+ success
1520
+ row {
1521
+ id
1522
+ sortOrder
1523
+ data
1524
+ updatedAt
1525
+ }
1526
+ error
1527
+ validationErrors
1528
+ }
1529
+ }`;
1530
+ const response = await graphqlClient.query(mutation, {
1531
+ input: {
1532
+ tableId: args.tableId,
1533
+ data: args.data,
1534
+ },
1535
+ });
1536
+ if (response.errors && response.errors.length > 0) {
1537
+ const result = {
1538
+ success: false,
1539
+ error: response.errors.map((e) => e.message).join('; '),
1540
+ };
1541
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1542
+ }
1543
+ const result = {
1544
+ success: true,
1545
+ data: response.data,
1546
+ };
1547
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1548
+ }
1549
+ catch (error) {
1550
+ const result = {
1551
+ success: false,
1552
+ error: error instanceof Error ? error.message : 'Unknown error',
1553
+ };
1554
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1555
+ }
1556
+ });
1557
+ // ============================================================================
1558
+ // Tool: Update Data Table Row
1559
+ // ============================================================================
1560
+ server.tool('update_data_table_row', 'Update an existing row in a data table (partial update - only provided keys are changed). Supports optimistic concurrency via eTag. Example: Update the price of a menu item.', {
1561
+ tableId: z.string().describe('The table ID containing the row'),
1562
+ rowId: z.string().describe('The row ID to update'),
1563
+ data: z.array(z.unknown()).describe('Partial row data. Only provided keys are updated.'),
1564
+ eTag: z.string().optional().describe('Optional ETag for optimistic concurrency. Pass the row\'s current ETag to prevent conflicting writes.'),
1565
+ }, async (args) => {
1566
+ try {
1567
+ const mutation = `
1568
+ mutation UpdateDataTableRow($input: UpdateDataTableRowInput) {
1569
+ updateDataTableRow(input: $input) {
1570
+ success
1571
+ row {
1572
+ id
1573
+ sortOrder
1574
+ data
1575
+ updatedAt
1576
+ }
1577
+ error
1578
+ validationErrors
1579
+ }
1580
+ }`;
1581
+ const input = {
1582
+ tableId: args.tableId,
1583
+ rowId: args.rowId,
1584
+ data: args.data,
1585
+ };
1586
+ if (args.eTag !== undefined)
1587
+ input.eTag = args.eTag;
1588
+ const response = await graphqlClient.query(mutation, { input });
1589
+ if (response.errors && response.errors.length > 0) {
1590
+ const result = {
1591
+ success: false,
1592
+ error: response.errors.map((e) => e.message).join('; '),
1593
+ };
1594
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1595
+ }
1596
+ const result = {
1597
+ success: true,
1598
+ data: response.data,
1599
+ };
1600
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1601
+ }
1602
+ catch (error) {
1603
+ const result = {
1604
+ success: false,
1605
+ error: error instanceof Error ? error.message : 'Unknown error',
1606
+ };
1607
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1608
+ }
1609
+ });
1610
+ // ============================================================================
1611
+ // Tool: Delete Data Table Row
1612
+ // ============================================================================
1613
+ server.tool('delete_data_table_row', 'Delete a row from a data table.', {
1614
+ tableId: z.string().describe('The data table ID'),
1615
+ rowId: z.string().describe('The row ID to delete'),
1616
+ }, async (args) => {
1617
+ try {
1618
+ const mutation = `
1619
+ mutation DeleteDataTableRow($tableId: String, $rowId: String) {
1620
+ deleteDataTableRow(tableId: $tableId, rowId: $rowId) {
1621
+ success
1622
+ error
1623
+ }
1624
+ }`;
1625
+ const response = await graphqlClient.query(mutation, {
1626
+ tableId: args.tableId,
1627
+ rowId: args.rowId,
1628
+ });
1629
+ if (response.errors && response.errors.length > 0) {
1630
+ const result = {
1631
+ success: false,
1632
+ error: response.errors.map((e) => e.message).join('; '),
1633
+ };
1634
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1635
+ }
1636
+ const result = {
1637
+ success: true,
1638
+ data: response.data,
1639
+ };
1640
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1641
+ }
1642
+ catch (error) {
1643
+ const result = {
1644
+ success: false,
1645
+ error: error instanceof Error ? error.message : 'Unknown error',
1646
+ };
1647
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1648
+ }
1649
+ });
1650
+ // ============================================================================
1651
+ // Tool: Batch Create Data Table Rows
1652
+ // ============================================================================
1653
+ server.tool('batch_create_data_table_rows', 'Batch create multiple rows in a data table (max 100 rows per call). Example: Bulk-load menu items from an external data source.', {
1654
+ tableId: z.string().describe('The table ID to add rows to'),
1655
+ rows: z.array(z.array(z.unknown())).describe('Array of row data objects to create (max 100)'),
1656
+ }, async (args) => {
1657
+ try {
1658
+ const mutation = `
1659
+ mutation BatchCreateDataTableRows($input: BatchCreateDataTableRowsInput) {
1660
+ batchCreateDataTableRows(input: $input) {
1661
+ success
1662
+ results {
1663
+ rowId
1664
+ success
1665
+ error
1666
+ }
1667
+ error
1668
+ }
1669
+ }`;
1670
+ const response = await graphqlClient.query(mutation, {
1671
+ input: {
1672
+ tableId: args.tableId,
1673
+ rows: args.rows,
1674
+ },
1675
+ });
1676
+ if (response.errors && response.errors.length > 0) {
1677
+ const result = {
1678
+ success: false,
1679
+ error: response.errors.map((e) => e.message).join('; '),
1680
+ };
1681
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1682
+ }
1683
+ const result = {
1684
+ success: true,
1685
+ data: response.data,
1686
+ };
1687
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1688
+ }
1689
+ catch (error) {
1690
+ const result = {
1691
+ success: false,
1692
+ error: error instanceof Error ? error.message : 'Unknown error',
1693
+ };
1694
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1695
+ }
1696
+ });
1697
+ // ============================================================================
1698
+ // Tool: Batch Delete Data Table Rows
1699
+ // ============================================================================
1700
+ server.tool('batch_delete_data_table_rows', 'Batch delete multiple rows from a data table (max 100 row IDs per call).', {
1701
+ tableId: z.string().describe('The table ID containing the rows'),
1702
+ rowIds: z.array(z.string()).describe('Row IDs to delete (max 100)'),
1703
+ }, async (args) => {
1704
+ try {
1705
+ const mutation = `
1706
+ mutation BatchDeleteDataTableRows($input: BatchDeleteDataTableRowsInput) {
1707
+ batchDeleteDataTableRows(input: $input) {
1708
+ success
1709
+ deletedCount
1710
+ results {
1711
+ rowId
1712
+ success
1713
+ error
1714
+ }
1715
+ error
1716
+ }
1717
+ }`;
1718
+ const response = await graphqlClient.query(mutation, {
1719
+ input: {
1720
+ tableId: args.tableId,
1721
+ rowIds: args.rowIds,
1722
+ },
1723
+ });
1724
+ if (response.errors && response.errors.length > 0) {
1725
+ const result = {
1726
+ success: false,
1727
+ error: response.errors.map((e) => e.message).join('; '),
1728
+ };
1729
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1730
+ }
1731
+ const result = {
1732
+ success: true,
1733
+ data: response.data,
1734
+ };
1735
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1736
+ }
1737
+ catch (error) {
1738
+ const result = {
1739
+ success: false,
1740
+ error: error instanceof Error ? error.message : 'Unknown error',
1741
+ };
1742
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1743
+ }
1744
+ });
1745
+ // ============================================================================
1746
+ // Tool: Import Data Table Rows
1747
+ // ============================================================================
1748
+ server.tool('import_data_table_rows', 'Import rows from CSV content into a data table. Use replace mode to overwrite all existing rows, or append mode to add to existing rows.', {
1749
+ tableId: z.string().describe('The table ID to import rows into'),
1750
+ csvContent: z.string().describe('Raw CSV content (header row + data rows)'),
1751
+ replace: z.boolean().default(false).describe('When true, replaces all existing rows. When false (default), appends to existing rows.'),
1752
+ }, async (args) => {
1753
+ try {
1754
+ const mutation = `
1755
+ mutation ImportDataTableRows($input: ImportDataTableRowsInput) {
1756
+ importDataTableRows(input: $input) {
1757
+ success
1758
+ successCount
1759
+ errorCount
1760
+ errors {
1761
+ lineNumber
1762
+ error
1763
+ }
1764
+ error
1765
+ }
1766
+ }`;
1767
+ const response = await graphqlClient.query(mutation, {
1768
+ input: {
1769
+ tableId: args.tableId,
1770
+ csvContent: args.csvContent,
1771
+ replace: args.replace,
1772
+ },
1773
+ });
1774
+ if (response.errors && response.errors.length > 0) {
1775
+ const result = {
1776
+ success: false,
1777
+ error: response.errors.map((e) => e.message).join('; '),
1778
+ };
1779
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1780
+ }
1781
+ const result = {
1782
+ success: true,
1783
+ data: response.data,
1784
+ };
1785
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1786
+ }
1787
+ catch (error) {
1788
+ const result = {
1789
+ success: false,
1790
+ error: error instanceof Error ? error.message : 'Unknown error',
1791
+ };
1792
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1793
+ }
1794
+ });
1795
+ // ============================================================================
1796
+ // Tool: Reorder Data Table Rows
1797
+ // ============================================================================
1798
+ server.tool('reorder_data_table_rows', 'Reorder rows in a data table by specifying the desired row ID sequence.', {
1799
+ tableId: z.string().describe('The table ID containing the rows'),
1800
+ rowIds: z.array(z.string()).describe('Row IDs in the desired display order'),
1801
+ }, async (args) => {
1802
+ try {
1803
+ const mutation = `
1804
+ mutation ReorderDataTableRows($input: ReorderDataTableRowsInput) {
1805
+ reorderDataTableRows(input: $input) {
1806
+ success
1807
+ error
1808
+ }
1809
+ }`;
1810
+ const response = await graphqlClient.query(mutation, {
1811
+ input: {
1812
+ tableId: args.tableId,
1813
+ rowIds: args.rowIds,
1814
+ },
1815
+ });
1816
+ if (response.errors && response.errors.length > 0) {
1817
+ const result = {
1818
+ success: false,
1819
+ error: response.errors.map((e) => e.message).join('; '),
1820
+ };
1821
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1822
+ }
1823
+ const result = {
1824
+ success: true,
1825
+ data: response.data,
1826
+ };
1827
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1828
+ }
1829
+ catch (error) {
1830
+ const result = {
1831
+ success: false,
1832
+ error: error instanceof Error ? error.message : 'Unknown error',
1833
+ };
1834
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1835
+ }
1836
+ });
1837
+ // ============================================================================
1838
+ // Tool: Rollback Data Table Row
1839
+ // ============================================================================
1840
+ server.tool('rollback_data_table_row', 'Roll back a data table row to a previous version, creating a new version for the rollback action. Example: Undo an accidental price change by restoring a previous version.', {
1841
+ tableId: z.string().describe('The table ID containing the row'),
1842
+ rowId: z.string().describe('The row ID to roll back'),
1843
+ version: z.number().describe('The version number to restore'),
1844
+ }, async (args) => {
1845
+ try {
1846
+ const mutation = `
1847
+ mutation RollbackDataTableRow($input: RollbackDataTableRowInput) {
1848
+ rollbackDataTableRow(input: $input) {
1849
+ success
1850
+ newVersion
1851
+ restoredRow {
1852
+ id
1853
+ sortOrder
1854
+ data
1855
+ updatedAt
1856
+ }
1857
+ error
1858
+ }
1859
+ }`;
1860
+ const response = await graphqlClient.query(mutation, {
1861
+ input: {
1862
+ tableId: args.tableId,
1863
+ rowId: args.rowId,
1864
+ version: args.version,
1865
+ },
1866
+ });
1867
+ if (response.errors && response.errors.length > 0) {
1868
+ const result = {
1869
+ success: false,
1870
+ error: response.errors.map((e) => e.message).join('; '),
1871
+ };
1872
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1873
+ }
1874
+ const result = {
1875
+ success: true,
1876
+ data: response.data,
1877
+ };
1878
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1879
+ }
1880
+ catch (error) {
1881
+ const result = {
1882
+ success: false,
1883
+ error: error instanceof Error ? error.message : 'Unknown error',
1884
+ };
1885
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1886
+ }
1887
+ });
1888
+ // ============================================================================
922
1889
  // Tool: Cache Management
923
1890
  // ============================================================================
924
1891
  server.tool('manage_cache', 'Manage the response cache - view statistics or clear cached data.', {
@@ -968,6 +1935,15 @@ server.resource('schema://reveldigital/graphql', 'Revel Digital GraphQL Schema',
968
1935
  - templateGroups - Query template groups
969
1936
  - user - Query users
970
1937
 
1938
+ ### Data Tables
1939
+ - list_data_tables - List data tables with pagination
1940
+ - get_data_table - Get table definition with column schema
1941
+ - list_data_table_rows - List rows with filtering, sorting, pagination
1942
+ - get_data_table_row - Get a single row by ID
1943
+ - export_data_table_rows - Export all rows as CSV
1944
+ - get_data_table_row_versions - List row version history
1945
+ - get_data_table_row_version - Get a specific version snapshot
1946
+
971
1947
  ### Device Commands & Permissions
972
1948
  - displayCommands - Query available commands for the account (e.g., reboot, clear_cache)
973
1949
  - permissions - Get current user/API key permissions and allowed/denied mutations
@@ -1014,6 +1990,19 @@ Raw logs return too much data for AI context windows.
1014
1990
  - remove_playlist_source - Remove a source from a playlist
1015
1991
  - reorder_playlist_sources - Reorder sources within a playlist
1016
1992
 
1993
+ ### Data Table Management
1994
+ - create_data_table - Create a new data table with column schema
1995
+ - update_data_table - Update table definition (partial update)
1996
+ - delete_data_table - Delete a table and all its rows
1997
+ - create_data_table_row - Create a new row
1998
+ - update_data_table_row - Update a row (partial update with optimistic concurrency)
1999
+ - delete_data_table_row - Delete a row
2000
+ - batch_create_data_table_rows - Batch create rows (max 100)
2001
+ - batch_delete_data_table_rows - Batch delete rows (max 100)
2002
+ - import_data_table_rows - Import rows from CSV content
2003
+ - reorder_data_table_rows - Reorder rows
2004
+ - rollback_data_table_row - Roll back a row to a previous version
2005
+
1017
2006
  ## Field Presets
1018
2007
  Use the build_query tool with preset parameter:
1019
2008
  - minimal - Just ID and name
@@ -1040,6 +2029,29 @@ or use set_auth tool at runtime.
1040
2029
  };
1041
2030
  });
1042
2031
  // ============================================================================
2032
+ // Prompts: System Prompts for AI Agents
2033
+ // ============================================================================
2034
+ server.prompt('system-prompt', 'Full system prompt for AI agents. Use as the system message for complete tool docs, query patterns, and domain knowledge.', { timezone: z.string().optional().describe('IANA timezone (e.g., "America/New_York"). Defaults to UTC.') }, async (args) => {
2035
+ const prompt = generateSystemPrompt({
2036
+ currentDateTime: new Date().toISOString(),
2037
+ timezone: args.timezone,
2038
+ });
2039
+ return {
2040
+ description: 'Revel Digital AI agent system prompt',
2041
+ messages: [{ role: 'user', content: { type: 'text', text: prompt } }],
2042
+ };
2043
+ });
2044
+ server.prompt('system-prompt-compact', 'Token-optimized compact system prompt for AI agents. Use when context window space is limited.', { timezone: z.string().optional().describe('IANA timezone (e.g., "America/New_York"). Defaults to UTC.') }, async (args) => {
2045
+ const prompt = generateCompactPrompt({
2046
+ currentDateTime: new Date().toISOString(),
2047
+ timezone: args.timezone,
2048
+ });
2049
+ return {
2050
+ description: 'Revel Digital AI agent system prompt (compact)',
2051
+ messages: [{ role: 'user', content: { type: 'text', text: prompt } }],
2052
+ };
2053
+ });
2054
+ // ============================================================================
1043
2055
  // Start Server
1044
2056
  // ============================================================================
1045
2057
  async function main() {