@reveldigital/mcp-graphql-proxy 2.1.0 → 2.3.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
@@ -119,9 +119,11 @@ server.tool('build_query', 'Build an optimized GraphQL query with field selectio
119
119
  'playlist', 'playlistGroups', 'schedule', 'scheduleGroups',
120
120
  'template', 'templateGroups', 'user',
121
121
  // Alerts & Monitoring
122
- 'alert',
122
+ 'alert', 'alertRule',
123
123
  // Audit & Compliance
124
124
  'auditEvent',
125
+ // Connector Data
126
+ 'connectorData',
125
127
  // Data Tables
126
128
  'dataTables', 'dataTable',
127
129
  // Device Commands & Permissions
@@ -159,6 +161,11 @@ server.tool('build_query', 'Build an optimized GraphQL query with field selectio
159
161
  // Alert-specific arguments
160
162
  activeOnly: z.boolean().optional().describe('Filter to show only active alerts (alert only)'),
161
163
  resolved: z.boolean().optional().describe('Filter by resolved state (alert only)'),
164
+ orgId: z.string().optional().describe('Filter by organization ID (alert, alertRule)'),
165
+ // Connector data arguments
166
+ datasourceId: z.string().optional().describe('Registered data source identifier (connectorData only)'),
167
+ datasetId: z.string().optional().describe('Registered dataset identifier on that source (connectorData only)'),
168
+ params: z.record(z.unknown()).optional().describe('Optional validated parameters for the dataset (connectorData only)'),
162
169
  }, async (args) => {
163
170
  try {
164
171
  const queryArgs = {};
@@ -197,6 +204,15 @@ server.tool('build_query', 'Build an optimized GraphQL query with field selectio
197
204
  queryArgs.activeOnly = args.activeOnly;
198
205
  if (args.resolved !== undefined)
199
206
  queryArgs.resolved = args.resolved;
207
+ if (args.orgId !== undefined)
208
+ queryArgs.orgId = args.orgId;
209
+ // Connector data arguments
210
+ if (args.datasourceId !== undefined)
211
+ queryArgs.datasourceId = args.datasourceId;
212
+ if (args.datasetId !== undefined)
213
+ queryArgs.datasetId = args.datasetId;
214
+ if (args.params !== undefined)
215
+ queryArgs.params = args.params;
200
216
  const { query, variables } = QueryBuilder.build({
201
217
  operation: args.operation,
202
218
  fields: args.fields ?? [],
@@ -1153,6 +1169,753 @@ mutation ReorderPlaylistSources($input: ReorderPlaylistSourcesInput) {
1153
1169
  }
1154
1170
  });
1155
1171
  // ============================================================================
1172
+ // Tool: Update Template
1173
+ // ============================================================================
1174
+ server.tool('update_template', "Update a template's properties (name, group, tags, dimensions, colors, orientation, script). Only provided fields are changed. Example: AI adjusts template dimensions to optimize for a new display resolution.", {
1175
+ templateId: z.string().describe('The template ID to update'),
1176
+ name: z.string().optional().describe('Template name'),
1177
+ groupId: z.string().optional().describe('Group ID to assign the template to'),
1178
+ tags: z.string().optional().describe('Tags (newline-delimited values stored in the Description field)'),
1179
+ script: z.string().optional().describe('Template script for custom logic'),
1180
+ backColor: z.string().optional().describe("Background color (e.g., '#000000')"),
1181
+ width: z.number().optional().describe('Template width in pixels'),
1182
+ height: z.number().optional().describe('Template height in pixels'),
1183
+ orientation: z.number().optional().describe('Display orientation (0=Landscape, 1=Portrait, 2=Landscape Reversed, 3=Portrait Reversed)'),
1184
+ }, async (args) => {
1185
+ try {
1186
+ const mutation = `
1187
+ mutation UpdateTemplate($input: UpdateTemplateInput) {
1188
+ updateTemplate(input: $input) {
1189
+ success
1190
+ template {
1191
+ id
1192
+ name
1193
+ width
1194
+ height
1195
+ orientation
1196
+ modules {
1197
+ id
1198
+ name
1199
+ type
1200
+ left
1201
+ top
1202
+ width
1203
+ height
1204
+ playlistId
1205
+ sequence
1206
+ }
1207
+ }
1208
+ error
1209
+ }
1210
+ }`;
1211
+ const input = { templateId: args.templateId };
1212
+ if (args.name !== undefined)
1213
+ input.name = args.name;
1214
+ if (args.groupId !== undefined)
1215
+ input.groupId = args.groupId;
1216
+ if (args.tags !== undefined)
1217
+ input.tags = args.tags;
1218
+ if (args.script !== undefined)
1219
+ input.script = args.script;
1220
+ if (args.backColor !== undefined)
1221
+ input.backColor = args.backColor;
1222
+ if (args.width !== undefined)
1223
+ input.width = args.width;
1224
+ if (args.height !== undefined)
1225
+ input.height = args.height;
1226
+ if (args.orientation !== undefined)
1227
+ input.orientation = args.orientation;
1228
+ const response = await graphqlClient.query(mutation, { input });
1229
+ if (response.errors && response.errors.length > 0) {
1230
+ const result = {
1231
+ success: false,
1232
+ error: response.errors.map((e) => e.message).join('; '),
1233
+ };
1234
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1235
+ }
1236
+ const result = {
1237
+ success: true,
1238
+ data: response.data,
1239
+ };
1240
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1241
+ }
1242
+ catch (error) {
1243
+ const result = {
1244
+ success: false,
1245
+ error: error instanceof Error ? error.message : 'Unknown error',
1246
+ };
1247
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1248
+ }
1249
+ });
1250
+ // ============================================================================
1251
+ // Tool: Add Template Module
1252
+ // ============================================================================
1253
+ server.tool('add_template_module', "Add a new module (zone/region) to a template. Modules define rectangular areas where content from a playlist is displayed. Example: AI adds a new weather module to show real-time conditions in the corner.", {
1254
+ templateId: z.string().describe('The template ID to add the module to'),
1255
+ module: z.object({
1256
+ name: z.string().optional().describe('Module/zone name'),
1257
+ type: z.string().optional().describe("Module/zone type (e.g., 'Playlist', 'Clock', 'Weather', 'Ticker', 'Web')"),
1258
+ left: z.number().optional().describe('Left position in pixels'),
1259
+ top: z.number().optional().describe('Top position in pixels'),
1260
+ width: z.number().optional().describe('Width in pixels'),
1261
+ height: z.number().optional().describe('Height in pixels'),
1262
+ playlistId: z.string().optional().describe('Playlist ID assigned to this module'),
1263
+ sequence: z.number().optional().describe('Z-order/sequence for overlapping modules (higher appears in front)'),
1264
+ }).describe('The module to add'),
1265
+ }, async (args) => {
1266
+ try {
1267
+ const mutation = `
1268
+ mutation AddTemplateModule($input: AddTemplateModuleInput) {
1269
+ addTemplateModule(input: $input) {
1270
+ success
1271
+ module {
1272
+ id
1273
+ name
1274
+ type
1275
+ left
1276
+ top
1277
+ width
1278
+ height
1279
+ playlistId
1280
+ sequence
1281
+ options {
1282
+ id
1283
+ name
1284
+ value
1285
+ }
1286
+ }
1287
+ error
1288
+ }
1289
+ }`;
1290
+ const response = await graphqlClient.query(mutation, {
1291
+ input: {
1292
+ templateId: args.templateId,
1293
+ module: args.module,
1294
+ },
1295
+ });
1296
+ if (response.errors && response.errors.length > 0) {
1297
+ const result = {
1298
+ success: false,
1299
+ error: response.errors.map((e) => e.message).join('; '),
1300
+ };
1301
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1302
+ }
1303
+ const result = {
1304
+ success: true,
1305
+ data: response.data,
1306
+ };
1307
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1308
+ }
1309
+ catch (error) {
1310
+ const result = {
1311
+ success: false,
1312
+ error: error instanceof Error ? error.message : 'Unknown error',
1313
+ };
1314
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1315
+ }
1316
+ });
1317
+ // ============================================================================
1318
+ // Tool: Update Template Module
1319
+ // ============================================================================
1320
+ server.tool('update_template_module', 'Update a module (zone/region) within a template. Only provided fields are changed. Example: AI repositions a module based on engagement analytics, or changes the assigned playlist.', {
1321
+ templateId: z.string().describe('The template ID containing the module'),
1322
+ moduleId: z.string().describe('The module ID to update'),
1323
+ name: z.string().optional().describe('Module/zone name'),
1324
+ type: z.string().optional().describe("Module/zone type (e.g., 'Playlist', 'Clock', 'Weather', 'Ticker', 'Web')"),
1325
+ left: z.number().optional().describe('Left position in pixels'),
1326
+ top: z.number().optional().describe('Top position in pixels'),
1327
+ width: z.number().optional().describe('Width in pixels'),
1328
+ height: z.number().optional().describe('Height in pixels'),
1329
+ playlistId: z.string().optional().describe('Playlist ID assigned to this module'),
1330
+ sequence: z.number().optional().describe('Z-order/sequence for overlapping modules'),
1331
+ }, async (args) => {
1332
+ try {
1333
+ const mutation = `
1334
+ mutation UpdateTemplateModule($input: UpdateTemplateModuleInput) {
1335
+ updateTemplateModule(input: $input) {
1336
+ success
1337
+ module {
1338
+ id
1339
+ name
1340
+ type
1341
+ left
1342
+ top
1343
+ width
1344
+ height
1345
+ playlistId
1346
+ sequence
1347
+ options {
1348
+ id
1349
+ name
1350
+ value
1351
+ }
1352
+ }
1353
+ error
1354
+ }
1355
+ }`;
1356
+ const input = {
1357
+ templateId: args.templateId,
1358
+ moduleId: args.moduleId,
1359
+ };
1360
+ if (args.name !== undefined)
1361
+ input.name = args.name;
1362
+ if (args.type !== undefined)
1363
+ input.type = args.type;
1364
+ if (args.left !== undefined)
1365
+ input.left = args.left;
1366
+ if (args.top !== undefined)
1367
+ input.top = args.top;
1368
+ if (args.width !== undefined)
1369
+ input.width = args.width;
1370
+ if (args.height !== undefined)
1371
+ input.height = args.height;
1372
+ if (args.playlistId !== undefined)
1373
+ input.playlistId = args.playlistId;
1374
+ if (args.sequence !== undefined)
1375
+ input.sequence = args.sequence;
1376
+ const response = await graphqlClient.query(mutation, { input });
1377
+ if (response.errors && response.errors.length > 0) {
1378
+ const result = {
1379
+ success: false,
1380
+ error: response.errors.map((e) => e.message).join('; '),
1381
+ };
1382
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1383
+ }
1384
+ const result = {
1385
+ success: true,
1386
+ data: response.data,
1387
+ };
1388
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1389
+ }
1390
+ catch (error) {
1391
+ const result = {
1392
+ success: false,
1393
+ error: error instanceof Error ? error.message : 'Unknown error',
1394
+ };
1395
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1396
+ }
1397
+ });
1398
+ // ============================================================================
1399
+ // Tool: Remove Template Module
1400
+ // ============================================================================
1401
+ server.tool('remove_template_module', 'Remove a module (zone/region) from a template by module ID. Example: AI removes an underperforming ad module to optimize content flow.', {
1402
+ templateId: z.string().describe('The template ID containing the module'),
1403
+ moduleId: z.string().describe('The module ID to remove'),
1404
+ }, async (args) => {
1405
+ try {
1406
+ const mutation = `
1407
+ mutation RemoveTemplateModule($input: RemoveTemplateModuleInput) {
1408
+ removeTemplateModule(input: $input) {
1409
+ success
1410
+ module {
1411
+ id
1412
+ name
1413
+ type
1414
+ }
1415
+ error
1416
+ }
1417
+ }`;
1418
+ const response = await graphqlClient.query(mutation, {
1419
+ input: {
1420
+ templateId: args.templateId,
1421
+ moduleId: args.moduleId,
1422
+ },
1423
+ });
1424
+ if (response.errors && response.errors.length > 0) {
1425
+ const result = {
1426
+ success: false,
1427
+ error: response.errors.map((e) => e.message).join('; '),
1428
+ };
1429
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1430
+ }
1431
+ const result = {
1432
+ success: true,
1433
+ data: response.data,
1434
+ };
1435
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1436
+ }
1437
+ catch (error) {
1438
+ const result = {
1439
+ success: false,
1440
+ error: error instanceof Error ? error.message : 'Unknown error',
1441
+ };
1442
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1443
+ }
1444
+ });
1445
+ // ============================================================================
1446
+ // Tool: Reorder Template Modules
1447
+ // ============================================================================
1448
+ server.tool('reorder_template_modules', 'Reorder modules (zones) within a template by updating their sequence/z-order. Higher sequence values appear in front. Example: AI reorders modules to bring important content to the front based on viewer engagement.', {
1449
+ templateId: z.string().describe('The template ID'),
1450
+ moduleSequences: z.array(z.object({
1451
+ moduleId: z.string().describe('The module ID'),
1452
+ sequence: z.number().describe('The new sequence/z-order value'),
1453
+ })).describe('List of module IDs with their new sequence numbers'),
1454
+ }, async (args) => {
1455
+ try {
1456
+ const mutation = `
1457
+ mutation ReorderTemplateModules($input: ReorderTemplateModulesInput) {
1458
+ reorderTemplateModules(input: $input) {
1459
+ success
1460
+ template {
1461
+ id
1462
+ name
1463
+ modules {
1464
+ id
1465
+ name
1466
+ type
1467
+ sequence
1468
+ }
1469
+ }
1470
+ error
1471
+ }
1472
+ }`;
1473
+ const response = await graphqlClient.query(mutation, {
1474
+ input: {
1475
+ templateId: args.templateId,
1476
+ moduleSequences: args.moduleSequences,
1477
+ },
1478
+ });
1479
+ if (response.errors && response.errors.length > 0) {
1480
+ const result = {
1481
+ success: false,
1482
+ error: response.errors.map((e) => e.message).join('; '),
1483
+ };
1484
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1485
+ }
1486
+ const result = {
1487
+ success: true,
1488
+ data: response.data,
1489
+ };
1490
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1491
+ }
1492
+ catch (error) {
1493
+ const result = {
1494
+ success: false,
1495
+ error: error instanceof Error ? error.message : 'Unknown error',
1496
+ };
1497
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1498
+ }
1499
+ });
1500
+ // ============================================================================
1501
+ // Tool: Batch Update Template Modules
1502
+ // ============================================================================
1503
+ server.tool('batch_update_template_modules', 'Batch update multiple modules in a template at once. Each entry must include the moduleId; only provided fields are changed. Example: AI repositions all modules to create a new layout optimized for viewer attention.', {
1504
+ templateId: z.string().describe('The template ID'),
1505
+ modules: z.array(z.object({
1506
+ moduleId: z.string().describe('The module ID to update'),
1507
+ name: z.string().optional().describe('Module/zone name'),
1508
+ type: z.string().optional().describe('Module/zone type'),
1509
+ left: z.number().optional().describe('Left position in pixels'),
1510
+ top: z.number().optional().describe('Top position in pixels'),
1511
+ width: z.number().optional().describe('Width in pixels'),
1512
+ height: z.number().optional().describe('Height in pixels'),
1513
+ playlistId: z.string().optional().describe('Playlist ID assigned to this module'),
1514
+ sequence: z.number().optional().describe('Z-order/sequence'),
1515
+ })).describe('List of module updates to apply (each requires moduleId)'),
1516
+ }, async (args) => {
1517
+ try {
1518
+ const mutation = `
1519
+ mutation BatchUpdateTemplateModules($input: BatchUpdateTemplateModulesInput) {
1520
+ batchUpdateTemplateModules(input: $input) {
1521
+ totalModules
1522
+ successCount
1523
+ failureCount
1524
+ results {
1525
+ success
1526
+ module {
1527
+ id
1528
+ name
1529
+ type
1530
+ left
1531
+ top
1532
+ width
1533
+ height
1534
+ playlistId
1535
+ sequence
1536
+ }
1537
+ error
1538
+ }
1539
+ }
1540
+ }`;
1541
+ const modules = args.modules.map((m) => ({ ...m, templateId: args.templateId }));
1542
+ const response = await graphqlClient.query(mutation, {
1543
+ input: {
1544
+ templateId: args.templateId,
1545
+ modules,
1546
+ },
1547
+ });
1548
+ if (response.errors && response.errors.length > 0) {
1549
+ const result = {
1550
+ success: false,
1551
+ error: response.errors.map((e) => e.message).join('; '),
1552
+ };
1553
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1554
+ }
1555
+ const result = {
1556
+ success: true,
1557
+ data: response.data,
1558
+ };
1559
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1560
+ }
1561
+ catch (error) {
1562
+ const result = {
1563
+ success: false,
1564
+ error: error instanceof Error ? error.message : 'Unknown error',
1565
+ };
1566
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1567
+ }
1568
+ });
1569
+ // ============================================================================
1570
+ // Tool: Set Module Options
1571
+ // ============================================================================
1572
+ server.tool('set_module_options', 'Set all configuration options for a module/zone (replaces existing options). Options are key-value pairs storing type-specific settings. Example: AI configures a weather module with location, units, and API key settings.', {
1573
+ templateId: z.string().describe('The template ID'),
1574
+ moduleId: z.string().describe('The module ID'),
1575
+ options: z.array(z.object({
1576
+ name: z.string().describe("Option name/key (e.g., 'timezone', 'location', 'url')"),
1577
+ value: z.string().describe('Option value (string representation, parsed by option type)'),
1578
+ })).describe('List of options to set (replaces all existing options)'),
1579
+ }, async (args) => {
1580
+ try {
1581
+ const mutation = `
1582
+ mutation SetModuleOptions($input: SetModuleOptionsInput) {
1583
+ setModuleOptions(input: $input) {
1584
+ success
1585
+ module {
1586
+ id
1587
+ name
1588
+ type
1589
+ options {
1590
+ id
1591
+ name
1592
+ value
1593
+ }
1594
+ }
1595
+ error
1596
+ }
1597
+ }`;
1598
+ const response = await graphqlClient.query(mutation, {
1599
+ input: {
1600
+ templateId: args.templateId,
1601
+ moduleId: args.moduleId,
1602
+ options: args.options,
1603
+ },
1604
+ });
1605
+ if (response.errors && response.errors.length > 0) {
1606
+ const result = {
1607
+ success: false,
1608
+ error: response.errors.map((e) => e.message).join('; '),
1609
+ };
1610
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1611
+ }
1612
+ const result = {
1613
+ success: true,
1614
+ data: response.data,
1615
+ };
1616
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1617
+ }
1618
+ catch (error) {
1619
+ const result = {
1620
+ success: false,
1621
+ error: error instanceof Error ? error.message : 'Unknown error',
1622
+ };
1623
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1624
+ }
1625
+ });
1626
+ // ============================================================================
1627
+ // Tool: Upsert Module Option
1628
+ // ============================================================================
1629
+ server.tool('upsert_module_option', 'Add or update a single module/zone option (matched by name). Example: AI updates the refresh interval for a web module.', {
1630
+ templateId: z.string().describe('The template ID'),
1631
+ moduleId: z.string().describe('The module ID'),
1632
+ option: z.object({
1633
+ name: z.string().describe("Option name/key (e.g., 'timezone', 'location', 'url')"),
1634
+ value: z.string().describe('Option value (string representation, parsed by option type)'),
1635
+ }).describe('The option to create or update (matched by name)'),
1636
+ }, async (args) => {
1637
+ try {
1638
+ const mutation = `
1639
+ mutation UpsertModuleOption($input: UpsertModuleOptionInput) {
1640
+ upsertModuleOption(input: $input) {
1641
+ success
1642
+ option {
1643
+ id
1644
+ name
1645
+ value
1646
+ }
1647
+ error
1648
+ }
1649
+ }`;
1650
+ const response = await graphqlClient.query(mutation, {
1651
+ input: {
1652
+ templateId: args.templateId,
1653
+ moduleId: args.moduleId,
1654
+ option: args.option,
1655
+ },
1656
+ });
1657
+ if (response.errors && response.errors.length > 0) {
1658
+ const result = {
1659
+ success: false,
1660
+ error: response.errors.map((e) => e.message).join('; '),
1661
+ };
1662
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1663
+ }
1664
+ const result = {
1665
+ success: true,
1666
+ data: response.data,
1667
+ };
1668
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1669
+ }
1670
+ catch (error) {
1671
+ const result = {
1672
+ success: false,
1673
+ error: error instanceof Error ? error.message : 'Unknown error',
1674
+ };
1675
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1676
+ }
1677
+ });
1678
+ // ============================================================================
1679
+ // Tool: Remove Module Option
1680
+ // ============================================================================
1681
+ server.tool('remove_module_option', 'Remove a module/zone option by name. Example: AI removes an outdated configuration setting from a module.', {
1682
+ templateId: z.string().describe('The template ID'),
1683
+ moduleId: z.string().describe('The module ID'),
1684
+ optionName: z.string().describe('The option name to remove'),
1685
+ }, async (args) => {
1686
+ try {
1687
+ const mutation = `
1688
+ mutation RemoveModuleOption($input: RemoveModuleOptionInput) {
1689
+ removeModuleOption(input: $input) {
1690
+ success
1691
+ option {
1692
+ id
1693
+ name
1694
+ value
1695
+ }
1696
+ error
1697
+ }
1698
+ }`;
1699
+ const response = await graphqlClient.query(mutation, {
1700
+ input: {
1701
+ templateId: args.templateId,
1702
+ moduleId: args.moduleId,
1703
+ optionName: args.optionName,
1704
+ },
1705
+ });
1706
+ if (response.errors && response.errors.length > 0) {
1707
+ const result = {
1708
+ success: false,
1709
+ error: response.errors.map((e) => e.message).join('; '),
1710
+ };
1711
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1712
+ }
1713
+ const result = {
1714
+ success: true,
1715
+ data: response.data,
1716
+ };
1717
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1718
+ }
1719
+ catch (error) {
1720
+ const result = {
1721
+ success: false,
1722
+ error: error instanceof Error ? error.message : 'Unknown error',
1723
+ };
1724
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1725
+ }
1726
+ });
1727
+ // ============================================================================
1728
+ // Tools: Alert Rules (create / update / delete)
1729
+ // ============================================================================
1730
+ // The API takes ruleSet as a serialized JSON string, not a nested object. Condition
1731
+ // types and operators are case-sensitive and validated server-side.
1732
+ const ALERT_RULE_CONDITION_TYPES = [
1733
+ 'TimeOffline', 'LastUpdate', 'MemoryUsage', 'CpuUsage', 'DiskUsage', 'NoContent',
1734
+ 'BytesTxDay', 'BytesRxDay', 'BytesTotalDay', 'BytesTxPeriod', 'BytesRxPeriod',
1735
+ 'BytesTotalPeriod', 'ClockSkew', 'DisplayDetected', 'BatteryVoltage',
1736
+ 'BatteryPercentage', 'SystemTemperature',
1737
+ ];
1738
+ const RULE_SET_DESCRIPTION = [
1739
+ 'Rule set configuration as a SERIALIZED JSON STRING (not a nested object).',
1740
+ 'Shape: an array of condition groups combined with logical AND; the `items` within each group are combined with logical OR.',
1741
+ 'Each item is {"type", "op", "value"}:',
1742
+ `\`type\` (required, case-sensitive) one of: ${ALERT_RULE_CONDITION_TYPES.join(', ')}.`,
1743
+ '`op` (optional) one of EQ, NE, GT, LT, GTE, LTE (defaults to EQ).',
1744
+ '`value` (required) a non-empty array of numbers.',
1745
+ 'Example: [{"items":[{"type":"CpuUsage","op":"GTE","value":[90]}]}]',
1746
+ 'Invalid rule sets are rejected. Null or empty leaves the rule unevaluated.',
1747
+ ].join(' ');
1748
+ const ALERT_RULE_SELECTION = `
1749
+ success
1750
+ alertRule {
1751
+ id
1752
+ name
1753
+ ruleSet
1754
+ isEnabled
1755
+ period
1756
+ threshold
1757
+ webhookUrl
1758
+ allDevices
1759
+ allUsers
1760
+ deviceIds
1761
+ userIds
1762
+ createdOn
1763
+ updatedOn
1764
+ }
1765
+ error`;
1766
+ server.tool('create_alert_rule', 'Create a new alert rule — the conditions that trigger alerts. Example: create a rule that fires when a lobby display goes offline. Use build_query with the alertRule operation to read existing rules first.', {
1767
+ name: z.string().describe('The name of the alert rule'),
1768
+ ruleSet: z.string().optional().describe(RULE_SET_DESCRIPTION),
1769
+ isEnabled: z.boolean().optional().describe('Whether the rule is enabled and will be evaluated. Disabled rules are stored but never trigger.'),
1770
+ period: z.number().optional().describe('The look-back / evaluation period, in MINUTES, used by time-window based conditions such as NoContent. Ignored by conditions that read the current value (e.g. CpuUsage).'),
1771
+ threshold: z.number().optional().describe('Legacy scalar threshold. Prefer expressing thresholds inside ruleSet.'),
1772
+ webhookUrl: z.string().optional().describe('Optional HTTPS webhook URL invoked (HTTP POST) when the rule triggers'),
1773
+ allDevices: z.boolean().optional().describe('If true, the rule is evaluated against every device in the account and deviceIds is ignored'),
1774
+ allUsers: z.boolean().optional().describe('If true, all users in the account are notified and userIds is ignored'),
1775
+ deviceIds: z.array(z.string()).optional().describe('Devices to associate with the rule. Ignored when allDevices is true.'),
1776
+ userIds: z.array(z.string()).optional().describe('Users to notify. Ignored when allUsers is true.'),
1777
+ }, async (args) => {
1778
+ try {
1779
+ const mutation = `
1780
+ mutation CreateAlertRule($input: AlertRuleInput) {
1781
+ createAlertRule(input: $input) {${ALERT_RULE_SELECTION}
1782
+ }
1783
+ }`;
1784
+ const input = { name: args.name };
1785
+ if (args.ruleSet !== undefined)
1786
+ input.ruleSet = args.ruleSet;
1787
+ if (args.isEnabled !== undefined)
1788
+ input.isEnabled = args.isEnabled;
1789
+ if (args.period !== undefined)
1790
+ input.period = args.period;
1791
+ if (args.threshold !== undefined)
1792
+ input.threshold = args.threshold;
1793
+ if (args.webhookUrl !== undefined)
1794
+ input.webhookUrl = args.webhookUrl;
1795
+ if (args.allDevices !== undefined)
1796
+ input.allDevices = args.allDevices;
1797
+ if (args.allUsers !== undefined)
1798
+ input.allUsers = args.allUsers;
1799
+ if (args.deviceIds !== undefined)
1800
+ input.deviceIds = args.deviceIds;
1801
+ if (args.userIds !== undefined)
1802
+ input.userIds = args.userIds;
1803
+ const response = await graphqlClient.query(mutation, { input });
1804
+ if (response.errors && response.errors.length > 0) {
1805
+ const result = {
1806
+ success: false,
1807
+ error: response.errors.map((e) => e.message).join('; '),
1808
+ };
1809
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1810
+ }
1811
+ const result = {
1812
+ success: true,
1813
+ data: response.data,
1814
+ };
1815
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1816
+ }
1817
+ catch (error) {
1818
+ const result = {
1819
+ success: false,
1820
+ error: error instanceof Error ? error.message : 'Unknown error',
1821
+ };
1822
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1823
+ }
1824
+ });
1825
+ server.tool('update_alert_rule', 'Update an existing alert rule. Only provided fields are changed. Example: disable a noisy rule, or widen its device scope.', {
1826
+ id: z.string().describe('The alert rule ID to update'),
1827
+ name: z.string().optional().describe('The name of the alert rule'),
1828
+ ruleSet: z.string().optional().describe(RULE_SET_DESCRIPTION),
1829
+ isEnabled: z.boolean().optional().describe('Whether the rule is enabled and will be evaluated'),
1830
+ period: z.number().optional().describe('The look-back / evaluation period, in MINUTES, used by time-window based conditions such as NoContent. Ignored by conditions that read the current value (e.g. CpuUsage).'),
1831
+ threshold: z.number().optional().describe('Legacy scalar threshold. Prefer expressing thresholds inside ruleSet.'),
1832
+ webhookUrl: z.string().optional().describe('Optional HTTPS webhook URL invoked when the rule triggers'),
1833
+ allDevices: z.boolean().optional().describe('If true, the rule is evaluated against every device and deviceIds is ignored'),
1834
+ allUsers: z.boolean().optional().describe('If true, all users are notified and userIds is ignored'),
1835
+ deviceIds: z.array(z.string()).optional().describe('Devices to associate with the rule. Ignored when allDevices is true.'),
1836
+ userIds: z.array(z.string()).optional().describe('Users to notify. Ignored when allUsers is true.'),
1837
+ }, async (args) => {
1838
+ try {
1839
+ const mutation = `
1840
+ mutation UpdateAlertRule($input: AlertRuleInput) {
1841
+ updateAlertRule(input: $input) {${ALERT_RULE_SELECTION}
1842
+ }
1843
+ }`;
1844
+ const input = { id: args.id };
1845
+ if (args.name !== undefined)
1846
+ input.name = args.name;
1847
+ if (args.ruleSet !== undefined)
1848
+ input.ruleSet = args.ruleSet;
1849
+ if (args.isEnabled !== undefined)
1850
+ input.isEnabled = args.isEnabled;
1851
+ if (args.period !== undefined)
1852
+ input.period = args.period;
1853
+ if (args.threshold !== undefined)
1854
+ input.threshold = args.threshold;
1855
+ if (args.webhookUrl !== undefined)
1856
+ input.webhookUrl = args.webhookUrl;
1857
+ if (args.allDevices !== undefined)
1858
+ input.allDevices = args.allDevices;
1859
+ if (args.allUsers !== undefined)
1860
+ input.allUsers = args.allUsers;
1861
+ if (args.deviceIds !== undefined)
1862
+ input.deviceIds = args.deviceIds;
1863
+ if (args.userIds !== undefined)
1864
+ input.userIds = args.userIds;
1865
+ const response = await graphqlClient.query(mutation, { input });
1866
+ if (response.errors && response.errors.length > 0) {
1867
+ const result = {
1868
+ success: false,
1869
+ error: response.errors.map((e) => e.message).join('; '),
1870
+ };
1871
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1872
+ }
1873
+ const result = {
1874
+ success: true,
1875
+ data: response.data,
1876
+ };
1877
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1878
+ }
1879
+ catch (error) {
1880
+ const result = {
1881
+ success: false,
1882
+ error: error instanceof Error ? error.message : 'Unknown error',
1883
+ };
1884
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1885
+ }
1886
+ });
1887
+ server.tool('delete_alert_rule', 'Delete an alert rule by ID. This stops the rule from ever triggering again — consider update_alert_rule with isEnabled=false if the rule may be needed later.', {
1888
+ id: z.string().describe('The alert rule ID to delete'),
1889
+ }, async (args) => {
1890
+ try {
1891
+ const mutation = `
1892
+ mutation DeleteAlertRule($id: String) {
1893
+ deleteAlertRule(id: $id) {${ALERT_RULE_SELECTION}
1894
+ }
1895
+ }`;
1896
+ const response = await graphqlClient.query(mutation, { id: args.id });
1897
+ if (response.errors && response.errors.length > 0) {
1898
+ const result = {
1899
+ success: false,
1900
+ error: response.errors.map((e) => e.message).join('; '),
1901
+ };
1902
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1903
+ }
1904
+ const result = {
1905
+ success: true,
1906
+ data: response.data,
1907
+ };
1908
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1909
+ }
1910
+ catch (error) {
1911
+ const result = {
1912
+ success: false,
1913
+ error: error instanceof Error ? error.message : 'Unknown error',
1914
+ };
1915
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1916
+ }
1917
+ });
1918
+ // ============================================================================
1156
1919
  // Tool: List Data Tables
1157
1920
  // ============================================================================
1158
1921
  server.tool('list_data_tables', 'List data tables in the account. Returns table summaries with pagination. Use to discover available tables before querying rows.', {
@@ -2142,6 +2905,10 @@ server.resource('schema://reveldigital/graphql', 'Revel Digital GraphQL Schema',
2142
2905
 
2143
2906
  ### Alerts & Monitoring
2144
2907
  - alert - Query system alerts for device issues and rule violations
2908
+ - alertRule - Query alert rules (the conditions that trigger alerts)
2909
+
2910
+ ### Connector Data
2911
+ - connectorData - Fetch a normalized dataset from a registered connector data source (datasourceId + datasetId + params)
2145
2912
 
2146
2913
  ### Analytics (require date range)
2147
2914
  Aggregated metrics optimized for AI context windows:
@@ -2171,6 +2938,11 @@ Raw logs return too much data for AI context windows.
2171
2938
  - send_device_command - Send commands to a specific device
2172
2939
  - send_bulk_device_commands - Send commands to multiple devices at once
2173
2940
 
2941
+ ### Alert Rule Management
2942
+ - create_alert_rule - Create a new alert rule (conditions that trigger alerts)
2943
+ - update_alert_rule - Update an existing alert rule (partial update)
2944
+ - delete_alert_rule - Delete an alert rule
2945
+
2174
2946
  ### Group Management
2175
2947
  - create_device_group - Create a new device group (optional parentId for nesting)
2176
2948
  - create_media_group - Create a new media group (optional parentId for nesting)
@@ -2191,6 +2963,17 @@ Raw logs return too much data for AI context windows.
2191
2963
  - remove_playlist_source - Remove a source from a playlist
2192
2964
  - reorder_playlist_sources - Reorder sources within a playlist
2193
2965
 
2966
+ ### Template Management
2967
+ - update_template - Update a template's properties (name, group, tags, dimensions, colors, orientation, script)
2968
+ - add_template_module - Add a new module (zone) to a template
2969
+ - update_template_module - Update a module within a template
2970
+ - remove_template_module - Remove a module from a template
2971
+ - reorder_template_modules - Reorder modules within a template (sequence/z-order)
2972
+ - batch_update_template_modules - Update multiple modules in a template at once
2973
+ - set_module_options - Set all options for a module (replaces existing)
2974
+ - upsert_module_option - Add or update a single module option (matched by name)
2975
+ - remove_module_option - Remove a module option by name
2976
+
2194
2977
  ### Data Table Management
2195
2978
  - create_data_table - Create a new data table with column schema
2196
2979
  - update_data_table - Update table definition (partial update)