snow-flow 1.1.45 → 1.1.47

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.
@@ -87,6 +87,17 @@
87
87
  },
88
88
  "includeCoAuthoredBy": true,
89
89
  "enabledMcpjsonServers": [
90
+ "servicenow-deployment",
91
+ "servicenow-flow-composer",
92
+ "servicenow-update-set",
93
+ "servicenow-intelligent",
94
+ "servicenow-graph-memory",
95
+ "servicenow-operations",
96
+ "servicenow-platform-development",
97
+ "servicenow-integration",
98
+ "servicenow-automation",
99
+ "servicenow-security-compliance",
100
+ "servicenow-reporting-analytics",
90
101
  "claude-flow",
91
102
  "ruv-swarm"
92
103
  ]
@@ -651,6 +651,49 @@ class ServiceNowOperationsMCP {
651
651
  },
652
652
  required: ['catalog_item_id', 'flow_id']
653
653
  }
654
+ },
655
+ {
656
+ name: 'snow_cleanup_test_artifacts',
657
+ description: 'Clean up test artifacts while preserving Update Set audit trail',
658
+ inputSchema: {
659
+ type: 'object',
660
+ properties: {
661
+ artifact_types: {
662
+ type: 'array',
663
+ description: 'Types of artifacts to clean up',
664
+ items: {
665
+ type: 'string',
666
+ enum: ['catalog_items', 'flows', 'users', 'requests', 'all']
667
+ },
668
+ default: ['catalog_items', 'flows', 'users']
669
+ },
670
+ test_patterns: {
671
+ type: 'array',
672
+ description: 'Name patterns that identify test artifacts',
673
+ items: { type: 'string' },
674
+ default: ['Test%', 'Mock%', 'Demo%', '%_test_%', '%test', '%mock']
675
+ },
676
+ max_age_hours: {
677
+ type: 'number',
678
+ description: 'Only clean artifacts older than this (hours)',
679
+ default: 1
680
+ },
681
+ dry_run: {
682
+ type: 'boolean',
683
+ description: 'Preview what would be deleted without actually deleting',
684
+ default: false
685
+ },
686
+ preserve_update_set_entries: {
687
+ type: 'boolean',
688
+ description: 'Keep Update Set entries as audit trail',
689
+ default: true
690
+ },
691
+ update_set_filter: {
692
+ type: 'string',
693
+ description: 'Only clean from specific Update Set (optional)'
694
+ }
695
+ }
696
+ }
654
697
  }
655
698
  ],
656
699
  };
