@reveldigital/mcp-graphql-proxy 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -533,6 +533,82 @@ mutation SendBulkDeviceCommands($input: BulkCommandInput) {
533
533
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
534
534
  }
535
535
  });
536
+ const groupMutations = [
537
+ {
538
+ toolName: 'create_device_group',
539
+ mutationField: 'createDeviceGroup',
540
+ mutationName: 'CreateDeviceGroup',
541
+ description: 'Create a new device group (organizational folder for player devices). Example: organize newly provisioned players into a "Lobby Displays" group.',
542
+ },
543
+ {
544
+ toolName: 'create_media_group',
545
+ mutationField: 'createMediaGroup',
546
+ mutationName: 'CreateMediaGroup',
547
+ description: 'Create a new media group (organizational folder for uploaded content files).',
548
+ },
549
+ {
550
+ toolName: 'create_playlist_group',
551
+ mutationField: 'createPlaylistGroup',
552
+ mutationName: 'CreatePlaylistGroup',
553
+ description: 'Create a new playlist group (organizational folder for playlists).',
554
+ },
555
+ {
556
+ toolName: 'create_schedule_group',
557
+ mutationField: 'createScheduleGroup',
558
+ mutationName: 'CreateScheduleGroup',
559
+ description: 'Create a new schedule group (organizational folder for schedules).',
560
+ },
561
+ {
562
+ toolName: 'create_template_group',
563
+ mutationField: 'createTemplateGroup',
564
+ mutationName: 'CreateTemplateGroup',
565
+ description: 'Create a new template group (organizational folder for screen layouts/templates).',
566
+ },
567
+ ];
568
+ for (const { toolName, mutationField, mutationName, description } of groupMutations) {
569
+ server.tool(toolName, description, {
570
+ name: z.string().describe('The name of the new group'),
571
+ parentId: z.string().optional().describe('Optional parent group ID to nest this group under. If omitted, the group is created at the root level.'),
572
+ }, async (args) => {
573
+ try {
574
+ const mutation = `
575
+ mutation ${mutationName}($input: CreateGroupInput) {
576
+ ${mutationField}(input: $input) {
577
+ success
578
+ group {
579
+ id
580
+ name
581
+ parentId
582
+ }
583
+ error
584
+ }
585
+ }`;
586
+ const input = { name: args.name };
587
+ if (args.parentId !== undefined)
588
+ input.parentId = args.parentId;
589
+ const response = await graphqlClient.query(mutation, { input });
590
+ if (response.errors && response.errors.length > 0) {
591
+ const result = {
592
+ success: false,
593
+ error: response.errors.map((e) => e.message).join('; '),
594
+ };
595
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
596
+ }
597
+ const result = {
598
+ success: true,
599
+ data: response.data,
600
+ };
601
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
602
+ }
603
+ catch (error) {
604
+ const result = {
605
+ success: false,
606
+ error: error instanceof Error ? error.message : 'Unknown error',
607
+ };
608
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
609
+ }
610
+ });
611
+ }
536
612
  // ============================================================================
537
613
  // Tool: Create Media From URL
538
614
  // ============================================================================
@@ -1077,6 +1153,562 @@ mutation ReorderPlaylistSources($input: ReorderPlaylistSourcesInput) {
1077
1153
  }
1078
1154
  });
1079
1155
  // ============================================================================
