snow-flow 10.0.1-dev.361 → 10.0.1-dev.388

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "10.0.1-dev.361",
3
+ "version": "10.0.1-dev.388",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -82,6 +82,7 @@ import * as integrationTools from '../tools/integration/index.js';
82
82
  import * as platformTools from '../tools/platform/index.js';
83
83
  import * as devopsTools from '../tools/devops/index.js';
84
84
  import * as virtualAgentTools from '../tools/virtual-agent/index.js';
85
+ import * as flowDesignerTools from '../tools/flow-designer/index.js';
85
86
 
86
87
  // ES Module compatible __dirname
87
88
  const __filename = fileURLToPath(import.meta.url);
@@ -148,6 +149,7 @@ const STATIC_TOOL_MODULES: Record<string, any> = {
148
149
  'platform': platformTools,
149
150
  'devops': devopsTools,
150
151
  'virtual-agent': virtualAgentTools,
152
+ 'flow-designer': flowDesignerTools,
151
153
  };
152
154
 
153
155
  export class ToolRegistry {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Flow Designer Tools - Export all flow designer tool modules
3
+ */
4
+
5
+ export { toolDefinition as snow_manage_flow_def, execute as snow_manage_flow_exec } from './snow_manage_flow.js';
@@ -0,0 +1,867 @@
1
+ /**
2
+ * snow_manage_flow - Complete Flow Designer lifecycle management
3
+ *
4
+ * Create, list, get, update, activate, deactivate, delete and publish
5
+ * Flow Designer flows and subflows via Table API with background script fallback.
6
+ *
7
+ * ServiceNow does NOT expose a public Flow Designer creation API.
8
+ * This tool uses the Table API (sys_hub_flow) with a GlideRecord fallback
9
+ * when the caller lacks direct table write permissions (403).
10
+ */
11
+
12
+ import { MCPToolDefinition, ServiceNowContext, ToolResult } from '../../shared/types.js';
13
+ import { getAuthenticatedClient } from '../../shared/auth.js';
14
+ import { createSuccessResult, createErrorResult, SnowFlowError, ErrorType } from '../../shared/error-handler.js';
15
+ import { summary } from '../../shared/output-formatter.js';
16
+
17
+ // ── helpers ────────────────────────────────────────────────────────────
18
+
19
+ function sanitizeInternalName(name: string): string {
20
+ return name
21
+ .toLowerCase()
22
+ .replace(/[^a-z0-9_]/g, '_')
23
+ .replace(/_+/g, '_')
24
+ .replace(/^_|_$/g, '');
25
+ }
26
+
27
+ function isSysId(value: string): boolean {
28
+ return /^[a-f0-9]{32}$/.test(value);
29
+ }
30
+
31
+ /**
32
+ * Resolve a flow name to its sys_id. If the value is already a 32-char hex
33
+ * string it is returned as-is.
34
+ */
35
+ async function resolveFlowId(client: any, flowId: string): Promise<string> {
36
+ if (isSysId(flowId)) return flowId;
37
+
38
+ var lookup = await client.get('/api/now/table/sys_hub_flow', {
39
+ params: {
40
+ sysparm_query: 'name=' + flowId,
41
+ sysparm_fields: 'sys_id',
42
+ sysparm_limit: 1
43
+ }
44
+ });
45
+
46
+ if (!lookup.data.result || lookup.data.result.length === 0) {
47
+ throw new SnowFlowError(ErrorType.NOT_FOUND, 'Flow not found: ' + flowId);
48
+ }
49
+ return lookup.data.result[0].sys_id;
50
+ }
51
+
52
+ // ── tool definition ────────────────────────────────────────────────────
53
+
54
+ export const toolDefinition: MCPToolDefinition = {
55
+ name: 'snow_manage_flow',
56
+ description: 'Complete Flow Designer lifecycle: create flows/subflows, list, get details, update, activate, deactivate, delete and publish',
57
+ category: 'automation',
58
+ subcategory: 'flow-designer',
59
+ use_cases: ['flow-designer', 'automation', 'flow-management', 'subflow'],
60
+ complexity: 'advanced',
61
+ frequency: 'high',
62
+ permission: 'write',
63
+ allowedRoles: ['developer', 'admin'],
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: {
67
+ action: {
68
+ type: 'string',
69
+ enum: ['create', 'create_subflow', 'list', 'get', 'update', 'activate', 'deactivate', 'delete', 'publish'],
70
+ description: 'Action to perform'
71
+ },
72
+
73
+ // ── shared identifiers ──
74
+ flow_id: {
75
+ type: 'string',
76
+ description: 'Flow sys_id or name (required for get, update, activate, deactivate, delete, publish)'
77
+ },
78
+
79
+ // ── create / create_subflow params ──
80
+ name: {
81
+ type: 'string',
82
+ description: 'Flow name (required for create / create_subflow)'
83
+ },
84
+ description: {
85
+ type: 'string',
86
+ description: 'Flow description'
87
+ },
88
+ trigger_type: {
89
+ type: 'string',
90
+ enum: ['record_created', 'record_updated', 'scheduled', 'manual'],
91
+ description: 'Trigger type (create only, default: manual)',
92
+ default: 'manual'
93
+ },
94
+ table: {
95
+ type: 'string',
96
+ description: 'Table for record-based triggers (e.g. "incident") or list filter'
97
+ },
98
+ trigger_condition: {
99
+ type: 'string',
100
+ description: 'Encoded query condition for the trigger'
101
+ },
102
+ category: {
103
+ type: 'string',
104
+ description: 'Flow category',
105
+ default: 'custom'
106
+ },
107
+ run_as: {
108
+ type: 'string',
109
+ enum: ['user', 'system'],
110
+ description: 'Run-as context (default: user)',
111
+ default: 'user'
112
+ },
113
+ activities: {
114
+ type: 'array',
115
+ description: 'Flow action steps',
116
+ items: {
117
+ type: 'object',
118
+ properties: {
119
+ name: { type: 'string', description: 'Step name' },
120
+ type: {
121
+ type: 'string',
122
+ enum: ['notification', 'field_update', 'create_record', 'script', 'log', 'wait', 'approval'],
123
+ description: 'Action type'
124
+ },
125
+ inputs: { type: 'object', description: 'Step-specific input values' }
126
+ }
127
+ }
128
+ },
129
+ inputs: {
130
+ type: 'array',
131
+ description: 'Flow input variables',
132
+ items: {
133
+ type: 'object',
134
+ properties: {
135
+ name: { type: 'string' },
136
+ label: { type: 'string' },
137
+ type: { type: 'string', enum: ['string', 'integer', 'boolean', 'reference', 'object', 'array'] },
138
+ mandatory: { type: 'boolean' },
139
+ default_value: { type: 'string' }
140
+ }
141
+ }
142
+ },
143
+ outputs: {
144
+ type: 'array',
145
+ description: 'Flow output variables',
146
+ items: {
147
+ type: 'object',
148
+ properties: {
149
+ name: { type: 'string' },
150
+ label: { type: 'string' },
151
+ type: { type: 'string', enum: ['string', 'integer', 'boolean', 'reference', 'object', 'array'] }
152
+ }
153
+ }
154
+ },
155
+ activate: {
156
+ type: 'boolean',
157
+ description: 'Activate flow after creation (default: true)',
158
+ default: true
159
+ },
160
+
161
+ // ── list params ──
162
+ type: {
163
+ type: 'string',
164
+ enum: ['flow', 'subflow', 'all'],
165
+ description: 'Filter by type (list only, default: all)',
166
+ default: 'all'
167
+ },
168
+ active_only: {
169
+ type: 'boolean',
170
+ description: 'Only list active flows (default: true)',
171
+ default: true
172
+ },
173
+ limit: {
174
+ type: 'number',
175
+ description: 'Max results for list',
176
+ default: 50
177
+ },
178
+
179
+ // ── update params ──
180
+ update_fields: {
181
+ type: 'object',
182
+ description: 'Fields to update (for update action)',
183
+ properties: {
184
+ name: { type: 'string' },
185
+ description: { type: 'string' },
186
+ category: { type: 'string' },
187
+ run_as: { type: 'string' }
188
+ }
189
+ }
190
+ },
191
+ required: ['action']
192
+ }
193
+ };
194
+
195
+ // ── execute ────────────────────────────────────────────────────────────
196
+
197
+ export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
198
+ var action = args.action;
199
+
200
+ try {
201
+ var client = await getAuthenticatedClient(context);
202
+
203
+ switch (action) {
204
+
205
+ // ────────────────────────────────────────────────────────────────
206
+ // CREATE
207
+ // ────────────────────────────────────────────────────────────────
208
+ case 'create':
209
+ case 'create_subflow': {
210
+ var flowName = args.name;
211
+ if (!flowName) {
212
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'name is required for ' + action);
213
+ }
214
+
215
+ var isSubflow = action === 'create_subflow';
216
+ var flowDescription = args.description || flowName;
217
+ var triggerType = isSubflow ? 'manual' : (args.trigger_type || 'manual');
218
+ var flowTable = args.table || '';
219
+ var triggerCondition = args.trigger_condition || '';
220
+ var flowCategory = args.category || 'custom';
221
+ var flowRunAs = isSubflow ? 'user_who_calls' : (args.run_as || 'user');
222
+ var activitiesArg = args.activities || [];
223
+ var inputsArg = args.inputs || [];
224
+ var outputsArg = args.outputs || [];
225
+ var shouldActivate = args.activate !== false;
226
+ var usedBackgroundScript = false;
227
+
228
+ // Build flow_definition JSON
229
+ var flowDefinition: any = {
230
+ name: flowName,
231
+ description: flowDescription,
232
+ trigger: {
233
+ type: triggerType,
234
+ table: flowTable,
235
+ condition: triggerCondition
236
+ },
237
+ activities: activitiesArg.map(function (act: any, idx: number) {
238
+ return {
239
+ name: act.name,
240
+ label: act.name,
241
+ type: act.type || 'script',
242
+ inputs: act.inputs || {},
243
+ order: (idx + 1) * 100,
244
+ active: true
245
+ };
246
+ }),
247
+ inputs: inputsArg.map(function (inp: any) {
248
+ return {
249
+ name: inp.name,
250
+ label: inp.label || inp.name,
251
+ type: inp.type || 'string',
252
+ mandatory: inp.mandatory || false,
253
+ default_value: inp.default_value || ''
254
+ };
255
+ }),
256
+ outputs: outputsArg.map(function (out: any) {
257
+ return {
258
+ name: out.name,
259
+ label: out.label || out.name,
260
+ type: out.type || 'string'
261
+ };
262
+ }),
263
+ version: '1.0'
264
+ };
265
+
266
+ // Remove trigger block for subflows
267
+ if (isSubflow) {
268
+ delete flowDefinition.trigger;
269
+ }
270
+
271
+ var flowData: any = {
272
+ name: flowName,
273
+ description: flowDescription,
274
+ active: shouldActivate,
275
+ internal_name: sanitizeInternalName(flowName),
276
+ category: flowCategory,
277
+ run_as: flowRunAs,
278
+ status: shouldActivate ? 'published' : 'draft',
279
+ validated: true,
280
+ type: isSubflow ? 'subflow' : 'flow',
281
+ flow_definition: JSON.stringify(flowDefinition),
282
+ latest_snapshot: JSON.stringify(flowDefinition)
283
+ };
284
+
285
+ // 1. Create flow record via Table API (with 403 fallback)
286
+ var flowResponse: any;
287
+ try {
288
+ flowResponse = await client.post('/api/now/table/sys_hub_flow', flowData);
289
+ } catch (tableError: any) {
290
+ if (tableError.response?.status === 403) {
291
+ usedBackgroundScript = true;
292
+ var createScript = [
293
+ "var gr = new GlideRecord('sys_hub_flow');",
294
+ "gr.initialize();",
295
+ "gr.name = " + JSON.stringify(flowName) + ";",
296
+ "gr.description = " + JSON.stringify(flowDescription) + ";",
297
+ "gr.active = " + shouldActivate + ";",
298
+ "gr.internal_name = " + JSON.stringify(sanitizeInternalName(flowName)) + ";",
299
+ "gr.category = " + JSON.stringify(flowCategory) + ";",
300
+ "gr.run_as = " + JSON.stringify(flowRunAs) + ";",
301
+ "gr.status = " + JSON.stringify(shouldActivate ? 'published' : 'draft') + ";",
302
+ "gr.validated = true;",
303
+ "gr.type = " + JSON.stringify(isSubflow ? 'subflow' : 'flow') + ";",
304
+ "gr.flow_definition = " + JSON.stringify(JSON.stringify(flowDefinition)) + ";",
305
+ "gr.latest_snapshot = " + JSON.stringify(JSON.stringify(flowDefinition)) + ";",
306
+ "var sysId = gr.insert();",
307
+ "if (sysId) {",
308
+ " gs.print(JSON.stringify({ sys_id: sysId, name: gr.name.toString() }));",
309
+ "} else {",
310
+ " gs.error('Failed to create flow');",
311
+ "}"
312
+ ].join('\n');
313
+
314
+ var scriptResp = await client.post('/api/now/table/sys_script_execution', {
315
+ script: createScript,
316
+ description: 'Create Flow: ' + flowName
317
+ });
318
+
319
+ var scriptOutput = scriptResp.data.result?.output || '';
320
+ var scriptMatch = scriptOutput.match(/\{[^}]+\}/);
321
+ if (scriptMatch) {
322
+ flowResponse = { data: { result: JSON.parse(scriptMatch[0]) } };
323
+ } else {
324
+ throw new Error('Failed to create flow via background script. Output: ' + scriptOutput);
325
+ }
326
+ } else {
327
+ throw tableError;
328
+ }
329
+ }
330
+
331
+ var createdFlow = flowResponse.data.result;
332
+ var flowSysId = createdFlow.sys_id;
333
+
334
+ // 2. Create trigger instance (non-manual flows only)
335
+ var triggerCreated = false;
336
+ if (!isSubflow && triggerType !== 'manual') {
337
+ try {
338
+ // Lookup trigger action type
339
+ var triggerTypeLookup: Record<string, string> = {
340
+ 'record_created': 'sn_fd.trigger.record_created',
341
+ 'record_updated': 'sn_fd.trigger.record_updated',
342
+ 'scheduled': 'sn_fd.trigger.scheduled'
343
+ };
344
+ var triggerInternalName = triggerTypeLookup[triggerType] || '';
345
+
346
+ if (triggerInternalName) {
347
+ var triggerDefResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
348
+ params: {
349
+ sysparm_query: 'internal_name=' + triggerInternalName,
350
+ sysparm_fields: 'sys_id',
351
+ sysparm_limit: 1
352
+ }
353
+ });
354
+
355
+ var triggerDefId = triggerDefResp.data.result?.[0]?.sys_id;
356
+ if (triggerDefId) {
357
+ var triggerData: any = {
358
+ flow: flowSysId,
359
+ action_type: triggerDefId,
360
+ name: triggerType,
361
+ order: 0,
362
+ active: true
363
+ };
364
+ if (flowTable) {
365
+ triggerData.table = flowTable;
366
+ }
367
+ if (triggerCondition) {
368
+ triggerData.condition = triggerCondition;
369
+ }
370
+
371
+ await client.post('/api/now/table/sys_hub_trigger_instance', triggerData);
372
+ triggerCreated = true;
373
+ }
374
+ }
375
+ } catch (triggerError) {
376
+ // Trigger creation is best-effort
377
+ }
378
+ }
379
+
380
+ // 3. Create action instances
381
+ var actionsCreated = 0;
382
+ for (var ai = 0; ai < activitiesArg.length; ai++) {
383
+ var activity = activitiesArg[ai];
384
+ try {
385
+ // Lookup action type definition
386
+ var actionTypeName = activity.type || 'script';
387
+ var actionTypeQuery = 'internal_nameLIKE' + actionTypeName + '^ORnameLIKE' + actionTypeName;
388
+
389
+ var actionDefResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
390
+ params: {
391
+ sysparm_query: actionTypeQuery,
392
+ sysparm_fields: 'sys_id,name,internal_name',
393
+ sysparm_limit: 1
394
+ }
395
+ });
396
+
397
+ var actionDefId = actionDefResp.data.result?.[0]?.sys_id;
398
+ var instanceData: any = {
399
+ flow: flowSysId,
400
+ name: activity.name,
401
+ order: (ai + 1) * 100,
402
+ active: true
403
+ };
404
+ if (actionDefId) {
405
+ instanceData.action_type = actionDefId;
406
+ }
407
+
408
+ await client.post('/api/now/table/sys_hub_action_instance', instanceData);
409
+ actionsCreated++;
410
+ } catch (actError) {
411
+ // Action creation is best-effort
412
+ }
413
+ }
414
+
415
+ // 4. Create flow variables (for subflows with inputs/outputs)
416
+ var varsCreated = 0;
417
+ if (isSubflow) {
418
+ for (var vi = 0; vi < inputsArg.length; vi++) {
419
+ var inp = inputsArg[vi];
420
+ try {
421
+ await client.post('/api/now/table/sys_hub_flow_variable', {
422
+ flow: flowSysId,
423
+ name: inp.name,
424
+ label: inp.label || inp.name,
425
+ type: inp.type || 'string',
426
+ mandatory: inp.mandatory || false,
427
+ default_value: inp.default_value || '',
428
+ variable_type: 'input'
429
+ });
430
+ varsCreated++;
431
+ } catch (varError) {
432
+ // Best-effort
433
+ }
434
+ }
435
+ for (var vo = 0; vo < outputsArg.length; vo++) {
436
+ var out = outputsArg[vo];
437
+ try {
438
+ await client.post('/api/now/table/sys_hub_flow_variable', {
439
+ flow: flowSysId,
440
+ name: out.name,
441
+ label: out.label || out.name,
442
+ type: out.type || 'string',
443
+ variable_type: 'output'
444
+ });
445
+ varsCreated++;
446
+ } catch (varError) {
447
+ // Best-effort
448
+ }
449
+ }
450
+ }
451
+
452
+ // 5. Activate if needed (and not already active from creation)
453
+ if (shouldActivate && usedBackgroundScript) {
454
+ try {
455
+ await client.patch('/api/now/table/sys_hub_flow/' + flowSysId, {
456
+ active: true,
457
+ status: 'published',
458
+ validated: true
459
+ });
460
+ } catch (activateError) {
461
+ // Best-effort
462
+ }
463
+ }
464
+
465
+ // 6. Best-effort snapshot
466
+ try {
467
+ await client.post('/api/sn_flow_designer/flow/snapshot', {
468
+ flow_id: flowSysId
469
+ });
470
+ } catch (snapError) {
471
+ // Snapshot API may not exist in all instances
472
+ }
473
+
474
+ // Build summary
475
+ var createSummary = summary()
476
+ .success('Created ' + (isSubflow ? 'subflow' : 'flow') + ': ' + flowName)
477
+ .field('sys_id', flowSysId)
478
+ .field('Type', isSubflow ? 'Subflow' : 'Flow')
479
+ .field('Category', flowCategory)
480
+ .field('Status', shouldActivate ? 'Published (active)' : 'Draft')
481
+ .field('Method', usedBackgroundScript ? 'Background script' : 'Table API');
482
+
483
+ if (!isSubflow && triggerType !== 'manual') {
484
+ createSummary.field('Trigger', triggerType + (triggerCreated ? ' (created)' : ' (best-effort)'));
485
+ if (flowTable) createSummary.field('Table', flowTable);
486
+ }
487
+ if (activitiesArg.length > 0) {
488
+ createSummary.field('Actions', actionsCreated + '/' + activitiesArg.length + ' created');
489
+ }
490
+ if (varsCreated > 0) {
491
+ createSummary.field('Variables', varsCreated + ' created');
492
+ }
493
+
494
+ return createSuccessResult({
495
+ created: true,
496
+ method: usedBackgroundScript ? 'background_script' : 'table_api',
497
+ flow: {
498
+ sys_id: flowSysId,
499
+ name: flowName,
500
+ type: isSubflow ? 'subflow' : 'flow',
501
+ category: flowCategory,
502
+ active: shouldActivate,
503
+ status: shouldActivate ? 'published' : 'draft'
504
+ },
505
+ trigger: !isSubflow && triggerType !== 'manual' ? {
506
+ type: triggerType,
507
+ table: flowTable,
508
+ condition: triggerCondition,
509
+ created: triggerCreated
510
+ } : null,
511
+ activities_created: actionsCreated,
512
+ activities_requested: activitiesArg.length,
513
+ variables_created: varsCreated
514
+ }, {}, createSummary.build());
515
+ }
516
+
517
+ // ────────────────────────────────────────────────────────────────
518
+ // LIST
519
+ // ────────────────────────────────────────────────────────────────
520
+ case 'list': {
521
+ var listQuery = '';
522
+ var filterType = args.type || 'all';
523
+ var activeOnly = args.active_only !== false;
524
+ var listLimit = args.limit || 50;
525
+ var filterTable = args.table || '';
526
+
527
+ if (filterType !== 'all') {
528
+ listQuery = 'type=' + filterType;
529
+ }
530
+ if (activeOnly) {
531
+ listQuery += (listQuery ? '^' : '') + 'active=true';
532
+ }
533
+ if (filterTable) {
534
+ listQuery += (listQuery ? '^' : '') + 'flow_definitionLIKE' + filterTable;
535
+ }
536
+
537
+ var listResp = await client.get('/api/now/table/sys_hub_flow', {
538
+ params: {
539
+ sysparm_query: listQuery || undefined,
540
+ sysparm_fields: 'sys_id,name,description,type,category,active,status,run_as,sys_created_on,sys_updated_on',
541
+ sysparm_limit: listLimit
542
+ }
543
+ });
544
+
545
+ var flows = (listResp.data.result || []).map(function (f: any) {
546
+ return {
547
+ sys_id: f.sys_id,
548
+ name: f.name,
549
+ description: f.description,
550
+ type: f.type,
551
+ category: f.category,
552
+ active: f.active === 'true',
553
+ status: f.status,
554
+ run_as: f.run_as,
555
+ created: f.sys_created_on,
556
+ updated: f.sys_updated_on
557
+ };
558
+ });
559
+
560
+ var listSummary = summary()
561
+ .success('Found ' + flows.length + ' flow' + (flows.length === 1 ? '' : 's'));
562
+
563
+ for (var li = 0; li < Math.min(flows.length, 15); li++) {
564
+ var lf = flows[li];
565
+ listSummary.bullet(
566
+ lf.name +
567
+ ' [' + (lf.type || 'flow') + ']' +
568
+ (lf.active ? '' : ' (inactive)')
569
+ );
570
+ }
571
+ if (flows.length > 15) {
572
+ listSummary.indented('... and ' + (flows.length - 15) + ' more');
573
+ }
574
+
575
+ return createSuccessResult({
576
+ action: 'list',
577
+ count: flows.length,
578
+ flows: flows
579
+ }, {}, listSummary.build());
580
+ }
581
+
582
+ // ────────────────────────────────────────────────────────────────
583
+ // GET
584
+ // ────────────────────────────────────────────────────────────────
585
+ case 'get': {
586
+ if (!args.flow_id) {
587
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for get action');
588
+ }
589
+
590
+ var getSysId = await resolveFlowId(client, args.flow_id);
591
+
592
+ // Fetch flow record
593
+ var getResp = await client.get('/api/now/table/sys_hub_flow/' + getSysId);
594
+ var flowRecord = getResp.data.result;
595
+
596
+ // Fetch trigger instances
597
+ var triggerInstances: any[] = [];
598
+ try {
599
+ var trigResp = await client.get('/api/now/table/sys_hub_trigger_instance', {
600
+ params: {
601
+ sysparm_query: 'flow=' + getSysId,
602
+ sysparm_fields: 'sys_id,name,action_type,table,condition,active,order',
603
+ sysparm_limit: 10
604
+ }
605
+ });
606
+ triggerInstances = trigResp.data.result || [];
607
+ } catch (e) { /* best-effort */ }
608
+
609
+ // Fetch action instances
610
+ var actionInstances: any[] = [];
611
+ try {
612
+ var actResp = await client.get('/api/now/table/sys_hub_action_instance', {
613
+ params: {
614
+ sysparm_query: 'flow=' + getSysId + '^ORDERBYorder',
615
+ sysparm_fields: 'sys_id,name,action_type,order,active',
616
+ sysparm_limit: 50
617
+ }
618
+ });
619
+ actionInstances = actResp.data.result || [];
620
+ } catch (e) { /* best-effort */ }
621
+
622
+ // Fetch flow variables
623
+ var flowVars: any[] = [];
624
+ try {
625
+ var varResp = await client.get('/api/now/table/sys_hub_flow_variable', {
626
+ params: {
627
+ sysparm_query: 'flow=' + getSysId,
628
+ sysparm_fields: 'sys_id,name,label,type,mandatory,variable_type,default_value',
629
+ sysparm_limit: 50
630
+ }
631
+ });
632
+ flowVars = varResp.data.result || [];
633
+ } catch (e) { /* best-effort */ }
634
+
635
+ // Fetch recent executions
636
+ var executions: any[] = [];
637
+ try {
638
+ var execResp = await client.get('/api/now/table/sys_hub_flow_run', {
639
+ params: {
640
+ sysparm_query: 'flow=' + getSysId + '^ORDERBYDESCsys_created_on',
641
+ sysparm_fields: 'sys_id,state,started,ended,duration,trigger_record_table,trigger_record_id',
642
+ sysparm_limit: 10
643
+ }
644
+ });
645
+ executions = execResp.data.result || [];
646
+ } catch (e) { /* best-effort */ }
647
+
648
+ var getSummary = summary()
649
+ .success('Flow: ' + (flowRecord.name || args.flow_id))
650
+ .field('sys_id', flowRecord.sys_id)
651
+ .field('Type', flowRecord.type)
652
+ .field('Category', flowRecord.category)
653
+ .field('Status', flowRecord.active === 'true' ? 'Active' : 'Inactive')
654
+ .field('Run as', flowRecord.run_as)
655
+ .field('Description', flowRecord.description);
656
+
657
+ if (triggerInstances.length > 0) {
658
+ getSummary.blank().line('Triggers: ' + triggerInstances.length);
659
+ for (var ti = 0; ti < triggerInstances.length; ti++) {
660
+ getSummary.bullet(triggerInstances[ti].name || 'trigger-' + ti);
661
+ }
662
+ }
663
+ if (actionInstances.length > 0) {
664
+ getSummary.blank().line('Actions: ' + actionInstances.length);
665
+ for (var aci = 0; aci < Math.min(actionInstances.length, 10); aci++) {
666
+ getSummary.bullet(actionInstances[aci].name || 'action-' + aci);
667
+ }
668
+ }
669
+ if (flowVars.length > 0) {
670
+ getSummary.blank().line('Variables: ' + flowVars.length);
671
+ }
672
+ if (executions.length > 0) {
673
+ getSummary.blank().line('Recent executions: ' + executions.length);
674
+ for (var ei = 0; ei < Math.min(executions.length, 5); ei++) {
675
+ var ex = executions[ei];
676
+ getSummary.bullet((ex.state || 'unknown') + ' - ' + (ex.started || 'pending'));
677
+ }
678
+ }
679
+
680
+ return createSuccessResult({
681
+ action: 'get',
682
+ flow: {
683
+ sys_id: flowRecord.sys_id,
684
+ name: flowRecord.name,
685
+ description: flowRecord.description,
686
+ type: flowRecord.type,
687
+ category: flowRecord.category,
688
+ active: flowRecord.active === 'true',
689
+ status: flowRecord.status,
690
+ run_as: flowRecord.run_as,
691
+ created: flowRecord.sys_created_on,
692
+ updated: flowRecord.sys_updated_on
693
+ },
694
+ triggers: triggerInstances.map(function (t: any) {
695
+ return {
696
+ sys_id: t.sys_id,
697
+ name: t.name,
698
+ action_type: typeof t.action_type === 'object' ? t.action_type.display_value : t.action_type,
699
+ table: t.table,
700
+ condition: t.condition,
701
+ active: t.active === 'true'
702
+ };
703
+ }),
704
+ actions: actionInstances.map(function (a: any) {
705
+ return {
706
+ sys_id: a.sys_id,
707
+ name: a.name,
708
+ action_type: typeof a.action_type === 'object' ? a.action_type.display_value : a.action_type,
709
+ order: a.order,
710
+ active: a.active === 'true'
711
+ };
712
+ }),
713
+ variables: flowVars.map(function (v: any) {
714
+ return {
715
+ sys_id: v.sys_id,
716
+ name: v.name,
717
+ label: v.label,
718
+ type: v.type,
719
+ mandatory: v.mandatory === 'true',
720
+ variable_type: v.variable_type,
721
+ default_value: v.default_value
722
+ };
723
+ }),
724
+ recent_executions: executions.map(function (e: any) {
725
+ return {
726
+ sys_id: e.sys_id,
727
+ state: e.state,
728
+ started: e.started,
729
+ ended: e.ended,
730
+ duration: e.duration,
731
+ trigger_table: e.trigger_record_table,
732
+ trigger_record: e.trigger_record_id
733
+ };
734
+ })
735
+ }, {}, getSummary.build());
736
+ }
737
+
738
+ // ────────────────────────────────────────────────────────────────
739
+ // UPDATE
740
+ // ────────────────────────────────────────────────────────────────
741
+ case 'update': {
742
+ if (!args.flow_id) {
743
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for update action');
744
+ }
745
+ var updateFields = args.update_fields;
746
+ if (!updateFields || Object.keys(updateFields).length === 0) {
747
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'update_fields is required for update action');
748
+ }
749
+
750
+ var updateSysId = await resolveFlowId(client, args.flow_id);
751
+ await client.patch('/api/now/table/sys_hub_flow/' + updateSysId, updateFields);
752
+
753
+ var updateSummary = summary()
754
+ .success('Updated flow: ' + args.flow_id)
755
+ .field('sys_id', updateSysId);
756
+
757
+ var fieldNames = Object.keys(updateFields);
758
+ for (var fi = 0; fi < fieldNames.length; fi++) {
759
+ updateSummary.field(fieldNames[fi], updateFields[fieldNames[fi]]);
760
+ }
761
+
762
+ return createSuccessResult({
763
+ action: 'update',
764
+ flow_id: updateSysId,
765
+ updated_fields: fieldNames,
766
+ message: 'Flow updated successfully'
767
+ }, {}, updateSummary.build());
768
+ }
769
+
770
+ // ────────────────────────────────────────────────────────────────
771
+ // ACTIVATE / PUBLISH
772
+ // ────────────────────────────────────────────────────────────────
773
+ case 'activate':
774
+ case 'publish': {
775
+ if (!args.flow_id) {
776
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for ' + action + ' action');
777
+ }
778
+
779
+ var activateSysId = await resolveFlowId(client, args.flow_id);
780
+ await client.patch('/api/now/table/sys_hub_flow/' + activateSysId, {
781
+ active: true,
782
+ status: 'published',
783
+ validated: true
784
+ });
785
+
786
+ var activateSummary = summary()
787
+ .success('Flow activated and published')
788
+ .field('sys_id', activateSysId);
789
+
790
+ return createSuccessResult({
791
+ action: action,
792
+ flow_id: activateSysId,
793
+ active: true,
794
+ status: 'published',
795
+ message: 'Flow activated and published'
796
+ }, {}, activateSummary.build());
797
+ }
798
+
799
+ // ────────────────────────────────────────────────────────────────
800
+ // DEACTIVATE
801
+ // ────────────────────────────────────────────────────────────────
802
+ case 'deactivate': {
803
+ if (!args.flow_id) {
804
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for deactivate action');
805
+ }
806
+
807
+ var deactivateSysId = await resolveFlowId(client, args.flow_id);
808
+ await client.patch('/api/now/table/sys_hub_flow/' + deactivateSysId, {
809
+ active: false
810
+ });
811
+
812
+ var deactivateSummary = summary()
813
+ .success('Flow deactivated')
814
+ .field('sys_id', deactivateSysId);
815
+
816
+ return createSuccessResult({
817
+ action: 'deactivate',
818
+ flow_id: deactivateSysId,
819
+ active: false,
820
+ message: 'Flow deactivated'
821
+ }, {}, deactivateSummary.build());
822
+ }
823
+
824
+ // ────────────────────────────────────────────────────────────────
825
+ // DELETE
826
+ // ────────────────────────────────────────────────────────────────
827
+ case 'delete': {
828
+ if (!args.flow_id) {
829
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for delete action');
830
+ }
831
+
832
+ var deleteSysId = await resolveFlowId(client, args.flow_id);
833
+ await client.delete('/api/now/table/sys_hub_flow/' + deleteSysId);
834
+
835
+ var deleteSummary = summary()
836
+ .success('Flow deleted')
837
+ .field('sys_id', deleteSysId);
838
+
839
+ return createSuccessResult({
840
+ action: 'delete',
841
+ flow_id: deleteSysId,
842
+ message: 'Flow deleted'
843
+ }, {}, deleteSummary.build());
844
+ }
845
+
846
+ default:
847
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'Unknown action: ' + action);
848
+ }
849
+
850
+ } catch (error: any) {
851
+ if (error instanceof SnowFlowError) {
852
+ return createErrorResult(error);
853
+ }
854
+ if (error.response?.status === 403) {
855
+ return createErrorResult(
856
+ 'Permission denied (403): Your ServiceNow user lacks Flow Designer permissions. ' +
857
+ 'Required roles: "flow_designer" or "admin". Contact your ServiceNow administrator.'
858
+ );
859
+ }
860
+ return createErrorResult(
861
+ new SnowFlowError(ErrorType.UNKNOWN_ERROR, error.message, { originalError: error })
862
+ );
863
+ }
864
+ }
865
+
866
+ export const version = '1.0.0';
867
+ export const author = 'Snow-Flow Team';
@@ -31,16 +31,12 @@ const LEGACY_WARNING = `
31
31
  ServiceNow Workflow (wf_workflow) is deprecated in favor of Flow Designer.
32
32
  Flow Designer provides a modern, visual interface for building automations.
33
33
 
34
- However, Flow Designer is NOT programmable via Snow-Flow at this time.
34
+ Use snow_manage_flow to create and manage Flow Designer flows programmatically.
35
35
 
36
36
  RECOMMENDATIONS:
37
- 1. For NEW automations → Consider building manually in Flow Designer
37
+ 1. For NEW automations → Use snow_manage_flow (action: create)
38
38
  2. For EXISTING workflows → This tool can manage legacy workflows
39
- 3. Need Flow Designer specs? Ask Snow-Flow to generate a Flow Designer
40
- specification document with the required triggers, actions, and logic.
41
-
42
- To generate a Flow Designer specification, ask:
43
- "Generate a Flow Designer specification for [describe your automation]"
39
+ 3. For MIGRATIONUse snow_manage_flow to recreate workflows as flows
44
40
  `;
45
41
 
46
42
  export const toolDefinition: MCPToolDefinition = {