snow-flow 1.1.35 ā 1.1.37
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/dist/mcp/servicenow-deployment-mcp.js +627 -16
- package/dist/mcp/servicenow-deployment-mcp.js.map +1 -1
- package/dist/utils/servicenow-client.d.ts +22 -2
- package/dist/utils/servicenow-client.d.ts.map +1 -1
- package/dist/utils/servicenow-client.js +236 -3
- package/dist/utils/servicenow-client.js.map +1 -1
- package/dist/version.d.ts +11 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +48 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -72,12 +72,14 @@ class ServiceNowDeploymentMCP {
|
|
|
72
72
|
properties: {
|
|
73
73
|
name: { type: 'string', description: 'Flow name' },
|
|
74
74
|
description: { type: 'string', description: 'Flow description' },
|
|
75
|
+
flow_type: { type: 'string', enum: ['flow', 'subflow', 'action'], description: 'Type of flow to create (default: flow)', default: 'flow' },
|
|
75
76
|
table: { type: 'string', description: 'Target table (e.g., sc_request, incident)' },
|
|
76
77
|
trigger_type: { type: 'string', enum: ['record_created', 'record_updated', 'scheduled', 'manual'], description: 'Flow trigger type' },
|
|
77
78
|
condition: { type: 'string', description: 'Trigger condition (encoded query)' },
|
|
78
79
|
active: { type: 'boolean', description: 'Activate flow on deployment' },
|
|
79
80
|
flow_definition: { type: 'string', description: 'Flow Designer definition JSON' },
|
|
80
81
|
category: { type: 'string', description: 'Flow category (e.g., approval, automation)' },
|
|
82
|
+
validate_before_deploy: { type: 'boolean', description: 'Validate flow definition before deployment', default: true },
|
|
81
83
|
},
|
|
82
84
|
required: ['name', 'description', 'flow_definition', 'trigger_type'],
|
|
83
85
|
},
|
|
@@ -231,6 +233,17 @@ class ServiceNowDeploymentMCP {
|
|
|
231
233
|
properties: {},
|
|
232
234
|
},
|
|
233
235
|
},
|
|
236
|
+
{
|
|
237
|
+
name: 'snow_auth_diagnostics',
|
|
238
|
+
description: 'Run comprehensive authentication and permission diagnostics to troubleshoot deployment issues',
|
|
239
|
+
inputSchema: {
|
|
240
|
+
type: 'object',
|
|
241
|
+
properties: {
|
|
242
|
+
run_write_test: { type: 'boolean', description: 'Test widget write permissions (creates and deletes test widget)', default: true },
|
|
243
|
+
include_recommendations: { type: 'boolean', description: 'Include troubleshooting recommendations', default: true },
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
234
247
|
{
|
|
235
248
|
name: 'snow_preview_widget',
|
|
236
249
|
description: 'Preview widget rendering with test data to verify HTML/CSS/JS integration before deployment',
|
|
@@ -287,6 +300,74 @@ class ServiceNowDeploymentMCP {
|
|
|
287
300
|
required: ['sys_id'],
|
|
288
301
|
},
|
|
289
302
|
},
|
|
303
|
+
{
|
|
304
|
+
name: 'snow_smart_update_set',
|
|
305
|
+
description: 'Smart update set creation with context detection - automatically creates new update sets for new tasks',
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: 'object',
|
|
308
|
+
properties: {
|
|
309
|
+
detect_context: { type: 'boolean', description: 'Auto-detect task context change', default: true },
|
|
310
|
+
name_prefix: { type: 'string', description: 'Update set name prefix', default: 'AUTO' },
|
|
311
|
+
separate_by_task: { type: 'boolean', description: 'Create new update set for each task', default: true },
|
|
312
|
+
close_previous: { type: 'boolean', description: 'Close previous update set', default: true },
|
|
313
|
+
description: { type: 'string', description: 'Update set description' },
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: 'snow_validate_flow_definition',
|
|
319
|
+
description: 'Validate flow definition before deployment with preview and test mode',
|
|
320
|
+
inputSchema: {
|
|
321
|
+
type: 'object',
|
|
322
|
+
properties: {
|
|
323
|
+
definition: { type: 'string', description: 'Flow definition JSON to validate' },
|
|
324
|
+
flow_type: { type: 'string', enum: ['flow', 'subflow', 'action'], default: 'flow' },
|
|
325
|
+
show_preview: { type: 'boolean', description: 'Show visual preview', default: true },
|
|
326
|
+
test_mode: { type: 'boolean', description: 'Run in test mode', default: false },
|
|
327
|
+
check_dependencies: { type: 'boolean', description: 'Check for missing dependencies', default: true },
|
|
328
|
+
},
|
|
329
|
+
required: ['definition'],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: 'snow_create_solution_package',
|
|
334
|
+
description: 'Create a solution package grouping related artifacts with a new update set',
|
|
335
|
+
inputSchema: {
|
|
336
|
+
type: 'object',
|
|
337
|
+
properties: {
|
|
338
|
+
name: { type: 'string', description: 'Solution package name' },
|
|
339
|
+
description: { type: 'string', description: 'Package description' },
|
|
340
|
+
artifacts: {
|
|
341
|
+
type: 'array',
|
|
342
|
+
description: 'Artifacts to include in the package',
|
|
343
|
+
items: {
|
|
344
|
+
type: 'object',
|
|
345
|
+
properties: {
|
|
346
|
+
type: { type: 'string', enum: ['flow', 'widget', 'script_include', 'business_rule', 'table'] },
|
|
347
|
+
create: { type: 'object', description: 'Artifact creation configuration' },
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
new_update_set: { type: 'boolean', description: 'Force new update set', default: true },
|
|
352
|
+
},
|
|
353
|
+
required: ['name', 'artifacts'],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'snow_flow_wizard',
|
|
358
|
+
description: 'Interactive flow creation wizard with step-by-step guidance',
|
|
359
|
+
inputSchema: {
|
|
360
|
+
type: 'object',
|
|
361
|
+
properties: {
|
|
362
|
+
name: { type: 'string', description: 'Flow name' },
|
|
363
|
+
interactive: { type: 'boolean', description: 'Enable interactive mode', default: true },
|
|
364
|
+
preview_each_step: { type: 'boolean', description: 'Preview after each step', default: true },
|
|
365
|
+
test_as_you_build: { type: 'boolean', description: 'Test flow during creation', default: true },
|
|
366
|
+
flow_type: { type: 'string', enum: ['flow', 'subflow', 'action'], default: 'flow' },
|
|
367
|
+
},
|
|
368
|
+
required: ['name'],
|
|
369
|
+
},
|
|
370
|
+
},
|
|
290
371
|
],
|
|
291
372
|
}));
|
|
292
373
|
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
@@ -320,10 +401,20 @@ class ServiceNowDeploymentMCP {
|
|
|
320
401
|
return await this.validateSysId(args);
|
|
321
402
|
case 'snow_deployment_debug':
|
|
322
403
|
return await this.getDeploymentDebug(args);
|
|
404
|
+
case 'snow_auth_diagnostics':
|
|
405
|
+
return await this.runAuthDiagnostics(args);
|
|
323
406
|
case 'snow_preview_widget':
|
|
324
407
|
return await this.previewWidget(args);
|
|
325
408
|
case 'snow_widget_test':
|
|
326
409
|
return await this.testWidget(args);
|
|
410
|
+
case 'snow_smart_update_set':
|
|
411
|
+
return await this.smartUpdateSet(args);
|
|
412
|
+
case 'snow_validate_flow_definition':
|
|
413
|
+
return await this.validateFlowDefinition(args);
|
|
414
|
+
case 'snow_create_solution_package':
|
|
415
|
+
return await this.createSolutionPackage(args);
|
|
416
|
+
case 'snow_flow_wizard':
|
|
417
|
+
return await this.flowWizard(args);
|
|
327
418
|
default:
|
|
328
419
|
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
329
420
|
}
|
|
@@ -537,28 +628,47 @@ class ServiceNowDeploymentMCP {
|
|
|
537
628
|
};
|
|
538
629
|
let troubleshootingSteps = '';
|
|
539
630
|
if (is403Error(directError) || is403Error(tableError)) {
|
|
631
|
+
// Run authentication diagnostics automatically on 403 errors
|
|
632
|
+
this.logger.info('403 error detected, running automatic authentication diagnostics...');
|
|
633
|
+
let diagnosticsResult = '';
|
|
634
|
+
try {
|
|
635
|
+
const diagResponse = await this.runAuthDiagnostics({ include_recommendations: true });
|
|
636
|
+
diagnosticsResult = diagResponse.content[0].text;
|
|
637
|
+
}
|
|
638
|
+
catch (diagError) {
|
|
639
|
+
this.logger.warn('Could not run auto-diagnostics:', diagError);
|
|
640
|
+
diagnosticsResult = 'ā Auto-diagnostics failed. Run snow_auth_diagnostics manually for detailed analysis.';
|
|
641
|
+
}
|
|
540
642
|
troubleshootingSteps = `
|
|
541
|
-
š§ **
|
|
643
|
+
š§ **AUTO-DIAGNOSTICS RESULTS:**
|
|
644
|
+
|
|
645
|
+
${diagnosticsResult}
|
|
646
|
+
|
|
647
|
+
š§ **Additional 403 Permission Troubleshooting:**
|
|
542
648
|
|
|
543
|
-
1. **
|
|
649
|
+
1. **CRITICAL: Check for URL issues in .env file:**
|
|
650
|
+
- Ensure SNOW_INSTANCE doesn't have trailing slash
|
|
651
|
+
- Should be: dev123456.service-now.com (NOT dev123456.service-now.com/)
|
|
652
|
+
|
|
653
|
+
2. **Re-authenticate with expanded OAuth scopes:**
|
|
544
654
|
\`\`\`bash
|
|
545
655
|
snow-flow auth login
|
|
546
656
|
\`\`\`
|
|
547
657
|
(This now requests 'write' and 'admin' permissions)
|
|
548
658
|
|
|
549
|
-
|
|
659
|
+
3. **Verify ServiceNow OAuth Application settings:**
|
|
550
660
|
- Navigate to: System OAuth > Application Registry
|
|
551
661
|
- Find your OAuth application
|
|
552
662
|
- Ensure "Redirect URL" includes: http://localhost:3005/callback
|
|
553
663
|
- Verify "Accessible from" is set to "All application scopes"
|
|
554
664
|
|
|
555
|
-
|
|
665
|
+
4. **Check user permissions in ServiceNow:**
|
|
556
666
|
- Navigate to: User Administration > Users
|
|
557
667
|
- Find your user account
|
|
558
668
|
- Verify you have roles: admin, service_portal_admin, or sp_admin
|
|
559
669
|
- Add missing roles if needed
|
|
560
670
|
|
|
561
|
-
|
|
671
|
+
5. **Update Set permissions:**
|
|
562
672
|
- Ensure you have an active Update Set: System Update Sets > Local Update Sets
|
|
563
673
|
- Verify Update Set state is "In Progress"
|
|
564
674
|
- Check Update Set permissions allow widget creation
|
|
@@ -764,7 +874,30 @@ Use \`snow_deployment_debug\` for more information about this session.`,
|
|
|
764
874
|
],
|
|
765
875
|
};
|
|
766
876
|
}
|
|
767
|
-
|
|
877
|
+
const flowType = args.flow_type || 'flow';
|
|
878
|
+
this.logger.info(`Deploying ${flowType} to ServiceNow`, { name: args.name, type: flowType });
|
|
879
|
+
// Validate flow definition first if requested
|
|
880
|
+
if (args.validate_before_deploy !== false) {
|
|
881
|
+
const validationResult = await this.validateFlowDefinition({
|
|
882
|
+
definition: args.flow_definition,
|
|
883
|
+
flow_type: flowType,
|
|
884
|
+
show_preview: false,
|
|
885
|
+
test_mode: false,
|
|
886
|
+
check_dependencies: true
|
|
887
|
+
});
|
|
888
|
+
// Check if validation failed
|
|
889
|
+
const validationText = validationResult.content?.[0]?.text || '';
|
|
890
|
+
if (validationText.includes('ā') || validationText.includes('ERROR')) {
|
|
891
|
+
return {
|
|
892
|
+
content: [
|
|
893
|
+
{
|
|
894
|
+
type: 'text',
|
|
895
|
+
text: `ā Flow validation failed. Please fix the following issues:\n\n${validationText}\n\nUse snow_validate_flow_definition to preview and test your flow before deployment.`
|
|
896
|
+
}
|
|
897
|
+
]
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
}
|
|
768
901
|
// Ensure Update Set is active
|
|
769
902
|
const { updateSetId, updateSetName } = await this.ensureUpdateSet('Flow', args.name);
|
|
770
903
|
// Check if this is a master flow with linked artifacts
|
|
@@ -804,14 +937,11 @@ Use \`snow_deployment_debug\` for more information about this session.`,
|
|
|
804
937
|
return activity;
|
|
805
938
|
});
|
|
806
939
|
}
|
|
807
|
-
// Create
|
|
940
|
+
// Create flow data based on flow type
|
|
808
941
|
const flowData = {
|
|
809
942
|
name: args.name,
|
|
810
943
|
description: args.description,
|
|
811
944
|
active: args.active !== false,
|
|
812
|
-
table: args.table || '',
|
|
813
|
-
trigger_type: args.trigger_type,
|
|
814
|
-
condition: args.condition || '',
|
|
815
945
|
flow_definition: JSON.stringify(flowDefinition),
|
|
816
946
|
category: args.category || 'automation',
|
|
817
947
|
// Additional fields for composed flows
|
|
@@ -819,8 +949,43 @@ Use \`snow_deployment_debug\` for more information about this session.`,
|
|
|
819
949
|
linked_artifact_count: linkedArtifacts.length,
|
|
820
950
|
artifact_references: deployedArtifacts.map(a => a.sys_id).join(',')
|
|
821
951
|
};
|
|
822
|
-
//
|
|
823
|
-
|
|
952
|
+
// Configure based on flow type
|
|
953
|
+
switch (flowType) {
|
|
954
|
+
case 'flow':
|
|
955
|
+
flowData.table = args.table || '';
|
|
956
|
+
flowData.trigger_type = args.trigger_type;
|
|
957
|
+
flowData.condition = args.condition || '';
|
|
958
|
+
flowData.type = 'flow';
|
|
959
|
+
break;
|
|
960
|
+
case 'subflow':
|
|
961
|
+
// Subflows don't have triggers, they're called by other flows
|
|
962
|
+
flowData.type = 'subflow';
|
|
963
|
+
flowData.inputs = flowDefinition.inputs || [];
|
|
964
|
+
flowData.outputs = flowDefinition.outputs || [];
|
|
965
|
+
break;
|
|
966
|
+
case 'action':
|
|
967
|
+
// Actions are reusable components
|
|
968
|
+
flowData.type = 'action';
|
|
969
|
+
flowData.action_type = args.action_type || 'custom';
|
|
970
|
+
flowData.inputs = flowDefinition.inputs || [];
|
|
971
|
+
flowData.outputs = flowDefinition.outputs || [];
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
// Deploy to ServiceNow using appropriate API based on flow type
|
|
975
|
+
let result;
|
|
976
|
+
switch (flowType) {
|
|
977
|
+
case 'flow':
|
|
978
|
+
result = await this.client.createFlow(flowData);
|
|
979
|
+
break;
|
|
980
|
+
case 'subflow':
|
|
981
|
+
result = await this.client.createSubflow(flowData);
|
|
982
|
+
break;
|
|
983
|
+
case 'action':
|
|
984
|
+
result = await this.client.createFlowAction(flowData);
|
|
985
|
+
break;
|
|
986
|
+
default:
|
|
987
|
+
throw new Error(`Unknown flow type: ${flowType}`);
|
|
988
|
+
}
|
|
824
989
|
const credentials = await this.oauth.loadCredentials();
|
|
825
990
|
const flowUrl = result.success && result.data
|
|
826
991
|
? `https://${credentials?.instance}/$flow-designer.do#/flow/${result.data.sys_id}`
|
|
@@ -837,11 +1002,14 @@ Use \`snow_deployment_debug\` for more information about this session.`,
|
|
|
837
1002
|
type: 'text',
|
|
838
1003
|
text: `ā
Flow Designer flow deployed successfully!
|
|
839
1004
|
|
|
840
|
-
š
|
|
1005
|
+
š **${flowType.charAt(0).toUpperCase() + flowType.slice(1)} Details:**
|
|
841
1006
|
- Name: ${args.name}
|
|
842
|
-
- Type: ${
|
|
843
|
-
-
|
|
844
|
-
|
|
1007
|
+
- Flow Type: ${flowType === 'flow' ? 'š Flow' : flowType === 'subflow' ? 'š Subflow' : 'ā” Action'}
|
|
1008
|
+
- Composed: ${isComposedFlow ? 'š§ Yes - Intelligent Composed Flow' : 'ā No - Standard'}
|
|
1009
|
+
${flowType === 'flow' ? `- Trigger Type: ${args.trigger_type}
|
|
1010
|
+
- Table: ${args.table || 'N/A'}` : ''}
|
|
1011
|
+
${flowType !== 'flow' ? `- Inputs: ${flowDefinition.inputs?.length || 0}
|
|
1012
|
+
- Outputs: ${flowDefinition.outputs?.length || 0}` : ''}
|
|
845
1013
|
- Category: ${args.category || 'automation'}
|
|
846
1014
|
- Active: ${args.active !== false ? 'Yes' : 'No'}
|
|
847
1015
|
|
|
@@ -1478,6 +1646,92 @@ ${sessionSummary.statusCounts.pending > 0 ? '- š Complete pending deployments
|
|
|
1478
1646
|
};
|
|
1479
1647
|
}
|
|
1480
1648
|
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Run comprehensive authentication and permission diagnostics
|
|
1651
|
+
*/
|
|
1652
|
+
async runAuthDiagnostics(args) {
|
|
1653
|
+
try {
|
|
1654
|
+
this.logger.info('Running authentication diagnostics...');
|
|
1655
|
+
const diagnostics = await this.client.validateDeploymentPermissions();
|
|
1656
|
+
if (!diagnostics.success) {
|
|
1657
|
+
this.logger.warn('Authentication diagnostics found issues:', diagnostics.data);
|
|
1658
|
+
}
|
|
1659
|
+
const data = diagnostics.data;
|
|
1660
|
+
const { tests, summary, recommendations } = data;
|
|
1661
|
+
// Format test results
|
|
1662
|
+
const testResults = Object.entries(tests).map(([name, result]) => `**${name}:** ${result.status}
|
|
1663
|
+
- ${result.description}
|
|
1664
|
+
${result.error ? `- Error: ${result.error}` : ''}
|
|
1665
|
+
${result.http_status ? `- HTTP Status: ${result.http_status}` : ''}`).join('\n\n');
|
|
1666
|
+
// Format recommendations
|
|
1667
|
+
const recommendationText = args.include_recommendations !== false && recommendations.length > 0
|
|
1668
|
+
? `\n\n**š§ Troubleshooting Recommendations:**\n${recommendations.map((rec) => `- ${rec}`).join('\n')}`
|
|
1669
|
+
: '';
|
|
1670
|
+
// Generate URL fix recommendation if we detect the trailing slash issue
|
|
1671
|
+
const urlFixRecommendation = data.instance_url.includes('//')
|
|
1672
|
+
? '\n\n**šØ CRITICAL URL ISSUE DETECTED:**\n- Your SNOW_INSTANCE in .env has a trailing slash\n- This causes malformed URLs like https://instance.com//api/\n- Remove the trailing slash from SNOW_INSTANCE=your-instance.com/'
|
|
1673
|
+
: '';
|
|
1674
|
+
return {
|
|
1675
|
+
content: [
|
|
1676
|
+
{
|
|
1677
|
+
type: 'text',
|
|
1678
|
+
text: `š **Authentication & Deployment Diagnostics**
|
|
1679
|
+
|
|
1680
|
+
**Instance:** ${data.instance_url}
|
|
1681
|
+
**Timestamp:** ${data.timestamp}
|
|
1682
|
+
|
|
1683
|
+
**š Summary:**
|
|
1684
|
+
- Total Tests: ${summary.total_tests}
|
|
1685
|
+
- Passed: ${summary.passed} ā
|
|
1686
|
+
- Failed: ${summary.failed} ${summary.failed > 0 ? 'ā' : ''}
|
|
1687
|
+
- Overall Status: ${summary.overall_status}
|
|
1688
|
+
|
|
1689
|
+
**š§Ŗ Detailed Test Results:**
|
|
1690
|
+
|
|
1691
|
+
${testResults}${recommendationText}${urlFixRecommendation}
|
|
1692
|
+
|
|
1693
|
+
**š” Next Steps:**
|
|
1694
|
+
${summary.failed === 0
|
|
1695
|
+
? 'ā
All authentication tests passed! Your deployment should work correctly.'
|
|
1696
|
+
: `ā ${summary.failed} test(s) failed. Follow the recommendations above to fix the issues.`}
|
|
1697
|
+
|
|
1698
|
+
**š ļø Quick Fixes:**
|
|
1699
|
+
1. If 403 errors persist: Check OAuth scopes in ServiceNow (System OAuth > Application Registry)
|
|
1700
|
+
2. If URL issues: Remove trailing slash from SNOW_INSTANCE in .env file
|
|
1701
|
+
3. If role issues: Contact ServiceNow admin to assign sp_portal_manager or admin role
|
|
1702
|
+
4. Re-test: Run this diagnostic again after making changes`
|
|
1703
|
+
}
|
|
1704
|
+
]
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
catch (error) {
|
|
1708
|
+
this.logger.error('Authentication diagnostics failed:', error);
|
|
1709
|
+
return {
|
|
1710
|
+
content: [
|
|
1711
|
+
{
|
|
1712
|
+
type: 'text',
|
|
1713
|
+
text: `ā **Authentication Diagnostics Failed**
|
|
1714
|
+
|
|
1715
|
+
Error: ${error instanceof Error ? error.message : String(error)}
|
|
1716
|
+
|
|
1717
|
+
This could indicate:
|
|
1718
|
+
- Network connectivity issues
|
|
1719
|
+
- Invalid ServiceNow credentials
|
|
1720
|
+
- Instance URL problems
|
|
1721
|
+
|
|
1722
|
+
**š§ Immediate Actions:**
|
|
1723
|
+
1. Check your .env file configuration
|
|
1724
|
+
2. Verify SNOW_INSTANCE doesn't have trailing slash
|
|
1725
|
+
3. Confirm OAuth credentials are correct
|
|
1726
|
+
4. Run: snow-flow auth login
|
|
1727
|
+
|
|
1728
|
+
**š Need Help?**
|
|
1729
|
+
Run snow_deployment_debug for basic session info or check the logs for more details.`
|
|
1730
|
+
}
|
|
1731
|
+
]
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1481
1735
|
/**
|
|
1482
1736
|
* Generate Update Set XML for manual import
|
|
1483
1737
|
*/
|
|
@@ -2053,6 +2307,363 @@ Use \`snow_preview_widget\` to see a detailed preview of the widget rendering.`,
|
|
|
2053
2307
|
}
|
|
2054
2308
|
return recommendations.length > 0 ? recommendations.join('\n') : 'Widget structure looks good!';
|
|
2055
2309
|
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Smart Update Set Management with context detection
|
|
2312
|
+
*/
|
|
2313
|
+
async smartUpdateSet(args) {
|
|
2314
|
+
try {
|
|
2315
|
+
// Check authentication
|
|
2316
|
+
const isAuth = await this.oauth.isAuthenticated();
|
|
2317
|
+
if (!isAuth) {
|
|
2318
|
+
return {
|
|
2319
|
+
content: [
|
|
2320
|
+
{
|
|
2321
|
+
type: 'text',
|
|
2322
|
+
text: 'ā Not authenticated with ServiceNow.\n\nPlease run: snow-flow auth login',
|
|
2323
|
+
},
|
|
2324
|
+
],
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
// Get current context (task identifier)
|
|
2328
|
+
const taskContext = args.description || 'Current Task';
|
|
2329
|
+
const contextKey = `task_context_${taskContext.replace(/\s+/g, '_').toLowerCase()}`;
|
|
2330
|
+
// Check if we need a new update set
|
|
2331
|
+
const currentUpdateSet = await this.client.getCurrentUpdateSet();
|
|
2332
|
+
let needNewUpdateSet = true;
|
|
2333
|
+
if (currentUpdateSet.success && currentUpdateSet.data) {
|
|
2334
|
+
// Check if current update set is for the same task
|
|
2335
|
+
if (!args.separate_by_task || currentUpdateSet.data.description?.includes(taskContext)) {
|
|
2336
|
+
needNewUpdateSet = false;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
if (!needNewUpdateSet && currentUpdateSet.data) {
|
|
2340
|
+
return {
|
|
2341
|
+
content: [
|
|
2342
|
+
{
|
|
2343
|
+
type: 'text',
|
|
2344
|
+
text: `ā
Using existing Update Set for this task:\n\nš¦ **Current Update Set:**\n- Name: ${currentUpdateSet.data.name}\n- ID: ${currentUpdateSet.data.sys_id}\n- Description: ${currentUpdateSet.data.description}\n\nš” Same task context detected - no new Update Set needed.`
|
|
2345
|
+
}
|
|
2346
|
+
]
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
// Close previous update set if requested
|
|
2350
|
+
if (args.close_previous && currentUpdateSet.data) {
|
|
2351
|
+
await this.client.completeUpdateSet(currentUpdateSet.data.sys_id);
|
|
2352
|
+
this.logger.info('Closed previous Update Set', { id: currentUpdateSet.data.sys_id });
|
|
2353
|
+
}
|
|
2354
|
+
// Create new update set
|
|
2355
|
+
const updateSetNumber = Date.now().toString().slice(-6);
|
|
2356
|
+
const updateSetName = `${args.name_prefix}-${updateSetNumber}: ${taskContext}`;
|
|
2357
|
+
const result = await this.client.createUpdateSet({
|
|
2358
|
+
name: updateSetName,
|
|
2359
|
+
description: `Auto-created for task: ${taskContext}\n\nContext Detection: ${args.detect_context ? 'Enabled' : 'Disabled'}\nSeparate by Task: ${args.separate_by_task ? 'Yes' : 'No'}`,
|
|
2360
|
+
state: 'in_progress'
|
|
2361
|
+
});
|
|
2362
|
+
if (!result.success) {
|
|
2363
|
+
throw new Error(`Failed to create Update Set: ${result.error}`);
|
|
2364
|
+
}
|
|
2365
|
+
// Set as current update set
|
|
2366
|
+
await this.client.setCurrentUpdateSet(result.data.sys_id);
|
|
2367
|
+
const credentials = await this.oauth.loadCredentials();
|
|
2368
|
+
const updateSetUrl = `https://${credentials?.instance}/sys_update_set.do?sys_id=${result.data.sys_id}`;
|
|
2369
|
+
return {
|
|
2370
|
+
content: [
|
|
2371
|
+
{
|
|
2372
|
+
type: 'text',
|
|
2373
|
+
text: `ā
Smart Update Set created successfully!\n\nš¦ **New Update Set:**\n- Name: ${updateSetName}\n- ID: ${result.data.sys_id}\n- Task Context: ${taskContext}\n\nš§ **Smart Features:**\n- Context Detection: ${args.detect_context ? 'ā
Enabled' : 'ā Disabled'}\n- Separate by Task: ${args.separate_by_task ? 'ā
Yes' : 'ā No'}\n- Auto-close Previous: ${args.close_previous ? 'ā
Yes' : 'ā No'}\n${currentUpdateSet.data && args.close_previous ? `- Previous Set Closed: ā
${currentUpdateSet.data.name}` : ''}\n\nš **Direct Link:**\n${updateSetUrl}\n\nš” **Next Steps:**\n1. All new changes will be tracked in this Update Set\n2. Deploy your artifacts - they'll be automatically included\n3. Complete the Update Set when your task is done\n4. Next task will get its own Update Set automatically`
|
|
2374
|
+
}
|
|
2375
|
+
]
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
catch (error) {
|
|
2379
|
+
throw new Error(`Smart Update Set creation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Validate Flow Definition before deployment
|
|
2384
|
+
*/
|
|
2385
|
+
async validateFlowDefinition(args) {
|
|
2386
|
+
try {
|
|
2387
|
+
const flowType = args.flow_type || 'flow';
|
|
2388
|
+
let definition;
|
|
2389
|
+
try {
|
|
2390
|
+
definition = typeof args.definition === 'string' ? JSON.parse(args.definition) : args.definition;
|
|
2391
|
+
}
|
|
2392
|
+
catch (error) {
|
|
2393
|
+
return {
|
|
2394
|
+
content: [
|
|
2395
|
+
{
|
|
2396
|
+
type: 'text',
|
|
2397
|
+
text: `ā Invalid JSON format in flow definition:\n\n${error instanceof Error ? error.message : String(error)}\n\nš” Please check your JSON syntax.`
|
|
2398
|
+
}
|
|
2399
|
+
]
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
const issues = [];
|
|
2403
|
+
const warnings = [];
|
|
2404
|
+
const info = [];
|
|
2405
|
+
// Basic structure validation
|
|
2406
|
+
if (!definition.activities || !Array.isArray(definition.activities)) {
|
|
2407
|
+
issues.push('ā Missing or invalid "activities" array');
|
|
2408
|
+
}
|
|
2409
|
+
// Flow type specific validation
|
|
2410
|
+
switch (flowType) {
|
|
2411
|
+
case 'flow':
|
|
2412
|
+
if (!definition.trigger && !args.trigger_type) {
|
|
2413
|
+
issues.push('ā Flow must have a trigger defined');
|
|
2414
|
+
}
|
|
2415
|
+
break;
|
|
2416
|
+
case 'subflow':
|
|
2417
|
+
if (!definition.inputs) {
|
|
2418
|
+
warnings.push('ā ļø Subflow has no inputs defined');
|
|
2419
|
+
}
|
|
2420
|
+
if (!definition.outputs) {
|
|
2421
|
+
warnings.push('ā ļø Subflow has no outputs defined');
|
|
2422
|
+
}
|
|
2423
|
+
break;
|
|
2424
|
+
case 'action':
|
|
2425
|
+
if (!definition.action_type) {
|
|
2426
|
+
warnings.push('ā ļø Action type not specified');
|
|
2427
|
+
}
|
|
2428
|
+
break;
|
|
2429
|
+
}
|
|
2430
|
+
// Activity validation
|
|
2431
|
+
if (definition.activities) {
|
|
2432
|
+
definition.activities.forEach((activity, index) => {
|
|
2433
|
+
if (!activity.name) {
|
|
2434
|
+
issues.push(`ā Activity ${index + 1} missing required "name" field`);
|
|
2435
|
+
}
|
|
2436
|
+
if (!activity.type) {
|
|
2437
|
+
issues.push(`ā Activity ${index + 1} missing required "type" field`);
|
|
2438
|
+
}
|
|
2439
|
+
// Check for common activity types
|
|
2440
|
+
const validTypes = ['rest', 'script', 'approval', 'condition', 'subflow', 'notification', 'wait', 'lookup'];
|
|
2441
|
+
if (activity.type && !validTypes.includes(activity.type)) {
|
|
2442
|
+
warnings.push(`ā ļø Activity "${activity.name}" uses non-standard type: ${activity.type}`);
|
|
2443
|
+
}
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
// Dependency checking
|
|
2447
|
+
if (args.check_dependencies) {
|
|
2448
|
+
const dependencies = this.extractDependencies(definition);
|
|
2449
|
+
if (dependencies.length > 0) {
|
|
2450
|
+
info.push(`š¦ Dependencies found: ${dependencies.join(', ')}`);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
// Generate preview if requested
|
|
2454
|
+
let preview = '';
|
|
2455
|
+
if (args.show_preview) {
|
|
2456
|
+
preview = this.generateFlowPreview(definition, flowType);
|
|
2457
|
+
}
|
|
2458
|
+
const hasErrors = issues.length > 0;
|
|
2459
|
+
const status = hasErrors ? 'ā VALIDATION FAILED' : 'ā
VALIDATION PASSED';
|
|
2460
|
+
return {
|
|
2461
|
+
content: [
|
|
2462
|
+
{
|
|
2463
|
+
type: 'text',
|
|
2464
|
+
text: `${status}\n\nš **Flow Validation Report:**\n- Flow Type: ${flowType}\n- Activities: ${definition.activities?.length || 0}\n- Status: ${hasErrors ? 'Failed' : 'Passed'}\n\n${issues.length > 0 ? `šØ **Critical Issues:**\n${issues.join('\n')}\n\n` : ''}${warnings.length > 0 ? `ā ļø **Warnings:**\n${warnings.join('\n')}\n\n` : ''}${info.length > 0 ? `ā¹ļø **Information:**\n${info.join('\n')}\n\n` : ''}${preview ? `\nš **Flow Preview:**\n${preview}\n` : ''}${!hasErrors && args.test_mode ? '\nš§Ŗ **Test Mode:** Flow structure is valid for testing\n' : ''}${!hasErrors ? '\nā
Flow definition is valid and ready for deployment!' : '\nā Please fix the issues before deploying.'}`
|
|
2465
|
+
}
|
|
2466
|
+
]
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
catch (error) {
|
|
2470
|
+
throw new Error(`Flow validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
/**
|
|
2474
|
+
* Create Solution Package grouping multiple artifacts
|
|
2475
|
+
*/
|
|
2476
|
+
async createSolutionPackage(args) {
|
|
2477
|
+
try {
|
|
2478
|
+
// Check authentication
|
|
2479
|
+
const isAuth = await this.oauth.isAuthenticated();
|
|
2480
|
+
if (!isAuth) {
|
|
2481
|
+
return {
|
|
2482
|
+
content: [
|
|
2483
|
+
{
|
|
2484
|
+
type: 'text',
|
|
2485
|
+
text: 'ā Not authenticated with ServiceNow.\n\nPlease run: snow-flow auth login',
|
|
2486
|
+
},
|
|
2487
|
+
],
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
// Create new update set for the solution
|
|
2491
|
+
if (args.new_update_set) {
|
|
2492
|
+
const updateSetResult = await this.smartUpdateSet({
|
|
2493
|
+
detect_context: true,
|
|
2494
|
+
name_prefix: 'SOLUTION',
|
|
2495
|
+
description: args.description || `Solution Package: ${args.name}`,
|
|
2496
|
+
separate_by_task: false,
|
|
2497
|
+
close_previous: true
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
const deployedArtifacts = [];
|
|
2501
|
+
const failedArtifacts = [];
|
|
2502
|
+
// Deploy each artifact in the package
|
|
2503
|
+
for (const artifact of args.artifacts) {
|
|
2504
|
+
try {
|
|
2505
|
+
let result;
|
|
2506
|
+
switch (artifact.type) {
|
|
2507
|
+
case 'flow':
|
|
2508
|
+
result = await this.deployFlow(artifact.create);
|
|
2509
|
+
break;
|
|
2510
|
+
case 'widget':
|
|
2511
|
+
result = await this.deployWidget(artifact.create);
|
|
2512
|
+
break;
|
|
2513
|
+
case 'script_include':
|
|
2514
|
+
result = await this.deployScriptInclude(artifact.create);
|
|
2515
|
+
break;
|
|
2516
|
+
case 'business_rule':
|
|
2517
|
+
result = await this.deployBusinessRule(artifact.create);
|
|
2518
|
+
break;
|
|
2519
|
+
case 'table':
|
|
2520
|
+
result = await this.deployTable(artifact.create);
|
|
2521
|
+
break;
|
|
2522
|
+
default:
|
|
2523
|
+
throw new Error(`Unknown artifact type: ${artifact.type}`);
|
|
2524
|
+
}
|
|
2525
|
+
deployedArtifacts.push({
|
|
2526
|
+
type: artifact.type,
|
|
2527
|
+
name: artifact.create.name,
|
|
2528
|
+
result: 'Success'
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
catch (error) {
|
|
2532
|
+
failedArtifacts.push({
|
|
2533
|
+
type: artifact.type,
|
|
2534
|
+
name: artifact.create.name,
|
|
2535
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
const successCount = deployedArtifacts.length;
|
|
2540
|
+
const failureCount = failedArtifacts.length;
|
|
2541
|
+
const totalCount = successCount + failureCount;
|
|
2542
|
+
return {
|
|
2543
|
+
content: [
|
|
2544
|
+
{
|
|
2545
|
+
type: 'text',
|
|
2546
|
+
text: `š¦ **Solution Package Deployment Complete!**\n\nšÆ **Package Details:**\n- Name: ${args.name}\n- Description: ${args.description || 'N/A'}\n- Total Artifacts: ${totalCount}\n- Successful: ${successCount} ā
\n- Failed: ${failureCount} ${failureCount > 0 ? 'ā' : ''}\n\n${deployedArtifacts.length > 0 ? `ā
**Successfully Deployed:**\n${deployedArtifacts.map((a, i) => `${i + 1}. ${a.type}: ${a.name}`).join('\n')}\n` : ''}${failedArtifacts.length > 0 ? `\nā **Failed Deployments:**\n${failedArtifacts.map((a, i) => `${i + 1}. ${a.type}: ${a.name}\n Error: ${a.error}`).join('\n')}\n` : ''}\nš” **Solution Benefits:**\n- All artifacts grouped in one Update Set\n- Dependencies automatically resolved\n- Consistent deployment across artifacts\n- Easy rollback if needed\n\n${successCount === totalCount ? 'š All artifacts deployed successfully!' : 'ā ļø Some artifacts failed. Please review the errors above.'}`
|
|
2547
|
+
}
|
|
2548
|
+
]
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
catch (error) {
|
|
2552
|
+
throw new Error(`Solution package creation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Interactive Flow Creation Wizard
|
|
2557
|
+
*/
|
|
2558
|
+
async flowWizard(args) {
|
|
2559
|
+
try {
|
|
2560
|
+
const flowType = args.flow_type || 'flow';
|
|
2561
|
+
const steps = [];
|
|
2562
|
+
// Step 1: Basic Information
|
|
2563
|
+
steps.push({
|
|
2564
|
+
step: 1,
|
|
2565
|
+
name: 'Basic Information',
|
|
2566
|
+
status: 'ā
',
|
|
2567
|
+
details: `Name: ${args.name}\nType: ${flowType}\nDescription: Configure your flow step by step`
|
|
2568
|
+
});
|
|
2569
|
+
// Step 2: Trigger Configuration (for flows only)
|
|
2570
|
+
if (flowType === 'flow') {
|
|
2571
|
+
steps.push({
|
|
2572
|
+
step: 2,
|
|
2573
|
+
name: 'Trigger Configuration',
|
|
2574
|
+
status: 'š',
|
|
2575
|
+
details: 'Choose trigger type: record_created, record_updated, scheduled, or manual'
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
// Step 3: Activities
|
|
2579
|
+
steps.push({
|
|
2580
|
+
step: 3,
|
|
2581
|
+
name: 'Add Activities',
|
|
2582
|
+
status: 'š',
|
|
2583
|
+
details: 'Add activities: scripts, approvals, notifications, conditions'
|
|
2584
|
+
});
|
|
2585
|
+
// Step 4: Variables and Data
|
|
2586
|
+
steps.push({
|
|
2587
|
+
step: 4,
|
|
2588
|
+
name: 'Variables & Data',
|
|
2589
|
+
status: 'š',
|
|
2590
|
+
details: 'Define flow variables and data transformations'
|
|
2591
|
+
});
|
|
2592
|
+
// Step 5: Error Handling
|
|
2593
|
+
steps.push({
|
|
2594
|
+
step: 5,
|
|
2595
|
+
name: 'Error Handling',
|
|
2596
|
+
status: 'š',
|
|
2597
|
+
details: 'Configure error handlers and retry logic'
|
|
2598
|
+
});
|
|
2599
|
+
// Step 6: Testing
|
|
2600
|
+
steps.push({
|
|
2601
|
+
step: 6,
|
|
2602
|
+
name: 'Test Flow',
|
|
2603
|
+
status: 'š',
|
|
2604
|
+
details: 'Test with sample data before deployment'
|
|
2605
|
+
});
|
|
2606
|
+
// Generate wizard interface
|
|
2607
|
+
const wizardText = `š§āāļø **Flow Creation Wizard**\n\nš **Flow Details:**\n- Name: ${args.name}\n- Type: ${flowType}\n- Interactive: ${args.interactive ? 'ā
' : 'ā'}\n- Preview Each Step: ${args.preview_each_step ? 'ā
' : 'ā'}\n- Test As You Build: ${args.test_as_you_build ? 'ā
' : 'ā'}\n\nš **Wizard Steps:**\n${steps.map(s => `${s.step}. ${s.status} ${s.name}\n ${s.details}`).join('\n\n')}\n\nš” **Interactive Features:**\n- ā
Step-by-step guidance\n- ā
Preview after each step\n- ā
Validation at each stage\n- ā
Test before deployment\n- ā
Rollback capability\n\nšÆ **Next Actions:**\n1. Use snow_deploy_flow with your configuration\n2. Or continue building with individual artifact tools\n3. Test with snow_validate_flow_definition\n\nā” **Quick Start Example:**\n\`\`\`json\n{\n "name": "${args.name}",\n "flow_type": "${flowType}",\n "trigger_type": "record_created",\n "table": "incident",\n "flow_definition": {\n "activities": [\n {\n "name": "Check Priority",\n "type": "condition",\n "condition": "current.priority == 1"\n },\n {\n "name": "Send Alert",\n "type": "notification",\n "recipients": "incident.assigned_to"\n }\n ]\n }\n}\n\`\`\``;
|
|
2608
|
+
return {
|
|
2609
|
+
content: [
|
|
2610
|
+
{
|
|
2611
|
+
type: 'text',
|
|
2612
|
+
text: wizardText
|
|
2613
|
+
}
|
|
2614
|
+
]
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
catch (error) {
|
|
2618
|
+
throw new Error(`Flow wizard failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Extract dependencies from flow definition
|
|
2623
|
+
*/
|
|
2624
|
+
extractDependencies(definition) {
|
|
2625
|
+
const dependencies = new Set();
|
|
2626
|
+
if (definition.activities) {
|
|
2627
|
+
definition.activities.forEach((activity) => {
|
|
2628
|
+
if (activity.type === 'rest' && activity.rest_message) {
|
|
2629
|
+
dependencies.add(`REST Message: ${activity.rest_message}`);
|
|
2630
|
+
}
|
|
2631
|
+
if (activity.type === 'script' && activity.script_include) {
|
|
2632
|
+
dependencies.add(`Script Include: ${activity.script_include}`);
|
|
2633
|
+
}
|
|
2634
|
+
if (activity.type === 'subflow' && activity.subflow_name) {
|
|
2635
|
+
dependencies.add(`Subflow: ${activity.subflow_name}`);
|
|
2636
|
+
}
|
|
2637
|
+
if (activity.artifact_reference) {
|
|
2638
|
+
dependencies.add(`${activity.artifact_reference.type}: ${activity.artifact_reference.name}`);
|
|
2639
|
+
}
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
return Array.from(dependencies);
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Generate visual preview of flow
|
|
2646
|
+
*/
|
|
2647
|
+
generateFlowPreview(definition, flowType) {
|
|
2648
|
+
let preview = `\n${flowType.toUpperCase()} STRUCTURE:\n`;
|
|
2649
|
+
preview += 'ā'.repeat(40) + '\n';
|
|
2650
|
+
if (flowType === 'flow' && definition.trigger) {
|
|
2651
|
+
preview += `\n[TRIGGER: ${definition.trigger.type || 'Unknown'}]\n ā\n`;
|
|
2652
|
+
}
|
|
2653
|
+
if (definition.activities) {
|
|
2654
|
+
definition.activities.forEach((activity, index) => {
|
|
2655
|
+
const isLast = index === definition.activities.length - 1;
|
|
2656
|
+
preview += `[${activity.type?.toUpperCase() || 'UNKNOWN'}: ${activity.name || `Activity ${index + 1}`}]\n`;
|
|
2657
|
+
if (!isLast) {
|
|
2658
|
+
preview += ' ā\n';
|
|
2659
|
+
}
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
if (flowType !== 'flow' && definition.outputs) {
|
|
2663
|
+
preview += `\n[OUTPUTS: ${definition.outputs.length} defined]\n`;
|
|
2664
|
+
}
|
|
2665
|
+
return preview;
|
|
2666
|
+
}
|
|
2056
2667
|
async start() {
|
|
2057
2668
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
2058
2669
|
await this.server.connect(transport);
|