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.
- package/.claude/settings.local.json +70 -0
- package/CLAUDE.md +777 -0
- package/LICENSE +21 -0
- package/README.md +562 -0
- package/assets/logo.svg +385 -0
- package/config/servicenow-instances.json.example +28 -0
- package/docs/403_TROUBLESHOOTING.md +329 -0
- package/docs/API_REFERENCE.md +1142 -0
- package/docs/APPLICATION_SCOPE_VALIDATION.md +681 -0
- package/docs/CLAUDE_DESKTOP_SETUP.md +373 -0
- package/docs/CONVENIENCE_TOOLS.md +601 -0
- package/docs/CONVENIENCE_TOOLS_SUMMARY.md +371 -0
- package/docs/FLOW_DESIGNER_GUIDE.md +1021 -0
- package/docs/IMPLEMENTATION_COMPLETE.md +165 -0
- package/docs/INSTANCE_SWITCHING_GUIDE.md +219 -0
- package/docs/MULTI_INSTANCE_CONFIGURATION.md +185 -0
- package/docs/NATURAL_LANGUAGE_SEARCH_IMPLEMENTATION.md +221 -0
- package/docs/PUPPETEER_INTEGRATION_PROPOSAL.md +1322 -0
- package/docs/QUICK_REFERENCE.md +395 -0
- package/docs/README.md +75 -0
- package/docs/RESOURCES_ARCHITECTURE.md +392 -0
- package/docs/RESOURCES_IMPLEMENTATION.md +276 -0
- package/docs/RESOURCES_SUMMARY.md +104 -0
- package/docs/SETUP_GUIDE.md +104 -0
- package/docs/UI_OPERATIONS_ARCHITECTURE.md +1219 -0
- package/docs/UI_OPERATIONS_DECISION_MATRIX.md +542 -0
- package/docs/UI_OPERATIONS_SUMMARY.md +507 -0
- package/docs/UPDATE_SET_VALIDATION.md +598 -0
- package/docs/UPDATE_SET_VALIDATION_SUMMARY.md +209 -0
- package/docs/VALIDATION_SUMMARY.md +479 -0
- package/jest.config.js +24 -0
- package/package.json +61 -0
- package/scripts/background_script_2025-09-29T20-19-35-101Z.js +23 -0
- package/scripts/link_ui_policy_actions_2025-09-29T20-17-15-218Z.js +90 -0
- package/scripts/set_update_set_Integration_Governance_Framework_2025-09-29T19-47-06-790Z.js +30 -0
- package/scripts/set_update_set_Integration_Governance_Framework_2025-09-29T19-59-33-152Z.js +30 -0
- package/scripts/set_update_set_current_2025-09-29T20-16-59-675Z.js +24 -0
- package/scripts/test_sys_dictionary_403.js +85 -0
- package/setup/setup-report.json +5313 -0
- package/src/config/comprehensive-table-definitions.json +2575 -0
- package/src/config/instance-config.json +4693 -0
- package/src/config/prompts.md +59 -0
- package/src/config/table-definitions.json +4681 -0
- package/src/config-manager.js +146 -0
- package/src/mcp-server-consolidated.js +2894 -0
- package/src/natural-language.js +472 -0
- package/src/resources.js +326 -0
- package/src/script-sync.js +428 -0
- package/src/server.js +125 -0
- package/src/servicenow-client.js +1625 -0
- package/src/stdio-server.js +52 -0
- package/start-mcp.sh +7 -0
package/src/resources.js
ADDED
|
@@ -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
|
+
}
|