snow-flow 3.3.5 → 3.3.6
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.6';
|
|
40
40
|
}
|
|
41
41
|
// Export a constant that uses the dynamic version
|
|
42
42
|
exports.VERSION = getDynamicVersion();
|
|
@@ -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.6",
|
|
4
|
+
"description": "Snow-Flow v3.3.6: CRITICAL QUERY & FIELD FIX - snow_query_table now fully supports pagination, field validation, and proper error handling! Fixed: offset support for large datasets, ORDERBY syntax, field discovery with correct ServiceNow queries, display values, and intelligent error messages. Enhanced field grouping (required/reference/regular), better limit strategies for analytics vs display, and clear content inclusion logic. Query any table with confidence - all 9 critical issues resolved! 180+ MCP tools across 17 specialized servers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"bin": {
|