@utaba/ucm-mcp-server
Version:
Universal Context Manager MCP Server - AI-native artifact management
131 lines • 5.66 kB
JavaScript
import { BaseToolController } from '../base/BaseToolController.js';
import { ValidationUtils } from '../../utils/ValidationUtils.js';
export class FindByPurposeController extends BaseToolController {
constructor(ucmClient, logger) {
super(ucmClient, logger);
}
get name() {
return 'mcp_ucm_find_by_purpose';
}
get description() {
return 'Find UCM artifacts by their intended purpose or use case using semantic search';
}
get inputSchema() {
return {
type: 'object',
properties: {
purpose: {
type: 'string',
description: 'Describe what you want to accomplish (e.g., "create user account", "send email notification")',
minLength: 3,
maxLength: 500
},
category: {
type: 'string',
enum: ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance'],
description: 'Filter results by artifact category'
},
technology: {
type: 'string',
description: 'Filter by technology stack (e.g., "typescript", "python", "nextjs")',
pattern: '^[a-zA-Z0-9\\-_]+$'
},
maxResults: {
type: 'number',
default: 10,
minimum: 1,
maximum: 50,
description: 'Maximum number of results to return'
}
},
required: ['purpose']
};
}
async handleExecute(params) {
const { purpose, category, technology, maxResults = 10 } = params;
// Sanitize the search query
const sanitizedPurpose = ValidationUtils.sanitizeSearchQuery(purpose);
// Validate optional category
if (category) {
ValidationUtils.validateCategory(category);
}
this.logger.debug('FindByPurposeController', `Searching for purpose: "${sanitizedPurpose}"`);
try {
// Build search filters
const filters = {};
if (category)
filters.category = category;
if (technology)
filters.technology = technology;
if (maxResults)
filters.limit = maxResults;
// Use semantic search through UCM API
const results = await this.ucmClient.searchArtifacts({
...filters,
// Note: Our current API doesn't support text search, using category filter instead
category: filters.category
});
// Transform results to include relevance scoring
const transformedResults = results.slice(0, maxResults).map((artifact, index) => ({
name: artifact.metadata?.name || 'Unknown',
path: artifact.path,
description: artifact.metadata?.description || '',
category: artifact.metadata?.category || '',
subcategory: artifact.metadata?.subcategory || '',
technology: artifact.metadata?.technology || 'technology-agnostic',
version: artifact.metadata?.version || '',
author: artifact.metadata?.author || '',
tags: artifact.metadata?.tags || [],
relevanceScore: this.calculateRelevanceScore(sanitizedPurpose, artifact, index),
lastUpdated: artifact.lastUpdated
}));
// Sort by relevance score (highest first)
transformedResults.sort((a, b) => b.relevanceScore - a.relevanceScore);
this.logger.info('FindByPurposeController', `Found ${transformedResults.length} artifacts for purpose search`);
return {
results: transformedResults,
query: {
purpose: sanitizedPurpose,
category,
technology,
maxResults
},
totalFound: results.length,
searchMetadata: {
searchType: 'purpose-based',
timestamp: new Date().toISOString(),
processingTimeMs: Date.now() - Date.now() // Simplified for demo
}
};
}
catch (error) {
this.logger.error('FindByPurposeController', 'Purpose search failed', '', error);
throw error;
}
}
calculateRelevanceScore(purpose, artifact, position) {
let score = 1.0 - (position * 0.1); // Base score decreases by position
const purposeLower = purpose.toLowerCase();
const description = (artifact.metadata?.description || '').toLowerCase();
const name = (artifact.metadata?.name || '').toLowerCase();
const tags = artifact.metadata?.tags || [];
// Boost score for exact matches in name
if (name.includes(purposeLower)) {
score += 0.5;
}
// Boost score for matches in description
if (description.includes(purposeLower)) {
score += 0.3;
}
// Boost score for tag matches
const purposeWords = purposeLower.split(' ');
for (const word of purposeWords) {
if (tags.some((tag) => tag.toLowerCase().includes(word))) {
score += 0.2;
}
}
// Ensure score stays within reasonable bounds
return Math.min(Math.max(score, 0.1), 1.0);
}
}
//# sourceMappingURL=FindByPurposeController.js.map