snow-flow 3.3.5 → 3.3.7
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/dynamic-version.js
CHANGED
|
@@ -36,7 +36,7 @@ function getDynamicVersion() {
|
|
|
36
36
|
console.warn('Warning: Could not read version from package.json:', error);
|
|
37
37
|
}
|
|
38
38
|
// Fallback to hardcoded version
|
|
39
|
-
return '3.3.
|
|
39
|
+
return '3.3.7';
|
|
40
40
|
}
|
|
41
41
|
// Export a constant that uses the dynamic version
|
|
42
42
|
exports.VERSION = getDynamicVersion();
|
|
@@ -1316,7 +1316,12 @@ ${is403Error ? '\n⚠️ **Possible False Negative**: Widget may have been crea
|
|
|
1316
1316
|
const { updateSetId, updateSetName } = await this.ensureUpdateSet('Portal Page', args.page_id);
|
|
1317
1317
|
// Validate portal page structure
|
|
1318
1318
|
if (!args.page_id || !args.title) {
|
|
1319
|
-
|
|
1319
|
+
this.logger.error('Portal page validation failed', {
|
|
1320
|
+
args,
|
|
1321
|
+
hasPageId: !!args.page_id,
|
|
1322
|
+
hasTitle: !!args.title
|
|
1323
|
+
});
|
|
1324
|
+
throw new Error(`Portal page must have page_id and title. Received: page_id=${args.page_id}, title=${args.title}`);
|
|
1320
1325
|
}
|
|
1321
1326
|
// Find widget sys_id if widget name is provided
|
|
1322
1327
|
let widgetSysId = args.widget_sys_id;
|
|
@@ -1337,18 +1342,22 @@ ${is403Error ? '\n⚠️ **Possible False Negative**: Widget may have been crea
|
|
|
1337
1342
|
}
|
|
1338
1343
|
}
|
|
1339
1344
|
// Determine portal sys_id
|
|
1340
|
-
let portalSysId = '';
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
this.
|
|
1345
|
+
let portalSysId = args.sp_portal || '';
|
|
1346
|
+
// If sp_portal is already a sys_id (32 char hex), use it directly
|
|
1347
|
+
if (!/^[a-f0-9]{32}$/.test(portalSysId)) {
|
|
1348
|
+
// Not a sys_id, try to look it up
|
|
1349
|
+
try {
|
|
1350
|
+
// Default to Employee Service Portal if available, otherwise standard Service Portal
|
|
1351
|
+
const portalQuery = args.portal === 'esc' ? 'url_suffix=esc' : 'url_suffix=sp';
|
|
1352
|
+
const portalResult = await this.client.searchRecords('sp_portal', portalQuery, 1);
|
|
1353
|
+
if (portalResult.success && portalResult.data?.result?.length > 0) {
|
|
1354
|
+
portalSysId = portalResult.data.result[0].sys_id;
|
|
1355
|
+
this.logger.info('Found portal', { portal: args.portal, sys_id: portalSysId });
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
catch (error) {
|
|
1359
|
+
this.logger.warn('Failed to lookup portal, using provided value', { portal: args.portal, error });
|
|
1348
1360
|
}
|
|
1349
|
-
}
|
|
1350
|
-
catch (error) {
|
|
1351
|
-
this.logger.warn('Failed to lookup portal, using default', { portal: args.portal, error });
|
|
1352
1361
|
}
|
|
1353
1362
|
// Create portal page
|
|
1354
1363
|
let pageResult;
|
|
@@ -1381,8 +1390,35 @@ ${is403Error ? '\n⚠️ **Possible False Negative**: Widget may have been crea
|
|
|
1381
1390
|
const credentials = await this.oauth.loadCredentials();
|
|
1382
1391
|
// Create widget instances on the page if widget is provided
|
|
1383
1392
|
const widgetInstances = [];
|
|
1384
|
-
|
|
1385
|
-
|
|
1393
|
+
// Support both widgets array and single widget
|
|
1394
|
+
const widgetsToCreate = args.widgets || [];
|
|
1395
|
+
// If no widgets array but a single widget is specified, create one widget
|
|
1396
|
+
if (widgetsToCreate.length === 0 && widgetSysId) {
|
|
1397
|
+
widgetsToCreate.push({
|
|
1398
|
+
widget: args.widget_name || widgetSysId,
|
|
1399
|
+
width: 12,
|
|
1400
|
+
row: 1,
|
|
1401
|
+
column: 1,
|
|
1402
|
+
title: args.widget_title || args.title || '',
|
|
1403
|
+
options: args.widget_options || {}
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
if (widgetsToCreate.length > 0) {
|
|
1407
|
+
for (const widgetConfig of widgetsToCreate) {
|
|
1408
|
+
// Determine widget sys_id for this widget
|
|
1409
|
+
let currentWidgetSysId = widgetSysId; // Default to the main widget
|
|
1410
|
+
// If widget config specifies a different widget, look it up
|
|
1411
|
+
if (widgetConfig.widget && widgetConfig.widget !== args.widget_name) {
|
|
1412
|
+
try {
|
|
1413
|
+
const widgetLookup = await this.client.searchRecords('sp_widget', `sys_id=${widgetConfig.widget}^ORname=${widgetConfig.widget}`, 1);
|
|
1414
|
+
if (widgetLookup.success && widgetLookup.data?.result?.length > 0) {
|
|
1415
|
+
currentWidgetSysId = widgetLookup.data.result[0].sys_id;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
catch (lookupError) {
|
|
1419
|
+
this.logger.warn('Could not find widget, using default', { widget: widgetConfig.widget });
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1386
1422
|
try {
|
|
1387
1423
|
// Create container
|
|
1388
1424
|
const containerResult = await this.client.createRecord('sp_container', {
|
|
@@ -1417,10 +1453,10 @@ ${is403Error ? '\n⚠️ **Possible False Negative**: Widget may have been crea
|
|
|
1417
1453
|
// Create widget instance
|
|
1418
1454
|
const instanceResult = await this.client.createRecord('sp_instance', {
|
|
1419
1455
|
sp_column: columnResult.data.sys_id,
|
|
1420
|
-
sp_widget:
|
|
1456
|
+
sp_widget: currentWidgetSysId, // Use the current widget sys_id, not the main one
|
|
1421
1457
|
order: widgetConfig.order || 100,
|
|
1422
1458
|
title: widgetConfig.title || '',
|
|
1423
|
-
options: JSON.stringify(widgetConfig.options || {}),
|
|
1459
|
+
options: JSON.stringify(widgetConfig.options || widgetConfig.widget_parameters || {}),
|
|
1424
1460
|
class_name: widgetConfig.instance_class || '',
|
|
1425
1461
|
color: widgetConfig.color || 'default',
|
|
1426
1462
|
active: true,
|
|
@@ -7260,7 +7296,23 @@ Use individual deployment tools like \`snow_deploy_${args.type}\` with manual co
|
|
|
7260
7296
|
case 'widget':
|
|
7261
7297
|
return await this.deployWidget(scopedConfig);
|
|
7262
7298
|
case 'portal_page':
|
|
7263
|
-
|
|
7299
|
+
// Map config fields to expected parameter names for portal_page
|
|
7300
|
+
const portalPageArgs = {
|
|
7301
|
+
page_id: scopedConfig.id || scopedConfig.page_id,
|
|
7302
|
+
title: scopedConfig.title,
|
|
7303
|
+
widget_name: scopedConfig.widget_name,
|
|
7304
|
+
widget_sys_id: scopedConfig.widget_sys_id,
|
|
7305
|
+
description: scopedConfig.summary || scopedConfig.description,
|
|
7306
|
+
page_css: scopedConfig.css,
|
|
7307
|
+
portal: scopedConfig.portal || scopedConfig.sp_portal || 'sp',
|
|
7308
|
+
public: scopedConfig.public !== undefined ? scopedConfig.public : true,
|
|
7309
|
+
requires_authentication: scopedConfig.requires_authentication,
|
|
7310
|
+
draft: scopedConfig.draft || false,
|
|
7311
|
+
// Map containers to widgets format
|
|
7312
|
+
widgets: this.mapContainersToWidgets(scopedConfig.containers),
|
|
7313
|
+
...scopedConfig // Include any other fields
|
|
7314
|
+
};
|
|
7315
|
+
return await this.deployPortalPage(portalPageArgs);
|
|
7264
7316
|
case 'application':
|
|
7265
7317
|
return await this.deployApplication(scopedConfig);
|
|
7266
7318
|
case 'xml_update_set':
|
|
@@ -8851,6 +8903,36 @@ c.$onInit = function() {
|
|
|
8851
8903
|
await this.server.connect(transport);
|
|
8852
8904
|
this.logger.info('ServiceNow Deployment MCP Server started');
|
|
8853
8905
|
}
|
|
8906
|
+
/**
|
|
8907
|
+
* Map container structure to widget structure for portal page deployment
|
|
8908
|
+
*/
|
|
8909
|
+
mapContainersToWidgets(containers) {
|
|
8910
|
+
if (!containers || !Array.isArray(containers)) {
|
|
8911
|
+
return [];
|
|
8912
|
+
}
|
|
8913
|
+
const widgets = [];
|
|
8914
|
+
for (const container of containers) {
|
|
8915
|
+
if (container.widget_instance) {
|
|
8916
|
+
widgets.push({
|
|
8917
|
+
widget: container.widget_instance.widget,
|
|
8918
|
+
width: container.width || 12,
|
|
8919
|
+
row: container.row || 1,
|
|
8920
|
+
column: container.column || 1,
|
|
8921
|
+
title: container.widget_instance.title || '',
|
|
8922
|
+
options: container.widget_instance.widget_parameters || {},
|
|
8923
|
+
container_title: container.title || '',
|
|
8924
|
+
class_name: container.class_name || '',
|
|
8925
|
+
background_color: container.background_color || '',
|
|
8926
|
+
background_image: container.background_image || '',
|
|
8927
|
+
background_style: container.background_style || 'default',
|
|
8928
|
+
instance_class: container.widget_instance.class_name || '',
|
|
8929
|
+
color: container.widget_instance.color || 'default',
|
|
8930
|
+
order: container.widget_instance.order || 100
|
|
8931
|
+
});
|
|
8932
|
+
}
|
|
8933
|
+
}
|
|
8934
|
+
return widgets;
|
|
8935
|
+
}
|
|
8854
8936
|
/**
|
|
8855
8937
|
* Escape XML special characters
|
|
8856
8938
|
*/
|
|
@@ -868,7 +868,12 @@ class ServiceNowOperationsMCP {
|
|
|
868
868
|
logger_js_1.logger.info(`⚠️ No specific context detected - using conservative limit. Consider specifying limit for your use case!`);
|
|
869
869
|
return 500; // Conservative default
|
|
870
870
|
};
|
|
871
|
-
const { table, query, include_content = false, fields, include_display_values = false, group_by, order_by
|
|
871
|
+
const { table, query = '', include_content = false, fields, include_display_values = false, group_by, order_by, offset = 0 // ✅ NEW: Support pagination!
|
|
872
|
+
} = args;
|
|
873
|
+
// Validate table name
|
|
874
|
+
if (!table) {
|
|
875
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Table name is required');
|
|
876
|
+
}
|
|
872
877
|
// Apply intelligent limit strategy
|
|
873
878
|
const limit = determineSmartLimit(args.limit, table, query, include_content || !!fields, fields);
|
|
874
879
|
// For analytics, we want NO limit at all
|
|
@@ -880,19 +885,22 @@ class ServiceNowOperationsMCP {
|
|
|
880
885
|
if (isMLTrainingContext && limit < 1000) {
|
|
881
886
|
logger_js_1.logger.warn(`⚠️ ML Training detected with low limit (${limit}). Consider setting limit=5000+ for better training data!`);
|
|
882
887
|
}
|
|
883
|
-
logger_js_1.logger.info(`Universal query on table '${table}' with: ${query} (limit: ${limit === undefined ? 'UNLIMITED' : limit}, include_content: ${include_content})`);
|
|
888
|
+
logger_js_1.logger.info(`Universal query on table '${table}' with: ${query} (limit: ${limit === undefined ? 'UNLIMITED' : limit}, offset: ${offset}, include_content: ${include_content})`);
|
|
884
889
|
try {
|
|
885
890
|
// Convert natural language to ServiceNow query if needed
|
|
886
|
-
const processedQuery = this.processNaturalLanguageQuery(query, table);
|
|
887
|
-
// Build the query with order_by
|
|
891
|
+
const processedQuery = this.processNaturalLanguageQuery(query || '', table);
|
|
892
|
+
// Build the query with CORRECT order_by syntax
|
|
888
893
|
let finalQuery = processedQuery;
|
|
889
894
|
if (order_by) {
|
|
895
|
+
// ✅ FIX: Use correct ServiceNow ordering syntax
|
|
890
896
|
const orderDirection = order_by.startsWith('-') ? 'DESC' : '';
|
|
891
897
|
const orderField = order_by.replace(/^-/, '');
|
|
892
|
-
finalQuery += `^ORDERBY${orderDirection}${orderField}`;
|
|
898
|
+
finalQuery += `^ORDERBY${orderDirection ? orderDirection : ''}${orderField}`;
|
|
893
899
|
}
|
|
894
|
-
//
|
|
895
|
-
const records =
|
|
900
|
+
// ✅ NEW: Use searchRecordsWithOffset when offset is provided
|
|
901
|
+
const records = offset > 0
|
|
902
|
+
? await this.client.searchRecordsWithOffset(table, finalQuery, effectiveLimit, offset)
|
|
903
|
+
: await this.client.searchRecords(table, finalQuery, effectiveLimit);
|
|
896
904
|
let result = {
|
|
897
905
|
table: table,
|
|
898
906
|
total_results: records.success ? records.data.result.length : 0,
|
|
@@ -908,33 +916,62 @@ class ServiceNowOperationsMCP {
|
|
|
908
916
|
result.grouped_counts = grouped;
|
|
909
917
|
result.unique_values = Object.keys(grouped).length;
|
|
910
918
|
}
|
|
911
|
-
//
|
|
912
|
-
|
|
919
|
+
// ✅ IMPROVED: Clear content decision logic
|
|
920
|
+
const shouldIncludeContent = include_content === true || (fields && fields.length > 0);
|
|
921
|
+
if (shouldIncludeContent) {
|
|
913
922
|
// Include full record data when specifically requested
|
|
914
923
|
result.records = records.success ? records.data.result : [];
|
|
915
|
-
// If specific fields requested, filter them
|
|
916
|
-
if (fields && fields.length > 0 && records.success) {
|
|
924
|
+
// If specific fields requested, filter and validate them
|
|
925
|
+
if (fields && fields.length > 0 && records.success && records.data.result.length > 0) {
|
|
926
|
+
// ✅ NEW: Validate fields exist
|
|
927
|
+
const sampleRecord = records.data.result[0];
|
|
928
|
+
const validFields = fields.filter((field) => {
|
|
929
|
+
const exists = sampleRecord.hasOwnProperty(field) ||
|
|
930
|
+
sampleRecord.hasOwnProperty(`${field}_display_value`);
|
|
931
|
+
if (!exists) {
|
|
932
|
+
logger_js_1.logger.warn(`⚠️ Field '${field}' not found in table '${table}'`);
|
|
933
|
+
}
|
|
934
|
+
return exists;
|
|
935
|
+
});
|
|
936
|
+
if (validFields.length === 0 && fields.length > 0) {
|
|
937
|
+
// All requested fields are invalid
|
|
938
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `None of the requested fields exist in table '${table}'. Available fields: ${Object.keys(sampleRecord).slice(0, 10).join(', ')}...`);
|
|
939
|
+
}
|
|
917
940
|
result.records = records.data.result.map((record) => {
|
|
918
941
|
const filtered = {};
|
|
919
942
|
// Always include sys_id and number/name if available
|
|
920
943
|
if (record.sys_id)
|
|
921
944
|
filtered.sys_id = record.sys_id;
|
|
922
|
-
if (record.number)
|
|
945
|
+
if (record.number && !validFields.includes('number'))
|
|
923
946
|
filtered.number = record.number;
|
|
924
|
-
if (record.name && !
|
|
947
|
+
if (record.name && !validFields.includes('name'))
|
|
925
948
|
filtered.name = record.name;
|
|
926
949
|
// Add requested fields
|
|
927
|
-
|
|
950
|
+
validFields.forEach((field) => {
|
|
928
951
|
if (record[field] !== undefined) {
|
|
929
952
|
filtered[field] = record[field];
|
|
930
|
-
//
|
|
931
|
-
if (include_display_values
|
|
932
|
-
|
|
953
|
+
// ✅ FIX: Properly handle display values
|
|
954
|
+
if (include_display_values) {
|
|
955
|
+
// Check for standard display value pattern
|
|
956
|
+
const displayField = `dv_${field}`; // Some tables use dv_ prefix
|
|
957
|
+
const displayFieldAlt = `${field}_display_value`; // Others use _display_value suffix
|
|
958
|
+
if (record[displayField]) {
|
|
959
|
+
filtered[`${field}_display`] = record[displayField];
|
|
960
|
+
}
|
|
961
|
+
else if (record[displayFieldAlt]) {
|
|
962
|
+
filtered[`${field}_display`] = record[displayFieldAlt];
|
|
963
|
+
}
|
|
933
964
|
}
|
|
934
965
|
}
|
|
935
966
|
});
|
|
936
967
|
return filtered;
|
|
937
968
|
});
|
|
969
|
+
// Add metadata about fields
|
|
970
|
+
result.fields_info = {
|
|
971
|
+
requested: fields,
|
|
972
|
+
valid: validFields,
|
|
973
|
+
invalid: fields.filter((f) => !validFields.includes(f))
|
|
974
|
+
};
|
|
938
975
|
}
|
|
939
976
|
}
|
|
940
977
|
else {
|
|
@@ -955,18 +992,40 @@ class ServiceNowOperationsMCP {
|
|
|
955
992
|
};
|
|
956
993
|
}
|
|
957
994
|
}
|
|
995
|
+
// ✅ NEW: Add pagination info when offset is used
|
|
996
|
+
if (offset > 0) {
|
|
997
|
+
result.pagination = {
|
|
998
|
+
offset: offset,
|
|
999
|
+
limit: effectiveLimit,
|
|
1000
|
+
has_more: records.success && records.data.result.length === effectiveLimit,
|
|
1001
|
+
next_offset: offset + effectiveLimit
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
958
1004
|
return {
|
|
959
1005
|
content: [
|
|
960
1006
|
{
|
|
961
1007
|
type: 'text',
|
|
962
|
-
text: `Found ${records.success ? records.data.result.length : 0} ${table} records matching query: "${query}"\n\n${JSON.stringify(result, null, 2)}`
|
|
1008
|
+
text: `Found ${records.success ? records.data.result.length : 0} ${table} records${offset > 0 ? ` (offset: ${offset})` : ''} matching query: "${query || 'all'}"\n\n${JSON.stringify(result, null, 2)}`
|
|
963
1009
|
}
|
|
964
1010
|
]
|
|
965
1011
|
};
|
|
966
1012
|
}
|
|
967
1013
|
catch (error) {
|
|
1014
|
+
// ✅ IMPROVED: Better error messages
|
|
968
1015
|
logger_js_1.logger.error(`Error querying ${table}:`, error);
|
|
969
|
-
|
|
1016
|
+
if (error.code === 'ECONNREFUSED') {
|
|
1017
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Cannot connect to ServiceNow. Please check your instance URL and network connection.`);
|
|
1018
|
+
}
|
|
1019
|
+
if (error.response?.status === 401) {
|
|
1020
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, `Authentication failed. Please run: snow-flow auth login`);
|
|
1021
|
+
}
|
|
1022
|
+
if (error.response?.status === 404) {
|
|
1023
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Table '${table}' not found. Please check the table name.`);
|
|
1024
|
+
}
|
|
1025
|
+
if (error.response?.status === 400) {
|
|
1026
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid query syntax: ${query}. Check ServiceNow encoded query format.`);
|
|
1027
|
+
}
|
|
1028
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to query ${table}: ${error.message || error}`);
|
|
970
1029
|
}
|
|
971
1030
|
}
|
|
972
1031
|
detectCommonFields(table, records) {
|
|
@@ -256,44 +256,84 @@ class ServiceNowPlatformDevelopmentMCP {
|
|
|
256
256
|
*/
|
|
257
257
|
async discoverTableFields(args) {
|
|
258
258
|
try {
|
|
259
|
-
const tableName = args.tableName;
|
|
259
|
+
const tableName = args.tableName || args.table_name; // Support both parameter names
|
|
260
|
+
if (!tableName) {
|
|
261
|
+
throw new Error('Table name is required (use tableName or table_name parameter)');
|
|
262
|
+
}
|
|
260
263
|
this.logger.info(`Discovering fields for table: ${tableName}`);
|
|
261
264
|
// First, resolve table name to sys_id if needed
|
|
262
265
|
const tableInfo = await this.getTableInfo(tableName);
|
|
263
266
|
if (!tableInfo) {
|
|
264
267
|
throw new Error(`Table not found: ${tableName}`);
|
|
265
268
|
}
|
|
266
|
-
// Get all fields for this table
|
|
267
|
-
|
|
269
|
+
// Get all fields for this table with CORRECT query syntax
|
|
270
|
+
// ✅ FIX: Use proper ServiceNow query for dictionary
|
|
271
|
+
const fieldsResponse = await this.client.searchRecords('sys_dictionary', `name=${tableInfo.name}^element!=NULL^ORname=${tableInfo.name}^elementISNOTEMPTY`, 500 // Increased limit for tables with many fields
|
|
272
|
+
);
|
|
268
273
|
if (!fieldsResponse.success || !fieldsResponse.data) {
|
|
269
274
|
throw new Error(`Failed to get fields for table: ${tableName}`);
|
|
270
275
|
}
|
|
271
|
-
|
|
276
|
+
// ✅ IMPROVED: Better field mapping with validation
|
|
277
|
+
const fields = fieldsResponse.data.result
|
|
278
|
+
.filter((field) => field.element && field.element !== 'null' && field.element !== 'NULL')
|
|
279
|
+
.map((field) => ({
|
|
272
280
|
name: field.element,
|
|
273
|
-
type: field.internal_type,
|
|
274
|
-
label: field.column_label,
|
|
275
|
-
mandatory: field.mandatory === 'true',
|
|
276
|
-
display: field.display === 'true',
|
|
277
|
-
max_length: field.max_length,
|
|
278
|
-
reference: field.reference,
|
|
279
|
-
choice: field.choice
|
|
280
|
-
|
|
281
|
+
type: field.internal_type || field.data_type || 'string',
|
|
282
|
+
label: field.column_label || field.element,
|
|
283
|
+
mandatory: field.mandatory === 'true' || field.mandatory === true,
|
|
284
|
+
display: field.display === 'true' || field.display === true,
|
|
285
|
+
max_length: parseInt(field.max_length) || null,
|
|
286
|
+
reference: field.reference || null,
|
|
287
|
+
choice: field.choice || null,
|
|
288
|
+
default_value: field.default_value || null,
|
|
289
|
+
read_only: field.read_only === 'true' || field.read_only === true
|
|
290
|
+
}))
|
|
291
|
+
.sort((a, b) => {
|
|
292
|
+
// Sort: sys_id first, then mandatory fields, then alphabetically
|
|
293
|
+
if (a.name === 'sys_id')
|
|
294
|
+
return -1;
|
|
295
|
+
if (b.name === 'sys_id')
|
|
296
|
+
return 1;
|
|
297
|
+
if (a.mandatory && !b.mandatory)
|
|
298
|
+
return -1;
|
|
299
|
+
if (!a.mandatory && b.mandatory)
|
|
300
|
+
return 1;
|
|
301
|
+
return a.name.localeCompare(b.name);
|
|
302
|
+
});
|
|
281
303
|
// Cache the table info
|
|
282
304
|
this.tableCache.set(tableName, {
|
|
283
305
|
name: tableInfo.name,
|
|
284
306
|
label: tableInfo.label,
|
|
285
307
|
fields: fields
|
|
286
308
|
});
|
|
309
|
+
// ✅ NEW: Group fields by category for better readability
|
|
310
|
+
const mandatoryFields = fields.filter((f) => f.mandatory);
|
|
311
|
+
const referenceFields = fields.filter((f) => f.reference);
|
|
312
|
+
const regularFields = fields.filter((f) => !f.mandatory && !f.reference);
|
|
287
313
|
return {
|
|
288
314
|
content: [{
|
|
289
315
|
type: 'text',
|
|
290
|
-
text: `📋 Fields for ${tableInfo.label} (${tableInfo.name}):\n\n
|
|
316
|
+
text: `📋 Fields for ${tableInfo.label} (${tableInfo.name}):\n\n` +
|
|
317
|
+
(mandatoryFields.length > 0 ? `**Required Fields:**\n${mandatoryFields.map((field) => `- **${field.name}** (${field.label})\n Type: ${field.type}${field.max_length ? ` [max: ${field.max_length}]` : ''}${field.default_value ? ` = '${field.default_value}'` : ''}`).join('\n')}\n\n` : '') +
|
|
318
|
+
(referenceFields.length > 0 ? `**Reference Fields:**\n${referenceFields.map((field) => `- **${field.name}** (${field.label})\n → ${field.reference}${field.mandatory ? ' *Required*' : ''}`).join('\n')}\n\n` : '') +
|
|
319
|
+
(regularFields.length > 0 ? `**Other Fields:**\n${regularFields.slice(0, 20).map((field) => `- ${field.name} (${field.type}${field.read_only ? ', read-only' : ''})`).join('\n')}${regularFields.length > 20 ? `\n ... and ${regularFields.length - 20} more fields` : ''}\n\n` : '') +
|
|
320
|
+
`🔍 Total: ${fields.length} fields (${mandatoryFields.length} required, ${referenceFields.length} references)\n` +
|
|
321
|
+
`✨ All fields discovered dynamically from ServiceNow!\n\n` +
|
|
322
|
+
`💡 Tip: Use these field names in snow_query_table with fields parameter`
|
|
291
323
|
}]
|
|
292
324
|
};
|
|
293
325
|
}
|
|
294
326
|
catch (error) {
|
|
295
327
|
this.logger.error('Failed to discover table fields:', error);
|
|
296
|
-
|
|
328
|
+
// ✅ IMPROVED: Better error messages
|
|
329
|
+
if (error.message?.includes('Table not found')) {
|
|
330
|
+
const tableName = args.tableName || args.table_name;
|
|
331
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Table '${tableName}' does not exist in ServiceNow. Please check the table name.`);
|
|
332
|
+
}
|
|
333
|
+
if (error.response?.status === 401) {
|
|
334
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, `Authentication required. Please run: snow-flow auth login`);
|
|
335
|
+
}
|
|
336
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover fields for ${args.tableName || args.table_name}: ${error.message || error}`);
|
|
297
337
|
}
|
|
298
338
|
}
|
|
299
339
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-flow",
|
|
3
|
-
"version": "3.3.
|
|
4
|
-
"description": "Snow-Flow v3.3.
|
|
3
|
+
"version": "3.3.7",
|
|
4
|
+
"description": "Snow-Flow v3.3.7: PORTAL PAGE FIX - Fixed portal_page deployment field mapping! Now correctly maps id->page_id, handles sp_portal sys_id, and converts containers to widgets format. Fixed widget instance creation with proper sys_id handling. Portal pages with AI chatbots and complex widgets now deploy successfully. Improved validation messages and support for both single widgets and widget arrays. 180+ MCP tools across 17 specialized servers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"bin": {
|