snow-flow 3.1.2 → 3.2.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.
@@ -0,0 +1,1009 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * ServiceNow Knowledge Management & Service Catalog MCP Server
5
+ * Handles knowledge articles, service catalog items, and related operations
6
+ * Uses official ServiceNow REST APIs for kb_knowledge and sc_cat_item tables
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
10
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
11
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
12
+ const servicenow_client_js_1 = require("../utils/servicenow-client.js");
13
+ const mcp_auth_middleware_js_1 = require("../utils/mcp-auth-middleware.js");
14
+ const mcp_config_manager_js_1 = require("../utils/mcp-config-manager.js");
15
+ const logger_js_1 = require("../utils/logger.js");
16
+ class ServiceNowKnowledgeCatalogMCP {
17
+ constructor() {
18
+ this.server = new index_js_1.Server({
19
+ name: 'servicenow-knowledge-catalog',
20
+ version: '1.0.0',
21
+ }, {
22
+ capabilities: {
23
+ tools: {},
24
+ },
25
+ });
26
+ this.client = new servicenow_client_js_1.ServiceNowClient();
27
+ this.logger = new logger_js_1.Logger('ServiceNowKnowledgeCatalogMCP');
28
+ this.config = mcp_config_manager_js_1.mcpConfig.getConfig();
29
+ this.setupHandlers();
30
+ }
31
+ setupHandlers() {
32
+ this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
33
+ tools: [
34
+ // Knowledge Management Tools
35
+ {
36
+ name: 'snow_create_knowledge_article',
37
+ description: 'Creates a knowledge article in ServiceNow Knowledge Base. Articles can contain solutions, how-to guides, or reference information.',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ short_description: { type: 'string', description: 'Article title' },
42
+ text: { type: 'string', description: 'Article content (HTML supported)' },
43
+ kb_knowledge_base: { type: 'string', description: 'Knowledge base sys_id or name' },
44
+ kb_category: { type: 'string', description: 'Category sys_id or name' },
45
+ article_type: { type: 'string', description: 'Type: text, html, wiki' },
46
+ workflow_state: { type: 'string', description: 'State: draft, review, published, retired' },
47
+ valid_to: { type: 'string', description: 'Expiration date (YYYY-MM-DD)' },
48
+ meta_description: { type: 'string', description: 'SEO meta description' },
49
+ keywords: { type: 'array', items: { type: 'string' }, description: 'Search keywords' },
50
+ author: { type: 'string', description: 'Author user sys_id or username' }
51
+ },
52
+ required: ['short_description', 'text']
53
+ }
54
+ },
55
+ {
56
+ name: 'snow_search_knowledge',
57
+ description: 'Searches knowledge articles using keywords, categories, or filters. Returns relevant articles with snippets.',
58
+ inputSchema: {
59
+ type: 'object',
60
+ properties: {
61
+ query: { type: 'string', description: 'Search query text' },
62
+ kb_knowledge_base: { type: 'string', description: 'Filter by knowledge base' },
63
+ kb_category: { type: 'string', description: 'Filter by category' },
64
+ workflow_state: { type: 'string', description: 'Filter by state (published, draft, etc.)' },
65
+ limit: { type: 'number', description: 'Maximum results to return', default: 10 },
66
+ include_content: { type: 'boolean', description: 'Include full article content', default: false }
67
+ },
68
+ required: ['query']
69
+ }
70
+ },
71
+ {
72
+ name: 'snow_update_knowledge_article',
73
+ description: 'Updates an existing knowledge article. Can modify content, metadata, or workflow state.',
74
+ inputSchema: {
75
+ type: 'object',
76
+ properties: {
77
+ sys_id: { type: 'string', description: 'Article sys_id to update' },
78
+ short_description: { type: 'string', description: 'Updated title' },
79
+ text: { type: 'string', description: 'Updated content' },
80
+ workflow_state: { type: 'string', description: 'New state' },
81
+ valid_to: { type: 'string', description: 'New expiration date' },
82
+ meta_description: { type: 'string', description: 'Updated SEO description' },
83
+ keywords: { type: 'array', items: { type: 'string' }, description: 'Updated keywords' }
84
+ },
85
+ required: ['sys_id']
86
+ }
87
+ },
88
+ {
89
+ name: 'snow_retire_knowledge_article',
90
+ description: 'Retires a knowledge article, making it unavailable for general use while preserving history.',
91
+ inputSchema: {
92
+ type: 'object',
93
+ properties: {
94
+ sys_id: { type: 'string', description: 'Article sys_id to retire' },
95
+ retirement_reason: { type: 'string', description: 'Reason for retirement' },
96
+ replacement_article: { type: 'string', description: 'Replacement article sys_id (optional)' }
97
+ },
98
+ required: ['sys_id']
99
+ }
100
+ },
101
+ {
102
+ name: 'snow_create_knowledge_base',
103
+ description: 'Creates a new knowledge base for organizing articles by topic, department, or audience.',
104
+ inputSchema: {
105
+ type: 'object',
106
+ properties: {
107
+ title: { type: 'string', description: 'Knowledge base title' },
108
+ description: { type: 'string', description: 'Knowledge base description' },
109
+ owner: { type: 'string', description: 'Owner user or group' },
110
+ managers: { type: 'array', items: { type: 'string' }, description: 'Manager users or groups' },
111
+ kb_version: { type: 'string', description: 'Version number' },
112
+ active: { type: 'boolean', description: 'Active status', default: true }
113
+ },
114
+ required: ['title']
115
+ }
116
+ },
117
+ {
118
+ name: 'snow_discover_knowledge_bases',
119
+ description: 'Discovers available knowledge bases and their categories in the ServiceNow instance.',
120
+ inputSchema: {
121
+ type: 'object',
122
+ properties: {
123
+ active_only: { type: 'boolean', description: 'Show only active knowledge bases', default: true }
124
+ }
125
+ }
126
+ },
127
+ // Service Catalog Tools
128
+ {
129
+ name: 'snow_create_catalog_item',
130
+ description: 'Creates a service catalog item for user self-service requests. Includes forms, workflows, and fulfillment.',
131
+ inputSchema: {
132
+ type: 'object',
133
+ properties: {
134
+ name: { type: 'string', description: 'Catalog item name' },
135
+ short_description: { type: 'string', description: 'Brief description' },
136
+ description: { type: 'string', description: 'Full description (HTML)' },
137
+ category: { type: 'string', description: 'Category sys_id or name' },
138
+ sc_catalogs: { type: 'string', description: 'Catalog sys_id or name' },
139
+ price: { type: 'string', description: 'Item price' },
140
+ recurring_price: { type: 'string', description: 'Recurring price' },
141
+ recurring_frequency: { type: 'string', description: 'Frequency: monthly, yearly' },
142
+ workflow: { type: 'string', description: 'Fulfillment workflow name' },
143
+ delivery_time: { type: 'string', description: 'Expected delivery time' },
144
+ active: { type: 'boolean', description: 'Active status', default: true },
145
+ billable: { type: 'boolean', description: 'Is billable', default: false },
146
+ mobile_hide_price: { type: 'boolean', description: 'Hide price on mobile', default: false }
147
+ },
148
+ required: ['name', 'short_description']
149
+ }
150
+ },
151
+ {
152
+ name: 'snow_create_catalog_variable',
153
+ description: 'Adds a variable (form field) to a catalog item for collecting user input during ordering.',
154
+ inputSchema: {
155
+ type: 'object',
156
+ properties: {
157
+ cat_item: { type: 'string', description: 'Catalog item sys_id' },
158
+ name: { type: 'string', description: 'Variable name' },
159
+ question_text: { type: 'string', description: 'Question to display' },
160
+ type: { type: 'string', description: 'Type: single_line_text, multi_line_text, select_box, checkbox, reference, etc.' },
161
+ order: { type: 'number', description: 'Display order' },
162
+ mandatory: { type: 'boolean', description: 'Is required', default: false },
163
+ default_value: { type: 'string', description: 'Default value' },
164
+ help_text: { type: 'string', description: 'Help text for users' },
165
+ reference: { type: 'string', description: 'Reference table (for reference type)' },
166
+ choice_table: { type: 'string', description: 'Choice list name (for select_box)' }
167
+ },
168
+ required: ['cat_item', 'name', 'question_text', 'type']
169
+ }
170
+ },
171
+ {
172
+ name: 'snow_create_catalog_ui_policy',
173
+ description: 'Creates UI policies for catalog items to control form behavior based on user input.',
174
+ inputSchema: {
175
+ type: 'object',
176
+ properties: {
177
+ cat_item: { type: 'string', description: 'Catalog item sys_id' },
178
+ short_description: { type: 'string', description: 'Policy name' },
179
+ condition: { type: 'string', description: 'Condition script' },
180
+ applies_to: { type: 'string', description: 'Applies to: item, set, or variable' },
181
+ active: { type: 'boolean', description: 'Active status', default: true },
182
+ on_load: { type: 'boolean', description: 'Run on form load', default: true },
183
+ reverse_if_false: { type: 'boolean', description: 'Reverse actions if false', default: true }
184
+ },
185
+ required: ['cat_item', 'short_description', 'condition']
186
+ }
187
+ },
188
+ {
189
+ name: 'snow_create_catalog_client_script',
190
+ description: 'Creates client scripts for catalog items to add custom JavaScript behavior to forms.',
191
+ inputSchema: {
192
+ type: 'object',
193
+ properties: {
194
+ cat_item: { type: 'string', description: 'Catalog item sys_id' },
195
+ name: { type: 'string', description: 'Script name' },
196
+ script: { type: 'string', description: 'JavaScript code' },
197
+ type: { type: 'string', description: 'Type: onLoad, onChange, onSubmit, onCellEdit' },
198
+ applies_to: { type: 'string', description: 'Applies to: item, set, variable' },
199
+ variable: { type: 'string', description: 'Variable name (for onChange)' },
200
+ active: { type: 'boolean', description: 'Active status', default: true }
201
+ },
202
+ required: ['cat_item', 'name', 'script', 'type']
203
+ }
204
+ },
205
+ {
206
+ name: 'snow_search_catalog',
207
+ description: 'Searches service catalog for items, categories, or catalogs. Returns available items for ordering.',
208
+ inputSchema: {
209
+ type: 'object',
210
+ properties: {
211
+ query: { type: 'string', description: 'Search query' },
212
+ category: { type: 'string', description: 'Filter by category' },
213
+ catalog: { type: 'string', description: 'Filter by catalog' },
214
+ active_only: { type: 'boolean', description: 'Show only active items', default: true },
215
+ include_variables: { type: 'boolean', description: 'Include item variables', default: false },
216
+ limit: { type: 'number', description: 'Maximum results', default: 20 }
217
+ }
218
+ }
219
+ },
220
+ {
221
+ name: 'snow_order_catalog_item',
222
+ description: 'Orders a catalog item programmatically, creating a request (RITM) with specified variable values.',
223
+ inputSchema: {
224
+ type: 'object',
225
+ properties: {
226
+ cat_item: { type: 'string', description: 'Catalog item sys_id' },
227
+ requested_for: { type: 'string', description: 'User sys_id or username' },
228
+ variables: { type: 'object', description: 'Variable name-value pairs' },
229
+ quantity: { type: 'number', description: 'Quantity to order', default: 1 },
230
+ delivery_address: { type: 'string', description: 'Delivery address' },
231
+ special_instructions: { type: 'string', description: 'Special instructions' }
232
+ },
233
+ required: ['cat_item', 'requested_for']
234
+ }
235
+ },
236
+ {
237
+ name: 'snow_get_catalog_item_details',
238
+ description: 'Gets detailed information about a catalog item including variables, pricing, and availability.',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ sys_id: { type: 'string', description: 'Catalog item sys_id' },
243
+ include_variables: { type: 'boolean', description: 'Include all variables', default: true },
244
+ include_ui_policies: { type: 'boolean', description: 'Include UI policies', default: false },
245
+ include_client_scripts: { type: 'boolean', description: 'Include client scripts', default: false }
246
+ },
247
+ required: ['sys_id']
248
+ }
249
+ },
250
+ {
251
+ name: 'snow_discover_catalogs',
252
+ description: 'Discovers available service catalogs and their categories in the ServiceNow instance.',
253
+ inputSchema: {
254
+ type: 'object',
255
+ properties: {
256
+ include_categories: { type: 'boolean', description: 'Include category tree', default: true },
257
+ active_only: { type: 'boolean', description: 'Show only active catalogs', default: true }
258
+ }
259
+ }
260
+ }
261
+ ]
262
+ }));
263
+ this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
264
+ try {
265
+ const { name, arguments: args } = request.params;
266
+ const authResult = await mcp_auth_middleware_js_1.mcpAuth.ensureAuthenticated();
267
+ if (!authResult.success) {
268
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, authResult.error || 'Authentication required');
269
+ }
270
+ switch (name) {
271
+ // Knowledge Management
272
+ case 'snow_create_knowledge_article':
273
+ return await this.createKnowledgeArticle(args);
274
+ case 'snow_search_knowledge':
275
+ return await this.searchKnowledge(args);
276
+ case 'snow_update_knowledge_article':
277
+ return await this.updateKnowledgeArticle(args);
278
+ case 'snow_retire_knowledge_article':
279
+ return await this.retireKnowledgeArticle(args);
280
+ case 'snow_create_knowledge_base':
281
+ return await this.createKnowledgeBase(args);
282
+ case 'snow_discover_knowledge_bases':
283
+ return await this.discoverKnowledgeBases(args);
284
+ // Service Catalog
285
+ case 'snow_create_catalog_item':
286
+ return await this.createCatalogItem(args);
287
+ case 'snow_create_catalog_variable':
288
+ return await this.createCatalogVariable(args);
289
+ case 'snow_create_catalog_ui_policy':
290
+ return await this.createCatalogUIPolicy(args);
291
+ case 'snow_create_catalog_client_script':
292
+ return await this.createCatalogClientScript(args);
293
+ case 'snow_search_catalog':
294
+ return await this.searchCatalog(args);
295
+ case 'snow_order_catalog_item':
296
+ return await this.orderCatalogItem(args);
297
+ case 'snow_get_catalog_item_details':
298
+ return await this.getCatalogItemDetails(args);
299
+ case 'snow_discover_catalogs':
300
+ return await this.discoverCatalogs(args);
301
+ default:
302
+ throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
303
+ }
304
+ }
305
+ catch (error) {
306
+ this.logger.error(`Error in ${request.params.name}:`, error);
307
+ throw error;
308
+ }
309
+ });
310
+ }
311
+ /**
312
+ * Create Knowledge Article
313
+ * Uses kb_knowledge table
314
+ */
315
+ async createKnowledgeArticle(args) {
316
+ try {
317
+ this.logger.info('Creating knowledge article...');
318
+ // Find knowledge base if name provided
319
+ let kbId = args.kb_knowledge_base;
320
+ if (kbId && !kbId.match(/^[a-f0-9]{32}$/)) {
321
+ const kbResponse = await this.client.searchRecords('kb_knowledge_base', `title=${kbId}`, 1);
322
+ if (kbResponse.success && kbResponse.data.result.length) {
323
+ kbId = kbResponse.data.result[0].sys_id;
324
+ }
325
+ }
326
+ // Find category if name provided
327
+ let categoryId = args.kb_category;
328
+ if (categoryId && !categoryId.match(/^[a-f0-9]{32}$/)) {
329
+ const catResponse = await this.client.searchRecords('kb_category', `label=${categoryId}`, 1);
330
+ if (catResponse.success && catResponse.data.result.length) {
331
+ categoryId = catResponse.data.result[0].sys_id;
332
+ }
333
+ }
334
+ const articleData = {
335
+ short_description: args.short_description,
336
+ text: args.text,
337
+ kb_knowledge_base: kbId || '',
338
+ kb_category: categoryId || '',
339
+ article_type: args.article_type || 'text',
340
+ workflow_state: args.workflow_state || 'draft',
341
+ valid_to: args.valid_to || '',
342
+ meta_description: args.meta_description || '',
343
+ keywords: args.keywords ? args.keywords.join(',') : '',
344
+ author: args.author || ''
345
+ };
346
+ const updateSetResult = await this.client.ensureUpdateSet();
347
+ const response = await this.client.createRecord('kb_knowledge', articleData);
348
+ if (!response.success) {
349
+ throw new Error(`Failed to create knowledge article: ${response.error}`);
350
+ }
351
+ return {
352
+ content: [{
353
+ type: 'text',
354
+ text: `āœ… Knowledge Article created successfully!
355
+
356
+ šŸ“š **${args.short_description}**
357
+ šŸ†” sys_id: ${response.data.sys_id}
358
+ šŸ“‚ Knowledge Base: ${args.kb_knowledge_base || 'Default'}
359
+ šŸ·ļø Category: ${args.kb_category || 'Uncategorized'}
360
+ šŸ“ Type: ${args.article_type || 'text'}
361
+ šŸ“Š State: ${args.workflow_state || 'draft'}
362
+ ${args.keywords ? `šŸ” Keywords: ${args.keywords.join(', ')}` : ''}
363
+ ${args.valid_to ? `šŸ“… Valid Until: ${args.valid_to}` : ''}
364
+
365
+ ✨ Article created and ready for review!`
366
+ }]
367
+ };
368
+ }
369
+ catch (error) {
370
+ this.logger.error('Failed to create knowledge article:', error);
371
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create knowledge article: ${error}`);
372
+ }
373
+ }
374
+ /**
375
+ * Search Knowledge Articles
376
+ * Uses kb_knowledge table with text search
377
+ */
378
+ async searchKnowledge(args) {
379
+ try {
380
+ this.logger.info('Searching knowledge articles...');
381
+ let query = `short_descriptionLIKE${args.query}^ORtextLIKE${args.query}`;
382
+ if (args.kb_knowledge_base) {
383
+ query += `^kb_knowledge_base=${args.kb_knowledge_base}`;
384
+ }
385
+ if (args.kb_category) {
386
+ query += `^kb_category=${args.kb_category}`;
387
+ }
388
+ if (args.workflow_state) {
389
+ query += `^workflow_state=${args.workflow_state}`;
390
+ }
391
+ else {
392
+ query += '^workflow_state=published'; // Default to published only
393
+ }
394
+ const limit = args.limit || 10;
395
+ const response = await this.client.searchRecords('kb_knowledge', query, limit);
396
+ if (!response.success) {
397
+ throw new Error('Failed to search knowledge articles');
398
+ }
399
+ const articles = response.data.result;
400
+ if (!articles.length) {
401
+ return {
402
+ content: [{
403
+ type: 'text',
404
+ text: `āŒ No knowledge articles found matching "${args.query}"`
405
+ }]
406
+ };
407
+ }
408
+ const articleList = articles.map((article) => {
409
+ const snippet = args.include_content ?
410
+ article.text?.substring(0, 200) + '...' :
411
+ article.short_description;
412
+ return `šŸ“„ **${article.short_description}**
413
+ šŸ†” ${article.sys_id}
414
+ šŸ“Š State: ${article.workflow_state}
415
+ šŸ“… Updated: ${article.sys_updated_on}
416
+ ${args.include_content ? `šŸ“ ${snippet}` : ''}`;
417
+ }).join('\n\n');
418
+ return {
419
+ content: [{
420
+ type: 'text',
421
+ text: `šŸ” Knowledge Search Results for "${args.query}":
422
+
423
+ ${articleList}
424
+
425
+ ✨ Found ${articles.length} article(s)`
426
+ }]
427
+ };
428
+ }
429
+ catch (error) {
430
+ this.logger.error('Failed to search knowledge:', error);
431
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to search knowledge: ${error}`);
432
+ }
433
+ }
434
+ /**
435
+ * Update Knowledge Article
436
+ */
437
+ async updateKnowledgeArticle(args) {
438
+ try {
439
+ this.logger.info('Updating knowledge article...');
440
+ const updateData = {};
441
+ if (args.short_description)
442
+ updateData.short_description = args.short_description;
443
+ if (args.text)
444
+ updateData.text = args.text;
445
+ if (args.workflow_state)
446
+ updateData.workflow_state = args.workflow_state;
447
+ if (args.valid_to)
448
+ updateData.valid_to = args.valid_to;
449
+ if (args.meta_description)
450
+ updateData.meta_description = args.meta_description;
451
+ if (args.keywords)
452
+ updateData.keywords = args.keywords.join(',');
453
+ const response = await this.client.updateRecord('kb_knowledge', args.sys_id, updateData);
454
+ if (!response.success) {
455
+ throw new Error(`Failed to update knowledge article: ${response.error}`);
456
+ }
457
+ return {
458
+ content: [{
459
+ type: 'text',
460
+ text: `āœ… Knowledge Article updated successfully!
461
+
462
+ šŸ†” sys_id: ${args.sys_id}
463
+ ${args.short_description ? `šŸ“š New Title: ${args.short_description}` : ''}
464
+ ${args.workflow_state ? `šŸ“Š New State: ${args.workflow_state}` : ''}
465
+ ${args.valid_to ? `šŸ“… Valid Until: ${args.valid_to}` : ''}
466
+
467
+ ✨ Article updated!`
468
+ }]
469
+ };
470
+ }
471
+ catch (error) {
472
+ this.logger.error('Failed to update knowledge article:', error);
473
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to update knowledge article: ${error}`);
474
+ }
475
+ }
476
+ /**
477
+ * Retire Knowledge Article
478
+ */
479
+ async retireKnowledgeArticle(args) {
480
+ try {
481
+ this.logger.info('Retiring knowledge article...');
482
+ const updateData = {
483
+ workflow_state: 'retired',
484
+ retirement_date: new Date().toISOString(),
485
+ retirement_reason: args.retirement_reason || 'Retired via API'
486
+ };
487
+ if (args.replacement_article) {
488
+ updateData['replacement_article'] = args.replacement_article;
489
+ }
490
+ const response = await this.client.updateRecord('kb_knowledge', args.sys_id, updateData);
491
+ if (!response.success) {
492
+ throw new Error(`Failed to retire knowledge article: ${response.error}`);
493
+ }
494
+ return {
495
+ content: [{
496
+ type: 'text',
497
+ text: `āœ… Knowledge Article retired successfully!
498
+
499
+ šŸ†” sys_id: ${args.sys_id}
500
+ šŸ“Š State: Retired
501
+ šŸ“ Reason: ${args.retirement_reason || 'Retired via API'}
502
+ ${args.replacement_article ? `šŸ”„ Replacement: ${args.replacement_article}` : ''}
503
+
504
+ ✨ Article retired and archived!`
505
+ }]
506
+ };
507
+ }
508
+ catch (error) {
509
+ this.logger.error('Failed to retire knowledge article:', error);
510
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to retire knowledge article: ${error}`);
511
+ }
512
+ }
513
+ /**
514
+ * Create Knowledge Base
515
+ */
516
+ async createKnowledgeBase(args) {
517
+ try {
518
+ this.logger.info('Creating knowledge base...');
519
+ const kbData = {
520
+ title: args.title,
521
+ description: args.description || '',
522
+ owner: args.owner || '',
523
+ kb_version: args.kb_version || '1.0',
524
+ active: args.active !== false
525
+ };
526
+ const updateSetResult = await this.client.ensureUpdateSet();
527
+ const response = await this.client.createRecord('kb_knowledge_base', kbData);
528
+ if (!response.success) {
529
+ throw new Error(`Failed to create knowledge base: ${response.error}`);
530
+ }
531
+ return {
532
+ content: [{
533
+ type: 'text',
534
+ text: `āœ… Knowledge Base created successfully!
535
+
536
+ šŸ“š **${args.title}**
537
+ šŸ†” sys_id: ${response.data.sys_id}
538
+ šŸ“ Description: ${args.description || 'No description'}
539
+ šŸ‘¤ Owner: ${args.owner || 'Not specified'}
540
+ šŸ”¢ Version: ${args.kb_version || '1.0'}
541
+ šŸ”„ Active: ${args.active !== false ? 'Yes' : 'No'}
542
+
543
+ ✨ Knowledge base ready for articles!`
544
+ }]
545
+ };
546
+ }
547
+ catch (error) {
548
+ this.logger.error('Failed to create knowledge base:', error);
549
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create knowledge base: ${error}`);
550
+ }
551
+ }
552
+ /**
553
+ * Discover Knowledge Bases
554
+ */
555
+ async discoverKnowledgeBases(args) {
556
+ try {
557
+ this.logger.info('Discovering knowledge bases...');
558
+ let query = '';
559
+ if (args.active_only) {
560
+ query = 'active=true';
561
+ }
562
+ const kbResponse = await this.client.searchRecords('kb_knowledge_base', query, 50);
563
+ if (!kbResponse.success) {
564
+ throw new Error('Failed to discover knowledge bases');
565
+ }
566
+ const knowledgeBases = kbResponse.data.result;
567
+ // Get categories for each knowledge base
568
+ const kbWithCategories = await Promise.all(knowledgeBases.map(async (kb) => {
569
+ const catResponse = await this.client.searchRecords('kb_category', `kb_knowledge_base=${kb.sys_id}`, 20);
570
+ const categories = catResponse.success ? catResponse.data.result : [];
571
+ return { ...kb, categories };
572
+ }));
573
+ const kbText = kbWithCategories.map((kb) => {
574
+ const categoryList = kb.categories.map((cat) => ` - ${cat.label}`).join('\n');
575
+ return `šŸ“š **${kb.title}** ${kb.active ? 'āœ…' : 'āŒ'}
576
+ šŸ†” ${kb.sys_id}
577
+ šŸ“ ${kb.description || 'No description'}
578
+ šŸ“‚ Categories:
579
+ ${categoryList || ' No categories'}`;
580
+ }).join('\n\n');
581
+ return {
582
+ content: [{
583
+ type: 'text',
584
+ text: `šŸ” Discovered Knowledge Bases:
585
+
586
+ ${kbText}
587
+
588
+ ✨ Found ${knowledgeBases.length} knowledge base(s)`
589
+ }]
590
+ };
591
+ }
592
+ catch (error) {
593
+ this.logger.error('Failed to discover knowledge bases:', error);
594
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover knowledge bases: ${error}`);
595
+ }
596
+ }
597
+ /**
598
+ * Create Catalog Item
599
+ * Uses sc_cat_item table
600
+ */
601
+ async createCatalogItem(args) {
602
+ try {
603
+ this.logger.info('Creating catalog item...');
604
+ // Find category if name provided
605
+ let categoryId = args.category;
606
+ if (categoryId && !categoryId.match(/^[a-f0-9]{32}$/)) {
607
+ const catResponse = await this.client.searchRecords('sc_category', `title=${categoryId}`, 1);
608
+ if (catResponse.success && catResponse.data.result.length) {
609
+ categoryId = catResponse.data.result[0].sys_id;
610
+ }
611
+ }
612
+ // Find catalog if name provided
613
+ let catalogId = args.sc_catalogs;
614
+ if (catalogId && !catalogId.match(/^[a-f0-9]{32}$/)) {
615
+ const catalogResponse = await this.client.searchRecords('sc_catalog', `title=${catalogId}`, 1);
616
+ if (catalogResponse.success && catalogResponse.data.result.length) {
617
+ catalogId = catalogResponse.data.result[0].sys_id;
618
+ }
619
+ }
620
+ const itemData = {
621
+ name: args.name,
622
+ short_description: args.short_description,
623
+ description: args.description || '',
624
+ category: categoryId || '',
625
+ sc_catalogs: catalogId || '',
626
+ price: args.price || '0',
627
+ recurring_price: args.recurring_price || '0',
628
+ recurring_frequency: args.recurring_frequency || '',
629
+ workflow: args.workflow || '',
630
+ delivery_time: args.delivery_time || '3 business days',
631
+ active: args.active !== false,
632
+ billable: args.billable || false,
633
+ mobile_hide_price: args.mobile_hide_price || false
634
+ };
635
+ const updateSetResult = await this.client.ensureUpdateSet();
636
+ const response = await this.client.createRecord('sc_cat_item', itemData);
637
+ if (!response.success) {
638
+ throw new Error(`Failed to create catalog item: ${response.error}`);
639
+ }
640
+ return {
641
+ content: [{
642
+ type: 'text',
643
+ text: `āœ… Catalog Item created successfully!
644
+
645
+ šŸ›ļø **${args.name}**
646
+ šŸ†” sys_id: ${response.data.sys_id}
647
+ šŸ“ ${args.short_description}
648
+ ${args.category ? `šŸ“‚ Category: ${args.category}` : ''}
649
+ ${args.price && args.price !== '0' ? `šŸ’° Price: $${args.price}` : ''}
650
+ ${args.recurring_price && args.recurring_price !== '0' ? `šŸ”„ Recurring: $${args.recurring_price} ${args.recurring_frequency || ''}` : ''}
651
+ šŸ“¦ Delivery: ${args.delivery_time || '3 business days'}
652
+ šŸ”„ Active: ${args.active !== false ? 'Yes' : 'No'}
653
+
654
+ ✨ Catalog item ready for ordering!`
655
+ }]
656
+ };
657
+ }
658
+ catch (error) {
659
+ this.logger.error('Failed to create catalog item:', error);
660
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create catalog item: ${error}`);
661
+ }
662
+ }
663
+ /**
664
+ * Create Catalog Variable
665
+ * Uses item_option_new table
666
+ */
667
+ async createCatalogVariable(args) {
668
+ try {
669
+ this.logger.info('Creating catalog variable...');
670
+ const variableData = {
671
+ cat_item: args.cat_item,
672
+ name: args.name,
673
+ question_text: args.question_text,
674
+ type: args.type,
675
+ order: args.order || 100,
676
+ mandatory: args.mandatory || false,
677
+ default_value: args.default_value || '',
678
+ help_text: args.help_text || '',
679
+ reference: args.reference || '',
680
+ choice_table: args.choice_table || ''
681
+ };
682
+ const response = await this.client.createRecord('item_option_new', variableData);
683
+ if (!response.success) {
684
+ throw new Error(`Failed to create catalog variable: ${response.error}`);
685
+ }
686
+ return {
687
+ content: [{
688
+ type: 'text',
689
+ text: `āœ… Catalog Variable created successfully!
690
+
691
+ šŸ“ **${args.question_text}**
692
+ šŸ†” sys_id: ${response.data.sys_id}
693
+ šŸ·ļø Name: ${args.name}
694
+ šŸ“Š Type: ${args.type}
695
+ šŸ”¢ Order: ${args.order || 100}
696
+ ${args.mandatory ? 'āš ļø Required: Yes' : 'āœ… Required: No'}
697
+ ${args.default_value ? `šŸ“‹ Default: ${args.default_value}` : ''}
698
+ ${args.help_text ? `ā“ Help: ${args.help_text}` : ''}
699
+
700
+ ✨ Variable added to catalog item!`
701
+ }]
702
+ };
703
+ }
704
+ catch (error) {
705
+ this.logger.error('Failed to create catalog variable:', error);
706
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create catalog variable: ${error}`);
707
+ }
708
+ }
709
+ /**
710
+ * Create Catalog UI Policy
711
+ * Uses catalog_ui_policy table
712
+ */
713
+ async createCatalogUIPolicy(args) {
714
+ try {
715
+ this.logger.info('Creating catalog UI policy...');
716
+ const policyData = {
717
+ catalog_item: args.cat_item,
718
+ short_description: args.short_description,
719
+ catalog_conditions: args.condition,
720
+ applies_catalog: args.applies_to || 'item',
721
+ active: args.active !== false,
722
+ applies_on_load: args.on_load !== false,
723
+ reverse_if_false: args.reverse_if_false !== false
724
+ };
725
+ const response = await this.client.createRecord('catalog_ui_policy', policyData);
726
+ if (!response.success) {
727
+ throw new Error(`Failed to create catalog UI policy: ${response.error}`);
728
+ }
729
+ return {
730
+ content: [{
731
+ type: 'text',
732
+ text: `āœ… Catalog UI Policy created successfully!
733
+
734
+ šŸ“‹ **${args.short_description}**
735
+ šŸ†” sys_id: ${response.data.sys_id}
736
+ šŸŽÆ Applies to: ${args.applies_to || 'item'}
737
+ šŸ”„ Active: ${args.active !== false ? 'Yes' : 'No'}
738
+ ⚔ On Load: ${args.on_load !== false ? 'Yes' : 'No'}
739
+ šŸ” Reverse if False: ${args.reverse_if_false !== false ? 'Yes' : 'No'}
740
+
741
+ ✨ UI policy configured!`
742
+ }]
743
+ };
744
+ }
745
+ catch (error) {
746
+ this.logger.error('Failed to create catalog UI policy:', error);
747
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create catalog UI policy: ${error}`);
748
+ }
749
+ }
750
+ /**
751
+ * Create Catalog Client Script
752
+ * Uses catalog_script_client table
753
+ */
754
+ async createCatalogClientScript(args) {
755
+ try {
756
+ this.logger.info('Creating catalog client script...');
757
+ const scriptData = {
758
+ cat_item: args.cat_item,
759
+ name: args.name,
760
+ script: args.script,
761
+ type: args.type,
762
+ applies_to: args.applies_to || 'item',
763
+ cat_variable: args.variable || '',
764
+ active: args.active !== false
765
+ };
766
+ const response = await this.client.createRecord('catalog_script_client', scriptData);
767
+ if (!response.success) {
768
+ throw new Error(`Failed to create catalog client script: ${response.error}`);
769
+ }
770
+ return {
771
+ content: [{
772
+ type: 'text',
773
+ text: `āœ… Catalog Client Script created successfully!
774
+
775
+ šŸ“œ **${args.name}**
776
+ šŸ†” sys_id: ${response.data.sys_id}
777
+ šŸŽÆ Type: ${args.type}
778
+ šŸ“ Applies to: ${args.applies_to || 'item'}
779
+ ${args.variable ? `šŸ“ Variable: ${args.variable}` : ''}
780
+ šŸ”„ Active: ${args.active !== false ? 'Yes' : 'No'}
781
+
782
+ ✨ Client script added to catalog item!`
783
+ }]
784
+ };
785
+ }
786
+ catch (error) {
787
+ this.logger.error('Failed to create catalog client script:', error);
788
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create catalog client script: ${error}`);
789
+ }
790
+ }
791
+ /**
792
+ * Search Service Catalog
793
+ */
794
+ async searchCatalog(args) {
795
+ try {
796
+ this.logger.info('Searching service catalog...');
797
+ let query = args.query ? `nameLIKE${args.query}^ORshort_descriptionLIKE${args.query}` : '';
798
+ if (args.category) {
799
+ query += query ? '^' : '';
800
+ query += `category=${args.category}`;
801
+ }
802
+ if (args.catalog) {
803
+ query += query ? '^' : '';
804
+ query += `sc_catalogs=${args.catalog}`;
805
+ }
806
+ if (args.active_only) {
807
+ query += query ? '^' : '';
808
+ query += 'active=true';
809
+ }
810
+ const limit = args.limit || 20;
811
+ const response = await this.client.searchRecords('sc_cat_item', query, limit);
812
+ if (!response.success) {
813
+ throw new Error('Failed to search catalog');
814
+ }
815
+ const items = response.data.result;
816
+ if (!items.length) {
817
+ return {
818
+ content: [{
819
+ type: 'text',
820
+ text: `āŒ No catalog items found${args.query ? ` matching "${args.query}"` : ''}`
821
+ }]
822
+ };
823
+ }
824
+ const itemList = items.map((item) => {
825
+ return `šŸ›ļø **${item.name}**
826
+ šŸ†” ${item.sys_id}
827
+ šŸ“ ${item.short_description}
828
+ ${item.price && item.price !== '0' ? `šŸ’° Price: $${item.price}` : ''}
829
+ šŸ”„ Active: ${item.active ? 'Yes' : 'No'}`;
830
+ }).join('\n\n');
831
+ return {
832
+ content: [{
833
+ type: 'text',
834
+ text: `šŸ” Catalog Search Results${args.query ? ` for "${args.query}"` : ''}:
835
+
836
+ ${itemList}
837
+
838
+ ✨ Found ${items.length} catalog item(s)`
839
+ }]
840
+ };
841
+ }
842
+ catch (error) {
843
+ this.logger.error('Failed to search catalog:', error);
844
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to search catalog: ${error}`);
845
+ }
846
+ }
847
+ /**
848
+ * Order Catalog Item
849
+ * Creates sc_request and sc_req_item records
850
+ */
851
+ async orderCatalogItem(args) {
852
+ try {
853
+ this.logger.info('Ordering catalog item...');
854
+ // Create service catalog request
855
+ const requestData = {
856
+ requested_for: args.requested_for,
857
+ opened_by: args.requested_for,
858
+ special_instructions: args.special_instructions || ''
859
+ };
860
+ const requestResponse = await this.client.createRecord('sc_request', requestData);
861
+ if (!requestResponse.success) {
862
+ throw new Error(`Failed to create request: ${requestResponse.error}`);
863
+ }
864
+ const requestId = requestResponse.data.sys_id;
865
+ // Create requested item (RITM)
866
+ const ritmData = {
867
+ request: requestId,
868
+ cat_item: args.cat_item,
869
+ requested_for: args.requested_for,
870
+ quantity: args.quantity || 1,
871
+ delivery_address: args.delivery_address || ''
872
+ };
873
+ const ritmResponse = await this.client.createRecord('sc_req_item', ritmData);
874
+ if (!ritmResponse.success) {
875
+ throw new Error(`Failed to create requested item: ${ritmResponse.error}`);
876
+ }
877
+ const ritmId = ritmResponse.data.sys_id;
878
+ const ritmNumber = ritmResponse.data.number;
879
+ // Set variable values if provided
880
+ if (args.variables) {
881
+ for (const [varName, varValue] of Object.entries(args.variables)) {
882
+ const varData = {
883
+ request_item: ritmId,
884
+ name: varName,
885
+ value: varValue
886
+ };
887
+ await this.client.createRecord('sc_item_option_mtom', varData);
888
+ }
889
+ }
890
+ return {
891
+ content: [{
892
+ type: 'text',
893
+ text: `āœ… Catalog Item ordered successfully!
894
+
895
+ šŸ›ļø **Order Placed**
896
+ šŸ†” Request: ${requestId}
897
+ šŸ“¦ RITM: ${ritmNumber}
898
+ šŸ‘¤ Requested For: ${args.requested_for}
899
+ šŸ“Š Quantity: ${args.quantity || 1}
900
+ ${args.delivery_address ? `šŸ“ Delivery: ${args.delivery_address}` : ''}
901
+ ${args.special_instructions ? `šŸ“ Instructions: ${args.special_instructions}` : ''}
902
+
903
+ ✨ Order submitted for fulfillment!`
904
+ }]
905
+ };
906
+ }
907
+ catch (error) {
908
+ this.logger.error('Failed to order catalog item:', error);
909
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to order catalog item: ${error}`);
910
+ }
911
+ }
912
+ /**
913
+ * Get Catalog Item Details
914
+ */
915
+ async getCatalogItemDetails(args) {
916
+ try {
917
+ this.logger.info('Getting catalog item details...');
918
+ const itemResponse = await this.client.getRecord('sc_cat_item', args.sys_id);
919
+ if (!itemResponse.success) {
920
+ throw new Error('Catalog item not found');
921
+ }
922
+ const item = itemResponse.data;
923
+ let details = `šŸ›ļø **${item.name}**
924
+ šŸ†” sys_id: ${item.sys_id}
925
+ šŸ“ ${item.short_description}
926
+ šŸ“„ ${item.description || 'No detailed description'}
927
+ ${item.price && item.price !== '0' ? `šŸ’° Price: $${item.price}` : ''}
928
+ ${item.recurring_price && item.recurring_price !== '0' ? `šŸ”„ Recurring: $${item.recurring_price}` : ''}
929
+ šŸ“¦ Delivery: ${item.delivery_time || '3 business days'}
930
+ šŸ”„ Active: ${item.active ? 'Yes' : 'No'}`;
931
+ // Get variables if requested
932
+ if (args.include_variables) {
933
+ const varResponse = await this.client.searchRecords('item_option_new', `cat_item=${args.sys_id}`, 50);
934
+ if (varResponse.success && varResponse.data.result.length) {
935
+ const variables = varResponse.data.result.map((v) => ` - ${v.question_text} (${v.type})${v.mandatory ? ' *Required' : ''}`).join('\n');
936
+ details += `\n\nšŸ“‹ **Variables:**\n${variables}`;
937
+ }
938
+ }
939
+ return {
940
+ content: [{
941
+ type: 'text',
942
+ text: details + '\n\n✨ Catalog item details retrieved!'
943
+ }]
944
+ };
945
+ }
946
+ catch (error) {
947
+ this.logger.error('Failed to get catalog item details:', error);
948
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get catalog item details: ${error}`);
949
+ }
950
+ }
951
+ /**
952
+ * Discover Service Catalogs
953
+ */
954
+ async discoverCatalogs(args) {
955
+ try {
956
+ this.logger.info('Discovering service catalogs...');
957
+ let query = '';
958
+ if (args.active_only) {
959
+ query = 'active=true';
960
+ }
961
+ const catalogResponse = await this.client.searchRecords('sc_catalog', query, 50);
962
+ if (!catalogResponse.success) {
963
+ throw new Error('Failed to discover catalogs');
964
+ }
965
+ const catalogs = catalogResponse.data.result;
966
+ // Get categories if requested
967
+ const catalogsWithDetails = await Promise.all(catalogs.map(async (catalog) => {
968
+ if (args.include_categories) {
969
+ const catResponse = await this.client.searchRecords('sc_category', `sc_catalog=${catalog.sys_id}`, 20);
970
+ const categories = catResponse.success ? catResponse.data.result : [];
971
+ return { ...catalog, categories };
972
+ }
973
+ return catalog;
974
+ }));
975
+ const catalogText = catalogsWithDetails.map((catalog) => {
976
+ let text = `šŸ›ļø **${catalog.title}** ${catalog.active ? 'āœ…' : 'āŒ'}
977
+ šŸ†” ${catalog.sys_id}
978
+ šŸ“ ${catalog.description || 'No description'}`;
979
+ if (catalog.categories) {
980
+ const categoryList = catalog.categories.map((cat) => ` - ${cat.title}`).join('\n');
981
+ text += `\nšŸ“‚ Categories:\n${categoryList || ' No categories'}`;
982
+ }
983
+ return text;
984
+ }).join('\n\n');
985
+ return {
986
+ content: [{
987
+ type: 'text',
988
+ text: `šŸ” Discovered Service Catalogs:
989
+
990
+ ${catalogText}
991
+
992
+ ✨ Found ${catalogs.length} catalog(s)`
993
+ }]
994
+ };
995
+ }
996
+ catch (error) {
997
+ this.logger.error('Failed to discover catalogs:', error);
998
+ throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover catalogs: ${error}`);
999
+ }
1000
+ }
1001
+ async run() {
1002
+ const transport = new stdio_js_1.StdioServerTransport();
1003
+ await this.server.connect(transport);
1004
+ this.logger.info('ServiceNow Knowledge & Catalog MCP Server running on stdio');
1005
+ }
1006
+ }
1007
+ const server = new ServiceNowKnowledgeCatalogMCP();
1008
+ server.run().catch(console.error);
1009
+ //# sourceMappingURL=servicenow-knowledge-catalog-mcp.js.map