servicenow-mcp-server 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.claude/settings.local.json +70 -0
  2. package/CLAUDE.md +777 -0
  3. package/LICENSE +21 -0
  4. package/README.md +562 -0
  5. package/assets/logo.svg +385 -0
  6. package/config/servicenow-instances.json.example +28 -0
  7. package/docs/403_TROUBLESHOOTING.md +329 -0
  8. package/docs/API_REFERENCE.md +1142 -0
  9. package/docs/APPLICATION_SCOPE_VALIDATION.md +681 -0
  10. package/docs/CLAUDE_DESKTOP_SETUP.md +373 -0
  11. package/docs/CONVENIENCE_TOOLS.md +601 -0
  12. package/docs/CONVENIENCE_TOOLS_SUMMARY.md +371 -0
  13. package/docs/FLOW_DESIGNER_GUIDE.md +1021 -0
  14. package/docs/IMPLEMENTATION_COMPLETE.md +165 -0
  15. package/docs/INSTANCE_SWITCHING_GUIDE.md +219 -0
  16. package/docs/MULTI_INSTANCE_CONFIGURATION.md +185 -0
  17. package/docs/NATURAL_LANGUAGE_SEARCH_IMPLEMENTATION.md +221 -0
  18. package/docs/PUPPETEER_INTEGRATION_PROPOSAL.md +1322 -0
  19. package/docs/QUICK_REFERENCE.md +395 -0
  20. package/docs/README.md +75 -0
  21. package/docs/RESOURCES_ARCHITECTURE.md +392 -0
  22. package/docs/RESOURCES_IMPLEMENTATION.md +276 -0
  23. package/docs/RESOURCES_SUMMARY.md +104 -0
  24. package/docs/SETUP_GUIDE.md +104 -0
  25. package/docs/UI_OPERATIONS_ARCHITECTURE.md +1219 -0
  26. package/docs/UI_OPERATIONS_DECISION_MATRIX.md +542 -0
  27. package/docs/UI_OPERATIONS_SUMMARY.md +507 -0
  28. package/docs/UPDATE_SET_VALIDATION.md +598 -0
  29. package/docs/UPDATE_SET_VALIDATION_SUMMARY.md +209 -0
  30. package/docs/VALIDATION_SUMMARY.md +479 -0
  31. package/jest.config.js +24 -0
  32. package/package.json +61 -0
  33. package/scripts/background_script_2025-09-29T20-19-35-101Z.js +23 -0
  34. package/scripts/link_ui_policy_actions_2025-09-29T20-17-15-218Z.js +90 -0
  35. package/scripts/set_update_set_Integration_Governance_Framework_2025-09-29T19-47-06-790Z.js +30 -0
  36. package/scripts/set_update_set_Integration_Governance_Framework_2025-09-29T19-59-33-152Z.js +30 -0
  37. package/scripts/set_update_set_current_2025-09-29T20-16-59-675Z.js +24 -0
  38. package/scripts/test_sys_dictionary_403.js +85 -0
  39. package/setup/setup-report.json +5313 -0
  40. package/src/config/comprehensive-table-definitions.json +2575 -0
  41. package/src/config/instance-config.json +4693 -0
  42. package/src/config/prompts.md +59 -0
  43. package/src/config/table-definitions.json +4681 -0
  44. package/src/config-manager.js +146 -0
  45. package/src/mcp-server-consolidated.js +2894 -0
  46. package/src/natural-language.js +472 -0
  47. package/src/resources.js +326 -0
  48. package/src/script-sync.js +428 -0
  49. package/src/server.js +125 -0
  50. package/src/servicenow-client.js +1625 -0
  51. package/src/stdio-server.js +52 -0
  52. package/start-mcp.sh +7 -0
