snow-flow 2.9.0 → 2.9.5

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.
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+
3
+ echo "🔍 Checking MCP Server Resources"
4
+ echo "================================="
5
+ echo ""
6
+
7
+ # Count MCP processes
8
+ echo "📊 MCP Process Count:"
9
+ MCP_COUNT=$(ps aux | grep -E "mcp|servicenow.*mcp" | grep -v grep | wc -l)
10
+ echo " Active MCP processes: $MCP_COUNT"
11
+ echo ""
12
+
13
+ # Show MCP processes with memory usage
14
+ echo "💾 MCP Process Memory Usage:"
15
+ ps aux | grep -E "mcp|servicenow.*mcp" | grep -v grep | awk '{printf " PID: %5s | Memory: %6s MB | CPU: %5s%% | Process: %s\n", $2, int($6/1024), $3, substr($0, index($0,$11))}'
16
+ echo ""
17
+
18
+ # Total memory used by MCP
19
+ echo "📈 Total Memory Statistics:"
20
+ TOTAL_MEM=$(ps aux | grep -E "mcp|servicenow.*mcp" | grep -v grep | awk '{sum += $6} END {print int(sum/1024)}')
21
+ echo " Total MCP memory usage: ${TOTAL_MEM:-0} MB"
22
+
23
+ # System memory
24
+ if [[ "$OSTYPE" == "darwin"* ]]; then
25
+ # macOS
26
+ TOTAL_SYSTEM_MEM=$(sysctl -n hw.memsize | awk '{print int($1/1024/1024)}')
27
+ echo " Total system memory: $TOTAL_SYSTEM_MEM MB"
28
+ else
29
+ # Linux
30
+ TOTAL_SYSTEM_MEM=$(free -m | awk '/^Mem:/{print $2}')
31
+ echo " Total system memory: $TOTAL_SYSTEM_MEM MB"
32
+ fi
33
+
34
+ echo ""
35
+ echo "⚠️ Warning Thresholds:"
36
+ if [ "$MCP_COUNT" -gt 10 ]; then
37
+ echo " ❌ Too many MCP processes ($MCP_COUNT > 10) - This will cause timeouts!"
38
+ echo " 💡 Run: pkill -f mcp"
39
+ else
40
+ echo " ✅ MCP process count OK ($MCP_COUNT <= 10)"
41
+ fi
42
+
43
+ if [ "${TOTAL_MEM:-0}" -gt 1500 ]; then
44
+ echo " ❌ High memory usage (${TOTAL_MEM}MB > 1500MB) - This will cause timeouts!"
45
+ echo " 💡 Run: npm run cleanup-mcp"
46
+ else
47
+ echo " ✅ Memory usage OK (${TOTAL_MEM:-0}MB <= 1500MB)"
48
+ fi
49
+
50
+ echo ""
51
+ echo "🔧 Node.js Processes:"
52
+ NODE_COUNT=$(ps aux | grep -E "node.*snow|node.*servicenow" | grep -v grep | wc -l)
53
+ echo " Active Node processes: $NODE_COUNT"
54
+
55
+ # Check for zombie processes
56
+ echo ""
57
+ echo "👻 Zombie/Defunct Processes:"
58
+ ZOMBIE_COUNT=$(ps aux | grep defunct | grep -v grep | wc -l)
59
+ if [ "$ZOMBIE_COUNT" -gt 0 ]; then
60
+ echo " ⚠️ Found $ZOMBIE_COUNT zombie processes"
61
+ ps aux | grep defunct | grep -v grep
62
+ else
63
+ echo " ✅ No zombie processes found"
64
+ fi
65
+
66
+ echo ""
67
+ echo "📋 Recommendations:"
68
+ echo ""
69
+
70
+ if [ "$MCP_COUNT" -gt 10 ] || [ "${TOTAL_MEM:-0}" -gt 1500 ]; then
71
+ echo " 🚨 RESOURCE ISSUES DETECTED!"
72
+ echo ""
73
+ echo " Quick fix:"
74
+ echo " 1. pkill -f mcp"
75
+ echo " 2. npm run cleanup-mcp"
76
+ echo " 3. npm run mcp:start"
77
+ echo ""
78
+ echo " Permanent fix:"
79
+ echo " 1. Add to .env:"
80
+ echo " SNOW_MAX_MCP_SERVERS=5"
81
+ echo " SNOW_MCP_MEMORY_LIMIT=200"
82
+ echo " 2. Restart snow-flow"
83
+ else
84
+ echo " ✅ Resources look healthy"
85
+ fi
@@ -197,7 +197,7 @@ const ConfigSchema = zod_1.z.object({
197
197
  password: zod_1.z.string().optional(),
198
198
  authType: zod_1.z.enum(['oauth', 'basic']).default('oauth'),
199
199
  apiVersion: zod_1.z.string().default('now'),
200
- timeout: zod_1.z.number().min(5000).default(60000),
200
+ timeout: zod_1.z.number().min(5000).default(120000), // Increased from 60s to 120s
201
201
  retryConfig: zod_1.z
202
202
  .object({
203
203
  maxRetries: zod_1.z.number().min(0).max(10).default(3),
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP On-Demand Proxy
4
+ * Routes MCP requests to the appropriate server, starting it if needed
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=mcp-on-demand-proxy.d.ts.map
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * MCP On-Demand Proxy
5
+ * Routes MCP requests to the appropriate server, starting it if needed
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
9
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
10
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
11
+ const mcp_on_demand_manager_js_1 = require("../utils/mcp-on-demand-manager.js");
12
+ const logger_js_1 = require("../utils/logger.js");
13
+ const logger = new logger_js_1.Logger('MCPProxy');
14
+ // Tool to server mapping
15
+ const TOOL_SERVER_MAP = {
16
+ // Operations tools
17
+ 'snow_query_table': 'servicenow-operations',
18
+ 'snow_query_incidents': 'servicenow-operations',
19
+ 'snow_create_incident': 'servicenow-operations',
20
+ 'snow_update_incident': 'servicenow-operations',
21
+ 'snow_query_users': 'servicenow-operations',
22
+ 'snow_create_user': 'servicenow-operations',
23
+ // Deployment tools
24
+ 'snow_create_widget': 'servicenow-deployment',
25
+ 'snow_deploy_widget': 'servicenow-deployment',
26
+ 'snow_create_flow': 'servicenow-deployment',
27
+ // ML tools
28
+ 'ml_train_incident_classifier': 'servicenow-machine-learning',
29
+ 'ml_classify_incident': 'servicenow-machine-learning',
30
+ 'ml_train_change_risk': 'servicenow-machine-learning',
31
+ 'ml_predict_change_risk': 'servicenow-machine-learning',
32
+ 'ml_train_anomaly_detector': 'servicenow-machine-learning',
33
+ 'ml_detect_anomalies': 'servicenow-machine-learning',
34
+ // Update Set tools
35
+ 'snow_create_update_set': 'servicenow-update-set',
36
+ 'snow_get_current_update_set': 'servicenow-update-set',
37
+ 'snow_commit_update_set': 'servicenow-update-set',
38
+ // Platform Development tools
39
+ 'snow_create_business_rule': 'servicenow-platform-development',
40
+ 'snow_create_script_include': 'servicenow-platform-development',
41
+ 'snow_create_client_script': 'servicenow-platform-development',
42
+ // Integration tools
43
+ 'snow_discover_rest_endpoints': 'servicenow-integration',
44
+ 'snow_create_rest_endpoint': 'servicenow-integration',
45
+ 'snow_create_transform_map': 'servicenow-integration',
46
+ // Automation tools
47
+ 'snow_create_scheduled_job': 'servicenow-automation',
48
+ 'snow_create_event_rule': 'servicenow-automation',
49
+ 'snow_create_workflow': 'servicenow-automation',
50
+ // Security tools
51
+ 'snow_create_security_rule': 'servicenow-security-compliance',
52
+ 'snow_scan_security': 'servicenow-security-compliance',
53
+ 'snow_check_compliance': 'servicenow-security-compliance',
54
+ // Reporting tools
55
+ 'snow_create_report': 'servicenow-reporting-analytics',
56
+ 'snow_create_dashboard': 'servicenow-reporting-analytics',
57
+ 'snow_get_kpis': 'servicenow-reporting-analytics',
58
+ // Intelligent tools
59
+ 'snow_batch_api': 'servicenow-intelligent',
60
+ 'snow_analyze_query': 'servicenow-intelligent',
61
+ 'snow_predict_change_impact': 'servicenow-intelligent',
62
+ // Snow-Flow tools
63
+ 'swarm_init': 'snow-flow',
64
+ 'agent_spawn': 'snow-flow',
65
+ 'task_orchestrate': 'snow-flow',
66
+ 'memory_usage': 'snow-flow',
67
+ 'neural_status': 'snow-flow',
68
+ 'task_categorize': 'snow-flow'
69
+ };
70
+ class MCPOnDemandProxy {
71
+ constructor() {
72
+ this.activeServers = new Map();
73
+ this.server = new index_js_1.Server({
74
+ name: 'mcp-on-demand-proxy',
75
+ version: '1.0.0',
76
+ }, {
77
+ capabilities: {
78
+ tools: {},
79
+ },
80
+ });
81
+ this.manager = mcp_on_demand_manager_js_1.MCPOnDemandManager.getInstance();
82
+ this.setupHandlers();
83
+ }
84
+ setupHandlers() {
85
+ // List all available tools from all servers
86
+ this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
87
+ logger.info('📋 Listing all available tools (servers will start on demand)');
88
+ // Return a comprehensive list of all tools
89
+ // In production, this could be loaded from a configuration file
90
+ const tools = Object.keys(TOOL_SERVER_MAP).map(toolName => ({
91
+ name: toolName,
92
+ description: `Tool ${toolName} (server starts on demand)`,
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {}
96
+ }
97
+ }));
98
+ return { tools };
99
+ });
100
+ // Handle tool calls by routing to appropriate server
101
+ this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
102
+ const { name: toolName, arguments: args } = request.params;
103
+ logger.info(`🔧 Tool requested: ${toolName}`);
104
+ // Find which server handles this tool
105
+ const serverName = TOOL_SERVER_MAP[toolName];
106
+ if (!serverName) {
107
+ throw new Error(`Unknown tool: ${toolName}`);
108
+ }
109
+ // Get or start the server
110
+ logger.info(`🚀 Getting server: ${serverName} for tool: ${toolName}`);
111
+ try {
112
+ const serverProcess = await this.manager.getServer(serverName);
113
+ // Forward the request to the actual server
114
+ // This is simplified - in production you'd use proper IPC
115
+ return await this.forwardToServer(serverName, toolName, args);
116
+ }
117
+ catch (error) {
118
+ logger.error(`Failed to handle tool ${toolName}:`, error);
119
+ throw error;
120
+ }
121
+ });
122
+ // Graceful shutdown
123
+ process.on('SIGINT', async () => {
124
+ logger.info('Shutting down MCP proxy...');
125
+ await this.manager.stopAll();
126
+ process.exit(0);
127
+ });
128
+ process.on('SIGTERM', async () => {
129
+ logger.info('Shutting down MCP proxy...');
130
+ await this.manager.stopAll();
131
+ process.exit(0);
132
+ });
133
+ }
134
+ /**
135
+ * Forward request to actual MCP server
136
+ * In production, this would use proper IPC/stdio communication
137
+ */
138
+ async forwardToServer(serverName, toolName, args) {
139
+ // For now, we'll use a simple exec approach
140
+ // In production, you'd maintain persistent connections
141
+ try {
142
+ // Create a temporary request file
143
+ const request = {
144
+ jsonrpc: '2.0',
145
+ method: 'tools/call',
146
+ params: {
147
+ name: toolName,
148
+ arguments: args
149
+ },
150
+ id: Date.now()
151
+ };
152
+ // This is a simplified example - in production use proper IPC
153
+ logger.info(`Forwarding ${toolName} to ${serverName}`);
154
+ // Return a mock response for now
155
+ return {
156
+ content: [{
157
+ type: 'text',
158
+ text: `Tool ${toolName} executed via on-demand server ${serverName}`
159
+ }]
160
+ };
161
+ }
162
+ catch (error) {
163
+ logger.error(`Failed to forward to ${serverName}:`, error);
164
+ throw error;
165
+ }
166
+ }
167
+ async run() {
168
+ const transport = new stdio_js_1.StdioServerTransport();
169
+ await this.server.connect(transport);
170
+ logger.info('🎯 MCP On-Demand Proxy started');
171
+ logger.info('📊 Servers will start automatically when tools are used');
172
+ // Log status periodically
173
+ setInterval(() => {
174
+ const status = this.manager.getStatus();
175
+ if (status.running > 0) {
176
+ logger.info(`📊 Status: ${status.running} running, ${status.stopped} stopped`);
177
+ }
178
+ }, 60000).unref();
179
+ }
180
+ }
181
+ // Start the proxy
182
+ const proxy = new MCPOnDemandProxy();
183
+ proxy.run().catch((error) => {
184
+ logger.error('Failed to start MCP proxy:', error);
185
+ process.exit(1);
186
+ });
187
+ //# sourceMappingURL=mcp-on-demand-proxy.js.map
@@ -15,6 +15,7 @@ const scope_manager_js_1 = require("../managers/scope-manager.js");
15
15
  const global_scope_strategy_js_1 = require("../strategies/global-scope-strategy.js");
