snow-flow 1.3.22 → 1.3.24
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/mcp/base-mcp-server.js +183 -15
- package/dist/mcp/servicenow-deployment-mcp.js +258 -9
- package/dist/mcp/servicenow-flow-composer-mcp.js +218 -32
- package/dist/mcp/servicenow-intelligent-mcp.js +354 -25
- package/dist/mcp/servicenow-security-compliance-mcp-refactored.js +9 -9
- package/dist/memory/memory-client.js +30 -2
- package/dist/memory/memory-operations.js +63 -11
- package/dist/memory/memory-system.js +89 -17
- package/dist/utils/servicenow-client.js +178 -48
- package/dist/utils/xml-first-flow-generator.js +24 -2
- package/dist/version.js +1 -1
- 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
|
}
|
|
@@ -43,15 +43,43 @@ class MemoryClient {
|
|
|
43
43
|
await this.operations.setContext(this.sessionId, options.key, options.value, this.agentId, options.expires, options.permissions);
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Retrieve data from shared context
|
|
46
|
+
* Retrieve data from shared context with agent isolation
|
|
47
47
|
*/
|
|
48
48
|
async retrieve(options) {
|
|
49
|
-
|
|
49
|
+
// CRITICAL FIX: Pass agent ID for proper memory isolation
|
|
50
|
+
const context = await this.operations.getContext(this.sessionId, options.key, this.agentId);
|
|
50
51
|
if (!context) {
|
|
51
52
|
return options.defaultValue !== undefined ? options.defaultValue : null;
|
|
52
53
|
}
|
|
53
54
|
return context.context_value;
|
|
54
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Retrieve data from truly shared context (no agent isolation)
|
|
58
|
+
* Use this when agents need to access shared coordination data
|
|
59
|
+
*/
|
|
60
|
+
async retrieveShared(options) {
|
|
61
|
+
// Try shared prefix first
|
|
62
|
+
const sharedKey = `__shared__::${options.key}`;
|
|
63
|
+
let context = await this.operations.getContext(this.sessionId, sharedKey);
|
|
64
|
+
// Fallback to original key for backward compatibility
|
|
65
|
+
if (!context) {
|
|
66
|
+
context = await this.operations.getContext(this.sessionId, options.key);
|
|
67
|
+
}
|
|
68
|
+
if (!context) {
|
|
69
|
+
return options.defaultValue !== undefined ? options.defaultValue : null;
|
|
70
|
+
}
|
|
71
|
+
return context.context_value;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Store data in truly shared context (no agent isolation)
|
|
75
|
+
* Use this when you want all agents to access the same data
|
|
76
|
+
*/
|
|
77
|
+
async storeShared(options) {
|
|
78
|
+
// Use a special shared prefix to avoid conflicts with namespaced keys
|
|
79
|
+
const sharedKey = `__shared__::${options.key}`;
|
|
80
|
+
await this.operations.setContext(this.sessionId, sharedKey, options.value, this.agentId, // Still track who created it
|
|
81
|
+
options.expires, options.permissions);
|
|
82
|
+
}
|
|
55
83
|
/**
|
|
56
84
|
* Get all context for current session
|
|
57
85
|
*/
|
|
@@ -278,23 +278,49 @@ class MemoryOperations {
|
|
|
278
278
|
}
|
|
279
279
|
// ==================== Shared Context Operations ====================
|
|
280
280
|
/**
|
|
281
|
-
*
|
|
281
|
+
* Create agent-specific namespaced key for memory isolation
|
|
282
|
+
* This prevents agents from overwriting each other's memory within the same session
|
|
283
|
+
*/
|
|
284
|
+
createNamespacedKey(context_key, agent_id) {
|
|
285
|
+
// Use a delimiter that's unlikely to conflict with normal keys
|
|
286
|
+
return `${agent_id}::${context_key}`;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Parse namespaced key to extract original key and agent ID
|
|
290
|
+
*/
|
|
291
|
+
parseNamespacedKey(namespaced_key) {
|
|
292
|
+
const parts = namespaced_key.split('::');
|
|
293
|
+
if (parts.length === 2) {
|
|
294
|
+
return { agent_id: parts[0], original_key: parts[1] };
|
|
295
|
+
}
|
|
296
|
+
return { agent_id: null, original_key: namespaced_key };
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Store shared context with agent isolation
|
|
282
300
|
*/
|
|
283
301
|
async setContext(session_id, context_key, context_value, created_by_agent, expires_at, access_permissions) {
|
|
284
302
|
try {
|
|
303
|
+
// CRITICAL FIX: Create agent-specific namespace for the context key
|
|
304
|
+
// This ensures agents don't overwrite each other's memory
|
|
305
|
+
const namespacedKey = this.createNamespacedKey(context_key, created_by_agent);
|
|
285
306
|
this.memory.run(`
|
|
286
307
|
INSERT OR REPLACE INTO shared_context
|
|
287
308
|
(session_id, context_key, context_value, created_by_agent, expires_at, access_permissions)
|
|
288
309
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
289
310
|
`, [
|
|
290
311
|
session_id,
|
|
291
|
-
|
|
312
|
+
namespacedKey,
|
|
292
313
|
typeof context_value === 'string' ? context_value : JSON.stringify(context_value),
|
|
293
314
|
created_by_agent,
|
|
294
315
|
expires_at ? expires_at.toISOString() : null,
|
|
295
316
|
access_permissions ? JSON.stringify(access_permissions) : null
|
|
296
317
|
]);
|
|
297
|
-
this.logger.debug('Context stored
|
|
318
|
+
this.logger.debug('Context stored with agent isolation', {
|
|
319
|
+
session_id,
|
|
320
|
+
original_key: context_key,
|
|
321
|
+
namespaced_key: namespacedKey,
|
|
322
|
+
created_by_agent
|
|
323
|
+
});
|
|
298
324
|
}
|
|
299
325
|
catch (error) {
|
|
300
326
|
this.logger.error('Failed to store context', error);
|
|
@@ -302,18 +328,43 @@ class MemoryOperations {
|
|
|
302
328
|
}
|
|
303
329
|
}
|
|
304
330
|
/**
|
|
305
|
-
* Get shared context
|
|
331
|
+
* Get shared context with agent isolation support
|
|
306
332
|
*/
|
|
307
|
-
async getContext(session_id, context_key) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
333
|
+
async getContext(session_id, context_key, requesting_agent) {
|
|
334
|
+
// CRITICAL FIX: Try to get agent-specific namespaced key first
|
|
335
|
+
let result = null;
|
|
336
|
+
if (requesting_agent) {
|
|
337
|
+
const namespacedKey = this.createNamespacedKey(context_key, requesting_agent);
|
|
338
|
+
result = this.memory.get(`
|
|
339
|
+
SELECT * FROM shared_context
|
|
340
|
+
WHERE session_id = ? AND context_key = ?
|
|
341
|
+
AND (expires_at IS NULL OR expires_at > datetime('now'))
|
|
342
|
+
`, [session_id, namespacedKey]);
|
|
343
|
+
this.logger.debug('Attempting namespaced context retrieval', {
|
|
344
|
+
session_id,
|
|
345
|
+
original_key: context_key,
|
|
346
|
+
namespaced_key: namespacedKey,
|
|
347
|
+
requesting_agent,
|
|
348
|
+
found: !!result
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
// Fallback: Try to get the original key for backward compatibility or shared data
|
|
352
|
+
if (!result) {
|
|
353
|
+
result = this.memory.get(`
|
|
354
|
+
SELECT * FROM shared_context
|
|
355
|
+
WHERE session_id = ? AND context_key = ?
|
|
356
|
+
AND (expires_at IS NULL OR expires_at > datetime('now'))
|
|
357
|
+
`, [session_id, context_key]);
|
|
358
|
+
this.logger.debug('Fallback context retrieval', {
|
|
359
|
+
session_id,
|
|
360
|
+
context_key,
|
|
361
|
+
found: !!result
|
|
362
|
+
});
|
|
363
|
+
}
|
|
313
364
|
if (result) {
|
|
314
365
|
try {
|
|
315
366
|
// Try to parse JSON values
|
|
316
|
-
if (result.context_value && result.context_value.startsWith('{') || result.context_value.startsWith('[')) {
|
|
367
|
+
if (result.context_value && (result.context_value.startsWith('{') || result.context_value.startsWith('['))) {
|
|
317
368
|
result.context_value = JSON.parse(result.context_value);
|
|
318
369
|
}
|
|
319
370
|
if (result.access_permissions) {
|
|
@@ -322,6 +373,7 @@ class MemoryOperations {
|
|
|
322
373
|
}
|
|
323
374
|
catch (e) {
|
|
324
375
|
// If parsing fails, return as-is
|
|
376
|
+
this.logger.warn('Failed to parse stored JSON context', { context_key, error: e });
|
|
325
377
|
}
|
|
326
378
|
}
|
|
327
379
|
return result;
|