@@ -690,6 +733,8 @@ class ServiceNowOperationsMCP {
690
733
  return await this.handleTestFlowWithMock(args);
691
734
  case 'snow_link_catalog_to_flow':
692
735
  return await this.handleLinkCatalogToFlow(args);
736
+ case 'snow_cleanup_test_artifacts':
737
+ return await this.handleCleanupTestArtifacts(args);
693
738
  default:
694
739
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Tool ${name} not found`);
695
740
  }
@@ -1557,6 +1602,34 @@ class ServiceNowOperationsMCP {
1557
1602
  try {
1558
1603
  switch (action) {
1559
1604
  case 'create': {
1605
+ // Get default catalog if none provided
1606
+ let catalogId = params.sc_catalogs || params.catalog_id;
1607
+ let categoryId = params.sc_categories || params.category_id;
1608
+ if (!catalogId) {
1609
+ // Find default Service Catalog
1610
+ const defaultCatalogResult = await this.client.searchRecords('sc_catalog', 'active=true^ORDERBYsys_created_on', 1);
1611
+ if (defaultCatalogResult.success && defaultCatalogResult.data?.result?.length > 0) {
1612
+ catalogId = defaultCatalogResult.data.result[0].sys_id;
1613
+ logger_js_1.logger.info('Using default catalog:', {
1614
+ catalogId,
1615
+ catalogName: defaultCatalogResult.data.result[0].title
1616
+ });
1617
+ }
1618
+ else {
1619
+ logger_js_1.logger.warn('No catalogs found, catalog item may not be visible');
1620
+ }
1621
+ }
1622
+ if (!categoryId) {
1623
+ // Find a default category like "Hardware" or "General"
1624
+ const defaultCategoryResult = await this.client.searchRecords('sc_category', 'active=true^titleLIKEHardware^ORtitleLIKEGeneral^ORtitleLIKEIT^ORDERBYsys_created_on', 1);
1625
+ if (defaultCategoryResult.success && defaultCategoryResult.data?.result?.length > 0) {
1626
+ categoryId = defaultCategoryResult.data.result[0].sys_id;
1627
+ logger_js_1.logger.info('Using default category:', {
1628
+ categoryId,
1629
+ categoryName: defaultCategoryResult.data.result[0].title
1630
+ });
1631
+ }
1632
+ }
1560
1633
  const catalogItem = {
1561
1634
  name: params.name,
1562
1635
  short_description: params.short_description,
@@ -1567,8 +1640,8 @@ class ServiceNowOperationsMCP {
1567
1640
  active: params.active !== false,
1568
1641
  workflow: params.workflow,
1569
1642
  picture: params.picture,
1570
- sc_catalogs: params.sc_catalogs || params.catalog_id,
1571
- sc_categories: params.sc_categories || params.category_id,
1643
+ sc_catalogs: catalogId,
1644
+ sc_categories: categoryId,
1572
1645
  sys_class_name: 'sc_cat_item'
1573
1646
  };
1574
1647
  const result = await this.client.createRecord('sc_cat_item', catalogItem);
@@ -2491,6 +2564,279 @@ class ServiceNowOperationsMCP {
2491
2564
  };
2492
2565
  }
2493
2566
  }
2567
+ async handleCleanupTestArtifacts(args) {
2568
+ const { artifact_types = ['catalog_items', 'flows', 'users'], test_patterns = ['Test%', 'Mock%', 'Demo%', '%_test_%', '%test', '%mock'], max_age_hours = 1, dry_run = false, preserve_update_set_entries = true, update_set_filter } = args;
2569
+ logger_js_1.logger.info('Starting test artifact cleanup', {
2570
+ artifact_types,
2571
+ test_patterns,
2572
+ max_age_hours,
2573
+ dry_run
2574
+ });
2575
+ const cleanupResults = {
2576
+ start_time: new Date().toISOString(),
2577
+ dry_run,
2578
+ artifacts_found: {
2579
+ catalog_items: [],
2580
+ flows: [],
2581
+ users: [],
2582
+ requests: []
2583
+ },
2584
+ artifacts_deleted: {
2585
+ catalog_items: 0,
2586
+ flows: 0,
2587
+ users: 0,
2588
+ requests: 0
2589
+ },
2590
+ update_set_entries_preserved: 0,
2591
+ errors: [],
2592
+ summary: ''
2593
+ };
2594
+ try {
2595
+ // Calculate cutoff time
2596
+ const cutoffTime = new Date();
2597
+ cutoffTime.setHours(cutoffTime.getHours() - max_age_hours);
2598
+ const cutoffISO = cutoffTime.toISOString();
2599
+ // Process each artifact type
2600
+ for (const artifactType of artifact_types) {
2601
+ if (artifactType === 'all' || artifact_types.includes('catalog_items')) {
2602
+ await this.cleanupTestCatalogItems(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
2603
+ }
2604
+ if (artifactType === 'all' || artifact_types.includes('flows')) {
2605
+ await this.cleanupTestFlows(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
2606
+ }
2607
+ if (artifactType === 'all' || artifact_types.includes('users')) {
2608
+ await this.cleanupTestUsers(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
2609
+ }
2610
+ if (artifactType === 'all' || artifact_types.includes('requests')) {
2611
+ await this.cleanupTestRequests(test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter);
2612
+ }
2613
+ }
2614
+ // Generate summary
2615
+ const totalFound = Object.values(cleanupResults.artifacts_found)
2616
+ .reduce((sum, items) => sum + (Array.isArray(items) ? items.length : 0), 0);
2617
+ const totalDeleted = Object.values(cleanupResults.artifacts_deleted)
2618
+ .reduce((sum, count) => sum + count, 0);
2619
+ cleanupResults.summary = dry_run
2620
+ ? `🔍 Dry Run: Found ${totalFound} test artifacts that would be cleaned up`
2621
+ : `🧹 Cleaned up ${totalDeleted} test artifacts successfully`;
2622
+ // Format results
2623
+ let resultText = `🧹 Test Artifact Cleanup Results\n${'='.repeat(50)}\n\n`;
2624
+ if (dry_run) {
2625
+ resultText += `🔍 **DRY RUN MODE** - No actual deletion performed\n\n`;
2626
+ }
2627
+ resultText += `⏰ **Cleanup Configuration:**\n`;
2628
+ resultText += `- Artifact Types: ${artifact_types.join(', ')}\n`;
2629
+ resultText += `- Test Patterns: ${test_patterns.join(', ')}\n`;
2630
+ resultText += `- Max Age: ${max_age_hours} hours\n`;
2631
+ resultText += `- Cutoff Time: ${cutoffISO}\n`;
2632
+ resultText += `- Preserve Update Set Entries: ${preserve_update_set_entries ? '✅' : '❌'}\n\n`;
2633
+ // Report findings by type
2634
+ ['catalog_items', 'flows', 'users', 'requests'].forEach(type => {
2635
+ const found = cleanupResults.artifacts_found[type] || [];
2636
+ const deleted = cleanupResults.artifacts_deleted[type] || 0;
2637
+ if (found.length > 0) {
2638
+ resultText += `📦 **${type.replace('_', ' ').toUpperCase()}:**\n`;
2639
+ resultText += ` ${dry_run ? 'Found' : 'Deleted'}: ${dry_run ? found.length : deleted}\n`;
2640
+ if (dry_run && found.length > 0) {
2641
+ found.slice(0, 10).forEach((item) => {
2642
+ resultText += ` - ${item.name} (${item.sys_id}) - Created: ${item.sys_created_on}\n`;
2643
+ });
2644
+ if (found.length > 10) {
2645
+ resultText += ` ... and ${found.length - 10} more\n`;
2646
+ }
2647
+ }
2648
+ resultText += '\n';
2649
+ }
2650
+ });
2651
+ if (cleanupResults.update_set_entries_preserved > 0) {
2652
+ resultText += `📋 **Update Set Audit Trail Preserved:** ${cleanupResults.update_set_entries_preserved} entries\n\n`;
2653
+ }
2654
+ if (cleanupResults.errors.length > 0) {
2655
+ resultText += `❌ **Errors:**\n`;
2656
+ cleanupResults.errors.forEach(error => {
2657
+ resultText += ` - ${error}\n`;
2658
+ });
2659
+ resultText += '\n';
2660
+ }
2661
+ resultText += `✅ **${cleanupResults.summary}**\n\n`;
2662
+ if (!dry_run && totalDeleted > 0) {
2663
+ resultText += `💡 **Note:** Update Set entries have been preserved as audit trail.\n`;
2664
+ resultText += `This shows that testing was performed and cleanup was completed.\n\n`;
2665
+ }
2666
+ if (dry_run) {
2667
+ resultText += `▶️ **Next Steps:**\n`;
2668
+ resultText += `1. Review the artifacts that would be deleted\n`;
2669
+ resultText += `2. Run again with dry_run: false to perform actual cleanup\n`;
2670
+ resultText += `3. Verify Update Set entries are preserved as intended\n`;
2671
+ }
2672
+ return {
2673
+ content: [{
2674
+ type: 'text',
2675
+ text: resultText
2676
+ }]
2677
+ };
2678
+ }
2679
+ catch (error) {
2680
+ logger_js_1.logger.error('Error during test artifact cleanup:', error);
2681
+ return {
2682
+ content: [{
2683
+ type: 'text',
2684
+ text: `❌ Test artifact cleanup failed\n\nError: ${error.message}\n\n` +
2685
+ `🔧 Troubleshooting:\n` +
2686
+ `1. Check ServiceNow connection and permissions\n` +
2687
+ `2. Verify test patterns are correct\n` +
2688
+ `3. Ensure artifacts exist and are accessible\n\n` +
2689
+ `Debug Info: ${JSON.stringify(cleanupResults, null, 2)}`
2690
+ }]
2691
+ };
2692
+ }
2693
+ }
2694
+ async cleanupTestCatalogItems(patterns, cutoffTime, dryRun, results, updateSetFilter) {
2695
+ try {
2696
+ // Build query for test catalog items
2697
+ const patternQueries = patterns.map(pattern => {
2698
+ if (pattern.includes('%')) {
2699
+ return `nameLIKE${pattern.replace(/%/g, '')}`;
2700
+ }
2701
+ else {
2702
+ return `name=${pattern}`;
2703
+ }
2704
+ });
2705
+ const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}`;
2706
+ const searchResult = await this.client.searchRecords('sc_cat_item', query, 100);
2707
+ if (searchResult.success && searchResult.data?.result) {
2708
+ results.artifacts_found.catalog_items = searchResult.data.result.map((item) => ({
2709
+ sys_id: item.sys_id,
2710
+ name: item.name,
2711
+ sys_created_on: item.sys_created_on
2712
+ }));
2713
+ if (!dryRun) {
2714
+ // Delete each catalog item
2715
+ for (const item of searchResult.data.result) {
2716
+ const deleteResult = await this.client.deleteRecord('sc_cat_item', item.sys_id);
2717
+ if (deleteResult.success) {
2718
+ results.artifacts_deleted.catalog_items++;
2719
+ }
2720
+ else {
2721
+ results.errors.push(`Failed to delete catalog item ${item.name}: ${deleteResult.error}`);
2722
+ }
2723
+ }
2724
+ }
2725
+ }
2726
+ }
2727
+ catch (error) {
2728
+ results.errors.push(`Error cleaning catalog items: ${error.message}`);
2729
+ }
2730
+ }
2731
+ async cleanupTestFlows(patterns, cutoffTime, dryRun, results, updateSetFilter) {
2732
+ try {
2733
+ const patternQueries = patterns.map(pattern => {
2734
+ if (pattern.includes('%')) {
2735
+ return `nameLIKE${pattern.replace(/%/g, '')}`;
2736
+ }
2737
+ else {
2738
+ return `name=${pattern}`;
2739
+ }
2740
+ });
2741
+ const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}`;
2742
+ const searchResult = await this.client.searchRecords('sys_hub_flow', query, 100);
2743
+ if (searchResult.success && searchResult.data?.result) {
2744
+ results.artifacts_found.flows = searchResult.data.result.map((flow) => ({
2745
+ sys_id: flow.sys_id,
2746
+ name: flow.name,
2747
+ sys_created_on: flow.sys_created_on
2748
+ }));
2749
+ if (!dryRun) {
2750
+ for (const flow of searchResult.data.result) {
2751
+ const deleteResult = await this.client.deleteRecord('sys_hub_flow', flow.sys_id);
2752
+ if (deleteResult.success) {
2753
+ results.artifacts_deleted.flows++;
2754
+ }
2755
+ else {
2756
+ results.errors.push(`Failed to delete flow ${flow.name}: ${deleteResult.error}`);
2757
+ }
2758
+ }
2759
+ }
2760
+ }
2761
+ }
2762
+ catch (error) {
2763
+ results.errors.push(`Error cleaning flows: ${error.message}`);
2764
+ }
2765
+ }
2766
+ async cleanupTestUsers(patterns, cutoffTime, dryRun, results, updateSetFilter) {
2767
+ try {
2768
+ const patternQueries = patterns.map(pattern => {
2769
+ if (pattern.includes('%')) {
2770
+ return `user_nameLIKE${pattern.replace(/%/g, '')}^ORfirst_nameLIKE${pattern.replace(/%/g, '')}`;
2771
+ }
2772
+ else {
2773
+ return `user_name=${pattern}^ORfirst_name=${pattern}`;
2774
+ }
2775
+ });
2776
+ const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}^active=false`;
2777
+ const searchResult = await this.client.searchRecords('sys_user', query, 100);
2778
+ if (searchResult.success && searchResult.data?.result) {
2779
+ results.artifacts_found.users = searchResult.data.result.map((user) => ({
2780
+ sys_id: user.sys_id,
2781
+ name: user.user_name || user.name,
2782
+ sys_created_on: user.sys_created_on
2783
+ }));
2784
+ if (!dryRun) {
2785
+ for (const user of searchResult.data.result) {
2786
+ const deleteResult = await this.client.deleteRecord('sys_user', user.sys_id);
2787
+ if (deleteResult.success) {
2788
+ results.artifacts_deleted.users++;
2789
+ }
2790
+ else {
2791
+ results.errors.push(`Failed to delete user ${user.user_name}: ${deleteResult.error}`);
2792
+ }
2793
+ }
2794
+ }
2795
+ }
2796
+ }
2797
+ catch (error) {
2798
+ results.errors.push(`Error cleaning users: ${error.message}`);
2799
+ }
2800
+ }
2801
+ async cleanupTestRequests(patterns, cutoffTime, dryRun, results, updateSetFilter) {
2802
+ try {
2803
+ const patternQueries = patterns.map(pattern => {
2804
+ if (pattern.includes('%')) {
2805
+ return `short_descriptionLIKE${pattern.replace(/%/g, '')}`;
2806
+ }
2807
+ else {
2808
+ return `short_description=${pattern}`;
2809
+ }
2810
+ });
2811
+ const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}`;
2812
+ const searchResult = await this.client.searchRecords('sc_request', query, 100);
2813
+ if (searchResult.success && searchResult.data?.result) {
2814
+ results.artifacts_found.requests = searchResult.data.result.map((req) => ({
2815
+ sys_id: req.sys_id,
2816
+ name: req.number || req.short_description,
2817
+ sys_created_on: req.sys_created_on
2818
+ }));
2819
+ if (!dryRun) {
2820
+ for (const request of searchResult.data.result) {
2821
+ // Cancel request instead of deleting (safer)
2822
+ const updateResult = await this.client.updateRecord('sc_request', request.sys_id, {
2823
+ state: '4', // Cancelled
2824
+ comments: 'Cancelled by test cleanup automation'
2825
+ });
2826
+ if (updateResult.success) {
2827
+ results.artifacts_deleted.requests++;
2828
+ }
2829
+ else {
2830
+ results.errors.push(`Failed to cancel request ${request.number}: ${updateResult.error}`);
2831
+ }
2832
+ }
2833
+ }
2834
+ }
2835
+ }
2836
+ catch (error) {
2837
+ results.errors.push(`Error cleaning requests: ${error.message}`);
2838
+ }
2839
+ }
2494
2840
  getServiceNowUrl() {
2495
2841
  // This would need to get the instance URL from credentials
2496
2842
  return 'https://instance.service-now.com';