snow-flow 2.8.9 → 2.9.4

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.
@@ -44,6 +44,7 @@ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
44
44
  const tf = __importStar(require("@tensorflow/tfjs-node"));
45
45
  const logger_js_1 = require("../utils/logger.js");
46
46
  const servicenow_client_js_1 = require("../utils/servicenow-client.js");
47
+ const ml_data_fetcher_js_1 = require("../utils/ml-data-fetcher.js");
47
48
  class ServiceNowMachineLearningMCP {
48
49
  constructor(credentials) {
49
50
  // Model cache
@@ -108,14 +109,18 @@ class ServiceNowMachineLearningMCP {
108
109
  // Training tools
109
110
  {
110
111
  name: 'ml_train_incident_classifier',
111
- description: 'Train LSTM neural network on historical incident data with INTELLIGENT data selection. Snow-Flow automatically selects balanced training data or accepts custom queries. Works WITHOUT PA/PI plugins - only needs incident table access!',
112
+ description: 'Train LSTM neural network on historical incident data with INTELLIGENT data selection. Snow-Flow automatically detects available data and uses the optimal amount (up to 5000) for best accuracy. Works WITHOUT PA/PI plugins - only needs incident table access!',
112
113
  inputSchema: {
113
114
  type: 'object',
114
115
  properties: {
115
116
  sample_size: {
116
117
  type: 'number',
117
- description: 'Number of incidents to use for training',
118
- default: 1000
118
+ description: 'Number of incidents to use for training. If not specified, automatically uses all available data (up to 5000). Set to limit training data.'
119
+ },
120
+ auto_maximize_data: {
121
+ type: 'boolean',
122
+ description: 'Automatically use all available incident data for best model accuracy (default: true)',
123
+ default: true
119
124
  },
120
125
  epochs: {
121
126
  type: 'number',
@@ -134,7 +139,7 @@ class ServiceNowMachineLearningMCP {
134
139
  },
135
140
  intelligent_selection: {
136
141
  type: 'boolean',
137
- description: 'Let Snow-Flow intelligently select balanced training data across categories, priorities, and time periods',
142
+ description: 'Let Snow-Flow intelligently select balanced training data across categories, priorities, and time periods. Combined with auto_maximize_data for optimal results.',
138
143
  default: true
139
144
  },
140
145
  focus_categories: {
@@ -505,7 +510,9 @@ class ServiceNowMachineLearningMCP {
505
510
  * Uses PI if available, otherwise uses custom TensorFlow.js
506
511
  */
507
512
  async trainIncidentClassifier(args) {
508
- const { sample_size = 1000, epochs = 50, validation_split = 0.2, query = '', intelligent_selection = true, focus_categories = [], batch_size = 100, streaming_mode = true } = args;
513
+ const { sample_size, // No default - will be determined dynamically
514
+ epochs = 50, validation_split = 0.2, query = '', intelligent_selection = true, focus_categories = [], batch_size = 100, streaming_mode = true, auto_maximize_data = true // New option to automatically use all available data
515
+ } = args;
509
516
  // CRITICAL FIX: Ensure max_vocabulary_size is ALWAYS valid
510
517
  const max_vocabulary_size = Math.max(1000, args.max_vocabulary_size || 5000);
511
518
  try {
@@ -513,6 +520,91 @@ class ServiceNowMachineLearningMCP {
513
520
  if (!this.mlAPICheckComplete) {
514
521
  await this.checkMLAPIAvailability();
515
522
  }
523
+ // First, check how much data is available
524
+ let actualSampleSize = sample_size || 2000; // Default to 2000 if not specified
525
+ if (auto_maximize_data || !sample_size) {
526
+ this.logger.info('🔍 Checking available incident data for optimal training...');
527
+ try {
528
+ // Count available incidents that match our criteria
529
+ const countQuery = query || (intelligent_selection ?
530
+ 'categoryISNOTEMPTY^descriptionISNOTEMPTY^sys_created_onONLast 6 months' :
531
+ '');
532
+ // Use ServiceNow aggregate API to count records efficiently
533
+ let totalAvailable = 0;
534
+ try {
535
+ // Try using the stats API first (most efficient)
536
+ const statsResponse = await this.makeServiceNowRequest('/api/now/stats/incident', {
537
+ sysparm_query: countQuery,
538
+ sysparm_count: true
539
+ });
540
+ if (statsResponse.data?.result?.stats?.count) {
541
+ totalAvailable = parseInt(statsResponse.data.result.stats.count);
542
+ }
543
+ }
544
+ catch (statsError) {
545
+ // Fallback: Use aggregate API
546
+ try {
547
+ const aggResponse = await this.makeServiceNowRequest('/api/now/table/incident', {
548
+ sysparm_query: countQuery,
549
+ sysparm_count: true,
550
+ sysparm_limit: 1
551
+ });
552
+ // ServiceNow returns count in result
553
+ if (aggResponse?.result && Array.isArray(aggResponse.result)) {
554
+ // Even with limit 1, we can estimate based on typical data
555
+ totalAvailable = 2000; // Conservative estimate when count API fails
556
+ this.logger.info('Using conservative estimate of 2000 incidents');
557
+ }
558
+ }
559
+ catch (aggError) {
560
+ // Final fallback: Estimate based on a sample
561
+ const sampleResult = await this.client.searchRecords('incident', countQuery, 1000);
562
+ if (sampleResult.success && sampleResult.data?.result) {
563
+ totalAvailable = sampleResult.data.result.length >= 1000 ? 5000 : sampleResult.data.result.length;
564
+ this.logger.info(`Estimated ${totalAvailable} incidents available (sampled)`);
565
+ }
566
+ }
567
+ }
568
+ // Use a reasonable maximum (5000) to avoid memory issues
569
+ const maxRecommended = 5000;
570
+ const optimalSize = Math.min(totalAvailable, maxRecommended);
571
+ if (totalAvailable > 0) {
572
+ this.logger.info(`📊 Found ${totalAvailable} incidents available for training`);
573
+ if (sample_size && sample_size > totalAvailable) {
574
+ this.logger.warn(`⚠️ Requested ${sample_size} samples but only ${totalAvailable} available`);
575
+ }
576
+ // Use the optimal amount of data
577
+ actualSampleSize = sample_size ?
578
+ Math.min(sample_size, totalAvailable) :
579
+ optimalSize;
580
+ this.logger.info(`✅ Using ${actualSampleSize} incidents for training (optimal for this dataset)`);
581
+ // Provide recommendations based on data size
582
+ if (actualSampleSize < 500) {
583
+ this.logger.warn('⚠️ Less than 500 samples - model accuracy may be limited');
584
+ this.logger.info('💡 Recommendation: Gather more incident data for better results');
585
+ }
586
+ else if (actualSampleSize < 1000) {
587
+ this.logger.info('📈 Moderate dataset - expect 70-80% accuracy');
588
+ }
589
+ else if (actualSampleSize < 2000) {
590
+ this.logger.info('📈 Good dataset - expect 80-85% accuracy');
591
+ }
592
+ else {
593
+ this.logger.info('🎯 Excellent dataset - expect 85-95% accuracy');
594
+ }
595
+ }
596
+ else {
597
+ // Fallback to a default if count fails
598
+ actualSampleSize = sample_size || 1000;
599
+ this.logger.info(`Using default sample size: ${actualSampleSize}`);
600
+ }
601
+ }
602
+ catch (error) {
603
+ // If counting fails, use the provided or default size
604
+ actualSampleSize = sample_size || 1000;
605
+ this.logger.warn('Could not determine available data, using:', actualSampleSize);
606
+ }
607
+ }
516
608
  // If PI is available, try to use it first
517
609
  if (this.hasPI) {
518
610
  try {
@@ -553,7 +645,7 @@ class ServiceNowMachineLearningMCP {
553
645
  // 🔴 CRITICAL FIX: Use full sample_size, not artificially limited amount
554
646
  let incidents = [];
555
647
  try {
556
- incidents = await this.fetchIncidentData(sample_size, {
648
+ incidents = await this.fetchIncidentData(actualSampleSize, {
557
649
  query,
558
650
  intelligent_selection,
559
651
  focus_categories
@@ -1169,40 +1261,115 @@ class ServiceNowMachineLearningMCP {
1169
1261
  else if (!finalQuery) {
1170
1262
  finalQuery = 'ORDERBYDESCsys_created_on';
1171
1263
  }
1172
- // Use searchRecords for proper authentication handling
1173
- // 🔴 CRITICAL FIX: Use the actual limit parameter, not default of 10
1174
- const response = await this.client.searchRecords('incident', finalQuery, limit);
1175
- this.logger.info(`Attempting to fetch ${limit} incidents with query: ${finalQuery}`);
1176
- if (!response.success || !response.data?.result) {
1177
- throw new Error('Failed to fetch incident data. Ensure you have read access to the incident table.');
1178
- }
1179
- this.logger.info(`Fetched ${response.data.result.length} incidents for ML training (requested: ${limit})`);
1180
- // If intelligent selection, ensure we have a balanced dataset
1181
- if (intelligent_selection && response.data.result.length > 0) {
1182
- const categoryDistribution = {};
1183
- const priorityDistribution = {};
1184
- response.data.result.forEach((inc) => {
1185
- const category = inc.category || 'uncategorized';
1186
- const priority = inc.priority || '3';
1187
- categoryDistribution[category] = (categoryDistribution[category] || 0) + 1;
1188
- priorityDistribution[priority] = (priorityDistribution[priority] || 0) + 1;
1264
+ // Use smart ML data fetcher for batched retrieval to avoid token limits
1265
+ this.logger.info(`🤖 Using smart ML data fetcher for ${limit} incidents`);
1266
+ try {
1267
+ // Create a delegate for the operations MCP
1268
+ const operationsMCP = {
1269
+ handleTool: async (toolName, args) => {
1270
+ if (toolName === 'snow_query_table') {
1271
+ const { table, query: q, limit: l, fields, include_content } = args;
1272
+ // Use the client to fetch data
1273
+ const response = await this.client.searchRecords(table, q || '', l || 100);
1274
+ if (!response.success) {
1275
+ throw new Error(`Query failed: ${response.error}`);
1276
+ }
1277
+ const records = response.data?.result || [];
1278
+ // Filter fields if specified
1279
+ let filteredRecords = records;
1280
+ if (fields && fields.length > 0) {
1281
+ filteredRecords = records.map((record) => {
1282
+ const filtered = {};
1283
+ for (const field of fields) {
1284
+ if (field in record) {
1285
+ filtered[field] = record[field];
1286
+ }
1287
+ }
1288
+ return filtered;
1289
+ });
1290
+ }
1291
+ // Format response
1292
+ if (include_content) {
1293
+ return {
1294
+ content: [{
1295
+ type: 'text',
1296
+ text: JSON.stringify(filteredRecords, null, 2)
1297
+ }]
1298
+ };
1299
+ }
1300
+ else {
1301
+ return {
1302
+ content: [{
1303
+ type: 'text',
1304
+ text: `Found ${records.length} ${table} records matching query: "${q || 'all'}"`
1305
+ }]
1306
+ };
1307
+ }
1308
+ }
1309
+ throw new Error(`Unknown tool: ${toolName}`);
1310
+ }
1311
+ };
1312
+ const dataFetcher = new ml_data_fetcher_js_1.MLDataFetcher(operationsMCP);
1313
+ const result = await dataFetcher.smartFetch({
1314
+ table: 'incident',
1315
+ query: finalQuery,
1316
+ totalSamples: limit,
1317
+ batchSize: 50, // Small batches to avoid token limits
1318
+ discoverFields: true,
1319
+ includeContent: true
1189
1320
  });
1190
- this.logger.info('Data distribution:');
1191
- this.logger.info(`Categories: ${JSON.stringify(categoryDistribution)}`);
1192
- this.logger.info(`Priorities: ${JSON.stringify(priorityDistribution)}`);
1321
+ this.logger.info(`🎉 Fetched ${result.totalFetched} incidents in ${result.batchesProcessed} batches`);
1322
+ this.logger.info(`🔍 Used fields: ${result.fields.slice(0, 10).join(', ')}${result.fields.length > 10 ? '...' : ''}`);
1323
+ // If intelligent selection, log distribution
1324
+ if (intelligent_selection && result.data.length > 0) {
1325
+ const categoryDistribution = {};
1326
+ const priorityDistribution = {};
1327
+ result.data.forEach((inc) => {
1328
+ const category = inc.category || 'uncategorized';
1329
+ const priority = inc.priority || '3';
1330
+ categoryDistribution[category] = (categoryDistribution[category] || 0) + 1;
1331
+ priorityDistribution[priority] = (priorityDistribution[priority] || 0) + 1;
1332
+ });
1333
+ this.logger.info('Data distribution:');
1334
+ this.logger.info(`Categories: ${Object.keys(categoryDistribution).length} unique`);
1335
+ this.logger.info(`Priorities: ${JSON.stringify(priorityDistribution)}`);
1336
+ }
1337
+ // Map the fetched data to our IncidentData format
1338
+ return result.data.map((inc) => ({
1339
+ short_description: inc.short_description || '',
1340
+ description: inc.description || '',
1341
+ category: inc.category || 'uncategorized',
1342
+ subcategory: inc.subcategory || '',
1343
+ priority: parseInt(inc.priority) || 3,
1344
+ impact: parseInt(inc.impact) || 2,
1345
+ urgency: parseInt(inc.urgency) || 2,
1346
+ resolved: inc.state === '6' || inc.state === '7' || inc.active === 'false',
1347
+ resolution_time: inc.resolved_at && inc.sys_created_on ?
1348
+ (new Date(inc.resolved_at).getTime() - new Date(inc.sys_created_on).getTime()) / 1000 : undefined
1349
+ }));
1350
+ }
1351
+ catch (error) {
1352
+ // Fallback to direct fetch with smaller limit if smart fetcher fails
1353
+ this.logger.warn('Smart fetcher failed, using fallback with reduced limit:', error.message);
1354
+ const fallbackLimit = Math.min(limit, 100); // Limit to 100 to avoid token issues
1355
+ const response = await this.client.searchRecords('incident', finalQuery, fallbackLimit);
1356
+ if (!response.success || !response.data?.result) {
1357
+ throw new Error('Failed to fetch incident data. Ensure you have read access to the incident table.');
1358
+ }
1359
+ this.logger.info(`Fetched ${response.data.result.length} incidents (fallback mode, limited to ${fallbackLimit})`);
1360
+ return response.data.result.map((inc) => ({
1361
+ short_description: inc.short_description || '',
1362
+ description: inc.description || '',
1363
+ category: inc.category || 'uncategorized',
1364
+ subcategory: inc.subcategory || '',
1365
+ priority: parseInt(inc.priority) || 3,
1366
+ impact: parseInt(inc.impact) || 2,
1367
+ urgency: parseInt(inc.urgency) || 2,
1368
+ resolved: inc.resolved === 'true',
1369
+ resolution_time: inc.resolved_at && inc.sys_created_on ?
1370
+ (new Date(inc.resolved_at).getTime() - new Date(inc.sys_created_on).getTime()) / 1000 : undefined
1371
+ }));
1193
1372
  }
1194
- return response.data.result.map((inc) => ({
1195
- short_description: inc.short_description || '',
1196
- description: inc.description || '',
1197
- category: inc.category || 'uncategorized',
1198
- subcategory: inc.subcategory || '',
1199
- priority: parseInt(inc.priority) || 3,
1200
- impact: parseInt(inc.impact) || 2,
1201
- urgency: parseInt(inc.urgency) || 2,
1202
- resolved: inc.resolved === 'true',
1203
- resolution_time: inc.resolved_at && inc.sys_created_on ?
1204
- (new Date(inc.resolved_at).getTime() - new Date(inc.sys_created_on).getTime()) / 1000 : undefined
1205
- }));
1206
1373
  }
1207
1374
  async prepareIncidentData(incidents) {
1208
1375
  // Create tokenizer
@@ -1303,28 +1470,111 @@ class ServiceNowMachineLearningMCP {
1303
1470
  return undefined;
1304
1471
  }
1305
1472
  async fetchChangeData(limit, includeFailed) {
1306
- // Fetch real change data from ServiceNow - NO MOCK DATA
1307
- const queryParams = {
1308
- sysparm_limit: limit,
1309
- sysparm_query: includeFailed ? 'state!=cancelled' : 'state=closed^close_code=successful',
1310
- sysparm_fields: 'number,short_description,risk,impact,category,type,state,close_code,sys_created_on,closed_at'
1311
- };
1312
- const response = await this.makeServiceNowRequest('/api/now/table/change_request', queryParams);
1313
- if (!response || !response.result) {
1314
- throw new Error('Failed to fetch change data from ServiceNow. ' +
1315
- 'Ensure you have permission to read change_request table.');
1316
- }
1317
- return response.result.map((change) => ({
1318
- number: change.number,
1319
- description: change.short_description || '',
1320
- risk: change.risk || 'moderate',
1321
- impact: parseInt(change.impact) || 2,
1322
- category: change.category || 'standard',
1323
- type: change.type || 'standard',
1324
- successful: change.close_code === 'successful',
1325
- duration: change.closed_at && change.sys_created_on ?
1326
- (new Date(change.closed_at).getTime() - new Date(change.sys_created_on).getTime()) / 1000 : 0
1327
- }));
1473
+ // Use smart data fetcher for change requests to avoid token limits
1474
+ this.logger.info(`🤖 Using smart ML data fetcher for ${limit} change requests`);
1475
+ const query = includeFailed ? 'state!=cancelled' : 'state=closed^close_code=successful';
1476
+ try {
1477
+ // Create a delegate for the operations MCP
1478
+ const operationsMCP = {
1479
+ handleTool: async (toolName, args) => {
1480
+ if (toolName === 'snow_query_table') {
1481
+ const { table, query: q, limit: l, fields, include_content } = args;
1482
+ // Use the client to fetch data
1483
+ const response = await this.client.searchRecords(table, q || '', l || 100);
1484
+ if (!response.success) {
1485
+ throw new Error(`Query failed: ${response.error}`);
1486
+ }
1487
+ const records = response.data?.result || [];
1488
+ // Filter fields if specified
1489
+ let filteredRecords = records;
1490
+ if (fields && fields.length > 0) {
1491
+ filteredRecords = records.map((record) => {
1492
+ const filtered = {};
1493
+ for (const field of fields) {
1494
+ if (field in record) {
1495
+ filtered[field] = record[field];
1496
+ }
1497
+ }
1498
+ return filtered;
1499
+ });
1500
+ }
1501
+ // Format response
1502
+ if (include_content) {
1503
+ return {
1504
+ content: [{
1505
+ type: 'text',
1506
+ text: JSON.stringify(filteredRecords, null, 2)
1507
+ }]
1508
+ };
1509
+ }
1510
+ else {
1511
+ return {
1512
+ content: [{
1513
+ type: 'text',
1514
+ text: `Found ${records.length} ${table} records matching query: "${q || 'all'}"`
1515
+ }]
1516
+ };
1517
+ }
1518
+ }
1519
+ throw new Error(`Unknown tool: ${toolName}`);
1520
+ }
1521
+ };
1522
+ const dataFetcher = new ml_data_fetcher_js_1.MLDataFetcher(operationsMCP);
1523
+ const result = await dataFetcher.smartFetch({
1524
+ table: 'change_request',
1525
+ query,
1526
+ totalSamples: limit,
1527
+ batchSize: 50, // Small batches to avoid token limits
1528
+ fields: ['number', 'short_description', 'risk', 'impact', 'category', 'type',
1529
+ 'state', 'close_code', 'sys_created_on', 'closed_at', 'start_date', 'end_date',
1530
+ 'assignment_group', 'approval', 'test_plan', 'backout_plan', 'rollback_tested'],
1531
+ includeContent: true
1532
+ });
1533
+ this.logger.info(`🎉 Fetched ${result.totalFetched} change requests in ${result.batchesProcessed} batches`);
1534
+ return result.data.map((change) => ({
1535
+ short_description: change.short_description || '',
1536
+ risk: change.risk || 'moderate',
1537
+ category: change.category || 'standard',
1538
+ type: change.type || 'standard',
1539
+ planned_start: change.start_date ? new Date(change.start_date) : new Date(),
1540
+ planned_end: change.end_date ? new Date(change.end_date) : new Date(),
1541
+ assignment_group: change.assignment_group?.display_value || change.assignment_group || '',
1542
+ approval_count: parseInt(change.approval) || 0,
1543
+ test_plan: change.test_plan === 'true' || false,
1544
+ backout_plan: change.backout_plan === 'true' || false,
1545
+ rollback_tested: change.rollback_tested === 'true' || false,
1546
+ implementation_success: change.close_code === 'successful'
1547
+ }));
1548
+ }
1549
+ catch (error) {
1550
+ // Fallback to direct API call with smaller limit
1551
+ this.logger.warn('Smart fetcher failed, using fallback:', error.message);
1552
+ const fallbackLimit = Math.min(limit, 100);
1553
+ const queryParams = {
1554
+ sysparm_limit: fallbackLimit,
1555
+ sysparm_query: query,
1556
+ sysparm_fields: 'number,short_description,risk,impact,category,type,state,close_code,sys_created_on,closed_at'
1557
+ };
1558
+ const response = await this.makeServiceNowRequest('/api/now/table/change_request', queryParams);
1559
+ if (!response || !response.result) {
1560
+ throw new Error('Failed to fetch change data from ServiceNow. ' +
1561
+ 'Ensure you have permission to read change_request table.');
1562
+ }
1563
+ return response.result.map((change) => ({
1564
+ short_description: change.short_description || '',
1565
+ risk: change.risk || 'moderate',
1566
+ category: change.category || 'standard',
1567
+ type: change.type || 'standard',
1568
+ planned_start: change.start_date ? new Date(change.start_date) : new Date(),
1569
+ planned_end: change.end_date ? new Date(change.end_date) : new Date(),
1570
+ assignment_group: change.assignment_group?.display_value || change.assignment_group || '',
1571
+ approval_count: parseInt(change.approval) || 0,
1572
+ test_plan: change.test_plan === 'true' || false,
1573
+ backout_plan: change.backout_plan === 'true' || false,
1574
+ rollback_tested: change.rollback_tested === 'true' || false,
1575
+ implementation_success: change.close_code === 'successful'
1576
+ }));
1577
+ }
1328
1578
  }
1329
1579
  async prepareChangeData(changes) {
1330
1580
  // Implement change data preparation
@@ -11,5 +11,75 @@
11
11
  * - Knowledge base integration
12
12
  * - Predictive analytics
13
13
  */
14
- export {};
14
+ declare class ServiceNowOperationsMCP {
15
+ private server;
16
+ private client;
17
+ constructor();
18
+ private setupToolHandlers;
19
+ private handleUniversalQuery;
20
+ private detectCommonFields;
21
+ private handleQueryIncidents;
22
+ private handleAnalyzeIncident;
23
+ private handleAutoResolveIncident;
24
+ private handleQueryRequests;
25
+ private handleQueryProblems;
26
+ private handleCMDBSearch;
27
+ private handleUserLookup;
28
+ private handleOperationalMetrics;
29
+ private handlePatternAnalysis;
30
+ private handleKnowledgeSearch;
31
+ private handlePredictiveAnalysis;
32
+ private processNaturalLanguageQuery;
33
+ private analyzeIncidents;
34
+ private getIncidentDetails;
35
+ private performIncidentAnalysis;
36
+ private generateRootCauseAnalysis;
37
+ private generateAutomatedActions;
38
+ private generateResolutionActions;
39
+ private executeResolutionActions;
40
+ private getRequestItems;
41
+ private getRelatedIncidents;
42
+ private getCIRelationships;
43
+ private getUserDetails;
44
+ private calculateOperationalMetrics;
45
+ private getDateFilter;
46
+ private analyzePatterns;
47
+ private analyzeIncidentPatterns;
48
+ private analyzeRequestTrends;
49
+ private analyzeProblemRootCauses;
50
+ private analyzeUserBehavior;
51
+ private calculateKnowledgeRelevance;
52
+ private performPredictiveAnalysis;
53
+ private analyzeIncidentTrends;
54
+ private analyzeSystemHealthTrends;
55
+ private analyzeResourceTrends;
56
+ private analyzeUserImpactTrends;
57
+ private predictIncidentVolume;
58
+ private predictSystemFailure;
59
+ private predictResourceExhaustion;
60
+ private predictUserImpact;
61
+ private handleCatalogItemManager;
62
+ private createCatalogVariable;
63
+ private mapVariableType;
64
+ private handleCatalogItemSearch;
65
+ private generateSearchVariations;
66
+ private generateSearchSuggestions;
67
+ private shouldExcludeItem;
68
+ private handleCleanupTestArtifacts;
69
+ private cleanupTestCatalogItems;
70
+ private cleanupTestUsers;
71
+ private cleanupTestRequests;
72
+ private getServiceNowUrl;
73
+ private handleCreateUserGroup;
74
+ private handleCreateUser;
75
+ private handleAssignUserToGroup;
76
+ private handleRemoveUserFromGroup;
77
+ private handleListGroupMembers;
78
+ private findUserBySysIdOrUsername;
79
+ private findGroupBySysIdOrName;
80
+ private findDepartment;
81
+ private findLocation;
82
+ run(): Promise<void>;
83
+ }
84
+ export { ServiceNowOperationsMCP };
15
85
  //# sourceMappingURL=servicenow-operations-mcp.d.ts.map
@@ -13,6 +13,7 @@
13
13
  * - Predictive analytics
14
14
  */
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.ServiceNowOperationsMCP = void 0;
16
17
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
17
18
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
18
19
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
@@ -131,7 +132,7 @@ class ServiceNowOperationsMCP {
131
132
  // 🎯 UNIVERSAL TABLE QUERY - Works for ANY ServiceNow table!
132
133
  {
133
134
  name: 'snow_query_table',
134
- description: '🚀 Universal high-performance query tool for ANY ServiceNow table - optimized for memory efficiency',
135
+ description: '🚀 Universal high-performance query tool for ANY ServiceNow table - optimized for memory efficiency. SMART DEFAULTS: 1000 records (5000 for ML training contexts)',
135
136
  inputSchema: {
136
137
  type: 'object',
137
138
  properties: {
@@ -146,8 +147,8 @@ class ServiceNowOperationsMCP {
146
147
  },
147
148
  limit: {
148
149
  type: 'number',
149
- description: 'Maximum number of results (default: 10)',
150
- default: 10
150
+ description: '🎯 Maximum number of results. SMART DEFAULTS: 1000 (normal queries), 5000 (ML training contexts). Set higher for large ML datasets (10000+)',
151
+ default: 1000
151
152
  },
152
153
  include_content: {
153
154
  type: 'boolean',
@@ -828,8 +829,37 @@ class ServiceNowOperationsMCP {
828
829
  });
829
830
  }
830
831
  async handleUniversalQuery(args) {
831
- const { table, query, limit = 10, include_content = false, fields, include_display_values = false, group_by, order_by } = args;
832
- logger_js_1.logger.info(`Universal query on table '${table}' with: ${query} (include_content: ${include_content})`);
832
+ // 🎯 SMART DEFAULT LIMITS - Context-aware for ML training
833
+ const determineSmartLimit = (providedLimit, table, query, includeContent) => {
834
+ if (providedLimit !== undefined)
835
+ return providedLimit; // User explicitly set limit
836
+ // ML Training context detection
837
+ const isMLContext = query?.toLowerCase().includes('train') ||
838
+ query?.toLowerCase().includes('ml') ||
839
+ table?.toLowerCase().includes('train') ||
840
+ (includeContent && table === 'incident'); // ML often needs incident content
841
+ if (isMLContext) {
842
+ logger_js_1.logger.info(`🧠 ML context detected - using ML-optimized limit: 5000`);
843
+ return 5000; // ML training needs more data
844
+ }
845
+ // Count-only queries can handle more records efficiently
846
+ if (!includeContent) {
847
+ return 2000; // Count queries are memory-efficient
848
+ }
849
+ // Normal content queries
850
+ return 1000; // Balanced default for content queries
851
+ };
852
+ const { table, query, include_content = false, fields, include_display_values = false, group_by, order_by } = args;
853
+ // Apply smart limit logic
854
+ const limit = determineSmartLimit(args.limit, table, query, include_content || !!fields);
855
+ // 🚨 ML Training Warning for low limits
856
+ const isMLTrainingContext = query?.toLowerCase().includes('train') ||
857
+ query?.toLowerCase().includes('ml') ||
858
+ args.limit !== undefined && args.limit < 1000;
859
+ if (isMLTrainingContext && limit < 1000) {
860
+ logger_js_1.logger.warn(`⚠️ ML Training detected with low limit (${limit}). Consider setting limit=5000+ for better training data!`);
861
+ }
862
+ logger_js_1.logger.info(`Universal query on table '${table}' with: ${query} (limit: ${limit}, include_content: ${include_content})`);
833
863
  try {
834
864
  // Convert natural language to ServiceNow query if needed
835
865
  const processedQuery = this.processNaturalLanguageQuery(query, table);
@@ -3140,10 +3170,13 @@ class ServiceNowOperationsMCP {
3140
3170
  logger_js_1.logger.info('ServiceNow Operations MCP Server started');
3141
3171
  }
3142
3172
  }
3143
- // Start the server
3144
- const server = new ServiceNowOperationsMCP();
3145
- server.run().catch((error) => {
3146
- logger_js_1.logger.error('Failed to start ServiceNow Operations MCP server:', error);
3147
- process.exit(1);
3148
- });
3173
+ exports.ServiceNowOperationsMCP = ServiceNowOperationsMCP;
3174
+ // Start the server only if run directly
3175
+ if (require.main === module) {
3176
+ const server = new ServiceNowOperationsMCP();
3177
+ server.run().catch((error) => {
3178
+ logger_js_1.logger.error('Failed to start ServiceNow Operations MCP server:', error);
3179
+ process.exit(1);
3180
+ });
3181
+ }
3149
3182
  //# sourceMappingURL=servicenow-operations-mcp.js.map
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Test Smart Limits for snow_query_table
4
+ *
5
+ * This script validates that the smart default limit system works correctly:
6
+ * - ML context: 5000 default
7
+ * - Count-only: 2000 default
8
+ * - Normal content: 1000 default
9
+ * - Explicit limits: Always respected
10
+ */
11
+ declare function testSmartLimits(): Promise<void>;
12
+ export { testSmartLimits };
13
+ //# sourceMappingURL=test-smart-limits.d.ts.map