snow-flow 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/{.claude/mcp-config.json → .mcp.json} +13 -12
  2. package/.releaserc.json +47 -0
  3. package/README.md +48 -1
  4. package/package.json +1 -1
  5. package/.claude/claude-settings.json +0 -15
  6. package/.claude/commands/analysis/README.md +0 -9
  7. package/.claude/commands/automation/README.md +0 -9
  8. package/.claude/commands/github/README.md +0 -11
  9. package/.claude/commands/helpers/standard-checkpoint-hooks.sh +0 -179
  10. package/.claude/commands/hooks/README.md +0 -11
  11. package/.claude/commands/monitoring/README.md +0 -9
  12. package/.claude/commands/optimization/README.md +0 -9
  13. package/.claude/commands/training/README.md +0 -9
  14. package/.claude/commands/workflows/README.md +0 -9
  15. package/.claude/config.json +0 -36
  16. package/.claude/helpers/github-setup.sh +0 -28
  17. package/.claude/helpers/quick-start.sh +0 -19
  18. package/.claude/helpers/setup-mcp.sh +0 -18
  19. package/.claude/settings.json +0 -114
  20. package/.claude/settings.local.json +0 -33
  21. package/.claude-flow/metrics/agent-metrics.json +0 -1
  22. package/.claude-flow/metrics/performance.json +0 -9
  23. package/.claude-flow/metrics/system-metrics.json +0 -15986
  24. package/.claude-flow/metrics/task-metrics.json +0 -10
  25. package/.roo/README.md +0 -402
  26. package/.roo/mcp-list.txt +0 -257
  27. package/.roo/mcp.json +0 -32
  28. package/.roo/workflows/basic-tdd.json +0 -32
  29. package/.roomodes +0 -201
  30. package/CLAUDE.md +0 -393
  31. package/bin/snow-flow.js +0 -3
  32. package/dist/cli.js.bak +0 -2052
  33. package/dist/config/snow-flow-config.d.ts +0 -1492
  34. package/dist/config/snow-flow-config.js +0 -938
  35. package/scripts/create-env.js +0 -91
  36. package/scripts/postinstall.js +0 -30
  37. package/scripts/register-mcp-servers.js +0 -210
  38. package/scripts/reset-mcp-servers.js +0 -246
  39. package/scripts/setup-mcp.js +0 -106
  40. package/scripts/update-version.js +0 -31
  41. package/src/agents/README.md +0 -192
  42. package/src/agents/base-agent.ts +0 -251
  43. package/src/agents/coordinator.ts +0 -971
  44. package/src/agents/index.ts +0 -18
  45. package/src/agents/queen-403-handler.ts +0 -230
  46. package/src/agents/queen-agent.ts +0 -1399
  47. package/src/agents/script-writer-agent.ts +0 -769
  48. package/src/agents/security-agent.ts +0 -726
  49. package/src/agents/widget-creator-agent.ts +0 -568
  50. package/src/api/error-handling.ts +0 -1758
  51. package/src/api/natural-language-mapper.ts +0 -630
  52. package/src/api/performance-optimizer.ts +0 -891
  53. package/src/api/transaction-manager.ts +0 -754
  54. package/src/cli/deploy-artifact.ts +0 -304
  55. package/src/cli/snow-flow-cli-integration.ts +0 -434
  56. package/src/cli.ts +0 -4455
  57. package/src/cli.ts.bak +0 -5616
  58. package/src/compliance/advanced-compliance-system.ts +0 -1268
  59. package/src/compliance/index.ts +0 -19
  60. package/src/config/snow-flow-config.ts +0 -1019
  61. package/src/documentation/index.ts +0 -16
  62. package/src/documentation/self-documenting-system.ts +0 -1421
  63. package/src/dynamic-version.ts +0 -44
  64. package/src/healing/index.ts +0 -20
  65. package/src/healing/self-healing-system.ts +0 -1435
  66. package/src/health/README.md +0 -161
  67. package/src/health/system-health.ts +0 -995
  68. package/src/index.ts +0 -60
  69. package/src/intelligence/acl-analyzer.ts +0 -377
  70. package/src/intelligence/auto-resolution-engine.ts +0 -643
  71. package/src/intelligence/gap-analysis-engine.ts +0 -488
  72. package/src/intelligence/manual-instructions-generator.ts +0 -744
  73. package/src/intelligence/mcp-coverage-analyzer.ts +0 -708
  74. package/src/intelligence/multi-pass-requirements-analyzer.ts +0 -603
  75. package/src/intelligence/performance-recommendations-engine.ts +0 -1223
  76. package/src/intelligence/requirements-analyzer.ts +0 -1148
  77. package/src/intelligence/task-analyzer.ts +0 -628
  78. package/src/managers/scope-manager.ts +0 -555
  79. package/src/mcp/advanced/servicenow-advanced-features-mcp.ts +0 -16110
  80. package/src/mcp/base-mcp-server.ts +0 -660
  81. package/src/mcp/http-transport-wrapper.ts +0 -426
  82. package/src/mcp/service-discovery-client.ts +0 -312
  83. package/src/mcp/servicenow-automation-mcp-refactored.ts +0 -678
  84. package/src/mcp/servicenow-automation-mcp.ts +0 -838
  85. package/src/mcp/servicenow-deployment-mcp-refactored.ts +0 -1292
  86. package/src/mcp/servicenow-deployment-mcp.ts +0 -7244
  87. package/src/mcp/servicenow-graph-memory-mcp-refactored.ts +0 -721
  88. package/src/mcp/servicenow-graph-memory-mcp.ts +0 -832
  89. package/src/mcp/servicenow-integration-mcp-refactored.ts +0 -655
  90. package/src/mcp/servicenow-integration-mcp.ts +0 -819
  91. package/src/mcp/servicenow-intelligent-mcp-refactored.ts +0 -977
  92. package/src/mcp/servicenow-intelligent-mcp.ts +0 -4638
  93. package/src/mcp/servicenow-mcp-server.ts +0 -728
  94. package/src/mcp/servicenow-memory-mcp.ts +0 -474
  95. package/src/mcp/servicenow-operations-mcp-refactored.ts +0 -1995
  96. package/src/mcp/servicenow-operations-mcp.ts +0 -3384
  97. package/src/mcp/servicenow-platform-development-mcp-refactored.ts +0 -611
  98. package/src/mcp/servicenow-platform-development-mcp.ts +0 -970
  99. package/src/mcp/servicenow-progressive-indexer.ts +0 -370
  100. package/src/mcp/servicenow-reporting-analytics-mcp-refactored.ts +0 -695
  101. package/src/mcp/servicenow-reporting-analytics-mcp.ts +0 -1123
  102. package/src/mcp/servicenow-security-compliance-mcp-refactored.ts +0 -642
  103. package/src/mcp/servicenow-security-compliance-mcp.ts +0 -1157
  104. package/src/mcp/servicenow-update-set-mcp-refactored.ts +0 -751
  105. package/src/mcp/servicenow-update-set-mcp.ts +0 -811
  106. package/src/mcp/shared/agent-context-provider.ts +0 -365
  107. package/src/mcp/shared/base-mcp-server.ts +0 -2463
  108. package/src/mcp/shared/mcp-memory-manager.ts +0 -540
  109. package/src/mcp/shared/mcp-resource-manager.ts +0 -367
  110. package/src/mcp/snow-flow-mcp.ts +0 -911
  111. package/src/mcp/start-all-mcp-servers.ts +0 -102
  112. package/src/mcp/start-servicenow-mcp.ts +0 -67
  113. package/src/monitoring/enhanced-monitoring-system.ts +0 -1345
  114. package/src/monitoring/performance-tracker.ts +0 -777
  115. package/src/optimization/cost-optimization-engine.ts +0 -1125
  116. package/src/optimization/index.ts +0 -10
  117. package/src/queen/README.md +0 -403
  118. package/src/queen/agent-factory.ts +0 -574
  119. package/src/queen/index.ts +0 -94
  120. package/src/queen/mcp-execution-bridge.ts +0 -681
  121. package/src/queen/neural-learning.ts +0 -319
  122. package/src/queen/parallel-agent-engine.ts +0 -848
  123. package/src/queen/queen-memory-system.ts +0 -490
  124. package/src/queen/queen-memory.ts +0 -456
  125. package/src/queen/servicenow-queen.ts +0 -1029
  126. package/src/queen/types.ts +0 -87
  127. package/src/rollback/smart-rollback-system.ts +0 -866
  128. package/src/schemas/deployment.schema.json +0 -58
  129. package/src/schemas/flow.schema.json +0 -79
  130. package/src/schemas/widget.schema.json +0 -72
  131. package/src/snow-flow-system.ts +0 -541
  132. package/src/sparc/sparc-help.ts +0 -49
  133. package/src/sparc/team-sparc.ts +0 -88
  134. package/src/strategies/global-scope-strategy.ts +0 -736
  135. package/src/templates/base/application.template.json +0 -45
  136. package/src/templates/base/business_rule.template.json +0 -33
  137. package/src/templates/base/script_include.template.json +0 -18
  138. package/src/templates/base/table.template.json +0 -64
  139. package/src/templates/base/widget.template.json +0 -25
  140. package/src/templates/patterns/composite.incident-management.template.json +0 -140
  141. package/src/templates/patterns/widget.dashboard.template.json +0 -238
  142. package/src/templates/patterns/widget.datatable.template.json +0 -292
  143. package/src/testing/integration-test-suite.ts +0 -1276
  144. package/src/types/index.ts +0 -183
  145. package/src/types/neo4j-driver.d.ts +0 -23
  146. package/src/types/servicenow.types.ts +0 -307
  147. package/src/types/snow-flow.types.ts +0 -186
  148. package/src/types/todo.types.ts +0 -70
  149. package/src/utils/action-type-cache.ts +0 -286
  150. package/src/utils/agent-detector.ts +0 -573
  151. package/src/utils/artifact-tracker.ts +0 -379
  152. package/src/utils/dependency-detector.ts +0 -233
  153. package/src/utils/deployment-metadata-handler.ts +0 -518
  154. package/src/utils/error-recovery.ts +0 -680
  155. package/src/utils/logger.ts +0 -60
  156. package/src/utils/mcp-auth-middleware.ts +0 -262
  157. package/src/utils/mcp-config-manager.ts +0 -270
  158. package/src/utils/mcp-server-manager.ts +0 -446
  159. package/src/utils/mcp-tool-registry.ts +0 -298
  160. package/src/utils/migrate-snow-flow.ts +0 -98
  161. package/src/utils/oauth-html-templates.ts +0 -188
  162. package/src/utils/scope-utils.ts +0 -443
  163. package/src/utils/servicenow-client.ts +0 -3321
  164. package/src/utils/servicenow-id-generator.ts +0 -181
  165. package/src/utils/snow-memory-manager.ts +0 -605
  166. package/src/utils/snow-oauth.ts +0 -797
  167. package/src/utils/template-engine.ts +0 -463
  168. package/src/utils/theme-manager.ts +0 -295
  169. package/src/utils/unified-auth-store.ts +0 -222
  170. package/src/utils/update-set-importer.ts +0 -422
  171. package/src/utils/update-set-xml-packager.ts +0 -371
  172. package/src/utils/widget-template-generator.ts +0 -1744
  173. package/src/version.ts +0 -1950
