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