@utaba/ucm-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +79 -0
  3. package/dist/clients/UcmApiClient.d.ts +53 -0
  4. package/dist/clients/UcmApiClient.js +297 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.js +68 -0
  7. package/dist/interfaces/ILogger.d.ts +8 -0
  8. package/dist/interfaces/ILogger.js +4 -0
  9. package/dist/interfaces/IMcpTool.d.ts +7 -0
  10. package/dist/interfaces/IMcpTool.js +3 -0
  11. package/dist/logging/ConsoleLogger.d.ts +16 -0
  12. package/dist/logging/ConsoleLogger.js +45 -0
  13. package/dist/logging/LoggerFactory.d.ts +9 -0
  14. package/dist/logging/LoggerFactory.js +12 -0
  15. package/dist/server/McpConfig.d.ts +26 -0
  16. package/dist/server/McpConfig.js +93 -0
  17. package/dist/server/McpHandler.d.ts +12 -0
  18. package/dist/server/McpHandler.js +69 -0
  19. package/dist/server/McpServer.d.ts +15 -0
  20. package/dist/server/McpServer.js +49 -0
  21. package/dist/server/ToolRegistry.d.ts +22 -0
  22. package/dist/server/ToolRegistry.js +85 -0
  23. package/dist/tools/artifacts/GetArtifactController.d.ts +34 -0
  24. package/dist/tools/artifacts/GetArtifactController.js +397 -0
  25. package/dist/tools/artifacts/GetLatestController.d.ts +39 -0
  26. package/dist/tools/artifacts/GetLatestController.js +469 -0
  27. package/dist/tools/artifacts/ListVersionsController.d.ts +43 -0
  28. package/dist/tools/artifacts/ListVersionsController.js +530 -0
  29. package/dist/tools/artifacts/PublishArtifactController.d.ts +37 -0
  30. package/dist/tools/artifacts/PublishArtifactController.js +605 -0
  31. package/dist/tools/base/BaseToolController.d.ts +16 -0
  32. package/dist/tools/base/BaseToolController.js +32 -0
  33. package/dist/tools/core/DeleteArtifactTool.d.ts +11 -0
  34. package/dist/tools/core/DeleteArtifactTool.js +82 -0
  35. package/dist/tools/core/GetArtifactTool.d.ts +13 -0
  36. package/dist/tools/core/GetArtifactTool.js +125 -0
  37. package/dist/tools/core/GetArtifactVersionsTool.d.ts +11 -0
  38. package/dist/tools/core/GetArtifactVersionsTool.js +63 -0
  39. package/dist/tools/core/GetChunkTool.d.ts +11 -0
  40. package/dist/tools/core/GetChunkTool.js +56 -0
  41. package/dist/tools/core/ListArtifactsTool.d.ts +11 -0
  42. package/dist/tools/core/ListArtifactsTool.js +84 -0
  43. package/dist/tools/core/PublishArtifactFromFileTool.d.ts +15 -0
  44. package/dist/tools/core/PublishArtifactFromFileTool.js +256 -0
  45. package/dist/tools/core/PublishArtifactTool.d.ts +13 -0
  46. package/dist/tools/core/PublishArtifactTool.js +197 -0
  47. package/dist/tools/discovery/BrowseCategoriesController.d.ts +25 -0
  48. package/dist/tools/discovery/BrowseCategoriesController.js +400 -0
  49. package/dist/tools/discovery/FindByPurposeController.d.ts +12 -0
  50. package/dist/tools/discovery/FindByPurposeController.js +131 -0
  51. package/dist/tools/discovery/ListAuthorsController.d.ts +20 -0
  52. package/dist/tools/discovery/ListAuthorsController.js +274 -0
  53. package/dist/tools/discovery/SearchArtifactsController.d.ts +14 -0
  54. package/dist/tools/discovery/SearchArtifactsController.js +226 -0
  55. package/dist/tools/list/ListNamespaceController.d.ts +1 -0
  56. package/dist/tools/list/ListNamespaceController.js +8 -0
  57. package/dist/tools/navigation/ExploreNamespaceController.d.ts +35 -0
  58. package/dist/tools/navigation/ExploreNamespaceController.js +548 -0
  59. package/dist/tools/utility/AuthorIndexTool.d.ts +11 -0
  60. package/dist/tools/utility/AuthorIndexTool.js +48 -0
  61. package/dist/tools/utility/HealthCheckController.d.ts +11 -0
  62. package/dist/tools/utility/HealthCheckController.js +56 -0
  63. package/dist/tools/utility/ListRepositoriesTool.d.ts +11 -0
  64. package/dist/tools/utility/ListRepositoriesTool.js +70 -0
  65. package/dist/tools/utility/QuickstartTool.d.ts +11 -0
  66. package/dist/tools/utility/QuickstartTool.js +36 -0
  67. package/dist/tools/utility/ValidatePathController.d.ts +30 -0
  68. package/dist/tools/utility/ValidatePathController.js +465 -0
  69. package/dist/types/UcmApiTypes.d.ts +40 -0
  70. package/dist/types/UcmApiTypes.js +4 -0
  71. package/dist/utils/McpErrorHandler.d.ts +25 -0
  72. package/dist/utils/McpErrorHandler.js +67 -0
  73. package/dist/utils/PathUtils.d.ts +61 -0
  74. package/dist/utils/PathUtils.js +178 -0
  75. package/dist/utils/ResponseChunker.d.ts +25 -0
  76. package/dist/utils/ResponseChunker.js +79 -0
  77. package/dist/utils/ValidationUtils.d.ts +10 -0
  78. package/dist/utils/ValidationUtils.js +50 -0
  79. package/package.json +37 -0
  80. package/package.json.backup +37 -0
