@utaba/ucm-mcp-server
Version:
Universal Context Manager MCP Server - AI-native artifact management
397 lines • 16.7 kB
JavaScript
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