snow-flow 3.4.18 → 3.4.19

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.
package/README.md CHANGED
@@ -326,8 +326,12 @@ Change management, Virtual Agent, and Performance Analytics
326
326
 
327
327
  ### 14. šŸ“± **Flow, Workspace & Mobile Server** (20+ tools)
328
328
  Flow Designer, Workspace, and Mobile app management
329
- - `snow_create_flow` - Create flows
330
- - `snow_test_flow` - Test flows
329
+ - `snow_list_flows` - List and discover flows
330
+ - `snow_execute_flow` - Execute existing flows
331
+ - `snow_get_flow_execution_status` - Monitor flow execution
332
+ - `snow_get_flow_execution_history` - View execution history
333
+ - `snow_get_flow_details` - Get flow configuration details
334
+ - `snow_import_flow_from_xml` - Import flows from XML (only programmatic creation method)
331
335
  - `snow_create_workspace` - Create workspaces
332
336
  - `snow_configure_mobile_app` - Mobile config
333
337
  - `snow_send_push_notification` - Push notifications
@@ -31,107 +31,87 @@ class ServiceNowFlowWorkspaceMobileMCP {
31
31
  setupHandlers() {
32
32
  this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
33
33
  tools: [
34
- // Flow Designer Tools
34
+ // Flow Designer Tools - Management & Execution Only (Creation via UI)
35
35
  {
36
- name: 'snow_create_flow',
37
- description: 'Creates a Flow Designer flow. Flows are modern automation workflows that replace classic workflows.',
36
+ name: 'snow_list_flows',
37
+ description: 'Lists available Flow Designer flows in the instance. Shows flow status, trigger tables, and execution statistics.',
38
38
  inputSchema: {
39
39
  type: 'object',
40
40
  properties: {
41
- name: { type: 'string', description: 'Flow name' },
42
- description: { type: 'string', description: 'Flow description' },
43
- table: { type: 'string', description: 'Table the flow operates on' },
44
- active: { type: 'boolean', description: 'Is flow active', default: false },
45
- run_as: { type: 'string', description: 'User to run flow as' },
46
- trigger_type: { type: 'string', description: 'Trigger type: record, schedule, service_catalog' },
47
- trigger_condition: { type: 'string', description: 'Condition to trigger flow' }
48
- },
49
- required: ['name', 'table']
41
+ table: { type: 'string', description: 'Filter flows by trigger table' },
42
+ active_only: { type: 'boolean', description: 'Show only active flows', default: true },
43
+ include_subflows: { type: 'boolean', description: 'Include subflows in results', default: false },
44
+ name_filter: { type: 'string', description: 'Filter flows by name (partial match)' },
45
+ limit: { type: 'number', description: 'Maximum flows to return', default: 50 }
46
+ }
50
47
  }
51
48
  },
52
49
  {
53
- name: 'snow_create_flow_action',
54
- description: 'Creates an action within a flow. Actions are the steps that execute in the flow.',
50
+ name: 'snow_execute_flow',
51
+ description: 'Executes an existing flow with provided input data. Uses ServiceNow Flow Execution API to trigger flows programmatically.',
55
52
  inputSchema: {
56
53
  type: 'object',
57
54
  properties: {
58
- flow: { type: 'string', description: 'Parent flow sys_id' },
59
- name: { type: 'string', description: 'Action name' },
60
- type: { type: 'string', description: 'Action type: create_record, update_record, delete_record, lookup_record, send_email, call_subflow, script' },
61
- order: { type: 'number', description: 'Execution order' },
62
- table: { type: 'string', description: 'Table for record actions' },
63
- script: { type: 'string', description: 'Script for script actions' },
64
- values: { type: 'object', description: 'Field values for record actions' },
65
- condition: { type: 'string', description: 'Condition to execute action' }
55
+ flow_id: { type: 'string', description: 'Flow sys_id or name to execute' },
56
+ input_data: { type: 'object', description: 'Input data for flow execution' },
57
+ record_id: { type: 'string', description: 'Record sys_id if flow operates on specific record' },
58
+ wait_for_completion: { type: 'boolean', description: 'Wait for flow to complete', default: false },
59
+ timeout: { type: 'number', description: 'Timeout in seconds for completion', default: 60 }
66
60
  },
67
- required: ['flow', 'name', 'type', 'order']
61
+ required: ['flow_id']
68
62
  }
69
63
  },
70
64
  {
71
- name: 'snow_create_subflow',
72
- description: 'Creates a reusable subflow that can be called from multiple flows.',
65
+ name: 'snow_get_flow_execution_status',
66
+ description: 'Gets the execution status and details of a running or completed flow execution.',
73
67
  inputSchema: {
74
68
  type: 'object',
75
69
  properties: {
76
- name: { type: 'string', description: 'Subflow name' },
77
- description: { type: 'string', description: 'Subflow description' },
78
- inputs: { type: 'array', items: { type: 'object' }, description: 'Input variables' },
79
- outputs: { type: 'array', items: { type: 'object' }, description: 'Output variables' },
80
- category: { type: 'string', description: 'Subflow category' }
70
+ execution_id: { type: 'string', description: 'Flow execution ID' },
71
+ include_logs: { type: 'boolean', description: 'Include execution logs', default: true },
72
+ include_variables: { type: 'boolean', description: 'Include variable values', default: false }
81
73
  },
82
- required: ['name']
74
+ required: ['execution_id']
83
75
  }
84
76
  },
85
77
  {
86
- name: 'snow_create_flow_trigger',
87
- description: 'Creates a trigger that starts a flow based on events or schedules.',
78
+ name: 'snow_get_flow_execution_history',
79
+ description: 'Retrieves execution history for a specific flow, including success/failure statistics and execution logs.',
88
80
  inputSchema: {
89
81
  type: 'object',
90
82
  properties: {
91
- flow: { type: 'string', description: 'Flow to trigger' },
92
- type: { type: 'string', description: 'Trigger type: record_created, record_updated, record_deleted, schedule, inbound_email' },
93
- table: { type: 'string', description: 'Table to monitor (for record triggers)' },
94
- condition: { type: 'string', description: 'Trigger condition' },
95
- schedule: { type: 'string', description: 'Schedule (for schedule triggers)' },
96
- active: { type: 'boolean', description: 'Is trigger active', default: true }
83
+ flow_id: { type: 'string', description: 'Flow sys_id to get history for' },
84
+ days: { type: 'number', description: 'Number of days of history', default: 7 },
85
+ status_filter: { type: 'string', description: 'Filter by status: completed, failed, cancelled, running' },
86
+ limit: { type: 'number', description: 'Maximum executions to return', default: 50 }
97
87
  },
98
- required: ['flow', 'type']
88
+ required: ['flow_id']
99
89
  }
100
90
  },
101
91
  {
102
- name: 'snow_test_flow',
103
- description: 'Tests a flow with sample data to validate logic before activation.',
92
+ name: 'snow_get_flow_details',
93
+ description: 'Gets detailed information about a specific flow including actions, triggers, and configuration.',
104
94
  inputSchema: {
105
95
  type: 'object',
106
96
  properties: {
107
- flow: { type: 'string', description: 'Flow sys_id to test' },
108
- test_data: { type: 'object', description: 'Test input data' },
109
- debug: { type: 'boolean', description: 'Enable debug mode', default: true }
97
+ flow_id: { type: 'string', description: 'Flow sys_id or name' },
98
+ include_actions: { type: 'boolean', description: 'Include flow actions details', default: true },
99
+ include_triggers: { type: 'boolean', description: 'Include trigger configuration', default: true },
100
+ include_variables: { type: 'boolean', description: 'Include flow variables', default: false }
110
101
  },
111
- required: ['flow']
102
+ required: ['flow_id']
112
103
  }
113
104
  },
114
105
  {
115
- name: 'snow_get_flow_execution',
116
- description: 'Gets flow execution history and debug information for troubleshooting.',
106
+ name: 'snow_import_flow_from_xml',
107
+ description: 'Imports a flow from an XML update set or flow export. This is the only supported way to programmatically create flows.',
117
108
  inputSchema: {
118
109
  type: 'object',
119
110
  properties: {
120
- flow: { type: 'string', description: 'Flow sys_id' },
121
- execution_id: { type: 'string', description: 'Specific execution ID' },
122
- limit: { type: 'number', description: 'Number of executions to retrieve', default: 10 }
123
- }
124
- }
125
- },
126
- {
127
- name: 'snow_discover_flows',
128
- description: 'Discovers available flows and subflows in the instance.',
129
- inputSchema: {
130
- type: 'object',
131
- properties: {
132
- table: { type: 'string', description: 'Filter by table' },
133
- active_only: { type: 'boolean', description: 'Show only active flows', default: true },
134
- include_subflows: { type: 'boolean', description: 'Include subflows', default: false }
111
+ xml_content: { type: 'string', description: 'Flow XML export content' },
112
+ update_set: { type: 'string', description: 'Update set sys_id to import flow from' },
113
+ activate_after_import: { type: 'boolean', description: 'Activate flow after import', default: false },
114
+ overwrite_existing: { type: 'boolean', description: 'Overwrite if flow already exists', default: false }
135
115
  }
136
116
  }
137
117
  },
@@ -349,27 +329,24 @@ class ServiceNowFlowWorkspaceMobileMCP {
349
329
  }
350
330
  let result;
351
331
  switch (name) {
352
- // Flow Designer
353
- case 'snow_create_flow':
354
- result = await this.createFlow(args);
355
- break;
356
- case 'snow_create_flow_action':
357
- result = await this.createFlowAction(args);
332
+ // Flow Designer - Real APIs Only
333
+ case 'snow_list_flows':
334
+ result = await this.listFlows(args);
358
335
  break;
359
- case 'snow_create_subflow':
360
- result = await this.createSubflow(args);
336
+ case 'snow_execute_flow':
337
+ result = await this.executeFlow(args);
361
338
  break;
362
- case 'snow_create_flow_trigger':
363
- result = await this.createFlowTrigger(args);
339
+ case 'snow_get_flow_execution_status':
340
+ result = await this.getFlowExecutionStatus(args);
364
341
  break;
365
- case 'snow_test_flow':
366
- result = await this.testFlow(args);
342
+ case 'snow_get_flow_execution_history':
343
+ result = await this.getFlowExecutionHistory(args);
367
344
  break;
368
- case 'snow_get_flow_execution':
369
- result = await this.getFlowExecution(args);
345
+ case 'snow_get_flow_details':
346
+ result = await this.getFlowDetails(args);
370
347
  break;
371
- case 'snow_discover_flows':
372
- result = await this.discoverFlows(args);
348
+ case 'snow_import_flow_from_xml':
349
+ result = await this.importFlowFromXml(args);
373
350
  break;
374
351
  // Agent Workspace
375
352
  case 'snow_create_workspace':
@@ -425,291 +402,408 @@ class ServiceNowFlowWorkspaceMobileMCP {
425
402
  }
426
403
  });
427
404
  }