@@ -0,0 +1,326 @@
1
+ /**
2
+ * ServiceNow MCP Server - MCP Resources Implementation
3
+ *
4
+ * Copyright (c) 2025 Happy Technologies LLC
5
+ * Licensed under the MIT License - see LICENSE file for details
6
+ *
7
+ * Provides read-only, cacheable access to ServiceNow data
8
+ */
9
+
10
+ export function createResourceHandlers(serviceNowClient, configManager, tableMetadata) {
11
+ /**
12
+ * List all available resources
13
+ */
14
+ const listResources = async () => {
15
+ const currentInstance = serviceNowClient.getCurrentInstance();
16
+ const instances = configManager.listInstances();
17
+
18
+ const resources = [
19
+ // Instance management
20
+ {
21
+ uri: 'servicenow://instances',
22
+ mimeType: 'application/json',
23
+ name: 'ServiceNow Instances',
24
+ description: 'List of all configured ServiceNow instances'
25
+ },
26
+ {
27
+ uri: `servicenow://${currentInstance.name}/info`,
28
+ mimeType: 'application/json',
29
+ name: `Current Instance Info (${currentInstance.name})`,
30
+ description: 'Information about the currently connected ServiceNow instance'
31
+ },
32
+
33
+ // Table metadata
34
+ {
35
+ uri: 'servicenow://tables',
36
+ mimeType: 'application/json',
37
+ name: 'Available Tables',
38
+ description: 'Complete list of available ServiceNow tables with metadata'
39
+ },
40
+
41
+ // Data resources (using current instance)
42
+ {
43
+ uri: `servicenow://${currentInstance.name}/incidents`,
44
+ mimeType: 'application/json',
45
+ name: `Active Incidents (${currentInstance.name})`,
46
+ description: 'List of active incidents from the current instance'
47
+ },
48
+ {
49
+ uri: `servicenow://${currentInstance.name}/users`,
50
+ mimeType: 'application/json',
51
+ name: `Users (${currentInstance.name})`,
52
+ description: 'List of users from the current instance'
53
+ },
54
+ {
55
+ uri: `servicenow://${currentInstance.name}/update-sets`,
56
+ mimeType: 'application/json',
57
+ name: `Update Sets (${currentInstance.name})`,
58
+ description: 'List of update sets from the current instance'
59
+ },
60
+ {
61
+ uri: `servicenow://${currentInstance.name}/groups`,
62
+ mimeType: 'application/json',
63
+ name: `User Groups (${currentInstance.name})`,
64
+ description: 'List of user groups from the current instance'
65
+ },
66
+ {
67
+ uri: `servicenow://${currentInstance.name}/change-requests`,
68
+ mimeType: 'application/json',
69
+ name: `Change Requests (${currentInstance.name})`,
70
+ description: 'List of change requests from the current instance'
71
+ }
72
+ ];
73
+
74
+ // Add per-instance resources for all configured instances
75
+ instances.forEach(instance => {
76
+ if (instance.name !== currentInstance.name) {
77
+ resources.push(
78
+ {
79
+ uri: `servicenow://${instance.name}/incidents`,
80
+ mimeType: 'application/json',
81
+ name: `Active Incidents (${instance.name})`,
82
+ description: `List of active incidents from ${instance.name}`
83
+ },
84
+ {
85
+ uri: `servicenow://${instance.name}/users`,
86
+ mimeType: 'application/json',
87
+ name: `Users (${instance.name})`,
88
+ description: `List of users from ${instance.name}`
89
+ },
90
+ {
91
+ uri: `servicenow://${instance.name}/update-sets`,
92
+ mimeType: 'application/json',
93
+ name: `Update Sets (${instance.name})`,
94
+ description: `List of update sets from ${instance.name}`
95
+ }
96
+ );
97
+ }
98
+ });
99
+
100
+ console.error(`📚 Listing ${resources.length} resources`);
101
+ return { resources };
102
+ };
103
+
104
+ /**
105
+ * Read a specific resource
106
+ */
107
+ const readResource = async (uri) => {
108
+ console.error(`📖 Reading resource: ${uri}`);
109
+
110
+ // Parse URI: servicenow://[instance]/[resource]/[id]
111
+ const uriPattern = /^servicenow:\/\/([^\/]+)(?:\/(.+))?$/;
112
+ const match = uri.match(uriPattern);
113
+
114
+ if (!match) {
115
+ throw new Error(`Invalid resource URI format: ${uri}. Expected: servicenow://[instance]/[resource]`);
116
+ }
117
+
118
+ const [, instanceOrResource, resourcePath] = match;
119
+
120
+ // Helper function to format response with metadata
121
+ const formatResource = (data, description = '') => {
122
+ const timestamp = new Date().toISOString();
123
+ const formattedData = {
124
+ metadata: {
125
+ timestamp,
126
+ instance: serviceNowClient.getCurrentInstance().name,
127
+ description,
128
+ record_count: Array.isArray(data) ? data.length : (data ? 1 : 0)
129
+ },
130
+ data
131
+ };
132
+
133
+ return {
134
+ contents: [{
135
+ uri,
136
+ mimeType: 'application/json',
137
+ text: JSON.stringify(formattedData, null, 2)
138
+ }]
139
+ };
140
+ };
141
+
142
+ // Resource: servicenow://instances
143
+ if (instanceOrResource === 'instances' && !resourcePath) {
144
+ const instances = configManager.listInstances();
145
+ return formatResource(instances, 'List of all configured ServiceNow instances');
146
+ }
147
+
148
+ // Resource: servicenow://tables
149
+ if (instanceOrResource === 'tables' && !resourcePath) {
150
+ const tableList = Object.entries(tableMetadata).map(([tableName, meta]) => ({
151
+ table_name: tableName,
152
+ label: meta.label,
153
+ description: meta.description,
154
+ key_field: meta.key_field,
155
+ operations: meta.operations,
156
+ package: meta.package
157
+ }));
158
+ return formatResource(tableList, 'Complete list of available ServiceNow tables with metadata');
159
+ }
160
+
161
+ // Instance-specific resources
162
+ const instanceName = instanceOrResource;
163
+ const resource = resourcePath;
164
+
165
+ // Save current instance to restore later
166
+ const originalInstance = serviceNowClient.getCurrentInstance();
167
+
168
+ // Switch to requested instance if different
169
+ if (instanceName !== originalInstance.name) {
170
+ const instance = configManager.getInstance(instanceName);
171
+ serviceNowClient.setInstance(instance.url, instance.username, instance.password, instance.name);
172
+ }
173
+
174
+ try {
175
+ // Resource: servicenow://[instance]/info
176
+ if (resource === 'info') {
177
+ const currentInstance = serviceNowClient.getCurrentInstance();
178
+ const instanceConfig = configManager.getInstance(instanceName);
179
+ const data = {
180
+ instance: {
181
+ name: currentInstance.name,
182
+ url: currentInstance.url,
183
+ description: instanceConfig.description || '',
184
+ default: instanceConfig.default || false
185
+ },
186
+ server_info: {
187
+ name: 'ServiceNow MCP Server',
188
+ version: '2.0.0',
189
+ description: 'Multi-instance ServiceNow MCP server with resources'
190
+ },
191
+ capabilities: {
192
+ total_tables: Object.keys(tableMetadata).length,
193
+ total_tools: 34,
194
+ operations: ['create', 'read', 'update', 'query', 'batch', 'workflow'],
195
+ features: ['multi_instance', 'resources', 'background_scripts', 'update_sets']
196
+ }
197
+ };
198
+ return formatResource(data, `Information about ${instanceName} instance`);
199
+ }
200
+
201
+ // Resource: servicenow://[instance]/incidents
202
+ if (resource === 'incidents') {
203
+ const incidents = await serviceNowClient.getRecords('incident', {
204
+ sysparm_query: 'active=true',
205
+ sysparm_limit: 25,
206
+ sysparm_fields: 'number,short_description,state,priority,assigned_to,sys_created_on,sys_updated_on'
207
+ });
208
+ return formatResource(incidents, `Active incidents from ${instanceName}`);
209
+ }
210
+
211
+ // Resource: servicenow://[instance]/incidents/[number]
212
+ if (resource && resource.startsWith('incidents/')) {
213
+ const incidentNumber = resource.split('/')[1];
214
+ const incidents = await serviceNowClient.getRecords('incident', {
215
+ sysparm_query: `number=${incidentNumber}`,
216
+ sysparm_limit: 1
217
+ });
218
+ if (incidents.length === 0) {
219
+ throw new Error(`Incident ${incidentNumber} not found in ${instanceName}`);
220
+ }
221
+ return formatResource(incidents[0], `Incident ${incidentNumber} from ${instanceName}`);
222
+ }
223
+
224
+ // Resource: servicenow://[instance]/users
225
+ if (resource === 'users') {
226
+ const users = await serviceNowClient.getRecords('sys_user', {
227
+ sysparm_query: 'active=true',
228
+ sysparm_limit: 50,
229
+ sysparm_fields: 'user_name,name,email,title,department,sys_created_on'
230
+ });
231
+ return formatResource(users, `Active users from ${instanceName}`);
232
+ }
233
+
234
+ // Resource: servicenow://[instance]/update-sets
235
+ if (resource === 'update-sets') {
236
+ const updateSets = await serviceNowClient.getRecords('sys_update_set', {
237
+ sysparm_query: 'state=in progress',
238
+ sysparm_limit: 25,
239
+ sysparm_fields: 'name,description,state,application,sys_created_on,sys_updated_on',
240
+ sysparm_order_by: '-sys_updated_on'
241
+ });
242
+ return formatResource(updateSets, `Update sets in progress from ${instanceName}`);
243
+ }
244
+
245
+ // Resource: servicenow://[instance]/update-sets/[sys_id]
246
+ if (resource && resource.startsWith('update-sets/')) {
247
+ const updateSetId = resource.split('/')[1];
248
+
249
+ // Get update set details
250
+ const updateSet = await serviceNowClient.getRecord('sys_update_set', updateSetId);
251
+
252
+ // Get update set contents
253
+ const updates = await serviceNowClient.getRecords('sys_update_xml', {
254
+ sysparm_query: `update_set=${updateSetId}`,
255
+ sysparm_fields: 'type,name,target_name,sys_created_on',
256
+ sysparm_limit: 1000
257
+ });
258
+
259
+ // Group by type
260
+ const typeGroups = {};
261
+ updates.forEach(update => {
262
+ const type = update.type || 'unknown';
263
+ if (!typeGroups[type]) {
264
+ typeGroups[type] = { count: 0, items: [] };
265
+ }
266
+ typeGroups[type].count++;
267
+ typeGroups[type].items.push({
268
+ name: update.name || update.target_name,
269
+ created: update.sys_created_on
270
+ });
271
+ });
272
+
273
+ const data = {
274
+ update_set: {
275
+ sys_id: updateSet.sys_id,
276
+ name: updateSet.name,
277
+ description: updateSet.description,
278
+ state: updateSet.state,
279
+ application: updateSet.application
280
+ },
281
+ total_records: updates.length,
282
+ components: Object.entries(typeGroups).map(([type, info]) => ({
283
+ type,
284
+ count: info.count,
285
+ items: info.items.slice(0, 10) // First 10 items per type
286
+ }))
287
+ };
288
+
289
+ return formatResource(data, `Update set ${updateSet.name} contents from ${instanceName}`);
290
+ }
291
+
292
+ // Resource: servicenow://[instance]/groups
293
+ if (resource === 'groups') {
294
+ const groups = await serviceNowClient.getRecords('sys_user_group', {
295
+ sysparm_query: 'active=true',
296
+ sysparm_limit: 50,
297
+ sysparm_fields: 'name,description,type,manager,sys_created_on'
298
+ });
299
+ return formatResource(groups, `Active user groups from ${instanceName}`);
300
+ }
301
+
302
+ // Resource: servicenow://[instance]/change-requests
303
+ if (resource === 'change-requests') {
304
+ const changes = await serviceNowClient.getRecords('change_request', {
305
+ sysparm_query: 'active=true',
306
+ sysparm_limit: 25,
307
+ sysparm_fields: 'number,short_description,state,priority,risk,start_date,end_date,sys_created_on'
308
+ });
309
+ return formatResource(changes, `Active change requests from ${instanceName}`);
310
+ }
311
+
312
+ throw new Error(`Unknown resource path: ${resource}. Available resources: info, incidents, users, update-sets, groups, change-requests`);
313
+ } finally {
314
+ // Restore original instance if we switched
315
+ if (instanceName !== originalInstance.name) {
316
+ const original = configManager.getInstance(originalInstance.name);
317
+ serviceNowClient.setInstance(original.url, original.username, original.password, original.name);
318
+ }
319
+ }
320
+ };
321
+
322
+ return {
323
+ listResources,
324
+ readResource
325
+ };
326
+ }