snow-flow
Version:
Snow-Flow v3.2.0: Complete ServiceNow Enterprise Suite with 180+ MCP Tools. ATF Testing, Knowledge Management, Service Catalog, Change Management with CAB scheduling, Virtual Agent chatbots with NLU, Performance Analytics KPIs, Flow Designer automation, A
1,125 lines (1,119 loc) • 89.3 kB
JavaScript
"use strict";
/**
* Base MCP Server with Agent Integration
* Provides common functionality for all MCP servers in the agent ecosystem
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseMCPServer = void 0;
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
const servicenow_client_js_1 = require("../../utils/servicenow-client.js");
const snow_oauth_js_1 = require("../../utils/snow-oauth.js");
const logger_js_1 = require("../../utils/logger.js");
const agent_context_provider_js_1 = require("./agent-context-provider.js");
const mcp_memory_manager_js_1 = require("./mcp-memory-manager.js");
const mcp_resource_manager_js_1 = require("./mcp-resource-manager.js");
class BaseMCPServer {
constructor(name, version = '1.0.0') {
this.server = new index_js_1.Server({
name,
version,
}, {
capabilities: {
tools: {},
resources: {},
},
});
this.client = new servicenow_client_js_1.ServiceNowClient();
this.oauth = new snow_oauth_js_1.ServiceNowOAuth();
this.logger = new logger_js_1.Logger(name);
this.contextProvider = new agent_context_provider_js_1.AgentContextProvider();
this.memory = mcp_memory_manager_js_1.MCPMemoryManager.getInstance();
this.resourceManager = new mcp_resource_manager_js_1.MCPResourceManager(name);
}
/**
* Execute a tool with agent context tracking
*/
async executeWithAgentContext(toolName, args, operation) {
// Extract agent context
const agentContext = this.contextProvider.extractAgentContext(args);
// Log operation start
this.logger.info(`Executing ${toolName}`, {
agent_id: agentContext.agent_id,
session_id: agentContext.session_id
});
// Execute with context tracking
const result = await this.contextProvider.executeWithContext({
...agentContext,
operation_name: toolName,
mcp_server: this.server.serverInfo?.name || 'unknown'
}, async () => operation(agentContext));
if (!result.success) {
return {
content: [
{
type: 'text',
text: `❌ Operation failed: ${result.error}`
}
],
metadata: {
agent_id: agentContext.agent_id,
session_id: agentContext.session_id,
duration_ms: result.duration_ms
}
};
}
return result.data;
}
/**
* MANDATORY authentication and connection validation before ServiceNow operations
*/
async validateServiceNowConnection() {
try {
this.logger.info('🔍 Validating ServiceNow connection...');
// 1. Check if we have valid credentials
const credentials = await this.oauth.loadCredentials();
if (!credentials) {
return {
success: false,
error: 'No ServiceNow credentials found. Set up .env file and run "snow-flow auth login".'
};
}
// 2. Check if we have access token (OAuth session)
if (!credentials.accessToken) {
return {
success: false,
error: 'OAuth authentication required. Run "snow-flow auth login" to authenticate.'
};
}
// 3. Check if token is still valid
const isAuth = await this.oauth.isAuthenticated();
if (!isAuth) {
this.logger.info('🔄 Token expired, attempting refresh...');
// Try to refresh token
const refreshResult = await this.oauth.refreshAccessToken();
if (!refreshResult.success) {
return {
success: false,
error: 'OAuth token expired and refresh failed. Run "snow-flow auth login" to re-authenticate.'
};
}
this.logger.info('✅ Token refreshed successfully');
}
// 4. Test actual ServiceNow connection
this.logger.info('🔧 About to call testConnection', {
clientType: this.client.constructor.name,
clientMethods: Object.getOwnPropertyNames(Object.getPrototypeOf(this.client)),
hasTestConnection: typeof this.client.testConnection === 'function',
hasMakeRequest: typeof this.client.makeRequest === 'function'
});
try {
const connectionTest = await this.client.testConnection();
if (!connectionTest.success) {
return {
success: false,
error: `ServiceNow connection failed: ${connectionTest.error}`
};
}
}
catch (testError) {
this.logger.error('🔧 testConnection error:', {
error: testError.message,
stack: testError.stack,
clientInfo: {
type: this.client.constructor.name,
methods: Object.getOwnPropertyNames(Object.getPrototypeOf(this.client))
}
});
throw testError;
}
this.logger.info('✅ ServiceNow connection validated');
return { success: true };
}
catch (error) {
this.logger.error('ServiceNow connection validation failed', error);
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Check authentication with proper error handling (deprecated - use validateServiceNowConnection)
*/
async checkAuthentication() {
const result = await this.validateServiceNowConnection();
return result.success;
}
/**
* Create standard authentication error response with detailed instructions
*/
createAuthenticationError(error) {
const errorText = error || 'Authentication validation failed';
return {
content: [
{
type: 'text',
text: `❌ ServiceNow Connection Failed
${errorText}
🔧 To fix this:
1. Ensure .env file has OAuth credentials:
SNOW_INSTANCE=your-instance.service-now.com
SNOW_CLIENT_ID=your_oauth_client_id
SNOW_CLIENT_SECRET=your_oauth_client_secret
2. Authenticate with ServiceNow:
snow-flow auth login
3. If you still get errors, run diagnostics:
snow_auth_diagnostics()
💡 To get OAuth credentials:
• ServiceNow: System OAuth > Application Registry > New OAuth Application
• Redirect URI: http://localhost:3005/callback (configurable via SNOW_REDIRECT_* env vars)
• Scopes: useraccount write admin`
}
]
};
}
/**
* Smart artifact discovery - check for existing artifacts before creating new ones
*/
async discoverExistingArtifacts(type, name, searchTerms) {
try {
this.logger.info(`🔍 Discovering existing ${type} artifacts...`);
const suggestions = [];
let artifacts = [];
// Search by name
const nameSearch = await this.client.searchRecords(this.getTableForType(type), `nameLIKE${name}`, 5);
if (nameSearch.success && nameSearch.data?.result?.length > 0) {
artifacts = nameSearch.data.result;
suggestions.push(`Found ${artifacts.length} existing ${type}(s) with similar names`);
suggestions.push(`Consider reusing: ${artifacts.map(a => a.name).join(', ')}`);
}
// Search by additional terms if provided
if (searchTerms && searchTerms.length > 0) {
for (const term of searchTerms) {
const termSearch = await this.client.searchRecords(this.getTableForType(type), `nameLIKE${term}^ORdescriptionLIKE${term}`, 3);
if (termSearch.success && termSearch.data?.result?.length > 0) {
const newArtifacts = termSearch.data.result.filter((new_artifact) => !artifacts.some(existing => existing.sys_id === new_artifact.sys_id));
artifacts.push(...newArtifacts);
if (newArtifacts.length > 0) {
suggestions.push(`Found ${newArtifacts.length} related ${type}(s) for "${term}"`);
}
}
}
}
return {
found: artifacts.length > 0,
artifacts,
suggestions
};
}
catch (error) {
this.logger.warn('Artifact discovery failed', error);
return { found: false, artifacts: [], suggestions: [] };
}
}
/**
* Ensure active Update Set for tracking changes
*/
async ensureUpdateSet(context, purpose) {
try {
this.logger.info('📦 Ensuring active Update Set...');
// Check current update set
const currentUpdateSet = await this.client.getCurrentUpdateSet();
if (currentUpdateSet.success && currentUpdateSet.data) {
this.logger.info(`✅ Using existing Update Set: ${currentUpdateSet.data.name}`);
return currentUpdateSet.data.sys_id;
}
// Create new update set
const updateSetName = purpose
? `Snow-Flow ${purpose} - ${new Date().toISOString().split('T')[0]}`
: `Snow-Flow Changes - ${new Date().toISOString().split('T')[0]}`;
this.logger.info(`📦 Creating new Update Set: ${updateSetName}`);
const newUpdateSet = await this.client.createUpdateSet({
name: updateSetName,
description: `Automated changes from Snow-Flow Agent (${context.agent_id})`,
state: 'in_progress'
});
if (newUpdateSet.success && newUpdateSet.data) {
// Set it as current
await this.client.setCurrentUpdateSet(newUpdateSet.data.sys_id);
this.logger.info(`✅ Update Set created and activated: ${newUpdateSet.data.sys_id}`);
return newUpdateSet.data.sys_id;
}
this.logger.warn('⚠️ Could not create Update Set, changes will not be tracked');
return null;
}
catch (error) {
this.logger.warn('Update Set creation failed', error);
return null;
}
}
/**
* Automatically track artifact in Update Set
*/
async trackArtifact(sysId, type, name, updateSetId) {
try {
if (!updateSetId) {
this.logger.warn('No Update Set ID provided for artifact tracking');
return;
}
this.logger.info(`📋 Tracking ${type} artifact: ${name} (${sysId})`);
// Add artifact to update set tracking (this is handled automatically by ServiceNow
// when artifacts are created/modified, but we can log it for visibility)
this.logger.info(`✅ Artifact tracked in Update Set: ${updateSetId}`);
}
catch (error) {
this.logger.warn('Artifact tracking failed', error);
}
}
/**
* Get ServiceNow table name for artifact type
*/
getTableForType(type) {
const tableMap = {
'widget': 'sp_widget',
'flow': 'sys_hub_flow',
'subflow': 'sys_hub_flow',
'action': 'sys_hub_action_type_definition',
'script': 'sys_script_include',
'business_rule': 'sys_script',
'table': 'sys_db_object',
'application': 'sys_app'
};
return tableMap[type] || 'sys_metadata';
}
/**
* Report progress during long operations
*/
async reportProgress(context, progress, phase) {
await this.contextProvider.reportProgress(context, progress, phase);
}
/**
* Store artifact with agent tracking
*/
async storeArtifact(context, artifact) {
await this.contextProvider.storeArtifact(context, artifact);
}
/**
* Notify handoff to another agent
*/
async notifyHandoff(context, to_agent, artifact_info) {
await this.contextProvider.notifyHandoff(context, to_agent, artifact_info);
}
/**
* Request Queen intervention for issues
*/
async requestQueenIntervention(context, issue) {
await this.contextProvider.requestQueenIntervention(context, issue);
}
/**
* Get session context for coordination
*/
async getSessionContext(session_id) {
return await this.contextProvider.getSessionContext(session_id);
}
/**
* Create success response with metadata
*/
createSuccessResponse(message, data, metadata) {
const content = [
{
type: 'text',
text: `✅ ${message}`
}
];
if (data) {
content.push({
type: 'text',
text: JSON.stringify(data, null, 2)
});
}
return {
content,
metadata
};
}
/**
* Create error response with metadata
*/
createErrorResponse(message, error, metadata) {
const content = [
{
type: 'text',
text: `❌ ${message}`
}
];
if (error) {
content.push({
type: 'text',
text: `Error details: ${error instanceof Error ? error.message : String(error)}`
});
}
return {
content,
metadata
};
}
/**
* Enhanced error handling with intelligent recovery strategies
*/
async handleServiceNowError(error, operation, context, fallbackOptions) {
this.logger.error(`ServiceNow operation failed: ${operation}`, error);
const errorMessage = error instanceof Error ? error.message : String(error);
const errorCode = this.extractErrorCode(errorMessage);
// Categorize error and determine recovery strategy
const errorCategory = this.categorizeError(errorMessage, errorCode);
switch (errorCategory) {
case 'authentication':
return this.handleAuthenticationError(error, operation, context);
case 'permissions':
return this.handlePermissionError(error, operation, context, fallbackOptions);
case 'network':
return this.handleNetworkError(error, operation, context, fallbackOptions);
case 'validation':
return this.handleValidationError(error, operation, context);
case 'scope':
return this.handleScopeError(error, operation, context, fallbackOptions);
default:
return this.handleGenericError(error, operation, context, fallbackOptions);
}
}
/**
* Categorize error for appropriate recovery strategy
*/
categorizeError(errorMessage, errorCode) {
const msgLower = errorMessage.toLowerCase();
// Authentication errors
if (msgLower.includes('unauthorized') ||
msgLower.includes('invalid_grant') ||
msgLower.includes('authentication') ||
errorCode === '401' || errorCode === '403') {
return 'authentication';
}
// Permission errors
if (msgLower.includes('insufficient privileges') ||
msgLower.includes('access denied') ||
msgLower.includes('acl')) {
return 'permissions';
}
// Network errors
if (msgLower.includes('network') ||
msgLower.includes('timeout') ||
msgLower.includes('connection') ||
msgLower.includes('econnrefused')) {
return 'network';
}
// Validation errors
if (msgLower.includes('invalid') ||
msgLower.includes('required field') ||
msgLower.includes('constraint') ||
msgLower.includes('validation')) {
return 'validation';
}
// Scope errors
if (msgLower.includes('scope') ||
msgLower.includes('application') ||
msgLower.includes('global')) {
return 'scope';
}
return 'generic';
}
/**
* Handle authentication errors with automatic recovery
*/
async handleAuthenticationError(error, operation, context) {
this.logger.info('🔄 Attempting automatic authentication recovery...');
// Try to refresh token automatically
try {
const refreshResult = await this.oauth.refreshAccessToken();
if (refreshResult.success) {
this.logger.info('✅ Token refreshed successfully, retry operation');
// Store recovery in context for coordination
await this.memory.updateSharedContext({
session_id: context.session_id,
context_key: `auth_recovery_${operation}`,
context_value: JSON.stringify({
recovery_action: 'token_refresh',
timestamp: new Date().toISOString(),
operation: operation
}),
created_by_agent: context.agent_id
});
return this.createSuccessResponse('Authentication recovered - token refreshed', {
recovery_action: 'token_refresh',
next_step: 'retry_operation',
can_continue: true
});
}
}
catch (refreshError) {
this.logger.warn('Token refresh failed', refreshError);
}
// Request Queen intervention for authentication issues
await this.requestQueenIntervention(context, {
type: 'authentication_failure',
priority: 'high',
description: `Authentication failed for ${operation}`,
attempted_solutions: ['token_refresh']
});
return this.createAuthenticationError(`Authentication failed during ${operation}. Automatic recovery failed.`);
}
/**
* Handle permission errors with scope escalation
*/
async handlePermissionError(error, operation, context, fallbackOptions) {
const errorMessage = error instanceof Error ? error.message : String(error);
// Try scope escalation if enabled
if (fallbackOptions?.enableScopeEscalation) {
this.logger.info('🔝 Attempting scope escalation...');
try {
// Try to escalate to global scope
const escalationResult = await this.escalateToGlobalScope(operation, context);
if (escalationResult.success) {
return this.createSuccessResponse('Permission issue resolved via scope escalation', escalationResult);
}
}
catch (escalationError) {
this.logger.warn('Scope escalation failed', escalationError);
}
}
// Request permission elevation
await this.requestQueenIntervention(context, {
type: 'permission_elevation_needed',
priority: 'high',
description: `Insufficient privileges for ${operation}: ${errorMessage}`,
attempted_solutions: fallbackOptions?.enableScopeEscalation ? ['scope_escalation'] : []
});
return this.createErrorResponse(`Permission error during ${operation}`, {
error: errorMessage,
recovery_options: [
'1. Request admin privileges from Queen Agent',
'2. Try scope escalation to global',
'3. Create manual Update Set instructions',
'4. Use alternative deployment approach'
],
next_steps: [
'Contact ServiceNow admin for elevated permissions',
'Or use snow_deploy with fallback_strategy="manual_steps"'
]
});
}
/**
* Handle network errors with intelligent retry
*/
async handleNetworkError(error, operation, context, fallbackOptions) {
if (fallbackOptions?.enableRetry) {
this.logger.info('🔄 Network error - attempting retry with backoff...');
// Implement exponential backoff retry
const maxRetries = 3;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
await this.delay(Math.pow(2, attempt) * 1000); // Exponential backoff
try {
// Test connection
const connectionTest = await this.client.testConnection();
if (connectionTest.success) {
this.logger.info(`✅ Network recovered after ${attempt} attempts`);
return this.createSuccessResponse('Network error recovered - connection restored', {
recovery_action: 'network_retry',
attempts: attempt,
can_continue: true
});
}
}
catch (retryError) {
this.logger.warn(`Retry attempt ${attempt} failed`, retryError);
}
}
}
return this.createErrorResponse(`Network error during ${operation}`, {
error: error instanceof Error ? error.message : String(error),
recovery_options: [
'1. Check ServiceNow instance availability',
'2. Verify network connectivity',
'3. Check firewall/proxy settings',
'4. Retry operation in a few minutes'
],
next_steps: [
'Wait for network recovery and retry',
'Or check ServiceNow instance status'
]
});
}
/**
* Handle validation errors with correction suggestions
*/
async handleValidationError(error, operation, context) {
const errorMessage = error instanceof Error ? error.message : String(error);
// Extract field information from validation error
const fieldInfo = this.extractFieldInfoFromError(errorMessage);
return this.createErrorResponse(`Validation error during ${operation}`, {
error: errorMessage,
field_issues: fieldInfo,
recovery_options: [
'1. Fix required fields and retry',
'2. Check field constraints and formats',
'3. Use snow_validate_deployment() first',
'4. Review ServiceNow table schema'
],
next_steps: [
'Update configuration with correct field values',
'Validate against ServiceNow table requirements'
]
});
}
/**
* Handle scope errors with automatic fallback
*/
async handleScopeError(error, operation, context, fallbackOptions) {
if (fallbackOptions?.enableScopeEscalation) {
this.logger.info('🌐 Scope error - attempting global scope fallback...');
try {
const scopeFallback = await this.fallbackToGlobalScope(operation, context);
if (scopeFallback.success) {
return this.createSuccessResponse('Scope error resolved - using global scope', scopeFallback);
}
}
catch (scopeError) {
this.logger.warn('Global scope fallback failed', scopeError);
}
}
return this.createErrorResponse(`Scope error during ${operation}`, {
error: error instanceof Error ? error.message : String(error),
recovery_options: [
'1. Deploy to global scope instead',
'2. Create scoped application first',
'3. Use manual deployment approach',
'4. Check application scope permissions'
],
next_steps: [
'Try snow_deploy with scope_strategy="global"',
'Or create application scope manually first'
]
});
}
/**
* Handle generic errors with comprehensive guidance
*/
async handleGenericError(error, operation, context, fallbackOptions) {
const errorMessage = error instanceof Error ? error.message : String(error);
// Generate contextual recovery options based on operation type
const recoveryOptions = this.generateRecoveryOptions(operation, errorMessage);
// Store error for pattern analysis
await this.memory.updateSharedContext({
session_id: context.session_id,
context_key: `error_${operation}_${Date.now()}`,
context_value: JSON.stringify({
error: errorMessage,
operation: operation,
timestamp: new Date().toISOString(),
recovery_attempted: Object.keys(fallbackOptions || {})
}),
created_by_agent: context.agent_id
});
return this.createErrorResponse(`Unexpected error during ${operation}`, {
error: errorMessage,
recovery_options: recoveryOptions,
next_steps: [
'Review error details and try alternative approach',
'Use snow_deployment_debug() for diagnostics',
'Contact Queen Agent for intervention'
],
fallback_available: fallbackOptions?.enableManualSteps || false
});
}
/**
* Helper methods for error handling
*/
extractErrorCode(errorMessage) {
const codeMatch = errorMessage.match(/(\d{3})/);
return codeMatch ? codeMatch[1] : undefined;
}
extractFieldInfoFromError(errorMessage) {
const fieldMatch = errorMessage.match(/field[:\s]+([a-z_]+)/i);
const requiredMatch = errorMessage.match(/required/i);
return {
field: fieldMatch ? fieldMatch[1] : null,
is_required: !!requiredMatch,
raw_message: errorMessage
};
}
generateRecoveryOptions(operation, errorMessage) {
const options = [
'1. Review error message and fix configuration',
'2. Check ServiceNow instance status and permissions'
];
if (operation.includes('deploy')) {
options.push('3. Use snow_validate_deployment() before retrying');
options.push('4. Try manual deployment with Update Set');
}
if (operation.includes('search') || operation.includes('find')) {
options.push('3. Try alternative search terms');
options.push('4. Use snow_comprehensive_search() for broader search');
}
options.push('5. Request Queen Agent intervention for complex issues');
return options;
}
async escalateToGlobalScope(operation, context) {
this.logger.info(`🔝 Attempting permission escalation for ${operation}`);
try {
// Enhanced authentication validation
const authValidation = await this.validateServiceNowConnection();
if (!authValidation.success) {
return {
success: false,
reason: 'Authentication validation failed before escalation',
recovery_action: 'Run: snow-flow auth login',
auth_error: authValidation.error
};
}
// Get current user information with enhanced OAuth scope handling
this.logger.info('🔍 Analyzing current user permissions and OAuth scopes...');
let currentUser = null;
let currentRoles = [];
let oAuthScopes = [];
try {
// Get current user details
const userResponse = await this.client.makeRequest({
method: 'GET',
endpoint: '/api/now/v2/table/sys_user',
params: {
sysparm_query: 'active=true^user_name=javascript:gs.getUserName()',
sysparm_fields: 'sys_id,user_name,name,email,active,locked_out,roles',
sysparm_limit: 1
}
});
if (userResponse.data?.result?.[0]) {
currentUser = userResponse.data.result[0];
this.logger.info(`👤 Current user: ${currentUser.name} (${currentUser.user_name})`);
}
// Get detailed role information
const rolesResponse = await this.client.makeRequest({
method: 'GET',
endpoint: '/api/now/v2/table/sys_user_has_role',
params: {
sysparm_query: `user.user_name=${currentUser?.user_name || 'javascript:gs.getUserName()'}`,
sysparm_fields: 'role.name,role.description,state,inherited',
sysparm_limit: 100
}
});
if (rolesResponse.data?.result) {
currentRoles = rolesResponse.data.result
.filter((r) => r.state === 'active')
.map((r) => r['role.name'])
.filter(Boolean);
this.logger.info(`🛡️ Active roles: ${currentRoles.join(', ')}`);
}
// Check OAuth token scopes if available
try {
// Attempt to get token info if method exists
if (typeof this.oauth.getTokenInfo === 'function') {
const tokenInfo = await this.oauth.getTokenInfo();
if (tokenInfo?.scope) {
oAuthScopes = tokenInfo.scope.split(' ');
this.logger.info(`🔑 OAuth scopes: ${oAuthScopes.join(', ')}`);
}
}
else {
this.logger.debug('OAuth getTokenInfo method not available');
}
}
catch (scopeError) {
this.logger.debug('Could not retrieve OAuth scope information', { error: scopeError });
}
}
catch (roleError) {
this.logger.warn('Could not fetch complete user information', { error: roleError });
// Continue with limited information
}
// Enhanced role _analysis based on operation type
const requiredRoles = this.determineRequiredRoles(operation);
const requiredScopes = this.determineRequiredOAuthScopes(operation);
const missingRoles = requiredRoles.filter(role => !currentRoles.includes(role));
const missingScopes = requiredScopes.filter(scope => !oAuthScopes.includes(scope));
this.logger.info(`📊 Permission Analysis:`, {
required_roles: requiredRoles,
current_roles: currentRoles,
missing_roles: missingRoles,
required_scopes: requiredScopes,
current_scopes: oAuthScopes,
missing_scopes: missingScopes
});
// Check if user has sufficient permissions
if (missingRoles.length === 0 && missingScopes.length === 0) {
// User has all required permissions, issue might be elsewhere
return {
success: true,
reason: 'User has all required roles and scopes - permission issue may be configuration related',
current_roles: currentRoles,
current_scopes: oAuthScopes,
diagnostic_actions: [
'Check ACL rules for the specific table/operation',
'Verify application scope permissions',
'Check domain separation settings',
'Review field-level security',
'Validate write permissions for target table'
]
};
}
// Enhanced automatic role assignment with better error handling
let autoAssignmentAttempted = false;
const canAutoAssign = currentRoles.includes('admin') ||
currentRoles.includes('user_admin') ||
currentRoles.includes('system_administrator');
if (canAutoAssign && missingRoles.length > 0) {
this.logger.info('🤖 Attempting automatic role assignment...');
try {
const assignmentResult = await this.attemptEnhancedRoleAssignment(currentUser?.sys_id, missingRoles, context);
if (assignmentResult.success) {
autoAssignmentAttempted = true;
// Update context with successful assignment
await this.memory.updateSharedContext({
session_id: context.session_id,
context_key: `permission_escalation_${operation}`,
context_value: JSON.stringify({
escalation_type: 'automatic_role_assignment',
assigned_roles: assignmentResult.assigned_roles,
timestamp: new Date().toISOString(),
operation: operation
}),
created_by_agent: context.agent_id
});
return {
success: true,
reason: 'Roles automatically assigned successfully',
assigned_roles: assignmentResult.assigned_roles,
assignment_details: assignmentResult.details,
recommended_action: 'Retry the original operation - new permissions are active'
};
}
}
catch (assignError) {
this.logger.warn('Automatic role assignment failed', { error: assignError });
autoAssignmentAttempted = true; // We tried
}
}
// Enhanced OAuth scope elevation attempt
if (missingScopes.length > 0) {
this.logger.info('🔑 Attempting OAuth scope elevation...');
try {
const scopeElevationResult = await this.attemptOAuthScopeElevation(missingScopes, operation, context);
if (scopeElevationResult.success) {
return {
success: true,
reason: 'OAuth scopes elevated successfully',
elevated_scopes: scopeElevationResult.elevated_scopes,
recommended_action: 'Retry the original operation with elevated OAuth permissions'
};
}
}
catch (scopeError) {
this.logger.warn('OAuth scope elevation failed', { error: scopeError });
}
}
// Request Queen intervention for complex permission issues
await this.requestQueenIntervention(context, {
type: 'permission_escalation_required',
priority: 'high',
description: `Permission escalation needed for ${operation}. Missing roles: ${missingRoles.join(', ')}. Missing OAuth scopes: ${missingScopes.join(', ')}.`,
attempted_solutions: [
...(autoAssignmentAttempted ? ['automatic_role_assignment'] : []),
...(missingScopes.length > 0 ? ['oauth_scope_elevation'] : [])
]
});
// Comprehensive manual escalation instructions
const manualSteps = [
`1. ServiceNow Administrator Actions Required:`,
` • Navigate to User Administration > Users`,
` • Find user: ${currentUser?.name || 'Current User'} (${currentUser?.user_name || 'unknown'})`,
` • Add missing roles: ${missingRoles.join(', ')}`,
...missingScopes.length > 0 ? [
`2. OAuth Application Configuration:`,
` • Update OAuth application scopes to include: ${missingScopes.join(', ')}`,
` • Re-authorize application with elevated permissions`
] : [],
`${missingScopes.length > 0 ? '3' : '2'}. Verification Steps:`,
` • Test permissions: snow_validate_live_connection({ test_level: "permissions" })`,
` • Retry operation: ${operation}`
];
return {
success: false,
reason: 'Manual permission escalation required',
current_user: currentUser,
current_roles: currentRoles,
missing_roles: missingRoles,
current_oauth_scopes: oAuthScopes,
missing_oauth_scopes: missingScopes,
escalation_required: true,
auto_assignment_attempted: autoAssignmentAttempted,
manual_steps: manualSteps,
estimated_time: `${5 + (missingRoles.length * 2)}–${10 + (missingRoles.length * 3)} minutes with admin access`,
priority: missingRoles.includes('admin') || missingRoles.includes('system_administrator') ? 'critical' : 'high'
};
}
catch (error) {
this.logger.error('🚨 Permission escalation process failed', { error, operation, context });
return {
success: false,
reason: 'Critical error during permission escalation',
error: error instanceof Error ? error.message : String(error),
error_type: this.categorizeError(error instanceof Error ? error.message : String(error)),
recovery_actions: [
'1. Verify ServiceNow connection: snow_validate_live_connection()',
'2. Check authentication status: snow_auth_diagnostics()',
'3. Confirm API access is enabled in ServiceNow',
'4. Contact ServiceNow administrator for manual role assignment',
'5. Review system logs for additional error details'
],
escalation_required: true,
contact_admin: true
};
}
}
/**
* Determine required roles based on operation type
*/
determineRequiredRoles(operation) {
const roles = [];
// Base roles for most operations
if (operation.includes('deploy') || operation.includes('create') || operation.includes('update')) {
roles.push('rest_service', 'web_service_admin');
}
// Widget operations
if (operation.includes('widget')) {
roles.push('sp_admin', 'sp_portal_admin');
}
// Flow operations
if (operation.includes('flow') || operation.includes('workflow')) {
roles.push('flow_designer', 'admin');
}
// Application operations
if (operation.includes('application') || operation.includes('app')) {
roles.push('app_creator', 'admin');
}
// Update set operations
if (operation.includes('update_set')) {
roles.push('admin');
}
// Global scope operations
if (operation.includes('global')) {
roles.push('admin', 'system_administrator');
}
return Array.from(new Set(roles)); // Remove duplicates
}
/**
* Determine required OAuth scopes based on operation type
*/
determineRequiredOAuthScopes(operation) {
const scopes = ['useraccount']; // Base scope for most operations
// Operations that require write access
if (operation.includes('deploy') || operation.includes('create') || operation.includes('update')) {
scopes.push('write');
}
// Administrative operations
if (operation.includes('admin') || operation.includes('global') || operation.includes('system')) {
scopes.push('admin');
}
// Flow Designer operations may need specific scopes
if (operation.includes('flow') || operation.includes('workflow')) {
scopes.push('write', 'admin');
}
return Array.from(new Set(scopes)); // Remove duplicates
}
/**
* Enhanced role assignment with better validation
*/
async attemptEnhancedRoleAssignment(userSysId, roles, context) {
try {
const assignedRoles = [];
const assignmentDetails = [];
for (const roleName of roles) {
try {
// Get the role sys_id with enhanced validation
const roleResponse = await this.client.makeRequest({
method: 'GET',
endpoint: '/api/now/v2/table/sys_user_role',
params: {
sysparm_query: `name=${roleName}^active=true`,
sysparm_fields: 'sys_id,name,description,can_delegate',
sysparm_limit: 1
}
});
if (roleResponse.data?.result?.[0]) {
const role = roleResponse.data.result[0];
// Check if user already has this role
const existingRoleResponse = await this.client.makeRequest({
method: 'GET',
endpoint: '/api/now/v2/table/sys_user_has_role',
params: {
sysparm_query: `user=${userSysId}^role=${role.sys_id}`,
sysparm_limit: 1
}
});
if (existingRoleResponse.data?.result?.length > 0) {
this.logger.info(`User already has role: ${roleName}`);
assignedRoles.push(roleName);
continue;
}
// Attempt to assign role
const assignmentResponse = await this.client.makeRequest({
method: 'POST',
endpoint: '/api/now/v2/table/sys_user_has_role',
data: {
user: userSysId,
role: role.sys_id,
state: 'active',
granted_by: 'snow_flow_automation'
}
});
if (assignmentResponse.data?.result) {
assignedRoles.push(roleName);
assignmentDetails.push({
role: roleName,
sys_id: assignmentResponse.data.result.sys_id,
status: 'assigned'
});
this.logger.info(`Successfully assigned role: ${roleName}`);
}
}
else {
this.logger.warn(`Role not found: ${roleName}`);
}
}
catch (roleError) {
this.logger.warn(`Failed to assign role ${roleName}`, { error: roleError });
assignmentDetails.push({
role: roleName,
status: 'failed',
error: roleError instanceof Error ? roleError.message : String(roleError)
});
}
}
return {
success: assignedRoles.length > 0,
assigned_roles: assignedRoles,
details: assignmentDetails
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Attempt OAuth scope elevation
*/
async attemptOAuthScopeElevation(missingScopes, operation, context) {
try {
this.logger.info('Requesting OAuth scope elevation...', { missing_scopes: missingScopes });
// In most ServiceNow OAuth implementations, scope elevation requires
// re-authorization through the OAuth flow. We can't dynamically elevate
// scopes without user interaction.
// Check if we can refresh token with additional scopes
let currentToken = null;
try {
if (typeof this.oauth.getTokenInfo === 'function') {
currentToken = await this.oauth.getTokenInfo();
}
}
catch (error) {
this.logger.debug('Could not get current token info', { error });
}
if (!currentToken) {
return {
success: false,
error: 'No active OAuth token found or method not available'
};
}
// Attempt token refresh - this typically won't add new scopes
// but might refresh existing elevated permissions
const refreshResult = await this.oauth.refreshAccessToken();
if (!refreshResult.success) {
return {
success: false,
error: 'Failed to refresh OAuth token'
};
}
// Check if the refresh gave us the scopes we need
let newTokenInfo = null;
try {
if (typeof this.oauth.getTokenInfo === 'function') {
newTokenInfo = await this.oauth.getTokenInfo();
}
}
catch (error) {
this.logger.debug('Could not get new token info after refresh', { error });
}
const newScopes = newTokenInfo?.scope?.split(' ') || [];
const actuallyMissing = missingScopes.filter(scope => !newScopes.includes(scope));
if (actuallyMissing.length === 0) {
return {
success: true,
elevated_scopes: missingScopes
};
}
// For missing scopes, we need manual re-authorization
return {
success: false,
error: `OAuth scopes require manual re-authorization: ${actuallyMissing.join(', ')}`
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Enhanced deployment context extraction
*/
async extractEnhancedDeploymentContext(operation, context) {
try {
// Extract from agent context
const contextData = context;
// Enhanced artifact type detection
const artifactType = this.extractArtifactType(operation);
const artifactName = contextData.artifact_name ||
this.extractArtifactName(operation) ||
`fallback_${artifactType}_${Date.now()}`;
// Determine current scope from various sources
let currentScope = contextData.current_scope || 'scoped_application';
if (contextData.deployment_config?.sys_scope) {
currentScope = contextData.deployment_config.sys_scope;
}
// Analyze original error for better global scope strategy
const originalError = contextData.original_error || 'Scoped dep