snow-flow 1.3.0 → 1.3.2
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/.claude-flow/queen/queen-memory.db +0 -0
- package/.env.example +249 -14
- package/CLAUDE.md +185 -24
- package/README.md +55 -5
- package/dist/api/error-handling.js +898 -4
- package/dist/api/performance-optimizer.js +3 -2
- package/dist/cli.js +590 -200
- package/dist/config/snow-flow-config.js +320 -8
- package/dist/health/system-health.js +288 -21
- package/dist/mcp/base-mcp-server.js +2 -0
- package/dist/mcp/http-transport-wrapper.js +65 -4
- package/dist/mcp/servicenow-automation-mcp-refactored.js +90 -9
- package/dist/mcp/servicenow-deployment-mcp-refactored.js +151 -2
- package/dist/mcp/servicenow-deployment-mcp.js +828 -15
- package/dist/mcp/servicenow-integration-mcp-refactored.js +100 -9
- package/dist/mcp/servicenow-intelligent-mcp.js +198 -7
- package/dist/mcp/servicenow-memory-mcp.js +2 -1
- package/dist/mcp/servicenow-operations-mcp-refactored.js +3 -2
- package/dist/mcp/servicenow-xml-flow-mcp.js +671 -0
- package/dist/mcp/shared/base-mcp-server.js +1356 -8
- package/dist/mcp/shared/mcp-resource-manager.js +304 -0
- package/dist/queen/parallel-agent-engine.js +26 -8
- package/dist/queen/servicenow-queen.js +47 -44
- package/dist/sparc/sparc-help.js +37 -26
- package/dist/sparc/team-sparc.js +60 -16
- package/dist/utils/action-type-cache.js +2 -1
- package/dist/utils/mcp-config-manager.js +7 -3
- package/dist/utils/mcp-server-manager.js +7 -3
- package/dist/utils/servicenow-client.js +134 -107
- package/dist/utils/servicenow-id-generator.js +171 -0
- package/dist/utils/snow-oauth.js +13 -8
- package/dist/utils/widget-template-generator.js +1690 -0
- package/dist/utils/xml-first-flow-generator.js +473 -0
- package/dist/version.js +7 -1
- package/flow-update-sets/flow_build_automated_approval_flow_with_notifications_flow.xml +203 -0
- package/flow-update-sets/flow_create_approval_flow_for_equipment_requests_flow.xml +206 -0
- package/flow-update-sets/flow_create_equipment_approval_flow_with_manager_approv_flow.xml +254 -0
- package/flow-update-sets/iphone_15_pro_approval_flow.xml +203 -0
- package/flow-update-sets/test_iphone_approval_flow_flow.xml +303 -0
- package/package.json +2 -1
- package/test-config.js +55 -0
- package/test-real-monitoring.js +184 -0
|
@@ -3,6 +3,39 @@
|
|
|
3
3
|
* Snow-Flow System Health Monitoring
|
|
4
4
|
* Comprehensive health checks for all components
|
|
5
5
|
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
6
39
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
40
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
41
|
};
|
|
@@ -12,12 +45,14 @@ const events_1 = require("events");
|
|
|
12
45
|
const servicenow_client_1 = require("../utils/servicenow-client");
|
|
13
46
|
const logger_1 = require("../utils/logger");
|
|
14
47
|
const os_1 = __importDefault(require("os"));
|
|
48
|
+
const child_process_1 = require("child_process");
|
|
15
49
|
class SystemHealth extends events_1.EventEmitter {
|
|
16
50
|
constructor(options) {
|
|
17
51
|
super();
|
|
18
52
|
this.checkHistory = [];
|
|
19
53
|
this.totalChecks = 0;
|
|
20
54
|
this.failedChecks = 0;
|
|
55
|
+
this.lastCPUInfo = null;
|
|
21
56
|
this.memory = options.memory;
|
|
22
57
|
this.mcpManager = options.mcpManager;
|
|
23
58
|
this.config = options.config;
|
|
@@ -32,7 +67,8 @@ class SystemHealth extends events_1.EventEmitter {
|
|
|
32
67
|
// Initialize ServiceNow client for health checks
|
|
33
68
|
try {
|
|
34
69
|
this.serviceNowClient = new servicenow_client_1.ServiceNowClient();
|
|
35
|
-
|
|
70
|
+
// Note: ServiceNowClient may not have an initialize method
|
|
71
|
+
// await this.serviceNowClient.initialize();
|
|
36
72
|
}
|
|
37
73
|
catch (error) {
|
|
38
74
|
this.logger.warn('ServiceNow client initialization failed:', error);
|
|
@@ -72,6 +108,10 @@ class SystemHealth extends events_1.EventEmitter {
|
|
|
72
108
|
this.checkInterval = undefined;
|
|
73
109
|
this.logger.info('Health monitoring stopped');
|
|
74
110
|
}
|
|
111
|
+
if (this.cpuCheckInterval) {
|
|
112
|
+
clearInterval(this.cpuCheckInterval);
|
|
113
|
+
this.cpuCheckInterval = undefined;
|
|
114
|
+
}
|
|
75
115
|
}
|
|
76
116
|
/**
|
|
77
117
|
* Perform comprehensive health check
|
|
@@ -214,10 +254,10 @@ class SystemHealth extends events_1.EventEmitter {
|
|
|
214
254
|
async checkMCP() {
|
|
215
255
|
const startTime = Date.now();
|
|
216
256
|
try {
|
|
217
|
-
const serverStatuses =
|
|
218
|
-
const totalServers =
|
|
219
|
-
const healthyServers =
|
|
220
|
-
const unhealthyServers =
|
|
257
|
+
const serverStatuses = this.mcpManager.getServerStatus();
|
|
258
|
+
const totalServers = serverStatuses.length;
|
|
259
|
+
const healthyServers = serverStatuses.filter(s => s.status === 'running').length;
|
|
260
|
+
const unhealthyServers = serverStatuses.filter(s => s.status === 'error').length;
|
|
221
261
|
const responseTime = Date.now() - startTime;
|
|
222
262
|
let status = 'healthy';
|
|
223
263
|
let message = `All ${totalServers} MCP servers running`;
|
|
@@ -286,8 +326,7 @@ class SystemHealth extends events_1.EventEmitter {
|
|
|
286
326
|
status,
|
|
287
327
|
message,
|
|
288
328
|
details: {
|
|
289
|
-
|
|
290
|
-
version: testResult.version,
|
|
329
|
+
connected: true,
|
|
291
330
|
responseTime
|
|
292
331
|
},
|
|
293
332
|
timestamp: new Date(),
|
|
@@ -355,6 +394,7 @@ class SystemHealth extends events_1.EventEmitter {
|
|
|
355
394
|
const startTime = Date.now();
|
|
356
395
|
try {
|
|
357
396
|
const resources = await this.getSystemResources();
|
|
397
|
+
const enhancedMetrics = await this.getEnhancedSystemMetrics();
|
|
358
398
|
const responseTime = Date.now() - startTime;
|
|
359
399
|
let status = 'healthy';
|
|
360
400
|
let message = 'System resources within normal limits';
|
|
@@ -375,6 +415,21 @@ class SystemHealth extends events_1.EventEmitter {
|
|
|
375
415
|
status = 'unhealthy';
|
|
376
416
|
issues.push(`Critical disk usage: ${resources.diskUsage.toFixed(1)}%`);
|
|
377
417
|
}
|
|
418
|
+
else if (resources.diskUsage > 80) {
|
|
419
|
+
status = status === 'healthy' ? 'degraded' : status;
|
|
420
|
+
issues.push(`High disk usage: ${resources.diskUsage.toFixed(1)}%`);
|
|
421
|
+
}
|
|
422
|
+
// Check process memory
|
|
423
|
+
const heapUsagePercent = (enhancedMetrics.process.memory.heapUsed / enhancedMetrics.process.memory.heapTotal) * 100;
|
|
424
|
+
if (heapUsagePercent > 90) {
|
|
425
|
+
status = status === 'degraded' ? 'unhealthy' : 'degraded';
|
|
426
|
+
issues.push(`High heap usage: ${heapUsagePercent.toFixed(1)}%`);
|
|
427
|
+
}
|
|
428
|
+
// Check network connectivity
|
|
429
|
+
if (!enhancedMetrics.network.connected) {
|
|
430
|
+
status = 'degraded';
|
|
431
|
+
issues.push('Network connectivity issues detected');
|
|
432
|
+
}
|
|
378
433
|
if (issues.length > 0) {
|
|
379
434
|
message = issues.join(', ');
|
|
380
435
|
}
|
|
@@ -382,7 +437,10 @@ class SystemHealth extends events_1.EventEmitter {
|
|
|
382
437
|
component: 'system',
|
|
383
438
|
status,
|
|
384
439
|
message,
|
|
385
|
-
details:
|
|
440
|
+
details: {
|
|
441
|
+
...resources,
|
|
442
|
+
enhanced: enhancedMetrics
|
|
443
|
+
},
|
|
386
444
|
timestamp: new Date(),
|
|
387
445
|
responseTime
|
|
388
446
|
};
|
|
@@ -465,19 +523,10 @@ class SystemHealth extends events_1.EventEmitter {
|
|
|
465
523
|
const totalMemory = os_1.default.totalmem();
|
|
466
524
|
const freeMemory = os_1.default.freemem();
|
|
467
525
|
const usedMemory = totalMemory - freeMemory;
|
|
468
|
-
// Calculate CPU usage
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
for (const type in cpu.times) {
|
|
473
|
-
totalTick += cpu.times[type];
|
|
474
|
-
}
|
|
475
|
-
totalIdle += cpu.times.idle;
|
|
476
|
-
});
|
|
477
|
-
const cpuUsage = 100 - ~~(100 * totalIdle / totalTick);
|
|
478
|
-
// Get disk usage (simplified - you may want to use a library like 'diskusage')
|
|
479
|
-
const diskUsage = 50; // Placeholder - implement actual disk usage check
|
|
480
|
-
const diskTotal = 100; // Placeholder
|
|
526
|
+
// Calculate CPU usage with averaging
|
|
527
|
+
const cpuUsage = await this.calculateCPUUsage();
|
|
528
|
+
// Get real disk usage
|
|
529
|
+
const { diskUsage, diskTotal } = await this.getDiskUsage();
|
|
481
530
|
return {
|
|
482
531
|
cpuUsage,
|
|
483
532
|
memoryUsage: usedMemory / 1024 / 1024, // MB
|
|
@@ -575,5 +624,223 @@ class SystemHealth extends events_1.EventEmitter {
|
|
|
575
624
|
return 0;
|
|
576
625
|
return responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length;
|
|
577
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* Calculate real CPU usage with averaging
|
|
629
|
+
*/
|
|
630
|
+
async calculateCPUUsage() {
|
|
631
|
+
const cpus = os_1.default.cpus();
|
|
632
|
+
const currentCPUInfo = cpus.map(cpu => {
|
|
633
|
+
const total = Object.values(cpu.times).reduce((acc, time) => acc + time, 0);
|
|
634
|
+
return {
|
|
635
|
+
idle: cpu.times.idle,
|
|
636
|
+
total: total
|
|
637
|
+
};
|
|
638
|
+
});
|
|
639
|
+
if (!this.lastCPUInfo) {
|
|
640
|
+
this.lastCPUInfo = currentCPUInfo;
|
|
641
|
+
// Wait a bit and calculate again for initial reading
|
|
642
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
643
|
+
return this.calculateCPUUsage();
|
|
644
|
+
}
|
|
645
|
+
let totalIdleDiff = 0;
|
|
646
|
+
let totalDiff = 0;
|
|
647
|
+
for (let i = 0; i < cpus.length; i++) {
|
|
648
|
+
const lastCPU = this.lastCPUInfo[i];
|
|
649
|
+
const currentCPU = currentCPUInfo[i];
|
|
650
|
+
const idleDiff = currentCPU.idle - lastCPU.idle;
|
|
651
|
+
const totalCPUDiff = currentCPU.total - lastCPU.total;
|
|
652
|
+
totalIdleDiff += idleDiff;
|
|
653
|
+
totalDiff += totalCPUDiff;
|
|
654
|
+
}
|
|
655
|
+
this.lastCPUInfo = currentCPUInfo;
|
|
656
|
+
if (totalDiff === 0)
|
|
657
|
+
return 0;
|
|
658
|
+
const idlePercentage = (totalIdleDiff / totalDiff) * 100;
|
|
659
|
+
const usagePercentage = 100 - idlePercentage;
|
|
660
|
+
return Math.max(0, Math.min(100, usagePercentage));
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Get real disk usage statistics
|
|
664
|
+
*/
|
|
665
|
+
async getDiskUsage() {
|
|
666
|
+
try {
|
|
667
|
+
// Platform-specific disk usage calculation
|
|
668
|
+
if (process.platform === 'darwin' || process.platform === 'linux') {
|
|
669
|
+
return this.getUnixDiskUsage();
|
|
670
|
+
}
|
|
671
|
+
else if (process.platform === 'win32') {
|
|
672
|
+
return this.getWindowsDiskUsage();
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
// Fallback for unsupported platforms
|
|
676
|
+
return { diskUsage: 0, diskTotal: 0 };
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
catch (error) {
|
|
680
|
+
this.logger.warn('Failed to get disk usage:', error);
|
|
681
|
+
return { diskUsage: 0, diskTotal: 0 };
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Get disk usage for Unix-based systems (macOS, Linux)
|
|
686
|
+
*/
|
|
687
|
+
getUnixDiskUsage() {
|
|
688
|
+
try {
|
|
689
|
+
// Get current working directory's mount point
|
|
690
|
+
const cwd = process.cwd();
|
|
691
|
+
// Get disk usage for the filesystem containing the current directory
|
|
692
|
+
const output = (0, child_process_1.execSync)(`df -k "${cwd}"`, { encoding: 'utf8' });
|
|
693
|
+
const lines = output.trim().split('\n');
|
|
694
|
+
if (lines.length < 2) {
|
|
695
|
+
throw new Error('Unexpected df output format');
|
|
696
|
+
}
|
|
697
|
+
// Handle cases where the output might span multiple lines
|
|
698
|
+
let dataLine = lines[1];
|
|
699
|
+
if (lines.length > 2 && !lines[1].includes('/')) {
|
|
700
|
+
// Some systems split long filesystem names across lines
|
|
701
|
+
dataLine = lines[1] + ' ' + lines[2];
|
|
702
|
+
}
|
|
703
|
+
// Parse the data line
|
|
704
|
+
const parts = dataLine.split(/\s+/);
|
|
705
|
+
// Find the numeric values (skip filesystem name)
|
|
706
|
+
let totalBlocks = 0;
|
|
707
|
+
let usedBlocks = 0;
|
|
708
|
+
let foundNumbers = false;
|
|
709
|
+
for (let i = 0; i < parts.length; i++) {
|
|
710
|
+
const num = parseInt(parts[i], 10);
|
|
711
|
+
if (!isNaN(num) && !foundNumbers) {
|
|
712
|
+
totalBlocks = num;
|
|
713
|
+
if (i + 1 < parts.length) {
|
|
714
|
+
usedBlocks = parseInt(parts[i + 1], 10);
|
|
715
|
+
foundNumbers = true;
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (!foundNumbers || isNaN(totalBlocks) || isNaN(usedBlocks)) {
|
|
721
|
+
throw new Error('Failed to parse disk usage numbers');
|
|
722
|
+
}
|
|
723
|
+
const diskTotal = totalBlocks / 1024 / 1024; // Convert to GB
|
|
724
|
+
const diskUsed = usedBlocks / 1024 / 1024; // Convert to GB
|
|
725
|
+
const diskUsage = (diskUsed / diskTotal) * 100;
|
|
726
|
+
return {
|
|
727
|
+
diskUsage: Math.round(diskUsage * 10) / 10, // Round to 1 decimal
|
|
728
|
+
diskTotal: Math.round(diskTotal * 10) / 10
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
this.logger.error('Unix disk usage check failed:', error);
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get disk usage for Windows systems
|
|
738
|
+
*/
|
|
739
|
+
getWindowsDiskUsage() {
|
|
740
|
+
try {
|
|
741
|
+
// Get disk usage for C: drive using WMIC
|
|
742
|
+
const output = (0, child_process_1.execSync)('wmic logicaldisk where caption="C:" get size,freespace /value', { encoding: 'utf8' });
|
|
743
|
+
const sizeMatch = output.match(/Size=(\d+)/);
|
|
744
|
+
const freeMatch = output.match(/FreeSpace=(\d+)/);
|
|
745
|
+
if (!sizeMatch || !freeMatch) {
|
|
746
|
+
throw new Error('Failed to parse Windows disk usage');
|
|
747
|
+
}
|
|
748
|
+
const totalBytes = parseInt(sizeMatch[1], 10);
|
|
749
|
+
const freeBytes = parseInt(freeMatch[1], 10);
|
|
750
|
+
const usedBytes = totalBytes - freeBytes;
|
|
751
|
+
const diskTotal = totalBytes / 1024 / 1024 / 1024; // Convert to GB
|
|
752
|
+
const diskUsed = usedBytes / 1024 / 1024 / 1024; // Convert to GB
|
|
753
|
+
const diskUsage = (diskUsed / diskTotal) * 100;
|
|
754
|
+
return {
|
|
755
|
+
diskUsage: Math.round(diskUsage * 10) / 10,
|
|
756
|
+
diskTotal: Math.round(diskTotal * 10) / 10
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
this.logger.error('Windows disk usage check failed:', error);
|
|
761
|
+
throw error;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Get process-specific memory usage
|
|
766
|
+
*/
|
|
767
|
+
getProcessMemoryUsage() {
|
|
768
|
+
const usage = process.memoryUsage();
|
|
769
|
+
return {
|
|
770
|
+
heapUsed: usage.heapUsed / 1024 / 1024, // MB
|
|
771
|
+
heapTotal: usage.heapTotal / 1024 / 1024, // MB
|
|
772
|
+
rss: usage.rss / 1024 / 1024, // MB
|
|
773
|
+
external: usage.external / 1024 / 1024, // MB
|
|
774
|
+
arrayBuffers: usage.arrayBuffers / 1024 / 1024 // MB
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Check network connectivity
|
|
779
|
+
*/
|
|
780
|
+
async checkNetworkConnectivity() {
|
|
781
|
+
try {
|
|
782
|
+
// Simple DNS lookup to check network connectivity
|
|
783
|
+
const dns = await Promise.resolve().then(() => __importStar(require('dns'))).then(m => m.promises);
|
|
784
|
+
await dns.lookup('google.com');
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
catch (error) {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Get additional SQLite database statistics
|
|
793
|
+
*/
|
|
794
|
+
async getExtendedDatabaseStats() {
|
|
795
|
+
try {
|
|
796
|
+
// Use the existing memory system stats
|
|
797
|
+
const dbStats = await this.memory.getDatabaseStats();
|
|
798
|
+
// Get additional table information if possible
|
|
799
|
+
try {
|
|
800
|
+
const tableInfo = await this.memory.query(`
|
|
801
|
+
SELECT
|
|
802
|
+
COUNT(DISTINCT name) as table_count,
|
|
803
|
+
SUM(CASE WHEN type = 'index' THEN 1 ELSE 0 END) as index_count
|
|
804
|
+
FROM sqlite_master
|
|
805
|
+
WHERE type IN ('table', 'index')
|
|
806
|
+
`);
|
|
807
|
+
return {
|
|
808
|
+
...dbStats,
|
|
809
|
+
tables: tableInfo[0]?.table_count || 0,
|
|
810
|
+
indexes: tableInfo[0]?.index_count || 0
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
// If query fails, just return basic stats
|
|
815
|
+
return dbStats;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
catch (error) {
|
|
819
|
+
this.logger.warn('Failed to get extended database stats:', error);
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Enhanced system check with more detailed metrics
|
|
825
|
+
*/
|
|
826
|
+
async getEnhancedSystemMetrics() {
|
|
827
|
+
const processMemory = this.getProcessMemoryUsage();
|
|
828
|
+
const networkConnected = await this.checkNetworkConnectivity();
|
|
829
|
+
const dbStats = await this.getExtendedDatabaseStats();
|
|
830
|
+
return {
|
|
831
|
+
process: {
|
|
832
|
+
memory: processMemory,
|
|
833
|
+
uptime: process.uptime(),
|
|
834
|
+
pid: process.pid,
|
|
835
|
+
version: process.version,
|
|
836
|
+
platform: process.platform,
|
|
837
|
+
arch: process.arch
|
|
838
|
+
},
|
|
839
|
+
network: {
|
|
840
|
+
connected: networkConnected
|
|
841
|
+
},
|
|
842
|
+
database: dbStats
|
|
843
|
+
};
|
|
844
|
+
}
|
|
578
845
|
}
|
|
579
846
|
exports.SystemHealth = SystemHealth;
|
|
@@ -11,6 +11,7 @@ exports.HttpTransportWrapper = void 0;
|
|
|
11
11
|
const express_1 = __importDefault(require("express"));
|
|
12
12
|
const logger_js_1 = require("../utils/logger.js");
|
|
13
13
|
const service_discovery_client_js_1 = require("./service-discovery-client.js");
|
|
14
|
+
const mcp_resource_manager_js_1 = require("./shared/mcp-resource-manager.js");
|
|
14
15
|
class HttpTransportWrapper {
|
|
15
16
|
constructor(mcpServer, config) {
|
|
16
17
|
this.isReady = false;
|
|
@@ -19,6 +20,7 @@ class HttpTransportWrapper {
|
|
|
19
20
|
this.logger = new logger_js_1.Logger(`HttpTransport:${config.name}`);
|
|
20
21
|
this.app = (0, express_1.default)();
|
|
21
22
|
this.serviceDiscovery = new service_discovery_client_js_1.ServiceDiscoveryClient();
|
|
23
|
+
this.resourceManager = new mcp_resource_manager_js_1.MCPResourceManager(config.name);
|
|
22
24
|
this.setupMiddleware();
|
|
23
25
|
this.setupRoutes();
|
|
24
26
|
}
|
|
@@ -117,6 +119,7 @@ class HttpTransportWrapper {
|
|
|
117
119
|
});
|
|
118
120
|
// Service info endpoint
|
|
119
121
|
this.app.get('/info', (req, res) => {
|
|
122
|
+
const resourceStats = this.resourceManager.getResourceStats();
|
|
120
123
|
res.json({
|
|
121
124
|
name: this.config.name,
|
|
122
125
|
version: this.config.version,
|
|
@@ -130,9 +133,41 @@ class HttpTransportWrapper {
|
|
|
130
133
|
'/mcp/list-resources',
|
|
131
134
|
'/mcp/read-resource'
|
|
132
135
|
]
|
|
136
|
+
},
|
|
137
|
+
resources: {
|
|
138
|
+
total: resourceStats.total,
|
|
139
|
+
cached: resourceStats.cached,
|
|
140
|
+
categories: resourceStats.categories
|
|
133
141
|
}
|
|
134
142
|
});
|
|
135
143
|
});
|
|
144
|
+
// Resource management endpoint
|
|
145
|
+
this.app.get('/resources', async (req, res) => {
|
|
146
|
+
try {
|
|
147
|
+
const resources = await this.resourceManager.listResources();
|
|
148
|
+
const stats = this.resourceManager.getResourceStats();
|
|
149
|
+
res.json({
|
|
150
|
+
resources,
|
|
151
|
+
stats
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
this.handleError(res, error);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
// Resource content endpoint
|
|
159
|
+
this.app.get('/resources/*', async (req, res) => {
|
|
160
|
+
try {
|
|
161
|
+
const resourcePath = req.params[0];
|
|
162
|
+
const uri = `servicenow://${resourcePath}`;
|
|
163
|
+
const resource = await this.resourceManager.readResource(uri);
|
|
164
|
+
res.set('Content-Type', resource.mimeType);
|
|
165
|
+
res.send(resource.text);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
this.handleError(res, error);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
136
171
|
// 404 handler
|
|
137
172
|
this.app.use('*', (req, res) => {
|
|
138
173
|
res.status(404).json({
|
|
@@ -184,12 +219,38 @@ class HttpTransportWrapper {
|
|
|
184
219
|
result = await this.handleToolCall(toolRequest);
|
|
185
220
|
break;
|
|
186
221
|
case 'listResources':
|
|
187
|
-
// Handle list resources - return
|
|
188
|
-
|
|
222
|
+
// Handle list resources - return available resources
|
|
223
|
+
try {
|
|
224
|
+
const resources = await this.resourceManager.listResources();
|
|
225
|
+
result = { resources };
|
|
226
|
+
this.logger.debug(`Listed ${resources.length} resources`);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
this.logger.error('Failed to list resources:', error);
|
|
230
|
+
result = { resources: [] };
|
|
231
|
+
}
|
|
189
232
|
break;
|
|
190
233
|
case 'readResource':
|
|
191
|
-
// Handle read resource -
|
|
192
|
-
|
|
234
|
+
// Handle read resource - read specific resource by URI
|
|
235
|
+
if (!params.uri) {
|
|
236
|
+
throw new Error('Resource URI is required for readResource');
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const resource = await this.resourceManager.readResource(params.uri);
|
|
240
|
+
result = {
|
|
241
|
+
contents: [{
|
|
242
|
+
uri: resource.uri,
|
|
243
|
+
mimeType: resource.mimeType,
|
|
244
|
+
text: resource.text
|
|
245
|
+
}]
|
|
246
|
+
};
|
|
247
|
+
this.logger.debug(`Read resource: ${params.uri}`);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
this.logger.error(`Failed to read resource ${params.uri}:`, error);
|
|
251
|
+
throw new Error(`Failed to read resource: ${error instanceof Error ? error.message : String(error)}`);
|
|
252
|
+
}
|
|
253
|
+
break;
|
|
193
254
|
default:
|
|
194
255
|
throw new Error(`Unsupported MCP method: ${method}`);
|
|
195
256
|
}
|
|
@@ -497,15 +497,96 @@ class ServiceNowAutomationMCP extends base_mcp_server_js_1.BaseMCPServer {
|
|
|
497
497
|
throw new Error(`Scheduled job not found: ${args.jobName}`);
|
|
498
498
|
}
|
|
499
499
|
const job = jobResponse.data.result[0];
|
|
500
|
-
//
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
500
|
+
// Execute the scheduled job and monitor its execution
|
|
501
|
+
const executionStartTime = Date.now();
|
|
502
|
+
let testResult;
|
|
503
|
+
try {
|
|
504
|
+
// Trigger the scheduled job execution
|
|
505
|
+
this.logger.info('Triggering scheduled job execution', {
|
|
506
|
+
jobName: job.name,
|
|
507
|
+
jobId: job.sys_id
|
|
508
|
+
});
|
|
509
|
+
// Method 1: Try to execute via sys_trigger endpoint
|
|
510
|
+
const triggerResponse = await this.client.post('/api/now/table/sys_trigger', {
|
|
511
|
+
script: `gs.eventQueue('sysauto.script.execute', null, '${job.sys_id}', '');`,
|
|
512
|
+
name: `Test execution of ${job.name}`,
|
|
513
|
+
next_action: new Date(Date.now() + 5000).toISOString() // Execute in 5 seconds
|
|
514
|
+
});
|
|
515
|
+
if (triggerResponse.result) {
|
|
516
|
+
this.logger.info('Job execution trigger created', {
|
|
517
|
+
triggerId: triggerResponse.result.sys_id
|
|
518
|
+
});
|
|
519
|
+
// Wait a brief moment for execution to begin
|
|
520
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
521
|
+
// Check for recent execution history
|
|
522
|
+
const executionHistoryResponse = await this.client.get(`/api/now/table/sys_execution_tracker?sysparm_query=source=${job.sys_id}^ORDERBYDESCsys_created_on&sysparm_limit=1`);
|
|
523
|
+
const realExecutionTime = Date.now() - executionStartTime;
|
|
524
|
+
if (executionHistoryResponse.result && executionHistoryResponse.result.length > 0) {
|
|
525
|
+
const execution = executionHistoryResponse.result[0];
|
|
526
|
+
testResult = {
|
|
527
|
+
job_name: job.name,
|
|
528
|
+
job_id: job.sys_id,
|
|
529
|
+
test_status: 'executed',
|
|
530
|
+
execution_time: realExecutionTime,
|
|
531
|
+
trigger_id: triggerResponse.result.sys_id,
|
|
532
|
+
execution_tracker_id: execution.sys_id,
|
|
533
|
+
execution_state: execution.state,
|
|
534
|
+
execution_progress: execution.completion_percentage || 0,
|
|
535
|
+
message: `Job execution initiated successfully. Trigger created: ${triggerResponse.result.sys_id}`,
|
|
536
|
+
details: {
|
|
537
|
+
job_active: job.active,
|
|
538
|
+
job_script: job.script ? 'Present' : 'Missing',
|
|
539
|
+
job_condition: job.condition_script || 'None',
|
|
540
|
+
execution_history_found: true
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
testResult = {
|
|
546
|
+
job_name: job.name,
|
|
547
|
+
job_id: job.sys_id,
|
|
548
|
+
test_status: 'triggered',
|
|
549
|
+
execution_time: realExecutionTime,
|
|
550
|
+
trigger_id: triggerResponse.result.sys_id,
|
|
551
|
+
message: `Job execution trigger created successfully. Check sys_execution_tracker for progress.`,
|
|
552
|
+
details: {
|
|
553
|
+
job_active: job.active,
|
|
554
|
+
job_script: job.script ? 'Present' : 'Missing',
|
|
555
|
+
job_condition: job.condition_script || 'None',
|
|
556
|
+
execution_history_found: false
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
throw new Error('Failed to create execution trigger');
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
catch (executionError) {
|
|
566
|
+
// If direct execution fails, perform validation instead
|
|
567
|
+
this.logger.warn('Job execution failed, performing validation instead', {
|
|
568
|
+
jobName: job.name,
|
|
569
|
+
error: executionError instanceof Error ? executionError.message : String(executionError)
|
|
570
|
+
});
|
|
571
|
+
const realExecutionTime = Date.now() - executionStartTime;
|
|
572
|
+
testResult = {
|
|
573
|
+
job_name: job.name,
|
|
574
|
+
job_id: job.sys_id,
|
|
575
|
+
test_status: 'validated',
|
|
576
|
+
execution_time: realExecutionTime,
|
|
577
|
+
message: `Job validation completed. Execution failed: ${executionError instanceof Error ? executionError.message : String(executionError)}`,
|
|
578
|
+
details: {
|
|
579
|
+
job_active: job.active,
|
|
580
|
+
job_script: job.script ? 'Present' : 'Missing',
|
|
581
|
+
job_condition: job.condition_script || 'None',
|
|
582
|
+
job_run_as: job.run_as || 'system',
|
|
583
|
+
job_schedule: job.run_period || 'Not scheduled',
|
|
584
|
+
validation_performed: true,
|
|
585
|
+
execution_attempted: true,
|
|
586
|
+
execution_error: executionError instanceof Error ? executionError.message : String(executionError)
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
}
|
|
509
590
|
return {
|
|
510
591
|
success: true,
|
|
511
592
|
result: testResult,
|