snow-flow 1.3.16 → 1.3.18
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/COMMIT_MESSAGE.txt +31 -0
- package/README.md +28 -25
- package/action-v2-implementation.js +338 -0
- package/decode-action-values.js +164 -0
- package/dist/agents/index.js +1 -4
- package/dist/agents/queen-agent.js +3 -1
- package/dist/cli.js +50 -30
- 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/improved-flow-xml-generator.js +68 -10
- package/dist/utils/xml-first-flow-generator.js +23 -32
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
41
41
|
exports.ServiceNowXMLFlowMCP = void 0;
|
|
42
42
|
const base_mcp_server_1 = require("./base-mcp-server");
|
|
43
43
|
const improved_flow_xml_generator_1 = __importStar(require("../utils/improved-flow-xml-generator"));
|
|
44
|
+
const xml_first_flow_generator_1 = require("../utils/xml-first-flow-generator"); // Keep for backward compatibility
|
|
44
45
|
const natural_language_mapper_1 = require("../api/natural-language-mapper");
|
|
45
46
|
const servicenow_id_generator_js_1 = require("../utils/servicenow-id-generator.js");
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
@@ -118,7 +119,7 @@ class ServiceNowXMLFlowMCP extends base_mcp_server_1.BaseMCPServer {
|
|
|
118
119
|
// Generate flow XML from natural language
|
|
119
120
|
this.registerTool({
|
|
120
121
|
name: 'snow_xml_flow_from_instruction',
|
|
121
|
-
description: '
|
|
122
|
+
description: '⚠️ DEPRECATED - Use snow_create_flow instead. This tool is kept for backwards compatibility only.',
|
|
122
123
|
inputSchema: {
|
|
123
124
|
type: 'object',
|
|
124
125
|
properties: {
|
|
@@ -130,6 +131,11 @@ class ServiceNowXMLFlowMCP extends base_mcp_server_1.BaseMCPServer {
|
|
|
130
131
|
type: 'boolean',
|
|
131
132
|
default: true,
|
|
132
133
|
description: 'Save XML to file'
|
|
134
|
+
},
|
|
135
|
+
auto_deploy: {
|
|
136
|
+
type: 'boolean',
|
|
137
|
+
default: true,
|
|
138
|
+
description: 'Automatically deploy XML to ServiceNow after generation (RECOMMENDED)'
|
|
133
139
|
}
|
|
134
140
|
},
|
|
135
141
|
required: ['instruction']
|
|
@@ -219,7 +225,7 @@ class ServiceNowXMLFlowMCP extends base_mcp_server_1.BaseMCPServer {
|
|
|
219
225
|
*/
|
|
220
226
|
async generateFlowFromInstruction(args) {
|
|
221
227
|
try {
|
|
222
|
-
const { instruction } = args;
|
|
228
|
+
const { instruction, auto_deploy = true } = args;
|
|
223
229
|
// Parse natural language to flow components
|
|
224
230
|
const flowRequirements = await this.nlMapper.parseFlowRequirements(instruction);
|
|
225
231
|
// Convert to IMPROVED flow definition
|
|
@@ -235,21 +241,128 @@ class ServiceNowXMLFlowMCP extends base_mcp_server_1.BaseMCPServer {
|
|
|
235
241
|
tags: ['auto-generated'],
|
|
236
242
|
activities: this.convertToImprovedActivities(flowRequirements)
|
|
237
243
|
};
|
|
238
|
-
// Use
|
|
239
|
-
const result = (0,
|
|
244
|
+
// Use PRODUCTION-READY generator with proper Update Set structure
|
|
245
|
+
const result = (0, xml_first_flow_generator_1.generateProductionFlowXML)(flowDef);
|
|
246
|
+
let deploymentResult = null;
|
|
247
|
+
// 🚀 AUTO-DEPLOYMENT: Deploy immediately if requested
|
|
248
|
+
if (auto_deploy) {
|
|
249
|
+
try {
|
|
250
|
+
this.logger.info('🚀 AUTO-DEPLOYING XML to ServiceNow...');
|
|
251
|
+
// Check authentication
|
|
252
|
+
const isAuth = await this.oauth.isAuthenticated();
|
|
253
|
+
if (!isAuth) {
|
|
254
|
+
throw new Error('Not authenticated with ServiceNow. Please run: snow-flow auth login');
|
|
255
|
+
}
|
|
256
|
+
// Initialize ServiceNow client
|
|
257
|
+
const client = new ServiceNowClient();
|
|
258
|
+
// Read the XML file
|
|
259
|
+
const fs = require('fs').promises;
|
|
260
|
+
const xmlContent = await fs.readFile(result.filePath, 'utf-8');
|
|
261
|
+
// Import XML as remote update set
|
|
262
|
+
const importResponse = await client.makeRequest({
|
|
263
|
+
method: 'POST',
|
|
264
|
+
url: '/api/now/table/sys_remote_update_set',
|
|
265
|
+
headers: {
|
|
266
|
+
'Content-Type': 'application/xml',
|
|
267
|
+
'Accept': 'application/json'
|
|
268
|
+
},
|
|
269
|
+
data: xmlContent
|
|
270
|
+
});
|
|
271
|
+
if (!importResponse.result || !importResponse.result.sys_id) {
|
|
272
|
+
throw new Error('Failed to import XML update set');
|
|
273
|
+
}
|
|
274
|
+
const remoteUpdateSetId = importResponse.result.sys_id;
|
|
275
|
+
this.logger.info(`✅ XML imported successfully (sys_id: ${remoteUpdateSetId})`);
|
|
276
|
+
// Load the update set
|
|
277
|
+
await client.makeRequest({
|
|
278
|
+
method: 'PUT',
|
|
279
|
+
url: `/api/now/table/sys_remote_update_set/${remoteUpdateSetId}`,
|
|
280
|
+
data: {
|
|
281
|
+
state: 'loaded'
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// Find the loaded update set
|
|
285
|
+
const loadedResponse = await client.makeRequest({
|
|
286
|
+
method: 'GET',
|
|
287
|
+
url: '/api/now/table/sys_update_set',
|
|
288
|
+
params: {
|
|
289
|
+
sysparm_query: `remote_sys_id=${remoteUpdateSetId}`,
|
|
290
|
+
sysparm_limit: 1
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
if (!loadedResponse.result || loadedResponse.result.length === 0) {
|
|
294
|
+
throw new Error('Failed to find loaded update set');
|
|
295
|
+
}
|
|
296
|
+
const updateSetId = loadedResponse.result[0].sys_id;
|
|
297
|
+
const updateSetName = loadedResponse.result[0].name;
|
|
298
|
+
// Preview the update set
|
|
299
|
+
await client.makeRequest({
|
|
300
|
+
method: 'POST',
|
|
301
|
+
url: `/api/now/table/sys_update_set/${updateSetId}/preview`
|
|
302
|
+
});
|
|
303
|
+
// Check for preview problems
|
|
304
|
+
const previewProblems = await client.makeRequest({
|
|
305
|
+
method: 'GET',
|
|
306
|
+
url: '/api/now/table/sys_update_preview_problem',
|
|
307
|
+
params: {
|
|
308
|
+
sysparm_query: `update_set=${updateSetId}`,
|
|
309
|
+
sysparm_limit: 100
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
if (previewProblems.result && previewProblems.result.length > 0) {
|
|
313
|
+
const problemsList = previewProblems.result.map((p) => `- ${p.type}: ${p.description}`).join('\n');
|
|
314
|
+
throw new Error(`Preview found problems:\n${problemsList}\n\nPlease review and resolve in ServiceNow UI`);
|
|
315
|
+
}
|
|
316
|
+
// Commit the update set
|
|
317
|
+
await client.makeRequest({
|
|
318
|
+
method: 'POST',
|
|
319
|
+
url: `/api/now/table/sys_update_set/${updateSetId}/commit`
|
|
320
|
+
});
|
|
321
|
+
deploymentResult = {
|
|
322
|
+
success: true,
|
|
323
|
+
message: '✅ XML automatically deployed to ServiceNow!',
|
|
324
|
+
update_set_id: updateSetId,
|
|
325
|
+
update_set_name: updateSetName,
|
|
326
|
+
steps_completed: [
|
|
327
|
+
'✅ XML imported as remote update set',
|
|
328
|
+
'✅ Update set loaded successfully',
|
|
329
|
+
'✅ Preview completed with no problems',
|
|
330
|
+
'✅ Update set committed successfully'
|
|
331
|
+
]
|
|
332
|
+
};
|
|
333
|
+
this.logger.info('✅ Auto-deployment successful', deploymentResult);
|
|
334
|
+
}
|
|
335
|
+
catch (deployError) {
|
|
336
|
+
this.logger.warn('⚠️ Auto-deployment failed, providing manual instructions', deployError);
|
|
337
|
+
deploymentResult = {
|
|
338
|
+
success: false,
|
|
339
|
+
error: deployError instanceof Error ? deployError.message : String(deployError),
|
|
340
|
+
manual_command: `snow-flow deploy-xml ${result.filePath}`,
|
|
341
|
+
troubleshooting: [
|
|
342
|
+
'1. Check ServiceNow authentication: snow-flow auth status',
|
|
343
|
+
'2. Verify admin permissions in ServiceNow',
|
|
344
|
+
'3. Review XML file for format issues',
|
|
345
|
+
'4. Try manual deployment command above'
|
|
346
|
+
]
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
240
350
|
return {
|
|
241
351
|
success: true,
|
|
242
352
|
xml: args.save_to_file === false ? result.xml : undefined,
|
|
243
353
|
file_path: result.filePath,
|
|
244
354
|
flow_definition: flowDef,
|
|
245
|
-
|
|
355
|
+
deployment: deploymentResult,
|
|
356
|
+
message: `✅ Generated IMPROVED flow XML from instruction: ${instruction}${deploymentResult?.success ? ' + DEPLOYED!' : ''}`,
|
|
246
357
|
improvements: [
|
|
247
358
|
'✅ Production-ready Flow Designer format',
|
|
248
359
|
'✅ Complete XML structure with all required fields',
|
|
249
360
|
'✅ Base64+gzip encoded action values',
|
|
250
|
-
'✅ Proper v2 table usage'
|
|
361
|
+
'✅ Proper v2 table usage',
|
|
362
|
+
...(deploymentResult?.success ? ['✅ Automatically deployed to ServiceNow!'] : [])
|
|
251
363
|
],
|
|
252
|
-
import_instructions: result.instructions
|
|
364
|
+
import_instructions: deploymentResult?.success ? 'Flow is already deployed and ready to use!' : result.instructions,
|
|
365
|
+
auto_deploy_command: deploymentResult?.manual_command || `snow-flow deploy-xml ${result.filePath}`
|
|
253
366
|
};
|
|
254
367
|
}
|
|
255
368
|
catch (error) {
|
package/dist/types/index.js
CHANGED
|
@@ -17,4 +17,3 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
// Re-export coordination framework types
|
|
19
19
|
__exportStar(require("./snow-flow.types"), exports);
|
|
20
|
-
__exportStar(require("../coordination/types"), exports);
|