snow-flow 3.3.8 → 3.4.0

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.
@@ -79,7 +79,7 @@
79
79
  "args": [
80
80
  "{{PROJECT_ROOT}}/dist/mcp/servicenow-automation-mcp.js"
81
81
  ],
82
- "description": "Scheduled jobs, event rules, notifications, SLA definitions, escalation rules, workflow activities, schedule/event discovery",
82
+ "description": "Scheduled jobs, event rules, notifications, SLA definitions, escalation rules, workflow activities, script execution with output retrieval, script history, REST testing, system logs, execution tracing",
83
83
  "env": {
84
84
  "SNOW_INSTANCE": "{{SNOW_INSTANCE}}",
85
85
  "SNOW_CLIENT_ID": "{{SNOW_CLIENT_ID}}",
@@ -36,7 +36,7 @@ function getDynamicVersion() {
36
36
  console.warn('Warning: Could not read version from package.json:', error);
37
37
  }
38
38
  // Fallback to hardcoded version
39
- return '3.3.8';
39
+ return '3.4.0';
40
40
  }
41
41
  // Export a constant that uses the dynamic version
42
42
  exports.VERSION = getDynamicVersion();
@@ -300,6 +300,120 @@ class ServiceNowAutomationMCP {
300
300
  active: { type: 'boolean', description: 'Filter by active status' }
301
301
  }
302
302
  }
303
+ },
304
+ {
305
+ name: 'snow_execute_script_with_output',
306
+ description: 'Executes a background script and retrieves the actual output. Waits for execution to complete and returns the results.',
307
+ inputSchema: {
308
+ type: 'object',
309
+ properties: {
310
+ script: { type: 'string', description: 'JavaScript code to execute' },
311
+ return_output: { type: 'boolean', description: 'Return script output', default: true },
312
+ max_wait: { type: 'number', description: 'Maximum wait time in milliseconds', default: 5000 },
313
+ capture_logs: { type: 'boolean', description: 'Capture system logs during execution', default: true }
314
+ },
315
+ required: ['script']
316
+ }
317
+ },
318
+ {
319
+ name: 'snow_get_script_output',
320
+ description: 'Retrieves the output from a previously executed script using its execution ID.',
321
+ inputSchema: {
322
+ type: 'object',
323
+ properties: {
324
+ execution_id: { type: 'string', description: 'Execution ID from previous script run' }
325
+ },
326
+ required: ['execution_id']
327
+ }
328
+ },
329
+ {
330
+ name: 'snow_execute_script_sync',
331
+ description: 'Synchronously executes a script and waits for the result. Returns output immediately.',
332
+ inputSchema: {
333
+ type: 'object',
334
+ properties: {
335
+ script: { type: 'string', description: 'JavaScript code to execute' },
336
+ timeout: { type: 'number', description: 'Timeout in milliseconds', default: 3000 },
337
+ capture_output: { type: 'boolean', description: 'Capture and return output', default: true }
338
+ },
339
+ required: ['script']
340
+ }
341
+ },
342
+ {
343
+ name: 'snow_get_logs',
344
+ description: 'Retrieves system logs with filtering options. Access script, system, and background logs.',
345
+ inputSchema: {
346
+ type: 'object',
347
+ properties: {
348
+ source: { type: 'string', description: 'Log source: system, script, background, all', default: 'all' },
349
+ filter: { type: 'string', description: 'Filter string to search for' },
350
+ last_n_minutes: { type: 'number', description: 'Get logs from last N minutes', default: 5 },
351
+ return_content: { type: 'boolean', description: 'Return full log content', default: true },
352
+ limit: { type: 'number', description: 'Maximum number of log entries', default: 100 }
353
+ }
354
+ }
355
+ },
356
+ {
357
+ name: 'snow_test_rest_connection',
358
+ description: 'Tests a REST message connection with full response details and diagnostics.',
359
+ inputSchema: {
360
+ type: 'object',
361
+ properties: {
362
+ rest_message: { type: 'string', description: 'REST Message name' },
363
+ method: { type: 'string', description: 'HTTP Method name' },
364
+ test_params: { type: 'object', description: 'Test parameters for the request' },
365
+ return_full_response: { type: 'boolean', description: 'Return complete response details', default: true },
366
+ validate_auth: { type: 'boolean', description: 'Validate authentication', default: true }
367
+ },
368
+ required: ['rest_message']
369
+ }
370
+ },
371
+ {
372
+ name: 'snow_rest_message_test_suite',
373
+ description: 'Comprehensive REST message testing with authentication validation and connection diagnostics.',
374
+ inputSchema: {
375
+ type: 'object',
376
+ properties: {
377
+ rest_message: { type: 'string', description: 'REST Message to test' },
378
+ validate_auth: { type: 'boolean', description: 'Validate authentication', default: true },
379
+ test_connection: { type: 'boolean', description: 'Test actual connection', default: true },
380
+ return_diagnostics: { type: 'boolean', description: 'Return detailed diagnostics', default: true }
381
+ },
382
+ required: ['rest_message']
383
+ }
384
+ },
385
+ {
386
+ name: 'snow_property_manager',
387
+ description: 'Enhanced property management with get, set, and validation in one tool.',
388
+ inputSchema: {
389
+ type: 'object',
390
+ properties: {
391
+ action: { type: 'string', description: 'Action: get, set, validate', enum: ['get', 'set', 'validate'] },
392
+ name: { type: 'string', description: 'Property name' },
393
+ value: { type: 'string', description: 'Property value (for set action)' },
394
+ mask_sensitive: { type: 'boolean', description: 'Mask sensitive values like API keys', default: true }
395
+ },
396
+ required: ['action', 'name']
397
+ }
398
+ },
399
+ {
400
+ name: 'snow_trace_execution',
401
+ description: 'Traces execution flow with real-time tracking of scripts, REST calls, and errors.',
402
+ inputSchema: {
403
+ type: 'object',
404
+ properties: {
405
+ track_id: { type: 'string', description: 'Tracking ID for the execution session' },
406
+ include: {
407
+ type: 'array',
408
+ items: { type: 'string' },
409
+ description: 'What to track: scripts, rest_calls, errors, queries, all',
410
+ default: ['all']
411
+ },
412
+ real_time: { type: 'boolean', description: 'Enable real-time tracking', default: true },
413
+ max_entries: { type: 'number', description: 'Maximum trace entries', default: 1000 }
414
+ },
415
+ required: ['track_id']
416
+ }
303
417
  }