1156
+ // Tool: Update Template
1157
+ // ============================================================================
1158
+ 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.", {
1159
+ templateId: z.string().describe('The template ID to update'),
1160
+ name: z.string().optional().describe('Template name'),
1161
+ groupId: z.string().optional().describe('Group ID to assign the template to'),
1162
+ tags: z.string().optional().describe('Tags (newline-delimited values stored in the Description field)'),
1163
+ script: z.string().optional().describe('Template script for custom logic'),
1164
+ backColor: z.string().optional().describe("Background color (e.g., '#000000')"),
1165
+ width: z.number().optional().describe('Template width in pixels'),
1166
+ height: z.number().optional().describe('Template height in pixels'),
1167
+ orientation: z.number().optional().describe('Display orientation (0=Landscape, 1=Portrait, 2=Landscape Reversed, 3=Portrait Reversed)'),
1168
+ }, async (args) => {
1169
+ try {
1170
+ const mutation = `
1171
+ mutation UpdateTemplate($input: UpdateTemplateInput) {
1172
+ updateTemplate(input: $input) {
1173
+ success
1174
+ template {
1175
+ id
1176
+ name
1177
+ width
1178
+ height
1179
+ orientation
1180
+ modules {
1181
+ id
1182
+ name
1183
+ type
1184
+ left
1185
+ top
1186
+ width
1187
+ height
1188
+ playlistId
1189
+ sequence
1190
+ }
1191
+ }
1192
+ error
1193
+ }
1194
+ }`;
1195
+ const input = { templateId: args.templateId };
1196
+ if (args.name !== undefined)
1197
+ input.name = args.name;
1198
+ if (args.groupId !== undefined)
1199
+ input.groupId = args.groupId;
1200
+ if (args.tags !== undefined)
1201
+ input.tags = args.tags;
1202
+ if (args.script !== undefined)
1203
+ input.script = args.script;
1204
+ if (args.backColor !== undefined)
1205
+ input.backColor = args.backColor;
1206
+ if (args.width !== undefined)
1207
+ input.width = args.width;
1208
+ if (args.height !== undefined)
1209
+ input.height = args.height;
1210
+ if (args.orientation !== undefined)
1211
+ input.orientation = args.orientation;
1212
+ const response = await graphqlClient.query(mutation, { input });
1213
+ if (response.errors && response.errors.length > 0) {
1214
+ const result = {
1215
+ success: false,
1216
+ error: response.errors.map((e) => e.message).join('; '),
1217
+ };
1218
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1219
+ }
1220
+ const result = {
1221
+ success: true,
1222
+ data: response.data,
1223
+ };
1224
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1225
+ }
1226
+ catch (error) {
1227
+ const result = {
1228
+ success: false,
1229
+ error: error instanceof Error ? error.message : 'Unknown error',
1230
+ };
1231
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1232
+ }
1233
+ });
1234
+ // ============================================================================
1235
+ // Tool: Add Template Module
1236
+ // ============================================================================
1237
+ 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.", {
1238
+ templateId: z.string().describe('The template ID to add the module to'),
1239
+ module: z.object({
1240
+ name: z.string().optional().describe('Module/zone name'),
1241
+ type: z.string().optional().describe("Module/zone type (e.g., 'Playlist', 'Clock', 'Weather', 'Ticker', 'Web')"),
1242
+ left: z.number().optional().describe('Left position in pixels'),
1243
+ top: z.number().optional().describe('Top position in pixels'),
1244
+ width: z.number().optional().describe('Width in pixels'),
1245
+ height: z.number().optional().describe('Height in pixels'),
1246
+ playlistId: z.string().optional().describe('Playlist ID assigned to this module'),
1247
+ sequence: z.number().optional().describe('Z-order/sequence for overlapping modules (higher appears in front)'),
1248
+ }).describe('The module to add'),
1249
+ }, async (args) => {
1250
+ try {
1251
+ const mutation = `
1252
+ mutation AddTemplateModule($input: AddTemplateModuleInput) {
1253
+ addTemplateModule(input: $input) {
1254
+ success
1255
+ module {
1256
+ id
1257
+ name
1258
+ type
1259
+ left
1260
+ top
1261
+ width
1262
+ height
1263
+ playlistId
1264
+ sequence
1265
+ options {
1266
+ id
1267
+ name
1268
+ value
1269
+ }
1270
+ }
1271
+ error
1272
+ }
1273
+ }`;
1274
+ const response = await graphqlClient.query(mutation, {
1275
+ input: {
1276
+ templateId: args.templateId,
1277
+ module: args.module,
1278
+ },
1279
+ });
1280
+ if (response.errors && response.errors.length > 0) {
1281
+ const result = {
1282
+ success: false,
1283
+ error: response.errors.map((e) => e.message).join('; '),
1284
+ };
1285
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1286
+ }
1287
+ const result = {
1288
+ success: true,
1289
+ data: response.data,
1290
+ };
1291
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1292
+ }
1293
+ catch (error) {
1294
+ const result = {
1295
+ success: false,
1296
+ error: error instanceof Error ? error.message : 'Unknown error',
1297
+ };
1298
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1299
+ }
1300
+ });
1301
+ // ============================================================================
1302
+ // Tool: Update Template Module
1303
+ // ============================================================================
1304
+ 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.', {
1305
+ templateId: z.string().describe('The template ID containing the module'),
1306
+ moduleId: z.string().describe('The module ID to update'),
1307
+ name: z.string().optional().describe('Module/zone name'),
1308
+ type: z.string().optional().describe("Module/zone type (e.g., 'Playlist', 'Clock', 'Weather', 'Ticker', 'Web')"),
1309
+ left: z.number().optional().describe('Left position in pixels'),
1310
+ top: z.number().optional().describe('Top position in pixels'),
1311
+ width: z.number().optional().describe('Width in pixels'),
1312
+ height: z.number().optional().describe('Height in pixels'),
1313
+ playlistId: z.string().optional().describe('Playlist ID assigned to this module'),
1314
+ sequence: z.number().optional().describe('Z-order/sequence for overlapping modules'),
1315
+ }, async (args) => {
1316
+ try {
1317
+ const mutation = `
1318
+ mutation UpdateTemplateModule($input: UpdateTemplateModuleInput) {
1319
+ updateTemplateModule(input: $input) {
1320
+ success
1321
+ module {
1322
+ id
1323
+ name
1324
+ type
1325
+ left
1326
+ top
1327
+ width
1328
+ height
1329
+ playlistId
1330
+ sequence
1331
+ options {
1332
+ id
1333
+ name
1334
+ value
1335
+ }
1336
+ }
1337
+ error
1338
+ }
1339
+ }`;
1340
+ const input = {
1341
+ templateId: args.templateId,
1342
+ moduleId: args.moduleId,
1343
+ };
1344
+ if (args.name !== undefined)
1345
+ input.name = args.name;
1346
+ if (args.type !== undefined)
1347
+ input.type = args.type;
1348
+ if (args.left !== undefined)
1349
+ input.left = args.left;
1350
+ if (args.top !== undefined)
1351
+ input.top = args.top;
1352
+ if (args.width !== undefined)
1353
+ input.width = args.width;
1354
+ if (args.height !== undefined)
1355
+ input.height = args.height;
1356
+ if (args.playlistId !== undefined)
1357
+ input.playlistId = args.playlistId;
1358
+ if (args.sequence !== undefined)
1359
+ input.sequence = args.sequence;
1360
+ const response = await graphqlClient.query(mutation, { input });
1361
+ if (response.errors && response.errors.length > 0) {
1362
+ const result = {
1363
+ success: false,
1364
+ error: response.errors.map((e) => e.message).join('; '),
1365
+ };
1366
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1367
+ }
1368
+ const result = {
1369
+ success: true,
1370
+ data: response.data,
1371
+ };
1372
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1373
+ }
1374
+ catch (error) {
1375
+ const result = {
1376
+ success: false,
1377
+ error: error instanceof Error ? error.message : 'Unknown error',
1378
+ };
1379
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1380
+ }
1381
+ });
1382
+ // ============================================================================
1383
+ // Tool: Remove Template Module
1384
+ // ============================================================================
1385
+ 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.', {
1386
+ templateId: z.string().describe('The template ID containing the module'),
1387
+ moduleId: z.string().describe('The module ID to remove'),
1388
+ }, async (args) => {
1389
+ try {
1390
+ const mutation = `
1391
+ mutation RemoveTemplateModule($input: RemoveTemplateModuleInput) {
1392
+ removeTemplateModule(input: $input) {
1393
+ success
1394
+ module {
1395
+ id
1396
+ name
1397
+ type
1398
+ }
1399
+ error
1400
+ }
1401
+ }`;
1402
+ const response = await graphqlClient.query(mutation, {
1403
+ input: {
1404
+ templateId: args.templateId,
1405
+ moduleId: args.moduleId,
1406
+ },
1407
+ });
1408
+ if (response.errors && response.errors.length > 0) {
1409
+ const result = {
1410
+ success: false,
1411
+ error: response.errors.map((e) => e.message).join('; '),
1412
+ };
1413
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1414
+ }
1415
+ const result = {
1416
+ success: true,
1417
+ data: response.data,
1418
+ };
1419
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1420
+ }
1421
+ catch (error) {
1422
+ const result = {
1423
+ success: false,
1424
+ error: error instanceof Error ? error.message : 'Unknown error',
1425
+ };
1426
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1427
+ }
1428
+ });
1429
+ // ============================================================================
1430
+ // Tool: Reorder Template Modules
1431
+ // ============================================================================
1432
+ 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.', {
1433
+ templateId: z.string().describe('The template ID'),
1434
+ moduleSequences: z.array(z.object({
1435
+ moduleId: z.string().describe('The module ID'),
1436
+ sequence: z.number().describe('The new sequence/z-order value'),
1437
+ })).describe('List of module IDs with their new sequence numbers'),
1438
+ }, async (args) => {
1439
+ try {
1440
+ const mutation = `
1441
+ mutation ReorderTemplateModules($input: ReorderTemplateModulesInput) {
1442
+ reorderTemplateModules(input: $input) {
1443
+ success
1444
+ template {
1445
+ id
1446
+ name
1447
+ modules {
1448
+ id
1449
+ name
1450
+ type
1451
+ sequence
1452
+ }
1453
+ }
1454
+ error
1455
+ }
1456
+ }`;
1457
+ const response = await graphqlClient.query(mutation, {
1458
+ input: {
1459
+ templateId: args.templateId,
1460
+ moduleSequences: args.moduleSequences,
1461
+ },
1462
+ });
1463
+ if (response.errors && response.errors.length > 0) {
1464
+ const result = {
1465
+ success: false,
1466
+ error: response.errors.map((e) => e.message).join('; '),
1467
+ };
1468
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1469
+ }
1470
+ const result = {
1471
+ success: true,
1472
+ data: response.data,
1473
+ };
1474
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1475
+ }
1476
+ catch (error) {
1477
+ const result = {
1478
+ success: false,
1479
+ error: error instanceof Error ? error.message : 'Unknown error',
1480
+ };
1481
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1482
+ }
1483
+ });
1484
+ // ============================================================================
1485
+ // Tool: Batch Update Template Modules
1486
+ // ============================================================================
1487
+ 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.', {
1488
+ templateId: z.string().describe('The template ID'),
1489
+ modules: z.array(z.object({
1490
+ moduleId: z.string().describe('The module ID to update'),
1491
+ name: z.string().optional().describe('Module/zone name'),
1492
+ type: z.string().optional().describe('Module/zone type'),
1493
+ left: z.number().optional().describe('Left position in pixels'),
1494
+ top: z.number().optional().describe('Top position in pixels'),
1495
+ width: z.number().optional().describe('Width in pixels'),
1496
+ height: z.number().optional().describe('Height in pixels'),
1497
+ playlistId: z.string().optional().describe('Playlist ID assigned to this module'),
1498
+ sequence: z.number().optional().describe('Z-order/sequence'),
1499
+ })).describe('List of module updates to apply (each requires moduleId)'),
1500
+ }, async (args) => {
1501
+ try {
1502
+ const mutation = `
1503
+ mutation BatchUpdateTemplateModules($input: BatchUpdateTemplateModulesInput) {
1504
+ batchUpdateTemplateModules(input: $input) {
1505
+ totalModules
1506
+ successCount
1507
+ failureCount
1508
+ results {
1509
+ success
1510
+ module {
1511
+ id
1512
+ name
1513
+ type
1514
+ left
1515
+ top
1516
+ width
1517
+ height
1518
+ playlistId
1519
+ sequence
1520
+ }
1521
+ error
1522
+ }
1523
+ }
1524
+ }`;
1525
+ const modules = args.modules.map((m) => ({ ...m, templateId: args.templateId }));
1526
+ const response = await graphqlClient.query(mutation, {
1527
+ input: {
1528
+ templateId: args.templateId,
1529
+ modules,
1530
+ },
1531
+ });
1532
+ if (response.errors && response.errors.length > 0) {
1533
+ const result = {
1534
+ success: false,
1535
+ error: response.errors.map((e) => e.message).join('; '),
1536
+ };
1537
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1538
+ }
1539
+ const result = {
1540
+ success: true,
1541
+ data: response.data,
1542
+ };
1543
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1544
+ }
1545
+ catch (error) {
1546
+ const result = {
1547
+ success: false,
1548
+ error: error instanceof Error ? error.message : 'Unknown error',
1549
+ };
1550
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1551
+ }
1552
+ });
1553
+ // ============================================================================
1554
+ // Tool: Set Module Options
1555
+ // ============================================================================
1556
+ 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.', {
1557
+ templateId: z.string().describe('The template ID'),
1558
+ moduleId: z.string().describe('The module ID'),
1559
+ options: z.array(z.object({
1560
+ name: z.string().describe("Option name/key (e.g., 'timezone', 'location', 'url')"),
1561
+ value: z.string().describe('Option value (string representation, parsed by option type)'),
1562
+ })).describe('List of options to set (replaces all existing options)'),
1563
+ }, async (args) => {
1564
+ try {
1565
+ const mutation = `
1566
+ mutation SetModuleOptions($input: SetModuleOptionsInput) {
1567
+ setModuleOptions(input: $input) {
1568
+ success
1569
+ module {
1570
+ id
1571
+ name
1572
+ type
1573
+ options {
1574
+ id
1575
+ name
1576
+ value
1577
+ }
1578
+ }
1579
+ error
1580
+ }
1581
+ }`;
1582
+ const response = await graphqlClient.query(mutation, {
1583
+ input: {
1584
+ templateId: args.templateId,
1585
+ moduleId: args.moduleId,
1586
+ options: args.options,
1587
+ },
1588
+ });
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: Upsert Module Option
1612
+ // ============================================================================
1613
+ 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.', {
1614
+ templateId: z.string().describe('The template ID'),
1615
+ moduleId: z.string().describe('The module ID'),
1616
+ option: z.object({
1617
+ name: z.string().describe("Option name/key (e.g., 'timezone', 'location', 'url')"),
1618
+ value: z.string().describe('Option value (string representation, parsed by option type)'),
1619
+ }).describe('The option to create or update (matched by name)'),
1620
+ }, async (args) => {
1621
+ try {
1622
+ const mutation = `
1623
+ mutation UpsertModuleOption($input: UpsertModuleOptionInput) {
1624
+ upsertModuleOption(input: $input) {
1625
+ success
1626
+ option {
1627
+ id
1628
+ name
1629
+ value
1630
+ }
1631
+ error
1632
+ }
1633
+ }`;
1634
+ const response = await graphqlClient.query(mutation, {
1635
+ input: {
1636
+ templateId: args.templateId,
1637
+ moduleId: args.moduleId,
1638
+ option: args.option,
1639
+ },
1640
+ });
1641
+ if (response.errors && response.errors.length > 0) {
1642
+ const result = {
1643
+ success: false,
1644
+ error: response.errors.map((e) => e.message).join('; '),
1645
+ };
1646
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1647
+ }
1648
+ const result = {
1649
+ success: true,
1650
+ data: response.data,
1651
+ };
1652
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1653
+ }
1654
+ catch (error) {
1655
+ const result = {
1656
+ success: false,
1657
+ error: error instanceof Error ? error.message : 'Unknown error',
1658
+ };
1659
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1660
+ }
1661
+ });
1662
+ // ============================================================================
1663
+ // Tool: Remove Module Option
1664
+ // ============================================================================
1665
+ server.tool('remove_module_option', 'Remove a module/zone option by name. Example: AI removes an outdated configuration setting from a module.', {
1666
+ templateId: z.string().describe('The template ID'),
1667
+ moduleId: z.string().describe('The module ID'),
1668
+ optionName: z.string().describe('The option name to remove'),
1669
+ }, async (args) => {
1670
+ try {
1671
+ const mutation = `
1672
+ mutation RemoveModuleOption($input: RemoveModuleOptionInput) {
1673
+ removeModuleOption(input: $input) {
1674
+ success
1675
+ option {
1676
+ id
1677
+ name
1678
+ value
1679
+ }
1680
+ error
1681
+ }
1682
+ }`;
1683
+ const response = await graphqlClient.query(mutation, {
1684
+ input: {
1685
+ templateId: args.templateId,
1686
+ moduleId: args.moduleId,
1687
+ optionName: args.optionName,
1688
+ },
1689
+ });
1690
+ if (response.errors && response.errors.length > 0) {
1691
+ const result = {
1692
+ success: false,
1693
+ error: response.errors.map((e) => e.message).join('; '),
1694
+ };
1695
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1696
+ }
1697
+ const result = {
1698
+ success: true,
1699
+ data: response.data,
1700
+ };
1701
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1702
+ }
1703
+ catch (error) {
1704
+ const result = {
1705
+ success: false,
1706
+ error: error instanceof Error ? error.message : 'Unknown error',
1707
+ };
1708
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1709
+ }
1710
+ });
1711
+ // ============================================================================
1080
1712
  // Tool: List Data Tables
