servicenow-mcp-server 2.1.0

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.
Files changed (52) hide show
  1. package/.claude/settings.local.json +70 -0
  2. package/CLAUDE.md +777 -0
  3. package/LICENSE +21 -0
  4. package/README.md +562 -0
  5. package/assets/logo.svg +385 -0
  6. package/config/servicenow-instances.json.example +28 -0
  7. package/docs/403_TROUBLESHOOTING.md +329 -0
  8. package/docs/API_REFERENCE.md +1142 -0
  9. package/docs/APPLICATION_SCOPE_VALIDATION.md +681 -0
  10. package/docs/CLAUDE_DESKTOP_SETUP.md +373 -0
  11. package/docs/CONVENIENCE_TOOLS.md +601 -0
  12. package/docs/CONVENIENCE_TOOLS_SUMMARY.md +371 -0
  13. package/docs/FLOW_DESIGNER_GUIDE.md +1021 -0
  14. package/docs/IMPLEMENTATION_COMPLETE.md +165 -0
  15. package/docs/INSTANCE_SWITCHING_GUIDE.md +219 -0
  16. package/docs/MULTI_INSTANCE_CONFIGURATION.md +185 -0
  17. package/docs/NATURAL_LANGUAGE_SEARCH_IMPLEMENTATION.md +221 -0
  18. package/docs/PUPPETEER_INTEGRATION_PROPOSAL.md +1322 -0
  19. package/docs/QUICK_REFERENCE.md +395 -0
  20. package/docs/README.md +75 -0
  21. package/docs/RESOURCES_ARCHITECTURE.md +392 -0
  22. package/docs/RESOURCES_IMPLEMENTATION.md +276 -0
  23. package/docs/RESOURCES_SUMMARY.md +104 -0
  24. package/docs/SETUP_GUIDE.md +104 -0
  25. package/docs/UI_OPERATIONS_ARCHITECTURE.md +1219 -0
  26. package/docs/UI_OPERATIONS_DECISION_MATRIX.md +542 -0
  27. package/docs/UI_OPERATIONS_SUMMARY.md +507 -0
  28. package/docs/UPDATE_SET_VALIDATION.md +598 -0
  29. package/docs/UPDATE_SET_VALIDATION_SUMMARY.md +209 -0
  30. package/docs/VALIDATION_SUMMARY.md +479 -0
  31. package/jest.config.js +24 -0
  32. package/package.json +61 -0
  33. package/scripts/background_script_2025-09-29T20-19-35-101Z.js +23 -0
  34. package/scripts/link_ui_policy_actions_2025-09-29T20-17-15-218Z.js +90 -0
  35. package/scripts/set_update_set_Integration_Governance_Framework_2025-09-29T19-47-06-790Z.js +30 -0
  36. package/scripts/set_update_set_Integration_Governance_Framework_2025-09-29T19-59-33-152Z.js +30 -0
  37. package/scripts/set_update_set_current_2025-09-29T20-16-59-675Z.js +24 -0
  38. package/scripts/test_sys_dictionary_403.js +85 -0
  39. package/setup/setup-report.json +5313 -0
  40. package/src/config/comprehensive-table-definitions.json +2575 -0
  41. package/src/config/instance-config.json +4693 -0
  42. package/src/config/prompts.md +59 -0
  43. package/src/config/table-definitions.json +4681 -0
  44. package/src/config-manager.js +146 -0
  45. package/src/mcp-server-consolidated.js +2894 -0
  46. package/src/natural-language.js +472 -0
  47. package/src/resources.js +326 -0
  48. package/src/script-sync.js +428 -0
  49. package/src/server.js +125 -0
  50. package/src/servicenow-client.js +1625 -0
  51. package/src/stdio-server.js +52 -0
  52. package/start-mcp.sh +7 -0
