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,2894 @@
1
+ /**
2
+ * ServiceNow MCP Server - MCP Tool Registration
3
+ *
4
+ * Copyright (c) 2025 Happy Technologies LLC
5
+ * Licensed under the MIT License - see LICENSE file for details
6
+ */
7
+
8
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
+ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
10
+ import fs from 'fs/promises';
11
+ import path from 'path';
12
+ import { configManager } from './config-manager.js';
13
+ import { syncScript, syncAllScripts, SCRIPT_TYPES } from './script-sync.js';
14
+ import { parseNaturalLanguage, getSupportedPatterns } from './natural-language.js';
15
+
16
+ export async function createMcpServer(serviceNowClient) {
17
+ const server = new Server(
18
+ {
19
+ name: 'servicenow-server',
20
+ version: '2.0.0',
21
+ },
22
+ {
23
+ capabilities: {
24
+ tools: {},
25
+ resources: {},
26
+ prompts: {}
27
+ }
28
+ }
29
+ );
30
+
31
+ // Set up progress callback for ServiceNow client
32
+ serviceNowClient.setProgressCallback((message) => {
33
+ try {
34
+ server.notification({
35
+ method: 'notifications/progress',
36
+ params: {
37
+ progress: message
38
+ }
39
+ });
40
+ } catch (error) {
41
+ console.error('Failed to send progress notification:', error.message);
42
+ }
43
+ });
44
+
45
+ // Load table metadata
46
+ let tableMetadata = {};
47
+ try {
48
+ const metadataPath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'config/comprehensive-table-definitions.json');
49
+ const rawData = await fs.readFile(metadataPath, 'utf-8');
50
+ const fullData = JSON.parse(rawData);
51
+
52
+ // Extract just the table definitions, filtering out metadata
53
+ Object.entries(fullData).forEach(([key, value]) => {
54
+ if (!key.startsWith('_') && typeof value === 'object' && value.table) {
55
+ tableMetadata[key] = value;
56
+ }
57
+ });
58
+
59
+ console.error(`✅ Loaded metadata for ${Object.keys(tableMetadata).length} ServiceNow tables`);
60
+ } catch (error) {
61
+ console.error('⚠️ Failed to load table metadata:', error.message);
62
+ }
63
+
64
+ // Set up consolidated tool handlers
65
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
66
+ console.error(`📋 Tool list requested by Claude Code`);
67
+
68
+ const tools = [
69
+ {
70
+ name: 'SN-Set-Instance',
71
+ description: 'Switch to a different ServiceNow instance. Use this at the start of your session to target a specific instance (dev, test, prod, etc.). Lists available instances if no name provided.',
72
+ inputSchema: {
73
+ type: 'object',
74
+ properties: {
75
+ instance_name: {
76
+ type: 'string',
77
+ description: 'Name of the instance to switch to (e.g., "dev", "prod", "test"). Leave empty to list available instances.'
78
+ }
79
+ }
80
+ }
81
+ },
82
+ {
83
+ name: 'SN-Get-Current-Instance',
84
+ description: 'Get information about the currently active ServiceNow instance',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {}
88
+ }
89
+ },
90
+ {
91
+ name: 'SN-Query-Table',
92
+ description: 'Query any ServiceNow table by name with flexible filtering',
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {
96
+ table_name: {
97
+ type: 'string',
98
+ description: 'ServiceNow table name (e.g., "incident", "sys_user", "cmdb_ci") (required)'
99
+ },
100
+ query: {
101
+ type: 'string',
102
+ description: 'ServiceNow encoded query string (e.g., "state=1^priority=1") (optional)'
103
+ },
104
+ fields: {
105
+ type: 'string',
106
+ description: 'Comma-separated list of fields to return (optional)'
107
+ },
108
+ limit: {
109
+ type: 'number',
110
+ description: 'Maximum number of records to return (default: 25)',
111
+ default: 25
112
+ },
113
+ offset: {
114
+ type: 'number',
115
+ description: 'Number of records to skip for pagination (optional)'
116
+ },
117
+ order_by: {
118
+ type: 'string',
119
+ description: 'Field to sort by (e.g., "created_on" or "-priority" for descending) (optional)'
120
+ }
121
+ },
122
+ required: ['table_name']
123
+ }
124
+ },
125
+ {
126
+ name: 'SN-Create-Record',
127
+ description: 'Create a record in any ServiceNow table by name. WARNING: For catalog_ui_policy_action table, fields ui_policy and catalog_variable cannot be set via REST API - use SN-Execute-Background-Script with setValue() after creation.',
128
+ inputSchema: {
129
+ type: 'object',
130
+ properties: {
131
+ table_name: {
132
+ type: 'string',
133
+ description: 'ServiceNow table name (e.g., "incident", "sys_user", "cmdb_ci") (required)'
134
+ },
135
+ data: {
136
+ type: 'object',
137
+ description: 'Record data as key-value pairs (e.g., {"short_description": "Test", "priority": 1}) (required)'
138
+ }
139
+ },
140
+ required: ['table_name', 'data']
141
+ }
142
+ },
143
+ {
144
+ name: 'SN-Get-Record',
145
+ description: 'Get a specific record from any ServiceNow table by sys_id',
146
+ inputSchema: {
147
+ type: 'object',
148
+ properties: {
149
+ table_name: {
150
+ type: 'string',
151
+ description: 'ServiceNow table name (e.g., "incident", "sys_user", "cmdb_ci") (required)'
152
+ },
153
+ sys_id: {
154
+ type: 'string',
155
+ description: 'System ID of the record to retrieve (required)'
156
+ },
157
+ fields: {
158
+ type: 'string',
159
+ description: 'Comma-separated list of fields to return (optional)'
160
+ }
161
+ },
162
+ required: ['table_name', 'sys_id']
163
+ }
164
+ },
165
+ {
166
+ name: 'SN-Update-Record',
167
+ description: 'Update a record in any ServiceNow table by sys_id',
168
+ inputSchema: {
169
+ type: 'object',
170
+ properties: {
171
+ table_name: {
172
+ type: 'string',
173
+ description: 'ServiceNow table name (e.g., "incident", "sys_user", "cmdb_ci") (required)'
174
+ },
175
+ sys_id: {
176
+ type: 'string',
177
+ description: 'System ID of the record to update (required)'
178
+ },
179
+ data: {
180
+ type: 'object',
181
+ description: 'Record data to update as key-value pairs (e.g., {"state": 6, "resolution_notes": "Fixed"}) (required)'
182
+ }
183
+ },
184
+ required: ['table_name', 'sys_id', 'data']
185
+ }
186
+ },
187
+ {
188
+ name: 'SN-Get-Table-Schema',
189
+ description: 'Get the schema/metadata for any ServiceNow table including required fields, common fields, and field descriptions',
190
+ inputSchema: {
191
+ type: 'object',
192
+ properties: {
193
+ table_name: {
194
+ type: 'string',
195
+ description: 'ServiceNow table name (required)'
196
+ }
197
+ },
198
+ required: ['table_name']
199
+ }
200
+ },
201
+ {
202
+ name: 'SN-List-Available-Tables',
203
+ description: 'List all available ServiceNow tables with their descriptions and capabilities',
204
+ inputSchema: {
205
+ type: 'object',
206
+ properties: {
207
+ category: {
208
+ type: 'string',
209
+ description: 'Filter by category: "core_itsm", "platform", "service_catalog", "cmdb", or "all" (optional)'
210
+ }
211
+ },
212
+ required: []
213
+ }
214
+ },
215
+ // Convenience tools for most common operations
216
+ {
217
+ name: 'SN-List-Incidents',
218
+ description: 'List Incident records with filtering and pagination',
219
+ inputSchema: {
220
+ type: 'object',
221
+ properties: {
222
+ state: {
223
+ type: 'string',
224
+ description: 'Filter by state (e.g., "New", "In Progress", "Resolved") (optional)'
225
+ },
226
+ priority: {
227
+ type: 'number',
228
+ description: 'Filter by priority (1-5) (optional)'
229
+ },
230
+ query: {
231
+ type: 'string',
232
+ description: 'ServiceNow encoded query string (e.g., "state=1^priority=1") (optional)'
233
+ },
234
+ limit: {
235
+ type: 'number',
236
+ description: 'Maximum number of records to return (default: 25)',
237
+ default: 25
238
+ },
239
+ offset: {
240
+ type: 'number',
241
+ description: 'Number of records to skip for pagination (optional)'
242
+ },
243
+ fields: {
244
+ type: 'string',
245
+ description: 'Comma-separated list of fields to return (optional)'
246
+ },
247
+ order_by: {
248
+ type: 'string',
249
+ description: 'Field to sort by (e.g., "created_on" or "-priority" for descending) (optional)'
250
+ }
251
+ },
252
+ required: []
253
+ }
254
+ },
255
+ {
256
+ name: 'SN-Create-Incident',
257
+ description: 'Create a new Incident',
258
+ inputSchema: {
259
+ type: 'object',
260
+ properties: {
261
+ short_description: { type: 'string', description: 'short description (required)' },
262
+ description: { type: 'string', description: 'description (optional)' },
263
+ caller_id: { type: 'string', description: 'caller id (optional)' },
264
+ category: { type: 'string', description: 'category (optional)' },
265
+ subcategory: { type: 'string', description: 'subcategory (optional)' },
266
+ urgency: { type: 'string', description: 'urgency (optional)' },
267
+ impact: { type: 'string', description: 'impact (optional)' },
268
+ priority: { type: 'string', description: 'priority (optional)' },
269
+ assigned_to: { type: 'string', description: 'assigned to (optional)' },
270
+ assignment_group: { type: 'string', description: 'assignment group (optional)' },
271
+ state: { type: 'string', description: 'state (optional)' },
272
+ work_notes: { type: 'string', description: 'work notes (optional)' },
273
+ sys_created_by: { type: 'string', description: 'sys created by (optional)' },
274
+ sys_created_on: { type: 'string', description: 'sys created on (optional)' },
275
+ sys_updated_by: { type: 'string', description: 'sys updated by (optional)' },
276
+ sys_updated_on: { type: 'string', description: 'sys updated on (optional)' }
277
+ },
278
+ required: ['short_description']
279
+ }
280
+ },
281
+ {
282
+ name: 'SN-Get-Incident',
283
+ description: 'Get a Incident by ID',
284
+ inputSchema: {
285
+ type: 'object',
286
+ properties: {
287
+ sys_id: { type: 'string', description: 'System ID' }
288
+ },
289
+ required: ['sys_id']
290
+ }
291
+ },
292
+ {
293
+ name: 'SN-List-SysUsers',
294
+ description: 'List Sys User records with filtering and pagination',
295
+ inputSchema: {
296
+ type: 'object',
297
+ properties: {
298
+ query: {
299
+ type: 'string',
300
+ description: 'ServiceNow encoded query string (e.g., "state=1^priority=1") (optional)'
301
+ },
302
+ limit: {
303
+ type: 'number',
304
+ description: 'Maximum number of records to return (default: 25)',
305
+ default: 25
306
+ },
307
+ offset: {
308
+ type: 'number',
309
+ description: 'Number of records to skip for pagination (optional)'
310
+ },
311
+ fields: {
312
+ type: 'string',
313
+ description: 'Comma-separated list of fields to return (optional)'
314
+ },
315
+ order_by: {
316
+ type: 'string',
317
+ description: 'Field to sort by (e.g., "created_on" or "-priority" for descending) (optional)'
318
+ }
319
+ },
320
+ required: []
321
+ }
322
+ },
323
+ {
324
+ name: 'SN-List-CmdbCis',
325
+ description: 'List Cmdb Ci records with filtering and pagination',
326
+ inputSchema: {
327
+ type: 'object',
328
+ properties: {
329
+ query: {
330
+ type: 'string',
331
+ description: 'ServiceNow encoded query string (e.g., "state=1^priority=1") (optional)'
332
+ },
333
+ limit: {
334
+ type: 'number',
335
+ description: 'Maximum number of records to return (default: 25)',
336
+ default: 25
337
+ },
338
+ offset: {
339
+ type: 'number',
340
+ description: 'Number of records to skip for pagination (optional)'
341
+ },
342
+ fields: {
343
+ type: 'string',
344
+ description: 'Comma-separated list of fields to return (optional)'
345
+ },
346
+ order_by: {
347
+ type: 'string',
348
+ description: 'Field to sort by (e.g., "created_on" or "-priority" for descending) (optional)'
349
+ }
350
+ },
351
+ required: []
352
+ }
353
+ },
354
+ {
355
+ name: 'SN-List-SysUserGroups',
356
+ description: 'List Sys User Group records with filtering and pagination',
357
+ inputSchema: {
358
+ type: 'object',
359
+ properties: {
360
+ query: {
361
+ type: 'string',
362
+ description: 'ServiceNow encoded query string (e.g., "state=1^priority=1") (optional)'
363
+ },
364
+ limit: {
365
+ type: 'number',
366
+ description: 'Maximum number of records to return (default: 25)',
367
+ default: 25
368
+ },
369
+ offset: {
370
+ type: 'number',
371
+ description: 'Number of records to skip for pagination (optional)'
372
+ },
373
+ fields: {
374
+ type: 'string',
375
+ description: 'Comma-separated list of fields to return (optional)'
376
+ },
377
+ order_by: {
378
+ type: 'string',
379
+ description: 'Field to sort by (e.g., "created_on" or "-priority" for descending) (optional)'
380
+ }
381
+ },
382
+ required: []
383
+ }
384
+ },
385
+ {
386
+ name: 'SN-List-ChangeRequests',
387
+ description: 'List Change Request records with filtering and pagination',
388
+ inputSchema: {
389
+ type: 'object',
390
+ properties: {
391
+ state: {
392
+ type: 'string',
393
+ description: 'Filter by change state (optional)'
394
+ },
395
+ type: {
396
+ type: 'string',
397
+ description: 'Filter by change type (e.g., "Normal", "Emergency") (optional)'
398
+ },
399
+ query: {
400
+ type: 'string',
401
+ description: 'ServiceNow encoded query string (e.g., "state=1^priority=1") (optional)'
402
+ },
403
+ limit: {
404
+ type: 'number',
405
+ description: 'Maximum number of records to return (default: 25)',
406
+ default: 25
407
+ },
408
+ offset: {
409
+ type: 'number',
410
+ description: 'Number of records to skip for pagination (optional)'
411
+ },
412
+ fields: {
413
+ type: 'string',
414
+ description: 'Comma-separated list of fields to return (optional)'
415
+ },
416
+ order_by: {
417
+ type: 'string',
418
+ description: 'Field to sort by (e.g., "created_on" or "-priority" for descending) (optional)'
419
+ }
420
+ },
421
+ required: []
422
+ }
423
+ },
424
+ {
425
+ name: 'SN-Set-Update-Set',
426
+ description: 'Generate a fix script to set the current update set using GlideUpdateSet API. Cannot be done via REST API - creates script file for manual execution in ServiceNow UI.',
427
+ inputSchema: {
428
+ type: 'object',
429
+ properties: {
430
+ update_set_sys_id: {
431
+ type: 'string',
432
+ description: 'System ID of the update set to make current (required)'
433
+ }
434
+ },
435
+ required: ['update_set_sys_id']
436
+ }
437
+ },
438
+ {
439
+ name: 'SN-Get-Current-Update-Set',
440
+ description: 'Get the currently active update set',
441
+ inputSchema: {
442
+ type: 'object',
443
+ properties: {},
444
+ required: []
445
+ }
446
+ },
447
+ {
448
+ name: 'SN-List-Update-Sets',
449
+ description: 'List available update sets',
450
+ inputSchema: {
451
+ type: 'object',
452
+ properties: {
453
+ query: {
454
+ type: 'string',
455
+ description: 'ServiceNow encoded query string (e.g., "state=in progress") (optional)'
456
+ },
457
+ limit: {
458
+ type: 'number',
459
+ description: 'Maximum number of records to return (default: 25)',
460
+ default: 25
461
+ },
462
+ offset: {
463
+ type: 'number',
464
+ description: 'Number of records to skip for pagination (optional)'
465
+ },
466
+ fields: {
467
+ type: 'string',
468
+ description: 'Comma-separated list of fields to return (optional)'
469
+ },
470
+ order_by: {
471
+ type: 'string',
472
+ description: 'Field to sort by (e.g., "created_on" or "-sys_created_on" for descending) (optional)'
473
+ }
474
+ },
475
+ required: []
476
+ }
477
+ },
478
+ {
479
+ name: 'SN-Set-Current-Application',
480
+ description: 'Set the current application scope using the UI API. This changes which application is active for development and configuration changes.',
481
+ inputSchema: {
482
+ type: 'object',
483
+ properties: {
484
+ app_sys_id: {
485
+ type: 'string',
486
+ description: 'System ID of the application to make current (required)'
487
+ }
488
+ },
489
+ required: ['app_sys_id']
490
+ }
491
+ },
492
+ {
493
+ name: 'SN-List-Problems',
494
+ description: 'List Problem records with filtering and pagination',
495
+ inputSchema: {
496
+ type: 'object',
497
+ properties: {
498
+ query: {
499
+ type: 'string',
500
+ description: 'ServiceNow encoded query string (e.g., "state=1^priority=1") (optional)'
501
+ },
502
+ limit: {
503
+ type: 'number',
504
+ description: 'Maximum number of records to return (default: 25)',
505
+ default: 25
506
+ },
507
+ offset: {
508
+ type: 'number',
509
+ description: 'Number of records to skip for pagination (optional)'
510
+ },
511
+ fields: {
512
+ type: 'string',
513
+ description: 'Comma-separated list of fields to return (optional)'
514
+ },
515
+ order_by: {
516
+ type: 'string',
517
+ description: 'Field to sort by (e.g., "created_on" or "-priority" for descending) (optional)'
518
+ }
519
+ },
520
+ required: []
521
+ }
522
+ },
523
+ {
524
+ name: 'SN-Natural-Language-Search',
525
+ description: 'Search ServiceNow records using natural language queries. Converts human-readable queries into ServiceNow encoded queries and executes them. Supports: Priority (P1-P5, high/low), Assignment (assigned to me, unassigned, assigned to <name>), Dates (created today, last 7 days, recent), States (new/open/closed/in progress), Content (about SAP, containing error), Impact/Urgency (high/medium/low), Numbers (number is INC0012345). Examples: "find all P1 incidents", "show recent problems assigned to me", "high priority changes created last week", "open incidents about SAP", "unassigned P2 incidents created today". Returns both the parsed encoded query and matching records with pattern analysis.',
526
+ inputSchema: {
527
+ type: 'object',
528
+ properties: {
529
+ query: {
530
+ type: 'string',
531
+ description: 'Natural language query (e.g., "high priority incidents assigned to me", "recent problems about database") (required)'
532
+ },
533
+ table: {
534
+ type: 'string',
535
+ description: 'Target ServiceNow table name (default: "incident"). Common tables: incident, problem, change_request, sys_user, cmdb_ci',
536
+ default: 'incident'
537
+ },
538
+ limit: {
539
+ type: 'number',
540
+ description: 'Maximum number of records to return (default: 25)',
541
+ default: 25
542
+ },
543
+ fields: {
544
+ type: 'string',
545
+ description: 'Comma-separated list of fields to return (optional)'
546
+ },
547
+ order_by: {
548
+ type: 'string',
549
+ description: 'Field to sort by (e.g., "sys_created_on" or "-priority" for descending) (optional)'
550
+ },
551
+ show_patterns: {
552
+ type: 'boolean',
553
+ description: 'Include pattern matching details in response (default: true)',
554
+ default: true
555
+ }
556
+ },
557
+ required: ['query']
558
+ }
559
+ },
560
+ {
561
+ name: 'SN-Execute-Background-Script',
562
+ description: '🚀 EXECUTES background scripts with THREE methods: (1) sys_trigger [DEFAULT & MOST RELIABLE] - Creates scheduled job that runs in 1 second and auto-deletes, (2) UI endpoint (sys.scripts.do) - Attempts direct execution via UI, (3) Fix script - Manual fallback. Use for: setting update sets, complex GlideRecord operations, GlideUpdateSet API calls, etc. The sys_trigger method is most reliable and works consistently!',
563
+ inputSchema: {
564
+ type: 'object',
565
+ properties: {
566
+ script: {
567
+ type: 'string',
568
+ description: 'JavaScript code to execute (required)'
569
+ },
570
+ description: {
571
+ type: 'string',
572
+ description: 'Description of what the script does (optional)'
573
+ },
574
+ execution_method: {
575
+ type: 'string',
576
+ description: 'Execution method: "trigger" (default - most reliable), "ui" (UI endpoint), "auto" (try trigger then ui then fix script)',
577
+ enum: ['trigger', 'ui', 'auto'],
578
+ default: 'trigger'
579
+ }
580
+ },
581
+ required: ['script']
582
+ }
583
+ },
584
+ {
585
+ name: 'SN-Create-Fix-Script',
586
+ description: '⚠️ CREATES (not executes) a fix script file for MANUAL execution. ServiceNow REST API does NOT support direct script execution. This tool generates a .js file in /scripts/ directory with full instructions and optional auto-delete flag. You MUST manually copy and run the script in ServiceNow UI: System Definition → Scripts - Background. Use for: linking UI Policy Actions, setting update sets, complex GlideRecord operations that cannot be done via REST API.',
587
+ inputSchema: {
588
+ type: 'object',
589
+ properties: {
590
+ script_name: {
591
+ type: 'string',
592
+ description: 'Name for the script file (e.g., "link_ui_policy_actions") (required)'
593
+ },
594
+ script_content: {
595
+ type: 'string',
596
+ description: 'JavaScript code content (required)'
597
+ },
598
+ description: {
599
+ type: 'string',
600
+ description: 'Description of what the script does (optional)'
601
+ },
602
+ auto_delete: {
603
+ type: 'boolean',
604
+ description: 'If true, script file will be deleted after you confirm execution (default: false)',
605
+ default: false
606
+ }
607
+ },
608
+ required: ['script_name', 'script_content']
609
+ }
610
+ },
611
+ {
612
+ name: 'SN-Discover-Table-Schema',
613
+ description: 'Deep schema introspection with ServiceNow-specific metadata including type codes, choice tables, and relationships',
614
+ inputSchema: {
615
+ type: 'object',
616
+ properties: {
617
+ table_name: {
618
+ type: 'string',
619
+ description: 'ServiceNow table name (required)'
620
+ },
621
+ include_type_codes: {
622
+ type: 'boolean',
623
+ description: 'Show internal type codes (e.g., 1=Choice, 5=Select Box) (optional)',
624
+ default: false
625
+ },
626
+ include_choice_tables: {
627
+ type: 'boolean',
628
+ description: 'Show which choice tables to use (optional)',
629
+ default: false
630
+ },
631
+ include_relationships: {
632
+ type: 'boolean',
633
+ description: 'Show parent/child table relationships (optional)',
634
+ default: false
635
+ },
636
+ include_ui_policies: {
637
+ type: 'boolean',
638
+ description: 'Show UI policies affecting this table (optional)',
639
+ default: false
640
+ },
641
+ include_business_rules: {
642
+ type: 'boolean',
643
+ description: 'Show business rules for this table (optional)',
644
+ default: false
645
+ },
646
+ include_field_constraints: {
647
+ type: 'boolean',
648
+ description: 'Show field validations and defaults (optional)',
649
+ default: false
650
+ }
651
+ },
652
+ required: ['table_name']
653
+ }
654
+ },
655
+ {
656
+ name: 'SN-Batch-Create',
657
+ description: 'Create multiple related records in one operation with variable references and transactional support. Reports progress during execution.',
658
+ inputSchema: {
659
+ type: 'object',
660
+ properties: {
661
+ operations: {
662
+ type: 'array',
663
+ description: 'Array of create operations. Each operation can reference previous operations via ${save_as_name}',
664
+ items: {
665
+ type: 'object',
666
+ properties: {
667
+ table: { type: 'string', description: 'Table name' },
668
+ data: { type: 'object', description: 'Record data' },
669
+ save_as: { type: 'string', description: 'Variable name to save sys_id as (optional)' }
670
+ }
671
+ }
672
+ },
673
+ transaction: {
674
+ type: 'boolean',
675
+ description: 'All-or-nothing transaction (default: true)',
676
+ default: true
677
+ },
678
+ progress: {
679
+ type: 'boolean',
680
+ description: 'Report progress notifications (default: true)',
681
+ default: true
682
+ }
683
+ },
684
+ required: ['operations']
685
+ }
686
+ },
687
+ {
688
+ name: 'SN-Batch-Update',
689
+ description: 'Update multiple records efficiently in a single operation. Reports progress during execution.',
690
+ inputSchema: {
691
+ type: 'object',
692
+ properties: {
693
+ updates: {
694
+ type: 'array',
695
+ description: 'Array of update operations',
696
+ items: {
697
+ type: 'object',
698
+ properties: {
699
+ table: { type: 'string', description: 'Table name' },
700
+ sys_id: { type: 'string', description: 'Record sys_id' },
701
+ data: { type: 'object', description: 'Fields to update' }
702
+ }
703
+ }
704
+ },
705
+ stop_on_error: {
706
+ type: 'boolean',
707
+ description: 'Stop processing on first error (default: false)',
708
+ default: false
709
+ },
710
+ progress: {
711
+ type: 'boolean',
712
+ description: 'Report progress notifications (default: true)',
713
+ default: true
714
+ }
715
+ },
716
+ required: ['updates']
717
+ }
718
+ },
719
+ {
720
+ name: 'SN-Explain-Field',
721
+ description: 'Get comprehensive explanation of a specific field including type, constraints, and known issues',
722
+ inputSchema: {
723
+ type: 'object',
724
+ properties: {
725
+ table: {
726
+ type: 'string',
727
+ description: 'Table name (required)'
728
+ },
729
+ field: {
730
+ type: 'string',
731
+ description: 'Field name (required)'
732
+ },
733
+ include_examples: {
734
+ type: 'boolean',
735
+ description: 'Include usage examples (default: true)',
736
+ default: true
737
+ }
738
+ },
739
+ required: ['table', 'field']
740
+ }
741
+ },
742
+ {
743
+ name: 'SN-Validate-Configuration',
744
+ description: 'Validate catalog item configuration including variables, UI policies, and business rules',
745
+ inputSchema: {
746
+ type: 'object',
747
+ properties: {
748
+ catalog_item: {
749
+ type: 'string',
750
+ description: 'Catalog item sys_id (required)'
751
+ },
752
+ checks: {
753
+ type: 'object',
754
+ description: 'Validation checks to perform',
755
+ properties: {
756
+ variables: {
757
+ type: 'object',
758
+ properties: {
759
+ check_linked: { type: 'boolean' },
760
+ check_types: { type: 'boolean' },
761
+ check_choices: { type: 'boolean' },
762
+ check_mandatory: { type: 'boolean' }
763
+ }
764
+ },
765
+ ui_policies: {
766
+ type: 'object',
767
+ properties: {
768
+ check_conditions: { type: 'boolean' },
769
+ check_actions_linked: { type: 'boolean' },
770
+ check_variables_exist: { type: 'boolean' }
771
+ }
772
+ }
773
+ }
774
+ }
775
+ },
776
+ required: ['catalog_item']
777
+ }
778
+ },
779
+ {
780
+ name: 'SN-Inspect-Update-Set',
781
+ description: 'Inspect update set contents and verify completeness',
782
+ inputSchema: {
783
+ type: 'object',
784
+ properties: {
785
+ update_set: {
786
+ type: 'string',
787
+ description: 'Update set sys_id (required)'
788
+ },
789
+ show_components: {
790
+ type: 'boolean',
791
+ description: 'Show component breakdown (default: true)',
792
+ default: true
793
+ },
794
+ show_dependencies: {
795
+ type: 'boolean',
796
+ description: 'Show missing dependencies (default: false)',
797
+ default: false
798
+ }
799
+ },
800
+ required: ['update_set']
801
+ }
802
+ },
803
+ {
804
+ name: 'SN-Create-Workflow',
805
+ description: 'Create a complete ServiceNow workflow with activities, transitions, and conditions. This tool orchestrates the entire workflow creation process: base workflow → version → activities → transitions → publish. Reports progress during creation.',
806
+ inputSchema: {
807
+ type: 'object',
808
+ properties: {
809
+ name: {
810
+ type: 'string',
811
+ description: 'Workflow name (required)'
812
+ },
813
+ description: {
814
+ type: 'string',
815
+ description: 'Workflow description (optional)'
816
+ },
817
+ table: {
818
+ type: 'string',
819
+ description: 'Table this workflow runs against (e.g., "incident", "change_request")'
820
+ },
821
+ condition: {
822
+ type: 'string',
823
+ description: 'Condition for workflow to trigger (e.g., "state=1^priority=1") (optional)'
824
+ },
825
+ activities: {
826
+ type: 'array',
827
+ description: 'Array of activity definitions',
828
+ items: {
829
+ type: 'object',
830
+ properties: {
831
+ name: { type: 'string', description: 'Activity name' },
832
+ script: { type: 'string', description: 'JavaScript code to execute' },
833
+ activity_definition_sys_id: { type: 'string', description: 'Activity type sys_id (optional)' }
834
+ },
835
+ required: ['name']
836
+ }
837
+ },
838
+ transitions: {
839
+ type: 'array',
840
+ description: 'Array of transition definitions (connects activities)',
841
+ items: {
842
+ type: 'object',
843
+ properties: {
844
+ from: { type: 'string', description: 'From activity name' },
845
+ to: { type: 'string', description: 'To activity name' },
846
+ condition_script: { type: 'string', description: 'JavaScript condition (optional)' }
847
+ },
848
+ required: ['from', 'to']
849
+ }
850
+ },
851
+ publish: {
852
+ type: 'boolean',
853
+ description: 'Publish workflow after creation (default: false)',
854
+ default: false
855
+ },
856
+ progress: {
857
+ type: 'boolean',
858
+ description: 'Report progress notifications (default: true)',
859
+ default: true
860
+ }
861
+ },
862
+ required: ['name', 'table', 'activities']
863
+ }
864
+ },
865
+ {
866
+ name: 'SN-Create-Activity',
867
+ description: 'Create a single workflow activity with embedded JavaScript code. Use this for adding activities to existing workflows.',
868
+ inputSchema: {
869
+ type: 'object',
870
+ properties: {
871
+ workflow_version_sys_id: {
872
+ type: 'string',
873
+ description: 'Workflow version sys_id (required)'
874
+ },
875
+ name: {
876
+ type: 'string',
877
+ description: 'Activity name (required)'
878
+ },
879
+ script: {
880
+ type: 'string',
881
+ description: 'JavaScript code to execute in this activity (optional)'
882
+ },
883
+ activity_definition_sys_id: {
884
+ type: 'string',
885
+ description: 'Activity type sys_id (optional - defaults to generic activity)'
886
+ },
887
+ x: {
888
+ type: 'number',
889
+ description: 'X coordinate on canvas (default: 100)'
890
+ },
891
+ y: {
892
+ type: 'number',
893
+ description: 'Y coordinate on canvas (default: 100)'
894
+ }
895
+ },
896
+ required: ['workflow_version_sys_id', 'name']
897
+ }
898
+ },
899
+ {
900
+ name: 'SN-Create-Transition',
901
+ description: 'Create a transition between two workflow activities with optional condition',
902
+ inputSchema: {
903
+ type: 'object',
904
+ properties: {
905
+ from_activity_sys_id: {
906
+ type: 'string',
907
+ description: 'From activity sys_id (required)'
908
+ },
909
+ to_activity_sys_id: {
910
+ type: 'string',
911
+ description: 'To activity sys_id (required)'
912
+ },
913
+ condition_script: {
914
+ type: 'string',
915
+ description: 'JavaScript condition for this transition (optional)'
916
+ },
917
+ order: {
918
+ type: 'number',
919
+ description: 'Transition order (default: 1)'
920
+ }
921
+ },
922
+ required: ['from_activity_sys_id', 'to_activity_sys_id']
923
+ }
924
+ },
925
+ {
926
+ name: 'SN-Publish-Workflow',
927
+ description: 'Publish a workflow version, setting the start activity and making it active',
928
+ inputSchema: {
929
+ type: 'object',
930
+ properties: {
931
+ version_sys_id: {
932
+ type: 'string',
933
+ description: 'Workflow version sys_id (required)'
934
+ },
935
+ start_activity_sys_id: {
936
+ type: 'string',
937
+ description: 'Starting activity sys_id (required)'
938
+ }
939
+ },
940
+ required: ['version_sys_id', 'start_activity_sys_id']
941
+ }
942
+ },
943
+ {
944
+ name: 'SN-Move-Records-To-Update-Set',
945
+ description: 'Move sys_update_xml records to a different update set. Supports filtering by sys_ids, time range, or source update set. Extremely useful when records end up in wrong update set (e.g., "Default" instead of custom set). Reports progress during move operation.',
946
+ inputSchema: {
947
+ type: 'object',
948
+ properties: {
949
+ update_set_id: {
950
+ type: 'string',
951
+ description: 'Target update set sys_id to move records to (required)'
952
+ },
953
+ record_sys_ids: {
954
+ type: 'array',
955
+ description: 'Array of sys_update_xml sys_ids to move (optional)',
956
+ items: { type: 'string' }
957
+ },
958
+ time_range: {
959
+ type: 'object',
960
+ description: 'Time range to filter records (optional - format: YYYY-MM-DD HH:MM:SS)',
961
+ properties: {
962
+ start: { type: 'string', description: 'Start time (e.g., "2025-09-29 20:00:00")' },
963
+ end: { type: 'string', description: 'End time (e.g., "2025-09-29 20:03:31")' }
964
+ }
965
+ },
966
+ source_update_set: {
967
+ type: 'string',
968
+ description: 'Filter by source update set name (e.g., "Default") (optional)'
969
+ },
970
+ table: {
971
+ type: 'string',
972
+ description: 'Table name (default: sys_update_xml)',
973
+ default: 'sys_update_xml'
974
+ },
975
+ progress: {
976
+ type: 'boolean',
977
+ description: 'Report progress notifications (default: true)',
978
+ default: true
979
+ }
980
+ },
981
+ required: ['update_set_id']
982
+ }
983
+ },
984
+ {
985
+ name: 'SN-Clone-Update-Set',
986
+ description: 'Clone an entire update set with all its sys_update_xml records. Creates a complete copy for backup, testing, or branching development work. Reports progress during cloning operation.',
987
+ inputSchema: {
988
+ type: 'object',
989
+ properties: {
990
+ source_update_set_id: {
991
+ type: 'string',
992
+ description: 'Source update set sys_id to clone (required)'
993
+ },
994
+ new_name: {
995
+ type: 'string',
996
+ description: 'Name for the new cloned update set (required)'
997
+ },
998
+ progress: {
999
+ type: 'boolean',
1000
+ description: 'Report progress notifications (default: true)',
1001
+ default: true
1002
+ }
1003
+ },
1004
+ required: ['source_update_set_id', 'new_name']
1005
+ }
1006
+ },
1007
+ // Incident convenience tools
1008
+ {
1009
+ name: 'SN-Add-Comment',
1010
+ description: 'Add a comment to an incident. Accepts incident number for better UX.',
1011
+ inputSchema: {
1012
+ type: 'object',
1013
+ properties: {
1014
+ incident_number: {
1015
+ type: 'string',
1016
+ description: 'Incident number (e.g., "INC0012345") (required)'
1017
+ },
1018
+ comment: {
1019
+ type: 'string',
1020
+ description: 'Comment text to add (required)'
1021
+ },
1022
+ instance: {
1023
+ type: 'string',
1024
+ description: 'Instance name (optional, uses default if not specified)'
1025
+ }
1026
+ },
1027
+ required: ['incident_number', 'comment']
1028
+ }
1029
+ },
1030
+ {
1031
+ name: 'SN-Add-Work-Notes',
1032
+ description: 'Add work notes to an incident. Accepts incident number for better UX.',
1033
+ inputSchema: {
1034
+ type: 'object',
1035
+ properties: {
1036
+ incident_number: {
1037
+ type: 'string',
1038
+ description: 'Incident number (e.g., "INC0012345") (required)'
1039
+ },
1040
+ work_notes: {
1041
+ type: 'string',
1042
+ description: 'Work notes text to add (required)'
1043
+ },
1044
+ instance: {
1045
+ type: 'string',
1046
+ description: 'Instance name (optional, uses default if not specified)'
1047
+ }
1048
+ },
1049
+ required: ['incident_number', 'work_notes']
1050
+ }
1051
+ },
1052
+ {
1053
+ name: 'SN-Assign-Incident',
1054
+ description: 'Assign an incident to a user and/or group. Resolves user names automatically.',
1055
+ inputSchema: {
1056
+ type: 'object',
1057
+ properties: {
1058
+ incident_number: {
1059
+ type: 'string',
1060
+ description: 'Incident number (e.g., "INC0012345") (required)'
1061
+ },
1062
+ assigned_to: {
1063
+ type: 'string',
1064
+ description: 'User name or sys_id to assign to (required)'
1065
+ },
1066
+ assignment_group: {
1067
+ type: 'string',
1068
+ description: 'Assignment group name or sys_id (optional)'
1069
+ },
1070
+ instance: {
1071
+ type: 'string',
1072
+ description: 'Instance name (optional, uses default if not specified)'
1073
+ }
1074
+ },
1075
+ required: ['incident_number', 'assigned_to']
1076
+ }
1077
+ },
1078
+ {
1079
+ name: 'SN-Resolve-Incident',
1080
+ description: 'Resolve an incident with resolution notes and code.',
1081
+ inputSchema: {
1082
+ type: 'object',
1083
+ properties: {
1084
+ incident_number: {
1085
+ type: 'string',
1086
+ description: 'Incident number (e.g., "INC0012345") (required)'
1087
+ },
1088
+ resolution_notes: {
1089
+ type: 'string',
1090
+ description: 'Resolution notes describing the fix (required)'
1091
+ },
1092
+ resolution_code: {
1093
+ type: 'string',
1094
+ description: 'Resolution code (e.g., "Solved (Permanently)", "Solved (Work Around)") (optional)'
1095
+ },
1096
+ instance: {
1097
+ type: 'string',
1098
+ description: 'Instance name (optional, uses default if not specified)'
1099
+ }
1100
+ },
1101
+ required: ['incident_number', 'resolution_notes']
1102
+ }
1103
+ },
1104
+ {
1105
+ name: 'SN-Close-Incident',
1106
+ description: 'Close an incident with close notes and code.',
1107
+ inputSchema: {
1108
+ type: 'object',
1109
+ properties: {
1110
+ incident_number: {
1111
+ type: 'string',
1112
+ description: 'Incident number (e.g., "INC0012345") (required)'
1113
+ },
1114
+ close_notes: {
1115
+ type: 'string',
1116
+ description: 'Close notes (required)'
1117
+ },
1118
+ close_code: {
1119
+ type: 'string',
1120
+ description: 'Close code (e.g., "Solved (Permanently)", "Solved (Work Around)") (optional)'
1121
+ },
1122
+ instance: {
1123
+ type: 'string',
1124
+ description: 'Instance name (optional, uses default if not specified)'
1125
+ }
1126
+ },
1127
+ required: ['incident_number', 'close_notes']
1128
+ }
1129
+ },
1130
+ // Change Request convenience tools
1131
+ {
1132
+ name: 'SN-Add-Change-Comment',
1133
+ description: 'Add a comment to a change request. Accepts change number for better UX.',
1134
+ inputSchema: {
1135
+ type: 'object',
1136
+ properties: {
1137
+ change_number: {
1138
+ type: 'string',
1139
+ description: 'Change request number (e.g., "CHG0012345") (required)'
1140
+ },
1141
+ comment: {
1142
+ type: 'string',
1143
+ description: 'Comment text to add (required)'
1144
+ },
1145
+ instance: {
1146
+ type: 'string',
1147
+ description: 'Instance name (optional, uses default if not specified)'
1148
+ }
1149
+ },
1150
+ required: ['change_number', 'comment']
1151
+ }
1152
+ },
1153
+ {
1154
+ name: 'SN-Assign-Change',
1155
+ description: 'Assign a change request to a user and/or group.',
1156
+ inputSchema: {
1157
+ type: 'object',
1158
+ properties: {
1159
+ change_number: {
1160
+ type: 'string',
1161
+ description: 'Change request number (e.g., "CHG0012345") (required)'
1162
+ },
1163
+ assigned_to: {
1164
+ type: 'string',
1165
+ description: 'User name or sys_id to assign to (required)'
1166
+ },
1167
+ assignment_group: {
1168
+ type: 'string',
1169
+ description: 'Assignment group name or sys_id (optional)'
1170
+ },
1171
+ instance: {
1172
+ type: 'string',
1173
+ description: 'Instance name (optional, uses default if not specified)'
1174
+ }
1175
+ },
1176
+ required: ['change_number', 'assigned_to']
1177
+ }
1178
+ },
1179
+ {
1180
+ name: 'SN-Approve-Change',
1181
+ description: 'Approve a change request.',
1182
+ inputSchema: {
1183
+ type: 'object',
1184
+ properties: {
1185
+ change_number: {
1186
+ type: 'string',
1187
+ description: 'Change request number (e.g., "CHG0012345") (required)'
1188
+ },
1189
+ approval_comments: {
1190
+ type: 'string',
1191
+ description: 'Comments for the approval (optional)'
1192
+ },
1193
+ instance: {
1194
+ type: 'string',
1195
+ description: 'Instance name (optional, uses default if not specified)'
1196
+ }
1197
+ },
1198
+ required: ['change_number']
1199
+ }
1200
+ },
1201
+ // Problem convenience tools
1202
+ {
1203
+ name: 'SN-Add-Problem-Comment',
1204
+ description: 'Add a comment to a problem. Accepts problem number for better UX.',
1205
+ inputSchema: {
1206
+ type: 'object',
1207
+ properties: {
1208
+ problem_number: {
1209
+ type: 'string',
1210
+ description: 'Problem number (e.g., "PRB0012345") (required)'
1211
+ },
1212
+ comment: {
1213
+ type: 'string',
1214
+ description: 'Comment text to add (required)'
1215
+ },
1216
+ instance: {
1217
+ type: 'string',
1218
+ description: 'Instance name (optional, uses default if not specified)'
1219
+ }
1220
+ },
1221
+ required: ['problem_number', 'comment']
1222
+ }
1223
+ },
1224
+ {
1225
+ name: 'SN-Close-Problem',
1226
+ description: 'Close a problem with resolution information.',
1227
+ inputSchema: {
1228
+ type: 'object',
1229
+ properties: {
1230
+ problem_number: {
1231
+ type: 'string',
1232
+ description: 'Problem number (e.g., "PRB0012345") (required)'
1233
+ },
1234
+ resolution_notes: {
1235
+ type: 'string',
1236
+ description: 'Resolution notes (required)'
1237
+ },
1238
+ resolution_code: {
1239
+ type: 'string',
1240
+ description: 'Resolution code (optional)'
1241
+ },
1242
+ instance: {
1243
+ type: 'string',
1244
+ description: 'Instance name (optional, uses default if not specified)'
1245
+ }
1246
+ },
1247
+ required: ['problem_number', 'resolution_notes']
1248
+ }
1249
+ }
1250
+ ];
1251
+
1252
+ console.error(`✅ Returning ${tools.length} consolidated tools to Claude Code`);
1253
+ return { tools };
1254
+ });
1255
+
1256
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1257
+ const { name, arguments: args } = request.params;
1258
+
1259
+ try {
1260
+ switch (name) {
1261
+ case 'SN-Set-Instance': {
1262
+ const { instance_name } = args;
1263
+
1264
+ // If no instance name provided, list available instances
1265
+ if (!instance_name) {
1266
+ const instances = configManager.listInstances();
1267
+ return {
1268
+ content: [{
1269
+ type: 'text',
1270
+ text: JSON.stringify({
1271
+ message: 'Available ServiceNow instances',
1272
+ current_instance: serviceNowClient.getCurrentInstance(),
1273
+ instances: instances
1274
+ }, null, 2)
1275
+ }]
1276
+ };
1277
+ }
1278
+
1279
+ // Get instance configuration
1280
+ const instance = configManager.getInstance(instance_name);
1281
+
1282
+ // Switch the client to the new instance
1283
+ serviceNowClient.setInstance(instance.url, instance.username, instance.password, instance.name);
1284
+
1285
+ console.error(`🔄 Switched to instance: ${instance.name} (${instance.url})`);
1286
+
1287
+ return {
1288
+ content: [{
1289
+ type: 'text',
1290
+ text: JSON.stringify({
1291
+ success: true,
1292
+ message: `Switched to ServiceNow instance: ${instance.name}`,
1293
+ instance: {
1294
+ name: instance.name,
1295
+ url: instance.url,
1296
+ description: instance.description
1297
+ }
1298
+ }, null, 2)
1299
+ }]
1300
+ };
1301
+ }
1302
+
1303
+ case 'SN-Get-Current-Instance': {
1304
+ const currentInstance = serviceNowClient.getCurrentInstance();
1305
+ return {
1306
+ content: [{
1307
+ type: 'text',
1308
+ text: JSON.stringify({
1309
+ current_instance: currentInstance,
1310
+ message: `Currently connected to: ${currentInstance.name} (${currentInstance.url})`
1311
+ }, null, 2)
1312
+ }]
1313
+ };
1314
+ }
1315
+
1316
+ case 'SN-Query-Table': {
1317
+ const { table_name, query, fields, limit = 25, offset, order_by } = args;
1318
+
1319
+ const queryParams = {
1320
+ sysparm_limit: limit,
1321
+ sysparm_query: query,
1322
+ sysparm_fields: fields,
1323
+ sysparm_offset: offset
1324
+ };
1325
+
1326
+ if (order_by) {
1327
+ queryParams.sysparm_order_by = order_by;
1328
+ }
1329
+
1330
+ const results = await serviceNowClient.getRecords(table_name, queryParams);
1331
+
1332
+ return {
1333
+ content: [{
1334
+ type: 'text',
1335
+ text: `Found ${results.length} records in ${table_name}:\n${JSON.stringify(results, null, 2)}`
1336
+ }]
1337
+ };
1338
+ }
1339
+
1340
+ case 'SN-Create-Record': {
1341
+ const { table_name, data } = args;
1342
+ const result = await serviceNowClient.createRecord(table_name, data);
1343
+
1344
+ const metadata = tableMetadata[table_name];
1345
+ const keyField = metadata?.key_field || 'sys_id';
1346
+ const identifier = result[keyField] || result.sys_id;
1347
+
1348
+ return {
1349
+ content: [{
1350
+ type: 'text',
1351
+ text: `Created ${metadata?.label || table_name} successfully: ${identifier}\n${JSON.stringify(result, null, 2)}`
1352
+ }]
1353
+ };
1354
+ }
1355
+
1356
+ case 'SN-Get-Record': {
1357
+ const { table_name, sys_id, fields } = args;
1358
+
1359
+ const queryParams = {};
1360
+ if (fields) queryParams.sysparm_fields = fields;
1361
+
1362
+ const result = await serviceNowClient.getRecord(table_name, sys_id, queryParams);
1363
+
1364
+ return {
1365
+ content: [{
1366
+ type: 'text',
1367
+ text: JSON.stringify(result, null, 2)
1368
+ }]
1369
+ };
1370
+ }
1371
+
1372
+ case 'SN-Update-Record': {
1373
+ const { table_name, sys_id, data } = args;
1374
+ const result = await serviceNowClient.updateRecord(table_name, sys_id, data);
1375
+
1376
+ const metadata = tableMetadata[table_name];
1377
+ const keyField = metadata?.key_field || 'sys_id';
1378
+ const identifier = result[keyField] || sys_id;
1379
+
1380
+ return {
1381
+ content: [{
1382
+ type: 'text',
1383
+ text: `Updated ${metadata?.label || table_name} ${identifier} successfully\n${JSON.stringify(result, null, 2)}`
1384
+ }]
1385
+ };
1386
+ }
1387
+
1388
+ case 'SN-Get-Table-Schema': {
1389
+ const { table_name } = args;
1390
+ const schema = tableMetadata[table_name];
1391
+
1392
+ if (!schema) {
1393
+ // FALLBACK: Try to fetch schema from ServiceNow API
1394
+ console.error(`⚠️ Table "${table_name}" not in local metadata, attempting API fallback...`);
1395
+ try {
1396
+ const apiSchema = await serviceNowClient.discoverTableSchema(table_name, {
1397
+ include_type_codes: false,
1398
+ include_choice_tables: false,
1399
+ include_relationships: false
1400
+ });
1401
+
1402
+ return {
1403
+ content: [{
1404
+ type: 'text',
1405
+ text: `Schema for ${table_name} (fetched from ServiceNow API):\n${JSON.stringify({
1406
+ table_name,
1407
+ label: apiSchema.label,
1408
+ fields: apiSchema.fields,
1409
+ source: 'live_api',
1410
+ note: 'This table is not in local metadata. Consider adding it to comprehensive-table-definitions.json for faster lookups.'
1411
+ }, null, 2)}`
1412
+ }]
1413
+ };
1414
+ } catch (error) {
1415
+ return {
1416
+ content: [{
1417
+ type: 'text',
1418
+ text: `No schema metadata found for table "${table_name}" in local cache, and API lookup failed: ${error.message}. The table may not exist or you may not have permissions. Use SN-Query-Table to attempt to query it.`
1419
+ }],
1420
+ isError: false
1421
+ };
1422
+ }
1423
+ }
1424
+
1425
+ return {
1426
+ content: [{
1427
+ type: 'text',
1428
+ text: JSON.stringify({
1429
+ table_name,
1430
+ label: schema.label,
1431
+ key_field: schema.key_field,
1432
+ display_field: schema.display_field,
1433
+ required_fields: schema.required_fields || [],
1434
+ common_fields: schema.common_fields || [],
1435
+ operations: schema.operations || ['create', 'read', 'update', 'list'],
1436
+ description: schema.description,
1437
+ package: schema.package,
1438
+ source: 'local_cache'
1439
+ }, null, 2)
1440
+ }]
1441
+ };
1442
+ }
1443
+
1444
+ case 'SN-List-Available-Tables': {
1445
+ const { category } = args;
1446
+
1447
+ const categories = {
1448
+ core_itsm: ['incident', 'change_request', 'problem', 'change_task', 'problem_task'],
1449
+ platform: ['sys_user', 'sys_user_group', 'sys_db_object', 'sys_dictionary', 'sys_properties'],
1450
+ service_catalog: ['sc_request', 'sc_req_item', 'sc_cat_item', 'sc_category'],
1451
+ cmdb: ['cmdb_ci', 'cmdb_ci_computer', 'cmdb_ci_server', 'cmdb_rel_ci']
1452
+ };
1453
+
1454
+ let tablesToList = Object.keys(tableMetadata);
1455
+
1456
+ if (category && category !== 'all' && categories[category]) {
1457
+ tablesToList = tablesToList.filter(t => categories[category].includes(t));
1458
+ }
1459
+
1460
+ const tableList = tablesToList.map(tableName => {
1461
+ const meta = tableMetadata[tableName];
1462
+ return {
1463
+ table_name: tableName,
1464
+ label: meta.label,
1465
+ description: meta.description,
1466
+ key_field: meta.key_field,
1467
+ priority: meta.priority,
1468
+ package: meta.package
1469
+ };
1470
+ });
1471
+
1472
+ return {
1473
+ content: [{
1474
+ type: 'text',
1475
+ text: `Available ServiceNow tables (${tableList.length} total):\n${JSON.stringify(tableList, null, 2)}`
1476
+ }]
1477
+ };
1478
+ }
1479
+
1480
+ // Convenience tool handlers
1481
+ case 'SN-List-Incidents': {
1482
+ const { state, priority, query, limit = 25, offset, fields, order_by } = args;
1483
+
1484
+ let finalQuery = query || '';
1485
+ if (state && !finalQuery.includes('state')) {
1486
+ finalQuery += (finalQuery ? '^' : '') + `state=${state}`;
1487
+ }
1488
+ if (priority && !finalQuery.includes('priority')) {
1489
+ finalQuery += (finalQuery ? '^' : '') + `priority=${priority}`;
1490
+ }
1491
+
1492
+ const queryParams = {
1493
+ sysparm_limit: limit,
1494
+ sysparm_query: finalQuery || undefined,
1495
+ sysparm_fields: fields,
1496
+ sysparm_offset: offset
1497
+ };
1498
+
1499
+ if (order_by) {
1500
+ queryParams.sysparm_order_by = order_by;
1501
+ }
1502
+
1503
+ const results = await serviceNowClient.getRecords('incident', queryParams);
1504
+
1505
+ return {
1506
+ content: [{
1507
+ type: 'text',
1508
+ text: `Found ${results.length} Incident(s):\n${JSON.stringify(results, null, 2)}`
1509
+ }]
1510
+ };
1511
+ }
1512
+
1513
+ case 'SN-Create-Incident': {
1514
+ const result = await serviceNowClient.createRecord('incident', args);
1515
+ return {
1516
+ content: [{
1517
+ type: 'text',
1518
+ text: `Created Incident: ${result.number}\n${JSON.stringify(result, null, 2)}`
1519
+ }]
1520
+ };
1521
+ }
1522
+
1523
+ case 'SN-Get-Incident': {
1524
+ const result = await serviceNowClient.getRecord('incident', args.sys_id);
1525
+ return {
1526
+ content: [{
1527
+ type: 'text',
1528
+ text: JSON.stringify(result, null, 2)
1529
+ }]
1530
+ };
1531
+ }
1532
+
1533
+ case 'SN-List-SysUsers': {
1534
+ const { query, limit = 25, offset, fields, order_by } = args;
1535
+
1536
+ const queryParams = {
1537
+ sysparm_limit: limit,
1538
+ sysparm_query: query,
1539
+ sysparm_fields: fields,
1540
+ sysparm_offset: offset
1541
+ };
1542
+
1543
+ if (order_by) {
1544
+ queryParams.sysparm_order_by = order_by;
1545
+ }
1546
+
1547
+ const results = await serviceNowClient.getRecords('sys_user', queryParams);
1548
+
1549
+ return {
1550
+ content: [{
1551
+ type: 'text',
1552
+ text: `Found ${results.length} Sys User(s):\n${JSON.stringify(results, null, 2)}`
1553
+ }]
1554
+ };
1555
+ }
1556
+
1557
+ case 'SN-List-CmdbCis': {
1558
+ const { query, limit = 25, offset, fields, order_by } = args;
1559
+
1560
+ const queryParams = {
1561
+ sysparm_limit: limit,
1562
+ sysparm_query: query,
1563
+ sysparm_fields: fields,
1564
+ sysparm_offset: offset
1565
+ };
1566
+
1567
+ if (order_by) {
1568
+ queryParams.sysparm_order_by = order_by;
1569
+ }
1570
+
1571
+ const results = await serviceNowClient.getRecords('cmdb_ci', queryParams);
1572
+
1573
+ return {
1574
+ content: [{
1575
+ type: 'text',
1576
+ text: `Found ${results.length} Cmdb Ci(s):\n${JSON.stringify(results, null, 2)}`
1577
+ }]
1578
+ };
1579
+ }
1580
+
1581
+ case 'SN-List-SysUserGroups': {
1582
+ const { query, limit = 25, offset, fields, order_by } = args;
1583
+
1584
+ const queryParams = {
1585
+ sysparm_limit: limit,
1586
+ sysparm_query: query,
1587
+ sysparm_fields: fields,
1588
+ sysparm_offset: offset
1589
+ };
1590
+
1591
+ if (order_by) {
1592
+ queryParams.sysparm_order_by = order_by;
1593
+ }
1594
+
1595
+ const results = await serviceNowClient.getRecords('sys_user_group', queryParams);
1596
+
1597
+ return {
1598
+ content: [{
1599
+ type: 'text',
1600
+ text: `Found ${results.length} Sys User Group(s):\n${JSON.stringify(results, null, 2)}`
1601
+ }]
1602
+ };
1603
+ }
1604
+
1605
+ case 'SN-List-ChangeRequests': {
1606
+ const { state, type, query, limit = 25, offset, fields, order_by } = args;
1607
+
1608
+ let finalQuery = query || '';
1609
+ if (state && !finalQuery.includes('state')) {
1610
+ finalQuery += (finalQuery ? '^' : '') + `state=${state}`;
1611
+ }
1612
+ if (type && !finalQuery.includes('type')) {
1613
+ finalQuery += (finalQuery ? '^' : '') + `type=${type}`;
1614
+ }
1615
+
1616
+ const queryParams = {
1617
+ sysparm_limit: limit,
1618
+ sysparm_query: finalQuery || undefined,
1619
+ sysparm_fields: fields,
1620
+ sysparm_offset: offset
1621
+ };
1622
+
1623
+ if (order_by) {
1624
+ queryParams.sysparm_order_by = order_by;
1625
+ }
1626
+
1627
+ const results = await serviceNowClient.getRecords('change_request', queryParams);
1628
+
1629
+ return {
1630
+ content: [{
1631
+ type: 'text',
1632
+ text: `Found ${results.length} Change Request(s):\n${JSON.stringify(results, null, 2)}`
1633
+ }]
1634
+ };
1635
+ }
1636
+
1637
+ case 'SN-List-Problems': {
1638
+ const { query, limit = 25, offset, fields, order_by } = args;
1639
+
1640
+ const queryParams = {
1641
+ sysparm_limit: limit,
1642
+ sysparm_query: query,
1643
+ sysparm_fields: fields,
1644
+ sysparm_offset: offset
1645
+ };
1646
+
1647
+ if (order_by) {
1648
+ queryParams.sysparm_order_by = order_by;
1649
+ }
1650
+
1651
+ const results = await serviceNowClient.getRecords('problem', queryParams);
1652
+
1653
+ return {
1654
+ content: [{
1655
+ type: 'text',
1656
+ text: `Found ${results.length} Problem(s):\n${JSON.stringify(results, null, 2)}`
1657
+ }]
1658
+ };
1659
+ }
1660
+
1661
+ case 'SN-Natural-Language-Search': {
1662
+ const { query, table = 'incident', limit = 25, fields, order_by, show_patterns = true } = args;
1663
+
1664
+ console.error(`🔍 Natural language search: "${query}" on ${table}`);
1665
+
1666
+ // Parse natural language query
1667
+ const parseResult = parseNaturalLanguage(query, table);
1668
+
1669
+ // Check if parsing succeeded
1670
+ if (!parseResult.encodedQuery) {
1671
+ return {
1672
+ content: [{
1673
+ type: 'text',
1674
+ text: `❌ Unable to parse query: "${query}"
1675
+
1676
+ ${parseResult.suggestions.join('\n')}
1677
+
1678
+ Unmatched text: "${parseResult.unmatchedText}"
1679
+
1680
+ ${show_patterns ? `\n## Supported Patterns:\n${JSON.stringify(getSupportedPatterns(), null, 2)}` : ''}`
1681
+ }]
1682
+ };
1683
+ }
1684
+
1685
+ // Execute the encoded query
1686
+ const queryParams = {
1687
+ sysparm_limit: limit,
1688
+ sysparm_query: parseResult.encodedQuery,
1689
+ sysparm_fields: fields,
1690
+ sysparm_offset: 0
1691
+ };
1692
+
1693
+ if (order_by) {
1694
+ queryParams.sysparm_order_by = order_by;
1695
+ }
1696
+
1697
+ const results = await serviceNowClient.getRecords(table, queryParams);
1698
+
1699
+ // Build response
1700
+ let responseText = `✅ Natural Language Search Results
1701
+
1702
+ **Original Query:** "${query}"
1703
+ **Target Table:** ${table}
1704
+ **Parsed Encoded Query:** \`${parseResult.encodedQuery}\`
1705
+ **Records Found:** ${results.length}/${limit}
1706
+
1707
+ `;
1708
+
1709
+ // Add pattern matching details if requested
1710
+ if (show_patterns && parseResult.matchedPatterns.length > 0) {
1711
+ responseText += `## Matched Patterns:\n`;
1712
+ parseResult.matchedPatterns.forEach((p, idx) => {
1713
+ responseText += `${idx + 1}. **"${p.matched}"** → \`${p.condition}\`\n`;
1714
+ });
1715
+ responseText += `\n`;
1716
+ }
1717
+
1718
+ // Add warnings for unmatched text
1719
+ if (parseResult.unmatchedText && parseResult.unmatchedText.length > 3) {
1720
+ responseText += `⚠️ **Unrecognized:** "${parseResult.unmatchedText}"\n\n`;
1721
+ }
1722
+
1723
+ // Add results
1724
+ if (results.length > 0) {
1725
+ responseText += `## Results:\n\`\`\`json\n${JSON.stringify(results, null, 2)}\n\`\`\``;
1726
+ } else {
1727
+ responseText += `## No records found matching the query.\n\nTry adjusting your search criteria or use SN-Query-Table for more control.`;
1728
+ }
1729
+
1730
+ return {
1731
+ content: [{
1732
+ type: 'text',
1733
+ text: responseText
1734
+ }]
1735
+ };
1736
+ }
1737
+
1738
+ case 'SN-Set-Update-Set': {
1739
+ const { update_set_sys_id } = args;
1740
+
1741
+ console.error(`🔄 Setting current update set to: ${update_set_sys_id}`);
1742
+
1743
+ try {
1744
+ // Try to set via API (UI endpoint or sys_trigger)
1745
+ const result = await serviceNowClient.setCurrentUpdateSet(update_set_sys_id);
1746
+
1747
+ if (result.method === 'sys_trigger') {
1748
+ return {
1749
+ content: [{
1750
+ type: 'text',
1751
+ text: `✅ Update set change scheduled via sys_trigger!
1752
+
1753
+ Update Set: ${result.update_set}
1754
+ sys_id: ${result.sys_id}
1755
+
1756
+ 🔧 Method: sys_trigger (scheduled job)
1757
+ 📊 Trigger Details:
1758
+ - Trigger sys_id: ${result.trigger_details.trigger_sys_id}
1759
+ - Trigger name: ${result.trigger_details.trigger_name}
1760
+ - Scheduled time: ${result.trigger_details.next_action}
1761
+ - Auto-delete: ${result.trigger_details.auto_delete ? 'Yes' : 'No'}
1762
+
1763
+ The script will execute in ~1 second and set your current update set. Refresh your ServiceNow browser after 2 seconds to see the change in the top bar.`
1764
+ }]
1765
+ };
1766
+ } else {
1767
+ return {
1768
+ content: [{
1769
+ type: 'text',
1770
+ text: `✅ Update set set to current: ${result.update_set}
1771
+
1772
+ 🔧 Method: UI API endpoint (/api/now/ui/concoursepicker/updateset)
1773
+ 📊 Response: ${JSON.stringify(result.response, null, 2)}
1774
+
1775
+ The update set has been set as your current update set. Refresh your ServiceNow browser to see the change in the top bar.`
1776
+ }]
1777
+ };
1778
+ }
1779
+ } catch (error) {
1780
+ // If both methods fail, fall back to creating fix script
1781
+ console.error('⚠️ Direct update set change failed, creating fix script...');
1782
+
1783
+ const updateSet = await serviceNowClient.getRecord('sys_update_set', update_set_sys_id);
1784
+
1785
+ const fs = await import('fs/promises');
1786
+ const path = await import('path');
1787
+
1788
+ const scriptsDir = path.resolve(process.cwd(), 'scripts');
1789
+ await fs.mkdir(scriptsDir, { recursive: true });
1790
+
1791
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
1792
+ const fileName = `set_update_set_${updateSet.name?.replace(/[^a-zA-Z0-9]/g, '_')}_${timestamp}.js`;
1793
+ const filePath = path.join(scriptsDir, fileName);
1794
+
1795
+ const scriptContent = `// Set current update set using GlideUpdateSet API
1796
+ var gus = new GlideUpdateSet();
1797
+ gus.set('${update_set_sys_id}');
1798
+ gs.info('✅ Update set changed to: ${updateSet.name}');`;
1799
+
1800
+ const fileContent = `/**
1801
+ * Fix Script: Set Current Update Set
1802
+ * Update Set: ${updateSet.name}
1803
+ * Update Set sys_id: ${update_set_sys_id}
1804
+ * Created: ${new Date().toISOString()}
1805
+ *
1806
+ * Note: Automated methods failed. Manual execution required.
1807
+ *
1808
+ * INSTRUCTIONS:
1809
+ * 1. Copy the script below (the GlideUpdateSet part)
1810
+ * 2. Navigate to ServiceNow: System Definition → Scripts - Background
1811
+ * 3. Paste the script
1812
+ * 4. Click "Run script"
1813
+ * 5. Verify output: "Update set changed to: ${updateSet.name}"
1814
+ * 6. Refresh your browser to see the update set in the top bar
1815
+ *
1816
+ * ALTERNATIVE: Manual UI Method
1817
+ * 1. Navigate to: System Update Sets → Local Update Sets
1818
+ * 2. Find: ${updateSet.name}
1819
+ * 3. Click "Make this my current set"
1820
+ */
1821
+
1822
+ ${scriptContent}`;
1823
+
1824
+ await fs.writeFile(filePath, fileContent, 'utf-8');
1825
+
1826
+ return {
1827
+ content: [{
1828
+ type: 'text',
1829
+ text: `⚠️ Automated update set change not available.
1830
+ Created fix script for manual execution: ${filePath}
1831
+
1832
+ Update Set: ${updateSet.name}
1833
+ Sys ID: ${update_set_sys_id}
1834
+
1835
+ 🔧 To Apply:
1836
+ 1. Open: ${filePath}
1837
+ 2. Copy the GlideUpdateSet script
1838
+ 3. Run in ServiceNow: System Definition → Scripts - Background
1839
+ 4. Refresh browser to see change
1840
+
1841
+ 💡 Alternative: Set manually in UI (System Update Sets → Local Update Sets → Make Current)`
1842
+ }]
1843
+ };
1844
+ }
1845
+ }
1846
+
1847
+ case 'SN-Get-Current-Update-Set': {
1848
+ const result = await serviceNowClient.getCurrentUpdateSet();
1849
+
1850
+ return {
1851
+ content: [{
1852
+ type: 'text',
1853
+ text: `Current update set:\n${JSON.stringify(result, null, 2)}`
1854
+ }]
1855
+ };
1856
+ }
1857
+
1858
+ case 'SN-List-Update-Sets': {
1859
+ const { query, limit = 25, offset, fields, order_by } = args;
1860
+
1861
+ const queryParams = {
1862
+ sysparm_limit: limit,
1863
+ sysparm_query: query,
1864
+ sysparm_fields: fields,
1865
+ sysparm_offset: offset
1866
+ };
1867
+
1868
+ if (order_by) {
1869
+ queryParams.sysparm_order_by = order_by;
1870
+ }
1871
+
1872
+ const results = await serviceNowClient.listUpdateSets(queryParams);
1873
+
1874
+ return {
1875
+ content: [{
1876
+ type: 'text',
1877
+ text: `Found ${results.length} Update Set(s):\n${JSON.stringify(results, null, 2)}`
1878
+ }]
1879
+ };
1880
+ }
1881
+
1882
+ case 'SN-Set-Current-Application': {
1883
+ const { app_sys_id } = args;
1884
+
1885
+ console.error(`🔄 Setting current application to: ${app_sys_id}`);
1886
+
1887
+ try {
1888
+ const result = await serviceNowClient.setCurrentApplication(app_sys_id);
1889
+
1890
+ return {
1891
+ content: [{
1892
+ type: 'text',
1893
+ text: `✅ Application set to current: ${result.application}
1894
+
1895
+ 🔧 Method: UI API endpoint (/api/now/ui/concoursepicker/application)
1896
+ 📊 Response: ${JSON.stringify(result.response, null, 2)}
1897
+
1898
+ The application scope has been set as your current application. Refresh your ServiceNow browser to see the change in the top bar.`
1899
+ }]
1900
+ };
1901
+ } catch (error) {
1902
+ console.error('❌ Failed to set current application:', error);
1903
+ return {
1904
+ content: [{
1905
+ type: 'text',
1906
+ text: `❌ Failed to set current application: ${error.message}
1907
+
1908
+ Please verify:
1909
+ 1. The app_sys_id is valid
1910
+ 2. You have permissions to access the application
1911
+ 3. The application exists in your instance`
1912
+ }]
1913
+ };
1914
+ }
1915
+ }
1916
+
1917
+ case 'SN-Execute-Background-Script': {
1918
+ const { script, description } = args;
1919
+
1920
+ console.error(`🚀 Executing background script via sys_trigger...`);
1921
+
1922
+ try {
1923
+ // Primary method: sys_trigger (ONLY working method)
1924
+ const result = await serviceNowClient.executeScriptViaTrigger(script, description, true);
1925
+
1926
+ return {
1927
+ content: [{
1928
+ type: 'text',
1929
+ text: `✅ Script scheduled for execution via sys_trigger!
1930
+
1931
+ ${description ? `Description: ${description}\n` : ''}
1932
+ 📊 Trigger Details:
1933
+ - Trigger sys_id: ${result.trigger_sys_id}
1934
+ - Trigger name: ${result.trigger_name}
1935
+ - Scheduled time: ${result.next_action}
1936
+ - Auto-delete: ${result.auto_delete ? 'Yes' : 'No'}
1937
+
1938
+ ${result.message}
1939
+
1940
+ The script will execute in ~1 second. You can monitor execution in:
1941
+ - System Logs → System Log → All
1942
+ - System Definition → Scheduled Jobs (filter by name: ${result.trigger_name})
1943
+
1944
+ 🔍 Script to execute:
1945
+ ${script.substring(0, 300)}${script.length > 300 ? '...' : ''}`
1946
+ }]
1947
+ };
1948
+ } catch (triggerError) {
1949
+ // Fallback: Create fix script if sys_trigger fails
1950
+ console.error('⚠️ Trigger method failed, creating fix script...', triggerError.message);
1951
+
1952
+ // Fallback: Create fix script file
1953
+ const fs = await import('fs/promises');
1954
+ const path = await import('path');
1955
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
1956
+ const script_name = `background_script_${timestamp}`;
1957
+
1958
+ const scriptsDir = path.resolve(process.cwd(), 'scripts');
1959
+ await fs.mkdir(scriptsDir, { recursive: true });
1960
+
1961
+ const fileName = `${script_name}.js`;
1962
+ const filePath = path.join(scriptsDir, fileName);
1963
+
1964
+ const fileContent = `/**
1965
+ * Background Script (Manual Execution Required)
1966
+ * Created: ${new Date().toISOString()}
1967
+ * ${description ? `Description: ${description}` : ''}
1968
+ *
1969
+ * Note: Direct execution failed due to authentication requirements.
1970
+ * This script must be executed manually in ServiceNow UI.
1971
+ *
1972
+ * INSTRUCTIONS:
1973
+ * 1. Copy the script below
1974
+ * 2. Navigate to ServiceNow: System Definition → Scripts - Background
1975
+ * 3. Paste the script
1976
+ * 4. Click "Run script"
1977
+ * 5. Verify output in the output panel
1978
+ */
1979
+
1980
+ ${script}
1981
+
1982
+ // End of script
1983
+ `;
1984
+
1985
+ await fs.writeFile(filePath, fileContent, 'utf-8');
1986
+
1987
+ return {
1988
+ content: [{
1989
+ type: 'text',
1990
+ text: `⚠️ Direct execution not available (requires UI session).
1991
+ Created fix script for manual execution: ${filePath}
1992
+
1993
+ 📋 To Execute Manually:
1994
+ 1. Open: ${filePath}
1995
+ 2. Copy the script content
1996
+ 3. In ServiceNow: System Definition → Scripts - Background
1997
+ 4. Paste and click "Run script"
1998
+
1999
+ Script Preview:
2000
+ ${script.substring(0, 200)}${script.length > 200 ? '...' : ''}`
2001
+ }]
2002
+ };
2003
+ }
2004
+ }
2005
+
2006
+ case 'SN-Create-Fix-Script': {
2007
+ const { script_name, script_content, description, auto_delete = false } = args;
2008
+
2009
+ console.error(`📝 Creating fix script: ${script_name}`);
2010
+
2011
+ // Import fs for file operations
2012
+ const fs = await import('fs/promises');
2013
+ const path = await import('path');
2014
+
2015
+ // Ensure /scripts directory exists
2016
+ const scriptsDir = path.resolve(process.cwd(), 'scripts');
2017
+ await fs.mkdir(scriptsDir, { recursive: true });
2018
+
2019
+ // Generate script file with header
2020
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
2021
+ const fileName = `${script_name}_${timestamp}.js`;
2022
+ const filePath = path.join(scriptsDir, fileName);
2023
+
2024
+ const fileContent = `/**
2025
+ * Fix Script: ${script_name}
2026
+ * Created: ${new Date().toISOString()}
2027
+ * ${description ? `Description: ${description}` : ''}
2028
+ *
2029
+ * INSTRUCTIONS:
2030
+ * 1. Copy the entire script below
2031
+ * 2. Navigate to ServiceNow: System Definition → Scripts - Background
2032
+ * 3. Paste the script
2033
+ * 4. Click "Run script"
2034
+ * 5. Verify output in the output panel
2035
+ * ${auto_delete ? '6. Delete this file after successful execution' : ''}
2036
+ */
2037
+
2038
+ ${script_content}
2039
+
2040
+ // End of script
2041
+ `;
2042
+
2043
+ await fs.writeFile(filePath, fileContent, 'utf-8');
2044
+
2045
+ return {
2046
+ content: [{
2047
+ type: 'text',
2048
+ text: `✅ Fix script created: ${filePath}
2049
+
2050
+ 📋 Next Steps:
2051
+ 1. Open the file: ${filePath}
2052
+ 2. Copy the entire script content
2053
+ 3. In ServiceNow, navigate to: System Definition → Scripts - Background
2054
+ 4. Paste and run the script
2055
+ 5. Verify the output${auto_delete ? '\n6. Delete the file after successful execution' : ''}
2056
+
2057
+ Script Preview (first 200 chars):
2058
+ ${script_content.substring(0, 200)}${script_content.length > 200 ? '...' : ''}`
2059
+ }]
2060
+ };
2061
+ }
2062
+
2063
+ case 'SN-Discover-Table-Schema': {
2064
+ const {
2065
+ table_name,
2066
+ include_type_codes = false,
2067
+ include_choice_tables = false,
2068
+ include_relationships = false,
2069
+ include_ui_policies = false,
2070
+ include_business_rules = false,
2071
+ include_field_constraints = false
2072
+ } = args;
2073
+
2074
+ console.error(`🔍 Discovering enhanced schema for ${table_name}`);
2075
+ const schema = await serviceNowClient.discoverTableSchema(table_name, {
2076
+ include_type_codes,
2077
+ include_choice_tables,
2078
+ include_relationships,
2079
+ include_ui_policies,
2080
+ include_business_rules,
2081
+ include_field_constraints
2082
+ });
2083
+
2084
+ return {
2085
+ content: [{
2086
+ type: 'text',
2087
+ text: `Enhanced schema for ${table_name}:\n${JSON.stringify(schema, null, 2)}`
2088
+ }]
2089
+ };
2090
+ }
2091
+
2092
+ case 'SN-Batch-Create': {
2093
+ const { operations, transaction = true, progress = true } = args;
2094
+
2095
+ console.error(`📦 Batch creating ${operations.length} records (transaction: ${transaction}, progress: ${progress})`);
2096
+ const result = await serviceNowClient.batchCreate(operations, transaction, progress);
2097
+
2098
+ return {
2099
+ content: [{
2100
+ type: 'text',
2101
+ text: `Batch create ${result.success ? 'completed' : 'failed'}:\n${JSON.stringify(result, null, 2)}`
2102
+ }]
2103
+ };
2104
+ }
2105
+
2106
+ case 'SN-Batch-Update': {
2107
+ const { updates, stop_on_error = false, progress = true } = args;
2108
+
2109
+ console.error(`📦 Batch updating ${updates.length} records (progress: ${progress})`);
2110
+ const result = await serviceNowClient.batchUpdate(updates, stop_on_error, progress);
2111
+
2112
+ return {
2113
+ content: [{
2114
+ type: 'text',
2115
+ text: `Batch update ${result.success ? 'completed' : 'completed with errors'}:\n${JSON.stringify(result, null, 2)}`
2116
+ }]
2117
+ };
2118
+ }
2119
+
2120
+ case 'SN-Explain-Field': {
2121
+ const { table, field, include_examples = true } = args;
2122
+
2123
+ console.error(`📖 Explaining field ${table}.${field}`);
2124
+ const explanation = await serviceNowClient.explainField(table, field, include_examples);
2125
+
2126
+ return {
2127
+ content: [{
2128
+ type: 'text',
2129
+ text: `Field explanation for ${table}.${field}:\n${JSON.stringify(explanation, null, 2)}`
2130
+ }]
2131
+ };
2132
+ }
2133
+
2134
+ case 'SN-Validate-Configuration': {
2135
+ const { catalog_item, checks = {} } = args;
2136
+
2137
+ console.error(`✅ Validating catalog item ${catalog_item}`);
2138
+ const validation = await serviceNowClient.validateCatalogConfiguration(catalog_item, checks);
2139
+
2140
+ return {
2141
+ content: [{
2142
+ type: 'text',
2143
+ text: `Validation ${validation.valid ? 'PASSED' : 'FAILED'}:\n${JSON.stringify(validation, null, 2)}`
2144
+ }]
2145
+ };
2146
+ }
2147
+
2148
+ case 'SN-Inspect-Update-Set': {
2149
+ const { update_set, show_components = true, show_dependencies = false } = args;
2150
+
2151
+ console.error(`🔎 Inspecting update set ${update_set}`);
2152
+ const inspection = await serviceNowClient.inspectUpdateSet(update_set, {
2153
+ show_components,
2154
+ show_dependencies
2155
+ });
2156
+
2157
+ return {
2158
+ content: [{
2159
+ type: 'text',
2160
+ text: `Update set inspection:\n${JSON.stringify(inspection, null, 2)}`
2161
+ }]
2162
+ };
2163
+ }
2164
+
2165
+ case 'SN-Create-Workflow': {
2166
+ const { name, description, table, condition, activities, transitions, publish = false, progress = true } = args;
2167
+
2168
+ console.error(`🔄 Creating workflow: ${name} (progress: ${progress})`);
2169
+
2170
+ // Build workflow specification
2171
+ const workflowSpec = {
2172
+ name,
2173
+ description,
2174
+ table,
2175
+ condition,
2176
+ activities,
2177
+ transitions,
2178
+ publish
2179
+ };
2180
+
2181
+ const result = await serviceNowClient.createCompleteWorkflow(workflowSpec, progress);
2182
+
2183
+ return {
2184
+ content: [{
2185
+ type: 'text',
2186
+ text: `✅ Workflow created successfully!
2187
+
2188
+ Workflow: ${result.workflow_name}
2189
+ Workflow sys_id: ${result.workflow_sys_id}
2190
+ Version sys_id: ${result.version_sys_id}
2191
+ Status: ${result.published ? 'Published' : 'Draft'}
2192
+
2193
+ Activities created: ${result.activities.length}
2194
+ ${result.activities.map(a => ` - ${a.name} (${a.activity_sys_id})`).join('\n')}
2195
+
2196
+ Transitions created: ${result.transitions.length}
2197
+
2198
+ ${result.published ? '✅ Workflow is published and ready to use!' : '⚠️ Workflow is in draft mode. Use SN-Publish-Workflow to publish it.'}
2199
+
2200
+ Full result:
2201
+ ${JSON.stringify(result, null, 2)}`
2202
+ }]
2203
+ };
2204
+ }
2205
+
2206
+ case 'SN-Create-Activity': {
2207
+ const { workflow_version_sys_id, name, script, activity_definition_sys_id, x, y } = args;
2208
+
2209
+ console.error(`➕ Creating activity: ${name}`);
2210
+
2211
+ const activityData = {
2212
+ workflow_version_sys_id,
2213
+ name,
2214
+ script,
2215
+ activity_definition_sys_id,
2216
+ x,
2217
+ y
2218
+ };
2219
+
2220
+ const result = await serviceNowClient.createActivity(activityData);
2221
+
2222
+ return {
2223
+ content: [{
2224
+ type: 'text',
2225
+ text: `✅ Activity created successfully!
2226
+
2227
+ Activity: ${result.name}
2228
+ Activity sys_id: ${result.activity_sys_id}
2229
+
2230
+ You can now:
2231
+ - Create transitions to/from this activity using SN-Create-Transition
2232
+ - Add this activity to workflow canvas in ServiceNow UI`
2233
+ }]
2234
+ };
2235
+ }
2236
+
2237
+ case 'SN-Create-Transition': {
2238
+ const { from_activity_sys_id, to_activity_sys_id, condition_script, order } = args;
2239
+
2240
+ console.error(`🔗 Creating transition`);
2241
+
2242
+ const transitionData = {
2243
+ from_activity_sys_id,
2244
+ to_activity_sys_id,
2245
+ order
2246
+ };
2247
+
2248
+ // If condition script provided, create condition first
2249
+ let condition_sys_id = null;
2250
+ if (condition_script) {
2251
+ const conditionData = {
2252
+ activity_sys_id: from_activity_sys_id,
2253
+ name: 'Transition Condition',
2254
+ condition: condition_script
2255
+ };
2256
+ const conditionResult = await serviceNowClient.createCondition(conditionData);
2257
+ condition_sys_id = conditionResult.condition_sys_id;
2258
+ transitionData.condition_sys_id = condition_sys_id;
2259
+ }
2260
+
2261
+ const result = await serviceNowClient.createTransition(transitionData);
2262
+
2263
+ return {
2264
+ content: [{
2265
+ type: 'text',
2266
+ text: `✅ Transition created successfully!
2267
+
2268
+ Transition sys_id: ${result.transition_sys_id}
2269
+ From activity: ${from_activity_sys_id}
2270
+ To activity: ${to_activity_sys_id}
2271
+ ${condition_sys_id ? `Condition sys_id: ${condition_sys_id}` : 'No condition (always transitions)'}
2272
+
2273
+ The workflow will now transition from the source activity to the target activity${condition_script ? ' when the condition is met' : ''}.`
2274
+ }]
2275
+ };
2276
+ }
2277
+
2278
+ case 'SN-Publish-Workflow': {
2279
+ const { version_sys_id, start_activity_sys_id } = args;
2280
+
2281
+ console.error(`🚀 Publishing workflow version ${version_sys_id}`);
2282
+
2283
+ const result = await serviceNowClient.publishWorkflow(version_sys_id, start_activity_sys_id);
2284
+
2285
+ return {
2286
+ content: [{
2287
+ type: 'text',
2288
+ text: `✅ Workflow published successfully!
2289
+
2290
+ Version sys_id: ${result.version_sys_id}
2291
+ Start activity: ${result.start_activity}
2292
+ Status: Published
2293
+
2294
+ The workflow is now active and will trigger based on its configured conditions.`
2295
+ }]
2296
+ };
2297
+ }
2298
+
2299
+ case 'SN-Move-Records-To-Update-Set': {
2300
+ const { update_set_id, record_sys_ids, time_range, source_update_set, table, progress = true } = args;
2301
+
2302
+ console.error(`📦 Moving records to update set ${update_set_id} (progress: ${progress})`);
2303
+
2304
+ const result = await serviceNowClient.moveRecordsToUpdateSet(update_set_id, {
2305
+ record_sys_ids,
2306
+ time_range,
2307
+ source_update_set,
2308
+ table,
2309
+ reportProgress: progress
2310
+ });
2311
+
2312
+ return {
2313
+ content: [{
2314
+ type: 'text',
2315
+ text: `✅ Records moved to update set!
2316
+
2317
+ Moved: ${result.moved} records
2318
+ Failed: ${result.failed} records
2319
+
2320
+ ${result.records.length > 0 ? `\nMoved records:\n${result.records.map(r => ` - ${r.type}: ${r.name} (${r.sys_id})`).join('\n')}` : ''}
2321
+
2322
+ ${result.errors.length > 0 ? `\n❌ Errors:\n${result.errors.map(e => ` - ${e.sys_id}: ${e.error}`).join('\n')}` : ''}
2323
+
2324
+ Full result:
2325
+ ${JSON.stringify(result, null, 2)}`
2326
+ }]
2327
+ };
2328
+ }
2329
+
2330
+ case 'SN-Clone-Update-Set': {
2331
+ const { source_update_set_id, new_name, progress = true } = args;
2332
+
2333
+ console.error(`🔄 Cloning update set ${source_update_set_id} (progress: ${progress})`);
2334
+
2335
+ const result = await serviceNowClient.cloneUpdateSet(source_update_set_id, new_name, progress);
2336
+
2337
+ return {
2338
+ content: [{
2339
+ type: 'text',
2340
+ text: `✅ Update set cloned successfully!
2341
+
2342
+ Source Update Set: ${result.source_update_set_name}
2343
+ Source sys_id: ${result.source_update_set_id}
2344
+
2345
+ New Update Set: ${result.new_update_set_name}
2346
+ New sys_id: ${result.new_update_set_id}
2347
+
2348
+ Records cloned: ${result.records_cloned} / ${result.total_source_records}
2349
+
2350
+ The cloned update set is now in "In Progress" state and ready for use.`
2351
+ }]
2352
+ };
2353
+ }
2354
+
2355
+ // Incident convenience tool handlers
2356
+ case 'SN-Add-Comment': {
2357
+ const { incident_number, comment } = args;
2358
+
2359
+ // Look up incident by number
2360
+ const incidents = await serviceNowClient.getRecords('incident', {
2361
+ sysparm_query: `number=${incident_number}`,
2362
+ sysparm_limit: 1
2363
+ });
2364
+
2365
+ if (!incidents || incidents.length === 0) {
2366
+ throw new Error(`Incident ${incident_number} not found`);
2367
+ }
2368
+
2369
+ const incident = incidents[0];
2370
+
2371
+ // Update comments field
2372
+ const result = await serviceNowClient.updateRecord('incident', incident.sys_id, {
2373
+ comments: comment
2374
+ });
2375
+
2376
+ return {
2377
+ content: [{
2378
+ type: 'text',
2379
+ text: `✅ Comment added to ${incident_number}
2380
+
2381
+ Incident: ${incident_number}
2382
+ sys_id: ${incident.sys_id}
2383
+ Comment: ${comment}
2384
+ Updated: ${new Date().toISOString()}
2385
+
2386
+ The comment has been successfully added to the incident.`
2387
+ }]
2388
+ };
2389
+ }
2390
+
2391
+ case 'SN-Add-Work-Notes': {
2392
+ const { incident_number, work_notes } = args;
2393
+
2394
+ // Look up incident by number
2395
+ const incidents = await serviceNowClient.getRecords('incident', {
2396
+ sysparm_query: `number=${incident_number}`,
2397
+ sysparm_limit: 1
2398
+ });
2399
+
2400
+ if (!incidents || incidents.length === 0) {
2401
+ throw new Error(`Incident ${incident_number} not found`);
2402
+ }
2403
+
2404
+ const incident = incidents[0];
2405
+
2406
+ // Update work_notes field
2407
+ const result = await serviceNowClient.updateRecord('incident', incident.sys_id, {
2408
+ work_notes: work_notes
2409
+ });
2410
+
2411
+ return {
2412
+ content: [{
2413
+ type: 'text',
2414
+ text: `✅ Work notes added to ${incident_number}
2415
+
2416
+ Incident: ${incident_number}
2417
+ sys_id: ${incident.sys_id}
2418
+ Work Notes: ${work_notes}
2419
+ Updated: ${new Date().toISOString()}
2420
+
2421
+ The work notes have been successfully added to the incident.`
2422
+ }]
2423
+ };
2424
+ }
2425
+
2426
+ case 'SN-Assign-Incident': {
2427
+ const { incident_number, assigned_to, assignment_group } = args;
2428
+
2429
+ // Look up incident by number
2430
+ const incidents = await serviceNowClient.getRecords('incident', {
2431
+ sysparm_query: `number=${incident_number}`,
2432
+ sysparm_limit: 1
2433
+ });
2434
+
2435
+ if (!incidents || incidents.length === 0) {
2436
+ throw new Error(`Incident ${incident_number} not found`);
2437
+ }
2438
+
2439
+ const incident = incidents[0];
2440
+
2441
+ // Resolve user if not a sys_id (32 character hex string)
2442
+ let assignedToId = assigned_to;
2443
+ if (!/^[0-9a-f]{32}$/i.test(assigned_to)) {
2444
+ const users = await serviceNowClient.getRecords('sys_user', {
2445
+ sysparm_query: `name=${assigned_to}^ORuser_name=${assigned_to}`,
2446
+ sysparm_limit: 1
2447
+ });
2448
+
2449
+ if (!users || users.length === 0) {
2450
+ throw new Error(`User "${assigned_to}" not found`);
2451
+ }
2452
+
2453
+ assignedToId = users[0].sys_id;
2454
+ }
2455
+
2456
+ // Resolve group if provided and not a sys_id
2457
+ let assignmentGroupId = assignment_group;
2458
+ if (assignment_group && !/^[0-9a-f]{32}$/i.test(assignment_group)) {
2459
+ const groups = await serviceNowClient.getRecords('sys_user_group', {
2460
+ sysparm_query: `name=${assignment_group}`,
2461
+ sysparm_limit: 1
2462
+ });
2463
+
2464
+ if (!groups || groups.length === 0) {
2465
+ throw new Error(`Group "${assignment_group}" not found`);
2466
+ }
2467
+
2468
+ assignmentGroupId = groups[0].sys_id;
2469
+ }
2470
+
2471
+ // Update assignment fields
2472
+ const updateData = {
2473
+ assigned_to: assignedToId
2474
+ };
2475
+
2476
+ if (assignmentGroupId) {
2477
+ updateData.assignment_group = assignmentGroupId;
2478
+ }
2479
+
2480
+ const result = await serviceNowClient.updateRecord('incident', incident.sys_id, updateData);
2481
+
2482
+ return {
2483
+ content: [{
2484
+ type: 'text',
2485
+ text: `✅ ${incident_number} assigned successfully
2486
+
2487
+ Incident: ${incident_number}
2488
+ sys_id: ${incident.sys_id}
2489
+ Assigned To: ${result.assigned_to?.display_value || assignedToId}
2490
+ ${assignmentGroupId ? `Assignment Group: ${result.assignment_group?.display_value || assignmentGroupId}` : ''}
2491
+ Updated: ${new Date().toISOString()}
2492
+
2493
+ The incident has been assigned successfully.`
2494
+ }]
2495
+ };
2496
+ }
2497
+
2498
+ case 'SN-Resolve-Incident': {
2499
+ const { incident_number, resolution_notes, resolution_code } = args;
2500
+
2501
+ // Look up incident by number
2502
+ const incidents = await serviceNowClient.getRecords('incident', {
2503
+ sysparm_query: `number=${incident_number}`,
2504
+ sysparm_limit: 1
2505
+ });
2506
+
2507
+ if (!incidents || incidents.length === 0) {
2508
+ throw new Error(`Incident ${incident_number} not found`);
2509
+ }
2510
+
2511
+ const incident = incidents[0];
2512
+
2513
+ // Update to resolved state (6)
2514
+ const updateData = {
2515
+ state: 6,
2516
+ close_notes: resolution_notes
2517
+ };
2518
+
2519
+ if (resolution_code) {
2520
+ updateData.close_code = resolution_code;
2521
+ }
2522
+
2523
+ const result = await serviceNowClient.updateRecord('incident', incident.sys_id, updateData);
2524
+
2525
+ return {
2526
+ content: [{
2527
+ type: 'text',
2528
+ text: `✅ ${incident_number} resolved successfully
2529
+
2530
+ Incident: ${incident_number}
2531
+ sys_id: ${incident.sys_id}
2532
+ State: Resolved (6)
2533
+ Resolution Notes: ${resolution_notes}
2534
+ ${resolution_code ? `Resolution Code: ${resolution_code}` : ''}
2535
+ Updated: ${new Date().toISOString()}
2536
+
2537
+ The incident has been resolved successfully.`
2538
+ }]
2539
+ };
2540
+ }
2541
+
2542
+ case 'SN-Close-Incident': {
2543
+ const { incident_number, close_notes, close_code } = args;
2544
+
2545
+ // Look up incident by number
2546
+ const incidents = await serviceNowClient.getRecords('incident', {
2547
+ sysparm_query: `number=${incident_number}`,
2548
+ sysparm_limit: 1
2549
+ });
2550
+
2551
+ if (!incidents || incidents.length === 0) {
2552
+ throw new Error(`Incident ${incident_number} not found`);
2553
+ }
2554
+
2555
+ const incident = incidents[0];
2556
+
2557
+ // Update to closed state (7)
2558
+ const updateData = {
2559
+ state: 7,
2560
+ close_notes: close_notes
2561
+ };
2562
+
2563
+ if (close_code) {
2564
+ updateData.close_code = close_code;
2565
+ }
2566
+
2567
+ const result = await serviceNowClient.updateRecord('incident', incident.sys_id, updateData);
2568
+
2569
+ return {
2570
+ content: [{
2571
+ type: 'text',
2572
+ text: `✅ ${incident_number} closed successfully
2573
+
2574
+ Incident: ${incident_number}
2575
+ sys_id: ${incident.sys_id}
2576
+ State: Closed (7)
2577
+ Close Notes: ${close_notes}
2578
+ ${close_code ? `Close Code: ${close_code}` : ''}
2579
+ Updated: ${new Date().toISOString()}
2580
+
2581
+ The incident has been closed successfully.`
2582
+ }]
2583
+ };
2584
+ }
2585
+
2586
+ // Change Request convenience tool handlers
2587
+ case 'SN-Add-Change-Comment': {
2588
+ const { change_number, comment } = args;
2589
+
2590
+ // Look up change by number
2591
+ const changes = await serviceNowClient.getRecords('change_request', {
2592
+ sysparm_query: `number=${change_number}`,
2593
+ sysparm_limit: 1
2594
+ });
2595
+
2596
+ if (!changes || changes.length === 0) {
2597
+ throw new Error(`Change request ${change_number} not found`);
2598
+ }
2599
+
2600
+ const change = changes[0];
2601
+
2602
+ // Update comments field
2603
+ const result = await serviceNowClient.updateRecord('change_request', change.sys_id, {
2604
+ comments: comment
2605
+ });
2606
+
2607
+ return {
2608
+ content: [{
2609
+ type: 'text',
2610
+ text: `✅ Comment added to ${change_number}
2611
+
2612
+ Change Request: ${change_number}
2613
+ sys_id: ${change.sys_id}
2614
+ Comment: ${comment}
2615
+ Updated: ${new Date().toISOString()}
2616
+
2617
+ The comment has been successfully added to the change request.`
2618
+ }]
2619
+ };
2620
+ }
2621
+
2622
+ case 'SN-Assign-Change': {
2623
+ const { change_number, assigned_to, assignment_group } = args;
2624
+
2625
+ // Look up change by number
2626
+ const changes = await serviceNowClient.getRecords('change_request', {
2627
+ sysparm_query: `number=${change_number}`,
2628
+ sysparm_limit: 1
2629
+ });
2630
+
2631
+ if (!changes || changes.length === 0) {
2632
+ throw new Error(`Change request ${change_number} not found`);
2633
+ }
2634
+
2635
+ const change = changes[0];
2636
+
2637
+ // Resolve user if not a sys_id
2638
+ let assignedToId = assigned_to;
2639
+ if (!/^[0-9a-f]{32}$/i.test(assigned_to)) {
2640
+ const users = await serviceNowClient.getRecords('sys_user', {
2641
+ sysparm_query: `name=${assigned_to}^ORuser_name=${assigned_to}`,
2642
+ sysparm_limit: 1
2643
+ });
2644
+
2645
+ if (!users || users.length === 0) {
2646
+ throw new Error(`User "${assigned_to}" not found`);
2647
+ }
2648
+
2649
+ assignedToId = users[0].sys_id;
2650
+ }
2651
+
2652
+ // Resolve group if provided and not a sys_id
2653
+ let assignmentGroupId = assignment_group;
2654
+ if (assignment_group && !/^[0-9a-f]{32}$/i.test(assignment_group)) {
2655
+ const groups = await serviceNowClient.getRecords('sys_user_group', {
2656
+ sysparm_query: `name=${assignment_group}`,
2657
+ sysparm_limit: 1
2658
+ });
2659
+
2660
+ if (!groups || groups.length === 0) {
2661
+ throw new Error(`Group "${assignment_group}" not found`);
2662
+ }
2663
+
2664
+ assignmentGroupId = groups[0].sys_id;
2665
+ }
2666
+
2667
+ // Update assignment fields
2668
+ const updateData = {
2669
+ assigned_to: assignedToId
2670
+ };
2671
+
2672
+ if (assignmentGroupId) {
2673
+ updateData.assignment_group = assignmentGroupId;
2674
+ }
2675
+
2676
+ const result = await serviceNowClient.updateRecord('change_request', change.sys_id, updateData);
2677
+
2678
+ return {
2679
+ content: [{
2680
+ type: 'text',
2681
+ text: `✅ ${change_number} assigned successfully
2682
+
2683
+ Change Request: ${change_number}
2684
+ sys_id: ${change.sys_id}
2685
+ Assigned To: ${result.assigned_to?.display_value || assignedToId}
2686
+ ${assignmentGroupId ? `Assignment Group: ${result.assignment_group?.display_value || assignmentGroupId}` : ''}
2687
+ Updated: ${new Date().toISOString()}
2688
+
2689
+ The change request has been assigned successfully.`
2690
+ }]
2691
+ };
2692
+ }
2693
+
2694
+ case 'SN-Approve-Change': {
2695
+ const { change_number, approval_comments } = args;
2696
+
2697
+ // Look up change by number
2698
+ const changes = await serviceNowClient.getRecords('change_request', {
2699
+ sysparm_query: `number=${change_number}`,
2700
+ sysparm_limit: 1
2701
+ });
2702
+
2703
+ if (!changes || changes.length === 0) {
2704
+ throw new Error(`Change request ${change_number} not found`);
2705
+ }
2706
+
2707
+ const change = changes[0];
2708
+
2709
+ // Update approval field and add comments
2710
+ const updateData = {
2711
+ approval: 'approved'
2712
+ };
2713
+
2714
+ if (approval_comments) {
2715
+ updateData.comments = approval_comments;
2716
+ }
2717
+
2718
+ const result = await serviceNowClient.updateRecord('change_request', change.sys_id, updateData);
2719
+
2720
+ return {
2721
+ content: [{
2722
+ type: 'text',
2723
+ text: `✅ ${change_number} approved successfully
2724
+
2725
+ Change Request: ${change_number}
2726
+ sys_id: ${change.sys_id}
2727
+ Approval: approved
2728
+ ${approval_comments ? `Comments: ${approval_comments}` : ''}
2729
+ Updated: ${new Date().toISOString()}
2730
+
2731
+ The change request has been approved successfully.`
2732
+ }]
2733
+ };
2734
+ }
2735
+
2736
+ // Problem convenience tool handlers
2737
+ case 'SN-Add-Problem-Comment': {
2738
+ const { problem_number, comment } = args;
2739
+
2740
+ // Look up problem by number
2741
+ const problems = await serviceNowClient.getRecords('problem', {
2742
+ sysparm_query: `number=${problem_number}`,
2743
+ sysparm_limit: 1
2744
+ });
2745
+
2746
+ if (!problems || problems.length === 0) {
2747
+ throw new Error(`Problem ${problem_number} not found`);
2748
+ }
2749
+
2750
+ const problem = problems[0];
2751
+
2752
+ // Update comments field
2753
+ const result = await serviceNowClient.updateRecord('problem', problem.sys_id, {
2754
+ comments: comment
2755
+ });
2756
+
2757
+ return {
2758
+ content: [{
2759
+ type: 'text',
2760
+ text: `✅ Comment added to ${problem_number}
2761
+
2762
+ Problem: ${problem_number}
2763
+ sys_id: ${problem.sys_id}
2764
+ Comment: ${comment}
2765
+ Updated: ${new Date().toISOString()}
2766
+
2767
+ The comment has been successfully added to the problem.`
2768
+ }]
2769
+ };
2770
+ }
2771
+
2772
+ case 'SN-Close-Problem': {
2773
+ const { problem_number, resolution_notes, resolution_code } = args;
2774
+
2775
+ // Look up problem by number
2776
+ const problems = await serviceNowClient.getRecords('problem', {
2777
+ sysparm_query: `number=${problem_number}`,
2778
+ sysparm_limit: 1
2779
+ });
2780
+
2781
+ if (!problems || problems.length === 0) {
2782
+ throw new Error(`Problem ${problem_number} not found`);
2783
+ }
2784
+
2785
+ const problem = problems[0];
2786
+
2787
+ // Update to resolved/closed state
2788
+ const updateData = {
2789
+ state: 3, // Resolved/Closed state for problem
2790
+ resolution_notes: resolution_notes
2791
+ };
2792
+
2793
+ if (resolution_code) {
2794
+ updateData.resolution_code = resolution_code;
2795
+ }
2796
+
2797
+ const result = await serviceNowClient.updateRecord('problem', problem.sys_id, updateData);
2798
+
2799
+ return {
2800
+ content: [{
2801
+ type: 'text',
2802
+ text: `✅ ${problem_number} closed successfully
2803
+
2804
+ Problem: ${problem_number}
2805
+ sys_id: ${problem.sys_id}
2806
+ State: Resolved/Closed (3)
2807
+ Resolution Notes: ${resolution_notes}
2808
+ ${resolution_code ? `Resolution Code: ${resolution_code}` : ''}
2809
+ Updated: ${new Date().toISOString()}
2810
+
2811
+ The problem has been closed successfully.`
2812
+ }]
2813
+ };
2814
+ }
2815
+
2816
+ default:
2817
+ throw new Error(`Unknown tool: ${name}`);
2818
+ }
2819
+ } catch (error) {
2820
+ return {
2821
+ content: [{
2822
+ type: 'text',
2823
+ text: `Error: ${error.message}`
2824
+ }],
2825
+ isError: true
2826
+ };
2827
+ }
2828
+ });
2829
+
2830
+ // Add resources
2831
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
2832
+ return {
2833
+ resources: [
2834
+ {
2835
+ uri: 'servicenow://instance',
2836
+ mimeType: 'application/json',
2837
+ name: 'ServiceNow Instance Info',
2838
+ description: 'Information about the connected ServiceNow instance'
2839
+ },
2840
+ {
2841
+ uri: 'servicenow://tables/all',
2842
+ mimeType: 'application/json',
2843
+ name: 'All ServiceNow Tables',
2844
+ description: 'Complete list of available ServiceNow tables with metadata'
2845
+ }
2846
+ ]
2847
+ };
2848
+ });
2849
+
2850
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2851
+ const { uri } = request.params;
2852
+
2853
+ if (uri === 'servicenow://instance') {
2854
+ const config = {
2855
+ server_info: {
2856
+ name: 'ServiceNow MCP Server (Consolidated)',
2857
+ version: '2.0.0',
2858
+ description: 'Consolidated ServiceNow integration with metadata-driven schema lookups'
2859
+ },
2860
+ instance_info: {
2861
+ url: process.env.SERVICENOW_INSTANCE_URL,
2862
+ username: process.env.SERVICENOW_USERNAME
2863
+ },
2864
+ capabilities: {
2865
+ total_tables: Object.keys(tableMetadata).length,
2866
+ operations: ['create', 'read', 'update', 'query', 'schema_lookup'],
2867
+ tools: 6
2868
+ }
2869
+ };
2870
+
2871
+ return {
2872
+ contents: [{
2873
+ uri: uri,
2874
+ mimeType: 'application/json',
2875
+ text: JSON.stringify(config, null, 2)
2876
+ }]
2877
+ };
2878
+ }
2879
+
2880
+ if (uri === 'servicenow://tables/all') {
2881
+ return {
2882
+ contents: [{
2883
+ uri: uri,
2884
+ mimeType: 'application/json',
2885
+ text: JSON.stringify(tableMetadata, null, 2)
2886
+ }]
2887
+ };
2888
+ }
2889
+
2890
+ throw new Error(`Unknown resource: ${uri}`);
2891
+ });
2892
+
2893
+ return server;
2894
+ }