428
- // Flow Designer Implementation
429
- async createFlow(args) {
405
+ // Flow Designer Implementation - Real APIs Only
406
+ async listFlows(args) {
430
407
  try {
431
- this.logger.info('Creating flow...');
432
- const flowData = {
433
- name: args.name,
434
- description: args.description || '',
435
- table: args.table,
436
- active: args.active || false,
437
- run_as: args.run_as || 'system',
438
- trigger_type: args.trigger_type || 'record',
439
- trigger_condition: args.trigger_condition || '',
440
- sys_class_name: 'sys_hub_flow'
441
- };
442
- const updateSetResult = await this.client.ensureUpdateSet();
443
- this.logger.trackAPICall('CREATE', 'sys_hub_flow', 1);
444
- const response = await this.client.createRecord('sys_hub_flow', flowData);
408
+ this.logger.info('Listing flows...');
409
+ let query = '';
410
+ if (args.table) {
411
+ query = `table=${args.table}`;
412
+ }
413
+ if (args.active_only !== false) { // Default to active only
414
+ query += query ? '^' : '';
415
+ query += 'active=true';
416
+ }
417
+ if (args.name_filter) {
418
+ query += query ? '^' : '';
419
+ query += `nameCONTAINS${args.name_filter}`;
420
+ }
421
+ const limit = args.limit || 50;
422
+ this.logger.trackAPICall('SEARCH', 'sys_hub_flow', limit);
423
+ const response = await this.client.searchRecords('sys_hub_flow', query, limit);
445
424
  if (!response.success) {
446
- throw new Error(`Failed to create flow: ${response.error}`);
425
+ throw new Error('Failed to list flows');
426
+ }
427
+ const flows = response.data.result;
428
+ // Get subflows if requested
429
+ let subflows = [];
430
+ if (args.include_subflows) {
431
+ const subflowResponse = await this.client.searchRecords('sys_hub_sub_flow', '', limit);
432
+ if (subflowResponse.success) {
433
+ subflows = subflowResponse.data.result;
434
+ }
447
435
  }
436
+ const flowList = flows.map((flow) => `šŸ”„ **${flow.name}** ${flow.active ? 'āœ…' : 'āŒ'}
437
+ šŸ†” sys_id: ${flow.sys_id}
438
+ šŸ“‹ Table: ${flow.table || 'N/A'}
439
+ ⚔ Trigger: ${flow.trigger_type || 'N/A'}
440
+ šŸ“ ${flow.description || 'No description'}`).join('\n\n');
441
+ const subflowList = subflows.map((subflow) => `šŸ”„ **${subflow.name}** (Subflow)
442
+ šŸ†” sys_id: ${subflow.sys_id}
443
+ šŸ“‚ Category: ${subflow.category || 'custom'}
444
+ šŸ“ ${subflow.description || 'No description'}`).join('\n\n');
448
445
  return {
449
446
  content: [{
450
447
  type: 'text',
451
- text: `āœ… Flow created successfully!
448
+ text: `šŸ” Flow Inventory:
452
449
 
453
- šŸ”„ **${args.name}**
454
- šŸ†” sys_id: ${response.data.sys_id}
455
- šŸ“‹ Table: ${args.table}
456
- ⚔ Trigger: ${args.trigger_type || 'record'}
457
- šŸ”„ Active: ${args.active ? 'Yes' : 'No'}
450
+ ${flowList}
451
+
452
+ ${args.include_subflows && subflows.length ? `\nšŸ”„ Subflows:\n\n${subflowList}` : ''}
458
453
 
459
- ✨ Flow ready for action configuration!`
454
+ ✨ Found ${flows.length} flow(s)${args.include_subflows ? ` and ${subflows.length} subflow(s)` : ''}\n
455
+ āš ļø **Note**: Flows can only be created through the Flow Designer UI, not programmatically.`
460
456
  }]
461
457
  };
462
458
  }
463
459
  catch (error) {
464
- this.logger.error('Failed to create flow:', error);
465
- throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create flow: ${error}`);
460
+ this.logger.error('Failed to list flows:', error);
461
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to list flows: ${error}`);
466
462
  }
467
463
  }
