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);