1081
1713
  // ============================================================================
1082
1714
  server.tool('list_data_tables', 'List data tables in the account. Returns table summaries with pagination. Use to discover available tables before querying rows.', {
@@ -2095,6 +2727,13 @@ Raw logs return too much data for AI context windows.
2095
2727
  - send_device_command - Send commands to a specific device
2096
2728
  - send_bulk_device_commands - Send commands to multiple devices at once
2097
2729
 
2730
+ ### Group Management
2731
+ - create_device_group - Create a new device group (optional parentId for nesting)
2732
+ - create_media_group - Create a new media group (optional parentId for nesting)
2733
+ - create_playlist_group - Create a new playlist group (optional parentId for nesting)
2734
+ - create_schedule_group - Create a new schedule group (optional parentId for nesting)
2735
+ - create_template_group - Create a new template group (optional parentId for nesting)
2736
+
2098
2737
  ### Media Management
2099
2738
  - create_media_from_url - Create media by downloading from a URL
2100
2739
  - update_media - Update media metadata (name, dates, group)
@@ -2108,6 +2747,17 @@ Raw logs return too much data for AI context windows.
2108
2747
  - remove_playlist_source - Remove a source from a playlist
2109
2748
  - reorder_playlist_sources - Reorder sources within a playlist
2110
2749
 
2750
+ ### Template Management
2751
+ - update_template - Update a template's properties (name, group, tags, dimensions, colors, orientation, script)
2752
+ - add_template_module - Add a new module (zone) to a template
2753
+ - update_template_module - Update a module within a template
2754
+ - remove_template_module - Remove a module from a template
2755
+ - reorder_template_modules - Reorder modules within a template (sequence/z-order)
2756
+ - batch_update_template_modules - Update multiple modules in a template at once
2757
+ - set_module_options - Set all options for a module (replaces existing)
2758
+ - upsert_module_option - Add or update a single module option (matched by name)
2759
+ - remove_module_option - Remove a module option by name
2760
+
2111
2761
  ### Data Table Management
2112
2762
  - create_data_table - Create a new data table with column schema
2113
2763
  - update_data_table - Update table definition (partial update)