16
16
  const artifact_tracker_js_1 = require("../utils/artifact-tracker.js");
17
17
  const servicenow_id_generator_js_1 = require("../utils/servicenow-id-generator.js");
18
+ const deployment_auth_fix_js_1 = require("../utils/deployment-auth-fix.js");
18
19
  const fs_1 = require("fs");
19
20
  const path_1 = require("path");
20
21
  class ServiceNowDeploymentMCP {
@@ -30,6 +31,7 @@ class ServiceNowDeploymentMCP {
30
31
  this.client = new servicenow_client_js_1.ServiceNowClient();
31
32
  this.oauth = new snow_oauth_js_1.ServiceNowOAuth();
32
33
  this.logger = new logger_js_1.Logger('ServiceNowDeploymentMCP');
34
+ this.deploymentAuthManager = new deployment_auth_fix_js_1.DeploymentAuthManager();
33
35
  // Initialize global scope management
34
36
  this.scopeManager = new scope_manager_js_1.ScopeManager({
35
37
  defaultScope: global_scope_strategy_js_1.ScopeType.GLOBAL,
@@ -472,6 +474,45 @@ class ServiceNowDeploymentMCP {
472
474
  updateSetName: updateSetName
473
475
  };
474
476
  }
477
+ /**
478
+ * Create a record with automatic 403 error recovery
479
+ * Will attempt to refresh token and retry once if 403 error occurs
480
+ */
481
+ async createRecordWithRetry(table, data) {
482
+ try {
483
+ // First attempt
484
+ return await this.client.createRecord(table, data);
485
+ }
486
+ catch (error) {
487
+ // Check if it's a 403 error
488
+ if (error.response?.status === 403 || error.message?.includes('403')) {
489
+ this.logger.warn('Got 403 error, attempting token refresh and retry...');
490
+ // Refresh token
491
+ const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
492
+ if (refreshResult.success && refreshResult.accessToken) {
493
+ // Client will use the new token automatically from unified auth store
494
+ // No need to call authenticate - the client reads from auth store
495
+ // Retry the operation
496
+ try {
497
+ this.logger.info('Retrying operation with refreshed token...');
498
+ return await this.client.createRecord(table, data);
499
+ }
500
+ catch (retryError) {
501
+ this.logger.error('Retry failed after token refresh:', retryError);
502
+ throw retryError;
503
+ }
504
+ }
505
+ else {
506
+ this.logger.error('Failed to refresh token for retry');
507
+ throw error;
508
+ }
509
+ }
510
+ else {
511
+ // Not a 403 error, just re-throw
512
+ throw error;
513
+ }
514
+ }
515
+ }
475
516
  /**
476
517
  * Ensure artifact is tracked in current Update Set
477
518
  */
@@ -537,17 +578,26 @@ class ServiceNowDeploymentMCP {
537
578
  }
538
579
  async deployWidget(args) {
539
580
  try {
540
- // Check authentication first
541
- const isAuth = await this.oauth.isAuthenticated();
542
- if (!isAuth) {
543
- return {
544
- content: [
545
- {
546
- type: 'text',
547
- text: '❌ Not authenticated with ServiceNow.\n\nPlease run: snow-flow auth login\n\nOr configure your .env file with ServiceNow OAuth credentials.',
548
- },
549
- ],
550
- };
581
+ // Enhanced authentication check with token refresh for deployment
582
+ const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
583
+ if (!authResult.isValid) {
584
+ this.logger.error('Deployment authentication failed:', authResult.error);
585
+ // If auth failed, try to refresh token
586
+ const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
587
+ if (!refreshResult.success) {
588
+ return {
589
+ content: [
590
+ {
591
+ type: 'text',
592
+ text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
593
+ },
594
+ ],
595
+ };
596
+ }
597
+ }
598
+ // Warn if token may lack write permissions
599
+ if (!authResult.hasWriteScope) {
600
+ this.logger.warn('⚠️ Token may lack write permissions, deployment might fail with 403');
551
601
  }
552
602
  this.logger.info('Deploying widget to ServiceNow', { name: args.name });
553
603
  // ENHANCED: Mandatory Update Set management with auto-activation
@@ -695,7 +745,7 @@ class ServiceNowDeploymentMCP {
695
745
  // Strategy 2: Direct table record creation (fallback)
696
746
  try {
697
747
  this.logger.info('🔄 Attempting fallback: Direct table record creation');
698
- result = await this.client.createRecord('sp_widget', {
748
+ result = await this.createRecordWithRetry('sp_widget', {
699
749
  name: args.name,
700
750
  id: args.name,
701
751
  title: args.title,
@@ -1069,17 +1119,26 @@ Use \`snow_deployment_debug\` for more information about this session.`,
1069
1119
  */
1070
1120
  async deployPortalPage(args) {
1071
1121
  try {
1072
- // Check authentication first
1073
- const isAuth = await this.oauth.isAuthenticated();
1074
- if (!isAuth) {
1075
- return {
1076
- content: [
1077
- {
1078
- type: 'text',
1079
- text: '❌ Not authenticated with ServiceNow.\n\nPlease run: snow-flow auth login\n\nOr configure your .env file with ServiceNow OAuth credentials.',
1080
- },
1081
- ],
1082
- };
1122
+ // Enhanced authentication check with token refresh for deployment
1123
+ const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
1124
+ if (!authResult.isValid) {
1125
+ this.logger.error('Deployment authentication failed:', authResult.error);
1126
+ // If auth failed, try to refresh token
1127
+ const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
1128
+ if (!refreshResult.success) {
1129
+ return {
1130
+ content: [
1131
+ {
1132
+ type: 'text',
1133
+ text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
1134
+ },
1135
+ ],
1136
+ };
1137
+ }
1138
+ }
1139
+ // Warn if token may lack write permissions
1140
+ if (!authResult.hasWriteScope) {
1141
+ this.logger.warn('⚠️ Token may lack write permissions, deployment might fail with 403');
1083
1142
  }
1084
1143
  this.logger.info('Deploying portal page to ServiceNow', { name: args.page_id });
1085
1144
  // Ensure Update Set is active
@@ -1124,7 +1183,7 @@ Use \`snow_deployment_debug\` for more information about this session.`,
1124
1183
  let pageResult;
1125
1184
  try {
1126
1185
  // Create the page record
1127
- pageResult = await this.client.createRecord('sp_page', {
1186
+ pageResult = await this.createRecordWithRetry('sp_page', {
1128
1187
  id: args.page_id,
1129
1188
  title: args.title,
1130
1189
  short_description: args.description || `Portal page created by Snow-Flow`,
@@ -1512,17 +1571,26 @@ ${args.widgets && args.widgets.length > 0 ? args.widgets.map((w, i) => `
1512
1571
  }
1513
1572
  async deployFlow(args) {
1514
1573
  try {
1515
- // Check authentication first
1516
- const isAuth = await this.oauth.isAuthenticated();
1517
- if (!isAuth) {
1518
- return {
1519
- content: [
1520
- {
1521
- type: 'text',
1522
- text: '❌ Not authenticated with ServiceNow.\n\nPlease run: snow-flow auth login\n\nOr configure your .env file with ServiceNow OAuth credentials.',
1523
- },
1524
- ],
1525
- };
1574
+ // Enhanced authentication check with token refresh for deployment
1575
+ const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
1576
+ if (!authResult.isValid) {
1577
+ this.logger.error('Deployment authentication failed:', authResult.error);
1578
+ // If auth failed, try to refresh token
1579
+ const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
1580
+ if (!refreshResult.success) {
1581
+ return {
1582
+ content: [
1583
+ {
1584
+ type: 'text',
1585
+ text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
1586
+ },
1587
+ ],
1588
+ };
1589
+ }
1590
+ }
1591
+ // Warn if token may lack write permissions
1592
+ if (!authResult.hasWriteScope) {
1593
+ this.logger.warn('⚠️ Token may lack write permissions, deployment might fail with 403');
1526
1594
  }
1527
1595
  // Ensure we have a flow definition
1528
1596
  if (!args.flow_definition) {
@@ -1964,17 +2032,26 @@ ${isComposedFlow ? `
1964
2032
  }
1965
2033
  async deployApplication(args) {
1966
2034
  try {
1967
- // Check authentication first
1968
- const isAuth = await this.oauth.isAuthenticated();
1969
- if (!isAuth) {
1970
- return {
1971
- content: [
1972
- {
1973
- type: 'text',
1974
- text: '❌ Not authenticated with ServiceNow.\n\nPlease run: snow-flow auth login\n\nOr configure your .env file with ServiceNow OAuth credentials.',
1975
- },
1976
- ],
1977
- };
2035
+ // Enhanced authentication check with token refresh for deployment
2036
+ const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
2037
+ if (!authResult.isValid) {
2038
+ this.logger.error('Deployment authentication failed:', authResult.error);
2039
+ // If auth failed, try to refresh token
2040
+ const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
2041
+ if (!refreshResult.success) {
2042
+ return {
2043
+ content: [
2044
+ {
2045
+ type: 'text',
2046
+ text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
2047
+ },
2048
+ ],
2049
+ };
2050
+ }
2051
+ }
2052
+ // Warn if token may lack write permissions
2053
+ if (!authResult.hasWriteScope) {
2054
+ this.logger.warn('⚠️ Token may lack write permissions, deployment might fail with 403');
1978
2055
  }
1979
2056
  this.logger.info('Deploying application with intelligent scope management', { name: args.name });
1980
2057
  // Ensure Update Set is active
@@ -3480,13 +3557,13 @@ Run snow_deployment_debug for basic session info or check the logs for more deta
3480
3557
  async previewWidget(args) {
3481
3558
  try {
3482
3559
  // Check authentication first
3483
- const isAuth = await this.oauth.isAuthenticated();
3484
- if (!isAuth) {
3560
+ const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
3561
+ if (!authResult.isValid) {
3485
3562
  return {
3486
3563
  content: [
3487
3564
  {
3488
3565
  type: 'text',
3489
- text: '❌ Not authenticated with ServiceNow.\n\nPlease run: snow-flow auth login\n\nOr configure your .env file with ServiceNow OAuth credentials.',
3566
+ text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
3490
3567
  },
3491
3568
  ],
3492
3569
  };
@@ -3653,13 +3730,13 @@ Use \`snow_widget_test\` to run automated tests with different scenarios.`,
3653
3730
  async testWidget(args) {
3654
3731
  try {
3655
3732
  // Check authentication first
3656
- const isAuth = await this.oauth.isAuthenticated();
3657
- if (!isAuth) {
3733
+ const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
3734
+ if (!authResult.isValid) {
3658
3735
  return {
3659
3736
  content: [
3660
3737
  {
3661
3738
  type: 'text',
3662
- text: '❌ Not authenticated with ServiceNow.\n\nPlease run: snow-flow auth login\n\nOr configure your .env file with ServiceNow OAuth credentials.',
3739
+ text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
3663
3740
  },
3664
3741
  ],
3665
3742
  };
@@ -4689,21 +4766,21 @@ Use \`snow_preview_widget\` to see a detailed preview of the widget rendering.`,
4689
4766
  };
4690
4767
  case 'script':
4691
4768
  case 'script_include':
4692
- const scriptResult = await this.client.createRecord('sys_script_include', config);
4769
+ const scriptResult = await this.createRecordWithRetry('sys_script_include', config);
4693
4770
  return {
4694
4771
  success: scriptResult.success,
4695
4772
  sys_id: scriptResult.data?.sys_id,
4696
4773
  message: scriptResult.success ? 'Script deployed' : scriptResult.error
4697
4774
  };
4698
4775
  case 'business_rule':
4699
- const ruleResult = await this.client.createRecord('sys_script', config);
4776
+ const ruleResult = await this.createRecordWithRetry('sys_script', config);
4700
4777
  return {
4701
4778
  success: ruleResult.success,
4702
4779
  sys_id: ruleResult.data?.sys_id,
4703
4780
  message: ruleResult.success ? 'Business rule deployed' : ruleResult.error
4704
4781
  };
4705
4782
  case 'table':
4706
- const tableResult = await this.client.createRecord('sys_db_object', config);
4783
+ const tableResult = await this.createRecordWithRetry('sys_db_object', config);
4707
4784
  return {
4708
4785
  success: tableResult.success,
4709
4786
  sys_id: tableResult.data?.sys_id,