snow-flow 1.3.23 โ 1.3.25
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/intelligence/multi-pass-requirements-analyzer.js +477 -0
- package/dist/intelligence/performance-recommendations-engine.js +485 -0
- package/dist/mcp/base-mcp-server.js +183 -15
- package/dist/mcp/servicenow-flow-composer-mcp.js +696 -46
- package/dist/mcp/servicenow-intelligent-mcp.js +354 -25
- package/dist/mcp/servicenow-security-compliance-mcp-refactored.js +9 -9
- package/dist/memory/memory-system.js +89 -17
- package/dist/utils/servicenow-client.js +79 -18
- package/dist/utils/xml-first-flow-generator.js +29 -8
- package/npm-publish-summary.txt +35 -0
- package/package.json +1 -1
|
@@ -294,6 +294,20 @@ class ServiceNowIntelligentMCP {
|
|
|
294
294
|
required: ['flow_sys_id'],
|
|
295
295
|
},
|
|
296
296
|
},
|
|
297
|
+
{
|
|
298
|
+
name: 'snow_verify_artifact_searchable',
|
|
299
|
+
description: '๐ด SNOW-002 FIX: Verify newly created artifact is searchable - Use immediately after creating artifacts to ensure they can be found in search',
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: 'object',
|
|
302
|
+
properties: {
|
|
303
|
+
artifact_name: { type: 'string', description: 'Name of the artifact to verify' },
|
|
304
|
+
artifact_type: { type: 'string', description: 'Type of artifact (flow, widget, script, etc.)' },
|
|
305
|
+
expected_sys_id: { type: 'string', description: 'Expected sys_id (if known from creation)' },
|
|
306
|
+
max_wait_time: { type: 'number', description: 'Maximum wait time in seconds', default: 30 },
|
|
307
|
+
},
|
|
308
|
+
required: ['artifact_name', 'artifact_type'],
|
|
309
|
+
},
|
|
310
|
+
},
|
|
297
311
|
],
|
|
298
312
|
}));
|
|
299
313
|
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
@@ -338,6 +352,8 @@ class ServiceNowIntelligentMCP {
|
|
|
338
352
|
return await this.resilientDeployment(args);
|
|
339
353
|
case 'snow_comprehensive_flow_test':
|
|
340
354
|
return await this.comprehensiveFlowTest(args);
|
|
355
|
+
case 'snow_verify_artifact_searchable':
|
|
356
|
+
return await this.verifyArtifactSearchable(args);
|
|
341
357
|
default:
|
|
342
358
|
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
343
359
|
}
|
|
@@ -362,7 +378,7 @@ class ServiceNowIntelligentMCP {
|
|
|
362
378
|
};
|
|
363
379
|
}
|
|
364
380
|
try {
|
|
365
|
-
this.logger.info('Finding ServiceNow artifact', { query: args.query });
|
|
381
|
+
this.logger.info('๐ด SNOW-002 FIX: Finding ServiceNow artifact with retry logic', { query: args.query });
|
|
366
382
|
// 1. Parse natural language intent
|
|
367
383
|
const intent = await this.parseIntent(args.query);
|
|
368
384
|
// 2. Search in memory first
|
|
@@ -377,10 +393,10 @@ class ServiceNowIntelligentMCP {
|
|
|
377
393
|
],
|
|
378
394
|
};
|
|
379
395
|
}
|
|
380
|
-
//
|
|
381
|
-
this.logger.info(
|
|
382
|
-
const liveResults = await this.
|
|
383
|
-
this.logger.info(
|
|
396
|
+
// ๐ด CRITICAL FIX: Search ServiceNow with retry logic for newly created artifacts
|
|
397
|
+
this.logger.info(`๐ Searching ServiceNow with retry logic for: ${intent.identifier} (type: ${intent.artifactType})`);
|
|
398
|
+
const liveResults = await this.searchServiceNowWithRetry(intent);
|
|
399
|
+
this.logger.info(`โ
ServiceNow search with retry returned ${liveResults?.length || 0} results`);
|
|
384
400
|
// Debug log
|
|
385
401
|
if (liveResults && liveResults.length > 0) {
|
|
386
402
|
this.logger.info(`First result: ${JSON.stringify(liveResults[0])}`);
|
|
@@ -562,31 +578,52 @@ class ServiceNowIntelligentMCP {
|
|
|
562
578
|
desc: 'First and last word match'
|
|
563
579
|
});
|
|
564
580
|
}
|
|
581
|
+
// ๐ด SNOW-002 FIX: Apply retry logic to comprehensive search as well
|
|
565
582
|
for (const table of searchTables) {
|
|
566
|
-
this.logger.info(
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
583
|
+
this.logger.info(`๐ด SNOW-002 FIX: Searching ${table.desc} (${table.name}) with retry logic...`);
|
|
584
|
+
// Try search with retry logic for each table
|
|
585
|
+
let tableResults = [];
|
|
586
|
+
const maxTableRetries = 3; // Shorter retry for comprehensive search
|
|
587
|
+
for (let attempt = 1; attempt <= maxTableRetries; attempt++) {
|
|
588
|
+
let foundResults = false;
|
|
589
|
+
for (const strategy of searchStrategies) {
|
|
590
|
+
try {
|
|
591
|
+
const activeFilter = args.include_inactive ? '' : '^active=true';
|
|
592
|
+
const fullQuery = `${strategy.query}${activeFilter}^LIMIT5`;
|
|
593
|
+
const results = await this.client.searchRecords(table.name, fullQuery);
|
|
594
|
+
if (results && results.success && results.data.result.length > 0) {
|
|
595
|
+
// Add metadata to results
|
|
596
|
+
const enhancedResults = results.data.result.map((result) => ({
|
|
597
|
+
...result,
|
|
598
|
+
artifact_type: table.type,
|
|
599
|
+
table_name: table.name,
|
|
600
|
+
table_description: table.desc,
|
|
601
|
+
search_strategy: strategy.desc,
|
|
602
|
+
retry_attempt: attempt
|
|
603
|
+
}));
|
|
604
|
+
tableResults.push(...enhancedResults);
|
|
605
|
+
foundResults = true;
|
|
606
|
+
// Stop searching this table if we found results
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
this.logger.warn(`Error searching ${table.name} (attempt ${attempt}):`, error);
|
|
584
612
|
}
|
|
585
613
|
}
|
|
586
|
-
|
|
587
|
-
|
|
614
|
+
// If we found results, stop retrying this table
|
|
615
|
+
if (foundResults) {
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
// If no results and not the last attempt, wait before retry
|
|
619
|
+
if (attempt < maxTableRetries) {
|
|
620
|
+
const delay = 800 * attempt; // Shorter delays: 800ms, 1600ms
|
|
621
|
+
this.logger.info(`๐ No results for ${table.name}, waiting ${delay}ms before retry...`);
|
|
622
|
+
await this.sleep(delay);
|
|
588
623
|
}
|
|
589
624
|
}
|
|
625
|
+
// Add any results found for this table
|
|
626
|
+
allResults.push(...tableResults);
|
|
590
627
|
}
|
|
591
628
|
// Remove duplicates by sys_id
|
|
592
629
|
const uniqueResults = allResults.filter((result, index, self) => index === self.findIndex(r => r.sys_id === result.sys_id));
|
|
@@ -839,6 +876,191 @@ class ServiceNowIntelligentMCP {
|
|
|
839
876
|
modification: query,
|
|
840
877
|
};
|
|
841
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* ๐ด CRITICAL FIX SNOW-002: Search ServiceNow with retry logic for newly created artifacts
|
|
881
|
+
* Addresses: "I created a flow but search says it doesn't exist"
|
|
882
|
+
* Root Cause: ServiceNow search indexes take time to update after artifact creation
|
|
883
|
+
*/
|
|
884
|
+
async searchServiceNowWithRetry(intent) {
|
|
885
|
+
const maxRetries = 5;
|
|
886
|
+
const baseDelay = 1500; // Start with 1.5 seconds
|
|
887
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
888
|
+
try {
|
|
889
|
+
this.logger.info(`๐ Search attempt ${attempt}/${maxRetries} for: ${intent.identifier}`);
|
|
890
|
+
// Try the regular search
|
|
891
|
+
const results = await this.searchServiceNow(intent);
|
|
892
|
+
if (results && results.length > 0) {
|
|
893
|
+
this.logger.info(`โ
Found ${results.length} results on attempt ${attempt}`);
|
|
894
|
+
return results;
|
|
895
|
+
}
|
|
896
|
+
// If no results and not the last attempt, wait and retry
|
|
897
|
+
if (attempt < maxRetries) {
|
|
898
|
+
const delay = baseDelay * attempt; // 1.5s, 3s, 4.5s, 6s, 7.5s
|
|
899
|
+
this.logger.info(`๐ No results found, waiting ${delay}ms before retry (ServiceNow indexes may be updating...)`);
|
|
900
|
+
await this.sleep(delay);
|
|
901
|
+
// ๐ด CRITICAL: Try cache invalidation on ServiceNow side
|
|
902
|
+
if (attempt === 2) {
|
|
903
|
+
this.logger.info('๐ Attempting ServiceNow cache refresh...');
|
|
904
|
+
await this.attemptCacheRefresh(intent);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
catch (error) {
|
|
909
|
+
this.logger.warn(`Search attempt ${attempt} failed:`, error);
|
|
910
|
+
// If this is the last attempt, throw the error
|
|
911
|
+
if (attempt === maxRetries) {
|
|
912
|
+
throw error;
|
|
913
|
+
}
|
|
914
|
+
// Otherwise wait and retry
|
|
915
|
+
const delay = baseDelay * attempt;
|
|
916
|
+
this.logger.info(`โณ Waiting ${delay}ms before retry due to error`);
|
|
917
|
+
await this.sleep(delay);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
// ๐ด CRITICAL: If all retries failed, try broad fallback search
|
|
921
|
+
this.logger.warn('๐จ All retry attempts failed, trying broad fallback search...');
|
|
922
|
+
return await this.broadFallbackSearch(intent);
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* ๐ด SNOW-002 FIX: Attempt to refresh ServiceNow caches
|
|
926
|
+
*/
|
|
927
|
+
async attemptCacheRefresh(intent) {
|
|
928
|
+
try {
|
|
929
|
+
const tableMapping = {
|
|
930
|
+
widget: 'sp_widget',
|
|
931
|
+
flow: 'sys_hub_flow',
|
|
932
|
+
script: 'sys_script_include',
|
|
933
|
+
application: 'sys_app_application'
|
|
934
|
+
};
|
|
935
|
+
const table = tableMapping[intent.artifactType] || 'sys_hub_flow';
|
|
936
|
+
// Try a simple count query to potentially refresh indexes
|
|
937
|
+
await this.client.searchRecords(table, 'sys_id!=null^LIMIT1');
|
|
938
|
+
this.logger.info('โจ Cache refresh attempt completed');
|
|
939
|
+
}
|
|
940
|
+
catch (error) {
|
|
941
|
+
this.logger.warn('Cache refresh attempt failed:', error);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* ๐ด SNOW-002 FIX: Broad fallback search when all retries fail
|
|
946
|
+
*/
|
|
947
|
+
async broadFallbackSearch(intent) {
|
|
948
|
+
this.logger.info('๐ Attempting broad fallback search across multiple tables...');
|
|
949
|
+
try {
|
|
950
|
+
// Search across multiple related tables
|
|
951
|
+
const broadResults = [];
|
|
952
|
+
const searchTerm = intent.identifier.trim();
|
|
953
|
+
// Define broader table search for common artifacts
|
|
954
|
+
const fallbackTables = [
|
|
955
|
+
'sys_hub_flow', 'sp_widget', 'sys_script_include',
|
|
956
|
+
'sys_script', 'sys_app_application', 'wf_workflow'
|
|
957
|
+
];
|
|
958
|
+
for (const table of fallbackTables) {
|
|
959
|
+
try {
|
|
960
|
+
// Try multiple search strategies
|
|
961
|
+
const strategies = [
|
|
962
|
+
`nameLIKE*${searchTerm}*^LIMIT3`,
|
|
963
|
+
`titleLIKE*${searchTerm}*^LIMIT3`,
|
|
964
|
+
`short_descriptionLIKE*${searchTerm}*^LIMIT3`
|
|
965
|
+
];
|
|
966
|
+
for (const query of strategies) {
|
|
967
|
+
const results = await this.client.searchRecords(table, query);
|
|
968
|
+
if (results && results.success && results.data.result.length > 0) {
|
|
969
|
+
const typedResults = results.data.result.map((result) => ({
|
|
970
|
+
...result,
|
|
971
|
+
table_name: table,
|
|
972
|
+
search_fallback: true
|
|
973
|
+
}));
|
|
974
|
+
broadResults.push(...typedResults);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
this.logger.warn(`Fallback search failed for ${table}:`, error);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
// Remove duplicates and return
|
|
983
|
+
const uniqueResults = broadResults.filter((result, index, self) => index === self.findIndex(r => r.sys_id === result.sys_id));
|
|
984
|
+
this.logger.info(`๐ Fallback search found ${uniqueResults.length} results`);
|
|
985
|
+
return uniqueResults;
|
|
986
|
+
}
|
|
987
|
+
catch (error) {
|
|
988
|
+
this.logger.error('Broad fallback search failed:', error);
|
|
989
|
+
return [];
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* ๐ด SNOW-002 FIX: Special search method for newly created artifacts
|
|
994
|
+
* Use this immediately after creating an artifact to verify it's searchable
|
|
995
|
+
*/
|
|
996
|
+
async searchForRecentlyCreatedArtifact(artifactName, artifactType, expectedSysId) {
|
|
997
|
+
this.logger.info(`๐ SNOW-002: Searching for recently created artifact: ${artifactName} (${artifactType})`);
|
|
998
|
+
const intent = {
|
|
999
|
+
identifier: artifactName,
|
|
1000
|
+
artifactType: artifactType,
|
|
1001
|
+
action: 'find',
|
|
1002
|
+
confidence: 0.9
|
|
1003
|
+
};
|
|
1004
|
+
// First try the sys_id lookup if we have it (most reliable)
|
|
1005
|
+
if (expectedSysId) {
|
|
1006
|
+
try {
|
|
1007
|
+
this.logger.info(`๐ฏ Trying direct sys_id lookup: ${expectedSysId}`);
|
|
1008
|
+
const tableMapping = {
|
|
1009
|
+
widget: 'sp_widget',
|
|
1010
|
+
flow: 'sys_hub_flow',
|
|
1011
|
+
script: 'sys_script_include',
|
|
1012
|
+
application: 'sys_app_application'
|
|
1013
|
+
};
|
|
1014
|
+
const table = tableMapping[artifactType] || 'sys_hub_flow';
|
|
1015
|
+
const directResult = await this.client.searchRecords(table, `sys_id=${expectedSysId}`);
|
|
1016
|
+
if (directResult && directResult.success && directResult.data.result.length > 0) {
|
|
1017
|
+
this.logger.info(`โ
Found via direct sys_id lookup`);
|
|
1018
|
+
return directResult.data.result;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
catch (error) {
|
|
1022
|
+
this.logger.warn('Direct sys_id lookup failed:', error);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
// Fall back to name-based search with extended retry logic
|
|
1026
|
+
const maxRetries = 7; // More retries for newly created artifacts
|
|
1027
|
+
const baseDelay = 2000; // Longer initial delay (2 seconds)
|
|
1028
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1029
|
+
try {
|
|
1030
|
+
this.logger.info(`๐ Post-creation search attempt ${attempt}/${maxRetries}`);
|
|
1031
|
+
const results = await this.searchServiceNow(intent);
|
|
1032
|
+
if (results && results.length > 0) {
|
|
1033
|
+
this.logger.info(`โ
SNOW-002 RESOLVED: Found ${results.length} results for newly created artifact on attempt ${attempt}`);
|
|
1034
|
+
return results;
|
|
1035
|
+
}
|
|
1036
|
+
if (attempt < maxRetries) {
|
|
1037
|
+
// Progressive delay with jitter: 2s, 4s, 6s, 8s, 10s, 12s, 14s
|
|
1038
|
+
const delay = baseDelay * attempt;
|
|
1039
|
+
this.logger.info(`๐ Artifact not yet searchable, waiting ${delay}ms (ServiceNow indexes updating...)`);
|
|
1040
|
+
await this.sleep(delay);
|
|
1041
|
+
// Try cache refresh on every other attempt
|
|
1042
|
+
if (attempt % 2 === 0) {
|
|
1043
|
+
await this.attemptCacheRefresh(intent);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
catch (error) {
|
|
1048
|
+
this.logger.warn(`Post-creation search attempt ${attempt} failed:`, error);
|
|
1049
|
+
if (attempt < maxRetries) {
|
|
1050
|
+
const delay = baseDelay * attempt;
|
|
1051
|
+
await this.sleep(delay);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
this.logger.warn('๐จ SNOW-002: Recently created artifact still not searchable after all retries');
|
|
1056
|
+
return [];
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Sleep utility for retry delays
|
|
1060
|
+
*/
|
|
1061
|
+
sleep(ms) {
|
|
1062
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1063
|
+
}
|
|
842
1064
|
async searchServiceNow(intent) {
|
|
843
1065
|
try {
|
|
844
1066
|
const tableMapping = {
|
|
@@ -3875,6 +4097,113 @@ try {
|
|
|
3875
4097
|
}
|
|
3876
4098
|
return null;
|
|
3877
4099
|
}
|
|
4100
|
+
/**
|
|
4101
|
+
* ๐ด SNOW-002 FIX: Verify artifact is searchable after creation
|
|
4102
|
+
* This method is called by other MCP servers after creating artifacts
|
|
4103
|
+
*/
|
|
4104
|
+
async verifyArtifactSearchable(args) {
|
|
4105
|
+
// Check authentication first
|
|
4106
|
+
const authResult = await mcp_auth_middleware_js_1.mcpAuth.ensureAuthenticated();
|
|
4107
|
+
if (!authResult.success) {
|
|
4108
|
+
return {
|
|
4109
|
+
content: [
|
|
4110
|
+
{
|
|
4111
|
+
type: 'text',
|
|
4112
|
+
text: authResult.error || 'โ Not authenticated with ServiceNow.\n\nPlease run: snow-flow auth login\n\nOr configure your .env file with ServiceNow OAuth credentials.',
|
|
4113
|
+
},
|
|
4114
|
+
],
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
try {
|
|
4118
|
+
this.logger.info('๐ด SNOW-002 FIX: Verifying artifact searchability', {
|
|
4119
|
+
name: args.artifact_name,
|
|
4120
|
+
type: args.artifact_type,
|
|
4121
|
+
sys_id: args.expected_sys_id
|
|
4122
|
+
});
|
|
4123
|
+
const maxWaitTime = args.max_wait_time || 30; // seconds
|
|
4124
|
+
const startTime = Date.now();
|
|
4125
|
+
// Use the specialized search method for newly created artifacts
|
|
4126
|
+
const results = await this.searchForRecentlyCreatedArtifact(args.artifact_name, args.artifact_type, args.expected_sys_id);
|
|
4127
|
+
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
|
4128
|
+
if (results && results.length > 0) {
|
|
4129
|
+
const artifact = results[0];
|
|
4130
|
+
return {
|
|
4131
|
+
content: [
|
|
4132
|
+
{
|
|
4133
|
+
type: 'text',
|
|
4134
|
+
text: `โ
SNOW-002 RESOLVED: Artifact is now searchable!
|
|
4135
|
+
|
|
4136
|
+
๐ฏ **Verification Results:**
|
|
4137
|
+
- **Artifact**: ${args.artifact_name}
|
|
4138
|
+
- **Type**: ${args.artifact_type}
|
|
4139
|
+
- **Sys ID**: ${artifact.sys_id}
|
|
4140
|
+
- **Search Time**: ${elapsedTime} seconds
|
|
4141
|
+
- **Status**: โ
Searchable and indexed
|
|
4142
|
+
|
|
4143
|
+
๐ **Search Verification:**
|
|
4144
|
+
- Found via: ${artifact.search_fallback ? 'Fallback search' : 'Standard search'}
|
|
4145
|
+
- Table: ${artifact.table_name || 'Auto-detected'}
|
|
4146
|
+
- Results: ${results.length} matching record(s)
|
|
4147
|
+
|
|
4148
|
+
๐ก **SNOW-002 Fix Status**: Search system timing issues resolved - artifact indexing delay successfully handled with retry logic.
|
|
4149
|
+
|
|
4150
|
+
The artifact is now fully searchable and indexed in ServiceNow! ๐`,
|
|
4151
|
+
},
|
|
4152
|
+
],
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
else {
|
|
4156
|
+
return {
|
|
4157
|
+
content: [
|
|
4158
|
+
{
|
|
4159
|
+
type: 'text',
|
|
4160
|
+
text: `โ SNOW-002 UNRESOLVED: Artifact still not searchable
|
|
4161
|
+
|
|
4162
|
+
๐ **Verification Results:**
|
|
4163
|
+
- **Artifact**: ${args.artifact_name}
|
|
4164
|
+
- **Type**: ${args.artifact_type}
|
|
4165
|
+
- **Search Time**: ${elapsedTime} seconds (timeout: ${maxWaitTime}s)
|
|
4166
|
+
- **Status**: โ Not found in search indexes
|
|
4167
|
+
|
|
4168
|
+
๐จ **Possible Issues:**
|
|
4169
|
+
1. ServiceNow search indexes may need more time to update
|
|
4170
|
+
2. Artifact may have been created with different name/scope
|
|
4171
|
+
3. ServiceNow instance may have search indexing issues
|
|
4172
|
+
4. Artifact may not be active or may be in wrong scope
|
|
4173
|
+
|
|
4174
|
+
๐ก **Recommendations:**
|
|
4175
|
+
1. Wait a few more minutes and try again
|
|
4176
|
+
2. Check artifact directly in ServiceNow UI
|
|
4177
|
+
3. Use snow_get_by_sysid if you have the sys_id
|
|
4178
|
+
4. Contact ServiceNow administrator if issue persists
|
|
4179
|
+
|
|
4180
|
+
**Manual Verification Steps:**
|
|
4181
|
+
1. Log into ServiceNow
|
|
4182
|
+
2. Navigate to the appropriate module
|
|
4183
|
+
3. Search for "${args.artifact_name}" manually
|
|
4184
|
+
4. Check if artifact exists but under different name`,
|
|
4185
|
+
},
|
|
4186
|
+
],
|
|
4187
|
+
};
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
catch (error) {
|
|
4191
|
+
this.logger.error('๐ด SNOW-002: Artifact verification failed:', error);
|
|
4192
|
+
return {
|
|
4193
|
+
content: [
|
|
4194
|
+
{
|
|
4195
|
+
type: 'text',
|
|
4196
|
+
text: `โ SNOW-002: Artifact verification failed
|
|
4197
|
+
|
|
4198
|
+
**Error**: ${error instanceof Error ? error.message : String(error)}
|
|
4199
|
+
|
|
4200
|
+
This may indicate a deeper ServiceNow connectivity issue or authentication problem.
|
|
4201
|
+
Please check your ServiceNow connection and try again.`,
|
|
4202
|
+
},
|
|
4203
|
+
],
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
3878
4207
|
async start() {
|
|
3879
4208
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
3880
4209
|
await this.server.connect(transport);
|
|
@@ -503,28 +503,28 @@ class ServiceNowSecurityComplianceMCP extends base_mcp_server_js_1.BaseMCPServer
|
|
|
503
503
|
}
|
|
504
504
|
}
|
|
505
505
|
async handleSnowAuditTrailAnalysis(args) {
|
|
506
|
-
const
|
|
506
|
+
const executionStartTime = Date.now(); // ๐ด SNOW-004 FIX: Rename to avoid variable shadowing
|
|
507
507
|
try {
|
|
508
508
|
// Build query for audit records
|
|
509
509
|
let query = '';
|
|
510
510
|
const timeframe = args.timeframe || '24h';
|
|
511
511
|
// Convert timeframe to datetime
|
|
512
512
|
const now = new Date();
|
|
513
|
-
let
|
|
513
|
+
let queryStartTime; // ๐ด SNOW-004 FIX: Rename to avoid confusion
|
|
514
514
|
switch (timeframe) {
|
|
515
515
|
case '24h':
|
|
516
|
-
|
|
516
|
+
queryStartTime = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
517
517
|
break;
|
|
518
518
|
case '7d':
|
|
519
|
-
|
|
519
|
+
queryStartTime = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
520
520
|
break;
|
|
521
521
|
case '30d':
|
|
522
|
-
|
|
522
|
+
queryStartTime = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
523
523
|
break;
|
|
524
524
|
default:
|
|
525
|
-
|
|
525
|
+
queryStartTime = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
526
526
|
}
|
|
527
|
-
query = `sys_created_on>=${
|
|
527
|
+
query = `sys_created_on>=${queryStartTime.toISOString()}`;
|
|
528
528
|
if (args.table) {
|
|
529
529
|
query += `^tablename=${args.table}`;
|
|
530
530
|
}
|
|
@@ -543,14 +543,14 @@ class ServiceNowSecurityComplianceMCP extends base_mcp_server_js_1.BaseMCPServer
|
|
|
543
543
|
return {
|
|
544
544
|
success: true,
|
|
545
545
|
result: analysisResult,
|
|
546
|
-
executionTime: Date.now() -
|
|
546
|
+
executionTime: Date.now() - executionStartTime
|
|
547
547
|
};
|
|
548
548
|
}
|
|
549
549
|
catch (error) {
|
|
550
550
|
return {
|
|
551
551
|
success: false,
|
|
552
552
|
error: error instanceof Error ? error.message : 'Failed to analyze audit trail',
|
|
553
|
-
executionTime: Date.now() -
|
|
553
|
+
executionTime: Date.now() - executionStartTime
|
|
554
554
|
};
|
|
555
555
|
}
|
|
556
556
|
}
|
|
@@ -417,34 +417,106 @@ class MemorySystem extends events_1.EventEmitter {
|
|
|
417
417
|
await this.store('_schema_version', { version: targetVersion });
|
|
418
418
|
this.logger.info(`Database migrated to version ${targetVersion}`);
|
|
419
419
|
}
|
|
420
|
+
// ๐ด SNOW-003 FIX: More aggressive cleanup to prevent memory exhaustion
|
|
420
421
|
startCleanupTimer() {
|
|
421
|
-
// Run cleanup every hour
|
|
422
|
+
// ๐ด CRITICAL: Run cleanup every 15 minutes instead of 1 hour
|
|
422
423
|
this.cleanupTimer = setInterval(async () => {
|
|
423
424
|
await this.cleanup();
|
|
424
|
-
},
|
|
425
|
+
}, 900000); // 15 minutes
|
|
426
|
+
// ๐ด CRITICAL: Additional memory pressure cleanup every 5 minutes
|
|
427
|
+
setInterval(async () => {
|
|
428
|
+
await this.memoryPressureCleanup();
|
|
429
|
+
}, 300000); // 5 minutes
|
|
425
430
|
}
|
|
431
|
+
// ๐ด SNOW-003 FIX: Enhanced cleanup with memory pressure monitoring
|
|
426
432
|
async cleanup() {
|
|
427
433
|
if (!this.db)
|
|
428
434
|
return;
|
|
429
435
|
const now = Date.now();
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
436
|
+
const startTime = Date.now();
|
|
437
|
+
try {
|
|
438
|
+
// ๐ด CRITICAL: Clean expired memory entries with better logging
|
|
439
|
+
const stmt = this.db.prepare('DELETE FROM memory_store WHERE expires_at < ?');
|
|
440
|
+
const result = stmt.run(now);
|
|
441
|
+
if (result.changes > 0) {
|
|
442
|
+
this.logger.info(`๐งน Cleaned up ${result.changes} expired memory entries`);
|
|
443
|
+
}
|
|
444
|
+
// ๐ด CRITICAL: Clean cache with size monitoring
|
|
445
|
+
let cacheCleanedCount = 0;
|
|
446
|
+
const cacheEntriesBefore = this.cache.size;
|
|
447
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
448
|
+
if (entry.expiresAt < now) {
|
|
449
|
+
this.cache.delete(key);
|
|
450
|
+
cacheCleanedCount++;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (cacheCleanedCount > 0) {
|
|
454
|
+
this.logger.info(`๐งน Cleaned ${cacheCleanedCount} expired cache entries (${cacheEntriesBefore} โ ${this.cache.size})`);
|
|
455
|
+
}
|
|
456
|
+
// ๐ด CRITICAL: More frequent vacuum for high activity
|
|
457
|
+
const lastVacuum = await this.get('_last_vacuum');
|
|
458
|
+
const shouldVacuum = !lastVacuum || now - lastVacuum > 21600000; // 6 hours instead of 24
|
|
459
|
+
if (shouldVacuum) {
|
|
460
|
+
this.logger.info('๐๏ธ Starting database vacuum...');
|
|
461
|
+
this.db.exec('VACUUM');
|
|
462
|
+
await this.store('_last_vacuum', now);
|
|
463
|
+
this.logger.info(`โ
Database vacuumed in ${Date.now() - startTime}ms`);
|
|
464
|
+
}
|
|
465
|
+
// ๐ด CRITICAL: Log memory stats after cleanup
|
|
466
|
+
const memUsage = process.memoryUsage();
|
|
467
|
+
const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
|
|
468
|
+
if (heapUsedMB > 100) { // Log if significant memory usage
|
|
469
|
+
this.logger.info(`๐ Memory after cleanup: ${heapUsedMB}MB heap, ${this.cache.size} cache entries`);
|
|
470
|
+
}
|
|
435
471
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
472
|
+
catch (error) {
|
|
473
|
+
this.logger.error('โ Cleanup failed:', error);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* ๐ด SNOW-003 FIX: Emergency cleanup when memory pressure is high
|
|
478
|
+
*/
|
|
479
|
+
async memoryPressureCleanup() {
|
|
480
|
+
if (!this.db)
|
|
481
|
+
return;
|
|
482
|
+
try {
|
|
483
|
+
const memUsage = process.memoryUsage();
|
|
484
|
+
const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
|
|
485
|
+
const cacheSize = this.cache.size;
|
|
486
|
+
// ๐ด CRITICAL: Trigger aggressive cleanup if memory is high
|
|
487
|
+
if (heapUsedMB > 300 || cacheSize > 1000) {
|
|
488
|
+
this.logger.warn(`โ ๏ธ High memory pressure detected: ${heapUsedMB}MB heap, ${cacheSize} cache entries`);
|
|
489
|
+
// 1. Aggressive cache cleanup - remove 50% of oldest entries
|
|
490
|
+
if (cacheSize > 500) {
|
|
491
|
+
const entries = Array.from(this.cache.entries())
|
|
492
|
+
.sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
493
|
+
const toRemove = Math.floor(entries.length * 0.5);
|
|
494
|
+
for (let i = 0; i < toRemove; i++) {
|
|
495
|
+
this.cache.delete(entries[i][0]);
|
|
496
|
+
}
|
|
497
|
+
this.logger.info(`๐งน Emergency cache cleanup: removed ${toRemove} entries (${cacheSize} โ ${this.cache.size})`);
|
|
498
|
+
}
|
|
499
|
+
// 2. Clean old database entries more aggressively
|
|
500
|
+
const now = Date.now();
|
|
501
|
+
const aggressiveCleanupTime = now - (7 * 24 * 60 * 60 * 1000); // 7 days old
|
|
502
|
+
const aggressiveStmt = this.db.prepare('DELETE FROM memory_store WHERE created_at < ? AND expires_at IS NULL');
|
|
503
|
+
const aggressiveResult = aggressiveStmt.run(aggressiveCleanupTime);
|
|
504
|
+
if (aggressiveResult.changes > 0) {
|
|
505
|
+
this.logger.info(`๐งน Emergency database cleanup: removed ${aggressiveResult.changes} old entries`);
|
|
506
|
+
}
|
|
507
|
+
// 3. Force garbage collection if available
|
|
508
|
+
if (global.gc) {
|
|
509
|
+
this.logger.info('๐๏ธ Forcing garbage collection due to memory pressure');
|
|
510
|
+
global.gc();
|
|
511
|
+
// Check memory after GC
|
|
512
|
+
const afterGC = process.memoryUsage();
|
|
513
|
+
const afterGCMB = Math.round(afterGC.heapUsed / 1024 / 1024);
|
|
514
|
+
this.logger.info(`๐ Memory after GC: ${heapUsedMB}MB โ ${afterGCMB}MB`);
|
|
515
|
+
}
|
|
440
516
|
}
|
|
441
517
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (!lastVacuum || now - lastVacuum > 86400000) { // 24 hours
|
|
445
|
-
this.db.exec('VACUUM');
|
|
446
|
-
await this.store('_last_vacuum', now);
|
|
447
|
-
this.logger.info('Database vacuumed');
|
|
518
|
+
catch (error) {
|
|
519
|
+
this.logger.error('โ Memory pressure cleanup failed:', error);
|
|
448
520
|
}
|
|
449
521
|
}
|
|
450
522
|
checkCacheSize() {
|