snow-flow 1.1.42 โ 1.1.45
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 +97 -0
- package/dist/mcp/servicenow-deployment-mcp.js +352 -0
- package/dist/mcp/servicenow-deployment-mcp.js.map +1 -1
- package/dist/mcp/servicenow-intelligent-mcp.js +4 -0
- package/dist/mcp/servicenow-intelligent-mcp.js.map +1 -1
- package/dist/mcp/servicenow-operations-mcp.js +1226 -3
- package/dist/mcp/servicenow-operations-mcp.js.map +1 -1
- package/dist/mcp/servicenow-platform-development-mcp.js +187 -0
- package/dist/mcp/servicenow-platform-development-mcp.js.map +1 -1
- package/dist/utils/servicenow-client.d.ts +5 -0
- package/dist/utils/servicenow-client.d.ts.map +1 -1
- package/dist/utils/servicenow-client.js +73 -13
- package/dist/utils/servicenow-client.js.map +1 -1
- package/dist/version.d.ts +16 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +80 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -26,6 +26,15 @@ const operationalTableMapping = {
|
|
|
26
26
|
request_item: 'sc_req_item',
|
|
27
27
|
problem: 'problem',
|
|
28
28
|
change_request: 'change_request',
|
|
29
|
+
// Service Catalog
|
|
30
|
+
catalog: 'sc_catalog',
|
|
31
|
+
catalog_item: 'sc_cat_item',
|
|
32
|
+
catalog_category: 'sc_category',
|
|
33
|
+
catalog_variable: 'item_option_new',
|
|
34
|
+
catalog_variable_set: 'io_set_item',
|
|
35
|
+
catalog_ui_policy: 'catalog_ui_policy',
|
|
36
|
+
catalog_client_script: 'catalog_script_client',
|
|
37
|
+
catalog_template: 'sc_template',
|
|
29
38
|
// Task Management
|
|
30
39
|
task: 'task',
|
|
31
40
|
incident_task: 'incident_task',
|
|
@@ -51,9 +60,6 @@ const operationalTableMapping = {
|
|
|
51
60
|
// Knowledge Management
|
|
52
61
|
knowledge_base: 'kb_knowledge_base',
|
|
53
62
|
knowledge: 'kb_knowledge',
|
|
54
|
-
// Service Catalog
|
|
55
|
-
catalog: 'sc_catalog',
|
|
56
|
-
catalog_item: 'sc_cat_item',
|
|
57
63
|
// Metrics & Analytics
|
|
58
64
|
metric: 'sys_metric',
|
|
59
65
|
metric_instance: 'sys_metric_instance',
|
|
@@ -380,6 +386,271 @@ class ServiceNowOperationsMCP {
|
|
|
380
386
|
},
|
|
381
387
|
required: ['prediction_type']
|
|
382
388
|
}
|
|
389
|
+
},
|
|
390
|
+
// Service Catalog Management
|
|
391
|
+
{
|
|
392
|
+
name: 'snow_catalog_item_manager',
|
|
393
|
+
description: 'Manage service catalog items - create, update, configure variables and workflows',
|
|
394
|
+
inputSchema: {
|
|
395
|
+
type: 'object',
|
|
396
|
+
properties: {
|
|
397
|
+
action: {
|
|
398
|
+
type: 'string',
|
|
399
|
+
description: 'Action to perform',
|
|
400
|
+
enum: ['create', 'update', 'list', 'get', 'add_variable', 'set_workflow', 'publish', 'retire']
|
|
401
|
+
},
|
|
402
|
+
catalog_id: {
|
|
403
|
+
type: 'string',
|
|
404
|
+
description: 'Catalog sys_id (for create action)'
|
|
405
|
+
},
|
|
406
|
+
category_id: {
|
|
407
|
+
type: 'string',
|
|
408
|
+
description: 'Category sys_id'
|
|
409
|
+
},
|
|
410
|
+
item_id: {
|
|
411
|
+
type: 'string',
|
|
412
|
+
description: 'Catalog item sys_id (for update/get actions)'
|
|
413
|
+
},
|
|
414
|
+
name: {
|
|
415
|
+
type: 'string',
|
|
416
|
+
description: 'Catalog item name'
|
|
417
|
+
},
|
|
418
|
+
short_description: {
|
|
419
|
+
type: 'string',
|
|
420
|
+
description: 'Short description'
|
|
421
|
+
},
|
|
422
|
+
description: {
|
|
423
|
+
type: 'string',
|
|
424
|
+
description: 'Full description'
|
|
425
|
+
},
|
|
426
|
+
price: {
|
|
427
|
+
type: 'string',
|
|
428
|
+
description: 'Item price (e.g., "100.00")'
|
|
429
|
+
},
|
|
430
|
+
recurring_price: {
|
|
431
|
+
type: 'string',
|
|
432
|
+
description: 'Recurring price if applicable'
|
|
433
|
+
},
|
|
434
|
+
picture: {
|
|
435
|
+
type: 'string',
|
|
436
|
+
description: 'Picture attachment sys_id'
|
|
437
|
+
},
|
|
438
|
+
workflow: {
|
|
439
|
+
type: 'string',
|
|
440
|
+
description: 'Workflow to attach'
|
|
441
|
+
},
|
|
442
|
+
variable_set: {
|
|
443
|
+
type: 'string',
|
|
444
|
+
description: 'Variable set sys_id to attach'
|
|
445
|
+
},
|
|
446
|
+
variables: {
|
|
447
|
+
type: 'array',
|
|
448
|
+
description: 'Variables to add',
|
|
449
|
+
items: {
|
|
450
|
+
type: 'object',
|
|
451
|
+
properties: {
|
|
452
|
+
name: { type: 'string' },
|
|
453
|
+
label: { type: 'string' },
|
|
454
|
+
type: {
|
|
455
|
+
type: 'string',
|
|
456
|
+
enum: ['single_line_text', 'multi_line_text', 'select_box', 'checkbox', 'reference', 'date', 'datetime']
|
|
457
|
+
},
|
|
458
|
+
mandatory: { type: 'boolean' },
|
|
459
|
+
default_value: { type: 'string' },
|
|
460
|
+
choices: { type: 'array', items: { type: 'string' } }
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
active: {
|
|
465
|
+
type: 'boolean',
|
|
466
|
+
description: 'Whether the item is active',
|
|
467
|
+
default: true
|
|
468
|
+
},
|
|
469
|
+
billable: {
|
|
470
|
+
type: 'boolean',
|
|
471
|
+
description: 'Whether the item is billable',
|
|
472
|
+
default: false
|
|
473
|
+
},
|
|
474
|
+
sc_catalogs: {
|
|
475
|
+
type: 'string',
|
|
476
|
+
description: 'Comma-separated catalog sys_ids'
|
|
477
|
+
},
|
|
478
|
+
sc_categories: {
|
|
479
|
+
type: 'string',
|
|
480
|
+
description: 'Comma-separated category sys_ids'
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
required: ['action']
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: 'snow_catalog_item_search',
|
|
488
|
+
description: 'Search for catalog items with intelligent matching and discovery',
|
|
489
|
+
inputSchema: {
|
|
490
|
+
type: 'object',
|
|
491
|
+
properties: {
|
|
492
|
+
query: {
|
|
493
|
+
type: 'string',
|
|
494
|
+
description: 'Search query (e.g., "iPhone", "laptop", "mobile device")'
|
|
495
|
+
},
|
|
496
|
+
include_inactive: {
|
|
497
|
+
type: 'boolean',
|
|
498
|
+
description: 'Include inactive catalog items',
|
|
499
|
+
default: false
|
|
500
|
+
},
|
|
501
|
+
fuzzy_match: {
|
|
502
|
+
type: 'boolean',
|
|
503
|
+
description: 'Enable fuzzy matching for similar items',
|
|
504
|
+
default: true
|
|
505
|
+
},
|
|
506
|
+
category_filter: {
|
|
507
|
+
type: 'string',
|
|
508
|
+
description: 'Filter by category name or sys_id'
|
|
509
|
+
},
|
|
510
|
+
limit: {
|
|
511
|
+
type: 'number',
|
|
512
|
+
description: 'Maximum results to return',
|
|
513
|
+
default: 50
|
|
514
|
+
},
|
|
515
|
+
include_variables: {
|
|
516
|
+
type: 'boolean',
|
|
517
|
+
description: 'Include catalog item variables in results',
|
|
518
|
+
default: false
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
required: ['query']
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: 'snow_test_flow_with_mock',
|
|
526
|
+
description: 'Test flows with mock data - create test users, mock catalog items, and simulate flow execution',
|
|
527
|
+
inputSchema: {
|
|
528
|
+
type: 'object',
|
|
529
|
+
properties: {
|
|
530
|
+
flow_id: {
|
|
531
|
+
type: 'string',
|
|
532
|
+
description: 'Flow sys_id or name to test'
|
|
533
|
+
},
|
|
534
|
+
create_test_user: {
|
|
535
|
+
type: 'boolean',
|
|
536
|
+
description: 'Create a test user for the flow',
|
|
537
|
+
default: true
|
|
538
|
+
},
|
|
539
|
+
test_user_data: {
|
|
540
|
+
type: 'object',
|
|
541
|
+
description: 'Test user details',
|
|
542
|
+
properties: {
|
|
543
|
+
user_name: { type: 'string' },
|
|
544
|
+
first_name: { type: 'string' },
|
|
545
|
+
last_name: { type: 'string' },
|
|
546
|
+
email: { type: 'string' },
|
|
547
|
+
department: { type: 'string' },
|
|
548
|
+
manager: { type: 'string' }
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
mock_catalog_items: {
|
|
552
|
+
type: 'boolean',
|
|
553
|
+
description: 'Create mock catalog items for testing',
|
|
554
|
+
default: true
|
|
555
|
+
},
|
|
556
|
+
mock_catalog_data: {
|
|
557
|
+
type: 'array',
|
|
558
|
+
description: 'Mock catalog items to create',
|
|
559
|
+
items: {
|
|
560
|
+
type: 'object',
|
|
561
|
+
properties: {
|
|
562
|
+
name: { type: 'string' },
|
|
563
|
+
short_description: { type: 'string' },
|
|
564
|
+
price: { type: 'string' },
|
|
565
|
+
variables: { type: 'array' }
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
test_inputs: {
|
|
570
|
+
type: 'object',
|
|
571
|
+
description: 'Input values to test the flow with'
|
|
572
|
+
},
|
|
573
|
+
simulate_approvals: {
|
|
574
|
+
type: 'boolean',
|
|
575
|
+
description: 'Automatically approve any approval requests',
|
|
576
|
+
default: true
|
|
577
|
+
},
|
|
578
|
+
cleanup_after_test: {
|
|
579
|
+
type: 'boolean',
|
|
580
|
+
description: 'Remove test data after testing',
|
|
581
|
+
default: true
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
required: ['flow_id']
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
name: 'snow_link_catalog_to_flow',
|
|
589
|
+
description: 'Link catalog items directly to flows - configure flow as fulfillment process',
|
|
590
|
+
inputSchema: {
|
|
591
|
+
type: 'object',
|
|
592
|
+
properties: {
|
|
593
|
+
catalog_item_id: {
|
|
594
|
+
type: 'string',
|
|
595
|
+
description: 'Catalog item sys_id or name'
|
|
596
|
+
},
|
|
597
|
+
flow_id: {
|
|
598
|
+
type: 'string',
|
|
599
|
+
description: 'Flow sys_id or name to use for fulfillment'
|
|
600
|
+
},
|
|
601
|
+
link_type: {
|
|
602
|
+
type: 'string',
|
|
603
|
+
description: 'Type of link to create',
|
|
604
|
+
enum: ['workflow', 'flow_catalog_process', 'process_engine'],
|
|
605
|
+
default: 'flow_catalog_process'
|
|
606
|
+
},
|
|
607
|
+
variable_mapping: {
|
|
608
|
+
type: 'array',
|
|
609
|
+
description: 'Map catalog variables to flow inputs',
|
|
610
|
+
items: {
|
|
611
|
+
type: 'object',
|
|
612
|
+
properties: {
|
|
613
|
+
catalog_variable: { type: 'string', description: 'Catalog variable name' },
|
|
614
|
+
flow_input: { type: 'string', description: 'Flow input name' },
|
|
615
|
+
transform: { type: 'string', description: 'Optional transformation script' }
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
trigger_condition: {
|
|
620
|
+
type: 'string',
|
|
621
|
+
description: 'Condition for when to trigger the flow',
|
|
622
|
+
default: 'current.stage == "request_approved"'
|
|
623
|
+
},
|
|
624
|
+
execution_options: {
|
|
625
|
+
type: 'object',
|
|
626
|
+
description: 'Flow execution options',
|
|
627
|
+
properties: {
|
|
628
|
+
run_as: {
|
|
629
|
+
type: 'string',
|
|
630
|
+
description: 'User context for flow execution',
|
|
631
|
+
enum: ['requester', 'system', 'fulfiller'],
|
|
632
|
+
default: 'system'
|
|
633
|
+
},
|
|
634
|
+
wait_for_completion: {
|
|
635
|
+
type: 'boolean',
|
|
636
|
+
description: 'Wait for flow to complete before closing request',
|
|
637
|
+
default: true
|
|
638
|
+
},
|
|
639
|
+
update_request_on_progress: {
|
|
640
|
+
type: 'boolean',
|
|
641
|
+
description: 'Update request item with flow progress',
|
|
642
|
+
default: true
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
test_link: {
|
|
647
|
+
type: 'boolean',
|
|
648
|
+
description: 'Test the link by creating a sample request',
|
|
649
|
+
default: false
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
required: ['catalog_item_id', 'flow_id']
|
|
653
|
+
}
|
|
383
654
|
}
|
|
384
655
|
],
|
|
385
656
|
};
|
|
@@ -411,6 +682,14 @@ class ServiceNowOperationsMCP {
|
|
|
411
682
|
return await this.handleKnowledgeSearch(args);
|
|
412
683
|
case 'snow_predictive_analysis':
|
|
413
684
|
return await this.handlePredictiveAnalysis(args);
|
|
685
|
+
case 'snow_catalog_item_manager':
|
|
686
|
+
return await this.handleCatalogItemManager(args);
|
|
687
|
+
case 'snow_catalog_item_search':
|
|
688
|
+
return await this.handleCatalogItemSearch(args);
|
|
689
|
+
case 'snow_test_flow_with_mock':
|
|
690
|
+
return await this.handleTestFlowWithMock(args);
|
|
691
|
+
case 'snow_link_catalog_to_flow':
|
|
692
|
+
return await this.handleLinkCatalogToFlow(args);
|
|
414
693
|
default:
|
|
415
694
|
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Tool ${name} not found`);
|
|
416
695
|
}
|
|
@@ -1272,6 +1551,950 @@ class ServiceNowOperationsMCP {
|
|
|
1272
1551
|
}
|
|
1273
1552
|
];
|
|
1274
1553
|
}
|
|
1554
|
+
async handleCatalogItemManager(args) {
|
|
1555
|
+
const { action, item_id, ...params } = args;
|
|
1556
|
+
logger_js_1.logger.info(`Catalog item manager action: ${action}`, { item_id, params });
|
|
1557
|
+
try {
|
|
1558
|
+
switch (action) {
|
|
1559
|
+
case 'create': {
|
|
1560
|
+
const catalogItem = {
|
|
1561
|
+
name: params.name,
|
|
1562
|
+
short_description: params.short_description,
|
|
1563
|
+
description: params.description,
|
|
1564
|
+
price: params.price || '0',
|
|
1565
|
+
recurring_price: params.recurring_price || '0',
|
|
1566
|
+
billable: params.billable || false,
|
|
1567
|
+
active: params.active !== false,
|
|
1568
|
+
workflow: params.workflow,
|
|
1569
|
+
picture: params.picture,
|
|
1570
|
+
sc_catalogs: params.sc_catalogs || params.catalog_id,
|
|
1571
|
+
sc_categories: params.sc_categories || params.category_id,
|
|
1572
|
+
sys_class_name: 'sc_cat_item'
|
|
1573
|
+
};
|
|
1574
|
+
const result = await this.client.createRecord('sc_cat_item', catalogItem);
|
|
1575
|
+
if (result.success && params.variables && params.variables.length > 0) {
|
|
1576
|
+
// Add variables to the catalog item
|
|
1577
|
+
for (const variable of params.variables) {
|
|
1578
|
+
await this.createCatalogVariable(result.data.sys_id, variable);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
return {
|
|
1582
|
+
content: [{
|
|
1583
|
+
type: 'text',
|
|
1584
|
+
text: result.success
|
|
1585
|
+
? `โ
Catalog item created successfully!\n\nSys ID: ${result.data.sys_id}\nName: ${result.data.name}\n\nView at: ${this.getServiceNowUrl()}/nav_to.do?uri=sc_cat_item.do?sys_id=${result.data.sys_id}`
|
|
1586
|
+
: `โ Failed to create catalog item: ${result.error}`
|
|
1587
|
+
}]
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
case 'update': {
|
|
1591
|
+
if (!item_id) {
|
|
1592
|
+
throw new Error('item_id is required for update action');
|
|
1593
|
+
}
|
|
1594
|
+
const updateData = {};
|
|
1595
|
+
if (params.name)
|
|
1596
|
+
updateData.name = params.name;
|
|
1597
|
+
if (params.short_description)
|
|
1598
|
+
updateData.short_description = params.short_description;
|
|
1599
|
+
if (params.description)
|
|
1600
|
+
updateData.description = params.description;
|
|
1601
|
+
if (params.price)
|
|
1602
|
+
updateData.price = params.price;
|
|
1603
|
+
if (params.recurring_price)
|
|
1604
|
+
updateData.recurring_price = params.recurring_price;
|
|
1605
|
+
if (params.billable !== undefined)
|
|
1606
|
+
updateData.billable = params.billable;
|
|
1607
|
+
if (params.active !== undefined)
|
|
1608
|
+
updateData.active = params.active;
|
|
1609
|
+
if (params.workflow)
|
|
1610
|
+
updateData.workflow = params.workflow;
|
|
1611
|
+
if (params.sc_catalogs)
|
|
1612
|
+
updateData.sc_catalogs = params.sc_catalogs;
|
|
1613
|
+
if (params.sc_categories)
|
|
1614
|
+
updateData.sc_categories = params.sc_categories;
|
|
1615
|
+
const result = await this.client.updateRecord('sc_cat_item', item_id, updateData);
|
|
1616
|
+
return {
|
|
1617
|
+
content: [{
|
|
1618
|
+
type: 'text',
|
|
1619
|
+
text: result.success
|
|
1620
|
+
? `โ
Catalog item updated successfully!\n\nSys ID: ${item_id}\n\nView at: ${this.getServiceNowUrl()}/nav_to.do?uri=sc_cat_item.do?sys_id=${item_id}`
|
|
1621
|
+
: `โ Failed to update catalog item: ${result.error}`
|
|
1622
|
+
}]
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
case 'list': {
|
|
1626
|
+
const query = params.category_id ? `sc_categories=${params.category_id}` : 'active=true';
|
|
1627
|
+
const result = await this.client.searchRecords('sc_cat_item', query, 50);
|
|
1628
|
+
if (result.success) {
|
|
1629
|
+
const items = result.data.result.map((item) => ({
|
|
1630
|
+
sys_id: item.sys_id,
|
|
1631
|
+
name: item.name,
|
|
1632
|
+
short_description: item.short_description,
|
|
1633
|
+
price: item.price,
|
|
1634
|
+
active: item.active,
|
|
1635
|
+
category: item.category?.display_value
|
|
1636
|
+
}));
|
|
1637
|
+
return {
|
|
1638
|
+
content: [{
|
|
1639
|
+
type: 'text',
|
|
1640
|
+
text: `๐ Found ${items.length} catalog items:\n\n${JSON.stringify(items, null, 2)}`
|
|
1641
|
+
}]
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
else {
|
|
1645
|
+
throw new Error(`Failed to list catalog items: ${result.error}`);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
case 'get': {
|
|
1649
|
+
if (!item_id) {
|
|
1650
|
+
throw new Error('item_id is required for get action');
|
|
1651
|
+
}
|
|
1652
|
+
const result = await this.client.getRecord('sc_cat_item', item_id);
|
|
1653
|
+
if (result.success) {
|
|
1654
|
+
// Get variables for the item
|
|
1655
|
+
const variables = await this.client.searchRecords('item_option_new', `cat_item=${item_id}`, 50);
|
|
1656
|
+
return {
|
|
1657
|
+
content: [{
|
|
1658
|
+
type: 'text',
|
|
1659
|
+
text: `๐ Catalog Item Details:\n\n${JSON.stringify(result.data, null, 2)}\n\n๐ Variables:\n${JSON.stringify(variables.data?.result || [], null, 2)}`
|
|
1660
|
+
}]
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
else {
|
|
1664
|
+
throw new Error(`Failed to get catalog item: ${result.error}`);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
case 'add_variable': {
|
|
1668
|
+
if (!item_id) {
|
|
1669
|
+
throw new Error('item_id is required for add_variable action');
|
|
1670
|
+
}
|
|
1671
|
+
if (!params.variables || params.variables.length === 0) {
|
|
1672
|
+
throw new Error('variables array is required');
|
|
1673
|
+
}
|
|
1674
|
+
const results = [];
|
|
1675
|
+
for (const variable of params.variables) {
|
|
1676
|
+
const result = await this.createCatalogVariable(item_id, variable);
|
|
1677
|
+
results.push(result);
|
|
1678
|
+
}
|
|
1679
|
+
return {
|
|
1680
|
+
content: [{
|
|
1681
|
+
type: 'text',
|
|
1682
|
+
text: `โ
Added ${results.length} variables to catalog item ${item_id}`
|
|
1683
|
+
}]
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
case 'set_workflow': {
|
|
1687
|
+
if (!item_id) {
|
|
1688
|
+
throw new Error('item_id is required for set_workflow action');
|
|
1689
|
+
}
|
|
1690
|
+
if (!params.workflow) {
|
|
1691
|
+
throw new Error('workflow is required');
|
|
1692
|
+
}
|
|
1693
|
+
const result = await this.client.updateRecord('sc_cat_item', item_id, {
|
|
1694
|
+
workflow: params.workflow
|
|
1695
|
+
});
|
|
1696
|
+
return {
|
|
1697
|
+
content: [{
|
|
1698
|
+
type: 'text',
|
|
1699
|
+
text: result.success
|
|
1700
|
+
? `โ
Workflow set successfully for catalog item ${item_id}`
|
|
1701
|
+
: `โ Failed to set workflow: ${result.error}`
|
|
1702
|
+
}]
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
case 'publish': {
|
|
1706
|
+
if (!item_id) {
|
|
1707
|
+
throw new Error('item_id is required for publish action');
|
|
1708
|
+
}
|
|
1709
|
+
const result = await this.client.updateRecord('sc_cat_item', item_id, {
|
|
1710
|
+
active: true
|
|
1711
|
+
});
|
|
1712
|
+
return {
|
|
1713
|
+
content: [{
|
|
1714
|
+
type: 'text',
|
|
1715
|
+
text: result.success
|
|
1716
|
+
? `โ
Catalog item ${item_id} published successfully`
|
|
1717
|
+
: `โ Failed to publish catalog item: ${result.error}`
|
|
1718
|
+
}]
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
case 'retire': {
|
|
1722
|
+
if (!item_id) {
|
|
1723
|
+
throw new Error('item_id is required for retire action');
|
|
1724
|
+
}
|
|
1725
|
+
const result = await this.client.updateRecord('sc_cat_item', item_id, {
|
|
1726
|
+
active: false
|
|
1727
|
+
});
|
|
1728
|
+
return {
|
|
1729
|
+
content: [{
|
|
1730
|
+
type: 'text',
|
|
1731
|
+
text: result.success
|
|
1732
|
+
? `โ
Catalog item ${item_id} retired successfully`
|
|
1733
|
+
: `โ Failed to retire catalog item: ${result.error}`
|
|
1734
|
+
}]
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
default:
|
|
1738
|
+
throw new Error(`Unknown action: ${action}`);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
catch (error) {
|
|
1742
|
+
logger_js_1.logger.error('Error in catalog item manager:', error);
|
|
1743
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Catalog item manager error: ${error}`);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
async createCatalogVariable(catalogItemId, variable) {
|
|
1747
|
+
const variableData = {
|
|
1748
|
+
cat_item: catalogItemId,
|
|
1749
|
+
name: variable.name,
|
|
1750
|
+
question_text: variable.label || variable.name,
|
|
1751
|
+
type: this.mapVariableType(variable.type),
|
|
1752
|
+
mandatory: variable.mandatory || false,
|
|
1753
|
+
default_value: variable.default_value || '',
|
|
1754
|
+
order: variable.order || 100
|
|
1755
|
+
};
|
|
1756
|
+
// Handle select box choices
|
|
1757
|
+
if (variable.type === 'select_box' && variable.choices) {
|
|
1758
|
+
variableData.lookup_table = 'question_choice';
|
|
1759
|
+
// Note: In a real implementation, you'd need to create choice records
|
|
1760
|
+
}
|
|
1761
|
+
return await this.client.createRecord('item_option_new', variableData);
|
|
1762
|
+
}
|
|
1763
|
+
mapVariableType(type) {
|
|
1764
|
+
const typeMap = {
|
|
1765
|
+
'single_line_text': '6', // Single Line Text
|
|
1766
|
+
'multi_line_text': '2', // Multi Line Text
|
|
1767
|
+
'select_box': '3', // Select Box
|
|
1768
|
+
'checkbox': '7', // Checkbox
|
|
1769
|
+
'reference': '8', // Reference
|
|
1770
|
+
'date': '9', // Date
|
|
1771
|
+
'datetime': '10' // Date/Time
|
|
1772
|
+
};
|
|
1773
|
+
return typeMap[type] || '6'; // Default to single line text
|
|
1774
|
+
}
|
|
1775
|
+
async handleCatalogItemSearch(args) {
|
|
1776
|
+
const { query, include_inactive = false, fuzzy_match = true, category_filter, limit = 50, include_variables = false } = args;
|
|
1777
|
+
logger_js_1.logger.info(`Searching catalog items for: ${query}`, { fuzzy_match, category_filter, limit });
|
|
1778
|
+
try {
|
|
1779
|
+
// Build search queries
|
|
1780
|
+
const searchQueries = [];
|
|
1781
|
+
// Exact match
|
|
1782
|
+
searchQueries.push(`name=${query}`);
|
|
1783
|
+
// Contains match
|
|
1784
|
+
searchQueries.push(`nameLIKE${query}`);
|
|
1785
|
+
searchQueries.push(`short_descriptionLIKE${query}`);
|
|
1786
|
+
// Fuzzy matching for similar items
|
|
1787
|
+
if (fuzzy_match) {
|
|
1788
|
+
// Handle common variations
|
|
1789
|
+
const variations = this.generateSearchVariations(query);
|
|
1790
|
+
variations.forEach(variation => {
|
|
1791
|
+
searchQueries.push(`nameLIKE${variation}`);
|
|
1792
|
+
searchQueries.push(`short_descriptionLIKE${variation}`);
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
// Add active filter
|
|
1796
|
+
const activeFilter = include_inactive ? '' : '^active=true';
|
|
1797
|
+
// Add category filter if provided
|
|
1798
|
+
let categoryFilter = '';
|
|
1799
|
+
if (category_filter) {
|
|
1800
|
+
// First try to find the category
|
|
1801
|
+
const categoryResult = await this.client.searchRecords('sc_category', `name=${category_filter}^ORsys_id=${category_filter}`, 1);
|
|
1802
|
+
if (categoryResult.success && categoryResult.data?.result?.length > 0) {
|
|
1803
|
+
categoryFilter = `^sc_categories=${categoryResult.data.result[0].sys_id}`;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
// Execute searches
|
|
1807
|
+
const allResults = new Map();
|
|
1808
|
+
for (const searchQuery of searchQueries) {
|
|
1809
|
+
const fullQuery = searchQuery + activeFilter + categoryFilter;
|
|
1810
|
+
const result = await this.client.searchRecords('sc_cat_item', fullQuery, limit);
|
|
1811
|
+
if (result.success && result.data?.result) {
|
|
1812
|
+
result.data.result.forEach((item) => {
|
|
1813
|
+
if (!allResults.has(item.sys_id)) {
|
|
1814
|
+
allResults.set(item.sys_id, {
|
|
1815
|
+
sys_id: item.sys_id,
|
|
1816
|
+
name: item.name,
|
|
1817
|
+
short_description: item.short_description,
|
|
1818
|
+
price: item.price,
|
|
1819
|
+
recurring_price: item.recurring_price,
|
|
1820
|
+
active: item.active,
|
|
1821
|
+
category: item.sc_categories?.display_value,
|
|
1822
|
+
catalog: item.sc_catalogs?.display_value,
|
|
1823
|
+
model_number: item.model,
|
|
1824
|
+
manufacturer: item.vendor,
|
|
1825
|
+
variables: []
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
// Get variables if requested
|
|
1832
|
+
if (include_variables && allResults.size > 0) {
|
|
1833
|
+
for (const [sys_id, item] of allResults) {
|
|
1834
|
+
const variablesResult = await this.client.searchRecords('item_option_new', `cat_item=${sys_id}`, 50);
|
|
1835
|
+
if (variablesResult.success && variablesResult.data?.result) {
|
|
1836
|
+
item.variables = variablesResult.data.result.map((v) => ({
|
|
1837
|
+
name: v.name,
|
|
1838
|
+
label: v.question_text,
|
|
1839
|
+
type: v.type,
|
|
1840
|
+
mandatory: v.mandatory,
|
|
1841
|
+
default_value: v.default_value
|
|
1842
|
+
}));
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
const resultsArray = Array.from(allResults.values());
|
|
1847
|
+
// Sort by relevance (exact matches first)
|
|
1848
|
+
resultsArray.sort((a, b) => {
|
|
1849
|
+
const aExact = a.name.toLowerCase() === query.toLowerCase();
|
|
1850
|
+
const bExact = b.name.toLowerCase() === query.toLowerCase();
|
|
1851
|
+
if (aExact && !bExact)
|
|
1852
|
+
return -1;
|
|
1853
|
+
if (!aExact && bExact)
|
|
1854
|
+
return 1;
|
|
1855
|
+
return 0;
|
|
1856
|
+
});
|
|
1857
|
+
if (resultsArray.length === 0) {
|
|
1858
|
+
// Provide suggestions if no results found
|
|
1859
|
+
const suggestions = this.generateSearchSuggestions(query);
|
|
1860
|
+
return {
|
|
1861
|
+
content: [{
|
|
1862
|
+
type: 'text',
|
|
1863
|
+
text: `โ No catalog items found for "${query}"\n\n` +
|
|
1864
|
+
`๐ก Suggestions:\n` +
|
|
1865
|
+
suggestions.map(s => `- Try searching for "${s}"`).join('\n') +
|
|
1866
|
+
`\n\n๐ก Tips:\n` +
|
|
1867
|
+
`- Check if the item name is spelled correctly\n` +
|
|
1868
|
+
`- Try searching with partial names (e.g., "iPhone" instead of "iPhone 6S")\n` +
|
|
1869
|
+
`- Use category filter to narrow down results\n` +
|
|
1870
|
+
`- Enable include_inactive if looking for retired items`
|
|
1871
|
+
}]
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
// Format results
|
|
1875
|
+
let resultText = `๐ Found ${resultsArray.length} catalog items matching "${query}":\n\n`;
|
|
1876
|
+
resultsArray.forEach((item, index) => {
|
|
1877
|
+
resultText += `${index + 1}. **${item.name}** (${item.sys_id})\n`;
|
|
1878
|
+
resultText += ` ๐ ${item.short_description || 'No description'}\n`;
|
|
1879
|
+
resultText += ` ๐ฐ Price: $${item.price || '0'} ${item.recurring_price ? `(+ $${item.recurring_price}/month)` : ''}\n`;
|
|
1880
|
+
resultText += ` ๐ Category: ${item.category || 'Uncategorized'}\n`;
|
|
1881
|
+
resultText += ` ๐ Catalog: ${item.catalog || 'Default'}\n`;
|
|
1882
|
+
if (item.model_number) {
|
|
1883
|
+
resultText += ` ๐ข Model: ${item.model_number}\n`;
|
|
1884
|
+
}
|
|
1885
|
+
if (item.manufacturer) {
|
|
1886
|
+
resultText += ` ๐ญ Manufacturer: ${item.manufacturer}\n`;
|
|
1887
|
+
}
|
|
1888
|
+
resultText += ` โ
Status: ${item.active ? 'Active' : 'Inactive'}\n`;
|
|
1889
|
+
if (item.variables && item.variables.length > 0) {
|
|
1890
|
+
resultText += ` ๐ Variables (${item.variables.length}):\n`;
|
|
1891
|
+
item.variables.forEach((v) => {
|
|
1892
|
+
resultText += ` - ${v.label} (${v.name}) - ${v.type}${v.mandatory ? ' *Required' : ''}\n`;
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
resultText += '\n';
|
|
1896
|
+
});
|
|
1897
|
+
resultText += `\n๐ก Use snow_catalog_item_manager to create or update catalog items`;
|
|
1898
|
+
return {
|
|
1899
|
+
content: [{
|
|
1900
|
+
type: 'text',
|
|
1901
|
+
text: resultText
|
|
1902
|
+
}]
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
catch (error) {
|
|
1906
|
+
logger_js_1.logger.error('Error searching catalog items:', error);
|
|
1907
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Catalog search error: ${error}`);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
generateSearchVariations(query) {
|
|
1911
|
+
const variations = [];
|
|
1912
|
+
// Handle common product variations
|
|
1913
|
+
if (query.toLowerCase().includes('iphone')) {
|
|
1914
|
+
// Add variations without spaces, with different numbers
|
|
1915
|
+
const baseModel = query.replace(/iphone\s*/i, 'iPhone ');
|
|
1916
|
+
variations.push(baseModel);
|
|
1917
|
+
variations.push(baseModel.replace(' ', ''));
|
|
1918
|
+
// Try different model numbers
|
|
1919
|
+
const modelMatch = query.match(/\d+/);
|
|
1920
|
+
if (modelMatch) {
|
|
1921
|
+
const modelNum = parseInt(modelMatch[0]);
|
|
1922
|
+
variations.push(query.replace(modelNum.toString(), (modelNum + 1).toString()));
|
|
1923
|
+
variations.push(query.replace(modelNum.toString(), (modelNum - 1).toString()));
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
// Handle laptop/computer variations
|
|
1927
|
+
if (query.toLowerCase().includes('laptop') || query.toLowerCase().includes('computer')) {
|
|
1928
|
+
variations.push('notebook', 'macbook', 'thinkpad', 'dell', 'hp');
|
|
1929
|
+
}
|
|
1930
|
+
// Handle phone variations
|
|
1931
|
+
if (query.toLowerCase().includes('phone') || query.toLowerCase().includes('mobile')) {
|
|
1932
|
+
variations.push('smartphone', 'android', 'samsung', 'pixel');
|
|
1933
|
+
}
|
|
1934
|
+
return variations;
|
|
1935
|
+
}
|
|
1936
|
+
generateSearchSuggestions(query) {
|
|
1937
|
+
const suggestions = [];
|
|
1938
|
+
if (query.toLowerCase().includes('iphone')) {
|
|
1939
|
+
suggestions.push('iPhone', 'Apple iPhone', 'iOS device', 'Apple mobile');
|
|
1940
|
+
}
|
|
1941
|
+
else if (query.toLowerCase().includes('laptop')) {
|
|
1942
|
+
suggestions.push('notebook', 'computer', 'workstation', 'MacBook', 'ThinkPad');
|
|
1943
|
+
}
|
|
1944
|
+
else if (query.toLowerCase().includes('software')) {
|
|
1945
|
+
suggestions.push('license', 'application', 'subscription', 'SaaS');
|
|
1946
|
+
}
|
|
1947
|
+
else {
|
|
1948
|
+
// Generic suggestions
|
|
1949
|
+
suggestions.push(query.split(' ')[0], // First word only
|
|
1950
|
+
query.substring(0, Math.floor(query.length / 2)), // First half
|
|
1951
|
+
'hardware', 'software', 'equipment', 'device');
|
|
1952
|
+
}
|
|
1953
|
+
return [...new Set(suggestions)].filter(s => s && s !== query);
|
|
1954
|
+
}
|
|
1955
|
+
async handleTestFlowWithMock(args) {
|
|
1956
|
+
const { flow_id, create_test_user = true, test_user_data, mock_catalog_items = true, mock_catalog_data, test_inputs = {}, simulate_approvals = true, cleanup_after_test = true } = args;
|
|
1957
|
+
logger_js_1.logger.info(`Testing flow ${flow_id} with mock data`, {
|
|
1958
|
+
create_test_user,
|
|
1959
|
+
mock_catalog_items,
|
|
1960
|
+
simulate_approvals,
|
|
1961
|
+
cleanup_after_test
|
|
1962
|
+
});
|
|
1963
|
+
const testResults = {
|
|
1964
|
+
flow_id,
|
|
1965
|
+
test_run_id: `test_${Date.now()}`,
|
|
1966
|
+
created_data: {
|
|
1967
|
+
test_user: null,
|
|
1968
|
+
catalog_items: [],
|
|
1969
|
+
test_requests: []
|
|
1970
|
+
},
|
|
1971
|
+
execution_results: {
|
|
1972
|
+
status: 'pending',
|
|
1973
|
+
start_time: new Date().toISOString(),
|
|
1974
|
+
end_time: null,
|
|
1975
|
+
duration_ms: 0,
|
|
1976
|
+
execution_id: null,
|
|
1977
|
+
steps_executed: [],
|
|
1978
|
+
errors: [],
|
|
1979
|
+
approvals_simulated: []
|
|
1980
|
+
},
|
|
1981
|
+
cleanup_status: null
|
|
1982
|
+
};
|
|
1983
|
+
try {
|
|
1984
|
+
// Step 1: Find the flow
|
|
1985
|
+
const flowResult = await this.client.searchRecords('sys_hub_flow', `sys_id=${flow_id}^ORname=${flow_id}`, 1);
|
|
1986
|
+
if (!flowResult.success || !flowResult.data?.result?.length) {
|
|
1987
|
+
throw new Error(`Flow not found: ${flow_id}`);
|
|
1988
|
+
}
|
|
1989
|
+
const flow = flowResult.data.result[0];
|
|
1990
|
+
testResults.flow_id = flow.sys_id;
|
|
1991
|
+
// Step 2: Create test user if requested
|
|
1992
|
+
if (create_test_user) {
|
|
1993
|
+
const userData = test_user_data || {
|
|
1994
|
+
user_name: `test_user_${Date.now()}`,
|
|
1995
|
+
first_name: 'Test',
|
|
1996
|
+
last_name: 'User',
|
|
1997
|
+
email: `test_${Date.now()}@example.com`,
|
|
1998
|
+
department: 'IT',
|
|
1999
|
+
active: true
|
|
2000
|
+
};
|
|
2001
|
+
const userResult = await this.client.createRecord('sys_user', userData);
|
|
2002
|
+
if (userResult.success && userResult.data?.result) {
|
|
2003
|
+
testResults.created_data.test_user = {
|
|
2004
|
+
sys_id: userResult.data.result.sys_id,
|
|
2005
|
+
user_name: userData.user_name,
|
|
2006
|
+
email: userData.email
|
|
2007
|
+
};
|
|
2008
|
+
logger_js_1.logger.info('Created test user:', testResults.created_data.test_user);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
// Step 3: Create mock catalog items if requested
|
|
2012
|
+
if (mock_catalog_items) {
|
|
2013
|
+
const catalogData = mock_catalog_data || [
|
|
2014
|
+
{
|
|
2015
|
+
name: 'Test iPhone 6S',
|
|
2016
|
+
short_description: 'Test mobile device for flow testing',
|
|
2017
|
+
price: '699.00',
|
|
2018
|
+
active: true,
|
|
2019
|
+
billable: true
|
|
2020
|
+
},
|
|
2021
|
+
{
|
|
2022
|
+
name: 'Test Laptop',
|
|
2023
|
+
short_description: 'Test laptop for equipment provisioning',
|
|
2024
|
+
price: '1299.00',
|
|
2025
|
+
active: true,
|
|
2026
|
+
billable: true
|
|
2027
|
+
}
|
|
2028
|
+
];
|
|
2029
|
+
for (const item of catalogData) {
|
|
2030
|
+
const catalogResult = await this.client.createRecord('sc_cat_item', item);
|
|
2031
|
+
if (catalogResult.success && catalogResult.data?.result) {
|
|
2032
|
+
testResults.created_data.catalog_items.push({
|
|
2033
|
+
sys_id: catalogResult.data.result.sys_id,
|
|
2034
|
+
name: item.name,
|
|
2035
|
+
price: item.price
|
|
2036
|
+
});
|
|
2037
|
+
logger_js_1.logger.info('Created mock catalog item:', item.name);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
// Step 4: Execute the flow with test data
|
|
2042
|
+
try {
|
|
2043
|
+
// Prepare test inputs
|
|
2044
|
+
const flowInputs = {
|
|
2045
|
+
...test_inputs,
|
|
2046
|
+
test_mode: true,
|
|
2047
|
+
test_run_id: testResults.test_run_id
|
|
2048
|
+
};
|
|
2049
|
+
// If we created a test user, use them as the requested_for
|
|
2050
|
+
if (testResults.created_data.test_user) {
|
|
2051
|
+
flowInputs.requested_for = testResults.created_data.test_user.sys_id;
|
|
2052
|
+
flowInputs.opened_by = testResults.created_data.test_user.sys_id;
|
|
2053
|
+
}
|
|
2054
|
+
// If we created catalog items, use the first one
|
|
2055
|
+
if (testResults.created_data.catalog_items.length > 0) {
|
|
2056
|
+
flowInputs.cat_item = testResults.created_data.catalog_items[0].sys_id;
|
|
2057
|
+
}
|
|
2058
|
+
// Execute flow via REST API
|
|
2059
|
+
const executeResult = await this.executeFlowViaAPI(flow.sys_id, flowInputs);
|
|
2060
|
+
if (executeResult.success) {
|
|
2061
|
+
testResults.execution_results.status = 'running';
|
|
2062
|
+
testResults.execution_results.execution_id = executeResult.execution_id;
|
|
2063
|
+
// Monitor flow execution
|
|
2064
|
+
const executionStatus = await this.monitorFlowExecution(executeResult.execution_id, simulate_approvals);
|
|
2065
|
+
testResults.execution_results = {
|
|
2066
|
+
...testResults.execution_results,
|
|
2067
|
+
...executionStatus
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
else {
|
|
2071
|
+
testResults.execution_results.status = 'failed';
|
|
2072
|
+
testResults.execution_results.errors.push(executeResult.error || 'Unknown execution error');
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
catch (execError) {
|
|
2076
|
+
testResults.execution_results.status = 'error';
|
|
2077
|
+
testResults.execution_results.errors.push(execError.message);
|
|
2078
|
+
logger_js_1.logger.error('Flow execution error:', execError);
|
|
2079
|
+
}
|
|
2080
|
+
// Step 5: Cleanup if requested
|
|
2081
|
+
if (cleanup_after_test) {
|
|
2082
|
+
const cleanupResults = {
|
|
2083
|
+
user_deleted: false,
|
|
2084
|
+
catalog_items_deleted: 0,
|
|
2085
|
+
requests_cancelled: 0
|
|
2086
|
+
};
|
|
2087
|
+
// Delete test user
|
|
2088
|
+
if (testResults.created_data.test_user) {
|
|
2089
|
+
const deleteUserResult = await this.client.deleteRecord('sys_user', testResults.created_data.test_user.sys_id);
|
|
2090
|
+
cleanupResults.user_deleted = deleteUserResult.success;
|
|
2091
|
+
}
|
|
2092
|
+
// Delete catalog items
|
|
2093
|
+
for (const item of testResults.created_data.catalog_items) {
|
|
2094
|
+
const deleteItemResult = await this.client.deleteRecord('sc_cat_item', item.sys_id);
|
|
2095
|
+
if (deleteItemResult.success) {
|
|
2096
|
+
cleanupResults.catalog_items_deleted++;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
testResults.cleanup_status = cleanupResults;
|
|
2100
|
+
}
|
|
2101
|
+
// Format results
|
|
2102
|
+
const resultText = this.formatTestResults(testResults);
|
|
2103
|
+
return {
|
|
2104
|
+
content: [{
|
|
2105
|
+
type: 'text',
|
|
2106
|
+
text: resultText
|
|
2107
|
+
}]
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
catch (error) {
|
|
2111
|
+
logger_js_1.logger.error('Error testing flow with mock data:', error);
|
|
2112
|
+
testResults.execution_results.status = 'error';
|
|
2113
|
+
testResults.execution_results.errors.push(error.message);
|
|
2114
|
+
return {
|
|
2115
|
+
content: [{
|
|
2116
|
+
type: 'text',
|
|
2117
|
+
text: `โ Flow test failed\n\nError: ${error.message}\n\nTest Results:\n${JSON.stringify(testResults, null, 2)}`
|
|
2118
|
+
}]
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
async executeFlowViaAPI(flowId, inputs) {
|
|
2123
|
+
try {
|
|
2124
|
+
// Use the ServiceNow Flow API to execute the flow
|
|
2125
|
+
const result = await this.client.createRecord('sys_flow_context', {
|
|
2126
|
+
flow: flowId,
|
|
2127
|
+
inputs: JSON.stringify(inputs),
|
|
2128
|
+
state: 'executing'
|
|
2129
|
+
});
|
|
2130
|
+
if (result.success && result.data?.result) {
|
|
2131
|
+
return {
|
|
2132
|
+
success: true,
|
|
2133
|
+
execution_id: result.data.result.sys_id
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
return {
|
|
2137
|
+
success: false,
|
|
2138
|
+
error: 'Failed to start flow execution'
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
catch (error) {
|
|
2142
|
+
logger_js_1.logger.error('Error executing flow via API:', error);
|
|
2143
|
+
return {
|
|
2144
|
+
success: false,
|
|
2145
|
+
error: error.message
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
async monitorFlowExecution(executionId, simulateApprovals) {
|
|
2150
|
+
const maxWaitTime = 60000; // 60 seconds
|
|
2151
|
+
const pollInterval = 2000; // 2 seconds
|
|
2152
|
+
const startTime = Date.now();
|
|
2153
|
+
const executionResults = {
|
|
2154
|
+
status: 'running',
|
|
2155
|
+
steps_executed: [],
|
|
2156
|
+
approvals_simulated: [],
|
|
2157
|
+
errors: [],
|
|
2158
|
+
end_time: null,
|
|
2159
|
+
duration_ms: 0
|
|
2160
|
+
};
|
|
2161
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
2162
|
+
try {
|
|
2163
|
+
// Check flow context status
|
|
2164
|
+
const contextResult = await this.client.getRecord('sys_flow_context', executionId);
|
|
2165
|
+
if (!contextResult.success || !contextResult.data?.result) {
|
|
2166
|
+
executionResults.errors.push('Failed to get flow context');
|
|
2167
|
+
break;
|
|
2168
|
+
}
|
|
2169
|
+
const context = contextResult.data.result;
|
|
2170
|
+
executionResults.status = context.state;
|
|
2171
|
+
// Check for completed state
|
|
2172
|
+
if (context.state === 'complete' || context.state === 'cancelled' || context.state === 'error') {
|
|
2173
|
+
executionResults.end_time = context.sys_updated_on;
|
|
2174
|
+
executionResults.duration_ms = Date.now() - startTime;
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2177
|
+
// Check for approvals if simulating
|
|
2178
|
+
if (simulateApprovals) {
|
|
2179
|
+
const approvalResult = await this.client.searchRecords('sysapproval_approver', `source_table=sys_flow_context^document_id=${executionId}^state=requested`, 10);
|
|
2180
|
+
if (approvalResult.success && approvalResult.data?.result?.length > 0) {
|
|
2181
|
+
for (const approval of approvalResult.data.result) {
|
|
2182
|
+
// Auto-approve
|
|
2183
|
+
const approveResult = await this.client.updateRecord('sysapproval_approver', approval.sys_id, {
|
|
2184
|
+
state: 'approved',
|
|
2185
|
+
comments: 'Auto-approved by test framework'
|
|
2186
|
+
});
|
|
2187
|
+
if (approveResult.success) {
|
|
2188
|
+
executionResults.approvals_simulated.push({
|
|
2189
|
+
sys_id: approval.sys_id,
|
|
2190
|
+
approver: approval.approver.display_value,
|
|
2191
|
+
approved_at: new Date().toISOString()
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
// Wait before next poll
|
|
2198
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
2199
|
+
}
|
|
2200
|
+
catch (error) {
|
|
2201
|
+
executionResults.errors.push(error.message);
|
|
2202
|
+
executionResults.status = 'error';
|
|
2203
|
+
break;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
if (executionResults.status === 'running') {
|
|
2207
|
+
executionResults.status = 'timeout';
|
|
2208
|
+
executionResults.errors.push('Flow execution timed out after 60 seconds');
|
|
2209
|
+
}
|
|
2210
|
+
return executionResults;
|
|
2211
|
+
}
|
|
2212
|
+
formatTestResults(testResults) {
|
|
2213
|
+
let text = `๐งช Flow Test Results\n${'='.repeat(50)}\n\n`;
|
|
2214
|
+
text += `**Flow ID:** ${testResults.flow_id}\n`;
|
|
2215
|
+
text += `**Test Run ID:** ${testResults.test_run_id}\n\n`;
|
|
2216
|
+
// Test Data Created
|
|
2217
|
+
text += `๐ **Test Data Created:**\n`;
|
|
2218
|
+
if (testResults.created_data.test_user) {
|
|
2219
|
+
text += ` โ
Test User: ${testResults.created_data.test_user.user_name} (${testResults.created_data.test_user.sys_id})\n`;
|
|
2220
|
+
}
|
|
2221
|
+
if (testResults.created_data.catalog_items.length > 0) {
|
|
2222
|
+
text += ` โ
Catalog Items: ${testResults.created_data.catalog_items.length} created\n`;
|
|
2223
|
+
testResults.created_data.catalog_items.forEach((item) => {
|
|
2224
|
+
text += ` - ${item.name} ($${item.price})\n`;
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
// Execution Results
|
|
2228
|
+
text += `\n๐ **Execution Results:**\n`;
|
|
2229
|
+
text += ` Status: ${testResults.execution_results.status === 'complete' ? 'โ
' : 'โ'} ${testResults.execution_results.status}\n`;
|
|
2230
|
+
if (testResults.execution_results.start_time) {
|
|
2231
|
+
text += ` Start Time: ${testResults.execution_results.start_time}\n`;
|
|
2232
|
+
}
|
|
2233
|
+
if (testResults.execution_results.end_time) {
|
|
2234
|
+
text += ` End Time: ${testResults.execution_results.end_time}\n`;
|
|
2235
|
+
}
|
|
2236
|
+
if (testResults.execution_results.duration_ms) {
|
|
2237
|
+
text += ` Duration: ${(testResults.execution_results.duration_ms / 1000).toFixed(2)}s\n`;
|
|
2238
|
+
}
|
|
2239
|
+
if (testResults.execution_results.approvals_simulated.length > 0) {
|
|
2240
|
+
text += `\n โ
Approvals Simulated: ${testResults.execution_results.approvals_simulated.length}\n`;
|
|
2241
|
+
testResults.execution_results.approvals_simulated.forEach((approval) => {
|
|
2242
|
+
text += ` - ${approval.approver} at ${approval.approved_at}\n`;
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
if (testResults.execution_results.errors.length > 0) {
|
|
2246
|
+
text += `\n โ Errors:\n`;
|
|
2247
|
+
testResults.execution_results.errors.forEach((error) => {
|
|
2248
|
+
text += ` - ${error}\n`;
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
// Cleanup Status
|
|
2252
|
+
if (testResults.cleanup_status) {
|
|
2253
|
+
text += `\n๐งน **Cleanup Status:**\n`;
|
|
2254
|
+
text += ` User Deleted: ${testResults.cleanup_status.user_deleted ? 'โ
' : 'โ'}\n`;
|
|
2255
|
+
text += ` Catalog Items Deleted: ${testResults.cleanup_status.catalog_items_deleted}\n`;
|
|
2256
|
+
text += ` Requests Cancelled: ${testResults.cleanup_status.requests_cancelled}\n`;
|
|
2257
|
+
}
|
|
2258
|
+
// Recommendations
|
|
2259
|
+
text += `\n๐ก **Recommendations:**\n`;
|
|
2260
|
+
if (testResults.execution_results.status === 'complete') {
|
|
2261
|
+
text += ` โ
Flow executed successfully! Ready for production use.\n`;
|
|
2262
|
+
}
|
|
2263
|
+
else if (testResults.execution_results.status === 'timeout') {
|
|
2264
|
+
text += ` โ ๏ธ Flow execution timed out. Consider:\n`;
|
|
2265
|
+
text += ` - Checking for infinite loops\n`;
|
|
2266
|
+
text += ` - Reviewing wait conditions\n`;
|
|
2267
|
+
text += ` - Optimizing flow performance\n`;
|
|
2268
|
+
}
|
|
2269
|
+
else {
|
|
2270
|
+
text += ` โ Flow execution failed. Please:\n`;
|
|
2271
|
+
text += ` - Review error messages above\n`;
|
|
2272
|
+
text += ` - Check flow configuration\n`;
|
|
2273
|
+
text += ` - Verify all dependencies exist\n`;
|
|
2274
|
+
}
|
|
2275
|
+
return text;
|
|
2276
|
+
}
|
|
2277
|
+
async handleLinkCatalogToFlow(args) {
|
|
2278
|
+
const { catalog_item_id, flow_id, link_type = 'flow_catalog_process', variable_mapping = [], trigger_condition = 'current.stage == "request_approved"', execution_options = {}, test_link = false } = args;
|
|
2279
|
+
logger_js_1.logger.info(`Linking catalog item ${catalog_item_id} to flow ${flow_id}`, {
|
|
2280
|
+
link_type,
|
|
2281
|
+
variable_mapping_count: variable_mapping.length
|
|
2282
|
+
});
|
|
2283
|
+
const linkResults = {
|
|
2284
|
+
catalog_item: null,
|
|
2285
|
+
flow: null,
|
|
2286
|
+
link_created: false,
|
|
2287
|
+
link_details: null,
|
|
2288
|
+
variable_mappings: [],
|
|
2289
|
+
test_results: null,
|
|
2290
|
+
errors: []
|
|
2291
|
+
};
|
|
2292
|
+
try {
|
|
2293
|
+
// Step 1: Find the catalog item
|
|
2294
|
+
const catalogResult = await this.client.searchRecords('sc_cat_item', `sys_id=${catalog_item_id}^ORname=${catalog_item_id}`, 1);
|
|
2295
|
+
if (!catalogResult.success || !catalogResult.data?.result?.length) {
|
|
2296
|
+
throw new Error(`Catalog item not found: ${catalog_item_id}`);
|
|
2297
|
+
}
|
|
2298
|
+
linkResults.catalog_item = {
|
|
2299
|
+
sys_id: catalogResult.data.result[0].sys_id,
|
|
2300
|
+
name: catalogResult.data.result[0].name,
|
|
2301
|
+
category: catalogResult.data.result[0].sc_categories?.display_value
|
|
2302
|
+
};
|
|
2303
|
+
// Step 2: Find the flow
|
|
2304
|
+
const flowResult = await this.client.searchRecords('sys_hub_flow', `sys_id=${flow_id}^ORname=${flow_id}`, 1);
|
|
2305
|
+
if (!flowResult.success || !flowResult.data?.result?.length) {
|
|
2306
|
+
throw new Error(`Flow not found: ${flow_id}`);
|
|
2307
|
+
}
|
|
2308
|
+
linkResults.flow = {
|
|
2309
|
+
sys_id: flowResult.data.result[0].sys_id,
|
|
2310
|
+
name: flowResult.data.result[0].name,
|
|
2311
|
+
type: flowResult.data.result[0].type
|
|
2312
|
+
};
|
|
2313
|
+
// Step 3: Create the link based on link_type
|
|
2314
|
+
switch (link_type) {
|
|
2315
|
+
case 'flow_catalog_process': {
|
|
2316
|
+
// Modern approach: Use Flow Designer catalog process
|
|
2317
|
+
const processData = {
|
|
2318
|
+
catalog_item: linkResults.catalog_item.sys_id,
|
|
2319
|
+
flow: linkResults.flow.sys_id,
|
|
2320
|
+
active: true,
|
|
2321
|
+
condition: trigger_condition,
|
|
2322
|
+
run_as: execution_options.run_as || 'system',
|
|
2323
|
+
wait_for_completion: execution_options.wait_for_completion !== false
|
|
2324
|
+
};
|
|
2325
|
+
const processResult = await this.client.createRecord('sc_cat_item_producer', processData);
|
|
2326
|
+
if (processResult.success && processResult.data?.result) {
|
|
2327
|
+
linkResults.link_created = true;
|
|
2328
|
+
linkResults.link_details = {
|
|
2329
|
+
type: 'flow_catalog_process',
|
|
2330
|
+
sys_id: processResult.data.result.sys_id,
|
|
2331
|
+
table: 'sc_cat_item_producer'
|
|
2332
|
+
};
|
|
2333
|
+
// Create variable mappings
|
|
2334
|
+
for (const mapping of variable_mapping) {
|
|
2335
|
+
const mappingData = {
|
|
2336
|
+
producer: processResult.data.result.sys_id,
|
|
2337
|
+
catalog_variable: mapping.catalog_variable,
|
|
2338
|
+
flow_input: mapping.flow_input,
|
|
2339
|
+
transform_script: mapping.transform || ''
|
|
2340
|
+
};
|
|
2341
|
+
const mappingResult = await this.client.createRecord('sc_cat_item_producer_mapping', mappingData);
|
|
2342
|
+
if (mappingResult.success) {
|
|
2343
|
+
linkResults.variable_mappings.push({
|
|
2344
|
+
catalog_variable: mapping.catalog_variable,
|
|
2345
|
+
flow_input: mapping.flow_input,
|
|
2346
|
+
mapping_sys_id: mappingResult.data?.result?.sys_id
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
break;
|
|
2352
|
+
}
|
|
2353
|
+
case 'workflow': {
|
|
2354
|
+
// Legacy approach: Direct workflow assignment
|
|
2355
|
+
const updateData = {
|
|
2356
|
+
workflow: linkResults.flow.sys_id,
|
|
2357
|
+
no_order: false,
|
|
2358
|
+
no_proceed_checkout: false,
|
|
2359
|
+
no_quantity: false,
|
|
2360
|
+
no_delivery_time: false
|
|
2361
|
+
};
|
|
2362
|
+
const updateResult = await this.client.updateRecord('sc_cat_item', linkResults.catalog_item.sys_id, updateData);
|
|
2363
|
+
if (updateResult.success) {
|
|
2364
|
+
linkResults.link_created = true;
|
|
2365
|
+
linkResults.link_details = {
|
|
2366
|
+
type: 'workflow',
|
|
2367
|
+
field: 'workflow',
|
|
2368
|
+
value: linkResults.flow.sys_id
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
break;
|
|
2372
|
+
}
|
|
2373
|
+
case 'process_engine': {
|
|
2374
|
+
// Process Engine approach
|
|
2375
|
+
const engineData = {
|
|
2376
|
+
catalog_item: linkResults.catalog_item.sys_id,
|
|
2377
|
+
engine_type: 'flow',
|
|
2378
|
+
engine_id: linkResults.flow.sys_id,
|
|
2379
|
+
active: true,
|
|
2380
|
+
order: 100,
|
|
2381
|
+
condition: trigger_condition
|
|
2382
|
+
};
|
|
2383
|
+
const engineResult = await this.client.createRecord('sc_process_flow', engineData);
|
|
2384
|
+
if (engineResult.success && engineResult.data?.result) {
|
|
2385
|
+
linkResults.link_created = true;
|
|
2386
|
+
linkResults.link_details = {
|
|
2387
|
+
type: 'process_engine',
|
|
2388
|
+
sys_id: engineResult.data.result.sys_id,
|
|
2389
|
+
table: 'sc_process_flow'
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
break;
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
// Step 4: Test the link if requested
|
|
2396
|
+
if (test_link && linkResults.link_created) {
|
|
2397
|
+
logger_js_1.logger.info('Testing catalog-flow link with sample request');
|
|
2398
|
+
const testRequestData = {
|
|
2399
|
+
cat_item: linkResults.catalog_item.sys_id,
|
|
2400
|
+
requested_for: 'admin', // Default to admin user
|
|
2401
|
+
quantity: 1,
|
|
2402
|
+
comments: `Test request for catalog-flow link verification [${new Date().toISOString()}]`
|
|
2403
|
+
};
|
|
2404
|
+
// Add any mapped variables with test values
|
|
2405
|
+
for (const mapping of linkResults.variable_mappings) {
|
|
2406
|
+
testRequestData[`variables.${mapping.catalog_variable}`] = 'Test Value';
|
|
2407
|
+
}
|
|
2408
|
+
const testResult = await this.client.createRecord('sc_request', testRequestData);
|
|
2409
|
+
if (testResult.success && testResult.data?.result) {
|
|
2410
|
+
linkResults.test_results = {
|
|
2411
|
+
request_created: true,
|
|
2412
|
+
request_number: testResult.data.result.number,
|
|
2413
|
+
request_sys_id: testResult.data.result.sys_id,
|
|
2414
|
+
status: 'Check the request in ServiceNow to verify flow execution'
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
else {
|
|
2418
|
+
linkResults.test_results = {
|
|
2419
|
+
request_created: false,
|
|
2420
|
+
error: 'Failed to create test request'
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
// Format results
|
|
2425
|
+
let resultText = `๐ Catalog-Flow Link Results\n${'='.repeat(50)}\n\n`;
|
|
2426
|
+
resultText += `**Catalog Item:** ${linkResults.catalog_item.name} (${linkResults.catalog_item.sys_id})\n`;
|
|
2427
|
+
resultText += `**Flow:** ${linkResults.flow.name} (${linkResults.flow.sys_id})\n`;
|
|
2428
|
+
resultText += `**Link Type:** ${link_type}\n\n`;
|
|
2429
|
+
if (linkResults.link_created) {
|
|
2430
|
+
resultText += `โ
**Link Created Successfully!**\n`;
|
|
2431
|
+
resultText += ` Type: ${linkResults.link_details.type}\n`;
|
|
2432
|
+
if (linkResults.link_details.sys_id) {
|
|
2433
|
+
resultText += ` Record: ${linkResults.link_details.table} (${linkResults.link_details.sys_id})\n`;
|
|
2434
|
+
}
|
|
2435
|
+
if (linkResults.variable_mappings.length > 0) {
|
|
2436
|
+
resultText += `\n๐ **Variable Mappings Created:**\n`;
|
|
2437
|
+
linkResults.variable_mappings.forEach(mapping => {
|
|
2438
|
+
resultText += ` - ${mapping.catalog_variable} โ ${mapping.flow_input}\n`;
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
if (linkResults.test_results) {
|
|
2442
|
+
resultText += `\n๐งช **Test Results:**\n`;
|
|
2443
|
+
if (linkResults.test_results.request_created) {
|
|
2444
|
+
resultText += ` โ
Test request created: ${linkResults.test_results.request_number}\n`;
|
|
2445
|
+
resultText += ` ๐ ${linkResults.test_results.status}\n`;
|
|
2446
|
+
}
|
|
2447
|
+
else {
|
|
2448
|
+
resultText += ` โ ${linkResults.test_results.error}\n`;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
resultText += `\n๐ก **Next Steps:**\n`;
|
|
2452
|
+
resultText += `1. Test the catalog item by creating a request\n`;
|
|
2453
|
+
resultText += `2. Verify the flow executes with correct inputs\n`;
|
|
2454
|
+
resultText += `3. Monitor the request fulfillment process\n`;
|
|
2455
|
+
if (link_type === 'flow_catalog_process') {
|
|
2456
|
+
resultText += `\n๐ **Note:** Using modern Flow Designer catalog process\n`;
|
|
2457
|
+
resultText += ` - Flow will trigger on: ${trigger_condition}\n`;
|
|
2458
|
+
resultText += ` - Execution context: ${execution_options.run_as || 'system'}\n`;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
else {
|
|
2462
|
+
resultText += `โ **Failed to create link**\n`;
|
|
2463
|
+
if (linkResults.errors.length > 0) {
|
|
2464
|
+
resultText += `\nErrors:\n`;
|
|
2465
|
+
linkResults.errors.forEach(error => {
|
|
2466
|
+
resultText += ` - ${error}\n`;
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
return {
|
|
2471
|
+
content: [{
|
|
2472
|
+
type: 'text',
|
|
2473
|
+
text: resultText
|
|
2474
|
+
}]
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
catch (error) {
|
|
2478
|
+
logger_js_1.logger.error('Error linking catalog to flow:', error);
|
|
2479
|
+
linkResults.errors.push(error.message);
|
|
2480
|
+
return {
|
|
2481
|
+
content: [{
|
|
2482
|
+
type: 'text',
|
|
2483
|
+
text: `โ Failed to link catalog to flow\n\nError: ${error.message}\n\n` +
|
|
2484
|
+
`๐ก Troubleshooting:\n` +
|
|
2485
|
+
`1. Verify catalog item exists: ${catalog_item_id}\n` +
|
|
2486
|
+
`2. Verify flow exists: ${flow_id}\n` +
|
|
2487
|
+
`3. Check permissions for creating ${link_type} links\n` +
|
|
2488
|
+
`4. Ensure flow is active and published\n\n` +
|
|
2489
|
+
`Results: ${JSON.stringify(linkResults, null, 2)}`
|
|
2490
|
+
}]
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
getServiceNowUrl() {
|
|
2495
|
+
// This would need to get the instance URL from credentials
|
|
2496
|
+
return 'https://instance.service-now.com';
|
|
2497
|
+
}
|
|
1275
2498
|
async run() {
|
|
1276
2499
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
1277
2500
|
await this.server.connect(transport);
|