@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.
- package/LICENSE +29 -0
- package/README.md +79 -0
- package/dist/clients/UcmApiClient.d.ts +53 -0
- package/dist/clients/UcmApiClient.js +297 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +68 -0
- package/dist/interfaces/ILogger.d.ts +8 -0
- package/dist/interfaces/ILogger.js +4 -0
- package/dist/interfaces/IMcpTool.d.ts +7 -0
- package/dist/interfaces/IMcpTool.js +3 -0
- package/dist/logging/ConsoleLogger.d.ts +16 -0
- package/dist/logging/ConsoleLogger.js +45 -0
- package/dist/logging/LoggerFactory.d.ts +9 -0
- package/dist/logging/LoggerFactory.js +12 -0
- package/dist/server/McpConfig.d.ts +26 -0
- package/dist/server/McpConfig.js +93 -0
- package/dist/server/McpHandler.d.ts +12 -0
- package/dist/server/McpHandler.js +69 -0
- package/dist/server/McpServer.d.ts +15 -0
- package/dist/server/McpServer.js +49 -0
- package/dist/server/ToolRegistry.d.ts +22 -0
- package/dist/server/ToolRegistry.js +85 -0
- package/dist/tools/artifacts/GetArtifactController.d.ts +34 -0
- package/dist/tools/artifacts/GetArtifactController.js +397 -0
- package/dist/tools/artifacts/GetLatestController.d.ts +39 -0
- package/dist/tools/artifacts/GetLatestController.js +469 -0
- package/dist/tools/artifacts/ListVersionsController.d.ts +43 -0
- package/dist/tools/artifacts/ListVersionsController.js +530 -0
- package/dist/tools/artifacts/PublishArtifactController.d.ts +37 -0
- package/dist/tools/artifacts/PublishArtifactController.js +605 -0
- package/dist/tools/base/BaseToolController.d.ts +16 -0
- package/dist/tools/base/BaseToolController.js +32 -0
- package/dist/tools/core/DeleteArtifactTool.d.ts +11 -0
- package/dist/tools/core/DeleteArtifactTool.js +82 -0
- package/dist/tools/core/GetArtifactTool.d.ts +13 -0
- package/dist/tools/core/GetArtifactTool.js +125 -0
- package/dist/tools/core/GetArtifactVersionsTool.d.ts +11 -0
- package/dist/tools/core/GetArtifactVersionsTool.js +63 -0
- package/dist/tools/core/GetChunkTool.d.ts +11 -0
- package/dist/tools/core/GetChunkTool.js +56 -0
- package/dist/tools/core/ListArtifactsTool.d.ts +11 -0
- package/dist/tools/core/ListArtifactsTool.js +84 -0
- package/dist/tools/core/PublishArtifactFromFileTool.d.ts +15 -0
- package/dist/tools/core/PublishArtifactFromFileTool.js +256 -0
- package/dist/tools/core/PublishArtifactTool.d.ts +13 -0
- package/dist/tools/core/PublishArtifactTool.js +197 -0
- package/dist/tools/discovery/BrowseCategoriesController.d.ts +25 -0
- package/dist/tools/discovery/BrowseCategoriesController.js +400 -0
- package/dist/tools/discovery/FindByPurposeController.d.ts +12 -0
- package/dist/tools/discovery/FindByPurposeController.js +131 -0
- package/dist/tools/discovery/ListAuthorsController.d.ts +20 -0
- package/dist/tools/discovery/ListAuthorsController.js +274 -0
- package/dist/tools/discovery/SearchArtifactsController.d.ts +14 -0
- package/dist/tools/discovery/SearchArtifactsController.js +226 -0
- package/dist/tools/list/ListNamespaceController.d.ts +1 -0
- package/dist/tools/list/ListNamespaceController.js +8 -0
- package/dist/tools/navigation/ExploreNamespaceController.d.ts +35 -0
- package/dist/tools/navigation/ExploreNamespaceController.js +548 -0
- package/dist/tools/utility/AuthorIndexTool.d.ts +11 -0
- package/dist/tools/utility/AuthorIndexTool.js +48 -0
- package/dist/tools/utility/HealthCheckController.d.ts +11 -0
- package/dist/tools/utility/HealthCheckController.js +56 -0
- package/dist/tools/utility/ListRepositoriesTool.d.ts +11 -0
- package/dist/tools/utility/ListRepositoriesTool.js +70 -0
- package/dist/tools/utility/QuickstartTool.d.ts +11 -0
- package/dist/tools/utility/QuickstartTool.js +36 -0
- package/dist/tools/utility/ValidatePathController.d.ts +30 -0
- package/dist/tools/utility/ValidatePathController.js +465 -0
- package/dist/types/UcmApiTypes.d.ts +40 -0
- package/dist/types/UcmApiTypes.js +4 -0
- package/dist/utils/McpErrorHandler.d.ts +25 -0
- package/dist/utils/McpErrorHandler.js +67 -0
- package/dist/utils/PathUtils.d.ts +61 -0
- package/dist/utils/PathUtils.js +178 -0
- package/dist/utils/ResponseChunker.d.ts +25 -0
- package/dist/utils/ResponseChunker.js +79 -0
- package/dist/utils/ValidationUtils.d.ts +10 -0
- package/dist/utils/ValidationUtils.js +50 -0
- package/package.json +37 -0
- 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
|