@utaba/ucm-mcp-server
Version:
Universal Context Manager MCP Server - AI Productivity Platform
226 lines • 9.75 kB
JavaScript
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