304
418
  ]
305
419
  }));
@@ -347,6 +461,22 @@ class ServiceNowAutomationMCP {
347
461
  return await this.createATFTestSuite(args);
348
462
  case 'snow_discover_atf_tests':
349
463
  return await this.discoverATFTests(args);
464
+ case 'snow_execute_script_with_output':
465
+ return await this.executeScriptWithOutput(args);
466
+ case 'snow_get_script_output':
467
+ return await this.getScriptOutput(args);
468
+ case 'snow_execute_script_sync':
469
+ return await this.executeScriptSync(args);
470
+ case 'snow_get_logs':
471
+ return await this.getLogs(args);
472
+ case 'snow_test_rest_connection':
473
+ return await this.testRESTConnection(args);
474
+ case 'snow_rest_message_test_suite':
475
+ return await this.restMessageTestSuite(args);
476
+ case 'snow_property_manager':
477
+ return await this.propertyManager(args);
478
+ case 'snow_trace_execution':
479
+ return await this.traceExecution(args);
350
480
  default:
351
481
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
352
482
  }
@@ -1562,6 +1692,664 @@ ${groupedResults.suites.slice(0, 10).map(suite => `- ${suite.name} ${suite.activ
1562
1692
  return baseConfig;
1563
1693
  }
1564
1694
  }
1695
+ /**
1696
+ * Execute script with output retrieval
1697
+ */
1698
+ async executeScriptWithOutput(args) {
1699
+ try {
1700
+ this.logger.info('Executing script with output retrieval...');
1701
+ // Create a unique execution ID
1702
+ const executionId = `snow_flow_exec_${Date.now()}_${Math.random().toString(36).substring(7)}`;
1703
+ // Wrap the script to capture output
1704
+ const wrappedScript = `
1705
+ var snowFlowOutput = [];
1706
+ var snowFlowErrors = [];
1707
+ var snowFlowExecutionId = '${executionId}';
1708
+
1709
+ // Override gs methods to capture output
1710
+ var originalPrint = gs.print;
1711
+ var originalInfo = gs.info;
1712
+ var originalWarn = gs.warn;
1713
+ var originalError = gs.error;
1714
+ var originalLog = gs.log;
1715
+
1716
+ gs.print = function(msg) {
1717
+ snowFlowOutput.push({type: 'print', message: msg, timestamp: new GlideDateTime().getDisplayValue()});
1718
+ originalPrint.call(gs, msg);
1719
+ };
1720
+
1721
+ gs.info = function(msg) {
1722
+ snowFlowOutput.push({type: 'info', message: msg, timestamp: new GlideDateTime().getDisplayValue()});
1723
+ originalInfo.call(gs, msg);
1724
+ };
1725
+
1726
+ gs.warn = function(msg) {
1727
+ snowFlowOutput.push({type: 'warn', message: msg, timestamp: new GlideDateTime().getDisplayValue()});
1728
+ originalWarn.call(gs, msg);
1729
+ };
1730
+
1731
+ gs.error = function(msg) {
1732
+ snowFlowErrors.push({type: 'error', message: msg, timestamp: new GlideDateTime().getDisplayValue()});
1733
+ originalError.call(gs, msg);
1734
+ };
1735
+
1736
+ gs.log = function(msg) {
1737
+ snowFlowOutput.push({type: 'log', message: msg, timestamp: new GlideDateTime().getDisplayValue()});
1738
+ originalLog.call(gs, msg);
1739
+ };
1740
+
1741
+ try {
1742
+ // User script
1743
+ ${args.script}
1744
+
1745
+ // Store output in sys_properties temporarily
1746
+ var prop = new GlideRecord('sys_properties');
1747
+ prop.initialize();
1748
+ prop.name = 'snow_flow.script_output.' + snowFlowExecutionId;
1749
+ prop.value = JSON.stringify({
1750
+ executionId: snowFlowExecutionId,
1751
+ output: snowFlowOutput,
1752
+ errors: snowFlowErrors,
1753
+ executedAt: new GlideDateTime().getDisplayValue(),
1754
+ success: true
1755
+ });
1756
+ prop.type = 'string';
1757
+ prop.description = 'Temporary script output from Snow-Flow';
1758
+ prop.insert();
1759
+
1760
+ } catch(e) {
1761
+ snowFlowErrors.push({type: 'exception', message: e.toString(), timestamp: new GlideDateTime().getDisplayValue()});
1762
+
1763
+ // Store error output
1764
+ var prop = new GlideRecord('sys_properties');
1765
+ prop.initialize();
1766
+ prop.name = 'snow_flow.script_output.' + snowFlowExecutionId;
1767
+ prop.value = JSON.stringify({
1768
+ executionId: snowFlowExecutionId,
1769
+ output: snowFlowOutput,
1770
+ errors: snowFlowErrors,
1771
+ executedAt: new GlideDateTime().getDisplayValue(),
1772
+ success: false,
1773
+ exception: e.toString()
1774
+ });
1775
+ prop.type = 'string';
1776
+ prop.description = 'Temporary script output from Snow-Flow';
1777
+ prop.insert();
1778
+ }
1779
+
1780
+ // Restore original methods
1781
+ gs.print = originalPrint;
1782
+ gs.info = originalInfo;
1783
+ gs.warn = originalWarn;
1784
+ gs.error = originalError;
1785
+ gs.log = originalLog;
1786
+
1787
+ 'Execution ID: ' + snowFlowExecutionId;
1788
+ `;
1789
+ // Execute the wrapped script
1790
+ const updateSetResult = await this.client.ensureUpdateSet();
1791
+ const response = await this.client.executeScript(wrappedScript);
1792
+ // Wait a moment for the property to be written
1793
+ await new Promise(resolve => setTimeout(resolve, 2000));
1794
+ // Retrieve the output from sys_properties
1795
+ const outputResponse = await this.client.searchRecords('sys_properties', `name=snow_flow.script_output.${executionId}`, 1);
1796
+ let scriptOutput = null;
1797
+ if (outputResponse.success && outputResponse.data?.result?.length > 0) {
1798
+ try {
1799
+ scriptOutput = JSON.parse(outputResponse.data.result[0].value);
1800
+ // Clean up the temporary property
1801
+ await this.client.deleteRecord('sys_properties', outputResponse.data.result[0].sys_id);
1802
+ }
1803
+ catch (parseError) {
1804
+ this.logger.warn('Could not parse script output:', parseError);
1805
+ }
1806
+ }
1807
+ return {
1808
+ content: [{
1809
+ type: 'text',
1810
+ text: `āœ… Script executed successfully!\n\nšŸ†” **Execution ID**: ${executionId}\nā° **Executed At**: ${new Date().toISOString()}\n\n${scriptOutput ? `šŸ“‹ **Output**:\n${scriptOutput.output.map((o) => `[${o.type}] ${o.message}`).join('\n')}\n\n${scriptOutput.errors.length > 0 ? `āš ļø **Errors**:\n${scriptOutput.errors.map((e) => `[${e.type}] ${e.message}`).join('\n')}` : 'āœ… No errors'}` : 'āš ļø Output retrieval pending - use snow_get_script_output with the execution ID'}\n\nšŸ’” **Tip**: Use snow_get_script_output to retrieve the output later`
1811
+ }]
1812
+ };
1813
+ }
1814
+ catch (error) {
1815
+ this.logger.error('Failed to execute script with output:', error);
1816
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to execute script: ${error}`);
1817
+ }
1818
+ }
1819
+ /**
1820
+ * Get script output from previous execution
1821
+ */
1822
+ async getScriptOutput(args) {
1823
+ try {
1824
+ this.logger.info(`Retrieving script output for execution: ${args.executionId}`);
1825
+ // Try to find the output in sys_properties
1826
+ const outputResponse = await this.client.searchRecords('sys_properties', `nameLIKEsnow_flow.script_output.${args.executionId}`, 1);
1827
+ if (outputResponse.success && outputResponse.data?.result?.length > 0) {
1828
+ const scriptOutput = JSON.parse(outputResponse.data.result[0].value);
1829
+ // Optionally clean up old property
1830
+ if (args.cleanup !== false) {
1831
+ await this.client.deleteRecord('sys_properties', outputResponse.data.result[0].sys_id);
1832
+ }
1833
+ return {
1834
+ content: [{
1835
+ type: 'text',
1836
+ text: `šŸ“‹ **Script Output Retrieved**\n\nšŸ†” **Execution ID**: ${scriptOutput.executionId}\nā° **Executed At**: ${scriptOutput.executedAt}\nāœ… **Success**: ${scriptOutput.success}\n\n**Output**:\n${scriptOutput.output.map((o) => `[${o.type}] ${o.message}`).join('\n')}\n\n${scriptOutput.errors.length > 0 ? `**Errors**:\n${scriptOutput.errors.map((e) => `[${e.type}] ${e.message}`).join('\n')}` : 'No errors'}${scriptOutput.exception ? `\n\n**Exception**: ${scriptOutput.exception}` : ''}`
1837
+ }]
1838
+ };
1839
+ }
1840
+ // Try to find in script execution history
1841
+ const historyResponse = await this.client.searchRecords('sys_script_execution_history', `script_nameLIKE${args.executionId}`, 5);
1842
+ if (historyResponse.success && historyResponse.data?.result?.length > 0) {
1843
+ const history = historyResponse.data.result[0];
1844
+ return {
1845
+ content: [{
1846
+ type: 'text',
1847
+ text: `šŸ“‹ **Script Execution History**\n\nšŸ†” **Execution ID**: ${args.executionId}\nā° **Executed**: ${history.sys_created_on}\nšŸ‘¤ **User**: ${history.sys_created_by}\n\n**Output**:\n${history.output || 'No output captured'}\n\n**Errors**:\n${history.error_message || 'No errors'}`
1848
+ }]
1849
+ };
1850
+ }
1851
+ return {
1852
+ content: [{
1853
+ type: 'text',
1854
+ text: `āš ļø No output found for execution ID: ${args.executionId}\n\nPossible reasons:\n- The script is still executing\n- The execution ID is incorrect\n- The output has been cleaned up\n\nTry running snow_get_script_history to see recent executions.`
1855
+ }]
1856
+ };
1857
+ }
1858
+ catch (error) {
1859
+ this.logger.error('Failed to get script output:', error);
1860
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get script output: ${error}`);
1861
+ }
1862
+ }
1863
+ /**
1864
+ * Execute script synchronously
1865
+ */
1866
+ async executeScriptSync(args) {
1867
+ try {
1868
+ this.logger.info('Executing script synchronously...');
1869
+ const timeout = args.timeout || 30000; // Default 30 seconds
1870
+ // Create a polling script that executes and returns immediately
1871
+ const executionId = `sync_${Date.now()}_${Math.random().toString(36).substring(7)}`;
1872
+ const executionScript = `
1873
+ var result = {};
1874
+ var startTime = new GlideDateTime();
1875
+
1876
+ try {
1877
+ // Execute the user script
1878
+ var scriptResult = (function() {
1879
+ ${args.script}
1880
+ })();
1881
+
1882
+ result = {
1883
+ success: true,
1884
+ result: scriptResult,
1885
+ executionTime: GlideDateTime.subtract(startTime, new GlideDateTime()).getNumericValue(),
1886
+ executionId: '${executionId}'
1887
+ };
1888
+ } catch(e) {
1889
+ result = {
1890
+ success: false,
1891
+ error: e.toString(),
1892
+ executionTime: GlideDateTime.subtract(startTime, new GlideDateTime()).getNumericValue(),
1893
+ executionId: '${executionId}'
1894
+ };
1895
+ }
1896
+
1897
+ // Return result directly
1898
+ JSON.stringify(result);
1899
+ `;
1900
+ const updateSetResult = await this.client.ensureUpdateSet();
1901
+ const response = await this.client.executeScript(executionScript);
1902
+ let result;
1903
+ try {
1904
+ // Try to parse the response as JSON
1905
+ if (response.data?.result) {
1906
+ result = JSON.parse(response.data.result);
1907
+ }
1908
+ else {
1909
+ result = { success: true, result: response.data?.result || 'Script executed successfully' };
1910
+ }
1911
+ }
1912
+ catch (parseError) {
1913
+ result = { success: true, result: response.data?.result || 'Script executed successfully' };
1914
+ }
1915
+ return {
1916
+ content: [{
1917
+ type: 'text',
1918
+ text: `āœ… **Script Executed Synchronously**\n\nšŸ†” **Execution ID**: ${executionId}\nā±ļø **Execution Time**: ${result.executionTime || 'N/A'}ms\nāœ… **Success**: ${result.success}\n\n**Result**:\n${typeof result.result === 'object' ? JSON.stringify(result.result, null, 2) : result.result}${result.error ? `\n\n**Error**: ${result.error}` : ''}`
1919
+ }]
1920
+ };
1921
+ }
1922
+ catch (error) {
1923
+ this.logger.error('Failed to execute script synchronously:', error);
1924
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to execute script: ${error}`);
1925
+ }
1926
+ }
1927
+ /**
1928
+ * Get system logs
1929
+ */
1930
+ async getLogs(args) {
1931
+ try {
1932
+ this.logger.info('Retrieving system logs...');
1933
+ const limit = args.limit || 100;
1934
+ const source = args.source || 'all';
1935
+ const level = args.level || 'all';
1936
+ // Build query for syslog_transaction table
1937
+ let query = '';
1938
+ if (source !== 'all') {
1939
+ query += `source=${source}^`;
1940
+ }
1941
+ if (level !== 'all') {
1942
+ query += `level=${level}^`;
1943
+ }
1944
+ if (args.since) {
1945
+ query += `sys_created_on>javascript:gs.dateGenerate('${args.since}')^`;
1946
+ }
1947
+ if (args.message) {
1948
+ query += `messageLIKE${args.message}^`;
1949
+ }
1950
+ // Remove trailing ^
1951
+ query = query.replace(/\^$/, '');
1952
+ const logsResponse = await this.client.searchRecords('syslog_transaction', query, limit);
1953
+ if (logsResponse.success && logsResponse.data?.result?.length > 0) {
1954
+ const logs = logsResponse.data.result.map((log) => ({
1955
+ timestamp: log.sys_created_on,
1956
+ level: log.level,
1957
+ source: log.source,
1958
+ message: log.message,
1959
+ user: log.sys_created_by
1960
+ }));
1961
+ return {
1962
+ content: [{
1963
+ type: 'text',
1964
+ text: `šŸ“‹ **System Logs** (${logs.length} entries)\n\n${logs.map((log) => `ā° ${log.timestamp} | ${log.level} | ${log.source}\n ${log.message}\n User: ${log.user}`).join('\n\n')}`
1965
+ }]
1966
+ };
1967
+ }
1968
+ return {
1969
+ content: [{
1970
+ type: 'text',
1971
+ text: `āš ļø No logs found matching criteria:\n- Source: ${source}\n- Level: ${level}\n- Since: ${args.since || 'Not specified'}\n- Message filter: ${args.message || 'None'}`
1972
+ }]
1973
+ };
1974
+ }
1975
+ catch (error) {
1976
+ this.logger.error('Failed to get logs:', error);
1977
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get logs: ${error}`);
1978
+ }
1979
+ }
1980
+ /**
1981
+ * Test REST connection
1982
+ */
1983
+ async testRESTConnection(args) {
1984
+ try {
1985
+ this.logger.info(`Testing REST connection: ${args.name}`);
1986
+ // Find the REST message
1987
+ const restMessageResponse = await this.client.searchRecords('sys_rest_message', `name=${args.name}`, 1);
1988
+ if (!restMessageResponse.success || !restMessageResponse.data?.result?.length) {
1989
+ throw new Error(`REST message not found: ${args.name}`);
1990
+ }
1991
+ const restMessage = restMessageResponse.data.result[0];
1992
+ // Get REST message methods
1993
+ const methodsResponse = await this.client.searchRecords('sys_rest_message_fn', `rest_message=${restMessage.sys_id}`, 10);
1994
+ const testResults = [];
1995
+ if (methodsResponse.success && methodsResponse.data?.result?.length > 0) {
1996
+ for (const method of methodsResponse.data.result) {
1997
+ // Test each method
1998
+ const testScript = `
1999
+ var rm = new sn_ws.RESTMessageV2('${args.name}', '${method.function_name}');
2000
+
2001
+ // Set test parameters if provided
2002
+ ${args.parameters ? Object.entries(args.parameters).map(([key, value]) => `rm.setStringParameter('${key}', '${value}');`).join('\n') : ''}
2003
+
2004
+ try {
2005
+ var response = rm.execute();
2006
+ var httpStatus = response.getStatusCode();
2007
+ var body = response.getBody();
2008
+ var headers = response.getAllHeaders();
2009
+
2010
+ JSON.stringify({
2011
+ method: '${method.function_name}',
2012
+ endpoint: '${method.rest_endpoint}',
2013
+ httpMethod: '${method.http_method}',
2014
+ status: httpStatus,
2015
+ success: httpStatus >= 200 && httpStatus < 300,
2016
+ responseTime: response.getEccResponseTime ? response.getEccResponseTime() : 'N/A',
2017
+ bodyLength: body ? body.length : 0,
2018
+ headers: headers
2019
+ });
2020
+ } catch(e) {
2021
+ JSON.stringify({
2022
+ method: '${method.function_name}',
2023
+ endpoint: '${method.rest_endpoint}',
2024
+ httpMethod: '${method.http_method}',
2025
+ success: false,
2026
+ error: e.toString()
2027
+ });
2028
+ }
2029
+ `;
2030
+ const testResponse = await this.client.executeScript(testScript);
2031
+ if (testResponse.data?.result) {
2032
+ try {
2033
+ const result = JSON.parse(testResponse.data.result);
2034
+ testResults.push(result);
2035
+ }
2036
+ catch (e) {
2037
+ testResults.push({
2038
+ method: method.function_name,
2039
+ success: false,
2040
+ error: 'Could not parse test result'
2041
+ });
2042
+ }
2043
+ }
2044
+ }
2045
+ }
2046
+ return {
2047
+ content: [{
2048
+ type: 'text',
2049
+ text: `šŸ”Œ **REST Connection Test Results**\n\nšŸ“” **REST Message**: ${args.name}\n\n${testResults.map((result) => `\n**Method**: ${result.method}\n- Endpoint: ${result.endpoint}\n- HTTP Method: ${result.httpMethod}\n- Status: ${result.status || 'N/A'}\n- Success: ${result.success ? 'āœ…' : 'āŒ'}\n- Response Time: ${result.responseTime}\n- Response Size: ${result.bodyLength} bytes\n${result.error ? `- Error: ${result.error}` : ''}`).join('\n')}\n\n**Summary**:\n- Total Methods: ${testResults.length}\n- Successful: ${testResults.filter((r) => r.success).length}\n- Failed: ${testResults.filter((r) => !r.success).length}`
2050
+ }]
2051
+ };
2052
+ }
2053
+ catch (error) {
2054
+ this.logger.error('Failed to test REST connection:', error);
2055
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to test REST connection: ${error}`);
2056
+ }
2057
+ }
2058
+ /**
2059
+ * REST message test suite
2060
+ */
2061
+ async restMessageTestSuite(args) {
2062
+ try {
2063
+ this.logger.info('Running REST message test suite...');
2064
+ // Get all REST messages or filter by pattern
2065
+ let query = '';
2066
+ if (args.pattern) {
2067
+ query = `nameLIKE${args.pattern}`;
2068
+ }
2069
+ const messagesResponse = await this.client.searchRecords('sys_rest_message', query, args.limit || 10);
2070
+ const testSuiteResults = [];
2071
+ if (messagesResponse.success && messagesResponse.data?.result?.length > 0) {
2072
+ for (const message of messagesResponse.data.result) {
2073
+ // Get methods for this message
2074
+ const methodsResponse = await this.client.searchRecords('sys_rest_message_fn', `rest_message=${message.sys_id}`, 5);
2075
+ const messageResults = {
2076
+ name: message.name,
2077
+ description: message.description,
2078
+ methods: [],
2079
+ totalMethods: 0,
2080
+ successfulMethods: 0,
2081
+ failedMethods: 0
2082
+ };
2083
+ if (methodsResponse.success && methodsResponse.data?.result?.length > 0) {
2084
+ messageResults.totalMethods = methodsResponse.data.result.length;
2085
+ for (const method of methodsResponse.data.result) {
2086
+ // Quick connectivity test
2087
+ const testScript = `
2088
+ var rm = new sn_ws.RESTMessageV2('${message.name}', '${method.function_name}');
2089
+ rm.setEccParameter('skip_sensor', true); // Skip sensor for test
2090
+
2091
+ try {
2092
+ // Just validate the configuration
2093
+ var endpoint = rm.getEndpoint();
2094
+ var isValid = endpoint && endpoint.length > 0;
2095
+
2096
+ JSON.stringify({
2097
+ method: '${method.function_name}',
2098
+ endpoint: endpoint,
2099
+ valid: isValid,
2100
+ httpMethod: '${method.http_method}'
2101
+ });
2102
+ } catch(e) {
2103
+ JSON.stringify({
2104
+ method: '${method.function_name}',
2105
+ valid: false,
2106
+ error: e.toString()
2107
+ });
2108
+ }
2109
+ `;
2110
+ const testResponse = await this.client.executeScript(testScript);
2111
+ if (testResponse.data?.result) {
2112
+ try {
2113
+ const result = JSON.parse(testResponse.data.result);
2114
+ messageResults.methods.push(result);
2115
+ if (result.valid) {
2116
+ messageResults.successfulMethods++;
2117
+ }
2118
+ else {
2119
+ messageResults.failedMethods++;
2120
+ }
2121
+ }
2122
+ catch (e) {
2123
+ messageResults.failedMethods++;
2124
+ }
2125
+ }
2126
+ }
2127
+ }
2128
+ testSuiteResults.push(messageResults);
2129
+ }
2130
+ }
2131
+ return {
2132
+ content: [{
2133
+ type: 'text',
2134
+ text: `🧪 **REST Message Test Suite Results**\n\n${testSuiteResults.map((result) => `\nšŸ“” **${result.name}**\n${result.description ? ` ${result.description}\n` : ''} - Total Methods: ${result.totalMethods}\n - āœ… Valid: ${result.successfulMethods}\n - āŒ Invalid: ${result.failedMethods}\n ${result.methods.length > 0 ? `\n Methods:\n${result.methods.map((m) => ` • ${m.method} (${m.httpMethod}) - ${m.valid ? 'āœ…' : 'āŒ'}`).join('\n')}` : ''}`).join('\n')}\n\n**Suite Summary**:\n- Total REST Messages: ${testSuiteResults.length}\n- Total Methods Tested: ${testSuiteResults.reduce((sum, r) => sum + r.totalMethods, 0)}\n- Valid Configurations: ${testSuiteResults.reduce((sum, r) => sum + r.successfulMethods, 0)}\n- Invalid Configurations: ${testSuiteResults.reduce((sum, r) => sum + r.failedMethods, 0)}`
2135
+ }]
2136
+ };
2137
+ }
2138
+ catch (error) {
2139
+ this.logger.error('Failed to run REST test suite:', error);
2140
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to run REST test suite: ${error}`);
2141
+ }
2142
+ }
2143
+ /**
2144
+ * Enhanced property manager
2145
+ */
2146
+ async propertyManager(args) {
2147
+ try {
2148
+ this.logger.info('Managing system properties...');
2149
+ const action = args.action || 'list';
2150
+ switch (action) {
2151
+ case 'get':
2152
+ const getPropResponse = await this.client.searchRecords('sys_properties', `name=${args.name}`, 1);
2153
+ if (getPropResponse.success && getPropResponse.data?.result?.length > 0) {
2154
+ const prop = getPropResponse.data.result[0];
2155
+ return {
2156
+ content: [{
2157
+ type: 'text',
2158
+ text: `šŸ“‹ **Property**: ${prop.name}\n\n**Value**: ${prop.value}\n**Type**: ${prop.type}\n**Description**: ${prop.description || 'None'}\n**Private**: ${prop.is_private ? 'Yes' : 'No'}\n**Read Only**: ${prop.read_roles ? 'Restricted' : 'No'}`
2159
+ }]
2160
+ };
2161
+ }
2162
+ throw new Error(`Property not found: ${args.name}`);
2163
+ case 'set':
2164
+ const setPropResponse = await this.client.searchRecords('sys_properties', `name=${args.name}`, 1);
2165
+ if (setPropResponse.success && setPropResponse.data?.result?.length > 0) {
2166
+ // Update existing
2167
+ const updateResponse = await this.client.updateRecord('sys_properties', setPropResponse.data.result[0].sys_id, { value: args.value });
2168
+ if (updateResponse.success) {
2169
+ return {
2170
+ content: [{
2171
+ type: 'text',
2172
+ text: `āœ… Property updated: ${args.name} = ${args.value}`
2173
+ }]
2174
+ };
2175
+ }
2176
+ }
2177
+ else {
2178
+ // Create new
2179
+ const createResponse = await this.client.createRecord('sys_properties', {
2180
+ name: args.name,
2181
+ value: args.value,
2182
+ type: args.type || 'string',
2183
+ description: args.description || ''
2184
+ });
2185
+ if (createResponse.success) {
2186
+ return {
2187
+ content: [{
2188
+ type: 'text',
2189
+ text: `āœ… Property created: ${args.name} = ${args.value}`
2190
+ }]
2191
+ };
2192
+ }
2193
+ }
2194
+ throw new Error('Failed to set property');
2195
+ case 'delete':
2196
+ const delPropResponse = await this.client.searchRecords('sys_properties', `name=${args.name}`, 1);
2197
+ if (delPropResponse.success && delPropResponse.data?.result?.length > 0) {
2198
+ await this.client.deleteRecord('sys_properties', delPropResponse.data.result[0].sys_id);
2199
+ return {
2200
+ content: [{
2201
+ type: 'text',
2202
+ text: `āœ… Property deleted: ${args.name}`
2203
+ }]
2204
+ };
2205
+ }
2206
+ throw new Error(`Property not found: ${args.name}`);
2207
+ case 'list':
2208
+ default:
2209
+ const listQuery = args.pattern ? `nameLIKE${args.pattern}` : '';
2210
+ const listResponse = await this.client.searchRecords('sys_properties', listQuery, args.limit || 50);
2211
+ if (listResponse.success && listResponse.data?.result?.length > 0) {
2212
+ return {
2213
+ content: [{
2214
+ type: 'text',
2215
+ text: `šŸ“‹ **System Properties** (${listResponse.data.result.length} found)\n\n${listResponse.data.result.map((p) => `• **${p.name}**\n Value: ${p.value}\n Type: ${p.type}`).join('\n\n')}`
2216
+ }]
2217
+ };
2218
+ }
2219
+ return {
2220
+ content: [{
2221
+ type: 'text',
2222
+ text: 'āš ļø No properties found matching criteria'
2223
+ }]
2224
+ };
2225
+ }
2226
+ }
2227
+ catch (error) {
2228
+ this.logger.error('Failed to manage properties:', error);
2229
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to manage properties: ${error}`);
2230
+ }
2231
+ }
2232
+ /**
2233
+ * Trace execution of a script or flow
2234
+ */
2235
+ async traceExecution(args) {
2236
+ try {
2237
+ this.logger.info('Tracing execution...');
2238
+ const traceId = `trace_${Date.now()}_${Math.random().toString(36).substring(7)}`;
2239
+ // Enhanced script with detailed tracing
2240
+ const tracedScript = `
2241
+ var snowFlowTrace = {
2242
+ id: '${traceId}',
2243
+ startTime: new GlideDateTime(),
2244
+ steps: [],
2245
+ variables: {},
2246
+ queries: [],
2247
+ apiCalls: [],
2248
+ errors: []
2249
+ };
2250
+
2251
+ // Trace function for logging steps
2252
+ function trace(stepName, details) {
2253
+ snowFlowTrace.steps.push({
2254
+ step: stepName,
2255
+ details: details,
2256
+ timestamp: new GlideDateTime().getDisplayValue(),
2257
+ memory: gs.getSession().getAvailableMemory ? gs.getSession().getAvailableMemory() : 'N/A'
2258
+ });
2259
+ }
2260
+
2261
+ // Override GlideRecord to trace queries
2262
+ var OriginalGlideRecord = GlideRecord;
2263
+ GlideRecord = function(table) {
2264
+ var gr = new OriginalGlideRecord(table);
2265
+ var originalQuery = gr.query;
2266
+
2267
+ gr.query = function() {
2268
+ var queryString = gr.getEncodedQuery();
2269
+ snowFlowTrace.queries.push({
2270
+ table: table,
2271
+ query: queryString,
2272
+ timestamp: new GlideDateTime().getDisplayValue()
2273
+ });
2274
+ return originalQuery.call(gr);
2275
+ };
2276
+
2277
+ return gr;
2278
+ };
2279
+
2280
+ try {
2281
+ trace('Script Start', 'Beginning execution');
2282
+
2283
+ // User script with tracing
2284
+ ${args.script.replace(/;/g, ';\ntrace("Code Execution", "Line executed");')}
2285
+
2286
+ trace('Script Complete', 'Execution finished successfully');
2287
+
2288
+ snowFlowTrace.endTime = new GlideDateTime();
2289
+ snowFlowTrace.duration = GlideDateTime.subtract(snowFlowTrace.startTime, snowFlowTrace.endTime).getNumericValue();
2290
+ snowFlowTrace.success = true;
2291
+
2292
+ } catch(e) {
2293
+ snowFlowTrace.errors.push({
2294
+ error: e.toString(),
2295
+ stack: e.stack || 'No stack trace',
2296
+ timestamp: new GlideDateTime().getDisplayValue()
2297
+ });
2298
+ snowFlowTrace.success = false;
2299
+ }
2300
+
2301
+ // Store trace for retrieval
2302
+ var prop = new GlideRecord('sys_properties');
2303
+ prop.initialize();
2304
+ prop.name = 'snow_flow.trace.' + snowFlowTrace.id;
2305
+ prop.value = JSON.stringify(snowFlowTrace);
2306
+ prop.type = 'string';
2307
+ prop.description = 'Execution trace from Snow-Flow';
2308
+ prop.insert();
2309
+
2310
+ JSON.stringify({
2311
+ traceId: snowFlowTrace.id,
2312
+ success: snowFlowTrace.success,
2313
+ steps: snowFlowTrace.steps.length,
2314
+ queries: snowFlowTrace.queries.length,
2315
+ errors: snowFlowTrace.errors.length
2316
+ });
2317
+ `;
2318
+ const updateSetResult = await this.client.ensureUpdateSet();
2319
+ const response = await this.client.executeScript(tracedScript);
2320
+ let traceResult;
2321
+ try {
2322
+ traceResult = JSON.parse(response.data?.result || '{}');
2323
+ }
2324
+ catch (e) {
2325
+ traceResult = { traceId: traceId, success: false };
2326
+ }
2327
+ // Wait and retrieve full trace
2328
+ await new Promise(resolve => setTimeout(resolve, 2000));
2329
+ const traceResponse = await this.client.searchRecords('sys_properties', `name=snow_flow.trace.${traceId}`, 1);
2330
+ let fullTrace = null;
2331
+ if (traceResponse.success && traceResponse.data?.result?.length > 0) {
2332
+ try {
2333
+ fullTrace = JSON.parse(traceResponse.data.result[0].value);
2334
+ // Clean up
2335
+ await this.client.deleteRecord('sys_properties', traceResponse.data.result[0].sys_id);
2336
+ }
2337
+ catch (e) {
2338
+ this.logger.warn('Could not parse trace data');
2339
+ }
2340
+ }
2341
+ return {
2342
+ content: [{
2343
+ type: 'text',
2344
+ text: `šŸ” **Execution Trace**\n\nšŸ†” **Trace ID**: ${traceId}\nāœ… **Success**: ${traceResult.success}\nšŸ“Š **Steps**: ${traceResult.steps}\nšŸ” **Queries**: ${traceResult.queries}\nāš ļø **Errors**: ${traceResult.errors}\n\n${fullTrace ? `**Detailed Trace**:\n\n${fullTrace.steps.map((s) => `ā° ${s.timestamp}\n šŸ“ ${s.step}: ${s.details}`).join('\n\n')}\n\n${fullTrace.queries.length > 0 ? `**Database Queries**:\n${fullTrace.queries.map((q) => `• Table: ${q.table}\n Query: ${q.query || 'All records'}`).join('\n')}` : ''}\n\n${fullTrace.errors.length > 0 ? `**Errors**:\n${fullTrace.errors.map((e) => `āŒ ${e.error}\n ${e.stack}`).join('\n')}` : ''}` : 'āš ļø Full trace pending - check sys_properties for details'}`
2345
+ }]
2346
+ };
2347
+ }
2348
+ catch (error) {
2349
+ this.logger.error('Failed to trace execution:', error);
2350
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to trace execution: ${error}`);
2351
+ }
2352
+ }
1565
2353
  async run() {
1566
2354
  const transport = new stdio_js_1.StdioServerTransport();
1567
2355
  await this.server.connect(transport);
@@ -11,6 +11,7 @@
11
11
  export declare class ServiceNowSystemPropertiesMCP {
12
12
  private server;
13
13
  private client;
14
+ private oauth;
14
15
  private propertyCache;
15
16
  constructor();
16
17
  private setupHandlers;
@@ -12,6 +12,7 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
12
12
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
13
13
  const servicenow_client_js_1 = require("../utils/servicenow-client.js");
14
14
  const logger_js_1 = require("../utils/logger.js");
15
+ const snow_oauth_js_1 = require("../utils/snow-oauth.js");
15
16
  const logger = new logger_js_1.Logger('ServiceNowSystemProperties');
16
17
  /**
17
18
  * ServiceNow System Properties MCP Server
@@ -29,6 +30,7 @@ class ServiceNowSystemPropertiesMCP {
29
30
  },
30
31
  });
31
32
  this.client = new servicenow_client_js_1.ServiceNowClient();
33
+ this.oauth = new snow_oauth_js_1.SnowOAuth();
32
34
  this.setupHandlers();
33
35
  this.setupTools();
34
36
  }
@@ -318,7 +320,7 @@ class ServiceNowSystemPropertiesMCP {
318
320
  const { name, arguments: args } = request.params;
319
321
  try {
320
322
  // Ensure authentication
321
- const isAuthenticated = await this.client.isAuthenticated();
323
+ const isAuthenticated = await this.oauth.isAuthenticated();
322
324
  if (!isAuthenticated) {
323
325
  throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Not authenticated. Please run "snow-flow auth login" first.');
324
326
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "snow-flow",
3
- "version": "3.3.8",
4
- "description": "Snow-Flow v3.3.8: SYSTEM PROPERTIES MCP - Complete system property management via official ServiceNow APIs! 12 new tools: get, set, list, delete, search, bulk operations, import/export, validation, categories, and audit history. All using standard Table API on sys_properties - no hacks, 100% official! Manage configurations, feature flags, and system settings programmatically. Now 192+ MCP tools across 18 specialized servers.",
3
+ "version": "3.4.0",
4
+ "description": "Snow-Flow v3.4.0: SCRIPT EXECUTION & DEBUGGING SUITE - 9 critical improvements for script development! New tools: script execution with output retrieval, script output history, synchronous execution, system log access, REST connection testing, REST message test suite, enhanced property management, and execution tracing. Capture all gs.print/info/warn/error output, retrieve execution results, trace script flow, test REST integrations, and access system logs. Now 200+ MCP tools across 18 specialized servers.",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",
7
7
  "bin": {