@@ -0,0 +1,400 @@
1
+ import { BaseToolController } from '../base/BaseToolController.js';
2
+ import { ValidationUtils } from '../../utils/ValidationUtils.js';
3
+ export class BrowseCategoriesController extends BaseToolController {
4
+ constructor(ucmClient, logger) {
5
+ super(ucmClient, logger);
6
+ }
7
+ get name() {
8
+ return 'mcp_ucm_browse_categories';
9
+ }
10
+ get description() {
11
+ return 'Browse available categories and subcategories in the UCM repository with hierarchical navigation';
12
+ }
13
+ get inputSchema() {
14
+ return {
15
+ type: 'object',
16
+ properties: {
17
+ author: {
18
+ type: 'string',
19
+ description: 'Filter categories by specific author ID',
20
+ pattern: '^[a-zA-Z0-9\\-_]+$'
21
+ },
22
+ category: {
23
+ type: 'string',
24
+ enum: ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance'],
25
+ description: 'Browse subcategories within a specific category'
26
+ },
27
+ includeCounts: {
28
+ type: 'boolean',
29
+ default: true,
30
+ description: 'Include artifact counts for each category/subcategory'
31
+ },
32
+ includePreview: {
33
+ type: 'boolean',
34
+ default: false,
35
+ description: 'Include preview of popular artifacts in each category'
36
+ },
37
+ showEmpty: {
38
+ type: 'boolean',
39
+ default: false,
40
+ description: 'Show categories/subcategories even if they have no artifacts'
41
+ },
42
+ groupBy: {
43
+ type: 'string',
44
+ enum: ['category', 'author', 'technology'],
45
+ default: 'category',
46
+ description: 'Group results by specified field'
47
+ },
48
+ sortBy: {
49
+ type: 'string',
50
+ enum: ['name', 'count', 'recent'],
51
+ default: 'name',
52
+ description: 'Sort categories by name, artifact count, or recent activity'
53
+ }
54
+ },
55
+ required: []
56
+ };
57
+ }
58
+ async handleExecute(params) {
59
+ const { author, category, includeCounts = true, includePreview = false, showEmpty = false, groupBy = 'category', sortBy = 'name' } = params;
60
+ // Validate inputs
61
+ if (author) {
62
+ ValidationUtils.validateAuthorId(author);
63
+ }
64
+ if (category) {
65
+ ValidationUtils.validateCategory(category);
66
+ }
67
+ this.logger.debug('BrowseCategoriesController', `Browsing categories, groupBy: ${groupBy}`);
68
+ try {
69
+ let categoriesData;
70
+ if (author) {
71
+ // Get categories for specific author
72
+ categoriesData = await this.getAuthorCategories(author);
73
+ }
74
+ else {
75
+ // Get all categories from all authors
76
+ categoriesData = await this.getAllCategories();
77
+ }
78
+ // Process and structure the data based on groupBy parameter
79
+ let structuredData;
80
+ switch (groupBy) {
81
+ case 'author':
82
+ structuredData = await this.groupByAuthor(categoriesData, category);
83
+ break;
84
+ case 'technology':
85
+ structuredData = await this.groupByTechnology(categoriesData, category);
86
+ break;
87
+ default: // 'category'
88
+ structuredData = await this.groupByCategory(categoriesData, category);
89
+ }
90
+ // Apply filtering and enhancement
91
+ const processedData = await this.processCategories(structuredData, { includeCounts, includePreview, showEmpty });
92
+ // Sort the results
93
+ const sortedData = this.sortCategories(processedData, sortBy);
94
+ this.logger.info('BrowseCategoriesController', `Found ${Object.keys(sortedData).length} category groups`);
95
+ return {
96
+ categories: sortedData,
97
+ metadata: {
98
+ groupBy,
99
+ sortBy,
100
+ author,
101
+ baseCategory: category,
102
+ includeCounts,
103
+ includePreview,
104
+ showEmpty,
105
+ totalGroups: Object.keys(sortedData).length,
106
+ timestamp: new Date().toISOString()
107
+ },
108
+ navigation: this.buildNavigationHints(category, author)
109
+ };
110
+ }
111
+ catch (error) {
112
+ this.logger.error('BrowseCategoriesController', 'Failed to browse categories', '', error);
113
+ throw error;
114
+ }
115
+ }
116
+ async getAuthorCategories(authorId) {
117
+ try {
118
+ const authorData = await this.ucmClient.getAuthor(authorId);
119
+ return Array.isArray(authorData) ? authorData : [];
120
+ }
121
+ catch (error) {
122
+ this.logger.warn('BrowseCategoriesController', `Failed to get author ${authorId} data`, '', error);
123
+ return [];
124
+ }
125
+ }
126
+ async getAllCategories() {
127
+ try {
128
+ // Get all authors first
129
+ const authors = await this.ucmClient.getAuthors();
130
+ const allArtifacts = [];
131
+ // Collect artifacts from all authors
132
+ for (const author of authors) {
133
+ try {
134
+ const authorArtifacts = await this.ucmClient.getAuthor(author.id);
135
+ if (Array.isArray(authorArtifacts)) {
136
+ allArtifacts.push(...authorArtifacts);
137
+ }
138
+ }
139
+ catch (error) {
140
+ // Continue if we can't get data for one author
141
+ this.logger.debug('BrowseCategoriesController', `Skipping author ${author.id}`, '', error);
142
+ }
143
+ }
144
+ return allArtifacts;
145
+ }
146
+ catch (error) {
147
+ this.logger.error('BrowseCategoriesController', 'Failed to get all categories', '', error);
148
+ return [];
149
+ }
150
+ }
151
+ async groupByCategory(artifacts, filterCategory) {
152
+ const categoryGroups = {};
153
+ for (const artifact of artifacts) {
154
+ const category = artifact.metadata?.category || 'uncategorized';
155
+ const subcategory = artifact.metadata?.subcategory || 'general';
156
+ // Apply category filter if specified
157
+ if (filterCategory && category !== filterCategory) {
158
+ continue;
159
+ }
160
+ if (!categoryGroups[category]) {
161
+ categoryGroups[category] = {
162
+ name: category,
163
+ description: this.getCategoryDescription(category),
164
+ subcategories: {},
165
+ artifacts: []
166
+ };
167
+ }
168
+ if (!categoryGroups[category].subcategories[subcategory]) {
169
+ categoryGroups[category].subcategories[subcategory] = {
170
+ name: subcategory,
171
+ artifacts: [],
172
+ technologies: new Set(),
173
+ authors: new Set()
174
+ };
175
+ }
176
+ categoryGroups[category].subcategories[subcategory].artifacts.push(artifact);
177
+ if (artifact.metadata?.technology) {
178
+ categoryGroups[category].subcategories[subcategory].technologies.add(artifact.metadata.technology);
179
+ }
180
+ if (artifact.metadata?.author) {
181
+ categoryGroups[category].subcategories[subcategory].authors.add(artifact.metadata.author);
182
+ }
183
+ }
184
+ // Convert sets to arrays
185
+ Object.values(categoryGroups).forEach((category) => {
186
+ Object.values(category.subcategories).forEach((subcategory) => {
187
+ subcategory.technologies = Array.from(subcategory.technologies);
188
+ subcategory.authors = Array.from(subcategory.authors);
189
+ });
190
+ });
191
+ return categoryGroups;
192
+ }
193
+ async groupByAuthor(artifacts, filterCategory) {
194
+ const authorGroups = {};
195
+ for (const artifact of artifacts) {
196
+ const author = artifact.metadata?.author || 'unknown';
197
+ const category = artifact.metadata?.category || 'uncategorized';
198
+ // Apply category filter if specified
199
+ if (filterCategory && category !== filterCategory) {
200
+ continue;
201
+ }
202
+ if (!authorGroups[author]) {
203
+ authorGroups[author] = {
204
+ name: author,
205
+ categories: {},
206
+ totalArtifacts: 0
207
+ };
208
+ }
209
+ if (!authorGroups[author].categories[category]) {
210
+ authorGroups[author].categories[category] = {
211
+ name: category,
212
+ artifacts: [],
213
+ subcategories: new Set()
214
+ };
215
+ }
216
+ authorGroups[author].categories[category].artifacts.push(artifact);
217
+ if (artifact.metadata?.subcategory) {
218
+ authorGroups[author].categories[category].subcategories.add(artifact.metadata.subcategory);
219
+ }
220
+ authorGroups[author].totalArtifacts++;
221
+ }
222
+ // Convert sets to arrays
223
+ Object.values(authorGroups).forEach((author) => {
224
+ Object.values(author.categories).forEach((category) => {
225
+ category.subcategories = Array.from(category.subcategories);
226
+ });
227
+ });
228
+ return authorGroups;
229
+ }
230
+ async groupByTechnology(artifacts, filterCategory) {
231
+ const technologyGroups = {};
232
+ for (const artifact of artifacts) {
233
+ const technology = artifact.metadata?.technology || 'technology-agnostic';
234
+ const category = artifact.metadata?.category || 'uncategorized';
235
+ // Apply category filter if specified
236
+ if (filterCategory && category !== filterCategory) {
237
+ continue;
238
+ }
239
+ if (!technologyGroups[technology]) {
240
+ technologyGroups[technology] = {
241
+ name: technology,
242
+ categories: {},
243
+ totalArtifacts: 0
244
+ };
245
+ }
246
+ if (!technologyGroups[technology].categories[category]) {
247
+ technologyGroups[technology].categories[category] = {
248
+ name: category,
249
+ artifacts: [],
250
+ authors: new Set()
251
+ };
252
+ }
253
+ technologyGroups[technology].categories[category].artifacts.push(artifact);
254
+ if (artifact.metadata?.author) {
255
+ technologyGroups[technology].categories[category].authors.add(artifact.metadata.author);
256
+ }
257
+ technologyGroups[technology].totalArtifacts++;
258
+ }
259
+ // Convert sets to arrays
260
+ Object.values(technologyGroups).forEach((tech) => {
261
+ Object.values(tech.categories).forEach((category) => {
262
+ category.authors = Array.from(category.authors);
263
+ });
264
+ });
265
+ return technologyGroups;
266
+ }
267
+ async processCategories(categoriesData, options) {
268
+ const { includeCounts, includePreview, showEmpty } = options;
269
+ const processedData = {};
270
+ for (const [groupKey, groupData] of Object.entries(categoriesData)) {
271
+ const processedGroup = { ...groupData };
272
+ if (includeCounts) {
273
+ this.addCounts(processedGroup);
274
+ }
275
+ if (includePreview) {
276
+ this.addPreviews(processedGroup);
277
+ }
278
+ // Filter empty categories if showEmpty is false
279
+ if (!showEmpty) {
280
+ this.filterEmptyCategories(processedGroup);
281
+ }
282
+ // Only add group if it has content or showEmpty is true
283
+ if (showEmpty || this.hasContent(processedGroup)) {
284
+ processedData[groupKey] = processedGroup;
285
+ }
286
+ }
287
+ return processedData;
288
+ }
289
+ addCounts(groupData) {
290
+ if (groupData.subcategories) {
291
+ // Category-based grouping
292
+ for (const subcategory of Object.values(groupData.subcategories)) {
293
+ subcategory.count = subcategory.artifacts?.length || 0;
294
+ }
295
+ groupData.totalCount = Object.values(groupData.subcategories)
296
+ .reduce((sum, sub) => sum + (sub.count || 0), 0);
297
+ }
298
+ else if (groupData.categories) {
299
+ // Author or technology-based grouping
300
+ for (const category of Object.values(groupData.categories)) {
301
+ category.count = category.artifacts?.length || 0;
302
+ }
303
+ groupData.totalCount = groupData.totalArtifacts || 0;
304
+ }
305
+ }
306
+ addPreviews(groupData) {
307
+ if (groupData.subcategories) {
308
+ for (const subcategory of Object.values(groupData.subcategories)) {
309
+ subcategory.preview = this.getPreviewArtifacts(subcategory.artifacts || []);
310
+ }
311
+ }
312
+ else if (groupData.categories) {
313
+ for (const category of Object.values(groupData.categories)) {
314
+ category.preview = this.getPreviewArtifacts(category.artifacts || []);
315
+ }
316
+ }
317
+ }
318
+ filterEmptyCategories(groupData) {
319
+ if (groupData.subcategories) {
320
+ for (const [key, subcategory] of Object.entries(groupData.subcategories)) {
321
+ if (!subcategory.artifacts || subcategory.artifacts.length === 0) {
322
+ delete groupData.subcategories[key];
323
+ }
324
+ }
325
+ }
326
+ else if (groupData.categories) {
327
+ for (const [key, category] of Object.entries(groupData.categories)) {
328
+ if (!category.artifacts || category.artifacts.length === 0) {
329
+ delete groupData.categories[key];
330
+ }
331
+ }
332
+ }
333
+ }
334
+ hasContent(groupData) {
335
+ if (groupData.subcategories) {
336
+ return Object.keys(groupData.subcategories).length > 0;
337
+ }
338
+ if (groupData.categories) {
339
+ return Object.keys(groupData.categories).length > 0;
340
+ }
341
+ return groupData.totalArtifacts > 0;
342
+ }
343
+ getPreviewArtifacts(artifacts) {
344
+ return artifacts
345
+ .slice(0, 3)
346
+ .map(artifact => ({
347
+ name: artifact.metadata?.name || 'Unknown',
348
+ path: artifact.path,
349
+ description: artifact.metadata?.description?.substring(0, 100) + '...' || '',
350
+ version: artifact.metadata?.version || ''
351
+ }));
352
+ }
353
+ sortCategories(categoriesData, sortBy) {
354
+ const sortedEntries = Object.entries(categoriesData).sort(([, a], [, b]) => {
355
+ switch (sortBy) {
356
+ case 'count':
357
+ return (b.totalCount || 0) - (a.totalCount || 0);
358
+ case 'recent':
359
+ // Sort by most recent activity (simplified)
360
+ return 0; // Would implement based on lastUpdated timestamps
361
+ default: // 'name'
362
+ return a.name.localeCompare(b.name);
363
+ }
364
+ });
365
+ return Object.fromEntries(sortedEntries);
366
+ }
367
+ getCategoryDescription(category) {
368
+ const descriptions = {
369
+ commands: 'Executable micro-block commands that perform specific operations',
370
+ services: 'Service implementations and interfaces for business logic',
371
+ patterns: 'Reusable architectural and design patterns',
372
+ implementations: 'Complete implementations of specific features or systems',
373
+ contracts: 'Interface contracts and type definitions',
374
+ guidance: 'Documentation, best practices, and implementation guides'
375
+ };
376
+ return descriptions[category] || 'Miscellaneous artifacts';
377
+ }
378
+ buildNavigationHints(category, author) {
379
+ const hints = {
380
+ availableActions: [
381
+ 'Use mcp_ucm_get_artifact to view specific artifacts',
382
+ 'Use mcp_ucm_search_artifacts to search within categories',
383
+ 'Use mcp_ucm_explore_namespace for detailed browsing'
384
+ ]
385
+ };
386
+ if (category) {
387
+ hints.currentLevel = `Category: ${category}`;
388
+ hints.canNavigateTo = ['subcategories', 'artifacts'];
389
+ }
390
+ else {
391
+ hints.currentLevel = 'All categories';
392
+ hints.canNavigateTo = ['specific categories', 'authors', 'technologies'];
393
+ }
394
+ if (author) {
395
+ hints.authorContext = `Filtered by author: ${author}`;
396
+ }
397
+ return hints;
398
+ }
399
+ }
400
+ //# sourceMappingURL=BrowseCategoriesController.js.map
@@ -0,0 +1,12 @@
1
+ import { BaseToolController } from '../base/BaseToolController.js';
2
+ import { UcmApiClient } from '../../clients/UcmApiClient.js';
3
+ import { ILogger } from '../../interfaces/ILogger.js';
4
+ export declare class FindByPurposeController extends BaseToolController {
5
+ constructor(ucmClient: UcmApiClient, logger: ILogger);
6
+ get name(): string;
7
+ get description(): string;
8
+ get inputSchema(): any;
9
+ protected handleExecute(params: any): Promise<any>;
10
+ private calculateRelevanceScore;
11
+ }
12
+ //# sourceMappingURL=FindByPurposeController.d.ts.map
@@ -0,0 +1,131 @@
1
+ import { BaseToolController } from '../base/BaseToolController.js';
2
+ import { ValidationUtils } from '../../utils/ValidationUtils.js';
3
+ export class FindByPurposeController extends BaseToolController {
4
+ constructor(ucmClient, logger) {
5
+ super(ucmClient, logger);
6
+ }
7
+ get name() {
8
+ return 'mcp_ucm_find_by_purpose';
9
+ }
10
+ get description() {
11
+ return 'Find UCM artifacts by their intended purpose or use case using semantic search';
12
+ }
13
+ get inputSchema() {
14
+ return {
15
+ type: 'object',
16
+ properties: {
17
+ purpose: {
18
+ type: 'string',
19
+ description: 'Describe what you want to accomplish (e.g., "create user account", "send email notification")',
20
+ minLength: 3,
21
+ maxLength: 500
22
+ },
23
+ category: {
24
+ type: 'string',
25
+ enum: ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance'],
26
+ description: 'Filter results by artifact category'
27
+ },
28
+ technology: {
29
+ type: 'string',
30
+ description: 'Filter by technology stack (e.g., "typescript", "python", "nextjs")',
31
+ pattern: '^[a-zA-Z0-9\\-_]+$'
32
+ },
33
+ maxResults: {
34
+ type: 'number',
35
+ default: 10,
36
+ minimum: 1,
37
+ maximum: 50,
38
+ description: 'Maximum number of results to return'
39
+ }
40
+ },
41
+ required: ['purpose']
42
+ };
43
+ }
44
+ async handleExecute(params) {
45
+ const { purpose, category, technology, maxResults = 10 } = params;
46
+ // Sanitize the search query
47
+ const sanitizedPurpose = ValidationUtils.sanitizeSearchQuery(purpose);
48
+ // Validate optional category
49
+ if (category) {
50
+ ValidationUtils.validateCategory(category);
51
+ }
52
+ this.logger.debug('FindByPurposeController', `Searching for purpose: "${sanitizedPurpose}"`);
53
+ try {
54
+ // Build search filters
55
+ const filters = {};
56
+ if (category)
57
+ filters.category = category;
58
+ if (technology)
59
+ filters.technology = technology;
60
+ if (maxResults)
61
+ filters.limit = maxResults;
62
+ // Use semantic search through UCM API
63
+ const results = await this.ucmClient.searchArtifacts({
64
+ ...filters,
65
+ // Note: Our current API doesn't support text search, using category filter instead
66
+ category: filters.category
67
+ });
68
+ // Transform results to include relevance scoring
69
+ const transformedResults = results.slice(0, maxResults).map((artifact, index) => ({
70
+ name: artifact.metadata?.name || 'Unknown',
71
+ path: artifact.path,
72
+ description: artifact.metadata?.description || '',
73
+ category: artifact.metadata?.category || '',
74
+ subcategory: artifact.metadata?.subcategory || '',
75
+ technology: artifact.metadata?.technology || 'technology-agnostic',
76
+ version: artifact.metadata?.version || '',
77
+ author: artifact.metadata?.author || '',
78
+ tags: artifact.metadata?.tags || [],
79
+ relevanceScore: this.calculateRelevanceScore(sanitizedPurpose, artifact, index),
80
+ lastUpdated: artifact.lastUpdated
81
+ }));
82
+ // Sort by relevance score (highest first)
83
+ transformedResults.sort((a, b) => b.relevanceScore - a.relevanceScore);
84
+ this.logger.info('FindByPurposeController', `Found ${transformedResults.length} artifacts for purpose search`);
85
+ return {
86
+ results: transformedResults,
87
+ query: {
88
+ purpose: sanitizedPurpose,
89
+ category,
90
+ technology,
91
+ maxResults
92
+ },
93
+ totalFound: results.length,
94
+ searchMetadata: {
95
+ searchType: 'purpose-based',
96
+ timestamp: new Date().toISOString(),
97
+ processingTimeMs: Date.now() - Date.now() // Simplified for demo
98
+ }
99
+ };
100
+ }
101
+ catch (error) {
102
+ this.logger.error('FindByPurposeController', 'Purpose search failed', '', error);
103
+ throw error;
104
+ }
105
+ }
106
+ calculateRelevanceScore(purpose, artifact, position) {
107
+ let score = 1.0 - (position * 0.1); // Base score decreases by position
108
+ const purposeLower = purpose.toLowerCase();
109
+ const description = (artifact.metadata?.description || '').toLowerCase();
110
+ const name = (artifact.metadata?.name || '').toLowerCase();
111
+ const tags = artifact.metadata?.tags || [];
112
+ // Boost score for exact matches in name
113
+ if (name.includes(purposeLower)) {
114
+ score += 0.5;
115
+ }
116
+ // Boost score for matches in description
117
+ if (description.includes(purposeLower)) {
118
+ score += 0.3;
119
+ }
120
+ // Boost score for tag matches
121
+ const purposeWords = purposeLower.split(' ');
122
+ for (const word of purposeWords) {
123
+ if (tags.some((tag) => tag.toLowerCase().includes(word))) {
124
+ score += 0.2;
125
+ }
126
+ }
127
+ // Ensure score stays within reasonable bounds
128
+ return Math.min(Math.max(score, 0.1), 1.0);
129
+ }
130
+ }
131
+ //# sourceMappingURL=FindByPurposeController.js.map
@@ -0,0 +1,20 @@
1
+ import { BaseToolController } from '../base/BaseToolController.js';
2
+ import { UcmApiClient } from '../../clients/UcmApiClient.js';
3
+ import { ILogger } from '../../interfaces/ILogger.js';
4
+ export declare class ListAuthorsController extends BaseToolController {
5
+ constructor(ucmClient: UcmApiClient, logger: ILogger);
6
+ get name(): string;
7
+ get description(): string;
8
+ get inputSchema(): any;
9
+ protected handleExecute(params: any): Promise<any>;
10
+ private enrichAuthorData;
11
+ private sortAuthors;
12
+ private buildProfileUrl;
13
+ private extractCategories;
14
+ private extractTechnologies;
15
+ private getLastActivity;
16
+ private getPopularArtifacts;
17
+ private calculateTotalDownloads;
18
+ private calculateAverageRating;
19
+ }
20
+ //# sourceMappingURL=ListAuthorsController.d.ts.map