UNPKG

snow-flow

Version:

Snow-Flow v3.2.0: Complete ServiceNow Enterprise Suite with 180+ MCP Tools. ATF Testing, Knowledge Management, Service Catalog, Change Management with CAB scheduling, Virtual Agent chatbots with NLU, Performance Analytics KPIs, Flow Designer automation, A

1,009 lines (983 loc) • 48.3 kB
#!/usr/bin/env node "use strict"; /** * ServiceNow Knowledge Management & Service Catalog MCP Server * Handles knowledge articles, service catalog items, and related operations * Uses official ServiceNow REST APIs for kb_knowledge and sc_cat_item tables */ Object.defineProperty(exports, "__esModule", { value: true }); const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const servicenow_client_js_1 = require("../utils/servicenow-client.js"); const mcp_auth_middleware_js_1 = require("../utils/mcp-auth-middleware.js"); const mcp_config_manager_js_1 = require("../utils/mcp-config-manager.js"); const logger_js_1 = require("../utils/logger.js"); class ServiceNowKnowledgeCatalogMCP { constructor() { this.server = new index_js_1.Server({ name: 'servicenow-knowledge-catalog', version: '1.0.0', }, { capabilities: { tools: {}, }, }); this.client = new servicenow_client_js_1.ServiceNowClient(); this.logger = new logger_js_1.Logger('ServiceNowKnowledgeCatalogMCP'); this.config = mcp_config_manager_js_1.mcpConfig.getConfig(); this.setupHandlers(); } setupHandlers() { this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: [ // Knowledge Management Tools { name: 'snow_create_knowledge_article', description: 'Creates a knowledge article in ServiceNow Knowledge Base. Articles can contain solutions, how-to guides, or reference information.', inputSchema: { type: 'object', properties: { short_description: { type: 'string', description: 'Article title' }, text: { type: 'string', description: 'Article content (HTML supported)' }, kb_knowledge_base: { type: 'string', description: 'Knowledge base sys_id or name' }, kb_category: { type: 'string', description: 'Category sys_id or name' }, article_type: { type: 'string', description: 'Type: text, html, wiki' }, workflow_state: { type: 'string', description: 'State: draft, review, published, retired' }, valid_to: { type: 'string', description: 'Expiration date (YYYY-MM-DD)' }, meta_description: { type: 'string', description: 'SEO meta description' }, keywords: { type: 'array', items: { type: 'string' }, description: 'Search keywords' }, author: { type: 'string', description: 'Author user sys_id or username' } }, required: ['short_description', 'text'] } }, { name: 'snow_search_knowledge', description: 'Searches knowledge articles using keywords, categories, or filters. Returns relevant articles with snippets.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query text' }, kb_knowledge_base: { type: 'string', description: 'Filter by knowledge base' }, kb_category: { type: 'string', description: 'Filter by category' }, workflow_state: { type: 'string', description: 'Filter by state (published, draft, etc.)' }, limit: { type: 'number', description: 'Maximum results to return', default: 10 }, include_content: { type: 'boolean', description: 'Include full article content', default: false } }, required: ['query'] } }, { name: 'snow_update_knowledge_article', description: 'Updates an existing knowledge article. Can modify content, metadata, or workflow state.', inputSchema: { type: 'object', properties: { sys_id: { type: 'string', description: 'Article sys_id to update' }, short_description: { type: 'string', description: 'Updated title' }, text: { type: 'string', description: 'Updated content' }, workflow_state: { type: 'string', description: 'New state' }, valid_to: { type: 'string', description: 'New expiration date' }, meta_description: { type: 'string', description: 'Updated SEO description' }, keywords: { type: 'array', items: { type: 'string' }, description: 'Updated keywords' } }, required: ['sys_id'] } }, { name: 'snow_retire_knowledge_article', description: 'Retires a knowledge article, making it unavailable for general use while preserving history.', inputSchema: { type: 'object', properties: { sys_id: { type: 'string', description: 'Article sys_id to retire' }, retirement_reason: { type: 'string', description: 'Reason for retirement' }, replacement_article: { type: 'string', description: 'Replacement article sys_id (optional)' } }, required: ['sys_id'] } }, { name: 'snow_create_knowledge_base', description: 'Creates a new knowledge base for organizing articles by topic, department, or audience.', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Knowledge base title' }, description: { type: 'string', description: 'Knowledge base description' }, owner: { type: 'string', description: 'Owner user or group' }, managers: { type: 'array', items: { type: 'string' }, description: 'Manager users or groups' }, kb_version: { type: 'string', description: 'Version number' }, active: { type: 'boolean', description: 'Active status', default: true } }, required: ['title'] } }, { name: 'snow_discover_knowledge_bases', description: 'Discovers available knowledge bases and their categories in the ServiceNow instance.', inputSchema: { type: 'object', properties: { active_only: { type: 'boolean', description: 'Show only active knowledge bases', default: true } } } }, // Service Catalog Tools { name: 'snow_create_catalog_item', description: 'Creates a service catalog item for user self-service requests. Includes forms, workflows, and fulfillment.', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Catalog item name' }, short_description: { type: 'string', description: 'Brief description' }, description: { type: 'string', description: 'Full description (HTML)' }, category: { type: 'string', description: 'Category sys_id or name' }, sc_catalogs: { type: 'string', description: 'Catalog sys_id or name' }, price: { type: 'string', description: 'Item price' }, recurring_price: { type: 'string', description: 'Recurring price' }, recurring_frequency: { type: 'string', description: 'Frequency: monthly, yearly' }, workflow: { type: 'string', description: 'Fulfillment workflow name' }, delivery_time: { type: 'string', description: 'Expected delivery time' }, active: { type: 'boolean', description: 'Active status', default: true }, billable: { type: 'boolean', description: 'Is billable', default: false }, mobile_hide_price: { type: 'boolean', description: 'Hide price on mobile', default: false } }, required: ['name', 'short_description'] } }, { name: 'snow_create_catalog_variable', description: 'Adds a variable (form field) to a catalog item for collecting user input during ordering.', inputSchema: { type: 'object', properties: { cat_item: { type: 'string', description: 'Catalog item sys_id' }, name: { type: 'string', description: 'Variable name' }, question_text: { type: 'string', description: 'Question to display' }, type: { type: 'string', description: 'Type: single_line_text, multi_line_text, select_box, checkbox, reference, etc.' }, order: { type: 'number', description: 'Display order' }, mandatory: { type: 'boolean', description: 'Is required', default: false }, default_value: { type: 'string', description: 'Default value' }, help_text: { type: 'string', description: 'Help text for users' }, reference: { type: 'string', description: 'Reference table (for reference type)' }, choice_table: { type: 'string', description: 'Choice list name (for select_box)' } }, required: ['cat_item', 'name', 'question_text', 'type'] } }, { name: 'snow_create_catalog_ui_policy', description: 'Creates UI policies for catalog items to control form behavior based on user input.', inputSchema: { type: 'object', properties: { cat_item: { type: 'string', description: 'Catalog item sys_id' }, short_description: { type: 'string', description: 'Policy name' }, condition: { type: 'string', description: 'Condition script' }, applies_to: { type: 'string', description: 'Applies to: item, set, or variable' }, active: { type: 'boolean', description: 'Active status', default: true }, on_load: { type: 'boolean', description: 'Run on form load', default: true }, reverse_if_false: { type: 'boolean', description: 'Reverse actions if false', default: true } }, required: ['cat_item', 'short_description', 'condition'] } }, { name: 'snow_create_catalog_client_script', description: 'Creates client scripts for catalog items to add custom JavaScript behavior to forms.', inputSchema: { type: 'object', properties: { cat_item: { type: 'string', description: 'Catalog item sys_id' }, name: { type: 'string', description: 'Script name' }, script: { type: 'string', description: 'JavaScript code' }, type: { type: 'string', description: 'Type: onLoad, onChange, onSubmit, onCellEdit' }, applies_to: { type: 'string', description: 'Applies to: item, set, variable' }, variable: { type: 'string', description: 'Variable name (for onChange)' }, active: { type: 'boolean', description: 'Active status', default: true } }, required: ['cat_item', 'name', 'script', 'type'] } }, { name: 'snow_search_catalog', description: 'Searches service catalog for items, categories, or catalogs. Returns available items for ordering.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, category: { type: 'string', description: 'Filter by category' }, catalog: { type: 'string', description: 'Filter by catalog' }, active_only: { type: 'boolean', description: 'Show only active items', default: true }, include_variables: { type: 'boolean', description: 'Include item variables', default: false }, limit: { type: 'number', description: 'Maximum results', default: 20 } } } }, { name: 'snow_order_catalog_item', description: 'Orders a catalog item programmatically, creating a request (RITM) with specified variable values.', inputSchema: { type: 'object', properties: { cat_item: { type: 'string', description: 'Catalog item sys_id' }, requested_for: { type: 'string', description: 'User sys_id or username' }, variables: { type: 'object', description: 'Variable name-value pairs' }, quantity: { type: 'number', description: 'Quantity to order', default: 1 }, delivery_address: { type: 'string', description: 'Delivery address' }, special_instructions: { type: 'string', description: 'Special instructions' } }, required: ['cat_item', 'requested_for'] } }, { name: 'snow_get_catalog_item_details', description: 'Gets detailed information about a catalog item including variables, pricing, and availability.', inputSchema: { type: 'object', properties: { sys_id: { type: 'string', description: 'Catalog item sys_id' }, include_variables: { type: 'boolean', description: 'Include all variables', default: true }, include_ui_policies: { type: 'boolean', description: 'Include UI policies', default: false }, include_client_scripts: { type: 'boolean', description: 'Include client scripts', default: false } }, required: ['sys_id'] } }, { name: 'snow_discover_catalogs', description: 'Discovers available service catalogs and their categories in the ServiceNow instance.', inputSchema: { type: 'object', properties: { include_categories: { type: 'boolean', description: 'Include category tree', default: true }, active_only: { type: 'boolean', description: 'Show only active catalogs', default: true } } } } ] })); this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; const authResult = await mcp_auth_middleware_js_1.mcpAuth.ensureAuthenticated(); if (!authResult.success) { throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, authResult.error || 'Authentication required'); } switch (name) { // Knowledge Management case 'snow_create_knowledge_article': return await this.createKnowledgeArticle(args); case 'snow_search_knowledge': return await this.searchKnowledge(args); case 'snow_update_knowledge_article': return await this.updateKnowledgeArticle(args); case 'snow_retire_knowledge_article': return await this.retireKnowledgeArticle(args); case 'snow_create_knowledge_base': return await this.createKnowledgeBase(args); case 'snow_discover_knowledge_bases': return await this.discoverKnowledgeBases(args); // Service Catalog case 'snow_create_catalog_item': return await this.createCatalogItem(args); case 'snow_create_catalog_variable': return await this.createCatalogVariable(args); case 'snow_create_catalog_ui_policy': return await this.createCatalogUIPolicy(args); case 'snow_create_catalog_client_script': return await this.createCatalogClientScript(args); case 'snow_search_catalog': return await this.searchCatalog(args); case 'snow_order_catalog_item': return await this.orderCatalogItem(args); case 'snow_get_catalog_item_details': return await this.getCatalogItemDetails(args); case 'snow_discover_catalogs': return await this.discoverCatalogs(args); default: throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { this.logger.error(`Error in ${request.params.name}:`, error); throw error; } }); } /** * Create Knowledge Article * Uses kb_knowledge table */ async createKnowledgeArticle(args) { try { this.logger.info('Creating knowledge article...'); // Find knowledge base if name provided let kbId = args.kb_knowledge_base; if (kbId && !kbId.match(/^[a-f0-9]{32}$/)) { const kbResponse = await this.client.searchRecords('kb_knowledge_base', `title=${kbId}`, 1); if (kbResponse.success && kbResponse.data.result.length) { kbId = kbResponse.data.result[0].sys_id; } } // Find category if name provided let categoryId = args.kb_category; if (categoryId && !categoryId.match(/^[a-f0-9]{32}$/)) { const catResponse = await this.client.searchRecords('kb_category', `label=${categoryId}`, 1); if (catResponse.success && catResponse.data.result.length) { categoryId = catResponse.data.result[0].sys_id; } } const articleData = { short_description: args.short_description, text: args.text, kb_knowledge_base: kbId || '', kb_category: categoryId || '', article_type: args.article_type || 'text', workflow_state: args.workflow_state || 'draft', valid_to: args.valid_to || '', meta_description: args.meta_description || '', keywords: args.keywords ? args.keywords.join(',') : '', author: args.author || '' }; const updateSetResult = await this.client.ensureUpdateSet(); const response = await this.client.createRecord('kb_knowledge', articleData); if (!response.success) { throw new Error(`Failed to create knowledge article: ${response.error}`); } return { content: [{ type: 'text', text: `āœ… Knowledge Article created successfully! šŸ“š **${args.short_description}** šŸ†” sys_id: ${response.data.sys_id} šŸ“‚ Knowledge Base: ${args.kb_knowledge_base || 'Default'} šŸ·ļø Category: ${args.kb_category || 'Uncategorized'} šŸ“ Type: ${args.article_type || 'text'} šŸ“Š State: ${args.workflow_state || 'draft'} ${args.keywords ? `šŸ” Keywords: ${args.keywords.join(', ')}` : ''} ${args.valid_to ? `šŸ“… Valid Until: ${args.valid_to}` : ''} ✨ Article created and ready for review!` }] }; } catch (error) { this.logger.error('Failed to create knowledge article:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create knowledge article: ${error}`); } } /** * Search Knowledge Articles * Uses kb_knowledge table with text search */ async searchKnowledge(args) { try { this.logger.info('Searching knowledge articles...'); let query = `short_descriptionLIKE${args.query}^ORtextLIKE${args.query}`; if (args.kb_knowledge_base) { query += `^kb_knowledge_base=${args.kb_knowledge_base}`; } if (args.kb_category) { query += `^kb_category=${args.kb_category}`; } if (args.workflow_state) { query += `^workflow_state=${args.workflow_state}`; } else { query += '^workflow_state=published'; // Default to published only } const limit = args.limit || 10; const response = await this.client.searchRecords('kb_knowledge', query, limit); if (!response.success) { throw new Error('Failed to search knowledge articles'); } const articles = response.data.result; if (!articles.length) { return { content: [{ type: 'text', text: `āŒ No knowledge articles found matching "${args.query}"` }] }; } const articleList = articles.map((article) => { const snippet = args.include_content ? article.text?.substring(0, 200) + '...' : article.short_description; return `šŸ“„ **${article.short_description}** šŸ†” ${article.sys_id} šŸ“Š State: ${article.workflow_state} šŸ“… Updated: ${article.sys_updated_on} ${args.include_content ? `šŸ“ ${snippet}` : ''}`; }).join('\n\n'); return { content: [{ type: 'text', text: `šŸ” Knowledge Search Results for "${args.query}": ${articleList} ✨ Found ${articles.length} article(s)` }] }; } catch (error) { this.logger.error('Failed to search knowledge:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to search knowledge: ${error}`); } } /** * Update Knowledge Article */ async updateKnowledgeArticle(args) { try { this.logger.info('Updating knowledge article...'); const updateData = {}; if (args.short_description) updateData.short_description = args.short_description; if (args.text) updateData.text = args.text; if (args.workflow_state) updateData.workflow_state = args.workflow_state; if (args.valid_to) updateData.valid_to = args.valid_to; if (args.meta_description) updateData.meta_description = args.meta_description; if (args.keywords) updateData.keywords = args.keywords.join(','); const response = await this.client.updateRecord('kb_knowledge', args.sys_id, updateData); if (!response.success) { throw new Error(`Failed to update knowledge article: ${response.error}`); } return { content: [{ type: 'text', text: `āœ… Knowledge Article updated successfully! šŸ†” sys_id: ${args.sys_id} ${args.short_description ? `šŸ“š New Title: ${args.short_description}` : ''} ${args.workflow_state ? `šŸ“Š New State: ${args.workflow_state}` : ''} ${args.valid_to ? `šŸ“… Valid Until: ${args.valid_to}` : ''} ✨ Article updated!` }] }; } catch (error) { this.logger.error('Failed to update knowledge article:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to update knowledge article: ${error}`); } } /** * Retire Knowledge Article */ async retireKnowledgeArticle(args) { try { this.logger.info('Retiring knowledge article...'); const updateData = { workflow_state: 'retired', retirement_date: new Date().toISOString(), retirement_reason: args.retirement_reason || 'Retired via API' }; if (args.replacement_article) { updateData['replacement_article'] = args.replacement_article; } const response = await this.client.updateRecord('kb_knowledge', args.sys_id, updateData); if (!response.success) { throw new Error(`Failed to retire knowledge article: ${response.error}`); } return { content: [{ type: 'text', text: `āœ… Knowledge Article retired successfully! šŸ†” sys_id: ${args.sys_id} šŸ“Š State: Retired šŸ“ Reason: ${args.retirement_reason || 'Retired via API'} ${args.replacement_article ? `šŸ”„ Replacement: ${args.replacement_article}` : ''} ✨ Article retired and archived!` }] }; } catch (error) { this.logger.error('Failed to retire knowledge article:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to retire knowledge article: ${error}`); } } /** * Create Knowledge Base */ async createKnowledgeBase(args) { try { this.logger.info('Creating knowledge base...'); const kbData = { title: args.title, description: args.description || '', owner: args.owner || '', kb_version: args.kb_version || '1.0', active: args.active !== false }; const updateSetResult = await this.client.ensureUpdateSet(); const response = await this.client.createRecord('kb_knowledge_base', kbData); if (!response.success) { throw new Error(`Failed to create knowledge base: ${response.error}`); } return { content: [{ type: 'text', text: `āœ… Knowledge Base created successfully! šŸ“š **${args.title}** šŸ†” sys_id: ${response.data.sys_id} šŸ“ Description: ${args.description || 'No description'} šŸ‘¤ Owner: ${args.owner || 'Not specified'} šŸ”¢ Version: ${args.kb_version || '1.0'} šŸ”„ Active: ${args.active !== false ? 'Yes' : 'No'} ✨ Knowledge base ready for articles!` }] }; } catch (error) { this.logger.error('Failed to create knowledge base:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create knowledge base: ${error}`); } } /** * Discover Knowledge Bases */ async discoverKnowledgeBases(args) { try { this.logger.info('Discovering knowledge bases...'); let query = ''; if (args.active_only) { query = 'active=true'; } const kbResponse = await this.client.searchRecords('kb_knowledge_base', query, 50); if (!kbResponse.success) { throw new Error('Failed to discover knowledge bases'); } const knowledgeBases = kbResponse.data.result; // Get categories for each knowledge base const kbWithCategories = await Promise.all(knowledgeBases.map(async (kb) => { const catResponse = await this.client.searchRecords('kb_category', `kb_knowledge_base=${kb.sys_id}`, 20); const categories = catResponse.success ? catResponse.data.result : []; return { ...kb, categories }; })); const kbText = kbWithCategories.map((kb) => { const categoryList = kb.categories.map((cat) => ` - ${cat.label}`).join('\n'); return `šŸ“š **${kb.title}** ${kb.active ? 'āœ…' : 'āŒ'} šŸ†” ${kb.sys_id} šŸ“ ${kb.description || 'No description'} šŸ“‚ Categories: ${categoryList || ' No categories'}`; }).join('\n\n'); return { content: [{ type: 'text', text: `šŸ” Discovered Knowledge Bases: ${kbText} ✨ Found ${knowledgeBases.length} knowledge base(s)` }] }; } catch (error) { this.logger.error('Failed to discover knowledge bases:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover knowledge bases: ${error}`); } } /** * Create Catalog Item * Uses sc_cat_item table */ async createCatalogItem(args) { try { this.logger.info('Creating catalog item...'); // Find category if name provided let categoryId = args.category; if (categoryId && !categoryId.match(/^[a-f0-9]{32}$/)) { const catResponse = await this.client.searchRecords('sc_category', `title=${categoryId}`, 1); if (catResponse.success && catResponse.data.result.length) { categoryId = catResponse.data.result[0].sys_id; } } // Find catalog if name provided let catalogId = args.sc_catalogs; if (catalogId && !catalogId.match(/^[a-f0-9]{32}$/)) { const catalogResponse = await this.client.searchRecords('sc_catalog', `title=${catalogId}`, 1); if (catalogResponse.success && catalogResponse.data.result.length) { catalogId = catalogResponse.data.result[0].sys_id; } } const itemData = { name: args.name, short_description: args.short_description, description: args.description || '', category: categoryId || '', sc_catalogs: catalogId || '', price: args.price || '0', recurring_price: args.recurring_price || '0', recurring_frequency: args.recurring_frequency || '', workflow: args.workflow || '', delivery_time: args.delivery_time || '3 business days', active: args.active !== false, billable: args.billable || false, mobile_hide_price: args.mobile_hide_price || false }; const updateSetResult = await this.client.ensureUpdateSet(); const response = await this.client.createRecord('sc_cat_item', itemData); if (!response.success) { throw new Error(`Failed to create catalog item: ${response.error}`); } return { content: [{ type: 'text', text: `āœ… Catalog Item created successfully! šŸ›ļø **${args.name}** šŸ†” sys_id: ${response.data.sys_id} šŸ“ ${args.short_description} ${args.category ? `šŸ“‚ Category: ${args.category}` : ''} ${args.price && args.price !== '0' ? `šŸ’° Price: $${args.price}` : ''} ${args.recurring_price && args.recurring_price !== '0' ? `šŸ”„ Recurring: $${args.recurring_price} ${args.recurring_frequency || ''}` : ''} šŸ“¦ Delivery: ${args.delivery_time || '3 business days'} šŸ”„ Active: ${args.active !== false ? 'Yes' : 'No'} ✨ Catalog item ready for ordering!` }] }; } catch (error) { this.logger.error('Failed to create catalog item:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create catalog item: ${error}`); } } /** * Create Catalog Variable * Uses item_option_new table */ async createCatalogVariable(args) { try { this.logger.info('Creating catalog variable...'); const variableData = { cat_item: args.cat_item, name: args.name, question_text: args.question_text, type: args.type, order: args.order || 100, mandatory: args.mandatory || false, default_value: args.default_value || '', help_text: args.help_text || '', reference: args.reference || '', choice_table: args.choice_table || '' }; const response = await this.client.createRecord('item_option_new', variableData); if (!response.success) { throw new Error(`Failed to create catalog variable: ${response.error}`); } return { content: [{ type: 'text', text: `āœ… Catalog Variable created successfully! šŸ“ **${args.question_text}** šŸ†” sys_id: ${response.data.sys_id} šŸ·ļø Name: ${args.name} šŸ“Š Type: ${args.type} šŸ”¢ Order: ${args.order || 100} ${args.mandatory ? 'āš ļø Required: Yes' : 'āœ… Required: No'} ${args.default_value ? `šŸ“‹ Default: ${args.default_value}` : ''} ${args.help_text ? `ā“ Help: ${args.help_text}` : ''} ✨ Variable added to catalog item!` }] }; } catch (error) { this.logger.error('Failed to create catalog variable:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create catalog variable: ${error}`); } } /** * Create Catalog UI Policy * Uses catalog_ui_policy table */ async createCatalogUIPolicy(args) { try { this.logger.info('Creating catalog UI policy...'); const policyData = { catalog_item: args.cat_item, short_description: args.short_description, catalog_conditions: args.condition, applies_catalog: args.applies_to || 'item', active: args.active !== false, applies_on_load: args.on_load !== false, reverse_if_false: args.reverse_if_false !== false }; const response = await this.client.createRecord('catalog_ui_policy', policyData); if (!response.success) { throw new Error(`Failed to create catalog UI policy: ${response.error}`); } return { content: [{ type: 'text', text: `āœ… Catalog UI Policy created successfully! šŸ“‹ **${args.short_description}** šŸ†” sys_id: ${response.data.sys_id} šŸŽÆ Applies to: ${args.applies_to || 'item'} šŸ”„ Active: ${args.active !== false ? 'Yes' : 'No'} ⚔ On Load: ${args.on_load !== false ? 'Yes' : 'No'} šŸ” Reverse if False: ${args.reverse_if_false !== false ? 'Yes' : 'No'} ✨ UI policy configured!` }] }; } catch (error) { this.logger.error('Failed to create catalog UI policy:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create catalog UI policy: ${error}`); } } /** * Create Catalog Client Script * Uses catalog_script_client table */ async createCatalogClientScript(args) { try { this.logger.info('Creating catalog client script...'); const scriptData = { cat_item: args.cat_item, name: args.name, script: args.script, type: args.type, applies_to: args.applies_to || 'item', cat_variable: args.variable || '', active: args.active !== false }; const response = await this.client.createRecord('catalog_script_client', scriptData); if (!response.success) { throw new Error(`Failed to create catalog client script: ${response.error}`); } return { content: [{ type: 'text', text: `āœ… Catalog Client Script created successfully! šŸ“œ **${args.name}** šŸ†” sys_id: ${response.data.sys_id} šŸŽÆ Type: ${args.type} šŸ“ Applies to: ${args.applies_to || 'item'} ${args.variable ? `šŸ“ Variable: ${args.variable}` : ''} šŸ”„ Active: ${args.active !== false ? 'Yes' : 'No'} ✨ Client script added to catalog item!` }] }; } catch (error) { this.logger.error('Failed to create catalog client script:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create catalog client script: ${error}`); } } /** * Search Service Catalog */ async searchCatalog(args) { try { this.logger.info('Searching service catalog...'); let query = args.query ? `nameLIKE${args.query}^ORshort_descriptionLIKE${args.query}` : ''; if (args.category) { query += query ? '^' : ''; query += `category=${args.category}`; } if (args.catalog) { query += query ? '^' : ''; query += `sc_catalogs=${args.catalog}`; } if (args.active_only) { query += query ? '^' : ''; query += 'active=true'; } const limit = args.limit || 20; const response = await this.client.searchRecords('sc_cat_item', query, limit); if (!response.success) { throw new Error('Failed to search catalog'); } const items = response.data.result; if (!items.length) { return { content: [{ type: 'text', text: `āŒ No catalog items found${args.query ? ` matching "${args.query}"` : ''}` }] }; } const itemList = items.map((item) => { return `šŸ›ļø **${item.name}** šŸ†” ${item.sys_id} šŸ“ ${item.short_description} ${item.price && item.price !== '0' ? `šŸ’° Price: $${item.price}` : ''} šŸ”„ Active: ${item.active ? 'Yes' : 'No'}`; }).join('\n\n'); return { content: [{ type: 'text', text: `šŸ” Catalog Search Results${args.query ? ` for "${args.query}"` : ''}: ${itemList} ✨ Found ${items.length} catalog item(s)` }] }; } catch (error) { this.logger.error('Failed to search catalog:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to search catalog: ${error}`); } } /** * Order Catalog Item * Creates sc_request and sc_req_item records */ async orderCatalogItem(args) { try { this.logger.info('Ordering catalog item...'); // Create service catalog request const requestData = { requested_for: args.requested_for, opened_by: args.requested_for, special_instructions: args.special_instructions || '' }; const requestResponse = await this.client.createRecord('sc_request', requestData); if (!requestResponse.success) { throw new Error(`Failed to create request: ${requestResponse.error}`); } const requestId = requestResponse.data.sys_id; // Create requested item (RITM) const ritmData = { request: requestId, cat_item: args.cat_item, requested_for: args.requested_for, quantity: args.quantity || 1, delivery_address: args.delivery_address || '' }; const ritmResponse = await this.client.createRecord('sc_req_item', ritmData); if (!ritmResponse.success) { throw new Error(`Failed to create requested item: ${ritmResponse.error}`); } const ritmId = ritmResponse.data.sys_id; const ritmNumber = ritmResponse.data.number; // Set variable values if provided if (args.variables) { for (const [varName, varValue] of Object.entries(args.variables)) { const varData = { request_item: ritmId, name: varName, value: varValue }; await this.client.createRecord('sc_item_option_mtom', varData); } } return { content: [{ type: 'text', text: `āœ… Catalog Item ordered successfully! šŸ›ļø **Order Placed** šŸ†” Request: ${requestId} šŸ“¦ RITM: ${ritmNumber} šŸ‘¤ Requested For: ${args.requested_for} šŸ“Š Quantity: ${args.quantity || 1} ${args.delivery_address ? `šŸ“ Delivery: ${args.delivery_address}` : ''} ${args.special_instructions ? `šŸ“ Instructions: ${args.special_instructions}` : ''} ✨ Order submitted for fulfillment!` }] }; } catch (error) { this.logger.error('Failed to order catalog item:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to order catalog item: ${error}`); } } /** * Get Catalog Item Details */ async getCatalogItemDetails(args) { try { this.logger.info('Getting catalog item details...'); const itemResponse = await this.client.getRecord('sc_cat_item', args.sys_id); if (!itemResponse.success) { throw new Error('Catalog item not found'); } const item = itemResponse.data; let details = `šŸ›ļø **${item.name}** šŸ†” sys_id: ${item.sys_id} šŸ“ ${item.short_description} šŸ“„ ${item.description || 'No detailed description'} ${item.price && item.price !== '0' ? `šŸ’° Price: $${item.price}` : ''} ${item.recurring_price && item.recurring_price !== '0' ? `šŸ”„ Recurring: $${item.recurring_price}` : ''} šŸ“¦ Delivery: ${item.delivery_time || '3 business days'} šŸ”„ Active: ${item.active ? 'Yes' : 'No'}`; // Get variables if requested if (args.include_variables) { const varResponse = await this.client.searchRecords('item_option_new', `cat_item=${args.sys_id}`, 50); if (varResponse.success && varResponse.data.result.length) { const variables = varResponse.data.result.map((v) => ` - ${v.question_text} (${v.type})${v.mandatory ? ' *Required' : ''}`).join('\n'); details += `\n\nšŸ“‹ **Variables:**\n${variables}`; } } return { content: [{ type: 'text', text: details + '\n\n✨ Catalog item details retrieved!' }] }; } catch (error) { this.logger.error('Failed to get catalog item details:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to get catalog item details: ${error}`); } } /** * Discover Service Catalogs */ async discoverCatalogs(args) { try { this.logger.info('Discovering service catalogs...'); let query = ''; if (args.active_only) { query = 'active=true'; } const catalogResponse = await this.client.searchRecords('sc_catalog', query, 50); if (!catalogResponse.success) { throw new Error('Failed to discover catalogs'); } const catalogs = catalogResponse.data.result; // Get categories if requested const catalogsWithDetails = await Promise.all(catalogs.map(async (catalog) => { if (args.include_categories) { const catResponse = await this.client.searchRecords('sc_category', `sc_catalog=${catalog.sys_id}`, 20); const categories = catResponse.success ? catResponse.data.result : []; return { ...catalog, categories }; } return catalog; })); const catalogText = catalogsWithDetails.map((catalog) => { let text = `šŸ›ļø **${catalog.title}** ${catalog.active ? 'āœ…' : 'āŒ'} šŸ†” ${catalog.sys_id} šŸ“ ${catalog.description || 'No description'}`; if (catalog.categories) { const categoryList = catalog.categories.map((cat) => ` - ${cat.title}`).join('\n'); text += `\nšŸ“‚ Categories:\n${categoryList || ' No categories'}`; } return text; }).join('\n\n'); return { content: [{ type: 'text', text: `šŸ” Discovered Service Catalogs: ${catalogText} ✨ Found ${catalogs.length} catalog(s)` }] }; } catch (error) { this.logger.error('Failed to discover catalogs:', error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover catalogs: ${error}`); } } async run() { const transport = new stdio_js_1.StdioServerTransport(); await this.server.connect(transport); this.logger.info('ServiceNow Knowledge & Catalog MCP Server running on stdio'); } } const server = new ServiceNowKnowledgeCatalogMCP(); server.run().catch(console.error); //# sourceMappingURL=servicenow-knowledge-catalog-mcp.js.map