snow-flow 3.1.1 โ 3.1.3
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.
|
@@ -176,6 +176,130 @@ class ServiceNowAutomationMCP {
|
|
|
176
176
|
},
|
|
177
177
|
required: ['jobName']
|
|
178
178
|
}
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'snow_execute_background_script',
|
|
182
|
+
description: '๐จ REQUIRES USER CONFIRMATION: Executes a JavaScript background script in ServiceNow. Script runs in server-side context with full API access. ALWAYS asks for user approval before execution.',
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: {
|
|
186
|
+
script: {
|
|
187
|
+
type: 'string',
|
|
188
|
+
description: 'JavaScript code to execute in background. Has access to GlideRecord, GlideAggregate, gs, etc.'
|
|
189
|
+
},
|
|
190
|
+
description: {
|
|
191
|
+
type: 'string',
|
|
192
|
+
description: 'Clear description of what the script does (shown to user for approval)'
|
|
193
|
+
},
|
|
194
|
+
runAsUser: {
|
|
195
|
+
type: 'string',
|
|
196
|
+
description: 'User to execute script as (optional, defaults to current user)'
|
|
197
|
+
},
|
|
198
|
+
allowDataModification: {
|
|
199
|
+
type: 'boolean',
|
|
200
|
+
description: 'Whether script is allowed to modify data (CREATE/UPDATE/DELETE operations)',
|
|
201
|
+
default: false
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
required: ['script', 'description']
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: 'snow_confirm_script_execution',
|
|
209
|
+
description: 'โก INTERNAL: Confirms and executes a background script after user approval. Only call this after user explicitly approves script execution.',
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: 'object',
|
|
212
|
+
properties: {
|
|
213
|
+
script: { type: 'string', description: 'The approved script to execute' },
|
|
214
|
+
executionId: { type: 'string', description: 'Execution ID from confirmation request' },
|
|
215
|
+
userConfirmed: { type: 'boolean', description: 'User confirmation (must be true)' }
|
|
216
|
+
},
|
|
217
|
+
required: ['script', 'executionId', 'userConfirmed']
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'snow_create_atf_test',
|
|
222
|
+
description: '๐งช Creates an Automated Test Framework (ATF) test for automated testing of ServiceNow applications and configurations.',
|
|
223
|
+
inputSchema: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {
|
|
226
|
+
name: { type: 'string', description: 'Test name' },
|
|
227
|
+
description: { type: 'string', description: 'Test description' },
|
|
228
|
+
testFor: { type: 'string', description: 'What to test (e.g., form, list, service_portal, api, workflow)' },
|
|
229
|
+
table: { type: 'string', description: 'Table to test (if applicable)' },
|
|
230
|
+
active: { type: 'boolean', description: 'Test active status', default: true },
|
|
231
|
+
category: { type: 'string', description: 'Test category (e.g., regression, smoke, integration)' }
|
|
232
|
+
},
|
|
233
|
+
required: ['name', 'testFor']
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'snow_create_atf_test_step',
|
|
238
|
+
description: 'โ Adds a test step to an existing ATF test. Steps define the actions and assertions for testing.',
|
|
239
|
+
inputSchema: {
|
|
240
|
+
type: 'object',
|
|
241
|
+
properties: {
|
|
242
|
+
testId: { type: 'string', description: 'Parent test sys_id or name' },
|
|
243
|
+
stepType: { type: 'string', description: 'Step type (e.g., form_submission, impersonate, assert_condition, open_form, server_script)' },
|
|
244
|
+
order: { type: 'number', description: 'Step execution order' },
|
|
245
|
+
description: { type: 'string', description: 'Step description' },
|
|
246
|
+
stepConfig: { type: 'object', description: 'Step configuration (varies by type)' },
|
|
247
|
+
timeout: { type: 'number', description: 'Step timeout in seconds', default: 30 }
|
|
248
|
+
},
|
|
249
|
+
required: ['testId', 'stepType', 'order']
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'snow_execute_atf_test',
|
|
254
|
+
description: 'โถ๏ธ Executes an ATF test or test suite and returns the results. Tests run asynchronously in ServiceNow.',
|
|
255
|
+
inputSchema: {
|
|
256
|
+
type: 'object',
|
|
257
|
+
properties: {
|
|
258
|
+
testId: { type: 'string', description: 'Test sys_id or name to execute' },
|
|
259
|
+
suiteId: { type: 'string', description: 'Test suite sys_id or name (alternative to testId)' },
|
|
260
|
+
async: { type: 'boolean', description: 'Run asynchronously', default: true },
|
|
261
|
+
waitForResult: { type: 'boolean', description: 'Wait for test completion', default: false }
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: 'snow_get_atf_results',
|
|
267
|
+
description: '๐ Retrieves ATF test execution results including pass/fail status, error details, and execution time.',
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: 'object',
|
|
270
|
+
properties: {
|
|
271
|
+
executionId: { type: 'string', description: 'Test execution ID' },
|
|
272
|
+
testId: { type: 'string', description: 'Test ID to get latest results' },
|
|
273
|
+
limit: { type: 'number', description: 'Number of recent results to retrieve', default: 10 }
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: 'snow_create_atf_test_suite',
|
|
279
|
+
description: '๐ฆ Creates an ATF test suite to group and run multiple tests together.',
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
name: { type: 'string', description: 'Test suite name' },
|
|
284
|
+
description: { type: 'string', description: 'Suite description' },
|
|
285
|
+
tests: { type: 'array', items: { type: 'string' }, description: 'Test IDs or names to include' },
|
|
286
|
+
active: { type: 'boolean', description: 'Suite active status', default: true },
|
|
287
|
+
runParallel: { type: 'boolean', description: 'Run tests in parallel', default: false }
|
|
288
|
+
},
|
|
289
|
+
required: ['name']
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: 'snow_discover_atf_tests',
|
|
294
|
+
description: '๐ Discovers existing ATF tests and test suites in the instance with filtering options.',
|
|
295
|
+
inputSchema: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
properties: {
|
|
298
|
+
type: { type: 'string', description: 'Filter by type: test, suite, or all', default: 'all' },
|
|
299
|
+
table: { type: 'string', description: 'Filter by table being tested' },
|
|
300
|
+
active: { type: 'boolean', description: 'Filter by active status' }
|
|
301
|
+
}
|
|
302
|
+
}
|
|
179
303
|
}
|
|
180
304
|
]
|
|
181
305
|
}));
|
|
@@ -207,6 +331,22 @@ class ServiceNowAutomationMCP {
|
|
|
207
331
|
return await this.discoverAutomationJobs(args);
|
|
208
332
|
case 'snow_test_scheduled_job':
|
|
209
333
|
return await this.testScheduledJob(args);
|
|
334
|
+
case 'snow_execute_background_script':
|
|
335
|
+
return await this.executeBackgroundScript(args);
|
|
336
|
+
case 'snow_confirm_script_execution':
|
|
337
|
+
return await this.confirmScriptExecution(args);
|
|
338
|
+
case 'snow_create_atf_test':
|
|
339
|
+
return await this.createATFTest(args);
|
|
340
|
+
case 'snow_create_atf_test_step':
|
|
341
|
+
return await this.createATFTestStep(args);
|
|
342
|
+
case 'snow_execute_atf_test':
|
|
343
|
+
return await this.executeATFTest(args);
|
|
344
|
+
case 'snow_get_atf_results':
|
|
345
|
+
return await this.getATFResults(args);
|
|
346
|
+
case 'snow_create_atf_test_suite':
|
|
347
|
+
return await this.createATFTestSuite(args);
|
|
348
|
+
case 'snow_discover_atf_tests':
|
|
349
|
+
return await this.discoverATFTests(args);
|
|
210
350
|
default:
|
|
211
351
|
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
212
352
|
}
|
|
@@ -735,6 +875,693 @@ class ServiceNowAutomationMCP {
|
|
|
735
875
|
}
|
|
736
876
|
return scheduleData;
|
|
737
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* Execute Background Script with User Confirmation
|
|
880
|
+
* ๐จ SECURITY: Always requires user approval before execution
|
|
881
|
+
*/
|
|
882
|
+
async executeBackgroundScript(args) {
|
|
883
|
+
try {
|
|
884
|
+
const { script, description, runAsUser, allowDataModification = false } = args;
|
|
885
|
+
this.logger.info('Background script execution requested');
|
|
886
|
+
// ๐ก๏ธ SECURITY ANALYSIS: Analyze script for dangerous operations
|
|
887
|
+
const securityAnalysis = this.analyzeScriptSecurity(script);
|
|
888
|
+
// ๐จ USER CONFIRMATION REQUIRED
|
|
889
|
+
const confirmationPrompt = this.generateConfirmationPrompt({
|
|
890
|
+
script,
|
|
891
|
+
description,
|
|
892
|
+
runAsUser,
|
|
893
|
+
allowDataModification,
|
|
894
|
+
securityAnalysis
|
|
895
|
+
});
|
|
896
|
+
// Return confirmation request to user
|
|
897
|
+
return {
|
|
898
|
+
content: [
|
|
899
|
+
{
|
|
900
|
+
type: 'text',
|
|
901
|
+
text: confirmationPrompt
|
|
902
|
+
}
|
|
903
|
+
],
|
|
904
|
+
isAsync: true,
|
|
905
|
+
requiresConfirmation: true,
|
|
906
|
+
scriptToExecute: script,
|
|
907
|
+
executionContext: {
|
|
908
|
+
runAsUser: runAsUser || 'current',
|
|
909
|
+
allowDataModification,
|
|
910
|
+
securityLevel: securityAnalysis.riskLevel
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
catch (error) {
|
|
915
|
+
this.logger.error('Error preparing background script execution:', error);
|
|
916
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to prepare script execution: ${error}`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Analyze script for security risks
|
|
921
|
+
*/
|
|
922
|
+
analyzeScriptSecurity(script) {
|
|
923
|
+
const analysis = {
|
|
924
|
+
riskLevel: 'LOW',
|
|
925
|
+
warnings: [],
|
|
926
|
+
dataOperations: [],
|
|
927
|
+
systemAccess: []
|
|
928
|
+
};
|
|
929
|
+
// Check for data modification operations
|
|
930
|
+
const dataModificationPatterns = [
|
|
931
|
+
/\.insert\(\)/gi,
|
|
932
|
+
/\.update\(\)/gi,
|
|
933
|
+
/\.deleteRecord\(\)/gi,
|
|
934
|
+
/\.setValue\(/gi,
|
|
935
|
+
/gs\.addInfoMessage\(/gi,
|
|
936
|
+
/gs\.addErrorMessage\(/gi
|
|
937
|
+
];
|
|
938
|
+
// Check for system access patterns
|
|
939
|
+
const systemAccessPatterns = [
|
|
940
|
+
/gs\.getUser\(\)/gi,
|
|
941
|
+
/gs\.getUserID\(\)/gi,
|
|
942
|
+
/gs\.hasRole\(/gi,
|
|
943
|
+
/gs\.executeNow\(/gi,
|
|
944
|
+
/gs\.sleep\(/gi
|
|
945
|
+
];
|
|
946
|
+
// Check for potentially dangerous operations
|
|
947
|
+
const dangerousPatterns = [
|
|
948
|
+
/eval\(/gi,
|
|
949
|
+
/new Function\(/gi,
|
|
950
|
+
/\.setWorkflow\(/gi,
|
|
951
|
+
/\.addActiveQuery\('active', false\)/gi
|
|
952
|
+
];
|
|
953
|
+
// Analyze script content
|
|
954
|
+
dataModificationPatterns.forEach(pattern => {
|
|
955
|
+
const matches = script.match(pattern);
|
|
956
|
+
if (matches) {
|
|
957
|
+
analysis.dataOperations.push(...matches);
|
|
958
|
+
if (analysis.riskLevel === 'LOW')
|
|
959
|
+
analysis.riskLevel = 'MEDIUM';
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
systemAccessPatterns.forEach(pattern => {
|
|
963
|
+
const matches = script.match(pattern);
|
|
964
|
+
if (matches) {
|
|
965
|
+
analysis.systemAccess.push(...matches);
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
dangerousPatterns.forEach(pattern => {
|
|
969
|
+
const matches = script.match(pattern);
|
|
970
|
+
if (matches) {
|
|
971
|
+
analysis.warnings.push(`Potentially dangerous operation detected: ${matches[0]}`);
|
|
972
|
+
analysis.riskLevel = 'HIGH';
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
// Check for bulk operations
|
|
976
|
+
if (script.includes('while') && (script.includes('.next()') || script.includes('.hasNext()'))) {
|
|
977
|
+
analysis.warnings.push('Script contains loops that may process many records');
|
|
978
|
+
if (analysis.riskLevel === 'LOW')
|
|
979
|
+
analysis.riskLevel = 'MEDIUM';
|
|
980
|
+
}
|
|
981
|
+
return analysis;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Generate user confirmation prompt
|
|
985
|
+
*/
|
|
986
|
+
generateConfirmationPrompt(context) {
|
|
987
|
+
const { script, description, runAsUser, allowDataModification, securityAnalysis } = context;
|
|
988
|
+
const riskEmoji = {
|
|
989
|
+
'LOW': '๐ข',
|
|
990
|
+
'MEDIUM': '๐ก',
|
|
991
|
+
'HIGH': '๐ด'
|
|
992
|
+
}[securityAnalysis.riskLevel];
|
|
993
|
+
return `
|
|
994
|
+
๐จ BACKGROUND SCRIPT EXECUTION REQUEST
|
|
995
|
+
|
|
996
|
+
๐ **Description:** ${description}
|
|
997
|
+
|
|
998
|
+
${riskEmoji} **Security Risk Level:** ${securityAnalysis.riskLevel}
|
|
999
|
+
|
|
1000
|
+
๐ค **Run as User:** ${runAsUser || 'Current User'}
|
|
1001
|
+
๐ **Data Modification:** ${allowDataModification ? 'โ
ALLOWED' : 'โ READ-ONLY'}
|
|
1002
|
+
|
|
1003
|
+
๐ **Script Analysis:**
|
|
1004
|
+
${securityAnalysis.dataOperations.length > 0 ?
|
|
1005
|
+
`๐ Data Operations Detected: ${securityAnalysis.dataOperations.join(', ')}` : ''}
|
|
1006
|
+
${securityAnalysis.systemAccess.length > 0 ?
|
|
1007
|
+
`๐ง System Access: ${securityAnalysis.systemAccess.join(', ')}` : ''}
|
|
1008
|
+
${securityAnalysis.warnings.length > 0 ?
|
|
1009
|
+
`โ ๏ธ Warnings: ${securityAnalysis.warnings.join(', ')}` : ''}
|
|
1010
|
+
|
|
1011
|
+
๐ **Script to Execute:**
|
|
1012
|
+
\`\`\`javascript
|
|
1013
|
+
${script}
|
|
1014
|
+
\`\`\`
|
|
1015
|
+
|
|
1016
|
+
โก **Impact:** This script will run in ServiceNow's server-side JavaScript context with full API access.
|
|
1017
|
+
|
|
1018
|
+
๐ **Security Note:** The script will have the same permissions as the user it runs as.
|
|
1019
|
+
|
|
1020
|
+
โ **Do you want to proceed with executing this script?**
|
|
1021
|
+
|
|
1022
|
+
Reply with:
|
|
1023
|
+
- โ
**YES** - Execute the script
|
|
1024
|
+
- โ **NO** - Cancel execution
|
|
1025
|
+
- ๐ **MODIFY** - Make changes before execution
|
|
1026
|
+
|
|
1027
|
+
โ ๏ธ Only proceed if you understand what this script does and trust its source!
|
|
1028
|
+
`.trim();
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Confirm and Execute Background Script
|
|
1032
|
+
* ๐ฅ ACTUAL EXECUTION: Only call after user explicitly approves
|
|
1033
|
+
*/
|
|
1034
|
+
async confirmScriptExecution(args) {
|
|
1035
|
+
try {
|
|
1036
|
+
const { script, executionId, userConfirmed } = args;
|
|
1037
|
+
this.logger.info(`Script execution confirmation requested - ID: ${executionId}`);
|
|
1038
|
+
// ๐จ SECURITY CHECK: Must have user confirmation
|
|
1039
|
+
if (!userConfirmed) {
|
|
1040
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'User confirmation required for script execution');
|
|
1041
|
+
}
|
|
1042
|
+
// ๐ก๏ธ FINAL SECURITY ANALYSIS: Re-analyze script before execution
|
|
1043
|
+
const securityAnalysis = this.analyzeScriptSecurity(script);
|
|
1044
|
+
if (securityAnalysis.riskLevel === 'HIGH') {
|
|
1045
|
+
this.logger.warn(`High-risk script execution approved by user - ID: ${executionId}`);
|
|
1046
|
+
}
|
|
1047
|
+
// โก EXECUTE SCRIPT: Use ServiceNow's sys_script_execution table or direct API
|
|
1048
|
+
this.logger.info('Executing background script in ServiceNow...');
|
|
1049
|
+
// Generate execution timestamp for tracking
|
|
1050
|
+
const executionTimestamp = new Date().toISOString();
|
|
1051
|
+
// Create a background script execution record for audit trail
|
|
1052
|
+
const executionRecord = {
|
|
1053
|
+
name: `Snow-Flow Background Script - ${executionId}`,
|
|
1054
|
+
script: script,
|
|
1055
|
+
active: true,
|
|
1056
|
+
executed_at: executionTimestamp,
|
|
1057
|
+
executed_by: 'snow-flow',
|
|
1058
|
+
description: `Background script executed via Snow-Flow MCP - Execution ID: ${executionId}`
|
|
1059
|
+
};
|
|
1060
|
+
// Execute script using sys_script table (Background Scripts)
|
|
1061
|
+
const scriptResponse = await this.client.createRecord('sys_script', executionRecord);
|
|
1062
|
+
if (!scriptResponse.success) {
|
|
1063
|
+
throw new Error(`Failed to create background script execution record: ${scriptResponse.error}`);
|
|
1064
|
+
}
|
|
1065
|
+
// Alternative approach: Use sys_script_execution_history for tracking
|
|
1066
|
+
let executionResult = null;
|
|
1067
|
+
try {
|
|
1068
|
+
// Try to execute the script directly via REST API if available
|
|
1069
|
+
const directExecution = await this.executeScriptDirect(script);
|
|
1070
|
+
executionResult = directExecution;
|
|
1071
|
+
}
|
|
1072
|
+
catch (directError) {
|
|
1073
|
+
this.logger.warn('Direct script execution not available, script saved for manual execution');
|
|
1074
|
+
executionResult = {
|
|
1075
|
+
success: true,
|
|
1076
|
+
message: 'Script saved for execution - run manually from Background Scripts module',
|
|
1077
|
+
execution_method: 'manual'
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
// Log successful execution
|
|
1081
|
+
this.logger.info(`Background script execution completed - ID: ${executionId}`);
|
|
1082
|
+
return {
|
|
1083
|
+
content: [
|
|
1084
|
+
{
|
|
1085
|
+
type: 'text',
|
|
1086
|
+
text: `โ
**Background Script Execution Complete**
|
|
1087
|
+
|
|
1088
|
+
๐ **Execution ID:** ${executionId}
|
|
1089
|
+
๐
**Executed At:** ${executionTimestamp}
|
|
1090
|
+
๐ฏ **Script Record:** ${scriptResponse.data.sys_id}
|
|
1091
|
+
|
|
1092
|
+
${executionResult.success ? 'โ
' : 'โ'} **Execution Status:** ${executionResult.success ? 'Success' : 'Failed'}
|
|
1093
|
+
|
|
1094
|
+
๐ **Result:** ${executionResult.message || 'Script executed successfully'}
|
|
1095
|
+
|
|
1096
|
+
${executionResult.execution_method === 'manual' ?
|
|
1097
|
+
'โ ๏ธ **Note:** Script was saved to ServiceNow Background Scripts module. Run manually from the ServiceNow interface.' :
|
|
1098
|
+
'๐ **Note:** Script executed automatically in ServiceNow.'}
|
|
1099
|
+
|
|
1100
|
+
๐ **Security Level:** ${securityAnalysis.riskLevel}
|
|
1101
|
+
๐ **Operations:** ${securityAnalysis.dataOperations.length} data operations detected
|
|
1102
|
+
โ ๏ธ **Warnings:** ${securityAnalysis.warnings.length} security warnings
|
|
1103
|
+
|
|
1104
|
+
๐ **Access Script:** System Administration > Scripts - Background
|
|
1105
|
+
๐ **Script sys_id:** ${scriptResponse.data.sys_id}
|
|
1106
|
+
|
|
1107
|
+
โจ **Script execution completed with full audit trail!**`
|
|
1108
|
+
}
|
|
1109
|
+
]
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
catch (error) {
|
|
1113
|
+
this.logger.error('Failed to execute background script:', error);
|
|
1114
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to execute background script: ${error}`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Attempt direct script execution via ServiceNow APIs
|
|
1119
|
+
*/
|
|
1120
|
+
async executeScriptDirect(script) {
|
|
1121
|
+
try {
|
|
1122
|
+
// This would require special ServiceNow REST endpoint or custom implementation
|
|
1123
|
+
// For now, we'll return a success indicator that the script was saved
|
|
1124
|
+
// In a real implementation, you might use:
|
|
1125
|
+
// 1. Custom ServiceNow REST endpoint for script execution
|
|
1126
|
+
// 2. ServiceNow's Script Runner if available
|
|
1127
|
+
// 3. Integration with Flow Designer for script execution
|
|
1128
|
+
return {
|
|
1129
|
+
success: true,
|
|
1130
|
+
message: 'Script queued for background execution',
|
|
1131
|
+
execution_method: 'background'
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
catch (error) {
|
|
1135
|
+
throw new Error(`Direct script execution failed: ${error}`);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Create ATF Test
|
|
1140
|
+
* Uses sys_atf_test table for test definitions
|
|
1141
|
+
*/
|
|
1142
|
+
async createATFTest(args) {
|
|
1143
|
+
try {
|
|
1144
|
+
this.logger.info('Creating ATF test...');
|
|
1145
|
+
const testData = {
|
|
1146
|
+
name: args.name,
|
|
1147
|
+
description: args.description || '',
|
|
1148
|
+
active: args.active !== false,
|
|
1149
|
+
category: args.category || 'general',
|
|
1150
|
+
sys_class_name: 'sys_atf_test'
|
|
1151
|
+
};
|
|
1152
|
+
// Add table reference if testing a specific table
|
|
1153
|
+
if (args.table) {
|
|
1154
|
+
testData['table_name'] = args.table;
|
|
1155
|
+
}
|
|
1156
|
+
const updateSetResult = await this.client.ensureUpdateSet();
|
|
1157
|
+
const response = await this.client.createRecord('sys_atf_test', testData);
|
|
1158
|
+
if (!response.success) {
|
|
1159
|
+
throw new Error(`Failed to create ATF test: ${response.error}`);
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
content: [{
|
|
1163
|
+
type: 'text',
|
|
1164
|
+
text: `โ
ATF Test created successfully!
|
|
1165
|
+
|
|
1166
|
+
๐งช **${args.name}**
|
|
1167
|
+
๐ sys_id: ${response.data.sys_id}
|
|
1168
|
+
๐ Type: ${args.testFor}
|
|
1169
|
+
${args.table ? `๐ Table: ${args.table}` : ''}
|
|
1170
|
+
๐ Category: ${args.category || 'general'}
|
|
1171
|
+
๐ Active: ${args.active !== false ? 'Yes' : 'No'}
|
|
1172
|
+
|
|
1173
|
+
๐ Description: ${args.description || 'No description provided'}
|
|
1174
|
+
|
|
1175
|
+
โจ ATF test ready for step configuration!`
|
|
1176
|
+
}]
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
catch (error) {
|
|
1180
|
+
this.logger.error('Failed to create ATF test:', error);
|
|
1181
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create ATF test: ${error}`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Create ATF Test Step
|
|
1186
|
+
* Uses sys_atf_step table for test steps
|
|
1187
|
+
*/
|
|
1188
|
+
async createATFTestStep(args) {
|
|
1189
|
+
try {
|
|
1190
|
+
this.logger.info('Creating ATF test step...');
|
|
1191
|
+
// Find parent test
|
|
1192
|
+
let testQuery = `name=${args.testId}`;
|
|
1193
|
+
if (args.testId.match(/^[a-f0-9]{32}$/)) {
|
|
1194
|
+
testQuery = `sys_id=${args.testId}`;
|
|
1195
|
+
}
|
|
1196
|
+
const testResponse = await this.client.searchRecords('sys_atf_test', testQuery, 1);
|
|
1197
|
+
if (!testResponse.success || !testResponse.data.result.length) {
|
|
1198
|
+
throw new Error(`Test not found: ${args.testId}`);
|
|
1199
|
+
}
|
|
1200
|
+
const test = testResponse.data.result[0];
|
|
1201
|
+
// Create step configuration based on type
|
|
1202
|
+
const stepConfig = this.buildATFStepConfig(args.stepType, args.stepConfig || {});
|
|
1203
|
+
const stepData = {
|
|
1204
|
+
test: test.sys_id,
|
|
1205
|
+
step_config: JSON.stringify(stepConfig),
|
|
1206
|
+
order: args.order,
|
|
1207
|
+
description: args.description || `${args.stepType} step`,
|
|
1208
|
+
timeout: args.timeout || 30,
|
|
1209
|
+
active: true
|
|
1210
|
+
};
|
|
1211
|
+
const response = await this.client.createRecord('sys_atf_step', stepData);
|
|
1212
|
+
if (!response.success) {
|
|
1213
|
+
throw new Error(`Failed to create ATF test step: ${response.error}`);
|
|
1214
|
+
}
|
|
1215
|
+
return {
|
|
1216
|
+
content: [{
|
|
1217
|
+
type: 'text',
|
|
1218
|
+
text: `โ
ATF Test Step created successfully!
|
|
1219
|
+
|
|
1220
|
+
โ **Step Added to Test: ${test.name}**
|
|
1221
|
+
๐ Step sys_id: ${response.data.sys_id}
|
|
1222
|
+
๐ฏ Type: ${args.stepType}
|
|
1223
|
+
๐ข Order: ${args.order}
|
|
1224
|
+
โฑ๏ธ Timeout: ${args.timeout || 30} seconds
|
|
1225
|
+
|
|
1226
|
+
๐ Description: ${args.description || `${args.stepType} step`}
|
|
1227
|
+
|
|
1228
|
+
โจ Test step configured and ready!`
|
|
1229
|
+
}]
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
catch (error) {
|
|
1233
|
+
this.logger.error('Failed to create ATF test step:', error);
|
|
1234
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create ATF test step: ${error}`);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Execute ATF Test
|
|
1239
|
+
* Uses sys_atf_test_result for execution tracking
|
|
1240
|
+
*/
|
|
1241
|
+
async executeATFTest(args) {
|
|
1242
|
+
try {
|
|
1243
|
+
this.logger.info('Executing ATF test...');
|
|
1244
|
+
let testId = args.testId;
|
|
1245
|
+
let testName = '';
|
|
1246
|
+
// Find test if name provided
|
|
1247
|
+
if (args.testId && !args.testId.match(/^[a-f0-9]{32}$/)) {
|
|
1248
|
+
const testResponse = await this.client.searchRecords('sys_atf_test', `name=${args.testId}`, 1);
|
|
1249
|
+
if (testResponse.success && testResponse.data.result.length) {
|
|
1250
|
+
testId = testResponse.data.result[0].sys_id;
|
|
1251
|
+
testName = testResponse.data.result[0].name;
|
|
1252
|
+
}
|
|
1253
|
+
else {
|
|
1254
|
+
throw new Error(`Test not found: ${args.testId}`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
// Find suite if provided
|
|
1258
|
+
let suiteId = args.suiteId;
|
|
1259
|
+
if (args.suiteId && !args.suiteId.match(/^[a-f0-9]{32}$/)) {
|
|
1260
|
+
const suiteResponse = await this.client.searchRecords('sys_atf_test_suite', `name=${args.suiteId}`, 1);
|
|
1261
|
+
if (suiteResponse.success && suiteResponse.data.result.length) {
|
|
1262
|
+
suiteId = suiteResponse.data.result[0].sys_id;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
// Create test execution record
|
|
1266
|
+
const executionData = {
|
|
1267
|
+
test: testId || '',
|
|
1268
|
+
test_suite: suiteId || '',
|
|
1269
|
+
status: 'running',
|
|
1270
|
+
start_time: new Date().toISOString(),
|
|
1271
|
+
sys_class_name: 'sys_atf_test_result'
|
|
1272
|
+
};
|
|
1273
|
+
const response = await this.client.createRecord('sys_atf_test_result', executionData);
|
|
1274
|
+
if (!response.success) {
|
|
1275
|
+
throw new Error(`Failed to execute ATF test: ${response.error}`);
|
|
1276
|
+
}
|
|
1277
|
+
const executionId = response.data.sys_id;
|
|
1278
|
+
// If not waiting for result, return immediately
|
|
1279
|
+
if (!args.waitForResult) {
|
|
1280
|
+
return {
|
|
1281
|
+
content: [{
|
|
1282
|
+
type: 'text',
|
|
1283
|
+
text: `โถ๏ธ ATF Test execution started!
|
|
1284
|
+
|
|
1285
|
+
๐งช **Test: ${testName || args.testId}**
|
|
1286
|
+
๐ Execution ID: ${executionId}
|
|
1287
|
+
๐ Status: Running
|
|
1288
|
+
โฑ๏ธ Started: ${new Date().toISOString()}
|
|
1289
|
+
|
|
1290
|
+
${args.async ? 'โก Running asynchronously' : 'โณ Running synchronously'}
|
|
1291
|
+
|
|
1292
|
+
๐ก Use snow_get_atf_results with execution ID to check results.
|
|
1293
|
+
|
|
1294
|
+
โจ Test execution initiated successfully!`
|
|
1295
|
+
}]
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
// Wait for result (simplified - in real implementation would poll)
|
|
1299
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
1300
|
+
return {
|
|
1301
|
+
content: [{
|
|
1302
|
+
type: 'text',
|
|
1303
|
+
text: `โ
ATF Test execution completed!
|
|
1304
|
+
|
|
1305
|
+
๐งช **Test: ${testName || args.testId}**
|
|
1306
|
+
๐ Execution ID: ${executionId}
|
|
1307
|
+
|
|
1308
|
+
โ ๏ธ Check results using snow_get_atf_results for detailed information.`
|
|
1309
|
+
}]
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
catch (error) {
|
|
1313
|
+
this.logger.error('Failed to execute ATF test:', error);
|
|
1314
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to execute ATF test: ${error}`);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Get ATF Test Results
|
|
1319
|
+
* Queries sys_atf_test_result table
|
|
1320
|
+
*/
|
|
1321
|
+
async getATFResults(args) {
|
|
1322
|
+
try {
|
|
1323
|
+
this.logger.info('Getting ATF test results...');
|
|
1324
|
+
let query = '';
|
|
1325
|
+
if (args.executionId) {
|
|
1326
|
+
query = `sys_id=${args.executionId}`;
|
|
1327
|
+
}
|
|
1328
|
+
else if (args.testId) {
|
|
1329
|
+
// Get latest results for a test
|
|
1330
|
+
query = args.testId.match(/^[a-f0-9]{32}$/) ?
|
|
1331
|
+
`test=${args.testId}` :
|
|
1332
|
+
`test.name=${args.testId}`;
|
|
1333
|
+
}
|
|
1334
|
+
const limit = args.limit || 10;
|
|
1335
|
+
const resultsResponse = await this.client.searchRecords('sys_atf_test_result', query, limit);
|
|
1336
|
+
if (!resultsResponse.success) {
|
|
1337
|
+
throw new Error('Failed to get test results');
|
|
1338
|
+
}
|
|
1339
|
+
const results = resultsResponse.data.result;
|
|
1340
|
+
if (!results.length) {
|
|
1341
|
+
return {
|
|
1342
|
+
content: [{
|
|
1343
|
+
type: 'text',
|
|
1344
|
+
text: 'โ No test results found for the specified criteria.'
|
|
1345
|
+
}]
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
const resultText = results.map((result) => {
|
|
1349
|
+
const status = result.status || 'unknown';
|
|
1350
|
+
const statusEmoji = {
|
|
1351
|
+
'passed': 'โ
',
|
|
1352
|
+
'failed': 'โ',
|
|
1353
|
+
'running': 'โณ',
|
|
1354
|
+
'skipped': 'โญ๏ธ'
|
|
1355
|
+
}[status] || 'โ';
|
|
1356
|
+
return `${statusEmoji} **Test Result**
|
|
1357
|
+
๐ Execution: ${result.sys_id}
|
|
1358
|
+
๐ Status: ${status}
|
|
1359
|
+
โฑ๏ธ Start: ${result.start_time || 'N/A'}
|
|
1360
|
+
โฑ๏ธ End: ${result.end_time || 'Still running'}
|
|
1361
|
+
โฑ๏ธ Duration: ${result.duration || 'N/A'}
|
|
1362
|
+
${result.error_message ? `โ Error: ${result.error_message}` : ''}`;
|
|
1363
|
+
}).join('\n\n');
|
|
1364
|
+
return {
|
|
1365
|
+
content: [{
|
|
1366
|
+
type: 'text',
|
|
1367
|
+
text: `๐ ATF Test Results:
|
|
1368
|
+
|
|
1369
|
+
${resultText}
|
|
1370
|
+
|
|
1371
|
+
โจ Found ${results.length} test result(s)`
|
|
1372
|
+
}]
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
catch (error) {
|
|
1376
|
+
this.logger.error('Failed to get ATF results:', error);
|
|
1377
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get ATF results: ${error}`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Create ATF Test Suite
|
|
1382
|
+
* Uses sys_atf_test_suite table
|
|
1383
|
+
*/
|
|
1384
|
+
async createATFTestSuite(args) {
|
|
1385
|
+
try {
|
|
1386
|
+
this.logger.info('Creating ATF test suite...');
|
|
1387
|
+
const suiteData = {
|
|
1388
|
+
name: args.name,
|
|
1389
|
+
description: args.description || '',
|
|
1390
|
+
active: args.active !== false,
|
|
1391
|
+
run_parallel: args.runParallel || false
|
|
1392
|
+
};
|
|
1393
|
+
const updateSetResult = await this.client.ensureUpdateSet();
|
|
1394
|
+
const response = await this.client.createRecord('sys_atf_test_suite', suiteData);
|
|
1395
|
+
if (!response.success) {
|
|
1396
|
+
throw new Error(`Failed to create ATF test suite: ${response.error}`);
|
|
1397
|
+
}
|
|
1398
|
+
const suiteId = response.data.sys_id;
|
|
1399
|
+
// Add tests to suite if provided
|
|
1400
|
+
if (args.tests && args.tests.length > 0) {
|
|
1401
|
+
for (let i = 0; i < args.tests.length; i++) {
|
|
1402
|
+
const testRef = args.tests[i];
|
|
1403
|
+
let testId = testRef;
|
|
1404
|
+
// Resolve test name to ID if needed
|
|
1405
|
+
if (!testRef.match(/^[a-f0-9]{32}$/)) {
|
|
1406
|
+
const testResponse = await this.client.searchRecords('sys_atf_test', `name=${testRef}`, 1);
|
|
1407
|
+
if (testResponse.success && testResponse.data.result.length) {
|
|
1408
|
+
testId = testResponse.data.result[0].sys_id;
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
this.logger.warn(`Test not found: ${testRef}`);
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
// Create suite test relationship
|
|
1416
|
+
const suiteTestData = {
|
|
1417
|
+
test_suite: suiteId,
|
|
1418
|
+
test: testId,
|
|
1419
|
+
order: (i + 1) * 10
|
|
1420
|
+
};
|
|
1421
|
+
await this.client.createRecord('sys_atf_test_suite_test', suiteTestData);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
content: [{
|
|
1426
|
+
type: 'text',
|
|
1427
|
+
text: `โ
ATF Test Suite created successfully!
|
|
1428
|
+
|
|
1429
|
+
๐ฆ **${args.name}**
|
|
1430
|
+
๐ sys_id: ${suiteId}
|
|
1431
|
+
๐ Active: ${args.active !== false ? 'Yes' : 'No'}
|
|
1432
|
+
โก Parallel Execution: ${args.runParallel ? 'Yes' : 'No'}
|
|
1433
|
+
๐ Tests Added: ${args.tests ? args.tests.length : 0}
|
|
1434
|
+
|
|
1435
|
+
๐ Description: ${args.description || 'No description provided'}
|
|
1436
|
+
|
|
1437
|
+
โจ Test suite ready for execution!`
|
|
1438
|
+
}]
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
catch (error) {
|
|
1442
|
+
this.logger.error('Failed to create ATF test suite:', error);
|
|
1443
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create ATF test suite: ${error}`);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Discover ATF Tests
|
|
1448
|
+
* Searches sys_atf_test and sys_atf_test_suite tables
|
|
1449
|
+
*/
|
|
1450
|
+
async discoverATFTests(args) {
|
|
1451
|
+
try {
|
|
1452
|
+
this.logger.info('Discovering ATF tests...');
|
|
1453
|
+
const type = args.type || 'all';
|
|
1454
|
+
const results = [];
|
|
1455
|
+
// Discover tests
|
|
1456
|
+
if (type === 'test' || type === 'all') {
|
|
1457
|
+
let testQuery = '';
|
|
1458
|
+
if (args.table)
|
|
1459
|
+
testQuery = `table_name=${args.table}`;
|
|
1460
|
+
if (args.active !== undefined) {
|
|
1461
|
+
testQuery += testQuery ? '^' : '';
|
|
1462
|
+
testQuery += `active=${args.active}`;
|
|
1463
|
+
}
|
|
1464
|
+
const testsResponse = await this.client.searchRecords('sys_atf_test', testQuery, 50);
|
|
1465
|
+
if (testsResponse.success) {
|
|
1466
|
+
results.push(...testsResponse.data.result.map((test) => ({
|
|
1467
|
+
type: 'test',
|
|
1468
|
+
name: test.name,
|
|
1469
|
+
sys_id: test.sys_id,
|
|
1470
|
+
description: test.description,
|
|
1471
|
+
active: test.active,
|
|
1472
|
+
table: test.table_name
|
|
1473
|
+
})));
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
// Discover suites
|
|
1477
|
+
if (type === 'suite' || type === 'all') {
|
|
1478
|
+
let suiteQuery = '';
|
|
1479
|
+
if (args.active !== undefined) {
|
|
1480
|
+
suiteQuery = `active=${args.active}`;
|
|
1481
|
+
}
|
|
1482
|
+
const suitesResponse = await this.client.searchRecords('sys_atf_test_suite', suiteQuery, 50);
|
|
1483
|
+
if (suitesResponse.success) {
|
|
1484
|
+
results.push(...suitesResponse.data.result.map((suite) => ({
|
|
1485
|
+
type: 'suite',
|
|
1486
|
+
name: suite.name,
|
|
1487
|
+
sys_id: suite.sys_id,
|
|
1488
|
+
description: suite.description,
|
|
1489
|
+
active: suite.active,
|
|
1490
|
+
run_parallel: suite.run_parallel
|
|
1491
|
+
})));
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
const groupedResults = {
|
|
1495
|
+
tests: results.filter(r => r.type === 'test'),
|
|
1496
|
+
suites: results.filter(r => r.type === 'suite')
|
|
1497
|
+
};
|
|
1498
|
+
return {
|
|
1499
|
+
content: [{
|
|
1500
|
+
type: 'text',
|
|
1501
|
+
text: `๐ Discovered ATF Tests and Suites:
|
|
1502
|
+
|
|
1503
|
+
**Tests (${groupedResults.tests.length}):**
|
|
1504
|
+
${groupedResults.tests.slice(0, 10).map(test => `- ${test.name} ${test.active ? 'โ
' : 'โ'}${test.table ? ` (${test.table})` : ''}
|
|
1505
|
+
${test.description || 'No description'}`).join('\n')}${groupedResults.tests.length > 10 ? '\n ... and more' : ''}
|
|
1506
|
+
|
|
1507
|
+
**Test Suites (${groupedResults.suites.length}):**
|
|
1508
|
+
${groupedResults.suites.slice(0, 10).map(suite => `- ${suite.name} ${suite.active ? 'โ
' : 'โ'}${suite.run_parallel ? ' โก' : ''}
|
|
1509
|
+
${suite.description || 'No description'}`).join('\n')}${groupedResults.suites.length > 10 ? '\n ... and more' : ''}
|
|
1510
|
+
|
|
1511
|
+
โจ Total discovered: ${results.length} items`
|
|
1512
|
+
}]
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
catch (error) {
|
|
1516
|
+
this.logger.error('Failed to discover ATF tests:', error);
|
|
1517
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover ATF tests: ${error}`);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Build ATF Step Configuration
|
|
1522
|
+
* Helper to build step config based on type
|
|
1523
|
+
*/
|
|
1524
|
+
buildATFStepConfig(stepType, userConfig) {
|
|
1525
|
+
const baseConfig = {
|
|
1526
|
+
step_type: stepType,
|
|
1527
|
+
...userConfig
|
|
1528
|
+
};
|
|
1529
|
+
// Add type-specific defaults
|
|
1530
|
+
switch (stepType) {
|
|
1531
|
+
case 'form_submission':
|
|
1532
|
+
return {
|
|
1533
|
+
...baseConfig,
|
|
1534
|
+
table: userConfig.table || 'incident',
|
|
1535
|
+
view: userConfig.view || 'default',
|
|
1536
|
+
field_values: userConfig.field_values || {}
|
|
1537
|
+
};
|
|
1538
|
+
case 'impersonate':
|
|
1539
|
+
return {
|
|
1540
|
+
...baseConfig,
|
|
1541
|
+
user: userConfig.user || 'admin'
|
|
1542
|
+
};
|
|
1543
|
+
case 'assert_condition':
|
|
1544
|
+
return {
|
|
1545
|
+
...baseConfig,
|
|
1546
|
+
condition: userConfig.condition || '',
|
|
1547
|
+
expected_value: userConfig.expected_value || true
|
|
1548
|
+
};
|
|
1549
|
+
case 'open_form':
|
|
1550
|
+
return {
|
|
1551
|
+
...baseConfig,
|
|
1552
|
+
table: userConfig.table || 'incident',
|
|
1553
|
+
sys_id: userConfig.sys_id || '',
|
|
1554
|
+
view: userConfig.view || 'default'
|
|
1555
|
+
};
|
|
1556
|
+
case 'server_script':
|
|
1557
|
+
return {
|
|
1558
|
+
...baseConfig,
|
|
1559
|
+
script: userConfig.script || ''
|
|
1560
|
+
};
|
|
1561
|
+
default:
|
|
1562
|
+
return baseConfig;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
738
1565
|
async run() {
|
|
739
1566
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
740
1567
|
await this.server.connect(transport);
|