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

305 lines • 11.1 kB
"use strict"; /** * MCP Resource Manager * Comprehensive resource management for MCP servers */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MCPResourceManager = void 0; const promises_1 = require("fs/promises"); const path_1 = require("path"); const logger_js_1 = require("../../utils/logger.js"); class MCPResourceManager { constructor(serverName = 'mcp-server') { this.resourceCache = new Map(); this.resourceIndex = new Map(); this.categories = []; this.logger = new logger_js_1.Logger(`ResourceManager:${serverName}`); this.initializeCategories(); } /** * Initialize resource categories */ initializeCategories() { const projectRoot = this.getProjectRoot(); this.categories = [ { name: 'templates', description: 'ServiceNow artifact templates (widgets, flows, scripts, etc.)', basePath: (0, path_1.join)(projectRoot, 'src/templates'), uriPrefix: 'servicenow://templates/' }, { name: 'documentation', description: 'Setup guides, deployment documentation, and API references', basePath: projectRoot, uriPrefix: 'servicenow://docs/' }, { name: 'schemas', description: 'Data validation schemas and API schemas', basePath: (0, path_1.join)(projectRoot, 'src/schemas'), uriPrefix: 'servicenow://schemas/' }, { name: 'examples', description: 'Example implementations and sample data', basePath: (0, path_1.join)(projectRoot, 'src/templates/examples'), uriPrefix: 'servicenow://examples/' }, { name: 'help', description: 'Help content and guidance documents', basePath: (0, path_1.join)(projectRoot, 'src/sparc'), uriPrefix: 'servicenow://help/' } ]; } /** * Get project root directory */ getProjectRoot() { // Use process.cwd() to get the current working directory // This should be the project root when running the application return process.cwd(); } /** * List all available resources */ async listResources() { if (this.resourceIndex.size === 0) { await this.buildResourceIndex(); } return Array.from(this.resourceIndex.values()); } /** * Read a specific resource by URI */ async readResource(uri) { this.logger.debug(`Reading resource: ${uri}`); // Check cache first if (this.resourceCache.has(uri)) { this.logger.debug(`Resource found in cache: ${uri}`); return this.resourceCache.get(uri); } // Parse URI and determine file path const filePath = this.uriToFilePath(uri); if (!filePath) { throw new Error(`Invalid resource URI: ${uri}`); } try { const content = await this.loadResourceContent(filePath, uri); // Cache the content this.resourceCache.set(uri, content); return content; } catch (error) { this.logger.error(`Failed to read resource ${uri}:`, error); throw new Error(`Resource not found or inaccessible: ${uri}`); } } /** * Build comprehensive resource index */ async buildResourceIndex() { this.logger.info('Building resource index...'); for (const category of this.categories) { try { await this.indexCategory(category); } catch (error) { this.logger.warn(`Failed to index category ${category.name}:`, error); } } this.logger.info(`Resource index built: ${this.resourceIndex.size} resources`); } /** * Index resources in a specific category */ async indexCategory(category) { try { const stats = await (0, promises_1.stat)(category.basePath); if (!stats.isDirectory()) { this.logger.debug(`Category path is not a directory: ${category.basePath}`); return; } } catch (error) { this.logger.debug(`Category path does not exist: ${category.basePath}`); return; } if (category.name === 'documentation') { await this.indexDocumentationFiles(category); } else { await this.indexDirectoryRecursive(category.basePath, category); } } /** * Index documentation files (special handling for .md files in root) */ async indexDocumentationFiles(category) { try { const files = await (0, promises_1.readdir)(category.basePath); for (const file of files) { if (file.endsWith('.md') && file.toUpperCase().includes('SERVICENOW')) { const filePath = (0, path_1.join)(category.basePath, file); const uri = `${category.uriPrefix}${file}`; const resource = { uri, name: this.formatResourceName(file), description: `ServiceNow documentation: ${this.formatResourceName(file)}`, mimeType: this.getMimeType(file) }; this.resourceIndex.set(uri, resource); } } } catch (error) { this.logger.warn(`Failed to index documentation files:`, error); } } /** * Index directory recursively */ async indexDirectoryRecursive(dirPath, category, relativePath = '') { try { const entries = await (0, promises_1.readdir)(dirPath); for (const entry of entries) { const fullPath = (0, path_1.join)(dirPath, entry); const entryRelativePath = relativePath ? (0, path_1.join)(relativePath, entry) : entry; try { const stats = await (0, promises_1.stat)(fullPath); if (stats.isDirectory()) { await this.indexDirectoryRecursive(fullPath, category, entryRelativePath); } else if (this.isResourceFile(entry)) { const uri = `${category.uriPrefix}${entryRelativePath.replace(/\\/g, '/')}`; const resource = { uri, name: this.formatResourceName(entry), description: this.generateResourceDescription(entry, category.name), mimeType: this.getMimeType(entry) }; this.resourceIndex.set(uri, resource); } } catch (error) { this.logger.debug(`Failed to process ${fullPath}:`, error); } } } catch (error) { this.logger.warn(`Failed to read directory ${dirPath}:`, error); } } /** * Check if file should be exposed as a resource */ isResourceFile(filename) { const resourceExtensions = ['.json', '.md', '.yaml', '.yml', '.txt', '.ts', '.js']; const ext = (0, path_1.extname)(filename).toLowerCase(); return resourceExtensions.includes(ext); } /** * Convert URI to file path */ uriToFilePath(uri) { for (const category of this.categories) { if (uri.startsWith(category.uriPrefix)) { const relativePath = uri.substring(category.uriPrefix.length); if (category.name === 'documentation') { // Documentation files are in root return (0, path_1.join)(category.basePath, relativePath); } else { return (0, path_1.join)(category.basePath, relativePath); } } } return null; } /** * Load resource content from file */ async loadResourceContent(filePath, uri) { const content = await (0, promises_1.readFile)(filePath, 'utf-8'); const mimeType = this.getMimeType(filePath); return { uri, mimeType, text: content }; } /** * Get MIME type for file */ getMimeType(filePath) { const ext = (0, path_1.extname)(filePath).toLowerCase(); const mimeTypes = { '.json': 'application/json', '.md': 'text/markdown', '.yaml': 'application/yaml', '.yml': 'application/yaml', '.txt': 'text/plain', '.ts': 'text/typescript', '.js': 'text/javascript', '.html': 'text/html', '.css': 'text/css' }; return mimeTypes[ext] || 'text/plain'; } /** * Format resource name for display */ formatResourceName(filename) { const nameWithoutExt = (0, path_1.basename)(filename, (0, path_1.extname)(filename)); // Convert various naming conventions to readable names return nameWithoutExt .replace(/[-_]/g, ' ') .replace(/\b\w/g, l => l.toUpperCase()) .replace(/\.(template|schema|example)/i, ''); } /** * Generate resource description based on filename and category */ generateResourceDescription(filename, categoryName) { const name = this.formatResourceName(filename); const descriptions = { 'templates': `ServiceNow ${name} template`, 'documentation': `Documentation: ${name}`, 'schemas': `Validation schema for ${name}`, 'examples': `Example implementation: ${name}`, 'help': `Help content: ${name}` }; return descriptions[categoryName] || `Resource: ${name}`; } /** * Clear resource cache */ clearCache() { this.resourceCache.clear(); this.resourceIndex.clear(); this.logger.debug('Resource cache cleared'); } /** * Get resource statistics */ getResourceStats() { const categories = {}; for (const resource of this.resourceIndex.values()) { for (const category of this.categories) { if (resource.uri.startsWith(category.uriPrefix)) { categories[category.name] = (categories[category.name] || 0) + 1; break; } } } return { total: this.resourceIndex.size, cached: this.resourceCache.size, categories }; } } exports.MCPResourceManager = MCPResourceManager; //# sourceMappingURL=mcp-resource-manager.js.map