snow-flow 1.3.17 → 1.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +56 -68
- package/README.md +28 -25
- package/dist/agents/index.js +1 -4
- package/dist/agents/queen-agent.js +3 -1
- package/dist/cli.js +93 -33
- package/dist/mcp/servicenow-deployment-mcp-refactored.js +3 -0
- package/dist/mcp/servicenow-deployment-mcp.js +135 -0
- package/dist/mcp/servicenow-flow-composer-mcp.js +208 -40
- package/dist/mcp/servicenow-platform-development-mcp.js +27 -2
- package/dist/mcp/servicenow-update-set-mcp-refactored.js +4 -0
- package/dist/mcp/servicenow-update-set-mcp.js +4 -0
- package/dist/mcp/servicenow-xml-flow-mcp.js +120 -7
- package/dist/types/index.js +0 -1
- package/dist/utils/mcp-server-manager.js +23 -4
- package/dist/utils/servicenow-client.js +17 -1
- package/dist/utils/snow-oauth.js +12 -5
- package/dist/utils/unified-auth-store.js +193 -0
- package/dist/utils/xml-first-flow-generator.js +23 -32
- package/package.json +1 -1
|
@@ -5231,10 +5231,145 @@ Use individual deployment tools like \`snow_deploy_${args.type}\` with manual co
|
|
|
5231
5231
|
return await this.deployFlow(scopedConfig);
|
|
5232
5232
|
case 'application':
|
|
5233
5233
|
return await this.deployApplication(scopedConfig);
|
|
5234
|
+
case 'xml_update_set':
|
|
5235
|
+
return await this.deployXMLUpdateSet(scopedConfig);
|
|
5234
5236
|
default:
|
|
5235
5237
|
throw new Error(`Unsupported artifact type for unified deployment: ${type}`);
|
|
5236
5238
|
}
|
|
5237
5239
|
}
|
|
5240
|
+
/**
|
|
5241
|
+
* Deploy XML Update Set to ServiceNow
|
|
5242
|
+
*/
|
|
5243
|
+
async deployXMLUpdateSet(config) {
|
|
5244
|
+
const { xml_file_path, auto_preview = true, auto_commit = true } = config;
|
|
5245
|
+
if (!xml_file_path) {
|
|
5246
|
+
throw new Error('XML file path is required for xml_update_set deployment');
|
|
5247
|
+
}
|
|
5248
|
+
this.logger.info('🚀 Deploying XML Update Set', {
|
|
5249
|
+
file: xml_file_path,
|
|
5250
|
+
auto_preview,
|
|
5251
|
+
auto_commit
|
|
5252
|
+
});
|
|
5253
|
+
try {
|
|
5254
|
+
// Read XML file
|
|
5255
|
+
const fs = require('fs').promises;
|
|
5256
|
+
const xmlContent = await fs.readFile(xml_file_path, 'utf-8');
|
|
5257
|
+
// Import XML as remote update set
|
|
5258
|
+
const importResponse = await this.client.makeRequest({
|
|
5259
|
+
method: 'POST',
|
|
5260
|
+
url: '/api/now/table/sys_remote_update_set',
|
|
5261
|
+
headers: {
|
|
5262
|
+
'Content-Type': 'application/xml',
|
|
5263
|
+
'Accept': 'application/json'
|
|
5264
|
+
},
|
|
5265
|
+
data: xmlContent
|
|
5266
|
+
});
|
|
5267
|
+
if (!importResponse.result || !importResponse.result.sys_id) {
|
|
5268
|
+
throw new Error('Failed to import XML update set');
|
|
5269
|
+
}
|
|
5270
|
+
const remoteUpdateSetId = importResponse.result.sys_id;
|
|
5271
|
+
this.logger.info('✅ XML imported successfully', { sys_id: remoteUpdateSetId });
|
|
5272
|
+
// Load the update set
|
|
5273
|
+
await this.client.makeRequest({
|
|
5274
|
+
method: 'PUT',
|
|
5275
|
+
url: `/api/now/table/sys_remote_update_set/${remoteUpdateSetId}`,
|
|
5276
|
+
data: {
|
|
5277
|
+
state: 'loaded'
|
|
5278
|
+
}
|
|
5279
|
+
});
|
|
5280
|
+
// Find the loaded update set
|
|
5281
|
+
const loadedResponse = await this.client.makeRequest({
|
|
5282
|
+
method: 'GET',
|
|
5283
|
+
url: '/api/now/table/sys_update_set',
|
|
5284
|
+
params: {
|
|
5285
|
+
sysparm_query: `remote_sys_id=${remoteUpdateSetId}`,
|
|
5286
|
+
sysparm_limit: 1
|
|
5287
|
+
}
|
|
5288
|
+
});
|
|
5289
|
+
if (!loadedResponse.result || loadedResponse.result.length === 0) {
|
|
5290
|
+
throw new Error('Failed to find loaded update set');
|
|
5291
|
+
}
|
|
5292
|
+
const updateSetId = loadedResponse.result[0].sys_id;
|
|
5293
|
+
const updateSetName = loadedResponse.result[0].name;
|
|
5294
|
+
// Preview if requested
|
|
5295
|
+
if (auto_preview) {
|
|
5296
|
+
const previewResponse = await this.client.makeRequest({
|
|
5297
|
+
method: 'POST',
|
|
5298
|
+
url: `/api/now/table/sys_update_set/${updateSetId}/preview`
|
|
5299
|
+
});
|
|
5300
|
+
// Check preview results
|
|
5301
|
+
const previewProblems = await this.client.makeRequest({
|
|
5302
|
+
method: 'GET',
|
|
5303
|
+
url: '/api/now/table/sys_update_preview_problem',
|
|
5304
|
+
params: {
|
|
5305
|
+
sysparm_query: `update_set=${updateSetId}`,
|
|
5306
|
+
sysparm_limit: 100
|
|
5307
|
+
}
|
|
5308
|
+
});
|
|
5309
|
+
if (previewProblems.result && previewProblems.result.length > 0) {
|
|
5310
|
+
const problems = previewProblems.result.map((p) => `- ${p.type}: ${p.description}`).join('\n');
|
|
5311
|
+
if (auto_commit) {
|
|
5312
|
+
this.logger.warn('Preview found problems, skipping auto-commit', { problems });
|
|
5313
|
+
}
|
|
5314
|
+
return {
|
|
5315
|
+
success: true,
|
|
5316
|
+
message: 'XML imported and previewed with problems',
|
|
5317
|
+
update_set_id: updateSetId,
|
|
5318
|
+
update_set_name: updateSetName,
|
|
5319
|
+
preview_status: 'problems_found',
|
|
5320
|
+
problems: previewProblems.result,
|
|
5321
|
+
next_steps: [
|
|
5322
|
+
'1. Review preview problems in ServiceNow',
|
|
5323
|
+
'2. Resolve any issues',
|
|
5324
|
+
'3. Commit manually when ready'
|
|
5325
|
+
]
|
|
5326
|
+
};
|
|
5327
|
+
}
|
|
5328
|
+
// Commit if clean and requested
|
|
5329
|
+
if (auto_commit) {
|
|
5330
|
+
await this.client.makeRequest({
|
|
5331
|
+
method: 'POST',
|
|
5332
|
+
url: `/api/now/table/sys_update_set/${updateSetId}/commit`
|
|
5333
|
+
});
|
|
5334
|
+
return {
|
|
5335
|
+
success: true,
|
|
5336
|
+
message: '✅ XML Update Set imported, previewed, and committed successfully!',
|
|
5337
|
+
update_set_id: updateSetId,
|
|
5338
|
+
update_set_name: updateSetName,
|
|
5339
|
+
status: 'committed',
|
|
5340
|
+
flow_location: 'Flow Designer > Designer',
|
|
5341
|
+
next_steps: [
|
|
5342
|
+
'1. Navigate to Flow Designer',
|
|
5343
|
+
'2. Your flow should be visible in the list',
|
|
5344
|
+
'3. Open the flow to verify all components'
|
|
5345
|
+
]
|
|
5346
|
+
};
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
// Return success without preview/commit
|
|
5350
|
+
return {
|
|
5351
|
+
success: true,
|
|
5352
|
+
message: 'XML Update Set imported successfully',
|
|
5353
|
+
update_set_id: updateSetId,
|
|
5354
|
+
update_set_name: updateSetName,
|
|
5355
|
+
status: 'imported',
|
|
5356
|
+
next_steps: [
|
|
5357
|
+
'1. Navigate to System Update Sets > Local Update Sets',
|
|
5358
|
+
'2. Find your update set: ' + updateSetName,
|
|
5359
|
+
'3. Click Preview Update Set',
|
|
5360
|
+
'4. Review and commit when ready'
|
|
5361
|
+
]
|
|
5362
|
+
};
|
|
5363
|
+
}
|
|
5364
|
+
catch (error) {
|
|
5365
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
5366
|
+
this.logger.error('XML deployment failed', { error: errorMsg, file: xml_file_path });
|
|
5367
|
+
if (errorMsg.includes('ENOENT') || errorMsg.includes('no such file')) {
|
|
5368
|
+
throw new Error(`XML file not found: ${xml_file_path}`);
|
|
5369
|
+
}
|
|
5370
|
+
throw new Error(`XML deployment failed: ${errorMsg}`);
|
|
5371
|
+
}
|
|
5372
|
+
}
|
|
5238
5373
|
/**
|
|
5239
5374
|
* Check if error is permission-related
|
|
5240
5375
|
*/
|
|
@@ -4,6 +4,39 @@
|
|
|
4
4
|
* ServiceNow Flow Composer MCP Server
|
|
5
5
|
* Natural language flow creation with multi-artifact orchestration
|
|
6
6
|
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
7
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
41
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
9
42
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
@@ -11,7 +44,6 @@ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
|
11
44
|
const servicenow_client_js_1 = require("../utils/servicenow-client.js");
|
|
12
45
|
const snow_oauth_js_1 = require("../utils/snow-oauth.js");
|
|
13
46
|
const logger_js_1 = require("../utils/logger.js");
|
|
14
|
-
const flow_structure_builder_js_1 = require("../utils/flow-structure-builder.js");
|
|
15
47
|
class ServiceNowFlowComposerMCP {
|
|
16
48
|
constructor() {
|
|
17
49
|
this.server = new index_js_1.Server({
|
|
@@ -32,7 +64,7 @@ class ServiceNowFlowComposerMCP {
|
|
|
32
64
|
tools: [
|
|
33
65
|
{
|
|
34
66
|
name: 'snow_create_flow',
|
|
35
|
-
description: '
|
|
67
|
+
description: '🚀 PRIMARY FLOW TOOL - Create production-ready Flow Designer flows with XML-first approach and automatic deployment to ServiceNow. ZERO MANUAL STEPS!',
|
|
36
68
|
inputSchema: {
|
|
37
69
|
type: 'object',
|
|
38
70
|
properties: {
|
|
@@ -297,30 +329,43 @@ class ServiceNowFlowComposerMCP {
|
|
|
297
329
|
// 🧠 STEP 4: Generate complete flow definition
|
|
298
330
|
const flowDefinition = await this.generateFlowDefinition(parsedIntent, templateMatch, artifacts);
|
|
299
331
|
console.log('🧠 Generated flow definition:', JSON.stringify(flowDefinition, null, 2));
|
|
300
|
-
// 🧠 STEP 5: Deploy
|
|
332
|
+
// 🧠 STEP 5: Deploy using XML-first approach for maximum reliability
|
|
301
333
|
let deploymentResult = null;
|
|
302
334
|
if (args.deploy_immediately !== false) {
|
|
303
|
-
console.log('🚀 DEPLOYING
|
|
304
|
-
// Use the conversion utility to ensure proper format
|
|
305
|
-
const enhancedFlowDefinition = (0, flow_structure_builder_js_1.convertToFlowDefinition)({
|
|
306
|
-
name: parsedIntent.flowName,
|
|
307
|
-
description: parsedIntent.description,
|
|
308
|
-
table: parsedIntent.table,
|
|
309
|
-
trigger: parsedIntent.trigger,
|
|
310
|
-
activities: flowDefinition.activities || [],
|
|
311
|
-
variables: flowDefinition.variables || [],
|
|
312
|
-
connections: flowDefinition.connections || [],
|
|
313
|
-
error_handling: flowDefinition.error_handling || []
|
|
314
|
-
});
|
|
315
|
-
// Try enhanced method first, fallback to original if needed
|
|
335
|
+
console.log('🚀 DEPLOYING flow using XML-first approach...');
|
|
316
336
|
try {
|
|
317
|
-
|
|
318
|
-
|
|
337
|
+
// Import the XML flow generator
|
|
338
|
+
const { generateProductionFlowXML } = await Promise.resolve().then(() => __importStar(require('../utils/xml-first-flow-generator.js')));
|
|
339
|
+
// Convert to XML flow definition format
|
|
340
|
+
const xmlFlowDef = {
|
|
341
|
+
name: parsedIntent.flowName,
|
|
342
|
+
description: parsedIntent.description,
|
|
343
|
+
table: parsedIntent.table,
|
|
344
|
+
trigger_type: this.mapTriggerTypeToXML(parsedIntent.trigger.type),
|
|
345
|
+
trigger_condition: parsedIntent.trigger.condition || '',
|
|
346
|
+
activities: this.convertActivitiesToXML(flowDefinition.activities || []),
|
|
347
|
+
run_as: 'user',
|
|
348
|
+
accessible_from: 'package_private'
|
|
349
|
+
};
|
|
350
|
+
// Generate production-ready XML
|
|
351
|
+
const xmlResult = generateProductionFlowXML(xmlFlowDef);
|
|
352
|
+
console.log('✅ XML generated:', xmlResult.filePath);
|
|
353
|
+
// Auto-deploy XML to ServiceNow
|
|
354
|
+
await this.deployXMLToServiceNow(xmlResult.filePath);
|
|
355
|
+
deploymentResult = {
|
|
356
|
+
success: true,
|
|
357
|
+
method: 'xml_first',
|
|
358
|
+
xml_file: xmlResult.filePath,
|
|
359
|
+
message: '✅ Flow deployed using XML-first approach!'
|
|
360
|
+
};
|
|
319
361
|
}
|
|
320
|
-
catch (
|
|
321
|
-
console.warn('⚠️
|
|
322
|
-
deploymentResult =
|
|
323
|
-
|
|
362
|
+
catch (xmlError) {
|
|
363
|
+
console.warn('⚠️ XML deployment failed, providing manual instructions:', xmlError);
|
|
364
|
+
deploymentResult = {
|
|
365
|
+
success: false,
|
|
366
|
+
error: xmlError instanceof Error ? xmlError.message : String(xmlError),
|
|
367
|
+
fallback_instructions: 'Use snow-flow deploy-xml command for manual deployment'
|
|
368
|
+
};
|
|
324
369
|
}
|
|
325
370
|
}
|
|
326
371
|
const credentials = await this.oauth.loadCredentials();
|
|
@@ -329,9 +374,9 @@ class ServiceNowFlowComposerMCP {
|
|
|
329
374
|
content: [
|
|
330
375
|
{
|
|
331
376
|
type: 'text',
|
|
332
|
-
text: `🎯
|
|
377
|
+
text: `🎯 FLOW CREATED WITH XML-FIRST APPROACH!
|
|
333
378
|
|
|
334
|
-
${args.deploy_immediately !== false ? `🚀 **
|
|
379
|
+
${args.deploy_immediately !== false ? `🚀 **FULLY AUTOMATED DEPLOYMENT** - XML generated & deployed to ServiceNow!` : `📋 **PLANNING MODE** - Flow structure generated`}
|
|
335
380
|
|
|
336
381
|
🧠 **Intelligent Analysis:**
|
|
337
382
|
- **Flow Name**: ${parsedIntent.flowName}
|
|
@@ -347,26 +392,27 @@ ${args.deploy_immediately !== false ? `🚀 **LIVE DEPLOYMENT** - Real flow crea
|
|
|
347
392
|
- **Error Handling**: ${flowDefinition.error_handling?.length || 0} safety measures
|
|
348
393
|
- **Artifacts Used**: ${artifacts.existing.length} found, ${artifacts.created.length} created
|
|
349
394
|
|
|
350
|
-
🚀 **Deployment
|
|
351
|
-
${deploymentResult ? (deploymentResult.success ?
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
- **
|
|
355
|
-
- **Status**:
|
|
356
|
-
-
|
|
395
|
+
🚀 **XML-First Deployment:**
|
|
396
|
+
${deploymentResult ? (deploymentResult.success ?
|
|
397
|
+
`✅ Successfully deployed using XML-first approach!
|
|
398
|
+
- **Method**: Production-ready Update Set XML
|
|
399
|
+
- **XML File**: ${deploymentResult.xml_file}
|
|
400
|
+
- **Status**: Imported → Previewed → Committed ✅` :
|
|
401
|
+
`❌ Auto-deployment failed: ${deploymentResult.error}
|
|
402
|
+
- **Fallback**: ${deploymentResult.fallback_instructions}`) : '⏳ Ready for deployment'}
|
|
357
403
|
|
|
358
404
|
🔗 **ServiceNow Access:**
|
|
359
405
|
- Flow Designer: ${flowUrl}
|
|
360
406
|
- Flow Designer Home: https://${credentials?.instance}/flow-designer
|
|
361
407
|
|
|
362
|
-
🧠 **
|
|
363
|
-
-
|
|
364
|
-
-
|
|
365
|
-
-
|
|
366
|
-
-
|
|
367
|
-
-
|
|
408
|
+
🧠 **NEW Features (v1.3.17):**
|
|
409
|
+
- XML-first approach for maximum reliability ✅
|
|
410
|
+
- Automatic Update Set deployment ✅
|
|
411
|
+
- Zero manual steps required ✅
|
|
412
|
+
- Production-ready Flow Designer format ✅
|
|
413
|
+
- Intelligent error handling & fallbacks ✅
|
|
368
414
|
|
|
369
|
-
Your flow is now
|
|
415
|
+
Your flow is now live in ServiceNow Flow Designer! 🎉`,
|
|
370
416
|
},
|
|
371
417
|
],
|
|
372
418
|
};
|
|
@@ -1679,7 +1725,7 @@ ${flowInstruction.recommendations?.map((rec, index) => `${index + 1}. ${rec}`).j
|
|
|
1679
1725
|
|
|
1680
1726
|
🚀 **Next Steps:**
|
|
1681
1727
|
1. Use \`snow_template_matching\` to explore template options
|
|
1682
|
-
2. Run \`snow_create_flow\` to implement the recommended approach
|
|
1728
|
+
2. Run \`snow_create_flow\` with deploy_immediately: true to implement the recommended approach
|
|
1683
1729
|
3. Consider \`snow_scope_optimization\` for deployment strategy
|
|
1684
1730
|
|
|
1685
1731
|
✅ **Analysis Complete!** The instruction has been comprehensively analyzed with intelligent insights.`,
|
|
@@ -1841,7 +1887,7 @@ ${categoryFilteredResults.length === 0 ? `🔍 **No templates found matching you
|
|
|
1841
1887
|
- **Recommended**: ${categoryFilteredResults[0]?.confidence >= 0.8 ? 'Yes - High confidence match' : 'Consider manual review'}
|
|
1842
1888
|
|
|
1843
1889
|
🚀 **Next Steps:**
|
|
1844
|
-
1. Use \`snow_create_flow\` to implement the best matching template
|
|
1890
|
+
1. Use \`snow_create_flow\` with deploy_immediately: true to implement the best matching template
|
|
1845
1891
|
2. Review template customization options
|
|
1846
1892
|
3. Consider \`snow_intelligent_flow_analysis\` for detailed analysis
|
|
1847
1893
|
4. Modify instruction if no suitable templates found
|
|
@@ -1860,6 +1906,128 @@ ${categoryFilteredResults.length === 0 ? `🔍 **No templates found matching you
|
|
|
1860
1906
|
await this.server.connect(transport);
|
|
1861
1907
|
this.logger.info('ServiceNow Flow Composer MCP Server started');
|
|
1862
1908
|
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Map trigger type to XML format
|
|
1911
|
+
*/
|
|
1912
|
+
mapTriggerTypeToXML(triggerType) {
|
|
1913
|
+
const mapping = {
|
|
1914
|
+
'record_created': 'record_created',
|
|
1915
|
+
'record_updated': 'record_updated',
|
|
1916
|
+
'manual': 'manual',
|
|
1917
|
+
'scheduled': 'scheduled',
|
|
1918
|
+
'create': 'record_created',
|
|
1919
|
+
'update': 'record_updated',
|
|
1920
|
+
'on_create': 'record_created',
|
|
1921
|
+
'on_update': 'record_updated'
|
|
1922
|
+
};
|
|
1923
|
+
return mapping[triggerType.toLowerCase()] || 'manual';
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Convert activities to XML format
|
|
1927
|
+
*/
|
|
1928
|
+
convertActivitiesToXML(activities) {
|
|
1929
|
+
return activities.map((activity, index) => ({
|
|
1930
|
+
type: this.mapActivityTypeToXML(activity.type),
|
|
1931
|
+
name: activity.name || `Activity ${index + 1}`,
|
|
1932
|
+
inputs: activity.inputs || {},
|
|
1933
|
+
order: (index + 1) * 100,
|
|
1934
|
+
description: activity.description || activity.name
|
|
1935
|
+
}));
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Map activity type to XML format
|
|
1939
|
+
*/
|
|
1940
|
+
mapActivityTypeToXML(activityType) {
|
|
1941
|
+
const mapping = {
|
|
1942
|
+
'approval': 'approval',
|
|
1943
|
+
'notification': 'notification',
|
|
1944
|
+
'email': 'notification',
|
|
1945
|
+
'script': 'script',
|
|
1946
|
+
'create_record': 'create_record',
|
|
1947
|
+
'update_record': 'update_record',
|
|
1948
|
+
'rest_call': 'rest_step',
|
|
1949
|
+
'condition': 'condition',
|
|
1950
|
+
'subflow': 'assign_subflow'
|
|
1951
|
+
};
|
|
1952
|
+
return mapping[activityType.toLowerCase()] || 'script';
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Deploy XML file to ServiceNow automatically
|
|
1956
|
+
*/
|
|
1957
|
+
async deployXMLToServiceNow(xmlFilePath) {
|
|
1958
|
+
// Check authentication
|
|
1959
|
+
const isAuth = await this.oauth.isAuthenticated();
|
|
1960
|
+
if (!isAuth) {
|
|
1961
|
+
throw new Error('Not authenticated with ServiceNow. Please run: snow-flow auth login');
|
|
1962
|
+
}
|
|
1963
|
+
// Initialize ServiceNow client
|
|
1964
|
+
const ServiceNowClient = (await Promise.resolve().then(() => __importStar(require('../utils/servicenow-client.js')))).ServiceNowClient;
|
|
1965
|
+
const client = new ServiceNowClient();
|
|
1966
|
+
// Read the XML file
|
|
1967
|
+
const fs = require('fs').promises;
|
|
1968
|
+
const xmlContent = await fs.readFile(xmlFilePath, 'utf-8');
|
|
1969
|
+
// Import XML as remote update set
|
|
1970
|
+
const importResponse = await client.makeRequest({
|
|
1971
|
+
method: 'POST',
|
|
1972
|
+
url: '/api/now/table/sys_remote_update_set',
|
|
1973
|
+
headers: {
|
|
1974
|
+
'Content-Type': 'application/xml',
|
|
1975
|
+
'Accept': 'application/json'
|
|
1976
|
+
},
|
|
1977
|
+
data: xmlContent
|
|
1978
|
+
});
|
|
1979
|
+
if (!importResponse.result || !importResponse.result.sys_id) {
|
|
1980
|
+
throw new Error('Failed to import XML update set');
|
|
1981
|
+
}
|
|
1982
|
+
const remoteUpdateSetId = importResponse.result.sys_id;
|
|
1983
|
+
this.logger.info(`✅ XML imported successfully (sys_id: ${remoteUpdateSetId})`);
|
|
1984
|
+
// Load the update set
|
|
1985
|
+
await client.makeRequest({
|
|
1986
|
+
method: 'PUT',
|
|
1987
|
+
url: `/api/now/table/sys_remote_update_set/${remoteUpdateSetId}`,
|
|
1988
|
+
data: {
|
|
1989
|
+
state: 'loaded'
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
// Find the loaded update set
|
|
1993
|
+
const loadedResponse = await client.makeRequest({
|
|
1994
|
+
method: 'GET',
|
|
1995
|
+
url: '/api/now/table/sys_update_set',
|
|
1996
|
+
params: {
|
|
1997
|
+
sysparm_query: `remote_sys_id=${remoteUpdateSetId}`,
|
|
1998
|
+
sysparm_limit: 1
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
if (!loadedResponse.result || loadedResponse.result.length === 0) {
|
|
2002
|
+
throw new Error('Failed to find loaded update set');
|
|
2003
|
+
}
|
|
2004
|
+
const updateSetId = loadedResponse.result[0].sys_id;
|
|
2005
|
+
const updateSetName = loadedResponse.result[0].name;
|
|
2006
|
+
// Preview the update set
|
|
2007
|
+
await client.makeRequest({
|
|
2008
|
+
method: 'POST',
|
|
2009
|
+
url: `/api/now/table/sys_update_set/${updateSetId}/preview`
|
|
2010
|
+
});
|
|
2011
|
+
// Check for preview problems
|
|
2012
|
+
const previewProblems = await client.makeRequest({
|
|
2013
|
+
method: 'GET',
|
|
2014
|
+
url: '/api/now/table/sys_update_preview_problem',
|
|
2015
|
+
params: {
|
|
2016
|
+
sysparm_query: `update_set=${updateSetId}`,
|
|
2017
|
+
sysparm_limit: 100
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
if (previewProblems.result && previewProblems.result.length > 0) {
|
|
2021
|
+
const problemsList = previewProblems.result.map((p) => `- ${p.type}: ${p.description}`).join('\n');
|
|
2022
|
+
throw new Error(`Preview found problems:\n${problemsList}\n\nPlease review and resolve in ServiceNow UI`);
|
|
2023
|
+
}
|
|
2024
|
+
// Commit the update set
|
|
2025
|
+
await client.makeRequest({
|
|
2026
|
+
method: 'POST',
|
|
2027
|
+
url: `/api/now/table/sys_update_set/${updateSetId}/commit`
|
|
2028
|
+
});
|
|
2029
|
+
this.logger.info(`✅ Update set committed successfully: ${updateSetName}`);
|
|
2030
|
+
}
|
|
1863
2031
|
}
|
|
1864
2032
|
// Start the server
|
|
1865
2033
|
const server = new ServiceNowFlowComposerMCP();
|
|
@@ -302,6 +302,24 @@ class ServiceNowPlatformDevelopmentMCP {
|
|
|
302
302
|
async getTableInfo(tableName) {
|
|
303
303
|
try {
|
|
304
304
|
this.logger.debug(`Looking up table info for: ${tableName}`);
|
|
305
|
+
// First, check if this is a known standard table that may not appear in sys_db_object
|
|
306
|
+
const standardTables = {
|
|
307
|
+
'incident': { label: 'Incident' },
|
|
308
|
+
'problem': { label: 'Problem' },
|
|
309
|
+
'change_request': { label: 'Change Request' },
|
|
310
|
+
'sc_request': { label: 'Request' },
|
|
311
|
+
'sc_req_item': { label: 'Requested Item' },
|
|
312
|
+
'sc_task': { label: 'Catalog Task' },
|
|
313
|
+
'task': { label: 'Task' }
|
|
314
|
+
};
|
|
315
|
+
if (standardTables[tableName]) {
|
|
316
|
+
this.logger.debug(`Using known standard table: ${tableName}`);
|
|
317
|
+
return {
|
|
318
|
+
name: tableName,
|
|
319
|
+
label: standardTables[tableName].label,
|
|
320
|
+
sys_id: `standard_table_${tableName}` // Placeholder sys_id for standard tables
|
|
321
|
+
};
|
|
322
|
+
}
|
|
305
323
|
// Try direct lookup first
|
|
306
324
|
const tableResponse = await this.client.searchRecords('sys_db_object', `name=${tableName}`, 1);
|
|
307
325
|
if (tableResponse.success && tableResponse.data?.result?.length > 0) {
|
|
@@ -312,6 +330,8 @@ class ServiceNowPlatformDevelopmentMCP {
|
|
|
312
330
|
sys_id: table.sys_id
|
|
313
331
|
};
|
|
314
332
|
}
|
|
333
|
+
// Log the actual response for debugging
|
|
334
|
+
this.logger.debug(`Table lookup response for ${tableName}: ${JSON.stringify(tableResponse)}`);
|
|
315
335
|
// Try by sys_id
|
|
316
336
|
const tableByIdResponse = await this.client.searchRecords('sys_db_object', `sys_id=${tableName}`, 1);
|
|
317
337
|
if (tableByIdResponse.success && tableByIdResponse.data?.result?.length > 0) {
|
|
@@ -583,10 +603,15 @@ class ServiceNowPlatformDevelopmentMCP {
|
|
|
583
603
|
this.logger.debug(`Found table info: ${JSON.stringify(tableInfo)}`);
|
|
584
604
|
// Get detailed table metadata
|
|
585
605
|
this.logger.debug(`Attempting to fetch table details for sys_id: ${tableInfo.sys_id}`);
|
|
586
|
-
|
|
606
|
+
// Check if this is a standard table with placeholder sys_id
|
|
607
|
+
const isStandardTable = tableInfo.sys_id.startsWith('standard_table_');
|
|
608
|
+
let tableDetailsResponse = { success: false };
|
|
609
|
+
if (!isStandardTable) {
|
|
610
|
+
tableDetailsResponse = await this.client.getRecord('sys_db_object', tableInfo.sys_id);
|
|
611
|
+
}
|
|
587
612
|
// Declare the variable once with proper type
|
|
588
613
|
let tableDetails;
|
|
589
|
-
if (!tableDetailsResponse.success) {
|
|
614
|
+
if (!tableDetailsResponse.success || isStandardTable) {
|
|
590
615
|
const errorMessage = tableDetailsResponse.error ||
|
|
591
616
|
JSON.stringify(tableDetailsResponse) ||
|
|
592
617
|
'Unknown error occurred while fetching table details';
|
|
@@ -187,6 +187,10 @@ class ServiceNowUpdateSetMCP extends base_mcp_server_js_1.BaseMCPServer {
|
|
|
187
187
|
url: '/api/now/table/sys_update_set',
|
|
188
188
|
data: updateSetData
|
|
189
189
|
});
|
|
190
|
+
// Validate response structure
|
|
191
|
+
if (!response || !response.result) {
|
|
192
|
+
throw new Error(`Invalid API response: ${JSON.stringify(response)}`);
|
|
193
|
+
}
|
|
190
194
|
const updateSet = response.result;
|
|
191
195
|
// Create session
|
|
192
196
|
const session = {
|
|
@@ -276,6 +276,10 @@ class ServiceNowUpdateSetMCP {
|
|
|
276
276
|
if (!response.success) {
|
|
277
277
|
throw new Error(response.error || 'Failed to create Update Set');
|
|
278
278
|
}
|
|
279
|
+
// Validate response structure
|
|
280
|
+
if (!response.data || !response.data.sys_id) {
|
|
281
|
+
throw new Error(`Invalid Update Set response: missing data or sys_id. Response: ${JSON.stringify(response)}`);
|
|
282
|
+
}
|
|
279
283
|
// Auto-switch to Update Set if requested (default: true)
|
|
280
284
|
const autoSwitch = args.auto_switch !== false;
|
|
281
285
|
let switchedToUpdateSet = false;
|