UNPKG

@utaba/ucm-mcp-server

Version:

Universal Context Manager MCP Server - AI Productivity Platform

226 lines 9.75 kB
import { BaseToolController } from '../base/BaseToolController.js'; import { ValidationUtils } from '../../utils/ValidationUtils.js'; export class SearchArtifactsController extends BaseToolController { constructor(ucmClient, logger) { super(ucmClient, logger); } get name() { return 'mcp_ucm_search_artifacts'; } get description() { return 'Search UCM artifacts by name, description, tags, or metadata with advanced filtering options'; } get inputSchema() { return { type: 'object', properties: { query: { type: 'string', description: 'Search query to match against artifact names, descriptions, and tags', minLength: 1, maxLength: 200 }, filters: { type: 'object', properties: { author: { type: 'string', description: 'Filter by author ID', pattern: '^[a-zA-Z0-9\\-_]+$' }, category: { type: 'string', enum: ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance'], description: 'Filter by artifact category' }, technology: { type: 'string', description: 'Filter by technology (e.g., typescript, python)', pattern: '^[a-zA-Z0-9\\-_]+$' }, minRating: { type: 'number', minimum: 0, maximum: 5, description: 'Minimum rating score' }, contractVersion: { type: 'string', pattern: '^[0-9]+\\.[0-9]+$', description: 'Filter by contract version (e.g., "1.0")' }, hasExamples: { type: 'boolean', description: 'Filter artifacts that have usage examples' }, updatedSince: { type: 'string', format: 'date', description: 'Filter artifacts updated since this date (YYYY-MM-DD)' } } }, pagination: { type: 'object', properties: { limit: { type: 'number', default: 20, minimum: 1, maximum: 100, description: 'Number of results to return' }, offset: { type: 'number', default: 0, minimum: 0, description: 'Number of results to skip' } } }, sortBy: { type: 'string', enum: ['relevance', 'name', 'author', 'updated', 'created', 'version'], default: 'relevance', description: 'Sort results by specified field' }, sortOrder: { type: 'string', enum: ['asc', 'desc'], default: 'desc', description: 'Sort order (ascending or descending)' } }, required: ['query'] }; } async handleExecute(params) { const { query, filters = {}, pagination = {}, sortBy = 'relevance', sortOrder = 'desc' } = params; // Validate and sanitize inputs const sanitizedQuery = ValidationUtils.sanitizeSearchQuery(query); const limit = pagination.limit || 20; const offset = pagination.offset || 0; ValidationUtils.validatePageParams(offset, limit); // Validate optional filters if (filters.author) { ValidationUtils.validateAuthorId(filters.author); } if (filters.category) { ValidationUtils.validateCategory(filters.category); } if (filters.contractVersion && !filters.contractVersion.match(/^[0-9]+\.[0-9]+$/)) { throw this.formatError(new Error('Contract version must be in format X.Y')); } this.logger.debug('SearchArtifactsController', `Searching artifacts: "${sanitizedQuery}"`); try { // Build comprehensive search filters const searchFilters = { ...filters, limit, offset, sortBy, sortOrder }; // Execute search through UCM API const results = await this.ucmClient.searchArtifacts({ ...searchFilters, // Note: Our current API doesn't support text search, using category filter instead }); // Transform and enrich results const transformedArtifacts = results.map(artifact => ({ id: artifact.id, name: artifact.metadata?.name || 'Unknown', path: artifact.path, description: artifact.metadata?.description || '', author: artifact.metadata?.author || '', category: artifact.metadata?.category || '', subcategory: artifact.metadata?.subcategory || '', technology: artifact.metadata?.technology || null, version: artifact.metadata?.version || '', contractVersion: artifact.metadata?.contractVersion || null, tags: artifact.metadata?.tags || [], dependencies: artifact.metadata?.dependencies || {}, hasExamples: !!(artifact.examples && artifact.examples.length > 0), lastUpdated: artifact.lastUpdated, publishedAt: artifact.publishedAt, downloadCount: this.getDownloadCount(artifact), // Simulated for demo rating: this.calculateRating(artifact) // Simulated for demo })); // Apply client-side sorting if needed (fallback) const sortedArtifacts = this.applySorting(transformedArtifacts, sortBy, sortOrder); this.logger.info('SearchArtifactsController', `Found ${sortedArtifacts.length} artifacts`); return { artifacts: sortedArtifacts, pagination: { total: results.length, // In real implementation, this would come from API offset, limit, hasMore: results.length === limit }, searchQuery: sanitizedQuery, appliedFilters: filters, sorting: { sortBy, sortOrder }, searchMetadata: { searchType: 'general-search', timestamp: new Date().toISOString(), executionTimeMs: 0 // Would be measured in real implementation } }; } catch (error) { this.logger.error('SearchArtifactsController', 'Artifact search failed', '', error); throw error; } } applySorting(artifacts, sortBy, sortOrder) { return artifacts.sort((a, b) => { let aVal, bVal; switch (sortBy) { case 'name': aVal = a.name.toLowerCase(); bVal = b.name.toLowerCase(); break; case 'author': aVal = a.author.toLowerCase(); bVal = b.author.toLowerCase(); break; case 'updated': aVal = new Date(a.lastUpdated); bVal = new Date(b.lastUpdated); break; case 'version': aVal = a.version; bVal = b.version; break; case 'rating': aVal = a.rating; bVal = b.rating; break; default: // relevance aVal = a.rating || 0; bVal = b.rating || 0; } if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1; if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1; return 0; }); } getDownloadCount(artifact) { // Simulated download count - in real implementation this would come from database return Math.floor(Math.random() * 1000); } calculateRating(artifact) { // Simulated rating calculation - in real implementation this would be based on user reviews const baseRating = 3.0; const hasDescription = artifact.metadata?.description ? 0.5 : 0; const hasExamples = artifact.examples?.length > 0 ? 0.3 : 0; const hasTags = artifact.metadata?.tags?.length > 0 ? 0.2 : 0; return Math.min(baseRating + hasDescription + hasExamples + hasTags, 5.0); } } //# sourceMappingURL=SearchArtifactsController.js.map