@@ -0,0 +1,472 @@
1
+ /**
2
+ * ServiceNow MCP Server - Natural Language Query Processing
3
+ *
4
+ * Copyright (c) 2025 Happy Technologies LLC
5
+ * Licensed under the MIT License - see LICENSE file for details
6
+ *
7
+ * Natural Language to ServiceNow Encoded Query Parser
8
+ * Converts human-readable queries into ServiceNow encoded query strings.
9
+ * Uses pattern matching for reliability and speed.
10
+ *
11
+ * @module natural-language
12
+ */
13
+
14
+ /**
15
+ * Table-specific state mappings
16
+ */
17
+ const STATE_MAPPINGS = {
18
+ incident: {
19
+ 'new': '1',
20
+ 'in progress': '2',
21
+ 'on hold': '3',
22
+ 'resolved': '6',
23
+ 'closed': '7',
24
+ 'canceled': '8',
25
+ 'open': '1^ORstate=2^ORstate=3', // New, In Progress, or On Hold
26
+ 'active': 'active=true'
27
+ },
28
+ change_request: {
29
+ 'new': '-5',
30
+ 'assess': '-4',
31
+ 'authorize': '-3',
32
+ 'scheduled': '-2',
33
+ 'implement': '-1',
34
+ 'review': '0',
35
+ 'closed': '3',
36
+ 'canceled': '4',
37
+ 'open': 'state<0', // Negative states are open
38
+ 'active': 'active=true'
39
+ },
40
+ problem: {
41
+ 'new': '1',
42
+ 'assessed': '2',
43
+ 'root cause analysis': '3',
44
+ 'fix in progress': '4',
45
+ 'resolved': '6',
46
+ 'closed': '7',
47
+ 'open': '1^ORstate=2^ORstate=3^ORstate=4',
48
+ 'active': 'active=true'
49
+ }
50
+ };
51
+
52
+ /**
53
+ * Priority mappings (consistent across tables)
54
+ */
55
+ const PRIORITY_MAPPINGS = {
56
+ 'critical': '1',
57
+ 'high': '2',
58
+ 'moderate': '3',
59
+ 'low': '4',
60
+ 'planning': '5',
61
+ 'p1': '1',
62
+ 'p2': '2',
63
+ 'p3': '3',
64
+ 'p4': '4',
65
+ 'p5': '5'
66
+ };
67
+
68
+ /**
69
+ * Impact mappings
70
+ */
71
+ const IMPACT_MAPPINGS = {
72
+ 'high': '1',
73
+ 'medium': '2',
74
+ 'low': '3'
75
+ };
76
+
77
+ /**
78
+ * Urgency mappings
79
+ */
80
+ const URGENCY_MAPPINGS = {
81
+ 'high': '1',
82
+ 'medium': '2',
83
+ 'low': '3'
84
+ };
85
+
86
+ /**
87
+ * Pattern definitions for natural language parsing
88
+ * Each pattern has: regex, parser function, priority (higher = check first)
89
+ */
90
+ const PATTERNS = [
91
+ // Priority patterns
92
+ {
93
+ regex: /\b(critical|high|moderate|low|planning)\s+priority\b/i,
94
+ priority: 10,
95
+ parser: (match) => {
96
+ const level = match[1].toLowerCase();
97
+ return `priority=${PRIORITY_MAPPINGS[level]}`;
98
+ }
99
+ },
100
+ {
101
+ regex: /\bp([1-5])\b/i,
102
+ priority: 10,
103
+ parser: (match) => {
104
+ return `priority=${match[1]}`;
105
+ }
106
+ },
107
+ {
108
+ regex: /\bpriority\s+(critical|high|moderate|low|planning|[1-5])\b/i,
109
+ priority: 10,
110
+ parser: (match) => {
111
+ const level = match[1].toLowerCase();
112
+ return `priority=${PRIORITY_MAPPINGS[level] || match[1]}`;
113
+ }
114
+ },
115
+
116
+ // Impact patterns
117
+ {
118
+ regex: /\b(high|medium|low)\s+impact\b/i,
119
+ priority: 9,
120
+ parser: (match) => {
121
+ const level = match[1].toLowerCase();
122
+ return `impact=${IMPACT_MAPPINGS[level]}`;
123
+ }
124
+ },
125
+
126
+ // Urgency patterns
127
+ {
128
+ regex: /\b(high|medium|low)\s+urgency\b/i,
129
+ priority: 9,
130
+ parser: (match) => {
131
+ const level = match[1].toLowerCase();
132
+ return `urgency=${URGENCY_MAPPINGS[level]}`;
133
+ }
134
+ },
135
+
136
+ // Assignment patterns
137
+ {
138
+ regex: /\b(assigned\s+to\s+me|my\s+(incidents|problems|changes|tickets))\b/i,
139
+ priority: 15,
140
+ parser: () => 'assigned_to=javascript:gs.getUserID()'
141
+ },
142
+ {
143
+ regex: /\bunassigned\b/i,
144
+ priority: 15,
145
+ parser: () => 'assigned_toISEMPTY'
146
+ },
147
+ {
148
+ regex: /\bassigned\s+to\s+([a-zA-Z\s]+?)(?:\s+(?:and|or|with|created|opened|updated)|\s*$)/i,
149
+ priority: 14,
150
+ parser: (match) => {
151
+ const userName = match[1].trim();
152
+ // Note: This creates a LIKE query - could be enhanced with user lookup
153
+ return `assigned_to.nameLIKE${userName}`;
154
+ }
155
+ },
156
+
157
+ // Date patterns - relative
158
+ {
159
+ regex: /\b(created|opened|updated|modified|closed)\s+(today|yesterday)\b/i,
160
+ priority: 12,
161
+ parser: (match) => {
162
+ const field = match[1].toLowerCase() === 'opened' ? 'sys_created_on' : `${match[1].toLowerCase()}_on`;
163
+ const days = match[2].toLowerCase() === 'today' ? 0 : 1;
164
+ return `${field}>javascript:gs.daysAgoStart(${days})`;
165
+ }
166
+ },
167
+ {
168
+ regex: /\b(created|opened|updated|modified|closed)\s+(?:in\s+)?(?:the\s+)?last\s+(\d+)\s+(days?|weeks?|months?)\b/i,
169
+ priority: 12,
170
+ parser: (match) => {
171
+ const field = match[1].toLowerCase() === 'opened' ? 'sys_created_on' : `${match[1].toLowerCase()}_on`;
172
+ const amount = parseInt(match[2]);
173
+ const unit = match[3].toLowerCase();
174
+
175
+ let days = amount;
176
+ if (unit.startsWith('week')) days = amount * 7;
177
+ if (unit.startsWith('month')) days = amount * 30;
178
+
179
+ return `${field}>javascript:gs.daysAgo(${days})`;
180
+ }
181
+ },
182
+ {
183
+ regex: /\b(recent|recently\s+created|new)\b/i,
184
+ priority: 8,
185
+ parser: () => 'sys_created_on>javascript:gs.daysAgo(7)'
186
+ },
187
+ {
188
+ regex: /\b(created|opened|updated|modified|closed)\s+before\s+([a-zA-Z]+\s+\d{1,2}(?:,?\s+\d{4})?)\b/i,
189
+ priority: 11,
190
+ parser: (match) => {
191
+ const field = match[1].toLowerCase() === 'opened' ? 'sys_created_on' : `${match[1].toLowerCase()}_on`;
192
+ // Simple date parsing - could be enhanced
193
+ return `${field}<${match[2]}`;
194
+ }
195
+ },
196
+ {
197
+ regex: /\b(created|opened|updated|modified|closed)\s+after\s+([a-zA-Z]+\s+\d{1,2}(?:,?\s+\d{4})?)\b/i,
198
+ priority: 11,
199
+ parser: (match) => {
200
+ const field = match[1].toLowerCase() === 'opened' ? 'sys_created_on' : `${match[1].toLowerCase()}_on`;
201
+ return `${field}>${match[2]}`;
202
+ }
203
+ },
204
+
205
+ // State patterns (table-dependent)
206
+ {
207
+ regex: /\b(new|open|active|in\s+progress|on\s+hold|resolved|closed|canceled)\b/i,
208
+ priority: 7,
209
+ parser: (match, table) => {
210
+ const state = match[1].toLowerCase().trim();
211
+ const mapping = STATE_MAPPINGS[table] || STATE_MAPPINGS.incident;
212
+ return mapping[state] || `state=${state}`;
213
+ }
214
+ },
215
+
216
+ // Content search patterns
217
+ {
218
+ regex: /\b(?:about|containing|with|includes?)\s+["']?([a-zA-Z0-9\s]+?)["']?(?:\s+(?:and|or|in|with|created|opened|assigned)|\s*$)/i,
219
+ priority: 5,
220
+ parser: (match) => {
221
+ const searchTerm = match[1].trim();
222
+ return `short_descriptionLIKE${searchTerm}^ORdescriptionLIKE${searchTerm}`;
223
+ }
224
+ },
225
+ {
226
+ regex: /\bdescription\s+(?:contains|includes)\s+["']?([^"']+?)["']?(?:\s+(?:and|or)|\s*$)/i,
227
+ priority: 6,
228
+ parser: (match) => {
229
+ const searchTerm = match[1].trim();
230
+ return `descriptionLIKE${searchTerm}`;
231
+ }
232
+ },
233
+
234
+ // Number patterns
235
+ {
236
+ regex: /\bnumber\s+(?:is|=|equals?)\s+([A-Z]{3}\d{7})\b/i,
237
+ priority: 20,
238
+ parser: (match) => `number=${match[1]}`
239
+ },
240
+
241
+ // Caller patterns
242
+ {
243
+ regex: /\bcaller\s+(?:is|=)\s+([a-zA-Z\s]+?)(?:\s+(?:and|or|with)|\s*$)/i,
244
+ priority: 10,
245
+ parser: (match) => {
246
+ const callerName = match[1].trim();
247
+ return `caller_id.nameLIKE${callerName}`;
248
+ }
249
+ },
250
+
251
+ // Category patterns
252
+ {
253
+ regex: /\bcategory\s+(?:is|=)\s+([a-zA-Z\s]+?)(?:\s+(?:and|or|with)|\s*$)/i,
254
+ priority: 10,
255
+ parser: (match) => {
256
+ const category = match[1].trim();
257
+ return `categoryLIKE${category}`;
258
+ }
259
+ },
260
+
261
+ // Assignment group patterns
262
+ {
263
+ regex: /\bassignment\s+group\s+(?:is|=)\s+([a-zA-Z\s]+?)(?:\s+(?:and|or|with)|\s*$)/i,
264
+ priority: 10,
265
+ parser: (match) => {
266
+ const group = match[1].trim();
267
+ return `assignment_group.nameLIKE${group}`;
268
+ }
269
+ }
270
+ ];
271
+
272
+ /**
273
+ * Parse natural language query into ServiceNow encoded query
274
+ *
275
+ * @param {string} query - Natural language query
276
+ * @param {string} table - Target ServiceNow table (default: 'incident')
277
+ * @returns {object} - { encodedQuery, matchedPatterns, unmatchedText, suggestions }
278
+ */
279
+ export function parseNaturalLanguage(query, table = 'incident') {
280
+ if (!query || typeof query !== 'string') {
281
+ return {
282
+ encodedQuery: '',
283
+ matchedPatterns: [],
284
+ unmatchedText: query,
285
+ suggestions: ['Please provide a valid query string']
286
+ };
287
+ }
288
+
289
+ const originalQuery = query;
290
+ let remainingQuery = query;
291
+ const conditions = [];
292
+ const matchedPatterns = [];
293
+ const suggestions = [];
294
+
295
+ // Sort patterns by priority (highest first)
296
+ const sortedPatterns = [...PATTERNS].sort((a, b) => b.priority - a.priority);
297
+
298
+ // Process each pattern
299
+ for (const pattern of sortedPatterns) {
300
+ const match = remainingQuery.match(pattern.regex);
301
+ if (match) {
302
+ try {
303
+ const condition = pattern.parser(match, table);
304
+ conditions.push(condition);
305
+ matchedPatterns.push({
306
+ pattern: pattern.regex.toString(),
307
+ matched: match[0],
308
+ condition
309
+ });
310
+
311
+ // Remove matched text from remaining query
312
+ remainingQuery = remainingQuery.replace(match[0], ' ').trim();
313
+ } catch (error) {
314
+ console.error(`Pattern parsing error:`, error);
315
+ }
316
+ }
317
+ }
318
+
319
+ // Check for logical operators in remaining text
320
+ const hasAnd = /\band\b/i.test(remainingQuery);
321
+ const hasOr = /\bor\b/i.test(remainingQuery);
322
+
323
+ // Build encoded query
324
+ let encodedQuery = '';
325
+ if (conditions.length > 0) {
326
+ // Use OR if query contains "or", otherwise use AND (default)
327
+ const operator = hasOr ? '^OR' : '^';
328
+ encodedQuery = conditions.join(operator);
329
+ }
330
+
331
+ // Clean up remaining query
332
+ remainingQuery = remainingQuery
333
+ .replace(/\b(and|or|with|in|the|a|an)\b/gi, ' ')
334
+ .replace(/\s+/g, ' ')
335
+ .trim();
336
+
337
+ // Generate suggestions for unmatched text
338
+ if (remainingQuery.length > 3) {
339
+ suggestions.push(`Unrecognized: "${remainingQuery}"`);
340
+ suggestions.push('Try using encoded query format: field=value^field2=value2');
341
+ suggestions.push('Supported patterns: priority (P1-P5), state (new/open/closed), assigned to me/unassigned, recent, dates');
342
+ }
343
+
344
+ // If no patterns matched, return original query (might be encoded query already)
345
+ if (conditions.length === 0) {
346
+ // Check if it looks like an encoded query already
347
+ if (/[=^]/.test(originalQuery)) {
348
+ return {
349
+ encodedQuery: originalQuery,
350
+ matchedPatterns: [],
351
+ unmatchedText: '',
352
+ suggestions: ['Using query as-is (appears to be encoded query format)']
353
+ };
354
+ }
355
+
356
+ return {
357
+ encodedQuery: '',
358
+ matchedPatterns: [],
359
+ unmatchedText: originalQuery,
360
+ suggestions: [
361
+ 'No patterns matched. Supported patterns:',
362
+ '- Priority: "high priority", "P1", "priority 2"',
363
+ '- Assignment: "assigned to me", "unassigned", "assigned to John"',
364
+ '- State: "new", "open", "closed", "in progress"',
365
+ '- Dates: "created today", "last 7 days", "recent"',
366
+ '- Content: "about SAP", "containing error"',
367
+ 'Or use ServiceNow encoded query format: field=value^field2=value2'
368
+ ]
369
+ };
370
+ }
371
+
372
+ return {
373
+ encodedQuery,
374
+ matchedPatterns,
375
+ unmatchedText: remainingQuery,
376
+ suggestions: suggestions.length > 0 ? suggestions : ['Query parsed successfully']
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Test the natural language parser with example queries
382
+ *
383
+ * @param {string} table - Table name to test against
384
+ * @returns {Array} - Array of test results
385
+ */
386
+ export function testParser(table = 'incident') {
387
+ const testQueries = [
388
+ 'find all P1 incidents',
389
+ 'show recent problems assigned to me',
390
+ 'high priority changes created last week',
391
+ 'open incidents about SAP',
392
+ 'unassigned P2 incidents',
393
+ 'incidents created today with high priority',
394
+ 'closed problems assigned to John Smith',
395
+ 'critical incidents opened in the last 30 days',
396
+ 'show me my active tickets',
397
+ 'new incidents with high impact and high urgency',
398
+ 'incidents containing database error',
399
+ 'P1 incidents assigned to Network Team',
400
+ 'incidents created after January 1',
401
+ 'resolved incidents about authentication'
402
+ ];
403
+
404
+ return testQueries.map(query => ({
405
+ query,
406
+ result: parseNaturalLanguage(query, table)
407
+ }));
408
+ }
409
+
410
+ /**
411
+ * Get supported patterns documentation
412
+ *
413
+ * @returns {object} - Documentation of supported patterns
414
+ */
415
+ export function getSupportedPatterns() {
416
+ return {
417
+ priority: {
418
+ examples: ['high priority', 'P1', 'priority 2', 'critical priority'],
419
+ encodedQuery: 'priority=1'
420
+ },
421
+ assignment: {
422
+ examples: ['assigned to me', 'unassigned', 'assigned to John Smith', 'my incidents'],
423
+ encodedQuery: 'assigned_to=javascript:gs.getUserID() or assigned_toISEMPTY'
424
+ },
425
+ state: {
426
+ examples: ['new', 'open', 'active', 'in progress', 'closed', 'resolved'],
427
+ encodedQuery: 'state=1 (varies by table)'
428
+ },
429
+ dates: {
430
+ examples: ['created today', 'last 7 days', 'recent', 'opened yesterday', 'updated last week'],
431
+ encodedQuery: 'sys_created_on>javascript:gs.daysAgo(7)'
432
+ },
433
+ content: {
434
+ examples: ['about SAP', 'containing error', 'description contains authentication'],
435
+ encodedQuery: 'short_descriptionLIKESAP'
436
+ },
437
+ impact: {
438
+ examples: ['high impact', 'medium impact', 'low impact'],
439
+ encodedQuery: 'impact=1'
440
+ },
441
+ urgency: {
442
+ examples: ['high urgency', 'medium urgency', 'low urgency'],
443
+ encodedQuery: 'urgency=1'
444
+ },
445
+ number: {
446
+ examples: ['number is INC0012345'],
447
+ encodedQuery: 'number=INC0012345'
448
+ },
449
+ caller: {
450
+ examples: ['caller is John Smith'],
451
+ encodedQuery: 'caller_id.nameLIKEJohn Smith'
452
+ },
453
+ category: {
454
+ examples: ['category is Software'],
455
+ encodedQuery: 'categoryLIKESoftware'
456
+ },
457
+ assignmentGroup: {
458
+ examples: ['assignment group is Network Team'],
459
+ encodedQuery: 'assignment_group.nameLIKENetwork Team'
460
+ },
461
+ combining: {
462
+ examples: ['high priority and assigned to me', 'P1 or P2 incidents', 'recent and unassigned'],
463
+ encodedQuery: 'Use "and" for ^ operator, "or" for ^OR operator'
464
+ }
465
+ };
466
+ }
467
+
468
+ export default {
469
+ parseNaturalLanguage,
470
+ testParser,
471
+ getSupportedPatterns
472
+ };