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
535 lines • 20.4 kB
JavaScript
"use strict";
/**
* MCP Execution Bridge
*
* Bridges the gap between agent recommendations and actual MCP tool execution
* Enables Queen Agent and specialized agents to directly execute ServiceNow operations
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MCPExecutionBridge = void 0;
const eventemitter3_1 = require("eventemitter3");
const snow_oauth_js_1 = require("../utils/snow-oauth.js");
const queen_memory_js_1 = require("./queen-memory.js");
const logger_js_1 = require("../utils/logger.js");
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
const child_process_1 = require("child_process");
/**
* Maps agent recommendations to MCP tool calls and executes them
*/
class MCPExecutionBridge extends eventemitter3_1.EventEmitter {
constructor(memory) {
super();
this.mcpClients = new Map();
this.mcpProcesses = new Map();
// MCP server configurations
this.serverConfigs = new Map([
['deployment', {
name: 'servicenow-deployment',
command: 'node',
args: ['dist/mcp/servicenow-deployment-mcp.js']
}],
['intelligent', {
name: 'servicenow-intelligent',
command: 'node',
args: ['dist/mcp/servicenow-intelligent-mcp.js']
}],
['operations', {
name: 'servicenow-operations',
command: 'node',
args: ['dist/mcp/servicenow-operations-mcp.js']
}],
['update-set', {
name: 'servicenow-update-set',
command: 'node',
args: ['dist/mcp/servicenow-update-set-mcp.js']
}],
]);
// Tool to server mapping
this.toolServerMap = new Map([
// Deployment tools
['snow_deploy', 'deployment'],
['snow_deploy_widget', 'deployment'],
['snow_deploy_flow', 'deployment'],
['snow_preview_widget', 'deployment'],
['snow_widget_test', 'deployment'],
// Intelligent tools
['snow_find_artifact', 'intelligent'],
['snow_edit_artifact', 'intelligent'],
['snow_comprehensive_search', 'intelligent'],
['snow_analyze_requirements', 'intelligent'],
// Operations tools
['snow_catalog_item_search', 'operations'],
['snow_test_flow_with_mock', 'operations'],
['snow_link_catalog_to_flow', 'operations'],
// Update set tools
['snow_update_set_create', 'update-set'],
['snow_update_set_switch', 'update-set'],
['snow_update_set_add_artifact', 'update-set'],
]);
this.sessionAuth = new snow_oauth_js_1.ServiceNowOAuth();
this.memory = memory || new queen_memory_js_1.QueenMemorySystem();
this.logger = new logger_js_1.Logger('MCPExecutionBridge');
}
/**
* Execute an agent recommendation by calling the appropriate MCP tool
*/
async executeAgentRecommendation(agent, recommendation) {
const startTime = Date.now();
try {
this.logger.info(`Executing recommendation from ${agent.type} agent:`, {
tool: recommendation.tool,
action: recommendation.action,
confidence: recommendation.confidence
});
// 1. Map recommendation to MCP tool
const toolMapping = await this.mapRecommendationToTool(recommendation);
// 2. Ensure MCP client is connected
const client = await this.ensureMCPClient(toolMapping.server);
// 3. Execute tool with session authentication
const toolResult = await this.executeToolWithAuth(client, toolMapping.tool, toolMapping.params);
// 4. Store result in shared memory for other agents
await this.storeExecutionResult(agent.id, recommendation, toolResult);
// 5. Emit execution event for monitoring
this.emit('execution:complete', {
agent,
recommendation,
result: toolResult,
executionTime: Date.now() - startTime
});
return {
success: true,
recommendation,
toolResult: toolResult.result,
executionTime: Date.now() - startTime
};
}
catch (error) {
this.logger.error('Execution failed:', error);
// Try fallback strategies
const fallbackResult = await this.tryFallbackStrategies(agent, recommendation, error);
if (fallbackResult.success) {
return fallbackResult;
}
// Store failure for learning
await this.storeExecutionFailure(agent.id, recommendation, error);
return {
success: false,
recommendation,
error: error instanceof Error ? error.message : 'Unknown error',
executionTime: Date.now() - startTime,
fallbackUsed: true
};
}
}
/**
* Map agent recommendation to specific MCP tool
*/
async mapRecommendationToTool(recommendation) {
// Direct tool mapping if specified
if (recommendation.tool && recommendation.server) {
return {
server: recommendation.server,
tool: recommendation.tool,
params: recommendation.params
};
}
// Intelligent mapping based on action
const mapping = await this.intelligentToolMapping(recommendation);
// Store mapping decision for learning
await this.memory.store(`tool_mapping_${Date.now()}`, {
recommendation,
mapping,
timestamp: new Date().toISOString()
});
return mapping;
}
/**
* Intelligent tool mapping based on action description
*/
async intelligentToolMapping(recommendation) {
const action = recommendation.action.toLowerCase();
// Widget-related actions
if (action.includes('widget') || action.includes('portal')) {
if (action.includes('deploy') || action.includes('create')) {
return {
server: 'deployment',
tool: 'snow_deploy',
params: { type: 'widget', config: recommendation.params }
};
}
if (action.includes('test')) {
return {
server: 'deployment',
tool: 'snow_widget_test',
params: recommendation.params
};
}
}
// Flow-related actions (testing only - creation not supported)
if (action.includes('flow') || action.includes('workflow')) {
if (action.includes('test')) {
return {
server: 'operations',
tool: 'snow_test_flow_with_mock',
params: recommendation.params
};
}
}
// Search/discovery actions
if (action.includes('find') || action.includes('search') || action.includes('discover')) {
return {
server: 'intelligent',
tool: 'snow_find_artifact',
params: {
query: recommendation.params.query || recommendation.action,
type: recommendation.params.type || 'any'
}
};
}
// Update set actions
if (action.includes('update set') || action.includes('track')) {
if (action.includes('create')) {
return {
server: 'update-set',
tool: 'snow_update_set_create',
params: recommendation.params
};
}
if (action.includes('add') || action.includes('track')) {
return {
server: 'update-set',
tool: 'snow_update_set_add_artifact',
params: recommendation.params
};
}
}
// Default fallback - try intelligent search
return {
server: 'intelligent',
tool: 'snow_find_artifact',
params: {
query: recommendation.action,
type: 'any'
}
};
}
/**
* Ensure MCP client is connected and ready
*/
async ensureMCPClient(serverName) {
// Check if client already exists and is connected
const existingClient = this.mcpClients.get(serverName);
if (existingClient) {
return existingClient;
}
// Get server configuration
const config = this.serverConfigs.get(serverName);
if (!config) {
throw new Error(`Unknown MCP server: ${serverName}`);
}
// Spawn MCP server process
const serverProcess = (0, child_process_1.spawn)(config.command, config.args || [], {
env: { ...process.env, ...config.env },
stdio: ['pipe', 'pipe', 'pipe'],
});
this.mcpProcesses.set(serverName, serverProcess);
// Create MCP client
const transport = new stdio_js_1.StdioClientTransport({
command: config.command,
args: config.args,
env: { ...process.env, ...config.env },
});
const client = new index_js_1.Client({
name: `queen-agent-${serverName}`,
version: '1.0.0',
}, {
capabilities: {}
});
// Connect to server
await client.connect(transport);
// Store client
this.mcpClients.set(serverName, client);
this.logger.info(`Connected to MCP server: ${serverName}`);
return client;
}
/**
* Execute tool with authentication
*/
async executeToolWithAuth(client, tool, params) {
// Ensure we have valid authentication
const isAuthenticated = await this.sessionAuth.isAuthenticated();
if (!isAuthenticated) {
throw new Error('Not authenticated with ServiceNow. Run "snow-flow auth login" first.');
}
// Call the tool
const result = await client.callTool({
name: tool,
arguments: params
});
return result;
}
/**
* Store execution result in shared memory
*/
async storeExecutionResult(agentId, recommendation, result) {
const executionRecord = {
agentId,
recommendation,
result,
timestamp: new Date().toISOString(),
success: true
};
// Store in memory for coordination
await this.memory.store(`execution_${agentId}_${Date.now()}`, executionRecord);
// Update agent progress
await this.memory.storeProgress(agentId, {
lastExecution: executionRecord,
totalExecutions: await this.getAgentExecutionCount(agentId) + 1
});
}
/**
* Store execution failure for learning
*/
async storeExecutionFailure(agentId, recommendation, error) {
const failureRecord = {
agentId,
recommendation,
error: error instanceof Error ? error.message : String(error),
errorStack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString(),
success: false
};
// Store failure for pattern analysis
await this.memory.store(`failure_${agentId}_${Date.now()}`, failureRecord);
// Update failure patterns
await this.memory.storeFailurePattern({
tool: recommendation.tool,
action: recommendation.action,
errorType: this.classifyError(error),
frequency: 1
});
}
/**
* Try fallback strategies when primary execution fails
*/
async tryFallbackStrategies(agent, recommendation, error) {
const strategies = [
this.tryAlternativeTool.bind(this),
this.trySimplifiedParams.bind(this),
this.tryManualSteps.bind(this)
];
for (const strategy of strategies) {
try {
const result = await strategy(agent, recommendation, error);
if (result.success) {
this.logger.info('Fallback strategy succeeded:', strategy.name);
return result;
}
}
catch (strategyError) {
this.logger.warn(`Fallback strategy ${strategy.name} failed:`, strategyError);
}
}
return {
success: false,
recommendation,
error: 'All fallback strategies failed',
executionTime: 0,
fallbackUsed: true
};
}
/**
* Try using an alternative tool
*/
async tryAlternativeTool(agent, recommendation, error) {
// Map to alternative tools based on failure type
const alternatives = {
'snow_deploy': ['snow_deploy_widget', 'snow_deploy_flow'],
'snow_create_flow': ['snow_flow_wizard', 'snow_deploy'],
'snow_find_artifact': ['snow_comprehensive_search', 'snow_discover_existing_flows']
};
const altTools = alternatives[recommendation.tool];
if (!altTools || altTools.length === 0) {
throw new Error('No alternative tools available');
}
// Try first alternative
const altTool = altTools[0];
const altRecommendation = {
...recommendation,
tool: altTool,
params: this.adaptParamsForTool(recommendation.params, altTool)
};
return this.executeAgentRecommendation(agent, altRecommendation);
}
/**
* Try with simplified parameters
*/
async trySimplifiedParams(agent, recommendation, error) {
// Simplify complex params
const simplifiedParams = this.simplifyParams(recommendation.params);
const simplifiedRecommendation = {
...recommendation,
params: simplifiedParams
};
return this.executeAgentRecommendation(agent, simplifiedRecommendation);
}
/**
* Generate manual steps as fallback
*/
async tryManualSteps(agent, recommendation, error) {
// Generate manual steps based on recommendation
const manualSteps = this.generateManualSteps(recommendation);
return {
success: true,
recommendation,
toolResult: {
type: 'manual_steps',
steps: manualSteps,
reason: 'Automated execution failed, manual steps provided'
},
executionTime: 0,
fallbackUsed: true
};
}
/**
* Helper methods
*/
async getAgentExecutionCount(agentId) {
const progress = await this.memory.getProgress(agentId);
return progress?.totalExecutions || 0;
}
classifyError(error) {
if (!error)
return 'unknown';
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
if (message.includes('auth') || message.includes('401') || message.includes('403')) {
return 'authentication';
}
if (message.includes('timeout')) {
return 'timeout';
}
if (message.includes('not found') || message.includes('404')) {
return 'not_found';
}
if (message.includes('permission') || message.includes('access')) {
return 'permission';
}
if (message.includes('rate limit')) {
return 'rate_limit';
}
return 'general';
}
adaptParamsForTool(params, newTool) {
// Adapt parameters for different tool format
const adaptations = {
'snow_deploy_widget': (p) => ({
name: p.name || p.config?.name,
title: p.title || p.config?.title,
template: p.template || p.config?.template || '<div>Widget</div>',
css: p.css || p.config?.css || '',
client_script: p.client_script || p.config?.client_script || '',
server_script: p.server_script || p.config?.server_script || ''
}),
'snow_comprehensive_search': (p) => ({
query: p.query || p.name || p.description,
include_inactive: false
})
};
const adapter = adaptations[newTool];
return adapter ? adapter(params) : params;
}
simplifyParams(params) {
// Remove optional fields and complex nested structures
const simplified = {};
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
if (typeof value === 'object' && !Array.isArray(value)) {
// Flatten nested objects
simplified[key] = JSON.stringify(value);
}
else if (Array.isArray(value) && value.length > 5) {
// Limit array size
simplified[key] = value.slice(0, 5);
}
else {
simplified[key] = value;
}
}
}
return simplified;
}
generateManualSteps(recommendation) {
const action = recommendation.action.toLowerCase();
if (action.includes('widget')) {
return [
'1. Navigate to Service Portal > Widgets',
'2. Click "New" to create a widget',
`3. Set name to: ${recommendation.params.name || 'custom_widget'}`,
'4. Add HTML template, CSS, and scripts as needed',
'5. Save and test the widget',
'6. Add widget to a portal page'
];
}
if (action.includes('flow')) {
return [
'1. Navigate to Flow Designer',
'2. Click "New" > "Flow"',
`3. Set name to: ${recommendation.params.name || 'custom_flow'}`,
'4. Configure trigger conditions',
'5. Add flow steps and actions',
'6. Test and activate the flow'
];
}
return [
'1. Log in to ServiceNow instance',
`2. Execute action: ${recommendation.action}`,
'3. Use parameters provided in the recommendation',
'4. Test the implementation',
'5. Document the changes'
];
}
/**
* Cleanup resources
*/
async cleanup() {
// Disconnect all clients
for (const [name, client] of this.mcpClients) {
try {
await client.close();
this.logger.info(`Disconnected from MCP server: ${name}`);
}
catch (error) {
this.logger.error(`Error disconnecting from ${name}:`, error);
}
}
// Terminate all processes
for (const [name, process] of this.mcpProcesses) {
try {
process.kill('SIGTERM');
this.logger.info(`Terminated MCP server process: ${name}`);
}
catch (error) {
this.logger.error(`Error terminating ${name}:`, error);
}
}
this.mcpClients.clear();
this.mcpProcesses.clear();
}
/**
* Disconnect from MCP resources
*/
async disconnect() {
this.logger.info('Disconnecting MCP resources');
// Clean up any active connections or resources
}
/**
* Public shutdown method for cleanup
*/
async shutdown() {
this.logger.info('Shutting down MCP Execution Bridge');
await this.disconnect();
}
}
exports.MCPExecutionBridge = MCPExecutionBridge;
//# sourceMappingURL=mcp-execution-bridge.js.map