snow-flow 1.3.28 → 1.3.30
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/README.md +70 -0
- package/dist/cli.js +22 -17
- package/dist/mcp/servicenow-flow-composer-enhanced.js +396 -0
- package/dist/mcp/servicenow-flow-composer-mcp.js +82 -17
- package/dist/utils/complete-flow-xml-generator.js +1285 -0
- package/dist/utils/deployment-metadata-handler.js +422 -0
- package/dist/utils/mcp-tool-registry.js +271 -0
- package/dist/utils/update-set-importer.js +369 -0
- package/dist/utils/update-set-xml-packager.js +339 -0
- package/package.json +1 -1
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Deployment Metadata Handler
|
|
5
|
+
*
|
|
6
|
+
* Solves Issue #3: Metadata Response Failures
|
|
7
|
+
* Ensures all deployments return proper sys_id and API endpoints
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.DeploymentMetadataHandler = void 0;
|
|
11
|
+
exports.getMetadataHandler = getMetadataHandler;
|
|
12
|
+
exports.ensureDeploymentMetadata = ensureDeploymentMetadata;
|
|
13
|
+
const servicenow_client_js_1 = require("./servicenow-client.js");
|
|
14
|
+
const snow_oauth_js_1 = require("./snow-oauth.js");
|
|
15
|
+
const logger_js_1 = require("./logger.js");
|
|
16
|
+
class DeploymentMetadataHandler {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.metadataCache = new Map();
|
|
19
|
+
this.client = new servicenow_client_js_1.ServiceNowClient();
|
|
20
|
+
this.oauth = new snow_oauth_js_1.ServiceNowOAuth();
|
|
21
|
+
this.logger = new logger_js_1.Logger('DeploymentMetadataHandler');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extract metadata from deployment response
|
|
25
|
+
*/
|
|
26
|
+
async extractMetadata(deploymentType, deploymentResponse, additionalInfo) {
|
|
27
|
+
try {
|
|
28
|
+
let metadata = null;
|
|
29
|
+
switch (deploymentType) {
|
|
30
|
+
case 'flow':
|
|
31
|
+
metadata = await this.extractFlowMetadata(deploymentResponse, additionalInfo);
|
|
32
|
+
break;
|
|
33
|
+
case 'widget':
|
|
34
|
+
metadata = await this.extractWidgetMetadata(deploymentResponse, additionalInfo);
|
|
35
|
+
break;
|
|
36
|
+
case 'script':
|
|
37
|
+
metadata = await this.extractScriptMetadata(deploymentResponse, additionalInfo);
|
|
38
|
+
break;
|
|
39
|
+
case 'business_rule':
|
|
40
|
+
metadata = await this.extractBusinessRuleMetadata(deploymentResponse, additionalInfo);
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
metadata = await this.extractGenericMetadata(deploymentType, deploymentResponse, additionalInfo);
|
|
44
|
+
}
|
|
45
|
+
if (!metadata) {
|
|
46
|
+
throw new Error('Failed to extract metadata from deployment response');
|
|
47
|
+
}
|
|
48
|
+
// Verify the deployment
|
|
49
|
+
const verification = await this.verifyDeployment(metadata);
|
|
50
|
+
metadata.verification_status = verification.exists && verification.accessible ? 'verified' : 'failed';
|
|
51
|
+
// Cache the metadata
|
|
52
|
+
this.metadataCache.set(metadata.sys_id, metadata);
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
metadata,
|
|
56
|
+
verification
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
this.logger.error('Metadata extraction failed:', error);
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: error instanceof Error ? error.message : String(error)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract flow metadata
|
|
69
|
+
*/
|
|
70
|
+
async extractFlowMetadata(response, additionalInfo) {
|
|
71
|
+
let sysId = null;
|
|
72
|
+
let name = '';
|
|
73
|
+
// Try multiple paths to find sys_id
|
|
74
|
+
sysId = response?.sys_id ||
|
|
75
|
+
response?.result?.sys_id ||
|
|
76
|
+
response?.flow?.sys_id ||
|
|
77
|
+
additionalInfo?.flowSysId ||
|
|
78
|
+
additionalInfo?.sys_id;
|
|
79
|
+
// If still no sys_id, try to find it from update set
|
|
80
|
+
if (!sysId && additionalInfo?.update_set_id) {
|
|
81
|
+
sysId = await this.findFlowFromUpdateSet(additionalInfo.update_set_id);
|
|
82
|
+
}
|
|
83
|
+
// If still no sys_id, search by name
|
|
84
|
+
if (!sysId && (response?.name || additionalInfo?.name)) {
|
|
85
|
+
name = response?.name || additionalInfo?.name;
|
|
86
|
+
sysId = await this.findFlowByName(name);
|
|
87
|
+
}
|
|
88
|
+
if (!sysId) {
|
|
89
|
+
this.logger.warn('Could not find flow sys_id in response');
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
// Get additional details
|
|
93
|
+
const flowDetails = await this.getFlowDetails(sysId);
|
|
94
|
+
const credentials = await this.oauth.getCredentials();
|
|
95
|
+
return {
|
|
96
|
+
sys_id: sysId,
|
|
97
|
+
name: flowDetails?.name || name || 'Unknown Flow',
|
|
98
|
+
type: 'flow',
|
|
99
|
+
table: 'sys_hub_flow',
|
|
100
|
+
api_endpoint: `https://${credentials?.instance}/api/now/table/sys_hub_flow/${sysId}`,
|
|
101
|
+
ui_url: `https://${credentials?.instance}/flow-designer/${sysId}`,
|
|
102
|
+
created_on: flowDetails?.sys_created_on,
|
|
103
|
+
created_by: flowDetails?.sys_created_by,
|
|
104
|
+
update_set_id: additionalInfo?.update_set_id
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Extract widget metadata
|
|
109
|
+
*/
|
|
110
|
+
async extractWidgetMetadata(response, additionalInfo) {
|
|
111
|
+
let sysId = null;
|
|
112
|
+
let name = '';
|
|
113
|
+
// Try multiple paths to find sys_id
|
|
114
|
+
sysId = response?.sys_id ||
|
|
115
|
+
response?.result?.sys_id ||
|
|
116
|
+
response?.widget?.sys_id ||
|
|
117
|
+
additionalInfo?.widgetSysId ||
|
|
118
|
+
additionalInfo?.sys_id;
|
|
119
|
+
// Search by name if needed
|
|
120
|
+
if (!sysId && (response?.name || additionalInfo?.name)) {
|
|
121
|
+
name = response?.name || additionalInfo?.name;
|
|
122
|
+
sysId = await this.findWidgetByName(name);
|
|
123
|
+
}
|
|
124
|
+
if (!sysId) {
|
|
125
|
+
this.logger.warn('Could not find widget sys_id in response');
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const credentials = await this.oauth.getCredentials();
|
|
129
|
+
return {
|
|
130
|
+
sys_id: sysId,
|
|
131
|
+
name: name || response?.title || 'Unknown Widget',
|
|
132
|
+
type: 'widget',
|
|
133
|
+
table: 'sp_widget',
|
|
134
|
+
api_endpoint: `https://${credentials?.instance}/api/now/table/sp_widget/${sysId}`,
|
|
135
|
+
ui_url: `https://${credentials?.instance}/sp_config?id=widget_editor&sys_id=${sysId}`,
|
|
136
|
+
update_set_id: additionalInfo?.update_set_id
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Extract script include metadata
|
|
141
|
+
*/
|
|
142
|
+
async extractScriptMetadata(response, additionalInfo) {
|
|
143
|
+
let sysId = null;
|
|
144
|
+
sysId = response?.sys_id ||
|
|
145
|
+
response?.result?.sys_id ||
|
|
146
|
+
additionalInfo?.sys_id;
|
|
147
|
+
if (!sysId && response?.name) {
|
|
148
|
+
sysId = await this.findScriptIncludeByName(response.name);
|
|
149
|
+
}
|
|
150
|
+
if (!sysId)
|
|
151
|
+
return null;
|
|
152
|
+
const credentials = await this.oauth.getCredentials();
|
|
153
|
+
return {
|
|
154
|
+
sys_id: sysId,
|
|
155
|
+
name: response?.name || 'Unknown Script',
|
|
156
|
+
type: 'script_include',
|
|
157
|
+
table: 'sys_script_include',
|
|
158
|
+
api_endpoint: `https://${credentials?.instance}/api/now/table/sys_script_include/${sysId}`,
|
|
159
|
+
ui_url: `https://${credentials?.instance}/sys_script_include.do?sys_id=${sysId}`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Extract business rule metadata
|
|
164
|
+
*/
|
|
165
|
+
async extractBusinessRuleMetadata(response, additionalInfo) {
|
|
166
|
+
let sysId = null;
|
|
167
|
+
sysId = response?.sys_id ||
|
|
168
|
+
response?.result?.sys_id ||
|
|
169
|
+
additionalInfo?.sys_id;
|
|
170
|
+
if (!sysId && response?.name) {
|
|
171
|
+
sysId = await this.findBusinessRuleByName(response.name, response?.table);
|
|
172
|
+
}
|
|
173
|
+
if (!sysId)
|
|
174
|
+
return null;
|
|
175
|
+
const credentials = await this.oauth.getCredentials();
|
|
176
|
+
return {
|
|
177
|
+
sys_id: sysId,
|
|
178
|
+
name: response?.name || 'Unknown Business Rule',
|
|
179
|
+
type: 'business_rule',
|
|
180
|
+
table: 'sys_script',
|
|
181
|
+
api_endpoint: `https://${credentials?.instance}/api/now/table/sys_script/${sysId}`,
|
|
182
|
+
ui_url: `https://${credentials?.instance}/sys_script.do?sys_id=${sysId}`
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Extract generic metadata
|
|
187
|
+
*/
|
|
188
|
+
async extractGenericMetadata(type, response, additionalInfo) {
|
|
189
|
+
const sysId = response?.sys_id || response?.result?.sys_id || additionalInfo?.sys_id;
|
|
190
|
+
if (!sysId)
|
|
191
|
+
return null;
|
|
192
|
+
const credentials = await this.oauth.getCredentials();
|
|
193
|
+
const table = additionalInfo?.table || this.getTableForType(type);
|
|
194
|
+
return {
|
|
195
|
+
sys_id: sysId,
|
|
196
|
+
name: response?.name || additionalInfo?.name || `Unknown ${type}`,
|
|
197
|
+
type,
|
|
198
|
+
table,
|
|
199
|
+
api_endpoint: `https://${credentials?.instance}/api/now/table/${table}/${sysId}`,
|
|
200
|
+
ui_url: `https://${credentials?.instance}/${table}.do?sys_id=${sysId}`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Find flow from update set
|
|
205
|
+
*/
|
|
206
|
+
async findFlowFromUpdateSet(updateSetId) {
|
|
207
|
+
try {
|
|
208
|
+
const response = await this.client.makeRequest({
|
|
209
|
+
method: 'GET',
|
|
210
|
+
url: '/api/now/table/sys_update_xml',
|
|
211
|
+
params: {
|
|
212
|
+
sysparm_query: `update_set=${updateSetId}^name^STARTSWITHsys_hub_flow_`,
|
|
213
|
+
sysparm_limit: 1,
|
|
214
|
+
sysparm_fields: 'name'
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
if (response.result && response.result.length > 0) {
|
|
218
|
+
return response.result[0].name.replace('sys_hub_flow_', '');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
this.logger.warn('Error finding flow from update set:', error);
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Find flow by name
|
|
228
|
+
*/
|
|
229
|
+
async findFlowByName(name) {
|
|
230
|
+
try {
|
|
231
|
+
const response = await this.client.makeRequest({
|
|
232
|
+
method: 'GET',
|
|
233
|
+
url: '/api/now/table/sys_hub_flow',
|
|
234
|
+
params: {
|
|
235
|
+
sysparm_query: `name=${name}^ORinternal_name=${name}`,
|
|
236
|
+
sysparm_limit: 1,
|
|
237
|
+
sysparm_fields: 'sys_id'
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
if (response.result && response.result.length > 0) {
|
|
241
|
+
return response.result[0].sys_id;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
this.logger.warn('Error finding flow by name:', error);
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Find widget by name
|
|
251
|
+
*/
|
|
252
|
+
async findWidgetByName(name) {
|
|
253
|
+
try {
|
|
254
|
+
const response = await this.client.makeRequest({
|
|
255
|
+
method: 'GET',
|
|
256
|
+
url: '/api/now/table/sp_widget',
|
|
257
|
+
params: {
|
|
258
|
+
sysparm_query: `name=${name}^ORid=${name}`,
|
|
259
|
+
sysparm_limit: 1,
|
|
260
|
+
sysparm_fields: 'sys_id'
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
if (response.result && response.result.length > 0) {
|
|
264
|
+
return response.result[0].sys_id;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
this.logger.warn('Error finding widget by name:', error);
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Find script include by name
|
|
274
|
+
*/
|
|
275
|
+
async findScriptIncludeByName(name) {
|
|
276
|
+
try {
|
|
277
|
+
const response = await this.client.makeRequest({
|
|
278
|
+
method: 'GET',
|
|
279
|
+
url: '/api/now/table/sys_script_include',
|
|
280
|
+
params: {
|
|
281
|
+
sysparm_query: `name=${name}^ORapi_name=${name}`,
|
|
282
|
+
sysparm_limit: 1,
|
|
283
|
+
sysparm_fields: 'sys_id'
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
if (response.result && response.result.length > 0) {
|
|
287
|
+
return response.result[0].sys_id;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
this.logger.warn('Error finding script include by name:', error);
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Find business rule by name
|
|
297
|
+
*/
|
|
298
|
+
async findBusinessRuleByName(name, table) {
|
|
299
|
+
try {
|
|
300
|
+
let query = `name=${name}`;
|
|
301
|
+
if (table) {
|
|
302
|
+
query += `^collection=${table}`;
|
|
303
|
+
}
|
|
304
|
+
const response = await this.client.makeRequest({
|
|
305
|
+
method: 'GET',
|
|
306
|
+
url: '/api/now/table/sys_script',
|
|
307
|
+
params: {
|
|
308
|
+
sysparm_query: query,
|
|
309
|
+
sysparm_limit: 1,
|
|
310
|
+
sysparm_fields: 'sys_id'
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
if (response.result && response.result.length > 0) {
|
|
314
|
+
return response.result[0].sys_id;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
this.logger.warn('Error finding business rule by name:', error);
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get flow details
|
|
324
|
+
*/
|
|
325
|
+
async getFlowDetails(sysId) {
|
|
326
|
+
try {
|
|
327
|
+
const response = await this.client.makeRequest({
|
|
328
|
+
method: 'GET',
|
|
329
|
+
url: `/api/now/table/sys_hub_flow/${sysId}`,
|
|
330
|
+
params: {
|
|
331
|
+
sysparm_fields: 'name,sys_created_on,sys_created_by,internal_name'
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
return response.result;
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
this.logger.warn('Error getting flow details:', error);
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Verify deployment exists and is accessible
|
|
343
|
+
*/
|
|
344
|
+
async verifyDeployment(metadata) {
|
|
345
|
+
const verification = {
|
|
346
|
+
exists: false,
|
|
347
|
+
accessible: false,
|
|
348
|
+
functional: false
|
|
349
|
+
};
|
|
350
|
+
try {
|
|
351
|
+
// Check if record exists
|
|
352
|
+
const response = await this.client.makeRequest({
|
|
353
|
+
method: 'GET',
|
|
354
|
+
url: `/api/now/table/${metadata.table}/${metadata.sys_id}`,
|
|
355
|
+
params: {
|
|
356
|
+
sysparm_fields: 'sys_id,name'
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
verification.exists = !!response.result;
|
|
360
|
+
verification.accessible = response.success;
|
|
361
|
+
// For flows, check if it's active
|
|
362
|
+
if (metadata.type === 'flow' && response.result) {
|
|
363
|
+
verification.functional = response.result.active === 'true' || response.result.active === true;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
verification.functional = verification.exists;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
this.logger.warn('Verification failed:', error);
|
|
371
|
+
}
|
|
372
|
+
return verification;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get table name for deployment type
|
|
376
|
+
*/
|
|
377
|
+
getTableForType(type) {
|
|
378
|
+
const tableMap = {
|
|
379
|
+
'flow': 'sys_hub_flow',
|
|
380
|
+
'widget': 'sp_widget',
|
|
381
|
+
'script_include': 'sys_script_include',
|
|
382
|
+
'business_rule': 'sys_script',
|
|
383
|
+
'ui_script': 'sys_ui_script',
|
|
384
|
+
'client_script': 'sys_script_client',
|
|
385
|
+
'table': 'sys_db_object',
|
|
386
|
+
'application': 'sys_app'
|
|
387
|
+
};
|
|
388
|
+
return tableMap[type] || 'sys_metadata';
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Get cached metadata
|
|
392
|
+
*/
|
|
393
|
+
getCachedMetadata(sysId) {
|
|
394
|
+
return this.metadataCache.get(sysId) || null;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Clear metadata cache
|
|
398
|
+
*/
|
|
399
|
+
clearCache() {
|
|
400
|
+
this.metadataCache.clear();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
exports.DeploymentMetadataHandler = DeploymentMetadataHandler;
|
|
404
|
+
// Singleton instance
|
|
405
|
+
let handlerInstance = null;
|
|
406
|
+
/**
|
|
407
|
+
* Get or create handler instance
|
|
408
|
+
*/
|
|
409
|
+
function getMetadataHandler() {
|
|
410
|
+
if (!handlerInstance) {
|
|
411
|
+
handlerInstance = new DeploymentMetadataHandler();
|
|
412
|
+
}
|
|
413
|
+
return handlerInstance;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Helper to ensure deployment returns metadata
|
|
417
|
+
*/
|
|
418
|
+
async function ensureDeploymentMetadata(deploymentType, deploymentResponse, additionalInfo) {
|
|
419
|
+
const handler = getMetadataHandler();
|
|
420
|
+
return handler.extractMetadata(deploymentType, deploymentResponse, additionalInfo);
|
|
421
|
+
}
|
|
422
|
+
exports.default = DeploymentMetadataHandler;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* MCP Tool Registry Mapper
|
|
5
|
+
*
|
|
6
|
+
* Solves Issue #2: Tool Registry Mapping Failures
|
|
7
|
+
* Provides robust tool name resolution between different MCP providers
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.MCPToolRegistry = void 0;
|
|
11
|
+
exports.getToolRegistry = getToolRegistry;
|
|
12
|
+
exports.resolveToolName = resolveToolName;
|
|
13
|
+
exports.isValidTool = isValidTool;
|
|
14
|
+
exports.suggestTools = suggestTools;
|
|
15
|
+
const logger_js_1 = require("./logger.js");
|
|
16
|
+
class MCPToolRegistry {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.toolMappings = new Map();
|
|
19
|
+
this.logger = new logger_js_1.Logger('MCPToolRegistry');
|
|
20
|
+
this.initializeDefaultMappings();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Initialize default tool mappings
|
|
24
|
+
*/
|
|
25
|
+
initializeDefaultMappings() {
|
|
26
|
+
// ServiceNow table schema discovery - common confusion point
|
|
27
|
+
this.registerTool({
|
|
28
|
+
canonicalName: 'table_schema_discovery',
|
|
29
|
+
aliases: [
|
|
30
|
+
'mcp__servicenow-operations__snow_table_schema_discovery',
|
|
31
|
+
'snow_table_schema_discovery',
|
|
32
|
+
'table_schema',
|
|
33
|
+
'schema_discovery'
|
|
34
|
+
],
|
|
35
|
+
provider: 'servicenow-platform-development',
|
|
36
|
+
actualTool: 'mcp__servicenow-platform-development__snow_table_schema_discovery',
|
|
37
|
+
description: 'Comprehensive table schema discovery'
|
|
38
|
+
});
|
|
39
|
+
// Flow deployment tools
|
|
40
|
+
this.registerTool({
|
|
41
|
+
canonicalName: 'deploy_flow',
|
|
42
|
+
aliases: [
|
|
43
|
+
'mcp__servicenow-deployment__snow_deploy_flow',
|
|
44
|
+
'snow_deploy_flow',
|
|
45
|
+
'deploy_flow',
|
|
46
|
+
'flow_deploy'
|
|
47
|
+
],
|
|
48
|
+
provider: 'servicenow-deployment',
|
|
49
|
+
actualTool: 'mcp__servicenow-deployment__snow_deploy',
|
|
50
|
+
description: 'Deploy flows to ServiceNow'
|
|
51
|
+
});
|
|
52
|
+
// Widget deployment
|
|
53
|
+
this.registerTool({
|
|
54
|
+
canonicalName: 'deploy_widget',
|
|
55
|
+
aliases: [
|
|
56
|
+
'mcp__servicenow-deployment__snow_deploy_widget',
|
|
57
|
+
'snow_deploy_widget',
|
|
58
|
+
'deploy_widget',
|
|
59
|
+
'widget_deploy'
|
|
60
|
+
],
|
|
61
|
+
provider: 'servicenow-deployment',
|
|
62
|
+
actualTool: 'mcp__servicenow-deployment__snow_deploy',
|
|
63
|
+
description: 'Deploy widgets to ServiceNow'
|
|
64
|
+
});
|
|
65
|
+
// Flow creation
|
|
66
|
+
this.registerTool({
|
|
67
|
+
canonicalName: 'create_flow',
|
|
68
|
+
aliases: [
|
|
69
|
+
'mcp__servicenow-flow-composer__snow_create_flow',
|
|
70
|
+
'snow_create_flow',
|
|
71
|
+
'create_flow',
|
|
72
|
+
'flow_create',
|
|
73
|
+
'snow_xml_flow_from_instruction'
|
|
74
|
+
],
|
|
75
|
+
provider: 'servicenow-flow-composer',
|
|
76
|
+
actualTool: 'mcp__servicenow-flow-composer__snow_create_flow',
|
|
77
|
+
description: 'Create flows from natural language'
|
|
78
|
+
});
|
|
79
|
+
// Update Set management
|
|
80
|
+
this.registerTool({
|
|
81
|
+
canonicalName: 'update_set_create',
|
|
82
|
+
aliases: [
|
|
83
|
+
'mcp__servicenow-update-set__snow_update_set_create',
|
|
84
|
+
'snow_update_set_create',
|
|
85
|
+
'create_update_set',
|
|
86
|
+
'update_set_new'
|
|
87
|
+
],
|
|
88
|
+
provider: 'servicenow-update-set',
|
|
89
|
+
actualTool: 'mcp__servicenow-update-set__snow_update_set_create',
|
|
90
|
+
description: 'Create Update Sets'
|
|
91
|
+
});
|
|
92
|
+
// Authentication diagnostics
|
|
93
|
+
this.registerTool({
|
|
94
|
+
canonicalName: 'auth_diagnostics',
|
|
95
|
+
aliases: [
|
|
96
|
+
'mcp__servicenow-deployment__snow_auth_diagnostics',
|
|
97
|
+
'snow_auth_diagnostics',
|
|
98
|
+
'auth_check',
|
|
99
|
+
'permission_check'
|
|
100
|
+
],
|
|
101
|
+
provider: 'servicenow-deployment',
|
|
102
|
+
actualTool: 'mcp__servicenow-deployment__snow_auth_diagnostics',
|
|
103
|
+
description: 'Authentication and permission diagnostics'
|
|
104
|
+
});
|
|
105
|
+
// Catalog item management
|
|
106
|
+
this.registerTool({
|
|
107
|
+
canonicalName: 'catalog_item_manager',
|
|
108
|
+
aliases: [
|
|
109
|
+
'mcp__servicenow-operations__snow_catalog_item_manager',
|
|
110
|
+
'snow_catalog_item_manager',
|
|
111
|
+
'catalog_manager',
|
|
112
|
+
'manage_catalog'
|
|
113
|
+
],
|
|
114
|
+
provider: 'servicenow-operations',
|
|
115
|
+
actualTool: 'mcp__servicenow-operations__snow_catalog_item_manager',
|
|
116
|
+
description: 'Manage service catalog items'
|
|
117
|
+
});
|
|
118
|
+
// Platform development tools
|
|
119
|
+
this.registerTool({
|
|
120
|
+
canonicalName: 'discover_table_fields',
|
|
121
|
+
aliases: [
|
|
122
|
+
'mcp__servicenow-platform-development__snow_discover_table_fields',
|
|
123
|
+
'snow_discover_table_fields',
|
|
124
|
+
'discover_fields',
|
|
125
|
+
'table_fields'
|
|
126
|
+
],
|
|
127
|
+
provider: 'servicenow-platform-development',
|
|
128
|
+
actualTool: 'mcp__servicenow-platform-development__snow_discover_table_fields',
|
|
129
|
+
description: 'Discover table fields'
|
|
130
|
+
});
|
|
131
|
+
// Add more mappings as needed...
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Register a tool mapping
|
|
135
|
+
*/
|
|
136
|
+
registerTool(mapping) {
|
|
137
|
+
// Register by canonical name
|
|
138
|
+
this.toolMappings.set(mapping.canonicalName, mapping);
|
|
139
|
+
// Register by actual tool name
|
|
140
|
+
this.toolMappings.set(mapping.actualTool, mapping);
|
|
141
|
+
// Register by all aliases
|
|
142
|
+
mapping.aliases.forEach(alias => {
|
|
143
|
+
this.toolMappings.set(alias.toLowerCase(), mapping);
|
|
144
|
+
});
|
|
145
|
+
this.logger.debug(`Registered tool: ${mapping.canonicalName} with ${mapping.aliases.length} aliases`);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Resolve a tool name to its actual MCP tool
|
|
149
|
+
*/
|
|
150
|
+
resolveTool(toolName) {
|
|
151
|
+
// Try exact match first
|
|
152
|
+
let mapping = this.toolMappings.get(toolName);
|
|
153
|
+
// Try lowercase match
|
|
154
|
+
if (!mapping) {
|
|
155
|
+
mapping = this.toolMappings.get(toolName.toLowerCase());
|
|
156
|
+
}
|
|
157
|
+
// Try partial matches
|
|
158
|
+
if (!mapping) {
|
|
159
|
+
const searchKey = toolName.toLowerCase();
|
|
160
|
+
for (const [key, value] of this.toolMappings.entries()) {
|
|
161
|
+
if (key.includes(searchKey) || searchKey.includes(key)) {
|
|
162
|
+
mapping = value;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (mapping) {
|
|
168
|
+
this.logger.debug(`Resolved '${toolName}' to '${mapping.actualTool}'`);
|
|
169
|
+
return mapping.actualTool;
|
|
170
|
+
}
|
|
171
|
+
this.logger.warn(`Could not resolve tool: ${toolName}`);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get all tools for a provider
|
|
176
|
+
*/
|
|
177
|
+
getProviderTools(provider) {
|
|
178
|
+
const tools = [];
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
for (const mapping of this.toolMappings.values()) {
|
|
181
|
+
if (mapping.provider === provider && !seen.has(mapping.canonicalName)) {
|
|
182
|
+
tools.push(mapping);
|
|
183
|
+
seen.add(mapping.canonicalName);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return tools;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Search tools by keyword
|
|
190
|
+
*/
|
|
191
|
+
searchTools(keyword) {
|
|
192
|
+
const results = [];
|
|
193
|
+
const seen = new Set();
|
|
194
|
+
const searchKey = keyword.toLowerCase();
|
|
195
|
+
for (const mapping of this.toolMappings.values()) {
|
|
196
|
+
if (seen.has(mapping.canonicalName))
|
|
197
|
+
continue;
|
|
198
|
+
if (mapping.canonicalName.includes(searchKey) ||
|
|
199
|
+
mapping.description?.toLowerCase().includes(searchKey) ||
|
|
200
|
+
mapping.aliases.some(alias => alias.toLowerCase().includes(searchKey))) {
|
|
201
|
+
results.push(mapping);
|
|
202
|
+
seen.add(mapping.canonicalName);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return results;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if a tool exists
|
|
209
|
+
*/
|
|
210
|
+
toolExists(toolName) {
|
|
211
|
+
return this.resolveTool(toolName) !== null;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get tool info
|
|
215
|
+
*/
|
|
216
|
+
getToolInfo(toolName) {
|
|
217
|
+
const resolved = this.resolveTool(toolName);
|
|
218
|
+
if (!resolved)
|
|
219
|
+
return null;
|
|
220
|
+
return this.toolMappings.get(resolved) || null;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Export all mappings for documentation
|
|
224
|
+
*/
|
|
225
|
+
exportMappings() {
|
|
226
|
+
const exports = {};
|
|
227
|
+
const seen = new Set();
|
|
228
|
+
for (const mapping of this.toolMappings.values()) {
|
|
229
|
+
if (!seen.has(mapping.canonicalName)) {
|
|
230
|
+
exports[mapping.canonicalName] = mapping;
|
|
231
|
+
seen.add(mapping.canonicalName);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return exports;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
exports.MCPToolRegistry = MCPToolRegistry;
|
|
238
|
+
// Singleton instance
|
|
239
|
+
let registryInstance = null;
|
|
240
|
+
/**
|
|
241
|
+
* Get or create registry instance
|
|
242
|
+
*/
|
|
243
|
+
function getToolRegistry() {
|
|
244
|
+
if (!registryInstance) {
|
|
245
|
+
registryInstance = new MCPToolRegistry();
|
|
246
|
+
}
|
|
247
|
+
return registryInstance;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Helper function to resolve tool names
|
|
251
|
+
*/
|
|
252
|
+
function resolveToolName(toolName) {
|
|
253
|
+
const registry = getToolRegistry();
|
|
254
|
+
return registry.resolveTool(toolName) || toolName;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Helper to check if tool exists
|
|
258
|
+
*/
|
|
259
|
+
function isValidTool(toolName) {
|
|
260
|
+
const registry = getToolRegistry();
|
|
261
|
+
return registry.toolExists(toolName);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Helper to get tool suggestions
|
|
265
|
+
*/
|
|
266
|
+
function suggestTools(partial) {
|
|
267
|
+
const registry = getToolRegistry();
|
|
268
|
+
return registry.searchTools(partial);
|
|
269
|
+
}
|
|
270
|
+
// Export types and default instance
|
|
271
|
+
exports.default = MCPToolRegistry;
|