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 +6 -2
- package/dist/mcp/servicenow-flow-workspace-mobile-mcp.js +361 -267
- package/package.json +1 -1
- package/src/agents/README.md +6 -2
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
|
-
- `
|
|
330
|
-
- `
|
|
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: '
|
|
37
|
-
description: '
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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: '
|
|
54
|
-
description: '
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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: ['
|
|
61
|
+
required: ['flow_id']
|
|
68
62
|
}
|
|
69
63
|
},
|
|
70
64
|
{
|
|
71
|
-
name: '
|
|
72
|
-
description: '
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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: ['
|
|
74
|
+
required: ['execution_id']
|
|
83
75
|
}
|
|
84
76
|
},
|
|
85
77
|
{
|
|
86
|
-
name: '
|
|
87
|
-
description: '
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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: ['
|
|
88
|
+
required: ['flow_id']
|
|
99
89
|
}
|
|
100
90
|
},
|
|
101
91
|
{
|
|
102
|
-
name: '
|
|
103
|
-
description: '
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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: ['
|
|
102
|
+
required: ['flow_id']
|
|
112
103
|
}
|
|
113
104
|
},
|
|
114
105
|
{
|
|
115
|
-
name: '
|
|
116
|
-
description: '
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 '
|
|
354
|
-
result = await this.
|
|
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 '
|
|
360
|
-
result = await this.
|
|
336
|
+
case 'snow_execute_flow':
|
|
337
|
+
result = await this.executeFlow(args);
|
|
361
338
|
break;
|
|
362
|
-
case '
|
|
363
|
-
result = await this.
|
|
339
|
+
case 'snow_get_flow_execution_status':
|
|
340
|
+
result = await this.getFlowExecutionStatus(args);
|
|
364
341
|
break;
|
|
365
|
-
case '
|
|
366
|
-
result = await this.
|
|
342
|
+
case 'snow_get_flow_execution_history':
|
|
343
|
+
result = await this.getFlowExecutionHistory(args);
|
|
367
344
|
break;
|
|
368
|
-
case '
|
|
369
|
-
result = await this.
|
|
345
|
+
case 'snow_get_flow_details':
|
|
346
|
+
result = await this.getFlowDetails(args);
|
|
370
347
|
break;
|
|
371
|
-
case '
|
|
372
|
-
result = await this.
|
|
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
|
|
405
|
+
// Flow Designer Implementation - Real APIs Only
|
|
406
|
+
async listFlows(args) {
|
|
430
407
|
try {
|
|
431
|
-
this.logger.info('
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const
|
|
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(
|
|
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:
|
|
448
|
+
text: `š Flow Inventory:
|
|
452
449
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
āØ
|
|
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
|
|
465
|
-
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to
|
|
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
|
|
464
|
+
async executeFlow(args) {
|
|
469
465
|
try {
|
|
470
|
-
this.logger.info(
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
482
|
-
|
|
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
|
|
489
|
+
throw new Error(`Failed to execute flow: ${response.error}`);
|
|
485
490
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
492
|
-
š
|
|
493
|
-
š
|
|
494
|
-
|
|
495
|
-
${
|
|
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
|
-
āØ
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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: `ā
|
|
535
|
+
text: `ā
Flow execution started!
|
|
525
536
|
|
|
526
|
-
š **${
|
|
527
|
-
š
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
āØ
|
|
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
|
|
538
|
-
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to
|
|
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
|
|
551
|
+
async getFlowExecutionStatus(args) {
|
|
542
552
|
try {
|
|
543
|
-
this.logger.info(
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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:
|
|
588
|
+
text: `š Flow Execution Status:
|
|
560
589
|
|
|
561
|
-
|
|
562
|
-
š
|
|
563
|
-
š
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
āØ
|
|
597
|
+
⨠Execution details retrieved successfully!`
|
|
569
598
|
}]
|
|
570
599
|
};
|
|
571
600
|
}
|
|
572
601
|
catch (error) {
|
|
573
|
-
this.logger.error('Failed to
|
|
574
|
-
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to
|
|
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
|
|
606
|
+
async getFlowExecutionHistory(args) {
|
|
578
607
|
try {
|
|
579
|
-
this.logger.info(
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
586
|
-
const
|
|
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
|
-
|
|
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:
|
|
636
|
+
text: `š Flow Execution History:
|
|
593
637
|
|
|
594
|
-
š
|
|
595
|
-
š§Ŗ Test Data: ${args.test_data ? 'Provided' : 'Default'}
|
|
596
|
-
š Debug: ${args.debug !== false ? 'Enabled' : 'Disabled'}
|
|
638
|
+
š **${flow.name}**
|
|
597
639
|
|
|
598
|
-
|
|
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:
|
|
657
|
+
text: `š Flow Execution History:
|
|
606
658
|
|
|
607
|
-
|
|
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
|
-
|
|
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
|
|
618
|
-
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to
|
|
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
|
|
681
|
+
async getFlowDetails(args) {
|
|
622
682
|
try {
|
|
623
|
-
this.logger.info(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
|
|
629
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
637
|
-
if (
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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:
|
|
723
|
+
text: `š Flow Details:
|
|
654
724
|
|
|
655
|
-
${
|
|
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
|
-
|
|
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
|
|
663
|
-
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get flow
|
|
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
|
|
746
|
+
async importFlowFromXml(args) {
|
|
667
747
|
try {
|
|
668
|
-
this.logger.info('
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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:
|
|
786
|
+
text: `ā
Flow import initiated!
|
|
701
787
|
|
|
702
|
-
|
|
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
|
-
|
|
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
|
-
āØ
|
|
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
|
|
712
|
-
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to
|
|
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.
|
|
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",
|
package/src/agents/README.md
CHANGED
|
@@ -35,10 +35,14 @@ Specializes in creating ServiceNow Flow Designer workflows.
|
|
|
35
35
|
- Error handling design
|
|
36
36
|
|
|
37
37
|
**MCP Tools Used:**
|
|
38
|
-
- `
|
|
39
|
-
- `
|
|
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
|
|