UNPKG

@utaba/ucm-mcp-server

Version:

Universal Context Manager MCP Server - AI-native artifact management

274 lines 11.9 kB
import { BaseToolController } from '../base/BaseToolController.js'; import { ValidationUtils } from '../../utils/ValidationUtils.js'; export class ListAuthorsController extends BaseToolController { constructor(ucmClient, logger) { super(ucmClient, logger); } get name() { return 'mcp_ucm_list_authors'; } get description() { return 'List all available authors in the UCM repository with optional statistics and filtering'; } get inputSchema() { return { type: 'object', properties: { includeStats: { type: 'boolean', default: true, description: 'Include statistics (artifact count, latest activity) for each author' }, sortBy: { type: 'string', enum: ['name', 'artifactCount', 'lastActivity', 'joinDate'], default: 'name', description: 'Sort authors by specified field' }, sortOrder: { type: 'string', enum: ['asc', 'desc'], default: 'asc', description: 'Sort order (ascending or descending)' }, filter: { type: 'object', properties: { namePattern: { type: 'string', description: 'Filter authors by name pattern (supports wildcards)', maxLength: 100 }, minArtifacts: { type: 'number', minimum: 0, description: 'Minimum number of artifacts the author must have' }, hasRecentActivity: { type: 'boolean', description: 'Filter authors with activity in the last 30 days' }, categories: { type: 'array', items: { type: 'string', enum: ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance'] }, description: 'Filter authors who have artifacts in these categories' } } }, pagination: { type: 'object', properties: { limit: { type: 'number', default: 50, minimum: 1, maximum: 200, description: 'Number of authors to return' }, offset: { type: 'number', default: 0, minimum: 0, description: 'Number of authors to skip' } } } }, required: [] }; } async handleExecute(params) { const { includeStats = true, sortBy = 'name', sortOrder = 'asc', filter = {}, pagination = {} } = params; const limit = pagination.limit || 50; const offset = pagination.offset || 0; ValidationUtils.validatePageParams(offset, limit); // Validate filter categories if provided if (filter.categories) { for (const category of filter.categories) { ValidationUtils.validateCategory(category); } } this.logger.debug('ListAuthorsController', `Listing authors with stats: ${includeStats}`); try { // Get authors from UCM API const authors = await this.ucmClient.getAuthors(); // Apply filtering let filteredAuthors = authors; if (filter.namePattern) { const pattern = filter.namePattern.toLowerCase().replace(/\*/g, '.*'); const regex = new RegExp(pattern); filteredAuthors = filteredAuthors.filter(author => regex.test(author.name.toLowerCase()) || regex.test(author.id.toLowerCase())); } // Transform and enrich author data const enrichedAuthors = await this.enrichAuthorData(filteredAuthors, includeStats, filter); // Apply additional filtering based on stats let finalAuthors = enrichedAuthors; if (filter.minArtifacts !== undefined) { finalAuthors = finalAuthors.filter(author => author.artifactCount >= filter.minArtifacts); } if (filter.hasRecentActivity) { const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); finalAuthors = finalAuthors.filter(author => author.lastActivity && new Date(author.lastActivity) > thirtyDaysAgo); } if (filter.categories && filter.categories.length > 0) { finalAuthors = finalAuthors.filter(author => author.categories && filter.categories.some((cat) => author.categories.includes(cat))); } // Sort authors const sortedAuthors = this.sortAuthors(finalAuthors, sortBy, sortOrder); // Apply pagination const paginatedAuthors = sortedAuthors.slice(offset, offset + limit); this.logger.info('ListAuthorsController', `Listed ${paginatedAuthors.length} authors`); return { authors: paginatedAuthors, pagination: { total: sortedAuthors.length, offset, limit, hasMore: offset + limit < sortedAuthors.length }, sorting: { sortBy, sortOrder }, appliedFilters: filter, metadata: { totalAuthorsInSystem: authors.length, filteredCount: finalAuthors.length, includeStats, timestamp: new Date().toISOString() } }; } catch (error) { this.logger.error('ListAuthorsController', 'Failed to list authors', '', error); throw error; } } async enrichAuthorData(authors, includeStats, filter) { const enrichedAuthors = []; for (const author of authors) { const enrichedAuthor = { id: author.id, name: author.name, email: author.email, joinDate: author.createdAt || author.joinDate, profileUrl: this.buildProfileUrl(author.id) }; if (includeStats) { try { // Get author's artifacts for statistics const authorData = await this.ucmClient.getAuthor(author.id); const artifacts = Array.isArray(authorData) ? authorData : []; enrichedAuthor.artifactCount = artifacts.length; enrichedAuthor.categories = this.extractCategories(artifacts); enrichedAuthor.technologies = this.extractTechnologies(artifacts); enrichedAuthor.lastActivity = this.getLastActivity(artifacts); enrichedAuthor.popularArtifacts = this.getPopularArtifacts(artifacts); enrichedAuthor.totalDownloads = this.calculateTotalDownloads(artifacts); enrichedAuthor.averageRating = this.calculateAverageRating(artifacts); } catch (error) { // If we can't get stats, continue with basic info this.logger.warn('ListAuthorsController', `Failed to get stats for author ${author.id}`, '', error); enrichedAuthor.artifactCount = 0; enrichedAuthor.categories = []; enrichedAuthor.technologies = []; enrichedAuthor.lastActivity = null; } } enrichedAuthors.push(enrichedAuthor); } return enrichedAuthors; } sortAuthors(authors, sortBy, sortOrder) { return authors.sort((a, b) => { let aVal, bVal; switch (sortBy) { case 'name': aVal = a.name.toLowerCase(); bVal = b.name.toLowerCase(); break; case 'artifactCount': aVal = a.artifactCount || 0; bVal = b.artifactCount || 0; break; case 'lastActivity': aVal = a.lastActivity ? new Date(a.lastActivity) : new Date(0); bVal = b.lastActivity ? new Date(b.lastActivity) : new Date(0); break; case 'joinDate': aVal = new Date(a.joinDate || 0); bVal = new Date(b.joinDate || 0); break; default: aVal = a.name.toLowerCase(); bVal = b.name.toLowerCase(); } if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1; if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1; return 0; }); } buildProfileUrl(authorId) { // Build URL to author's profile page return `/browse/authors/${authorId}`; } extractCategories(artifacts) { const categories = new Set(); artifacts.forEach(artifact => { if (artifact.metadata?.category) { categories.add(artifact.metadata.category); } }); return Array.from(categories); } extractTechnologies(artifacts) { const technologies = new Set(); artifacts.forEach(artifact => { if (artifact.metadata?.technology) { technologies.add(artifact.metadata.technology); } }); return Array.from(technologies); } getLastActivity(artifacts) { if (artifacts.length === 0) return null; const dates = artifacts .map(artifact => artifact.lastUpdated || artifact.publishedAt) .filter(date => date) .map(date => new Date(date)) .sort((a, b) => b.getTime() - a.getTime()); return dates.length > 0 ? dates[0].toISOString() : null; } getPopularArtifacts(artifacts) { // Return top 3 most "popular" artifacts (simplified calculation) return artifacts .map(artifact => ({ name: artifact.metadata?.name || 'Unknown', path: artifact.path, category: artifact.metadata?.category, downloads: Math.floor(Math.random() * 1000) // Simulated })) .sort((a, b) => b.downloads - a.downloads) .slice(0, 3); } calculateTotalDownloads(artifacts) { // Simulated total downloads calculation return artifacts.reduce((total, _) => total + Math.floor(Math.random() * 100), 0); } calculateAverageRating(artifacts) { if (artifacts.length === 0) return 0; // Simulated average rating calculation const totalRating = artifacts.reduce((sum, _) => sum + (3.0 + Math.random() * 2), 0); return Math.round((totalRating / artifacts.length) * 10) / 10; } } //# sourceMappingURL=ListAuthorsController.js.map