UNPKG

@utaba/ucm-mcp-server

Version:

Universal Context Manager MCP Server - AI-native artifact management

397 lines 16.7 kB
import { BaseToolController } from '../base/BaseToolController.js'; import { ValidationUtils } from '../../utils/ValidationUtils.js'; import { parsePath } from '../../utils/PathUtils.js'; export class GetArtifactController extends BaseToolController { constructor(ucmClient, logger) { super(ucmClient, logger); } get name() { return 'mcp_ucm_get_artifact'; } get description() { return 'Retrieve a specific UCM artifact with its content, metadata, and optional examples'; } get inputSchema() { return { type: 'object', properties: { path: { type: 'string', description: 'Full artifact path (e.g., "utaba/commands/create-user/typescript/1.0.0" or "utaba/commands/create-user/1.0.0")', minLength: 5, maxLength: 200 }, includeContent: { type: 'boolean', default: true, description: 'Include the artifact source content in the response' }, includeMetadata: { type: 'boolean', default: true, description: 'Include comprehensive metadata about the artifact' }, includeExamples: { type: 'boolean', default: false, description: 'Include usage examples and documentation' }, includeDependencies: { type: 'boolean', default: false, description: 'Include detailed dependency information' }, includeVersionHistory: { type: 'boolean', default: false, description: 'Include version history for this artifact' }, format: { type: 'string', enum: ['full', 'summary', 'content-only', 'metadata-only'], default: 'full', description: 'Response format level' } }, required: ['path'] }; } async handleExecute(params) { const { path, includeContent = true, includeMetadata = true, includeExamples = false, includeDependencies = false, includeVersionHistory = false, format = 'full' } = params; // Validate and sanitize the artifact path ValidationUtils.validateArtifactPath(path); this.logger.debug('GetArtifactController', `Retrieving artifact: ${path}`); try { // Parse the path to get component parts const parsed = parsePath(path); // Get the artifact from UCM API const artifact = await this.ucmClient.getArtifact(parsed.author, parsed.category, parsed.subcategory, parsed.filename, parsed.version); if (!artifact) { throw new Error(`Artifact not found at path: ${path}`); } // Build response based on requested format and includes const response = await this.buildArtifactResponse(artifact, { includeContent, includeMetadata, includeExamples, includeDependencies, includeVersionHistory, format }); this.logger.info('GetArtifactController', `Successfully retrieved artifact: ${path}`); return response; } catch (error) { this.logger.error('GetArtifactController', `Failed to retrieve artifact: ${path}`, '', error); throw error; } } async buildArtifactResponse(artifact, options) { const { includeContent, includeMetadata, includeExamples, includeDependencies, includeVersionHistory, format } = options; // Base response structure const response = { path: artifact.path, id: artifact.id }; // Handle different format types switch (format) { case 'content-only': if (artifact.content) { response.content = artifact.content; } response.contentType = this.detectContentType(artifact); break; case 'metadata-only': if (includeMetadata && artifact.metadata) { response.metadata = this.enrichMetadata(artifact.metadata); } break; case 'summary': response.name = artifact.metadata?.name || 'Unknown'; response.description = artifact.metadata?.description || ''; response.author = artifact.metadata?.author || ''; response.category = artifact.metadata?.category || ''; response.version = artifact.metadata?.version || ''; response.lastUpdated = artifact.lastUpdated; break; default: // 'full' // Include basic artifact information response.name = artifact.metadata?.name || 'Unknown'; response.author = artifact.metadata?.author || ''; response.category = artifact.metadata?.category || ''; response.subcategory = artifact.metadata?.subcategory || ''; response.technology = artifact.metadata?.technology || null; response.version = artifact.metadata?.version || ''; response.lastUpdated = artifact.lastUpdated; response.publishedAt = artifact.publishedAt; // Include content if requested if (includeContent && artifact.content) { response.content = artifact.content; response.contentType = this.detectContentType(artifact); response.contentSize = artifact.content.length; } // Include metadata if requested if (includeMetadata && artifact.metadata) { response.metadata = this.enrichMetadata(artifact.metadata); } // Include examples if requested if (includeExamples && artifact.examples) { response.examples = this.processExamples(artifact.examples); } // Include dependencies if requested if (includeDependencies && artifact.metadata?.dependencies) { response.dependencies = await this.enrichDependencies(artifact.metadata.dependencies); } // Include version history if requested if (includeVersionHistory) { response.versionHistory = await this.getVersionHistory(artifact.path); } // Add usage and statistics response.usage = { downloadCount: this.getDownloadCount(artifact), rating: this.calculateRating(artifact), lastAccessed: this.getLastAccessTime() }; // Add related artifacts response.related = await this.getRelatedArtifacts(artifact); break; } // Add retrieval metadata response.retrievalMetadata = { timestamp: new Date().toISOString(), format, includedSections: this.getIncludedSections(options), cacheable: true, sourceApi: 'UCM-v1' }; return response; } enrichMetadata(metadata) { return { ...metadata, // Add computed fields isLatestVersion: this.isLatestVersion(metadata.version), qualityScore: this.calculateQualityScore(metadata), complexity: this.estimateComplexity(metadata), maturityLevel: this.assessMaturityLevel(metadata), // Sanitize sensitive data dependencies: metadata.dependencies ? { ...metadata.dependencies, // Remove any potential secrets from dependency configs external: this.sanitizeExternalDependencies(metadata.dependencies.external) } : undefined }; } processExamples(examples) { return examples.map((example, index) => ({ id: index + 1, title: example.title || `Example ${index + 1}`, description: example.description || '', code: example.code || example.content || '', language: example.language || this.detectLanguage(example.code), complexity: example.complexity || 'intermediate', category: example.category || 'usage', runnable: example.runnable !== false, // Default to true dependencies: example.dependencies || [] })); } async enrichDependencies(dependencies) { const enriched = { services: dependencies.services || [], commands: dependencies.commands || [], external: dependencies.external || {} }; // Add dependency resolution information enriched.resolution = { resolvable: await this.checkDependencyResolution(dependencies), missing: await this.findMissingDependencies(dependencies), conflicts: await this.detectDependencyConflicts(dependencies) }; return enriched; } async getVersionHistory(artifactPath) { try { // Parse the path to get component parts const parsed = parsePath(artifactPath); const versions = await this.ucmClient.getArtifactVersions(parsed.author, parsed.category, parsed.subcategory); return versions.map(version => ({ version: version.metadata?.version || '', publishedAt: version.publishedAt, contractVersion: version.metadata?.contractVersion, changesSummary: this.generateChangesSummary(version), isBreaking: this.isBreakingChange(version), downloadCount: this.getDownloadCount(version) })); } catch (error) { this.logger.warn('GetArtifactController', 'Failed to get version history', '', error); return []; } } async getRelatedArtifacts(artifact) { try { // Find related artifacts based on category, author, and tags const searchTerms = [ artifact.metadata?.category, artifact.metadata?.subcategory, ...(artifact.metadata?.tags || []) ].filter(Boolean).join(' '); if (!searchTerms) return []; const related = await this.ucmClient.searchArtifacts({ category: artifact.metadata?.category, subcategory: artifact.metadata?.subcategory, limit: 5 }); // Filter out the current artifact and format results return related .filter(rel => rel.path !== artifact.path) .slice(0, 3) .map(rel => ({ name: rel.metadata?.name || 'Unknown', path: rel.path, category: rel.metadata?.category, description: rel.metadata?.description?.substring(0, 100) + '...' || '', similarity: this.calculateSimilarity(artifact, rel) })); } catch (error) { this.logger.debug('GetArtifactController', 'Could not fetch related artifacts', '', error); return []; } } detectContentType(artifact) { const path = artifact.path || ''; const content = artifact.content || ''; if (path.includes('/typescript/') || content.includes('interface ') || content.includes('export class')) { return 'typescript'; } if (path.includes('/javascript/') || content.includes('function ') || content.includes('const ')) { return 'javascript'; } if (path.includes('/python/') || content.includes('def ') || content.includes('import ')) { return 'python'; } if (content.includes('```') || content.includes('# ')) { return 'markdown'; } return 'text'; } detectLanguage(code) { if (!code) return 'text'; if (code.includes('interface ') || code.includes('export class')) return 'typescript'; if (code.includes('function ') || code.includes('const ')) return 'javascript'; if (code.includes('def ') || code.includes('import ')) return 'python'; if (code.includes('<?php')) return 'php'; if (code.includes('public class')) return 'java'; return 'text'; } getIncludedSections(options) { const sections = []; if (options.includeContent) sections.push('content'); if (options.includeMetadata) sections.push('metadata'); if (options.includeExamples) sections.push('examples'); if (options.includeDependencies) sections.push('dependencies'); if (options.includeVersionHistory) sections.push('versionHistory'); return sections; } // Helper methods for metadata enrichment isLatestVersion(_version) { // In a real implementation, this would check against the latest version return true; // Simplified } calculateQualityScore(metadata) { let score = 50; // Base score if (metadata.description && metadata.description.length > 20) score += 20; if (metadata.tags && metadata.tags.length > 0) score += 10; if (metadata.dependencies) score += 10; if (metadata.contractVersion) score += 10; return Math.min(score, 100); } estimateComplexity(metadata) { const depCount = (metadata.dependencies?.services?.length || 0) + (metadata.dependencies?.commands?.length || 0); if (depCount === 0) return 'simple'; if (depCount <= 3) return 'moderate'; return 'complex'; } assessMaturityLevel(metadata) { const version = metadata.version || '0.0.0'; const [major] = version.split('.'); if (parseInt(major) >= 1) return 'stable'; if (version.startsWith('0.')) return 'beta'; return 'alpha'; } sanitizeExternalDependencies(external) { if (!external) return {}; const sanitized = {}; for (const [key, value] of Object.entries(external)) { // Remove potential secrets from configuration if (typeof value === 'string' && (key.toLowerCase().includes('secret') || key.toLowerCase().includes('key'))) { sanitized[key] = '[REDACTED]'; } else { sanitized[key] = value; } } return sanitized; } // Simplified implementations for demo purposes async checkDependencyResolution(_dependencies) { return true; // Simplified } async findMissingDependencies(_dependencies) { return []; // Simplified } async detectDependencyConflicts(_dependencies) { return []; // Simplified } generateChangesSummary(_version) { return 'Changes not tracked'; // Simplified } isBreakingChange(_version) { return false; // Simplified } getDownloadCount(_artifact) { return Math.floor(Math.random() * 1000); // Simulated } calculateRating(_artifact) { return 3.0 + Math.random() * 2; // Simulated } getLastAccessTime() { return new Date().toISOString(); } calculateSimilarity(artifact1, artifact2) { // Simple similarity calculation based on shared tags and category let similarity = 0; if (artifact1.metadata?.category === artifact2.metadata?.category) { similarity += 0.4; } const tags1 = artifact1.metadata?.tags || []; const tags2 = artifact2.metadata?.tags || []; const sharedTags = tags1.filter((tag) => tags2.includes(tag)); similarity += (sharedTags.length / Math.max(tags1.length, tags2.length, 1)) * 0.6; return Math.round(similarity * 100) / 100; } } //# sourceMappingURL=GetArtifactController.js.map