@@ -1,3384 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * ServiceNow Operations MCP Server
5
- *
6
- * Handles operational data queries, incident management, and intelligent analysis
7
- * Features:
8
- * - Incident, Request, Problem, Change management
9
- * - CMDB and User management
10
- * - Intelligent incident _analysis and auto-resolution
11
- * - Pattern recognition and root cause analysis
12
- * - Knowledge base integration
13
- * - Predictive analytics
14
- */
15
-
16
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
17
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
18
- import {
19
- CallToolRequestSchema,
20
- ErrorCode,
21
- ListToolsRequestSchema,
22
- McpError,
23
- } from '@modelcontextprotocol/sdk/types.js';
24
- import { ServiceNowClient } from '../utils/servicenow-client.js';
25
- import { logger } from '../utils/logger.js';
26
-
27
- // Operational table mappings for ServiceNow
28
- const operationalTableMapping = {
29
- // ITIL Core Processes
30
- incident: 'incident',
31
- request: 'sc_request',
32
- request_item: 'sc_req_item',
33
- problem: 'problem',
34
- change_request: 'change_request',
35
-
36
- // Service Catalog
37
- catalog: 'sc_catalog',
38
- catalog_item: 'sc_cat_item',
39
- catalog_category: 'sc_category',
40
- catalog_variable: 'item_option_new',
41
- catalog_variable_set: 'io_set_item',
42
- catalog_ui_policy: 'catalog_ui_policy',
43
- catalog_client_script: 'catalog_script_client',
44
- catalog_template: 'sc_template',
45
-
46
- // Task Management
47
- task: 'task',
48
- incident_task: 'incident_task',
49
- problem_task: 'problem_task',
50
- change_task: 'change_task',
51
- approval: 'sysapproval_approver',
52
-
53
- // User & Group Management
54
- user: 'sys_user',
55
- user_group: 'sys_user_group',
56
- user_role: 'sys_user_has_role',
57
-
58
- // CMDB (Configuration Management Database)
59
- configuration_item: 'cmdb_ci',
60
- server: 'cmdb_ci_server',
61
- application: 'cmdb_ci_application',
62
- database: 'cmdb_ci_database',
63
- network_device: 'cmdb_ci_network_device',
64
- service: 'cmdb_ci_service',
65
-
66
- // Logging & Audit
67
- system_log: 'syslog',
68
- audit_log: 'sys_audit',
69
- email: 'sys_email',
70
- attachment: 'sys_attachment',
71
-
72
- // Knowledge Management
73
- knowledge_base: 'kb_knowledge_base',
74
- knowledge: 'kb_knowledge',
75
-
76
- // Metrics & Analytics
77
- metric: 'sys_metric',
78
- metric_instance: 'sys_metric_instance',
79
-
80
- // Notifications
81
- notification: 'sysevent_email_action',
82
- email_log: 'sys_email'
83
- };
84
-
85
- // Common operational queries for intelligent analysis
86
- const commonPatterns = {
87
- // Network connectivity issues
88
- network_issues: {
89
- keywords: ['network', 'connectivity', 'ping', 'dns', 'timeout', 'unreachable'],
90
- common_solutions: [
91
- 'Check network connectivity',
92
- 'Verify DNS resolution',
93
- 'Test ping to server',
94
- 'Review firewall rules'
95
- ]
96
- },
97
-
98
- // Database connectivity
99
- database_issues: {
100
- keywords: ['database', 'connection', 'sql', 'timeout', 'deadlock', 'performance'],
101
- common_solutions: [
102
- 'Check database connection',
103
- 'Review connection pool settings',
104
- 'Analyze query performance',
105
- 'Check for blocking processes'
106
- ]
107
- },
108
-
109
- // Application errors
110
- application_errors: {
111
- keywords: ['application', 'error', 'exception', 'crash', 'memory', 'cpu'],
112
- common_solutions: [
113
- 'Check application logs',
114
- 'Review memory usage',
115
- 'Analyze CPU utilization',
116
- 'Restart application service'
117
- ]
118
- },
119
-
120
- // Authentication issues
121
- auth_issues: {
122
- keywords: ['authentication', 'login', 'password', 'ldap', 'sso', 'unauthorized'],
123
- common_solutions: [
124
- 'Reset user password',
125
- 'Check LDAP connectivity',
126
- 'Verify SSO configuration',
127
- 'Review user permissions'
128
- ]
129
- }
130
- };
131
-
132
- interface IncidentAnalysis {
133
- incident_id: string;
134
- patterns_found: string[];
135
- root_cause__analysis: string;
136
- suggested_resolution: string[];
137
- confidence_score: number;
138
- similar_incidents: any[];
139
- knowledge_articles: any[];
140
- automated_actions: string[];
141
- }
142
-
143
- interface OperationalMetrics {
144
- total_incidents: number;
145
- open_incidents: number;
146
- high_priority_incidents: number;
147
- avg_resolution_time: number;
148
- common_categories: string[];
149
- trending_issues: string[];
150
- }
151
-
152
- class ServiceNowOperationsMCP {
153
- private server: Server;
154
- private client: ServiceNowClient;
155
-
156
- constructor() {
157
- this.server = new Server(
158
- {
159
- name: 'servicenow-operations-mcp',
160
- version: '1.0.0',
161
- },
162
- {
163
- capabilities: {
164
- tools: {},
165
- },
166
- }
167
- );
168
-
169
- this.client = new ServiceNowClient();
170
- this.setupToolHandlers();
171
- }
172
-
173
- private setupToolHandlers() {
174
- // List available tools
175
- this.server.setRequestHandler(ListToolsRequestSchema, async () => {
176
- return {
177
- tools: [
178
- // Core Operational Queries
179
- {
180
- name: 'snow_query_incidents',
181
- description: 'Advanced incident querying with filters and _analysis',
182
- inputSchema: {
183
- type: 'object',
184
- properties: {
185
- query: {
186
- type: 'string',
187
- description: 'ServiceNow encoded query or natural language description'
188
- },
189
- limit: {
190
- type: 'number',
191
- description: 'Maximum number of results (default: 10)',
192
- default: 10
193
- },
194
- include__analysis: {
195
- type: 'boolean',
196
- description: 'Include intelligent _analysis of incidents',
197
- default: false
198
- },
199
- fields: {
200
- type: 'array',
201
- items: { type: 'string' },
202
- description: 'Specific fields to return'
203
- }
204
- },
205
- required: ['query']
206
- }
207
- },
208
-
209
- {
210
- name: 'snow_analyze_incident',
211
- description: 'Intelligent _analysis of a specific incident with auto-resolution suggestions',
212
- inputSchema: {
213
- type: 'object',
214
- properties: {
215
- incident_id: {
216
- type: 'string',
217
- description: 'Incident number or sys_id'
218
- },
219
- include_similar: {
220
- type: 'boolean',
221
- description: 'Include similar incidents in _analysis',
222
- default: true
223
- },
224
- suggest_resolution: {
225
- type: 'boolean',
226
- description: 'Generate automated resolution suggestions',
227
- default: true
228
- }
229
- },
230
- required: ['incident_id']
231
- }
232
- },
233
-
234
- {
235
- name: 'snow_auto_resolve_incident',
236
- description: 'Attempt automated resolution of technical incidents',
237
- inputSchema: {
238
- type: 'object',
239
- properties: {
240
- incident_id: {
241
- type: 'string',
242
- description: 'Incident number or sys_id'
243
- },
244
- dry_run: {
245
- type: 'boolean',
246
- description: 'Preview actions without executing',
247
- default: true
248
- }
249
- },
250
- required: ['incident_id']
251
- }
252
- },
253
-
254
- {
255
- name: 'snow_query_requests',
256
- description: 'Query and analyze service requests',
257
- inputSchema: {
258
- type: 'object',
259
- properties: {
260
- query: {
261
- type: 'string',
262
- description: 'ServiceNow encoded query or natural language'
263
- },
264
- limit: {
265
- type: 'number',
266
- description: 'Maximum number of results',
267
- default: 10
268
- },
269
- include_items: {
270
- type: 'boolean',
271
- description: 'Include request items',
272
- default: false
273
- }
274
- },
275
- required: ['query']
276
- }
277
- },
278
-
279
- {
280
- name: 'snow_query_problems',
281
- description: 'Query and analyze problems with root cause _analysis',
282
- inputSchema: {
283
- type: 'object',
284
- properties: {
285
- query: {
286
- type: 'string',
287
- description: 'ServiceNow encoded query or natural language'
288
- },
289
- limit: {
290
- type: 'number',
291
- description: 'Maximum number of results',
292
- default: 10
293
- },
294
- include_incidents: {
295
- type: 'boolean',
296
- description: 'Include related incidents',
297
- default: false
298
- }
299
- },
300
- required: ['query']
301
- }
302
- },
303
-
304
- {
305
- name: 'snow_cmdb_search',
306
- description: 'Search and analyze Configuration Items (CMDB)',
307
- inputSchema: {
308
- type: 'object',
309
- properties: {
310
- query: {
311
- type: 'string',
312
- description: 'Search query for configuration items'
313
- },
314
- ci_type: {
315
- type: 'string',
316
- description: 'Type of CI (server, application, service, etc.)',
317
- enum: ['server', 'application', 'database', 'network_device', 'service', 'any']
318
- },
319
- limit: {
320
- type: 'number',
321
- description: 'Maximum number of results',
322
- default: 10
323
- },
324
- include_relationships: {
325
- type: 'boolean',
326
- description: 'Include CI relationships',
327
- default: false
328
- }
329
- },
330
- required: ['query']
331
- }
332
- },
333
-
334
- {
335
- name: 'snow_user_lookup',
336
- description: 'Lookup and analyze user information',
337
- inputSchema: {
338
- type: 'object',
339
- properties: {
340
- identifier: {
341
- type: 'string',
342
- description: 'User ID, email, or name'
343
- },
344
- include_roles: {
345
- type: 'boolean',
346
- description: 'Include user roles',
347
- default: true
348
- },
349
- include_groups: {
350
- type: 'boolean',
351
- description: 'Include user groups',
352
- default: true
353
- }
354
- },
355
- required: ['identifier']
356
- }
357
- },
358
-
359
- {
360
- name: 'snow_operational_metrics',
361
- description: 'Get operational metrics and analytics',
362
- inputSchema: {
363
- type: 'object',
364
- properties: {
365
- timeframe: {
366
- type: 'string',
367
- description: 'Time period for metrics',
368
- enum: ['today', 'week', 'month', 'quarter'],
369
- default: 'week'
370
- },
371
- metric_types: {
372
- type: 'array',
373
- items: { type: 'string' },
374
- description: 'Types of metrics to include'
375
- }
376
- }
377
- }
378
- },
379
-
380
- {
381
- name: 'snow_pattern__analysis',
382
- description: 'Analyze patterns in incidents, requests, and problems',
383
- inputSchema: {
384
- type: 'object',
385
- properties: {
386
- analysis_type: {
387
- type: 'string',
388
- description: 'Type of pattern _analysis',
389
- enum: ['incident_patterns', 'request_trends', 'problem_root_causes', 'user_behavior']
390
- },
391
- timeframe: {
392
- type: 'string',
393
- description: 'Time period for _analysis',
394
- enum: ['day', 'week', 'month', 'quarter'],
395
- default: 'week'
396
- }
397
- },
398
- required: ['analysis_type']
399
- }
400
- },
401
-
402
- {
403
- name: 'snow_knowledge_search',
404
- description: 'Search knowledge base articles with intelligent matching',
405
- inputSchema: {
406
- type: 'object',
407
- properties: {
408
- query: {
409
- type: 'string',
410
- description: 'Search query or incident description'
411
- },
412
- match_incident: {
413
- type: 'string',
414
- description: 'Match knowledge to specific incident'
415
- },
416
- limit: {
417
- type: 'number',
418
- description: 'Maximum number of results',
419
- default: 5
420
- }
421
- },
422
- required: ['query']
423
- }
424
- },
425
-
426
- {
427
- name: 'snow_predictive__analysis',
428
- description: 'Predictive _analysis for potential issues and trends',
429
- inputSchema: {
430
- type: 'object',
431
- properties: {
432
- prediction_type: {
433
- type: 'string',
434
- description: 'Type of prediction',
435
- enum: ['incident_volume', 'system_failure', 'resource_exhaustion', 'user_impact']
436
- },
437
- timeframe: {
438
- type: 'string',
439
- description: 'Prediction timeframe',
440
- enum: ['day', 'week', 'month'],
441
- default: 'week'
442
- }
443
- },
444
- required: ['prediction_type']
445
- }
446
- },
447
-
448
- // Service Catalog Management
449
- {
450
- name: 'snow_catalog_item_manager',
451
- description: 'Manage service catalog items - create, update, configure variables and workflows',
452
- inputSchema: {
453
- type: 'object',
454
- properties: {
455
- action: {
456
- type: 'string',
457
- description: 'Action to perform',
458
- enum: ['create', 'update', 'list', 'get', 'add_variable', 'set_workflow', 'publish', 'retire']
459
- },
460
- catalog_id: {
461
- type: 'string',
462
- description: 'Catalog sys_id (for create action)'
463
- },
464
- category_id: {
465
- type: 'string',
466
- description: 'Category sys_id'
467
- },
468
- item_id: {
469
- type: 'string',
470
- description: 'Catalog item sys_id (for update/get actions)'
471
- },
472
- name: {
473
- type: 'string',
474
- description: 'Catalog item name'
475
- },
476
- short_description: {
477
- type: 'string',
478
- description: 'Short description'
479
- },
480
- description: {
481
- type: 'string',
482
- description: 'Full description'
483
- },
484
- price: {
485
- type: 'string',
486
- description: 'Item price (e.g., "100.00")'
487
- },
488
- recurring_price: {
489
- type: 'string',
490
- description: 'Recurring price if applicable'
491
- },
492
- picture: {
493
- type: 'string',
494
- description: 'Picture attachment sys_id'
495
- },
496
- workflow: {
497
- type: 'string',
498
- description: 'Workflow to attach'
499
- },
500
- variable_set: {
501
- type: 'string',
502
- description: 'Variable set sys_id to attach'
503
- },
504
- variables: {
505
- type: 'array',
506
- description: 'Variables to add',
507
- items: {
508
- type: 'object',
509
- properties: {
510
- name: { type: 'string' },
511
- label: { type: 'string' },
512
- type: {
513
- type: 'string',
514
- enum: ['single_line_text', 'multi_line_text', 'select_box', 'checkbox', 'reference', 'date', 'datetime']
515
- },
516
- mandatory: { type: 'boolean' },
517
- default_value: { type: 'string' },
518
- choices: { type: 'array', items: { type: 'string' } }
519
- }
520
- }
521
- },
522
- active: {
523
- type: 'boolean',
524
- description: 'Whether the item is active',
525
- default: true
526
- },
527
- billable: {
528
- type: 'boolean',
529
- description: 'Whether the item is billable',
530
- default: false
531
- },
532
- sc_catalogs: {
533
- type: 'string',
534
- description: 'Comma-separated catalog sys_ids'
535
- },
536
- sc_categories: {
537
- type: 'string',
538
- description: 'Comma-separated category sys_ids'
539
- }
540
- },
541
- required: ['action']
542
- }
543
- },
544
-
545
- {
546
- name: 'snow_catalog_item_search',
547
- description: 'Search for catalog items with intelligent matching and discovery',
548
- inputSchema: {
549
- type: 'object',
550
- properties: {
551
- query: {
552
- type: 'string',
553
- description: 'Search query (e.g., "iPhone", "laptop", "mobile device")'
554
- },
555
- include_inactive: {
556
- type: 'boolean',
557
- description: 'Include inactive catalog items',
558
- default: false
559
- },
560
- fuzzy_match: {
561
- type: 'boolean',
562
- description: 'Enable fuzzy matching for similar items',
563
- default: true
564
- },
565
- category_filter: {
566
- type: 'string',
567
- description: 'Filter by category name or sys_id'
568
- },
569
- limit: {
570
- type: 'number',
571
- description: 'Maximum results to return',
572
- default: 50
573
- },
574
- include_variables: {
575
- type: 'boolean',
576
- description: 'Include catalog item variables in results',
577
- default: false
578
- }
579
- },
580
- required: ['query']
581
- }
582
- },
583
-
584
- {
585
- name: 'snow_cleanup_test_artifacts',
586
- description: 'Clean up test artifacts while preserving Update Set audit trail',
587
- inputSchema: {
588
- type: 'object',
589
- properties: {
590
- artifact_types: {
591
- type: 'array',
592
- description: 'Types of artifacts to clean up',
593
- items: {
594
- type: 'string',
595
- enum: ['catalog_items', 'flows', 'users', 'requests', 'all']
596
- },
597
- default: ['catalog_items', 'flows', 'users']
598
- },
599
- test_patterns: {
600
- type: 'array',
601
- description: 'Name patterns that identify test artifacts',
602
- items: { type: 'string' },
603
- default: ['Test%', 'Mock%', 'Demo%', '%_test_%', '%test', '%mock']
604
- },
605
- max_age_hours: {
606
- type: 'number',
607
- description: 'Only clean artifacts older than this (hours)',
608
- default: 1
609
- },
610
- dry_run: {
611
- type: 'boolean',
612
- description: 'Preview what would be deleted without actually deleting',
613
- default: false
614
- },
615
- preserve_update_set_entries: {
616
- type: 'boolean',
617
- description: 'Keep Update Set entries as audit trail',
618
- default: true
619
- },
620
- update_set_filter: {
621
- type: 'string',
622
- description: 'Only clean from specific Update Set (optional)'
623
- }
624
- }
625
- }
626
- },
627
-
628
- // User and Group Management Tools
629
- {
630
- name: 'snow_create_user_group',
631
- description: 'Create a new user group in ServiceNow',
632
- inputSchema: {
633
- type: 'object',
634
- properties: {
635
- name: {
636
- type: 'string',
637
- description: 'Group name (e.g., "Need Approval")'
638
- },
639
- description: {
640
- type: 'string',
641
- description: 'Group description'
642
- },
643
- email: {
644
- type: 'string',
645
- description: 'Group email address (optional)'
646
- },
647
- manager: {
648
- type: 'string',
649
- description: 'Group manager sys_id or user_name (optional)'
650
- },
651
- parent: {
652
- type: 'string',
653
- description: 'Parent group sys_id (optional)'
654
- },
655
- type: {
656
- type: 'string',
657
- description: 'Group type (optional)',
658
- enum: ['', 'catalog', 'change', 'incident', 'problem', 'request']
659
- },
660
- active: {
661
- type: 'boolean',
662
- description: 'Is group active',
663
- default: true
664
- }
665
- },
666
- required: ['name']
667
- }
668
- },
669
-
670
- {
671
- name: 'snow_create_user',
672
- description: 'Create a new user in ServiceNow',
673
- inputSchema: {
674
- type: 'object',
675
- properties: {
676
- user_name: {
677
- type: 'string',
678
- description: 'Unique username for login'
679
- },
680
- first_name: {
681
- type: 'string',
682
- description: 'User first name'
683
- },
684
- last_name: {
685
- type: 'string',
686
- description: 'User last name'
687
- },
688
- email: {
689
- type: 'string',
690
- description: 'User email address'
691
- },
692
- department: {
693
- type: 'string',
694
- description: 'Department name or sys_id (optional)'
695
- },
696
- manager: {
697
- type: 'string',
698
- description: 'Manager sys_id or user_name (optional)'
699
- },
700
- phone: {
701
- type: 'string',
702
- description: 'Phone number (optional)'
703
- },
704
- title: {
705
- type: 'string',
706
- description: 'Job title (optional)'
707
- },
708
- location: {
709
- type: 'string',
710
- description: 'Location name or sys_id (optional)'
711
- },
712
- active: {
713
- type: 'boolean',
714
- description: 'Is user active',
715
- default: true
716
- },
717
- password: {
718
- type: 'string',
719
- description: 'Initial password (will require change on first login)'
720
- }
721
- },
722
- required: ['user_name', 'first_name', 'last_name', 'email']
723
- }
724
- },
725
-
726
- {
727
- name: 'snow_assign_user_to_group',
728
- description: 'Add a user to a group in ServiceNow',
729
- inputSchema: {
730
- type: 'object',
731
- properties: {
732
- user: {
733
- type: 'string',
734
- description: 'User sys_id or user_name'
735
- },
736
- group: {
737
- type: 'string',
738
- description: 'Group sys_id or name'
739
- }
740
- },
741
- required: ['user', 'group']
742
- }
743
- },
744
-
745
- {
746
- name: 'snow_remove_user_from_group',
747
- description: 'Remove a user from a group in ServiceNow',
748
- inputSchema: {
749
- type: 'object',
750
- properties: {
751
- user: {
752
- type: 'string',
753
- description: 'User sys_id or user_name'
754
- },
755
- group: {
756
- type: 'string',
757
- description: 'Group sys_id or name'
758
- }
759
- },
760
- required: ['user', 'group']
761
- }
762
- },
763
-
764
- {
765
- name: 'snow_list_group_members',
766
- description: 'List all members of a group',
767
- inputSchema: {
768
- type: 'object',
769
- properties: {
770
- group: {
771
- type: 'string',
772
- description: 'Group sys_id or name'
773
- },
774
- active_only: {
775
- type: 'boolean',
776
- description: 'Only show active users',
777
- default: true
778
- }
779
- },
780
- required: ['group']
781
- }
782
- }
783
- ],
784
- };
785
- });
786
-
787
- // Handle tool calls
788
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
789
- const { name, arguments: args } = request.params;
790
-
791
- try {
792
- switch (name) {
793
- case 'snow_query_incidents':
794
- return await this.handleQueryIncidents(args);
795
- case 'snow_analyze_incident':
796
- return await this.handleAnalyzeIncident(args);
797
- case 'snow_auto_resolve_incident':
798
- return await this.handleAutoResolveIncident(args);
799
- case 'snow_query_requests':
800
- return await this.handleQueryRequests(args);
801
- case 'snow_query_problems':
802
- return await this.handleQueryProblems(args);
803
- case 'snow_cmdb_search':
804
- return await this.handleCMDBSearch(args);
805
- case 'snow_user_lookup':
806
- return await this.handleUserLookup(args);
807
- case 'snow_operational_metrics':
808
- return await this.handleOperationalMetrics(args);
809
- case 'snow_pattern__analysis':
810
- return await this.handlePatternAnalysis(args);
811
- case 'snow_knowledge_search':
812
- return await this.handleKnowledgeSearch(args);
813
- case 'snow_predictive__analysis':
814
- return await this.handlePredictiveAnalysis(args);
815
- case 'snow_catalog_item_manager':
816
- return await this.handleCatalogItemManager(args);
817
- case 'snow_catalog_item_search':
818
- return await this.handleCatalogItemSearch(args);
819
- case 'snow_cleanup_test_artifacts':
820
- return await this.handleCleanupTestArtifacts(args);
821
- case 'snow_create_user_group':
822
- return await this.handleCreateUserGroup(args);
823
- case 'snow_create_user':
824
- return await this.handleCreateUser(args);
825
- case 'snow_assign_user_to_group':
826
- return await this.handleAssignUserToGroup(args);
827
- case 'snow_remove_user_from_group':
828
- return await this.handleRemoveUserFromGroup(args);
829
- case 'snow_list_group_members':
830
- return await this.handleListGroupMembers(args);
831
- default:
832
- throw new McpError(ErrorCode.MethodNotFound, `Tool ${name} not found`);
833
- }
834
- } catch (error) {
835
- logger.error(`Error executing tool ${name}:`, error);
836
- throw new McpError(ErrorCode.InternalError, `Failed to execute ${name}: ${error}`);
837
- }
838
- });
839
- }
840
-
841
- private async handleQueryIncidents(args: any) {
842
- const { query, limit = 10, include__analysis = false, fields } = args;
843
-
844
- logger.info(`Querying incidents with: ${query}`);
845
-
846
- try {
847
- // Convert natural language to ServiceNow query if needed
848
- const processedQuery = this.processNaturalLanguageQuery(query, 'incident');
849
-
850
- // Query incidents
851
- const incidents = await this.client.searchRecords('incident', processedQuery, limit);
852
-
853
- let result = {
854
- total_results: incidents.success ? incidents.data.result.length : 0,
855
- incidents: incidents.success ? incidents.data.result : []
856
- };
857
-
858
- // Add intelligent _analysis if requested
859
- if (include__analysis && incidents.success && incidents.data.result.length > 0) {
860
- const _analysis = await this.analyzeIncidents(incidents.data.result);
861
- result = { ...result, ..._analysis };
862
- }
863
-
864
- return {
865
- content: [
866
- {
867
- type: 'text',
868
- text: `Found ${incidents.success ? incidents.data.result.length : 0} incidents matching query: "${query}"\n\n${JSON.stringify(result, null, 2)}`
869
- }
870
- ]
871
- };
872
- } catch (error) {
873
- logger.error('Error querying incidents:', error);
874
- throw new McpError(ErrorCode.InternalError, `Failed to query incidents: ${error}`);
875
- }
876
- }
877
-
878
- private async handleAnalyzeIncident(args: any) {
879
- const { incident_id, include_similar = true, suggest_resolution = true } = args;
880
-
881
- logger.info(`Analyzing incident: ${incident_id}`);
882
-
883
- try {
884
- // Get incident details
885
- const incident = await this.getIncidentDetails(incident_id);
886
- if (!incident) {
887
- throw new Error(`Incident ${incident_id} not found`);
888
- }
889
-
890
- // Perform intelligent analysis
891
- const _analysis = await this.performIncidentAnalysis(incident, include_similar, suggest_resolution);
892
-
893
- return {
894
- content: [
895
- {
896
- type: 'text',
897
- text: `Incident Analysis for ${incident_id}:\n\n${JSON.stringify(_analysis, null, 2)}`
898
- }
899
- ]
900
- };
901
- } catch (error) {
902
- logger.error('Error analyzing incident:', error);
903
- throw new McpError(ErrorCode.InternalError, `Failed to analyze incident: ${error}`);
904
- }
905
- }
906
-
907
- private async handleAutoResolveIncident(args: any) {
908
- const { incident_id, dry_run = true } = args;
909
-
910
- logger.info(`Auto-resolving incident: ${incident_id} (dry_run: ${dry_run})`);
911
-
912
- try {
913
- // Get incident details
914
- const incident = await this.getIncidentDetails(incident_id);
915
- if (!incident) {
916
- throw new Error(`Incident ${incident_id} not found`);
917
- }
918
-
919
- // Analyze incident for auto-resolution potential
920
- const _analysis = await this.performIncidentAnalysis(incident, true, true);
921
-
922
- // Generate automated resolution actions
923
- const resolutionActions = await this.generateResolutionActions(incident, _analysis);
924
-
925
- const result: any = {
926
- incident_id,
927
- _analysis,
928
- resolution_actions: resolutionActions,
929
- dry_run,
930
- actions_executed: []
931
- };
932
-
933
- // Execute actions if not dry run
934
- if (!dry_run && resolutionActions.length > 0) {
935
- result.actions_executed = await this.executeResolutionActions(incident_id, resolutionActions);
936
- }
937
-
938
- return {
939
- content: [
940
- {
941
- type: 'text',
942
- text: `Auto-Resolution Analysis for ${incident_id}:\n\n${JSON.stringify(result, null, 2)}`
943
- }
944
- ]
945
- };
946
- } catch (error) {
947
- logger.error('Error auto-resolving incident:', error);
948
- throw new McpError(ErrorCode.InternalError, `Failed to auto-resolve incident: ${error}`);
949
- }
950
- }
951
-
952
- private async handleQueryRequests(args: any) {
953
- const { query, limit = 10, include_items = false } = args;
954
-
955
- logger.info(`Querying requests with: ${query}`);
956
-
957
- try {
958
- const processedQuery = this.processNaturalLanguageQuery(query, 'request');
959
- const requests = await this.client.searchRecords('sc_request', processedQuery, limit);
960
-
961
- let result: any = {
962
- total_results: requests.success ? requests.data.result.length : 0,
963
- requests: requests.success ? requests.data.result : []
964
- };
965
-
966
- // Include request items if requested
967
- if (include_items && requests.success && requests.data.result.length > 0) {
968
- const requestItems = await this.getRequestItems(requests.data.result);
969
- result = { ...result, request_items: requestItems };
970
- }
971
-
972
- return {
973
- content: [
974
- {
975
- type: 'text',
976
- text: `Found ${requests.success ? requests.data.result.length : 0} requests matching query: "${query}"\n\n${JSON.stringify(result, null, 2)}`
977
- }
978
- ]
979
- };
980
- } catch (error) {
981
- logger.error('Error querying requests:', error);
982
- throw new McpError(ErrorCode.InternalError, `Failed to query requests: ${error}`);
983
- }
984
- }
985
-
986
- private async handleQueryProblems(args: any) {
987
- const { query, limit = 10, include_incidents = false } = args;
988
-
989
- logger.info(`Querying problems with: ${query}`);
990
-
991
- try {
992
- const processedQuery = this.processNaturalLanguageQuery(query, 'problem');
993
- const problems = await this.client.searchRecords('problem', processedQuery, limit);
994
-
995
- let result: any = {
996
- total_results: problems.success ? problems.data.result.length : 0,
997
- problems: problems.success ? problems.data.result : []
998
- };
999
-
1000
- // Include related incidents if requested
1001
- if (include_incidents && problems.success && problems.data.result.length > 0) {
1002
- const incidents = await this.getRelatedIncidents(problems.data.result);
1003
- result = { ...result, related_incidents: incidents };
1004
- }
1005
-
1006
- return {
1007
- content: [
1008
- {
1009
- type: 'text',
1010
- text: `Found ${problems.success ? problems.data.result.length : 0} problems matching query: "${query}"\n\n${JSON.stringify(result, null, 2)}`
1011
- }
1012
- ]
1013
- };
1014
- } catch (error) {
1015
- logger.error('Error querying problems:', error);
1016
- throw new McpError(ErrorCode.InternalError, `Failed to query problems: ${error}`);
1017
- }
1018
- }
1019
-
1020
- private async handleCMDBSearch(args: any) {
1021
- const { query, ci_type = 'any', limit = 10, include_relationships = false } = args;
1022
-
1023
- logger.info(`Searching CMDB with: ${query}, type: ${ci_type}`);
1024
-
1025
- try {
1026
- const ciTable = ci_type === 'any' ? 'cmdb_ci' : (operationalTableMapping as any)[ci_type] || 'cmdb_ci';
1027
- const processedQuery = this.processNaturalLanguageQuery(query, 'cmdb');
1028
-
1029
- const configItems = await this.client.searchRecords(ciTable, processedQuery, limit);
1030
-
1031
- let result: any = {
1032
- total_results: configItems.success ? configItems.data.result.length : 0,
1033
- configuration_items: configItems.success ? configItems.data.result : [],
1034
- ci_type: ci_type
1035
- };
1036
-
1037
- // Include relationships if requested
1038
- if (include_relationships && configItems.success && configItems.data.result.length > 0) {
1039
- const relationships = await this.getCIRelationships(configItems.data.result);
1040
- result = { ...result, relationships };
1041
- }
1042
-
1043
- return {
1044
- content: [
1045
- {
1046
- type: 'text',
1047
- text: `Found ${configItems.success ? configItems.data.result.length : 0} configuration items matching query: "${query}"\n\n${JSON.stringify(result, null, 2)}`
1048
- }
1049
- ]
1050
- };
1051
- } catch (error) {
1052
- logger.error('Error searching CMDB:', error);
1053
- throw new McpError(ErrorCode.InternalError, `Failed to search CMDB: ${error}`);
1054
- }
1055
- }
1056
-
1057
- private async handleUserLookup(args: any) {
1058
- const { identifier, include_roles = true, include_groups = true } = args;
1059
-
1060
- logger.info(`Looking up user: ${identifier}`);
1061
-
1062
- try {
1063
- // Try different user lookup strategies
1064
- let userQuery = '';
1065
- if (identifier.includes('@')) {
1066
- userQuery = `email=${identifier}`;
1067
- } else if (identifier.includes('.')) {
1068
- userQuery = `user_name=${identifier}`;
1069
- } else {
1070
- userQuery = `nameLIKE${identifier}^ORuser_nameLIKE${identifier}^ORemailLIKE${identifier}`;
1071
- }
1072
-
1073
- const users = await this.client.searchRecords('sys_user', userQuery, 5);
1074
-
1075
- if (!users.success || users.data.result.length === 0) {
1076
- return {
1077
- content: [
1078
- {
1079
- type: 'text',
1080
- text: `No users found matching identifier: "${identifier}"`
1081
- }
1082
- ]
1083
- };
1084
- }
1085
-
1086
- let result: any = {
1087
- total_results: users.data.result.length,
1088
- users: users.data.result
1089
- };
1090
-
1091
- // Include roles and groups if requested
1092
- if (include_roles || include_groups) {
1093
- const userDetails = await this.getUserDetails(users.data.result[0].sys_id, include_roles, include_groups);
1094
- result = { ...result, user_details: userDetails };
1095
- }
1096
-
1097
- return {
1098
- content: [
1099
- {
1100
- type: 'text',
1101
- text: `Found ${users.success ? users.data.result.length : 0} users matching identifier: "${identifier}"\n\n${JSON.stringify(result, null, 2)}`
1102
- }
1103
- ]
1104
- };
1105
- } catch (error) {
1106
- logger.error('Error looking up user:', error);
1107
- throw new McpError(ErrorCode.InternalError, `Failed to lookup user: ${error}`);
1108
- }
1109
- }
1110
-
1111
- private async handleOperationalMetrics(args: any) {
1112
- const { timeframe = 'week', metric_types = [] } = args;
1113
-
1114
- logger.info(`Getting operational metrics for timeframe: ${timeframe}`);
1115
-
1116
- try {
1117
- const metrics = await this.calculateOperationalMetrics(timeframe, metric_types);
1118
-
1119
- return {
1120
- content: [
1121
- {
1122
- type: 'text',
1123
- text: `Operational Metrics (${timeframe}):\n\n${JSON.stringify(metrics, null, 2)}`
1124
- }
1125
- ]
1126
- };
1127
- } catch (error) {
1128
- logger.error('Error getting operational metrics:', error);
1129
- throw new McpError(ErrorCode.InternalError, `Failed to get operational metrics: ${error}`);
1130
- }
1131
- }
1132
-
1133
- private async handlePatternAnalysis(args: any) {
1134
- const { analysis_type, timeframe = 'week' } = args;
1135
-
1136
- logger.info(`Performing pattern _analysis: ${analysis_type} for ${timeframe}`);
1137
-
1138
- try {
1139
- const patterns = await this.analyzePatterns(analysis_type, timeframe);
1140
-
1141
- return {
1142
- content: [
1143
- {
1144
- type: 'text',
1145
- text: `Pattern Analysis (${analysis_type} - ${timeframe}):\n\n${JSON.stringify(patterns, null, 2)}`
1146
- }
1147
- ]
1148
- };
1149
- } catch (error) {
1150
- logger.error('Error analyzing patterns:', error);
1151
- throw new McpError(ErrorCode.InternalError, `Failed to analyze patterns: ${error}`);
1152
- }
1153
- }
1154
-
1155
- private async handleKnowledgeSearch(args: any) {
1156
- const { query, match_incident, limit = 5 } = args;
1157
-
1158
- logger.info(`Searching knowledge base with: ${query}`);
1159
-
1160
- try {
1161
- const processedQuery = this.processNaturalLanguageQuery(query, 'knowledge');
1162
- const articles = await this.client.searchRecords('kb_knowledge', processedQuery, limit);
1163
-
1164
- let result: any = {
1165
- total_results: articles.success ? articles.data.result.length : 0,
1166
- knowledge_articles: articles.success ? articles.data.result : []
1167
- };
1168
-
1169
- // If matching to incident, provide relevance scoring
1170
- if (match_incident) {
1171
- const incident = await this.getIncidentDetails(match_incident);
1172
- if (incident) {
1173
- const relevanceScores = await this.calculateKnowledgeRelevance(articles.success ? articles.data.result : [], incident);
1174
- result = { ...result, relevance_scores: relevanceScores };
1175
- }
1176
- }
1177
-
1178
- return {
1179
- content: [
1180
- {
1181
- type: 'text',
1182
- text: `Found ${articles.success ? articles.data.result.length : 0} knowledge articles matching query: "${query}"\n\n${JSON.stringify(result, null, 2)}`
1183
- }
1184
- ]
1185
- };
1186
- } catch (error) {
1187
- logger.error('Error searching knowledge base:', error);
1188
- throw new McpError(ErrorCode.InternalError, `Failed to search knowledge base: ${error}`);
1189
- }
1190
- }
1191
-
1192
- private async handlePredictiveAnalysis(args: any) {
1193
- const { prediction_type, timeframe = 'week' } = args;
1194
-
1195
- logger.info(`Performing predictive _analysis: ${prediction_type} for ${timeframe}`);
1196
-
1197
- try {
1198
- const predictions = await this.performPredictiveAnalysis(prediction_type, timeframe);
1199
-
1200
- return {
1201
- content: [
1202
- {
1203
- type: 'text',
1204
- text: `Predictive Analysis (${prediction_type} - ${timeframe}):\n\n${JSON.stringify(predictions, null, 2)}`
1205
- }
1206
- ]
1207
- };
1208
- } catch (error) {
1209
- logger.error('Error performing predictive _analysis:', error);
1210
- throw new McpError(ErrorCode.InternalError, `Failed to perform predictive _analysis: ${error}`);
1211
- }
1212
- }
1213
-
1214
- // Helper methods for intelligent analysis
1215
-
1216
- private processNaturalLanguageQuery(query: string, context: string): string {
1217
- // Convert natural language to ServiceNow encoded query
1218
- const lowerQuery = query.toLowerCase();
1219
-
1220
- // Common ServiceNow query patterns
1221
- if (lowerQuery.includes('high priority')) {
1222
- return 'priority=1';
1223
- }
1224
- if (lowerQuery.includes('critical')) {
1225
- return 'priority=1^ORseverity=1';
1226
- }
1227
- if (lowerQuery.includes('open') || lowerQuery.includes('active')) {
1228
- return 'active=true^state!=6^state!=7';
1229
- }
1230
- if (lowerQuery.includes('closed') || lowerQuery.includes('resolved')) {
1231
- return 'state=6^ORstate=7';
1232
- }
1233
- if (lowerQuery.includes('today')) {
1234
- return 'sys_created_onONToday@javascript:gs.daysAgoStart(0)@javascript:gs.daysAgoEnd(0)';
1235
- }
1236
- if (lowerQuery.includes('last week')) {
1237
- return 'sys_created_onONLast week@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(1)';
1238
- }
1239
-
1240
- // Context-specific processing
1241
- if (context === 'incident') {
1242
- if (lowerQuery.includes('network')) {
1243
- return 'categoryLIKEnetwork^ORshort_descriptionLIKEnetwork';
1244
- }
1245
- if (lowerQuery.includes('database')) {
1246
- return 'categoryLIKEdatabase^ORshort_descriptionLIKEdatabase';
1247
- }
1248
- }
1249
-
1250
- // Default: treat as general search
1251
- return `short_descriptionLIKE${query}^ORdescriptionLIKE${query}`;
1252
- }
1253
-
1254
- private async analyzeIncidents(incidents: any[]) {
1255
- const patterns = [];
1256
- const categories = new Map();
1257
-
1258
- for (const incident of incidents) {
1259
- // Count categories
1260
- const category = incident.category || 'uncategorized';
1261
- categories.set(category, (categories.get(category) || 0) + 1);
1262
-
1263
- // Detect patterns
1264
- const description = (incident.short_description || '').toLowerCase();
1265
- for (const [patternName, pattern] of Object.entries(commonPatterns)) {
1266
- if (pattern.keywords.some(keyword => description.includes(keyword))) {
1267
- patterns.push({
1268
- incident_number: incident.number,
1269
- pattern: patternName,
1270
- confidence: 0.8
1271
- });
1272
- }
1273
- }
1274
- }
1275
-
1276
- return {
1277
- patterns_detected: patterns,
1278
- category_distribution: Object.fromEntries(categories),
1279
- total_analyzed: incidents.length
1280
- };
1281
- }
1282
-
1283
- private async getIncidentDetails(incident_id: string): Promise<any> {
1284
- // Try to get incident by number first, then by sys_id
1285
- let query = `number=${incident_id}`;
1286
- let incidents = await this.client.searchRecords('incident', query, 1);
1287
-
1288
- if (!incidents.success || incidents.data.result.length === 0) {
1289
- // Try by sys_id
1290
- query = `sys_id=${incident_id}`;
1291
- incidents = await this.client.searchRecords('incident', query, 1);
1292
- }
1293
-
1294
- return (incidents.success && incidents.data.result.length > 0) ? incidents.data.result[0] : null;
1295
- }
1296
-
1297
- private async performIncidentAnalysis(incident: any, include_similar: boolean, suggest_resolution: boolean): Promise<IncidentAnalysis> {
1298
- const _analysis: IncidentAnalysis = {
1299
- incident_id: incident.number,
1300
- patterns_found: [],
1301
- root_cause__analysis: '',
1302
- suggested_resolution: [],
1303
- confidence_score: 0,
1304
- similar_incidents: [],
1305
- knowledge_articles: [],
1306
- automated_actions: []
1307
- };
1308
-
1309
- // Pattern detection
1310
- const description = (incident.short_description || '').toLowerCase();
1311
- for (const [patternName, pattern] of Object.entries(commonPatterns)) {
1312
- if (pattern.keywords.some(keyword => description.includes(keyword))) {
1313
- _analysis.patterns_found.push(patternName);
1314
- _analysis.suggested_resolution.push(...pattern.common_solutions);
1315
- _analysis.confidence_score += 0.2;
1316
- }
1317
- }
1318
-
1319
- // Find similar incidents
1320
- if (include_similar) {
1321
- const similarQuery = `short_descriptionLIKE${incident.short_description}^sys_id!=${incident.sys_id}^state=6`;
1322
- const similarIncidents = await this.client.searchRecords('incident', similarQuery, 5);
1323
- _analysis.similar_incidents = similarIncidents.success ? similarIncidents.data.result : [];
1324
- }
1325
-
1326
- // Root cause analysis
1327
- _analysis.root_cause__analysis = this.generateRootCauseAnalysis(incident, _analysis.patterns_found);
1328
-
1329
- // Search knowledge base
1330
- const kbQuery = `textLIKE${incident.short_description}^workflow_state=published`;
1331
- const kbArticles = await this.client.searchRecords('kb_knowledge', kbQuery, 3);
1332
- _analysis.knowledge_articles = kbArticles.success ? kbArticles.data.result : [];
1333
-
1334
- // Automated actions
1335
- _analysis.automated_actions = this.generateAutomatedActions(incident, _analysis.patterns_found);
1336
-
1337
- return _analysis;
1338
- }
1339
-
1340
- private generateRootCauseAnalysis(incident: any, patterns: string[]): string {
1341
- if (patterns.length === 0) {
1342
- return 'No specific patterns detected. Manual investigation required.';
1343
- }
1344
-
1345
- const _analysis = [];
1346
-
1347
- if (patterns.includes('network_issues')) {
1348
- _analysis.push('Network connectivity issue detected. Check network infrastructure, DNS, and firewall rules.');
1349
- }
1350
-
1351
- if (patterns.includes('database_issues')) {
1352
- _analysis.push('Database connectivity or performance issue detected. Check database server, connection pools, and query performance.');
1353
- }
1354
-
1355
- if (patterns.includes('application_errors')) {
1356
- _analysis.push('Application error detected. Check application logs, resource utilization, and service status.');
1357
- }
1358
-
1359
- if (patterns.includes('auth_issues')) {
1360
- _analysis.push('Authentication issue detected. Check user credentials, LDAP/SSO configuration, and permissions.');
1361
- }
1362
-
1363
- return _analysis.join(' ');
1364
- }
1365
-
1366
- private generateAutomatedActions(incident: any, patterns: string[]): string[] {
1367
- const actions: string[] = [];
1368
-
1369
- if (patterns.includes('network_issues')) {
1370
- actions.push('Execute network connectivity test');
1371
- actions.push('Check DNS resolution');
1372
- actions.push('Verify firewall rules');
1373
- }
1374
-
1375
- if (patterns.includes('database_issues')) {
1376
- actions.push('Test database connectivity');
1377
- actions.push('Check database performance metrics');
1378
- actions.push('Review connection pool status');
1379
- }
1380
-
1381
- if (patterns.includes('application_errors')) {
1382
- actions.push('Restart application service');
1383
- actions.push('Check application logs');
1384
- actions.push('Monitor resource utilization');
1385
- }
1386
-
1387
- if (patterns.includes('auth_issues')) {
1388
- actions.push('Reset user session');
1389
- actions.push('Verify user permissions');
1390
- actions.push('Check authentication service status');
1391
- }
1392
-
1393
- return actions;
1394
- }
1395
-
1396
- private async generateResolutionActions(incident: any, _analysis: IncidentAnalysis): Promise<string[]> {
1397
- const actions: string[] = [];
1398
-
1399
- // Add high-confidence automated actions
1400
- if (_analysis.confidence_score > 0.6) {
1401
- actions.push(..._analysis.automated_actions);
1402
- }
1403
-
1404
- // Add knowledge-based actions
1405
- if (_analysis.knowledge_articles.length > 0) {
1406
- actions.push('Apply resolution from knowledge article');
1407
- }
1408
-
1409
- // Add pattern-based actions
1410
- if (_analysis.similar_incidents.length > 0) {
1411
- actions.push('Apply resolution from similar incident');
1412
- }
1413
-
1414
- return actions;
1415
- }
1416
-
1417
- private async executeResolutionActions(incident_id: string, actions: string[]): Promise<string[]> {
1418
- const executed = [];
1419
-
1420
- for (const action of actions) {
1421
- try {
1422
- // This would integrate with actual automation systems
1423
- logger.info(`Executing action: ${action} for incident ${incident_id}`);
1424
- executed.push(`✅ ${action}`);
1425
- } catch (error) {
1426
- executed.push(`❌ ${action}: ${error}`);
1427
- }
1428
- }
1429
-
1430
- return executed;
1431
- }
1432
-
1433
- private async getRequestItems(requests: any[]): Promise<any[]> {
1434
- const items = [];
1435
-
1436
- for (const request of requests) {
1437
- const requestItems = await this.client.searchRecords('sc_req_item', `request=${request.sys_id}`, 10);
1438
- if (requestItems.success) {
1439
- items.push(...requestItems.data.result);
1440
- }
1441
- }
1442
-
1443
- return items;
1444
- }
1445
-
1446
- private async getRelatedIncidents(problems: any[]): Promise<any[]> {
1447
- const incidents = [];
1448
-
1449
- for (const problem of problems) {
1450
- const relatedIncidents = await this.client.searchRecords('incident', `problem_id=${problem.sys_id}`, 10);
1451
- if (relatedIncidents.success) {
1452
- incidents.push(...relatedIncidents.data.result);
1453
- }
1454
- }
1455
-
1456
- return incidents;
1457
- }
1458
-
1459
- private async getCIRelationships(configItems: any[]): Promise<any[]> {
1460
- const relationships = [];
1461
-
1462
- for (const ci of configItems) {
1463
- try {
1464
- const ciRelationships = await this.client.searchRecords('cmdb_rel_ci', `parent=${ci.sys_id}^ORchild=${ci.sys_id}`, 10);
1465
- relationships.push({
1466
- ci_id: ci.sys_id,
1467
- ci_name: ci.name,
1468
- relationships: ciRelationships
1469
- });
1470
- } catch (error) {
1471
- logger.error(`Error getting relationships for CI ${ci.sys_id}:`, error);
1472
- }
1473
- }
1474
-
1475
- return relationships;
1476
- }
1477
-
1478
- private async getUserDetails(userId: string, include_roles: boolean, include_groups: boolean): Promise<any> {
1479
- const details: any = {};
1480
-
1481
- if (include_roles) {
1482
- details.roles = await this.client.searchRecords('sys_user_has_role', `user=${userId}`, 20);
1483
- }
1484
-
1485
- if (include_groups) {
1486
- details.groups = await this.client.searchRecords('sys_user_grmember', `user=${userId}`, 20);
1487
- }
1488
-
1489
- return details;
1490
- }
1491
-
1492
- private async calculateOperationalMetrics(timeframe: string, metricTypes: string[]): Promise<OperationalMetrics> {
1493
- const dateFilter = this.getDateFilter(timeframe);
1494
-
1495
- const metrics: OperationalMetrics = {
1496
- total_incidents: 0,
1497
- open_incidents: 0,
1498
- high_priority_incidents: 0,
1499
- avg_resolution_time: 0,
1500
- common_categories: [],
1501
- trending_issues: []
1502
- };
1503
-
1504
- try {
1505
- // Get total incidents
1506
- const totalIncidents = await this.client.searchRecords('incident', `sys_created_on${dateFilter}`, 1000);
1507
- metrics.total_incidents = totalIncidents.success ? totalIncidents.data.result.length : 0;
1508
-
1509
- // Get open incidents
1510
- const openIncidents = await this.client.searchRecords('incident', `active=true^sys_created_on${dateFilter}`, 1000);
1511
- metrics.open_incidents = openIncidents.success ? openIncidents.data.result.length : 0;
1512
-
1513
- // Get high priority incidents
1514
- const highPriorityIncidents = await this.client.searchRecords('incident', `priority=1^sys_created_on${dateFilter}`, 1000);
1515
- metrics.high_priority_incidents = highPriorityIncidents.success ? highPriorityIncidents.data.result.length : 0;
1516
-
1517
- // Calculate common categories
1518
- const categories = new Map();
1519
- if (totalIncidents.success) {
1520
- totalIncidents.data.result.forEach((incident: any) => {
1521
- const category = incident.category || 'uncategorized';
1522
- categories.set(category, (categories.get(category) || 0) + 1);
1523
- });
1524
- }
1525
-
1526
- metrics.common_categories = Array.from(categories.entries())
1527
- .sort((a, b) => b[1] - a[1])
1528
- .slice(0, 5)
1529
- .map(([category, count]) => `${category}: ${count}`);
1530
-
1531
- } catch (error) {
1532
- logger.error('Error calculating operational metrics:', error);
1533
- }
1534
-
1535
- return metrics;
1536
- }
1537
-
1538
- private getDateFilter(timeframe: string): string {
1539
- switch (timeframe) {
1540
- case 'today':
1541
- return 'ONToday@javascript:gs.daysAgoStart(0)@javascript:gs.daysAgoEnd(0)';
1542
- case 'week':
1543
- return 'ONLast 7 days@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(0)';
1544
- case 'month':
1545
- return 'ONLast 30 days@javascript:gs.daysAgoStart(30)@javascript:gs.daysAgoEnd(0)';
1546
- case 'quarter':
1547
- return 'ONLast 90 days@javascript:gs.daysAgoStart(90)@javascript:gs.daysAgoEnd(0)';
1548
- default:
1549
- return 'ONLast 7 days@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(0)';
1550
- }
1551
- }
1552
-
1553
- private async analyzePatterns(analysisType: string, timeframe: string): Promise<any> {
1554
- const dateFilter = this.getDateFilter(timeframe);
1555
-
1556
- switch (analysisType) {
1557
- case 'incident_patterns':
1558
- return await this.analyzeIncidentPatterns(dateFilter);
1559
- case 'request_trends':
1560
- return await this.analyzeRequestTrends(dateFilter);
1561
- case 'problem_root_causes':
1562
- return await this.analyzeProblemRootCauses(dateFilter);
1563
- case 'user_behavior':
1564
- return await this.analyzeUserBehavior(dateFilter);
1565
- default:
1566
- return { error: 'Unknown _analysis type' };
1567
- }
1568
- }
1569
-
1570
- private async analyzeIncidentPatterns(dateFilter: string): Promise<any> {
1571
- const incidents = await this.client.searchRecords('incident', `sys_created_on${dateFilter}`, 1000);
1572
-
1573
- const patterns = {
1574
- by_category: new Map(),
1575
- by_priority: new Map(),
1576
- by_state: new Map(),
1577
- by_hour: new Map(),
1578
- common_keywords: new Map()
1579
- };
1580
-
1581
- if (incidents.success) {
1582
- incidents.data.result.forEach((incident: any) => {
1583
- // Category patterns
1584
- const category = incident.category || 'uncategorized';
1585
- patterns.by_category.set(category, (patterns.by_category.get(category) || 0) + 1);
1586
-
1587
- // Priority patterns
1588
- const priority = incident.priority || 'unknown';
1589
- patterns.by_priority.set(priority, (patterns.by_priority.get(priority) || 0) + 1);
1590
-
1591
- // State patterns
1592
- const state = incident.state || 'unknown';
1593
- patterns.by_state.set(state, (patterns.by_state.get(state) || 0) + 1);
1594
-
1595
- // Time patterns
1596
- if (incident.sys_created_on) {
1597
- const hour = new Date(incident.sys_created_on).getHours();
1598
- patterns.by_hour.set(hour, (patterns.by_hour.get(hour) || 0) + 1);
1599
- }
1600
-
1601
- // Keyword patterns
1602
- const description = (incident.short_description || '').toLowerCase();
1603
- const keywords = description.split(/\s+/).filter((word: string) => word.length > 3);
1604
- keywords.forEach((keyword: string) => {
1605
- patterns.common_keywords.set(keyword, (patterns.common_keywords.get(keyword) || 0) + 1);
1606
- });
1607
- });
1608
- }
1609
-
1610
- return {
1611
- total_incidents: incidents.success ? incidents.data.result.length : 0,
1612
- patterns: {
1613
- by_category: Object.fromEntries(patterns.by_category),
1614
- by_priority: Object.fromEntries(patterns.by_priority),
1615
- by_state: Object.fromEntries(patterns.by_state),
1616
- by_hour: Object.fromEntries(patterns.by_hour),
1617
- top_keywords: Array.from(patterns.common_keywords.entries())
1618
- .sort((a, b) => b[1] - a[1])
1619
- .slice(0, 10)
1620
- .map(([keyword, count]) => ({ keyword, count }))
1621
- }
1622
- };
1623
- }
1624
-
1625
- private async analyzeRequestTrends(dateFilter: string): Promise<any> {
1626
- const requests = await this.client.searchRecords('sc_request', `sys_created_on${dateFilter}`, 1000);
1627
-
1628
- const trends = {
1629
- by_state: new Map(),
1630
- by_requested_for: new Map(),
1631
- by_approval_status: new Map()
1632
- };
1633
-
1634
- if (requests.success) {
1635
- requests.data.result.forEach((request: any) => {
1636
- const state = request.state || 'unknown';
1637
- trends.by_state.set(state, (trends.by_state.get(state) || 0) + 1);
1638
-
1639
- const requestedFor = request.requested_for || 'unknown';
1640
- trends.by_requested_for.set(requestedFor, (trends.by_requested_for.get(requestedFor) || 0) + 1);
1641
-
1642
- const approvalStatus = request.approval || 'unknown';
1643
- trends.by_approval_status.set(approvalStatus, (trends.by_approval_status.get(approvalStatus) || 0) + 1);
1644
- });
1645
- }
1646
-
1647
- return {
1648
- total_requests: requests.success ? requests.data.result.length : 0,
1649
- trends: {
1650
- by_state: Object.fromEntries(trends.by_state),
1651
- top_requesters: Array.from(trends.by_requested_for.entries())
1652
- .sort((a, b) => b[1] - a[1])
1653
- .slice(0, 10),
1654
- by_approval_status: Object.fromEntries(trends.by_approval_status)
1655
- }
1656
- };
1657
- }
1658
-
1659
- private async analyzeProblemRootCauses(dateFilter: string): Promise<any> {
1660
- const problems = await this.client.searchRecords('problem', `sys_created_on${dateFilter}`, 1000);
1661
-
1662
- const rootCauses = {
1663
- by_category: new Map(),
1664
- by_state: new Map(),
1665
- resolution_patterns: new Map()
1666
- };
1667
-
1668
- if (problems.success) {
1669
- problems.data.result.forEach((problem: any) => {
1670
- const category = problem.category || 'uncategorized';
1671
- rootCauses.by_category.set(category, (rootCauses.by_category.get(category) || 0) + 1);
1672
-
1673
- const state = problem.state || 'unknown';
1674
- rootCauses.by_state.set(state, (rootCauses.by_state.get(state) || 0) + 1);
1675
-
1676
- if (problem.resolution_notes) {
1677
- const resolution = problem.resolution_notes.toLowerCase();
1678
- // Simple pattern matching for resolution types
1679
- if (resolution.includes('restart')) {
1680
- rootCauses.resolution_patterns.set('restart', (rootCauses.resolution_patterns.get('restart') || 0) + 1);
1681
- }
1682
- if (resolution.includes('configuration')) {
1683
- rootCauses.resolution_patterns.set('configuration', (rootCauses.resolution_patterns.get('configuration') || 0) + 1);
1684
- }
1685
- if (resolution.includes('patch') || resolution.includes('update')) {
1686
- rootCauses.resolution_patterns.set('patch/update', (rootCauses.resolution_patterns.get('patch/update') || 0) + 1);
1687
- }
1688
- }
1689
- });
1690
- }
1691
-
1692
- return {
1693
- total_problems: problems.success ? problems.data.result.length : 0,
1694
- root_causes: {
1695
- by_category: Object.fromEntries(rootCauses.by_category),
1696
- by_state: Object.fromEntries(rootCauses.by_state),
1697
- resolution_patterns: Object.fromEntries(rootCauses.resolution_patterns)
1698
- }
1699
- };
1700
- }
1701
-
1702
- private async analyzeUserBehavior(dateFilter: string): Promise<any> {
1703
- try {
1704
- // Query actual user activity data from ServiceNow
1705
- const activities = [];
1706
-
1707
- // Get recent incidents created by users
1708
- const incidents = await this.client.searchRecords(
1709
- 'incident',
1710
- `sys_created_on>=${dateFilter}^caller_idISNOTEMPTY`,
1711
- 100
1712
- );
1713
-
1714
- if (incidents.success && incidents.data) {
1715
- const incidentData = Array.isArray(incidents.data) ? incidents.data : [incidents.data];
1716
- const userIncidents = new Map();
1717
-
1718
- incidentData.forEach(inc => {
1719
- const userId = inc.caller_id?.value || inc.caller_id;
1720
- if (userId) {
1721
- userIncidents.set(userId, (userIncidents.get(userId) || 0) + 1);
1722
- }
1723
- });
1724
-
1725
- activities.push({
1726
- metric: 'incident_reporting',
1727
- total_incidents: incidentData.length,
1728
- unique_users: userIncidents.size,
1729
- avg_per_user: userIncidents.size > 0 ? Math.round(incidentData.length / userIncidents.size * 10) / 10 : 0
1730
- });
1731
- }
1732
-
1733
- // Get service requests
1734
- const requests = await this.client.searchRecords(
1735
- 'sc_request',
1736
- `sys_created_on>=${dateFilter}^requested_forISNOTEMPTY`,
1737
- 100
1738
- );
1739
-
1740
- if (requests.success && requests.data) {
1741
- const requestData = Array.isArray(requests.data) ? requests.data : [requests.data];
1742
- activities.push({
1743
- metric: 'request_frequency',
1744
- total_requests: requestData.length,
1745
- period: dateFilter
1746
- });
1747
- }
1748
-
1749
- return {
1750
- analysis_period: dateFilter,
1751
- generated_at: new Date().toISOString(),
1752
- user_activities: activities,
1753
- summary: `Analyzed ${activities.length} activity metrics for period: ${dateFilter}`,
1754
- note: 'Analysis based on actual ServiceNow data from incident and request tables'
1755
- };
1756
-
1757
- } catch (error) {
1758
- return {
1759
- error: 'User behavior _analysis failed',
1760
- message: error instanceof Error ? error.message : String(error),
1761
- available_metrics: ['incident_reporting', 'request_frequency']
1762
- };
1763
- }
1764
- }
1765
-
1766
- private async calculateKnowledgeRelevance(articles: any[], incident: any): Promise<any[]> {
1767
- const relevanceScores: any[] = [];
1768
-
1769
- const incidentDescription = (incident.short_description || '').toLowerCase();
1770
- const incidentKeywords = incidentDescription.split(/\s+/).filter((word: string) => word.length > 3);
1771
-
1772
- articles.forEach(article => {
1773
- const articleText = (article.text || '').toLowerCase();
1774
- const articleKeywords = articleText.split(/\s+/).filter((word: string) => word.length > 3);
1775
-
1776
- // Simple relevance scoring based on keyword overlap
1777
- const commonKeywords = incidentKeywords.filter((keyword: string) => articleKeywords.includes(keyword));
1778
- const relevanceScore = commonKeywords.length / Math.max(incidentKeywords.length, 1);
1779
-
1780
- relevanceScores.push({
1781
- article_id: article.sys_id,
1782
- article_title: article.title,
1783
- relevance_score: relevanceScore,
1784
- common_keywords: commonKeywords
1785
- });
1786
- });
1787
-
1788
- return relevanceScores.sort((a: any, b: any) => b.relevance_score - a.relevance_score);
1789
- }
1790
-
1791
- private async performPredictiveAnalysis(predictionType: string, timeframe: string): Promise<any> {
1792
- try {
1793
- const currentDate = new Date();
1794
- const _analysis: any = {
1795
- prediction_type: predictionType,
1796
- timeframe: timeframe,
1797
- generated_at: currentDate.toISOString(),
1798
- analysis_method: 'trend__analysis',
1799
- data_points: []
1800
- };
1801
-
1802
- switch (predictionType) {
1803
- case 'incident_volume':
1804
- _analysis.data_points = await this.analyzeIncidentTrends(timeframe);
1805
- _analysis.interpretation = 'Based on recent incident creation patterns';
1806
- break;
1807
- case 'system_failure':
1808
- _analysis.data_points = await this.analyzeSystemHealthTrends(timeframe);
1809
- _analysis.interpretation = 'Based on critical incident patterns and system health indicators';
1810
- break;
1811
- case 'resource_exhaustion':
1812
- _analysis.data_points = await this.analyzeResourceTrends(timeframe);
1813
- _analysis.interpretation = 'Based on request volumes and capacity indicators';
1814
- break;
1815
- case 'user_impact':
1816
- _analysis.data_points = await this.analyzeUserImpactTrends(timeframe);
1817
- _analysis.interpretation = 'Based on user-reported incidents and service requests';
1818
- break;
1819
- default:
1820
- _analysis.data_points = [{ error: 'Unknown prediction type', supported_types: ['incident_volume', 'system_failure', 'resource_exhaustion', 'user_impact'] }];
1821
- _analysis.interpretation = 'Unsupported _analysis type';
1822
- }
1823
-
1824
- return _analysis;
1825
- } catch (error) {
1826
- return {
1827
- prediction_type: predictionType,
1828
- timeframe: timeframe,
1829
- error: 'Analysis failed',
1830
- message: error instanceof Error ? error.message : String(error),
1831
- generated_at: new Date().toISOString()
1832
- };
1833
- }
1834
- }
1835
-
1836
- // New trend _analysis methods
1837
- private async analyzeIncidentTrends(timeframe: string): Promise<any[]> {
1838
- try {
1839
- const incidents = await this.client.searchRecords('incident', 'sys_created_onONToday@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(0)', 100);
1840
-
1841
- if (incidents.success && incidents.data) {
1842
- const incidentData = Array.isArray(incidents.data) ? incidents.data : [incidents.data];
1843
- const dailyCounts = new Map();
1844
-
1845
- incidentData.forEach(incident => {
1846
- const date = new Date(incident.sys_created_on).toDateString();
1847
- dailyCounts.set(date, (dailyCounts.get(date) || 0) + 1);
1848
- });
1849
-
1850
- return Array.from(dailyCounts.entries()).map(([date, count]) => ({
1851
- date,
1852
- incident_count: count,
1853
- trend: count > 5 ? 'increasing' : 'stable'
1854
- }));
1855
- }
1856
- } catch (error) {
1857
- return [{ error: 'Failed to analyze incident trends', message: String(error) }];
1858
- }
1859
- return [{ message: 'No incident data available for _analysis' }];
1860
- }
1861
-
1862
- private async analyzeSystemHealthTrends(timeframe: string): Promise<any[]> {
1863
- try {
1864
- const criticalIncidents = await this.client.searchRecords('incident', 'priority=1^sys_created_onONToday@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(0)', 50);
1865
-
1866
- if (criticalIncidents.success && criticalIncidents.data) {
1867
- const criticalData = Array.isArray(criticalIncidents.data) ? criticalIncidents.data : [criticalIncidents.data];
1868
-
1869
- return [{
1870
- metric: 'critical_incidents',
1871
- count: criticalData.length,
1872
- health_indicator: criticalData.length > 3 ? 'at_risk' : 'stable',
1873
- recent_issues: criticalData.slice(0, 3).map(inc => inc.short_description)
1874
- }];
1875
- }
1876
- } catch (error) {
1877
- return [{ error: 'Failed to analyze system health trends', message: String(error) }];
1878
- }
1879
- return [{ message: 'No system health data available' }];
1880
- }
1881
-
1882
- private async analyzeResourceTrends(timeframe: string): Promise<any[]> {
1883
- try {
1884
- const requests = await this.client.searchRecords('sc_request', 'sys_created_onONToday@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(0)', 100);
1885
-
1886
- if (requests.success && requests.data) {
1887
- const requestData = Array.isArray(requests.data) ? requests.data : [requests.data];
1888
-
1889
- return [{
1890
- metric: 'request_volume',
1891
- total_requests: requestData.length,
1892
- resource_pressure: requestData.length > 20 ? 'high' : 'normal',
1893
- trend: 'stable'
1894
- }];
1895
- }
1896
- } catch (error) {
1897
- return [{ error: 'Failed to analyze resource trends', message: String(error) }];
1898
- }
1899
- return [{ message: 'No resource usage data available' }];
1900
- }
1901
-
1902
- private async analyzeUserImpactTrends(timeframe: string): Promise<any[]> {
1903
- try {
1904
- const userIncidents = await this.client.searchRecords('incident', 'caller_idISNOTEMPTY^sys_created_onONToday@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(0)', 100);
1905
-
1906
- if (userIncidents.success && userIncidents.data) {
1907
- const incidentData = Array.isArray(userIncidents.data) ? userIncidents.data : [userIncidents.data];
1908
- const impactCounts = new Map();
1909
-
1910
- incidentData.forEach(incident => {
1911
- const impact = incident.impact || 'unknown';
1912
- impactCounts.set(impact, (impactCounts.get(impact) || 0) + 1);
1913
- });
1914
-
1915
- return [{
1916
- metric: 'user_impact_distribution',
1917
- high_impact: impactCounts.get('1') || 0,
1918
- medium_impact: impactCounts.get('2') || 0,
1919
- low_impact: impactCounts.get('3') || 0,
1920
- total_affected_users: incidentData.length,
1921
- impact_trend: (impactCounts.get('1') || 0) > 5 ? 'increasing' : 'stable'
1922
- }];
1923
- }
1924
- } catch (error) {
1925
- return [{ error: 'Failed to analyze user impact trends', message: String(error) }];
1926
- }
1927
- return [{ message: 'No user impact data available' }];
1928
- }
1929
-
1930
- private async predictIncidentVolume(timeframe: string): Promise<any[]> {
1931
- // Get historical incident data
1932
- const historicalData = await this.client.searchRecords('incident', 'sys_created_onONLast 30 days@javascript:gs.daysAgoStart(30)@javascript:gs.daysAgoEnd(0)', 1000);
1933
-
1934
- const dailyVolume = new Map();
1935
- if (historicalData.success) {
1936
- historicalData.data.result.forEach((incident: any) => {
1937
- const date = new Date(incident.sys_created_on).toDateString();
1938
- dailyVolume.set(date, (dailyVolume.get(date) || 0) + 1);
1939
- });
1940
- }
1941
-
1942
- const avgDailyVolume = Array.from(dailyVolume.values()).reduce((a, b) => a + b, 0) / dailyVolume.size;
1943
-
1944
- return [
1945
- {
1946
- metric: 'daily_incident_volume',
1947
- predicted_value: Math.round(avgDailyVolume * 1.1), // Simple trend prediction
1948
- current_average: Math.round(avgDailyVolume),
1949
- trend: 'increasing',
1950
- confidence: 0.6
1951
- }
1952
- ];
1953
- }
1954
-
1955
- private async predictSystemFailure(timeframe: string): Promise<any[]> {
1956
- // Analyze problem records and incident patterns
1957
- const problems = await this.client.searchRecords('problem', 'sys_created_onONLast 30 days@javascript:gs.daysAgoStart(30)@javascript:gs.daysAgoEnd(0)', 100);
1958
-
1959
- const systemFailures = problems.success ? problems.data.result.filter((problem: any) =>
1960
- (problem.short_description || '').toLowerCase().includes('system') ||
1961
- (problem.short_description || '').toLowerCase().includes('failure') ||
1962
- (problem.short_description || '').toLowerCase().includes('outage')
1963
- ) : [];
1964
-
1965
- return [
1966
- {
1967
- metric: 'system_failure_risk',
1968
- predicted_value: systemFailures.length > 5 ? 'high' : 'medium',
1969
- current_problems: systemFailures.length,
1970
- risk_factors: ['Historical system problems', 'Incident patterns'],
1971
- confidence: 0.5
1972
- }
1973
- ];
1974
- }
1975
-
1976
- private async predictResourceExhaustion(timeframe: string): Promise<any[]> {
1977
- // This would require integration with monitoring systems
1978
- return [
1979
- {
1980
- metric: 'resource_exhaustion_risk',
1981
- predicted_value: 'low',
1982
- message: 'Resource exhaustion prediction requires monitoring system integration',
1983
- confidence: 0.3
1984
- }
1985
- ];
1986
- }
1987
-
1988
- private async predictUserImpact(timeframe: string): Promise<any[]> {
1989
- // Analyze user-reported incidents
1990
- const userIncidents = await this.client.searchRecords('incident', 'sys_created_onONLast 7 days@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(0)^priority=1', 100);
1991
-
1992
- const impactLevels = new Map();
1993
- if (userIncidents.success) {
1994
- userIncidents.data.result.forEach((incident: any) => {
1995
- const impact = incident.impact || 'unknown';
1996
- impactLevels.set(impact, (impactLevels.get(impact) || 0) + 1);
1997
- });
1998
- }
1999
-
2000
- return [
2001
- {
2002
- metric: 'user_impact_prediction',
2003
- predicted_value: impactLevels.get('1') > 5 ? 'high' : 'medium',
2004
- current_high_impact: impactLevels.get('1') || 0,
2005
- trend_factors: ['High priority incidents', 'User complaints'],
2006
- confidence: 0.6
2007
- }
2008
- ];
2009
- }
2010
-
2011
- private async handleCatalogItemManager(args: any) {
2012
- const { action, item_id, ...params } = args;
2013
-
2014
- logger.info(`Catalog item manager action: ${action}`, { item_id, params });
2015
-
2016
- try {
2017
- switch (action) {
2018
- case 'create': {
2019
- // Get default catalog if none provided
2020
- let catalogId = params.sc_catalogs || params.catalog_id;
2021
- let categoryId = params.sc_categories || params.category_id;
2022
-
2023
- if (!catalogId) {
2024
- // Find default Service Catalog
2025
- const defaultCatalogResult = await this.client.searchRecords(
2026
- 'sc_catalog',
2027
- 'active=true^ORDERBYsys_created_on',
2028
- 1
2029
- );
2030
-
2031
- if (defaultCatalogResult.success && defaultCatalogResult.data?.result?.length > 0) {
2032
- catalogId = defaultCatalogResult.data.result[0].sys_id;
2033
- logger.info('Using default catalog:', {
2034
- catalogId,
2035
- catalogName: defaultCatalogResult.data.result[0].title
2036
- });
2037
- } else {
2038
- logger.warn('No catalogs found, catalog item may not be visible');
2039
- }
2040
- }
2041
-
2042
- if (!categoryId) {
2043
- // Find a default category like "Hardware" or "General"
2044
- const defaultCategoryResult = await this.client.searchRecords(
2045
- 'sc_category',
2046
- 'active=true^titleLIKEHardware^ORtitleLIKEGeneral^ORtitleLIKEIT^ORDERBYsys_created_on',
2047
- 1
2048
- );
2049
-
2050
- if (defaultCategoryResult.success && defaultCategoryResult.data?.result?.length > 0) {
2051
- categoryId = defaultCategoryResult.data.result[0].sys_id;
2052
- logger.info('Using default category:', {
2053
- categoryId,
2054
- categoryName: defaultCategoryResult.data.result[0].title
2055
- });
2056
- }
2057
- }
2058
-
2059
- const catalogItem = {
2060
- name: params.name,
2061
- short_description: params.short_description,
2062
- description: params.description,
2063
- price: params.price || '0',
2064
- recurring_price: params.recurring_price || '0',
2065
- billable: params.billable || false,
2066
- active: params.active !== false,
2067
- workflow: params.workflow,
2068
- picture: params.picture,
2069
- sc_catalogs: catalogId,
2070
- sc_categories: categoryId,
2071
- sys_class_name: 'sc_cat_item'
2072
- };
2073
-
2074
- const result = await this.client.createRecord('sc_cat_item', catalogItem);
2075
-
2076
- if (result.success && params.variables && params.variables.length > 0) {
2077
- // Add variables to the catalog item
2078
- for (const variable of params.variables) {
2079
- await this.createCatalogVariable(result.data.sys_id, variable);
2080
- }
2081
- }
2082
-
2083
- return {
2084
- content: [{
2085
- type: 'text',
2086
- text: result.success
2087
- ? `✅ Catalog item created successfully!\n\nSys ID: ${result.data.sys_id}\nName: ${result.data.name}\n\nView at: ${this.getServiceNowUrl()}/nav_to.do?uri=sc_cat_item.do?sys_id=${result.data.sys_id}`
2088
- : `❌ Failed to create catalog item: ${result.error}`
2089
- }]
2090
- };
2091
- }
2092
-
2093
- case 'update': {
2094
- if (!item_id) {
2095
- throw new Error('item_id is required for update action');
2096
- }
2097
-
2098
- const updateData: any = {};
2099
- if (params.name) updateData.name = params.name;
2100
- if (params.short_description) updateData.short_description = params.short_description;
2101
- if (params.description) updateData.description = params.description;
2102
- if (params.price) updateData.price = params.price;
2103
- if (params.recurring_price) updateData.recurring_price = params.recurring_price;
2104
- if (params.billable !== undefined) updateData.billable = params.billable;
2105
- if (params.active !== undefined) updateData.active = params.active;
2106
- if (params.workflow) updateData.workflow = params.workflow;
2107
- if (params.sc_catalogs) updateData.sc_catalogs = params.sc_catalogs;
2108
- if (params.sc_categories) updateData.sc_categories = params.sc_categories;
2109
-
2110
- const result = await this.client.updateRecord('sc_cat_item', item_id, updateData);
2111
-
2112
- return {
2113
- content: [{
2114
- type: 'text',
2115
- text: result.success
2116
- ? `✅ Catalog item updated successfully!\n\nSys ID: ${item_id}\n\nView at: ${this.getServiceNowUrl()}/nav_to.do?uri=sc_cat_item.do?sys_id=${item_id}`
2117
- : `❌ Failed to update catalog item: ${result.error}`
2118
- }]
2119
- };
2120
- }
2121
-
2122
- case 'list': {
2123
- const query = params.category_id ? `sc_categories=${params.category_id}` : 'active=true';
2124
- const result = await this.client.searchRecords('sc_cat_item', query, 50);
2125
-
2126
- if (result.success) {
2127
- const items = result.data.result.map((item: any) => ({
2128
- sys_id: item.sys_id,
2129
- name: item.name,
2130
- short_description: item.short_description,
2131
- price: item.price,
2132
- active: item.active,
2133
- category: item.category?.display_value
2134
- }));
2135
-
2136
- return {
2137
- content: [{
2138
- type: 'text',
2139
- text: `📋 Found ${items.length} catalog items:\n\n${JSON.stringify(items, null, 2)}`
2140
- }]
2141
- };
2142
- } else {
2143
- throw new Error(`Failed to list catalog items: ${result.error}`);
2144
- }
2145
- }
2146
-
2147
- case 'get': {
2148
- if (!item_id) {
2149
- throw new Error('item_id is required for get action');
2150
- }
2151
-
2152
- const result = await this.client.getRecord('sc_cat_item', item_id);
2153
-
2154
- if (result.success) {
2155
- // Get variables for the item
2156
- const variables = await this.client.searchRecords('item_option_new', `cat_item=${item_id}`, 50);
2157
-
2158
- return {
2159
- content: [{
2160
- type: 'text',
2161
- text: `📋 Catalog Item Details:\n\n${JSON.stringify(result.data, null, 2)}\n\n📊 Variables:\n${JSON.stringify(variables.data?.result || [], null, 2)}`
2162
- }]
2163
- };
2164
- } else {
2165
- throw new Error(`Failed to get catalog item: ${result.error}`);
2166
- }
2167
- }
2168
-
2169
- case 'add_variable': {
2170
- if (!item_id) {
2171
- throw new Error('item_id is required for add_variable action');
2172
- }
2173
-
2174
- if (!params.variables || params.variables.length === 0) {
2175
- throw new Error('variables array is required');
2176
- }
2177
-
2178
- const results = [];
2179
- for (const variable of params.variables) {
2180
- const result = await this.createCatalogVariable(item_id, variable);
2181
- results.push(result);
2182
- }
2183
-
2184
- return {
2185
- content: [{
2186
- type: 'text',
2187
- text: `✅ Added ${results.length} variables to catalog item ${item_id}`
2188
- }]
2189
- };
2190
- }
2191
-
2192
- case 'set_workflow': {
2193
- if (!item_id) {
2194
- throw new Error('item_id is required for set_workflow action');
2195
- }
2196
-
2197
- if (!params.workflow) {
2198
- throw new Error('workflow is required');
2199
- }
2200
-
2201
- const result = await this.client.updateRecord('sc_cat_item', item_id, {
2202
- workflow: params.workflow
2203
- });
2204
-
2205
- return {
2206
- content: [{
2207
- type: 'text',
2208
- text: result.success
2209
- ? `✅ Workflow set successfully for catalog item ${item_id}`
2210
- : `❌ Failed to set workflow: ${result.error}`
2211
- }]
2212
- };
2213
- }
2214
-
2215
- case 'publish': {
2216
- if (!item_id) {
2217
- throw new Error('item_id is required for publish action');
2218
- }
2219
-
2220
- const result = await this.client.updateRecord('sc_cat_item', item_id, {
2221
- active: true
2222
- });
2223
-
2224
- return {
2225
- content: [{
2226
- type: 'text',
2227
- text: result.success
2228
- ? `✅ Catalog item ${item_id} published successfully`
2229
- : `❌ Failed to publish catalog item: ${result.error}`
2230
- }]
2231
- };
2232
- }
2233
-
2234
- case 'retire': {
2235
- if (!item_id) {
2236
- throw new Error('item_id is required for retire action');
2237
- }
2238
-
2239
- const result = await this.client.updateRecord('sc_cat_item', item_id, {
2240
- active: false
2241
- });
2242
-
2243
- return {
2244
- content: [{
2245
- type: 'text',
2246
- text: result.success
2247
- ? `✅ Catalog item ${item_id} retired successfully`
2248
- : `❌ Failed to retire catalog item: ${result.error}`
2249
- }]
2250
- };
2251
- }
2252
-
2253
- default:
2254
- throw new Error(`Unknown action: ${action}`);
2255
- }
2256
- } catch (error) {
2257
- logger.error('Error in catalog item manager:', error);
2258
- throw new McpError(ErrorCode.InternalError, `Catalog item manager error: ${error}`);
2259
- }
2260
- }
2261
-
2262
- private async createCatalogVariable(catalogItemId: string, variable: any) {
2263
- const variableData: any = {
2264
- cat_item: catalogItemId,
2265
- name: variable.name,
2266
- question_text: variable.label || variable.name,
2267
- type: this.mapVariableType(variable.type),
2268
- mandatory: variable.mandatory || false,
2269
- default_value: variable.default_value || '',
2270
- order: variable.order || 100
2271
- };
2272
-
2273
- // Handle select box choices
2274
- if (variable.type === 'select_box' && variable.choices) {
2275
- variableData.lookup_table = 'question_choice';
2276
- // Note: In a real implementation, you'd need to create choice records
2277
- }
2278
-
2279
- return await this.client.createRecord('item_option_new', variableData);
2280
- }
2281
-
2282
- private mapVariableType(type: string): string {
2283
- const typeMap: Record<string, string> = {
2284
- 'single_line_text': '6', // Single Line Text
2285
- 'multi_line_text': '2', // Multi Line Text
2286
- 'select_box': '3', // Select Box
2287
- 'checkbox': '7', // Checkbox
2288
- 'reference': '8', // Reference
2289
- 'date': '9', // Date
2290
- 'datetime': '10' // Date/Time
2291
- };
2292
-
2293
- return typeMap[type] || '6'; // Default to single line text
2294
- }
2295
-
2296
- private async handleCatalogItemSearch(args: any) {
2297
- const { query, include_inactive = false, fuzzy_match = true, category_filter, limit = 50, include_variables = false } = args;
2298
-
2299
- logger.info(`Searching catalog items for: ${query}`, { fuzzy_match, category_filter, limit });
2300
-
2301
- try {
2302
- // Build search queries
2303
- const searchQueries = [];
2304
-
2305
- // Exact match
2306
- searchQueries.push(`name=${query}`);
2307
-
2308
- // Contains match
2309
- searchQueries.push(`nameLIKE${query}`);
2310
- searchQueries.push(`short_descriptionLIKE${query}`);
2311
-
2312
- // Fuzzy matching for similar items
2313
- if (fuzzy_match) {
2314
- // Handle common variations
2315
- const variations = this.generateSearchVariations(query);
2316
- variations.forEach(variation => {
2317
- searchQueries.push(`nameLIKE${variation}`);
2318
- searchQueries.push(`short_descriptionLIKE${variation}`);
2319
- });
2320
- }
2321
-
2322
- // Add active filter
2323
- const activeFilter = include_inactive ? '' : '^active=true';
2324
-
2325
- // Add category filter if provided
2326
- let categoryFilter = '';
2327
- if (category_filter) {
2328
- // First try to find the category
2329
- const categoryResult = await this.client.searchRecords('sc_category',
2330
- `nameLIKE${category_filter}^ORtitle=${category_filter}^ORsys_id=${category_filter}`,
2331
- 10);
2332
- if (categoryResult.success && categoryResult.data?.result?.length > 0) {
2333
- // Try to find the most relevant category
2334
- const exactMatch = categoryResult.data.result.find((c: any) =>
2335
- c.name.toLowerCase() === category_filter.toLowerCase());
2336
- const category = exactMatch || categoryResult.data.result[0];
2337
- categoryFilter = `^sc_categoriesLIKE${category.sys_id}`;
2338
- }
2339
- }
2340
-
2341
- // Execute searches
2342
- const allResults = new Map();
2343
-
2344
- for (const searchQuery of searchQueries) {
2345
- const fullQuery = searchQuery + activeFilter + categoryFilter;
2346
- const result = await this.client.searchRecords('sc_cat_item', fullQuery, limit);
2347
-
2348
- if (result.success && result.data?.result) {
2349
- result.data.result.forEach((item: any) => {
2350
- if (!allResults.has(item.sys_id)) {
2351
- // Filter out items that don't make sense for the search
2352
- const itemName = (item.name || '').toLowerCase();
2353
- const itemDesc = (item.short_description || '').toLowerCase();
2354
-
2355
- // Skip items that are clearly not what the user is looking for
2356
- if (this.shouldExcludeItem(query, itemName, itemDesc)) {
2357
- return;
2358
- }
2359
-
2360
- allResults.set(item.sys_id, {
2361
- sys_id: item.sys_id,
2362
- name: item.name,
2363
- short_description: item.short_description,
2364
- price: item.price,
2365
- recurring_price: item.recurring_price,
2366
- active: item.active,
2367
- category: item.sc_categories?.display_value,
2368
- catalog: item.sc_catalogs?.display_value,
2369
- model_number: item.model,
2370
- manufacturer: item.vendor,
2371
- variables: []
2372
- });
2373
- }
2374
- });
2375
- }
2376
- }
2377
-
2378
- // Get variables if requested
2379
- if (include_variables && allResults.size > 0) {
2380
- for (const [sys_id, item] of allResults) {
2381
- const variablesResult = await this.client.searchRecords('item_option_new', `cat_item=${sys_id}`, 50);
2382
- if (variablesResult.success && variablesResult.data?.result) {
2383
- item.variables = variablesResult.data.result.map((v: any) => ({
2384
- name: v.name,
2385
- label: v.question_text,
2386
- type: v.type,
2387
- mandatory: v.mandatory,
2388
- default_value: v.default_value
2389
- }));
2390
- }
2391
- }
2392
- }
2393
-
2394
- const resultsArray = Array.from(allResults.values());
2395
-
2396
- // Sort by relevance (exact matches first)
2397
- resultsArray.sort((a, b) => {
2398
- const aExact = a.name.toLowerCase() === query.toLowerCase();
2399
- const bExact = b.name.toLowerCase() === query.toLowerCase();
2400
- if (aExact && !bExact) return -1;
2401
- if (!aExact && bExact) return 1;
2402
- return 0;
2403
- });
2404
-
2405
- if (resultsArray.length === 0) {
2406
- // Provide suggestions if no results found
2407
- const suggestions = this.generateSearchSuggestions(query);
2408
- return {
2409
- content: [{
2410
- type: 'text',
2411
- text: `❌ No catalog items found for "${query}"\n\n` +
2412
- `💡 Suggestions:\n` +
2413
- suggestions.map(s => `- Try searching for "${s}"`).join('\n') +
2414
- `\n\n💡 Tips:\n` +
2415
- `- Check if the item name is spelled correctly\n` +
2416
- `- Try searching with partial names (e.g., "iPhone" instead of "iPhone 6S")\n` +
2417
- `- Use category filter to narrow down results\n` +
2418
- `- Enable include_inactive if looking for retired items`
2419
- }]
2420
- };
2421
- }
2422
-
2423
- // Format results
2424
- let resultText = `🔍 Found ${resultsArray.length} catalog items matching "${query}":\n\n`;
2425
-
2426
- resultsArray.forEach((item, index) => {
2427
- resultText += `${index + 1}. **${item.name}** (${item.sys_id})\n`;
2428
- resultText += ` 📝 ${item.short_description || 'No description'}\n`;
2429
- resultText += ` 💰 Price: $${item.price || '0'} ${item.recurring_price ? `(+ $${item.recurring_price}/month)` : ''}\n`;
2430
- resultText += ` 📁 Category: ${item.category || 'Uncategorized'}\n`;
2431
- resultText += ` 📚 Catalog: ${item.catalog || 'Default'}\n`;
2432
- if (item.model_number) {
2433
- resultText += ` 🔢 Model: ${item.model_number}\n`;
2434
- }
2435
- if (item.manufacturer) {
2436
- resultText += ` 🏭 Manufacturer: ${item.manufacturer}\n`;
2437
- }
2438
- resultText += ` ✅ Status: ${item.active ? 'Active' : 'Inactive'}\n`;
2439
-
2440
- if (item.variables && item.variables.length > 0) {
2441
- resultText += ` 📋 Variables (${item.variables.length}):\n`;
2442
- item.variables.forEach((v: any) => {
2443
- resultText += ` - ${v.label} (${v.name}) - ${v.type}${v.mandatory ? ' *Required' : ''}\n`;
2444
- });
2445
- }
2446
-
2447
- resultText += '\n';
2448
- });
2449
-
2450
- resultText += `\n💡 Use snow_catalog_item_manager to create or update catalog items`;
2451
-
2452
- return {
2453
- content: [{
2454
- type: 'text',
2455
- text: resultText
2456
- }]
2457
- };
2458
- } catch (error) {
2459
- logger.error('Error searching catalog items:', error);
2460
- throw new McpError(ErrorCode.InternalError, `Catalog search error: ${error}`);
2461
- }
2462
- }
2463
-
2464
- private generateSearchVariations(query: string): string[] {
2465
- const variations = [];
2466
- const lowerQuery = query.toLowerCase();
2467
-
2468
- // Handle common product variations
2469
- if (lowerQuery.includes('iphone')) {
2470
- // Add variations without spaces, with different numbers
2471
- const baseModel = query.replace(/iphone\s*/i, 'iPhone ');
2472
- variations.push(baseModel);
2473
- variations.push(baseModel.replace(' ', ''));
2474
-
2475
- // Try different model numbers
2476
- const modelMatch = query.match(/\d+/);
2477
- if (modelMatch) {
2478
- const modelNum = parseInt(modelMatch[0]);
2479
- variations.push(query.replace(modelNum.toString(), (modelNum + 1).toString()));
2480
- variations.push(query.replace(modelNum.toString(), (modelNum - 1).toString()));
2481
- }
2482
- }
2483
-
2484
- // Handle laptop/computer variations
2485
- if (lowerQuery.includes('laptop') || lowerQuery.includes('computer')) {
2486
- variations.push('notebook', 'macbook', 'thinkpad', 'dell', 'hp');
2487
- }
2488
-
2489
- // Handle phone variations
2490
- if (lowerQuery.includes('phone') || lowerQuery.includes('mobile')) {
2491
- variations.push('smartphone', 'android', 'samsung', 'pixel');
2492
- }
2493
-
2494
- // Handle monitor/display variations
2495
- if (lowerQuery.includes('monitor') || lowerQuery.includes('display') ||
2496
- lowerQuery.includes('screen') || lowerQuery.includes('lcd')) {
2497
- variations.push('monitor', 'display', 'screen', 'lcd', 'led', 'desktop monitor',
2498
- 'computer monitor', 'external display', 'lcd monitor', 'led monitor',
2499
- 'dell monitor', 'hp monitor', 'samsung monitor', 'lg monitor');
2500
- }
2501
-
2502
- // Handle desktop/workstation variations
2503
- if (lowerQuery.includes('desktop') || lowerQuery.includes('workstation')) {
2504
- variations.push('desktop computer', 'workstation', 'pc', 'desktop pc',
2505
- 'dell desktop', 'hp desktop', 'lenovo desktop');
2506
- }
2507
-
2508
- // Handle specific hardware terms
2509
- if (lowerQuery.includes('hardware') || lowerQuery.includes('equipment')) {
2510
- // Extract specific items from the query
2511
- const specificItems = [];
2512
- if (lowerQuery.includes('monitor') || lowerQuery.includes('display') || lowerQuery.includes('screen')) {
2513
- specificItems.push('monitor', 'display');
2514
- }
2515
- if (lowerQuery.includes('desktop')) {
2516
- specificItems.push('desktop', 'computer');
2517
- }
2518
- if (lowerQuery.includes('laptop')) {
2519
- specificItems.push('laptop', 'notebook');
2520
- }
2521
-
2522
- // If we found specific items, use those instead of generic "hardware"
2523
- if (specificItems.length > 0) {
2524
- variations.push(...specificItems);
2525
- }
2526
- }
2527
-
2528
- // Remove duplicates and empty strings
2529
- return [...new Set(variations)].filter(v => v && v.length > 0);
2530
- }
2531
-
2532
- private generateSearchSuggestions(query: string): string[] {
2533
- const suggestions = [];
2534
- const lowerQuery = query.toLowerCase();
2535
-
2536
- if (lowerQuery.includes('iphone')) {
2537
- suggestions.push('iPhone', 'Apple iPhone', 'iOS device', 'Apple mobile');
2538
- } else if (lowerQuery.includes('laptop')) {
2539
- suggestions.push('notebook', 'computer', 'workstation', 'MacBook', 'ThinkPad');
2540
- } else if (lowerQuery.includes('software')) {
2541
- suggestions.push('license', 'application', 'subscription', 'SaaS');
2542
- } else if (lowerQuery.includes('monitor') || lowerQuery.includes('display') ||
2543
- lowerQuery.includes('screen') || lowerQuery.includes('lcd')) {
2544
- suggestions.push('monitor', 'display', 'external monitor', 'desktop monitor',
2545
- 'Dell monitor', 'HP monitor', 'Samsung monitor');
2546
- } else if (lowerQuery.includes('desktop')) {
2547
- suggestions.push('desktop computer', 'workstation', 'PC', 'desktop PC',
2548
- 'Dell desktop', 'HP desktop');
2549
- } else if (lowerQuery.includes('hardware')) {
2550
- // For hardware searches, suggest specific item types
2551
- suggestions.push('monitor', 'keyboard', 'mouse', 'laptop', 'desktop',
2552
- 'printer', 'scanner', 'docking station', 'headset');
2553
- } else {
2554
- // Generic suggestions based on the query
2555
- const words = query.split(' ');
2556
- if (words.length > 1) {
2557
- // Try individual words
2558
- suggestions.push(...words.filter(w => w.length > 3));
2559
- // Try first and last word
2560
- suggestions.push(words[0], words[words.length - 1]);
2561
- }
2562
-
2563
- // Add generic category suggestions
2564
- suggestions.push('hardware', 'software', 'equipment', 'device', 'accessory');
2565
- }
2566
-
2567
- return [...new Set(suggestions)].filter(s => s && s !== query && s.length > 0);
2568
- }
2569
-
2570
- private shouldExcludeItem(query: string, itemName: string, itemDesc: string): boolean {
2571
- const lowerQuery = query.toLowerCase();
2572
-
2573
- // If searching for hardware/monitors/displays, exclude service/process items
2574
- if ((lowerQuery.includes('hardware') || lowerQuery.includes('monitor') ||
2575
- lowerQuery.includes('display') || lowerQuery.includes('screen') ||
2576
- lowerQuery.includes('desktop') || lowerQuery.includes('equipment')) &&
2577
- (itemName.includes('decommission') || itemName.includes('service') ||
2578
- itemName.includes('process') || itemName.includes('request') ||
2579
- itemName.includes('removal') || itemName.includes('decomm') ||
2580
- itemDesc.includes('decommission') || itemDesc.includes('service process'))) {
2581
- return true;
2582
- }
2583
-
2584
- // If searching for specific hardware, exclude unrelated items
2585
- if (lowerQuery.includes('monitor') || lowerQuery.includes('display') || lowerQuery.includes('screen')) {
2586
- // Exclude items that are clearly not monitors
2587
- if ((itemName.includes('server') || itemName.includes('controller') ||
2588
- itemName.includes('software') || itemName.includes('license') ||
2589
- itemName.includes('training') || itemName.includes('support')) &&
2590
- !itemName.includes('monitor') && !itemName.includes('display') &&
2591
- !itemName.includes('screen')) {
2592
- return true;
2593
- }
2594
- }
2595
-
2596
- // If searching for desktops, exclude non-desktop items
2597
- if (lowerQuery.includes('desktop')) {
2598
- if ((itemName.includes('mobile') || itemName.includes('phone') ||
2599
- itemName.includes('tablet') || itemName.includes('service')) &&
2600
- !itemName.includes('desktop')) {
2601
- return true;
2602
- }
2603
- }
2604
-
2605
- return false;
2606
- }
2607
-
2608
- private async handleCleanupTestArtifacts(args: any) {
2609
- const {
2610
- artifact_types = ['catalog_items', 'flows', 'users'],
2611
- test_patterns = ['Test%', 'Mock%', 'Demo%', '%_test_%', '%test', '%mock'],
2612
- max_age_hours = 1,
2613
- dry_run = false,
2614
- preserve_update_set_entries = true,
2615
- update_set_filter
2616
- } = args;
2617
-
2618
- logger.info('Starting test artifact cleanup', {
2619
- artifact_types,
2620
- test_patterns,
2621
- max_age_hours,
2622
- dry_run
2623
- });
2624
-
2625
- const cleanupResults = {
2626
- start_time: new Date().toISOString(),
2627
- dry_run,
2628
- artifacts_found: {
2629
- catalog_items: [],
2630
- flows: [],
2631
- users: [],
2632
- requests: []
2633
- } as any,
2634
- artifacts_deleted: {
2635
- catalog_items: 0,
2636
- flows: 0,
2637
- users: 0,
2638
- requests: 0
2639
- },
2640
- update_set_entries_preserved: 0,
2641
- errors: [] as string[],
2642
- summary: ''
2643
- };
2644
-
2645
- try {
2646
- // Calculate cutoff time
2647
- const cutoffTime = new Date();
2648
- cutoffTime.setHours(cutoffTime.getHours() - max_age_hours);
2649
- const cutoffISO = cutoffTime.toISOString();
2650
-
2651
- // Process each artifact type
2652
- for (const artifactType of artifact_types) {
2653
- if (artifactType === 'all' || artifact_types.includes('catalog_items')) {
2654
- await this.cleanupTestCatalogItems(
2655
- test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter
2656
- );
2657
- }
2658
-
2659
- // Flow cleanup removed in v1.4.38 - flows no longer supported
2660
-
2661
- if (artifactType === 'all' || artifact_types.includes('users')) {
2662
- await this.cleanupTestUsers(
2663
- test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter
2664
- );
2665
- }
2666
-
2667
- if (artifactType === 'all' || artifact_types.includes('requests')) {
2668
- await this.cleanupTestRequests(
2669
- test_patterns, cutoffISO, dry_run, cleanupResults, update_set_filter
2670
- );
2671
- }
2672
- }
2673
-
2674
- // Generate summary
2675
- const totalFound = Object.values(cleanupResults.artifacts_found)
2676
- .reduce((sum, items) => (typeof sum === 'number' ? sum : 0) + (Array.isArray(items) ? items.length : 0), 0);
2677
- const totalDeleted = Object.values(cleanupResults.artifacts_deleted)
2678
- .reduce((sum, count) => sum + (typeof count === 'number' ? count : 0), 0);
2679
-
2680
- cleanupResults.summary = dry_run
2681
- ? `🔍 Dry Run: Found ${totalFound} test artifacts that would be cleaned up`
2682
- : `🧹 Cleaned up ${totalDeleted} test artifacts successfully`;
2683
-
2684
- // Format results
2685
- let resultText = `🧹 Test Artifact Cleanup Results\n${'='.repeat(50)}\n\n`;
2686
-
2687
- if (dry_run) {
2688
- resultText += `🔍 **DRY RUN MODE** - No actual deletion performed\n\n`;
2689
- }
2690
-
2691
- resultText += `⏰ **Cleanup Configuration:**\n`;
2692
- resultText += `- Artifact Types: ${artifact_types.join(', ')}\n`;
2693
- resultText += `- Test Patterns: ${test_patterns.join(', ')}\n`;
2694
- resultText += `- Max Age: ${max_age_hours} hours\n`;
2695
- resultText += `- Cutoff Time: ${cutoffISO}\n`;
2696
- resultText += `- Preserve Update Set Entries: ${preserve_update_set_entries ? '✅' : '❌'}\n\n`;
2697
-
2698
- // Report findings by type
2699
- ['catalog_items', 'flows', 'users', 'requests'].forEach(type => {
2700
- const found = cleanupResults.artifacts_found[type] || [];
2701
- const deleted = cleanupResults.artifacts_deleted[type] || 0;
2702
-
2703
- if (found.length > 0) {
2704
- resultText += `📦 **${type.replace('_', ' ').toUpperCase()}:**\n`;
2705
- resultText += ` ${dry_run ? 'Found' : 'Deleted'}: ${dry_run ? found.length : deleted}\n`;
2706
-
2707
- if (dry_run && found.length > 0) {
2708
- found.slice(0, 10).forEach((item: any) => {
2709
- resultText += ` - ${item.name} (${item.sys_id}) - Created: ${item.sys_created_on}\n`;
2710
- });
2711
- if (found.length > 10) {
2712
- resultText += ` ... and ${found.length - 10} more\n`;
2713
- }
2714
- }
2715
- resultText += '\n';
2716
- }
2717
- });
2718
-
2719
- if (cleanupResults.update_set_entries_preserved > 0) {
2720
- resultText += `📋 **Update Set Audit Trail Preserved:** ${cleanupResults.update_set_entries_preserved} entries\n\n`;
2721
- }
2722
-
2723
- if (cleanupResults.errors.length > 0) {
2724
- resultText += `❌ **Errors:**\n`;
2725
- cleanupResults.errors.forEach(error => {
2726
- resultText += ` - ${error}\n`;
2727
- });
2728
- resultText += '\n';
2729
- }
2730
-
2731
- resultText += `✅ **${cleanupResults.summary}**\n\n`;
2732
-
2733
- if (!dry_run && totalDeleted > 0) {
2734
- resultText += `💡 **Note:** Update Set entries have been preserved as audit trail.\n`;
2735
- resultText += `This shows that testing was performed and cleanup was completed.\n\n`;
2736
- }
2737
-
2738
- if (dry_run) {
2739
- resultText += `▶️ **Next Steps:**\n`;
2740
- resultText += `1. Review the artifacts that would be deleted\n`;
2741
- resultText += `2. Run again with dry_run: false to perform actual cleanup\n`;
2742
- resultText += `3. Verify Update Set entries are preserved as intended\n`;
2743
- }
2744
-
2745
- return {
2746
- content: [{
2747
- type: 'text',
2748
- text: resultText
2749
- }]
2750
- };
2751
-
2752
- } catch (error: any) {
2753
- logger.error('Error during test artifact cleanup:', error);
2754
- return {
2755
- content: [{
2756
- type: 'text',
2757
- text: `❌ Test artifact cleanup failed\n\nError: ${error.message}\n\n` +
2758
- `🔧 Troubleshooting:\n` +
2759
- `1. Check ServiceNow connection and permissions\n` +
2760
- `2. Verify test patterns are correct\n` +
2761
- `3. Ensure artifacts exist and are accessible\n\n` +
2762
- `Debug Info: ${JSON.stringify(cleanupResults, null, 2)}`
2763
- }]
2764
- };
2765
- }
2766
- }
2767
-
2768
- private async cleanupTestCatalogItems(
2769
- patterns: string[],
2770
- cutoffTime: string,
2771
- dryRun: boolean,
2772
- results: any,
2773
- updateSetFilter?: string
2774
- ) {
2775
- try {
2776
- // Build query for test catalog items
2777
- const patternQueries = patterns.map(pattern => {
2778
- if (pattern.includes('%')) {
2779
- return `nameLIKE${pattern.replace(/%/g, '')}`;
2780
- } else {
2781
- return `name=${pattern}`;
2782
- }
2783
- });
2784
-
2785
- const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}`;
2786
-
2787
- const searchResult = await this.client.searchRecords('sc_cat_item', query, 100);
2788
-
2789
- if (searchResult.success && searchResult.data?.result) {
2790
- results.artifacts_found.catalog_items = searchResult.data.result.map((item: any) => ({
2791
- sys_id: item.sys_id,
2792
- name: item.name,
2793
- sys_created_on: item.sys_created_on
2794
- }));
2795
-
2796
- if (!dryRun) {
2797
- // Delete each catalog item
2798
- for (const item of searchResult.data.result) {
2799
- const deleteResult = await this.client.deleteRecord('sc_cat_item', item.sys_id);
2800
- if (deleteResult.success) {
2801
- results.artifacts_deleted.catalog_items++;
2802
- } else {
2803
- results.errors.push(`Failed to delete catalog item ${item.name}: ${deleteResult.error}`);
2804
- }
2805
- }
2806
- }
2807
- }
2808
- } catch (error: any) {
2809
- results.errors.push(`Error cleaning catalog items: ${error.message}`);
2810
- }
2811
- }
2812
-
2813
- private async cleanupTestUsers(
2814
- patterns: string[],
2815
- cutoffTime: string,
2816
- dryRun: boolean,
2817
- results: any,
2818
- updateSetFilter?: string
2819
- ) {
2820
- try {
2821
- const patternQueries = patterns.map(pattern => {
2822
- if (pattern.includes('%')) {
2823
- return `user_nameLIKE${pattern.replace(/%/g, '')}^ORfirst_nameLIKE${pattern.replace(/%/g, '')}`;
2824
- } else {
2825
- return `user_name=${pattern}^ORfirst_name=${pattern}`;
2826
- }
2827
- });
2828
-
2829
- const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}^active=false`;
2830
-
2831
- const searchResult = await this.client.searchRecords('sys_user', query, 100);
2832
-
2833
- if (searchResult.success && searchResult.data?.result) {
2834
- results.artifacts_found.users = searchResult.data.result.map((user: any) => ({
2835
- sys_id: user.sys_id,
2836
- name: user.user_name || user.name,
2837
- sys_created_on: user.sys_created_on
2838
- }));
2839
-
2840
- if (!dryRun) {
2841
- for (const user of searchResult.data.result) {
2842
- const deleteResult = await this.client.deleteRecord('sys_user', user.sys_id);
2843
- if (deleteResult.success) {
2844
- results.artifacts_deleted.users++;
2845
- } else {
2846
- results.errors.push(`Failed to delete user ${user.user_name}: ${deleteResult.error}`);
2847
- }
2848
- }
2849
- }
2850
- }
2851
- } catch (error: any) {
2852
- results.errors.push(`Error cleaning users: ${error.message}`);
2853
- }
2854
- }
2855
-
2856
- private async cleanupTestRequests(
2857
- patterns: string[],
2858
- cutoffTime: string,
2859
- dryRun: boolean,
2860
- results: any,
2861
- updateSetFilter?: string
2862
- ) {
2863
- try {
2864
- const patternQueries = patterns.map(pattern => {
2865
- if (pattern.includes('%')) {
2866
- return `short_descriptionLIKE${pattern.replace(/%/g, '')}`;
2867
- } else {
2868
- return `short_description=${pattern}`;
2869
- }
2870
- });
2871
-
2872
- const query = `(${patternQueries.join('^OR')})^sys_created_on<${cutoffTime}`;
2873
-
2874
- const searchResult = await this.client.searchRecords('sc_request', query, 100);
2875
-
2876
- if (searchResult.success && searchResult.data?.result) {
2877
- results.artifacts_found.requests = searchResult.data.result.map((req: any) => ({
2878
- sys_id: req.sys_id,
2879
- name: req.number || req.short_description,
2880
- sys_created_on: req.sys_created_on
2881
- }));
2882
-
2883
- if (!dryRun) {
2884
- for (const request of searchResult.data.result) {
2885
- // Cancel request instead of deleting (safer)
2886
- const updateResult = await this.client.updateRecord('sc_request', request.sys_id, {
2887
- state: '4', // Cancelled
2888
- comments: 'Cancelled by test cleanup automation'
2889
- });
2890
- if (updateResult.success) {
2891
- results.artifacts_deleted.requests++;
2892
- } else {
2893
- results.errors.push(`Failed to cancel request ${request.number}: ${updateResult.error}`);
2894
- }
2895
- }
2896
- }
2897
- }
2898
- } catch (error: any) {
2899
- results.errors.push(`Error cleaning requests: ${error.message}`);
2900
- }
2901
- }
2902
-
2903
- private getServiceNowUrl(): string {
2904
- // This would need to get the instance URL from credentials
2905
- return 'https://instance.service-now.com';
2906
- }
2907
-
2908
- private async handleCreateUserGroup(args: any) {
2909
- const { name, description, email, manager, parent, type, active = true } = args;
2910
-
2911
- logger.info(`Creating user group: ${name}`);
2912
-
2913
- try {
2914
- // Check if group already exists
2915
- const existingGroup = await this.client.get('/api/now/table/sys_user_group', {
2916
- sysparm_query: `name=${name}`,
2917
- sysparm_limit: 1
2918
- });
2919
-
2920
- if (existingGroup.result?.length > 0) {
2921
- return {
2922
- content: [{
2923
- type: 'text',
2924
- text: `✅ Group "${name}" already exists!\n\nSys ID: ${existingGroup.result[0].sys_id}\nManager: ${existingGroup.result[0].manager?.display_value || 'None'}\nType: ${existingGroup.result[0].type || 'Standard'}\nActive: ${existingGroup.result[0].active}\n\nNo action taken.`
2925
- }]
2926
- };
2927
- }
2928
-
2929
- // Prepare group data
2930
- const groupData: any = {
2931
- name,
2932
- active: active.toString()
2933
- };
2934
-
2935
- if (description) groupData.description = description;
2936
- if (email) groupData.email = email;
2937
- if (type) groupData.type = type;
2938
-
2939
- // Handle manager lookup
2940
- if (manager) {
2941
- const managerUser = await this.findUserBySysIdOrUsername(manager);
2942
- if (managerUser) {
2943
- groupData.manager = managerUser.sys_id;
2944
- } else {
2945
- logger.warn(`Manager "${manager}" not found, creating group without manager`);
2946
- }
2947
- }
2948
-
2949
- // Handle parent group lookup
2950
- if (parent) {
2951
- const parentGroup = await this.findGroupBySysIdOrName(parent);
2952
- if (parentGroup) {
2953
- groupData.parent = parentGroup.sys_id;
2954
- } else {
2955
- logger.warn(`Parent group "${parent}" not found, creating group without parent`);
2956
- }
2957
- }
2958
-
2959
- // Create the group
2960
- const result = await this.client.post('/api/now/table/sys_user_group', groupData);
2961
-
2962
- if (result.result) {
2963
- const groupUrl = `${process.env.SNOW_INSTANCE ? `https://${process.env.SNOW_INSTANCE.replace(/\/$/, '')}.service-now.com` : ''}/sys_user_group.do?sys_id=${result.result.sys_id}`;
2964
-
2965
- return {
2966
- content: [{
2967
- type: 'text',
2968
- text: `✅ **User Group Created Successfully!**\n\n**Group Name:** ${result.result.name}\n**Sys ID:** ${result.result.sys_id}\n**Type:** ${result.result.type || 'Standard'}\n**Active:** ${result.result.active}\n${result.result.manager ? `**Manager:** ${result.result.manager.display_value}` : ''}\n${result.result.parent ? `**Parent Group:** ${result.result.parent.display_value}` : ''}\n\n**View Group:** ${groupUrl}\n\nYou can now assign users to this group using \`snow_assign_user_to_group\`.`
2969
- }]
2970
- };
2971
- } else {
2972
- throw new Error('Failed to create group - no result returned');
2973
- }
2974
-
2975
- } catch (error) {
2976
- return {
2977
- content: [{
2978
- type: 'text',
2979
- text: `❌ **Failed to create user group**\n\nError: ${error instanceof Error ? error.message : String(error)}\n\nTip: Ensure you have the necessary permissions to create groups (admin or user_admin role).`
2980
- }]
2981
- };
2982
- }
2983
- }
2984
-
2985
- private async handleCreateUser(args: any) {
2986
- const {
2987
- user_name,
2988
- first_name,
2989
- last_name,
2990
- email,
2991
- department,
2992
- manager,
2993
- phone,
2994
- title,
2995
- location,
2996
- active = true,
2997
- password
2998
- } = args;
2999
-
3000
- logger.info(`Creating user: ${user_name}`);
3001
-
3002
- try {
3003
- // Check if user already exists
3004
- const existingUser = await this.client.get('/api/now/table/sys_user', {
3005
- sysparm_query: `user_name=${user_name}`,
3006
- sysparm_limit: 1
3007
- });
3008
-
3009
- if (existingUser.result?.length > 0) {
3010
- return {
3011
- content: [{
3012
- type: 'text',
3013
- text: `✅ User "${user_name}" already exists!\n\nSys ID: ${existingUser.result[0].sys_id}\nName: ${existingUser.result[0].first_name} ${existingUser.result[0].last_name}\nEmail: ${existingUser.result[0].email}\nActive: ${existingUser.result[0].active}\n\nNo action taken.`
3014
- }]
3015
- };
3016
- }
3017
-
3018
- // Prepare user data
3019
- const userData: any = {
3020
- user_name,
3021
- first_name,
3022
- last_name,
3023
- email,
3024
- active: active.toString()
3025
- };
3026
-
3027
- if (phone) userData.phone = phone;
3028
- if (title) userData.title = title;
3029
- if (password) userData.user_password = password;
3030
-
3031
- // Handle manager lookup
3032
- if (manager) {
3033
- const managerUser = await this.findUserBySysIdOrUsername(manager);
3034
- if (managerUser) {
3035
- userData.manager = managerUser.sys_id;
3036
- } else {
3037
- logger.warn(`Manager "${manager}" not found, creating user without manager`);
3038
- }
3039
- }
3040
-
3041
- // Handle department lookup
3042
- if (department) {
3043
- const dept = await this.findDepartment(department);
3044
- if (dept) {
3045
- userData.department = dept.sys_id;
3046
- } else {
3047
- logger.warn(`Department "${department}" not found, creating user without department`);
3048
- }
3049
- }
3050
-
3051
- // Handle location lookup
3052
- if (location) {
3053
- const loc = await this.findLocation(location);
3054
- if (loc) {
3055
- userData.location = loc.sys_id;
3056
- } else {
3057
- logger.warn(`Location "${location}" not found, creating user without location`);
3058
- }
3059
- }
3060
-
3061
- // Create the user
3062
- const result = await this.client.post('/api/now/table/sys_user', userData);
3063
-
3064
- if (result.result) {
3065
- const userUrl = `${process.env.SNOW_INSTANCE ? `https://${process.env.SNOW_INSTANCE.replace(/\/$/, '')}.service-now.com` : ''}/sys_user.do?sys_id=${result.result.sys_id}`;
3066
-
3067
- return {
3068
- content: [{
3069
- type: 'text',
3070
- text: `✅ **User Created Successfully!**\n\n**Username:** ${result.result.user_name}\n**Name:** ${result.result.first_name} ${result.result.last_name}\n**Email:** ${result.result.email}\n**Sys ID:** ${result.result.sys_id}\n**Active:** ${result.result.active}\n${result.result.manager ? `**Manager:** ${result.result.manager.display_value}` : ''}\n${result.result.department ? `**Department:** ${result.result.department.display_value}` : ''}\n${password ? '\n⚠️ **Note:** User will be required to change password on first login.' : ''}\n\n**View User:** ${userUrl}\n\nYou can now assign this user to groups using \`snow_assign_user_to_group\`.`
3071
- }]
3072
- };
3073
- } else {
3074
- throw new Error('Failed to create user - no result returned');
3075
- }
3076
-
3077
- } catch (error) {
3078
- return {
3079
- content: [{
3080
- type: 'text',
3081
- text: `❌ **Failed to create user**\n\nError: ${error instanceof Error ? error.message : String(error)}\n\nTip: Ensure you have the necessary permissions to create users (admin or user_admin role).`
3082
- }]
3083
- };
3084
- }
3085
- }
3086
-
3087
- private async handleAssignUserToGroup(args: any) {
3088
- const { user, group } = args;
3089
-
3090
- logger.info(`Assigning user ${user} to group ${group}`);
3091
-
3092
- try {
3093
- // Find the user
3094
- const userRecord = await this.findUserBySysIdOrUsername(user);
3095
- if (!userRecord) {
3096
- return {
3097
- content: [{
3098
- type: 'text',
3099
- text: `❌ User "${user}" not found. Please check the username or sys_id.`
3100
- }]
3101
- };
3102
- }
3103
-
3104
- // Find the group
3105
- const groupRecord = await this.findGroupBySysIdOrName(group);
3106
- if (!groupRecord) {
3107
- return {
3108
- content: [{
3109
- type: 'text',
3110
- text: `❌ Group "${group}" not found. Please check the group name or sys_id.\n\nTip: You can create a new group using \`snow_create_user_group\`.`
3111
- }]
3112
- };
3113
- }
3114
-
3115
- // Check if user is already in group
3116
- const existingMembership = await this.client.get('/api/now/table/sys_user_grmember', {
3117
- sysparm_query: `user=${userRecord.sys_id}^group=${groupRecord.sys_id}`,
3118
- sysparm_limit: 1
3119
- });
3120
-
3121
- if (existingMembership.result?.length > 0) {
3122
- return {
3123
- content: [{
3124
- type: 'text',
3125
- text: `ℹ️ User "${userRecord.user_name}" is already a member of group "${groupRecord.name}".\n\nNo action taken.`
3126
- }]
3127
- };
3128
- }
3129
-
3130
- // Add user to group
3131
- const result = await this.client.post('/api/now/table/sys_user_grmember', {
3132
- user: userRecord.sys_id,
3133
- group: groupRecord.sys_id
3134
- });
3135
-
3136
- if (result.result) {
3137
- return {
3138
- content: [{
3139
- type: 'text',
3140
- text: `✅ **User Added to Group Successfully!**\n\n**User:** ${userRecord.first_name} ${userRecord.last_name} (${userRecord.user_name})\n**Group:** ${groupRecord.name}\n**Membership Sys ID:** ${result.result.sys_id}\n\nThe user now has all permissions associated with the "${groupRecord.name}" group.`
3141
- }]
3142
- };
3143
- } else {
3144
- throw new Error('Failed to add user to group - no result returned');
3145
- }
3146
-
3147
- } catch (error) {
3148
- return {
3149
- content: [{
3150
- type: 'text',
3151
- text: `❌ **Failed to assign user to group**\n\nError: ${error instanceof Error ? error.message : String(error)}\n\nTip: Ensure you have the necessary permissions to manage group membership.`
3152
- }]
3153
- };
3154
- }
3155
- }
3156
-
3157
- private async handleRemoveUserFromGroup(args: any) {
3158
- const { user, group } = args;
3159
-
3160
- logger.info(`Removing user ${user} from group ${group}`);
3161
-
3162
- try {
3163
- // Find the user
3164
- const userRecord = await this.findUserBySysIdOrUsername(user);
3165
- if (!userRecord) {
3166
- return {
3167
- content: [{
3168
- type: 'text',
3169
- text: `❌ User "${user}" not found. Please check the username or sys_id.`
3170
- }]
3171
- };
3172
- }
3173
-
3174
- // Find the group
3175
- const groupRecord = await this.findGroupBySysIdOrName(group);
3176
- if (!groupRecord) {
3177
- return {
3178
- content: [{
3179
- type: 'text',
3180
- text: `❌ Group "${group}" not found. Please check the group name or sys_id.`
3181
- }]
3182
- };
3183
- }
3184
-
3185
- // Find the membership record
3186
- const membership = await this.client.get('/api/now/table/sys_user_grmember', {
3187
- sysparm_query: `user=${userRecord.sys_id}^group=${groupRecord.sys_id}`,
3188
- sysparm_limit: 1
3189
- });
3190
-
3191
- if (!membership.result || membership.result.length === 0) {
3192
- return {
3193
- content: [{
3194
- type: 'text',
3195
- text: `ℹ️ User "${userRecord.user_name}" is not a member of group "${groupRecord.name}".\n\nNo action taken.`
3196
- }]
3197
- };
3198
- }
3199
-
3200
- // Remove user from group
3201
- await this.client.delete(`/api/now/table/sys_user_grmember/${membership.result[0].sys_id}`);
3202
-
3203
- return {
3204
- content: [{
3205
- type: 'text',
3206
- text: `✅ **User Removed from Group Successfully!**\n\n**User:** ${userRecord.first_name} ${userRecord.last_name} (${userRecord.user_name})\n**Group:** ${groupRecord.name}\n\nThe user no longer has permissions associated with the "${groupRecord.name}" group.`
3207
- }]
3208
- };
3209
-
3210
- } catch (error) {
3211
- return {
3212
- content: [{
3213
- type: 'text',
3214
- text: `❌ **Failed to remove user from group**\n\nError: ${error instanceof Error ? error.message : String(error)}\n\nTip: Ensure you have the necessary permissions to manage group membership.`
3215
- }]
3216
- };
3217
- }
3218
- }
3219
-
3220
- private async handleListGroupMembers(args: any) {
3221
- const { group, active_only = true } = args;
3222
-
3223
- logger.info(`Listing members of group: ${group}`);
3224
-
3225
- try {
3226
- // Find the group
3227
- const groupRecord = await this.findGroupBySysIdOrName(group);
3228
- if (!groupRecord) {
3229
- return {
3230
- content: [{
3231
- type: 'text',
3232
- text: `❌ Group "${group}" not found. Please check the group name or sys_id.`
3233
- }]
3234
- };
3235
- }
3236
-
3237
- // Get group members
3238
- let query = `group=${groupRecord.sys_id}`;
3239
- if (active_only) {
3240
- query += '^user.active=true';
3241
- }
3242
-
3243
- const members = await this.client.get('/api/now/table/sys_user_grmember', {
3244
- sysparm_query: query,
3245
- sysparm_fields: 'user.user_name,user.first_name,user.last_name,user.email,user.title,user.active,user.sys_id',
3246
- sysparm_limit: 100
3247
- });
3248
-
3249
- if (!members.result || members.result.length === 0) {
3250
- return {
3251
- content: [{
3252
- type: 'text',
3253
- text: `ℹ️ **Group "${groupRecord.name}" has no members.**\n\nYou can add users to this group using \`snow_assign_user_to_group\`.`
3254
- }]
3255
- };
3256
- }
3257
-
3258
- // Format member list
3259
- let response = `👥 **Members of "${groupRecord.name}" Group**\n\n`;
3260
- response += `**Total Members:** ${members.result.length}${active_only ? ' (active only)' : ''}\n\n`;
3261
-
3262
- members.result.forEach((member: any, index: number) => {
3263
- const u = member.user;
3264
-
3265
- // Defensive programming: check if user object exists and has required fields
3266
- if (!u || !u.user_name) {
3267
- logger.warn(`Group member ${index + 1} has invalid user data:`, member);
3268
- response += `${index + 1}. **Invalid User Data** - ${member.sys_id || 'Unknown ID'}\n\n`;
3269
- return;
3270
- }
3271
-
3272
- const firstName = u.first_name || 'N/A';
3273
- const lastName = u.last_name || 'N/A';
3274
- const email = u.email || 'N/A';
3275
- const title = u.title || '';
3276
- const active = u.active !== undefined ? u.active : 'Unknown';
3277
- const sysId = u.sys_id || 'N/A';
3278
-
3279
- response += `${index + 1}. **${firstName} ${lastName}** (${u.user_name})\n`;
3280
- response += ` - Email: ${email}\n`;
3281
- if (title) response += ` - Title: ${title}\n`;
3282
- response += ` - Active: ${active}\n`;
3283
- response += ` - Sys ID: ${sysId}\n\n`;
3284
- });
3285
-
3286
- if (members.result.length === 100) {
3287
- response += `\n⚠️ **Note:** Results limited to 100 members. The group may have additional members.`;
3288
- }
3289
-
3290
- return {
3291
- content: [{
3292
- type: 'text',
3293
- text: response
3294
- }]
3295
- };
3296
-
3297
- } catch (error) {
3298
- return {
3299
- content: [{
3300
- type: 'text',
3301
- text: `❌ **Failed to list group members**\n\nError: ${error instanceof Error ? error.message : String(error)}`
3302
- }]
3303
- };
3304
- }
3305
- }
3306
-
3307
- // Helper methods
3308
- private async findUserBySysIdOrUsername(identifier: string) {
3309
- // First try as sys_id
3310
- if (identifier.match(/^[a-f0-9]{32}$/)) {
3311
- const result = await this.client.get(`/api/now/table/sys_user/${identifier}`);
3312
- if (result.result) return result.result;
3313
- }
3314
-
3315
- // Then try as username
3316
- const result = await this.client.get('/api/now/table/sys_user', {
3317
- sysparm_query: `user_name=${identifier}`,
3318
- sysparm_limit: 1
3319
- });
3320
-
3321
- return result.result?.[0] || null;
3322
- }
3323
-
3324
- private async findGroupBySysIdOrName(identifier: string) {
3325
- // First try as sys_id
3326
- if (identifier.match(/^[a-f0-9]{32}$/)) {
3327
- const result = await this.client.get(`/api/now/table/sys_user_group/${identifier}`);
3328
- if (result.result) return result.result;
3329
- }
3330
-
3331
- // Then try as name
3332
- const result = await this.client.get('/api/now/table/sys_user_group', {
3333
- sysparm_query: `name=${identifier}`,
3334
- sysparm_limit: 1
3335
- });
3336
-
3337
- return result.result?.[0] || null;
3338
- }
3339
-
3340
- private async findDepartment(identifier: string) {
3341
- // First try as sys_id
3342
- if (identifier.match(/^[a-f0-9]{32}$/)) {
3343
- const result = await this.client.get(`/api/now/table/cmn_department/${identifier}`);
3344
- if (result.result) return result.result;
3345
- }
3346
-
3347
- // Then try as name
3348
- const result = await this.client.get('/api/now/table/cmn_department', {
3349
- sysparm_query: `name=${identifier}`,
3350
- sysparm_limit: 1
3351
- });
3352
-
3353
- return result.result?.[0] || null;
3354
- }
3355
-
3356
- private async findLocation(identifier: string) {
3357
- // First try as sys_id
3358
- if (identifier.match(/^[a-f0-9]{32}$/)) {
3359
- const result = await this.client.get(`/api/now/table/cmn_location/${identifier}`);
3360
- if (result.result) return result.result;
3361
- }
3362
-
3363
- // Then try as name
3364
- const result = await this.client.get('/api/now/table/cmn_location', {
3365
- sysparm_query: `name=${identifier}`,
3366
- sysparm_limit: 1
3367
- });
3368
-
3369
- return result.result?.[0] || null;
3370
- }
3371
-
3372
- async run() {
3373
- const transport = new StdioServerTransport();
3374
- await this.server.connect(transport);
3375
- logger.info('ServiceNow Operations MCP Server started');
3376
- }
3377
- }
3378
-
3379
- // Start the server
3380
- const server = new ServiceNowOperationsMCP();
3381
- server.run().catch((error) => {
3382
- logger.error('Failed to start ServiceNow Operations MCP server:', error);
3383
- process.exit(1);
3384
- });