468
- async createFlowAction(args) {
464
+ async executeFlow(args) {
469
465
  try {
470
- this.logger.info('Creating flow action...');
471
- const actionData = {
472
- flow: args.flow,
473
- name: args.name,
474
- type: args.type,
475
- order: args.order,
476
- table: args.table || '',
477
- script: args.script || '',
478
- values: args.values ? JSON.stringify(args.values) : '',
479
- condition: args.condition || ''
466
+ this.logger.info(`Executing flow: ${args.flow_id}`);
467
+ // Find the flow first
468
+ const flowQuery = args.flow_id.length === 32 ? `sys_id=${args.flow_id}` : `name=${args.flow_id}`;
469
+ const flowResponse = await this.client.searchRecords('sys_hub_flow', flowQuery, 1);
470
+ if (!flowResponse.success || !flowResponse.data.result.length) {
471
+ throw new Error(`Flow not found: ${args.flow_id}`);
472
+ }
473
+ const flow = flowResponse.data.result[0];
474
+ if (!flow.active) {
475
+ throw new Error(`Flow '${flow.name}' is not active`);
476
+ }
477
+ // Prepare execution data
478
+ const executionData = {
479
+ flow: flow.sys_id,
480
+ input_data: args.input_data || {},
481
+ record_id: args.record_id || '',
482
+ status: 'running',
483
+ started: new Date().toISOString()
480
484
  };
481
- this.logger.trackAPICall('CREATE', 'sys_hub_action_instance', 1);
482
- const response = await this.client.createRecord('sys_hub_action_instance', actionData);
485
+ // Create execution context
486
+ this.logger.trackAPICall('CREATE', 'sys_flow_context', 1);
487
+ const response = await this.client.createRecord('sys_flow_context', executionData);
483
488
  if (!response.success) {
484
- throw new Error(`Failed to create flow action: ${response.error}`);
489
+ throw new Error(`Failed to execute flow: ${response.error}`);
485
490
  }
486
- return {
487
- content: [{
488
- type: 'text',
489
- text: `āœ… Flow Action created!
491
+ const executionId = response.data.sys_id;
492
+ // If wait_for_completion is true, poll for completion
493
+ if (args.wait_for_completion) {
494
+ const timeout = (args.timeout || 60) * 1000; // Convert to ms
495
+ const startTime = Date.now();
496
+ while (Date.now() - startTime < timeout) {
497
+ await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
498
+ const statusResponse = await this.client.searchRecords('sys_flow_context', `sys_id=${executionId}`, 1);
499
+ if (statusResponse.success && statusResponse.data.result.length) {
500
+ const execution = statusResponse.data.result[0];
501
+ if (execution.status !== 'running') {
502
+ return {
503
+ content: [{
504
+ type: 'text',
505
+ text: `āœ… Flow execution completed!
490
506
 
491
- ⚔ **${args.name}**
492
- šŸ†” sys_id: ${response.data.sys_id}
493
- šŸ“Š Type: ${args.type}
494
- šŸ”¢ Order: ${args.order}
495
- ${args.table ? `šŸ“‹ Table: ${args.table}` : ''}
507
+ šŸ”„ **${flow.name}**
508
+ šŸ†” Execution ID: ${executionId}
509
+ šŸ“Š Status: ${execution.status}
510
+ ā±ļø Duration: ${execution.duration || 'N/A'}
511
+ ${execution.error ? `āŒ Error: ${execution.error}` : ''}
496
512
 
497
- ✨ Action added to flow!`
498
- }]
499
- };
500
- }
501
- catch (error) {
502
- this.logger.error('Failed to create flow action:', error);
503
- throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create flow action: ${error}`);
504
- }
505
- }
506
- async createSubflow(args) {
507
- try {
508
- this.logger.info('Creating subflow...');
509
- const subflowData = {
510
- name: args.name,
511
- description: args.description || '',
512
- inputs: args.inputs ? JSON.stringify(args.inputs) : '',
513
- outputs: args.outputs ? JSON.stringify(args.outputs) : '',
514
- category: args.category || 'custom',
515
- sys_class_name: 'sys_hub_sub_flow'
516
- };
517
- const response = await this.client.createRecord('sys_hub_sub_flow', subflowData);
518
- if (!response.success) {
519
- throw new Error(`Failed to create subflow: ${response.error}`);
513
+ ✨ Flow execution finished!`
514
+ }]
515
+ };
516
+ }
517
+ }
518
+ }
519
+ return {
520
+ content: [{
521
+ type: 'text',
522
+ text: `ā° Flow execution timeout!
523
+
524
+ šŸ”„ **${flow.name}**
525
+ šŸ†” Execution ID: ${executionId}
526
+ ā±ļø Timeout: ${args.timeout || 60} seconds
527
+
528
+ āš ļø Flow is still running. Use snow_get_flow_execution_status to check progress.`
529
+ }]
530
+ };
520
531
  }
521
532
  return {
522
533
  content: [{
523
534
  type: 'text',
524
- text: `āœ… Subflow created!
535
+ text: `āœ… Flow execution started!
525
536
 
526
- šŸ”„ **${args.name}**
527
- šŸ†” sys_id: ${response.data.sys_id}
528
- šŸ“‚ Category: ${args.category || 'custom'}
529
- šŸ“„ Inputs: ${args.inputs ? args.inputs.length : 0}
530
- šŸ“¤ Outputs: ${args.outputs ? args.outputs.length : 0}
537
+ šŸ”„ **${flow.name}**
538
+ šŸ†” Execution ID: ${executionId}
539
+ šŸ“Š Status: running
540
+ ${args.record_id ? `šŸ“‹ Record: ${args.record_id}` : ''}
531
541
 
532
- ✨ Subflow ready for reuse!`
542
+ ✨ Use snow_get_flow_execution_status to monitor progress.`
533
543
  }]
534
544
  };
535
545
  }
536
546
  catch (error) {
537
- this.logger.error('Failed to create subflow:', error);
538
- throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create subflow: ${error}`);
547
+ this.logger.error('Failed to execute flow:', error);
548
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to execute flow: ${error}`);
539
549
  }
540
550
  }
541
- async createFlowTrigger(args) {
551
+ async getFlowExecutionStatus(args) {
542
552
  try {
543
- this.logger.info('Creating flow trigger...');
544
- const triggerData = {
545
- flow: args.flow,
546
- type: args.type,
547
- table: args.table || '',
548
- condition: args.condition || '',
549
- schedule: args.schedule || '',
550
- active: args.active !== false
551
- };
552
- const response = await this.client.createRecord('sys_hub_trigger_instance', triggerData);
553
- if (!response.success) {
554
- throw new Error(`Failed to create flow trigger: ${response.error}`);
553
+ this.logger.info(`Getting flow execution status: ${args.execution_id}`);
554
+ const response = await this.client.searchRecords('sys_flow_context', `sys_id=${args.execution_id}`, 1);
555
+ if (!response.success || !response.data.result.length) {
556
+ throw new Error(`Flow execution not found: ${args.execution_id}`);
557
+ }
558
+ const execution = response.data.result[0];
559
+ // Get flow details
560
+ const flowResponse = await this.client.searchRecords('sys_hub_flow', `sys_id=${execution.flow}`, 1);
561
+ const flowName = flowResponse.success && flowResponse.data.result.length ?
562
+ flowResponse.data.result[0].name : execution.flow;
563
+ let logInfo = '';
564
+ if (args.include_logs !== false) {
565
+ // Get execution logs if available
566
+ const logsResponse = await this.client.searchRecords('sys_flow_log', `context=${args.execution_id}`, 10);
567
+ if (logsResponse.success && logsResponse.data.result.length) {
568
+ const logs = logsResponse.data.result.slice(0, 5); // Show last 5 logs
569
+ logInfo = `\n\nšŸ“‹ **Recent Logs**:\n${logs.map((log) => `• ${log.level}: ${log.message}`).join('\n')}`;
570
+ }
571
+ }
572
+ let variableInfo = '';
573
+ if (args.include_variables && execution.variables) {
574
+ try {
575
+ const variables = JSON.parse(execution.variables);
576
+ variableInfo = `\n\nšŸ”§ **Variables**:\n${Object.entries(variables)
577
+ .slice(0, 5)
578
+ .map(([key, value]) => `• ${key}: ${value}`)
579
+ .join('\n')}`;
580
+ }
581
+ catch (e) {
582
+ // Variables not in JSON format
583
+ }
555
584
  }
556
585
  return {
557
586
  content: [{
558
587
  type: 'text',
559
- text: `āœ… Flow Trigger created!
588
+ text: `šŸ“Š Flow Execution Status:
560
589
 
561
- ⚔ **Trigger Configuration**
562
- šŸ†” sys_id: ${response.data.sys_id}
563
- šŸ“Š Type: ${args.type}
564
- ${args.table ? `šŸ“‹ Table: ${args.table}` : ''}
565
- ${args.schedule ? `ā° Schedule: ${args.schedule}` : ''}
566
- šŸ”„ Active: ${args.active !== false ? 'Yes' : 'No'}
590
+ šŸ”„ **${flowName}**
591
+ šŸ†” Execution ID: ${args.execution_id}
592
+ šŸ“Š Status: ${execution.status}
593
+ šŸ“… Started: ${execution.started}
594
+ ā±ļø Duration: ${execution.duration || 'In progress'}
595
+ ${execution.error ? `āŒ Error: ${execution.error}` : ''}${logInfo}${variableInfo}
567
596
 
568
- ✨ Trigger configured for flow!`
597
+ ✨ Execution details retrieved successfully!`
569
598
  }]
570
599
  };
571
600
  }
572
601
  catch (error) {
573
- this.logger.error('Failed to create flow trigger:', error);
574
- throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create flow trigger: ${error}`);
602
+ this.logger.error('Failed to get flow execution status:', error);
603
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get flow execution status: ${error}`);
575
604
  }
576
605
  }
577
- async testFlow(args) {
606
+ async getFlowExecutionHistory(args) {
578
607
  try {
579
- this.logger.info('Testing flow...');
580
- const testData = {
581
- flow: args.flow,
582
- test_data: args.test_data ? JSON.stringify(args.test_data) : '{}',
583
- debug: args.debug !== false,
584
- status: 'running'
585
- };
586
- const response = await this.client.createRecord('sys_flow_test_result', testData);
608
+ this.logger.info(`Getting flow execution history: ${args.flow_id}`);
609
+ // Find the flow first
610
+ const flowQuery = args.flow_id.length === 32 ? `sys_id=${args.flow_id}` : `name=${args.flow_id}`;
611
+ const flowResponse = await this.client.searchRecords('sys_hub_flow', flowQuery, 1);
612
+ if (!flowResponse.success || !flowResponse.data.result.length) {
613
+ throw new Error(`Flow not found: ${args.flow_id}`);
614
+ }
615
+ const flow = flowResponse.data.result[0];
616
+ // Build execution history query
617
+ let query = `flow=${flow.sys_id}`;
618
+ if (args.days) {
619
+ const daysAgo = new Date();
620
+ daysAgo.setDate(daysAgo.getDate() - args.days);
621
+ query += `^started>${daysAgo.toISOString()}`;
622
+ }
623
+ if (args.status_filter) {
624
+ query += `^status=${args.status_filter}`;
625
+ }
626
+ const limit = args.limit || 50;
627
+ const response = await this.client.searchRecords('sys_flow_context', query, limit);
587
628
  if (!response.success) {
588
- // Fallback message if table doesn't exist
629
+ throw new Error('Failed to get flow execution history');
630
+ }
631
+ const executions = response.data.result;
632
+ if (!executions.length) {
589
633
  return {
590
634
  content: [{
591
635
  type: 'text',
592
- text: `āš ļø Flow Test initiated!
636
+ text: `šŸ“Š Flow Execution History:
593
637
 
594
- šŸ”„ Flow: ${args.flow}
595
- 🧪 Test Data: ${args.test_data ? 'Provided' : 'Default'}
596
- šŸ› Debug: ${args.debug !== false ? 'Enabled' : 'Disabled'}
638
+ šŸ”„ **${flow.name}**
597
639
 
598
- ✨ Test running. Check Flow Designer for results.`
640
+ āŒ No executions found for the specified criteria.`
599
641
  }]
600
642
  };
601
643
  }
644
+ // Calculate statistics
645
+ const stats = {
646
+ total: executions.length,
647
+ completed: executions.filter((e) => e.status === 'completed').length,
648
+ failed: executions.filter((e) => e.status === 'failed').length,
649
+ running: executions.filter((e) => e.status === 'running').length,
650
+ cancelled: executions.filter((e) => e.status === 'cancelled').length
651
+ };
652
+ const successRate = stats.total > 0 ? ((stats.completed / stats.total) * 100).toFixed(1) : '0';
653
+ const executionList = executions.slice(0, 10).map((exec) => `• ${exec.started} | ${exec.status} | ${exec.duration || 'N/A'}${exec.error ? ' | Error: ' + exec.error.substring(0, 50) : ''}`).join('\n');
602
654
  return {
603
655
  content: [{
604
656
  type: 'text',
605
- text: `āœ… Flow Test started!
657
+ text: `šŸ“Š Flow Execution History:
606
658
 
607
- šŸ†” Test ID: ${response.data.sys_id}
608
- šŸ”„ Flow: ${args.flow}
609
- 🧪 Test Data: ${args.test_data ? 'Custom' : 'Default'}
610
- šŸ› Debug Mode: ${args.debug !== false ? 'On' : 'Off'}
659
+ šŸ”„ **${flow.name}**
611
660
 
612
- ✨ Test execution in progress!`
661
+ šŸ“ˆ **Statistics** (${args.days || 'All time'} days):
662
+ • Total: ${stats.total}
663
+ • āœ… Completed: ${stats.completed}
664
+ • āŒ Failed: ${stats.failed}
665
+ • šŸ”„ Running: ${stats.running}
666
+ • āøļø Cancelled: ${stats.cancelled}
667
+ • šŸ“Š Success Rate: ${successRate}%
668
+
669
+ šŸ“‹ **Recent Executions**:
670
+ ${executionList}
671
+
672
+ ✨ Showing ${Math.min(10, executions.length)} of ${stats.total} execution(s)`
613
673
  }]
614
674
  };
615
675
  }
616
676
  catch (error) {
617
- this.logger.error('Failed to test flow:', error);
618
- throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to test flow: ${error}`);
677
+ this.logger.error('Failed to get flow execution history:', error);
678
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get flow execution history: ${error}`);
619
679
  }
620
680
  }
621
- async getFlowExecution(args) {
681
+ async getFlowDetails(args) {
622
682
  try {
623
- this.logger.info('Getting flow execution...');
624
- let query = '';
625
- if (args.flow) {
626
- query = `flow=${args.flow}`;
683
+ this.logger.info(`Getting flow details: ${args.flow_id}`);
684
+ // Find the flow
685
+ const flowQuery = args.flow_id.length === 32 ? `sys_id=${args.flow_id}` : `name=${args.flow_id}`;
686
+ const flowResponse = await this.client.searchRecords('sys_hub_flow', flowQuery, 1);
687
+ if (!flowResponse.success || !flowResponse.data.result.length) {
688
+ throw new Error(`Flow not found: ${args.flow_id}`);
627
689
  }
628
- if (args.execution_id) {
629
- query = `sys_id=${args.execution_id}`;
690
+ const flow = flowResponse.data.result[0];
691
+ let actionsInfo = '';
692
+ if (args.include_actions !== false) {
693
+ const actionsResponse = await this.client.searchRecords('sys_hub_action_instance', `flow=${flow.sys_id}`, 20);
694
+ if (actionsResponse.success && actionsResponse.data.result.length) {
695
+ const actions = actionsResponse.data.result;
696
+ actionsInfo = `\n\n⚔ **Actions** (${actions.length}):\n${actions.map((action) => `• ${action.order || '?'}: ${action.name} (${action.type})`).join('\n')}`;
697
+ }
630
698
  }
631
- const limit = args.limit || 10;
632
- const response = await this.client.searchRecords('sys_flow_context', query, limit);
633
- if (!response.success) {
634
- throw new Error('Failed to get flow execution');
699
+ let triggersInfo = '';
700
+ if (args.include_triggers !== false) {
701
+ const triggersResponse = await this.client.searchRecords('sys_hub_trigger_instance', `flow=${flow.sys_id}`, 10);
702
+ if (triggersResponse.success && triggersResponse.data.result.length) {
703
+ const triggers = triggersResponse.data.result;
704
+ triggersInfo = `\n\nšŸŽÆ **Triggers** (${triggers.length}):\n${triggers.map((trigger) => `• ${trigger.type}: ${trigger.condition || 'Always'} ${trigger.active ? 'āœ…' : 'āŒ'}`).join('\n')}`;
705
+ }
635
706
  }
636
- const executions = response.data.result;
637
- if (!executions.length) {
638
- return {
639
- content: [{
640
- type: 'text',
641
- text: 'āŒ No flow executions found'
642
- }]
643
- };
707
+ let variablesInfo = '';
708
+ if (args.include_variables && flow.variables) {
709
+ try {
710
+ const variables = JSON.parse(flow.variables);
711
+ variablesInfo = `\n\nšŸ”§ **Variables** (${Object.keys(variables).length}):\n${Object.entries(variables)
712
+ .slice(0, 10)
713
+ .map(([key, value]) => `• ${key}: ${typeof value} = ${JSON.stringify(value)}`)
714
+ .join('\n')}`;
715
+ }
716
+ catch (e) {
717
+ variablesInfo = `\n\nšŸ”§ **Variables**: Raw format (not JSON)`;
718
+ }
644
719
  }
645
- const executionList = executions.map((exec) => `šŸ”„ **Execution ${exec.sys_id}**
646
- šŸ“… Started: ${exec.started}
647
- ā±ļø Duration: ${exec.duration || 'Running'}
648
- šŸ“Š Status: ${exec.status}
649
- ${exec.error ? `āŒ Error: ${exec.error}` : ''}`).join('\n\n');
650
720
  return {
651
721
  content: [{
652
722
  type: 'text',
653
- text: `šŸ“Š Flow Execution History:
723
+ text: `šŸ”„ Flow Details:
654
724
 
655
- ${executionList}
725
+ **${flow.name}** ${flow.active ? 'āœ…' : 'āŒ'}
726
+ šŸ†” sys_id: ${flow.sys_id}
727
+ šŸ“‹ Table: ${flow.table || 'N/A'}
728
+ ⚔ Trigger Type: ${flow.trigger_type || 'N/A'}
729
+ šŸ‘¤ Run As: ${flow.run_as || 'System'}
730
+ šŸ“ Description: ${flow.description || 'No description'}
731
+ šŸ”„ Version: ${flow.version || '1.0'}
732
+ šŸ“… Created: ${flow.sys_created_on}
733
+ šŸ“… Updated: ${flow.sys_updated_on}${actionsInfo}${triggersInfo}${variablesInfo}
734
+
735
+ ✨ Flow details retrieved successfully!
656
736
 
657
- ✨ Showing ${executions.length} execution(s)`
737
+ āš ļø **Note**: Flow creation and modification must be done through Flow Designer UI.`
658
738
  }]
659
739
  };
660
740
  }
661
741
  catch (error) {
662
- this.logger.error('Failed to get flow execution:', error);
663
- throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get flow execution: ${error}`);
742
+ this.logger.error('Failed to get flow details:', error);
743
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get flow details: ${error}`);
664
744
  }
665
745
  }
666
- async discoverFlows(args) {
746
+ async importFlowFromXml(args) {
667
747
  try {
668
- this.logger.info('Discovering flows...');
669
- let query = '';
670
- if (args.table) {
671
- query = `table=${args.table}`;
748
+ this.logger.info('Importing flow from XML...');
749
+ if (!args.xml_content && !args.update_set) {
750
+ throw new Error('Either xml_content or update_set must be provided');
672
751
  }
673
- if (args.active_only) {
674
- query += query ? '^' : '';
675
- query += 'active=true';
752
+ let importResult;
753
+ if (args.update_set) {
754
+ // Import from update set
755
+ const updateSetData = {
756
+ source_table: 'sys_update_set',
757
+ source_sys_id: args.update_set,
758
+ target_table: 'sys_hub_flow',
759
+ overwrite_existing: args.overwrite_existing || false
760
+ };
761
+ this.logger.trackAPICall('CREATE', 'sys_import_set_row', 1);
762
+ importResult = await this.client.createRecord('sys_import_set_row', updateSetData);
676
763
  }
677
- this.logger.trackAPICall('SEARCH', 'sys_hub_flow', 50);
678
- const response = await this.client.searchRecords('sys_hub_flow', query, 50);
679
- if (!response.success) {
680
- throw new Error('Failed to discover flows');
764
+ else if (args.xml_content) {
765
+ // Import from XML content
766
+ const importData = {
767
+ content: args.xml_content,
768
+ content_type: 'xml',
769
+ import_action: 'insert_or_update',
770
+ overwrite_existing: args.overwrite_existing || false
771
+ };
772
+ this.logger.trackAPICall('CREATE', 'sys_import_set_row', 1);
773
+ importResult = await this.client.createRecord('sys_import_set_row', importData);
681
774
  }
682
- const flows = response.data.result;
683
- // Get subflows if requested
684
- let subflows = [];
685
- if (args.include_subflows) {
686
- const subflowResponse = await this.client.searchRecords('sys_hub_sub_flow', '', 50);
687
- if (subflowResponse.success) {
688
- subflows = subflowResponse.data.result;
689
- }
775
+ if (!importResult || !importResult.success) {
776
+ throw new Error(`Failed to import flow: ${importResult?.error || 'Unknown error'}`);
777
+ }
778
+ // If activate_after_import is true, try to activate imported flows
779
+ if (args.activate_after_import) {
780
+ // This is a best effort - flow activation depends on the import results
781
+ this.logger.info('Attempting to activate imported flows...');
690
782
  }
691
- const flowList = flows.map((flow) => `šŸ”„ **${flow.name}** ${flow.active ? 'āœ…' : 'āŒ'}
692
- šŸ“‹ Table: ${flow.table || 'N/A'}
693
- šŸ“ ${flow.description || 'No description'}`).join('\n\n');
694
- const subflowList = subflows.map((subflow) => `šŸ”„ **${subflow.name}** (Subflow)
695
- šŸ“‚ Category: ${subflow.category || 'custom'}
696
- šŸ“ ${subflow.description || 'No description'}`).join('\n\n');
697
783
  return {
698
784
  content: [{
699
785
  type: 'text',
700
- text: `šŸ” Discovered Flows:
786
+ text: `āœ… Flow import initiated!
701
787
 
702
- ${flowList}
788
+ šŸ“„ **Import Details**
789
+ šŸ†” Import ID: ${importResult.data.sys_id}
790
+ šŸ“„ Source: ${args.update_set ? 'Update Set' : 'XML Content'}
791
+ šŸ”„ Overwrite: ${args.overwrite_existing ? 'Yes' : 'No'}
792
+ ⚔ Auto-activate: ${args.activate_after_import ? 'Yes' : 'No'}
703
793
 
704
- ${args.include_subflows && subflows.length ? `\nšŸ”„ Subflows:\n\n${subflowList}` : ''}
794
+ āš ļø **Important Notes**:
795
+ • Import processing may take a few minutes
796
+ • Check sys_import_log for detailed results
797
+ • Imported flows may need manual activation in Flow Designer
798
+ • Complex flows might require dependency resolution
705
799
 
706
- ✨ Found ${flows.length} flow(s)${args.include_subflows ? ` and ${subflows.length} subflow(s)` : ''}`
800
+ ✨ This is the ONLY supported way to create flows programmatically!`
707
801
  }]
708
802
  };
709
803
  }
710
804
  catch (error) {
711
- this.logger.error('Failed to discover flows:', error);
712
- throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover flows: ${error}`);
805
+ this.logger.error('Failed to import flow from XML:', error);
806
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to import flow from XML: ${error}`);
713
807
  }
714
808
  }
715
809
  // Agent Workspace Implementation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-flow",
3
- "version": "3.4.18",
3
+ "version": "3.4.19",
4
4
  "description": "ServiceNow development framework with MCP server integration. Executes background scripts using ES5 JavaScript only (ServiceNow Rhino engine requirement). Provides 17 MCP servers for complete ServiceNow operations including widget deployment with coherence validation, table operations, script execution, machine learning, advanced features, and comprehensive platform management.",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",
@@ -35,10 +35,14 @@ Specializes in creating ServiceNow Flow Designer workflows.
35
35
  - Error handling design
36
36
 
37
37
  **MCP Tools Used:**
38
- - `snow_create_flow` - Create flows from natural language
39
- - `snow_test_flow_with_mock` - Test flows with mock data
38
+ - `snow_list_flows` - Discover existing flows
39
+ - `snow_execute_flow` - Execute flows programmatically
40
+ - `snow_get_flow_execution_status` - Monitor flow execution
41
+ - `snow_import_flow_from_xml` - Import flows from XML (only creation method)
40
42
  - `snow_link_catalog_to_flow` - Link flows to catalog items
41
43
 
44
+ **Note:** Flow creation must be done through Flow Designer UI, not programmatically
45
+
42
46
  ### 3. Script Writer Agent (`script-writer-agent.ts`)
43
47
  Specializes in creating ServiceNow scripts.
44
48