@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,548 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { ValidationUtils } from '../../utils/ValidationUtils.js';
|
|
3
|
+
export class ExploreNamespaceController extends BaseToolController {
|
|
4
|
+
constructor(ucmClient, logger) {
|
|
5
|
+
super(ucmClient, logger);
|
|
6
|
+
}
|
|
7
|
+
get name() {
|
|
8
|
+
return 'mcp_ucm_explore_namespace';
|
|
9
|
+
}
|
|
10
|
+
get description() {
|
|
11
|
+
return 'Explore the hierarchical namespace structure of UCM artifacts with interactive navigation';
|
|
12
|
+
}
|
|
13
|
+
get inputSchema() {
|
|
14
|
+
return {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
path: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Namespace path to explore (e.g., "utaba", "utaba/commands", "utaba/commands/user"). Leave empty for root exploration.',
|
|
20
|
+
default: '',
|
|
21
|
+
maxLength: 150
|
|
22
|
+
},
|
|
23
|
+
depth: {
|
|
24
|
+
type: 'number',
|
|
25
|
+
minimum: 1,
|
|
26
|
+
maximum: 5,
|
|
27
|
+
default: 2,
|
|
28
|
+
description: 'How deep to explore the namespace hierarchy'
|
|
29
|
+
},
|
|
30
|
+
includeContent: {
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
default: false,
|
|
33
|
+
description: 'Include content previews for artifacts found'
|
|
34
|
+
},
|
|
35
|
+
includeStats: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
default: true,
|
|
38
|
+
description: 'Include statistics for each namespace level'
|
|
39
|
+
},
|
|
40
|
+
sortBy: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
enum: ['name', 'count', 'recent', 'popular'],
|
|
43
|
+
default: 'name',
|
|
44
|
+
description: 'Sort namespaces by specified criteria'
|
|
45
|
+
},
|
|
46
|
+
filterBy: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
technology: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'Filter by technology (e.g., "typescript", "python")'
|
|
52
|
+
},
|
|
53
|
+
category: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
enum: ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance'],
|
|
56
|
+
description: 'Filter by artifact category'
|
|
57
|
+
},
|
|
58
|
+
hasArtifacts: {
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
description: 'Only show namespaces that contain artifacts'
|
|
61
|
+
},
|
|
62
|
+
updatedSince: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
format: 'date',
|
|
65
|
+
description: 'Only show namespaces with artifacts updated since this date'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
expandPaths: {
|
|
70
|
+
type: 'array',
|
|
71
|
+
items: { type: 'string' },
|
|
72
|
+
description: 'Specific paths to expand regardless of depth limit'
|
|
73
|
+
},
|
|
74
|
+
includeNavigationHints: {
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
default: true,
|
|
77
|
+
description: 'Include hints for further navigation'
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
required: []
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async handleExecute(params) {
|
|
84
|
+
const { path = '', depth = 2, includeContent = false, includeStats = true, sortBy = 'name', filterBy = {}, expandPaths = [], includeNavigationHints = true } = params;
|
|
85
|
+
// Validate inputs
|
|
86
|
+
if (path && !this.isValidNamespacePath(path)) {
|
|
87
|
+
throw this.formatError(new Error('Invalid namespace path format'));
|
|
88
|
+
}
|
|
89
|
+
if (filterBy.category) {
|
|
90
|
+
ValidationUtils.validateCategory(filterBy.category);
|
|
91
|
+
}
|
|
92
|
+
this.logger.debug('ExploreNamespaceController', `Exploring namespace: "${path}" with depth ${depth}`);
|
|
93
|
+
try {
|
|
94
|
+
// Start exploration from the specified path
|
|
95
|
+
const explorationResult = await this.exploreNamespace(path, depth, { includeContent, includeStats, sortBy, filterBy, expandPaths });
|
|
96
|
+
// Build comprehensive response
|
|
97
|
+
const response = {
|
|
98
|
+
exploration: explorationResult,
|
|
99
|
+
currentPath: path,
|
|
100
|
+
explorationOptions: {
|
|
101
|
+
depth,
|
|
102
|
+
includeContent,
|
|
103
|
+
includeStats,
|
|
104
|
+
sortBy,
|
|
105
|
+
appliedFilters: filterBy
|
|
106
|
+
},
|
|
107
|
+
navigation: includeNavigationHints ? this.generateNavigationHints(explorationResult) : null,
|
|
108
|
+
breadcrumbs: this.buildBreadcrumbs(path),
|
|
109
|
+
metadata: {
|
|
110
|
+
timestamp: new Date().toISOString(),
|
|
111
|
+
totalNodesExplored: this.countTotalNodes(explorationResult),
|
|
112
|
+
maxDepthReached: this.calculateMaxDepth(explorationResult),
|
|
113
|
+
hasMoreContent: this.hasMoreContent(explorationResult, depth)
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
this.logger.info('ExploreNamespaceController', `Explored namespace with ${response.metadata.totalNodesExplored} nodes`);
|
|
117
|
+
return response;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.logger.error('ExploreNamespaceController', `Failed to explore namespace: ${path}`, '', error);
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async exploreNamespace(path, remainingDepth, options) {
|
|
125
|
+
if (remainingDepth <= 0 && !options.expandPaths.includes(path)) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const node = {
|
|
129
|
+
path,
|
|
130
|
+
name: this.getNameFromPath(path),
|
|
131
|
+
type: this.determineNodeType(path),
|
|
132
|
+
level: this.calculateLevel(path),
|
|
133
|
+
children: [],
|
|
134
|
+
artifacts: []
|
|
135
|
+
};
|
|
136
|
+
try {
|
|
137
|
+
// Get direct content at this level
|
|
138
|
+
const directContent = await this.getDirectContent(path, options.filterBy);
|
|
139
|
+
// Process artifacts at this level
|
|
140
|
+
if (directContent.artifacts && directContent.artifacts.length > 0) {
|
|
141
|
+
node.artifacts = await this.processArtifacts(directContent.artifacts, options);
|
|
142
|
+
}
|
|
143
|
+
// Process child namespaces
|
|
144
|
+
if (directContent.children && directContent.children.length > 0) {
|
|
145
|
+
const childPromises = directContent.children.map(async (childPath) => {
|
|
146
|
+
const shouldExpand = remainingDepth > 1 || options.expandPaths.includes(childPath);
|
|
147
|
+
if (shouldExpand) {
|
|
148
|
+
return await this.exploreNamespace(childPath, remainingDepth - 1, options);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Return summary info for unexplored children
|
|
152
|
+
return {
|
|
153
|
+
path: childPath,
|
|
154
|
+
name: this.getNameFromPath(childPath),
|
|
155
|
+
type: this.determineNodeType(childPath),
|
|
156
|
+
level: this.calculateLevel(childPath),
|
|
157
|
+
summary: await this.getNodeSummary(childPath, options.filterBy),
|
|
158
|
+
children: null, // Indicates unexplored
|
|
159
|
+
artifacts: []
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
const childResults = await Promise.all(childPromises);
|
|
164
|
+
node.children = childResults.filter(child => child !== null);
|
|
165
|
+
}
|
|
166
|
+
// Add statistics if requested
|
|
167
|
+
if (options.includeStats) {
|
|
168
|
+
node.statistics = await this.calculateNodeStatistics(node, options.filterBy);
|
|
169
|
+
}
|
|
170
|
+
// Sort children based on sort criteria
|
|
171
|
+
if (node.children && node.children.length > 0) {
|
|
172
|
+
node.children = this.sortNodes(node.children, options.sortBy);
|
|
173
|
+
}
|
|
174
|
+
return node;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
this.logger.warn('ExploreNamespaceController', `Failed to explore path: ${path}`, '', error);
|
|
178
|
+
return {
|
|
179
|
+
path,
|
|
180
|
+
name: this.getNameFromPath(path),
|
|
181
|
+
type: 'error',
|
|
182
|
+
level: this.calculateLevel(path),
|
|
183
|
+
error: error instanceof Error ? error.message : String(error),
|
|
184
|
+
children: [],
|
|
185
|
+
artifacts: []
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async getDirectContent(path, filterBy) {
|
|
190
|
+
const content = {
|
|
191
|
+
artifacts: [],
|
|
192
|
+
children: []
|
|
193
|
+
};
|
|
194
|
+
try {
|
|
195
|
+
if (!path) {
|
|
196
|
+
// Root level - get all authors
|
|
197
|
+
const authors = await this.ucmClient.getAuthors();
|
|
198
|
+
content.children = authors.map((author) => author.id);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
const pathParts = path.split('/');
|
|
202
|
+
if (pathParts.length === 1) {
|
|
203
|
+
// Author level - get categories
|
|
204
|
+
const authorData = await this.ucmClient.getAuthor(pathParts[0]);
|
|
205
|
+
if (Array.isArray(authorData)) {
|
|
206
|
+
// Extract unique categories
|
|
207
|
+
const categories = new Set();
|
|
208
|
+
authorData.forEach((artifact) => {
|
|
209
|
+
if (artifact.metadata?.category) {
|
|
210
|
+
categories.add(artifact.metadata.category);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
content.children = Array.from(categories).map(cat => `${path}/${cat}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else if (pathParts.length === 2) {
|
|
217
|
+
// Category level - get subcategories and artifacts
|
|
218
|
+
const [author, category] = pathParts;
|
|
219
|
+
const authorData = await this.ucmClient.getAuthor(author);
|
|
220
|
+
if (Array.isArray(authorData)) {
|
|
221
|
+
const categoryArtifacts = authorData.filter((artifact) => artifact.metadata?.category === category);
|
|
222
|
+
// Filter artifacts
|
|
223
|
+
const filteredArtifacts = this.applyFilters(categoryArtifacts, filterBy);
|
|
224
|
+
content.artifacts = filteredArtifacts;
|
|
225
|
+
// Extract subcategories
|
|
226
|
+
const subcategories = new Set();
|
|
227
|
+
categoryArtifacts.forEach((artifact) => {
|
|
228
|
+
if (artifact.metadata?.subcategory) {
|
|
229
|
+
subcategories.add(artifact.metadata.subcategory);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
content.children = Array.from(subcategories).map(sub => `${path}/${sub}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// Deeper levels - get artifacts
|
|
237
|
+
const artifacts = await this.getArtifactsAtPath(path);
|
|
238
|
+
content.artifacts = this.applyFilters(artifacts, filterBy);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
// If we can't get content, return empty but don't fail
|
|
244
|
+
this.logger.debug('ExploreNamespaceController', `No content found at path: ${path}`, '', error);
|
|
245
|
+
}
|
|
246
|
+
return content;
|
|
247
|
+
}
|
|
248
|
+
async getArtifactsAtPath(path) {
|
|
249
|
+
try {
|
|
250
|
+
// Try to get artifacts at this specific path
|
|
251
|
+
const pathParts = path.split('/');
|
|
252
|
+
if (pathParts.length >= 3) {
|
|
253
|
+
const [author, category, subcategory] = pathParts;
|
|
254
|
+
const authorData = await this.ucmClient.getAuthor(author);
|
|
255
|
+
if (Array.isArray(authorData)) {
|
|
256
|
+
return authorData.filter((artifact) => artifact.metadata?.category === category &&
|
|
257
|
+
artifact.metadata?.subcategory === subcategory);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
this.logger.debug('ExploreNamespaceController', `No artifacts at path: ${path}`, '', error);
|
|
263
|
+
}
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
applyFilters(artifacts, filterBy) {
|
|
267
|
+
let filtered = artifacts;
|
|
268
|
+
if (filterBy.technology) {
|
|
269
|
+
filtered = filtered.filter(artifact => artifact.metadata?.technology === filterBy.technology);
|
|
270
|
+
}
|
|
271
|
+
if (filterBy.category) {
|
|
272
|
+
filtered = filtered.filter(artifact => artifact.metadata?.category === filterBy.category);
|
|
273
|
+
}
|
|
274
|
+
if (filterBy.updatedSince) {
|
|
275
|
+
const sinceDate = new Date(filterBy.updatedSince);
|
|
276
|
+
filtered = filtered.filter(artifact => {
|
|
277
|
+
const updated = new Date(artifact.lastUpdated || artifact.publishedAt || 0);
|
|
278
|
+
return updated > sinceDate;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return filtered;
|
|
282
|
+
}
|
|
283
|
+
async processArtifacts(artifacts, options) {
|
|
284
|
+
return artifacts.map(artifact => {
|
|
285
|
+
const processed = {
|
|
286
|
+
id: artifact.id,
|
|
287
|
+
name: artifact.metadata?.name || 'Unknown',
|
|
288
|
+
path: artifact.path,
|
|
289
|
+
version: artifact.metadata?.version || 'unknown',
|
|
290
|
+
technology: artifact.metadata?.technology || null,
|
|
291
|
+
description: artifact.metadata?.description || '',
|
|
292
|
+
lastUpdated: artifact.lastUpdated,
|
|
293
|
+
tags: artifact.metadata?.tags || []
|
|
294
|
+
};
|
|
295
|
+
// Include content preview if requested
|
|
296
|
+
if (options.includeContent && artifact.content) {
|
|
297
|
+
processed.contentPreview = this.generateContentPreview(artifact.content);
|
|
298
|
+
}
|
|
299
|
+
return processed;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
async getNodeSummary(path, filterBy) {
|
|
303
|
+
try {
|
|
304
|
+
const content = await this.getDirectContent(path, filterBy);
|
|
305
|
+
return {
|
|
306
|
+
artifactCount: content.artifacts?.length || 0,
|
|
307
|
+
childCount: content.children?.length || 0,
|
|
308
|
+
hasContent: (content.artifacts?.length || 0) > 0 || (content.children?.length || 0) > 0,
|
|
309
|
+
lastActivity: this.getLastActivity(content.artifacts || [])
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
return {
|
|
314
|
+
artifactCount: 0,
|
|
315
|
+
childCount: 0,
|
|
316
|
+
hasContent: false,
|
|
317
|
+
lastActivity: null,
|
|
318
|
+
error: error instanceof Error ? error.message : String(error)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async calculateNodeStatistics(node, filterBy) {
|
|
323
|
+
const stats = {
|
|
324
|
+
directArtifacts: node.artifacts?.length || 0,
|
|
325
|
+
totalArtifacts: 0,
|
|
326
|
+
totalChildren: node.children?.length || 0,
|
|
327
|
+
depth: node.level,
|
|
328
|
+
technologies: new Set(),
|
|
329
|
+
categories: new Set(),
|
|
330
|
+
authors: new Set(),
|
|
331
|
+
lastActivity: null,
|
|
332
|
+
popularityScore: 0
|
|
333
|
+
};
|
|
334
|
+
// Count total artifacts recursively
|
|
335
|
+
stats.totalArtifacts = this.countArtifactsRecursively(node);
|
|
336
|
+
// Collect technology and category information
|
|
337
|
+
this.collectMetadataRecursively(node, stats);
|
|
338
|
+
// Calculate last activity
|
|
339
|
+
stats.lastActivity = this.getLastActivityRecursively(node);
|
|
340
|
+
// Calculate popularity score
|
|
341
|
+
stats.popularityScore = this.calculatePopularityScore(node);
|
|
342
|
+
return {
|
|
343
|
+
...stats,
|
|
344
|
+
technologies: Array.from(stats.technologies),
|
|
345
|
+
categories: Array.from(stats.categories),
|
|
346
|
+
authors: Array.from(stats.authors)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
sortNodes(nodes, sortBy) {
|
|
350
|
+
return nodes.sort((a, b) => {
|
|
351
|
+
switch (sortBy) {
|
|
352
|
+
case 'count':
|
|
353
|
+
const aCount = (a.statistics?.totalArtifacts || a.summary?.artifactCount || 0);
|
|
354
|
+
const bCount = (b.statistics?.totalArtifacts || b.summary?.artifactCount || 0);
|
|
355
|
+
return bCount - aCount;
|
|
356
|
+
case 'recent':
|
|
357
|
+
const aActivity = a.statistics?.lastActivity || a.summary?.lastActivity || '1970-01-01';
|
|
358
|
+
const bActivity = b.statistics?.lastActivity || b.summary?.lastActivity || '1970-01-01';
|
|
359
|
+
return new Date(bActivity).getTime() - new Date(aActivity).getTime();
|
|
360
|
+
case 'popular':
|
|
361
|
+
const aPopularity = a.statistics?.popularityScore || 0;
|
|
362
|
+
const bPopularity = b.statistics?.popularityScore || 0;
|
|
363
|
+
return bPopularity - aPopularity;
|
|
364
|
+
default: // 'name'
|
|
365
|
+
return a.name.localeCompare(b.name);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
generateNavigationHints(explorationResult) {
|
|
370
|
+
const hints = {
|
|
371
|
+
nextSteps: [],
|
|
372
|
+
interestingPaths: [],
|
|
373
|
+
recommendations: [],
|
|
374
|
+
shortcuts: []
|
|
375
|
+
};
|
|
376
|
+
// Analyze exploration result to generate helpful hints
|
|
377
|
+
if (explorationResult.children && explorationResult.children.length > 0) {
|
|
378
|
+
const unexplored = explorationResult.children.filter((child) => child.children === null);
|
|
379
|
+
if (unexplored.length > 0) {
|
|
380
|
+
hints.nextSteps.push(`Explore ${unexplored.length} unexplored child namespaces`);
|
|
381
|
+
hints.shortcuts.push('Use higher depth parameter to explore deeper automatically');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Find interesting paths based on artifact counts
|
|
385
|
+
this.findInterestingPaths(explorationResult, hints.interestingPaths);
|
|
386
|
+
// Generate recommendations based on content
|
|
387
|
+
if (explorationResult.statistics) {
|
|
388
|
+
const stats = explorationResult.statistics;
|
|
389
|
+
if (stats.totalArtifacts > 10) {
|
|
390
|
+
hints.recommendations.push('This namespace has rich content - consider using search tools for specific artifacts');
|
|
391
|
+
}
|
|
392
|
+
if (stats.technologies.length > 3) {
|
|
393
|
+
hints.recommendations.push('Multiple technologies available - filter by technology for focused exploration');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return hints;
|
|
397
|
+
}
|
|
398
|
+
buildBreadcrumbs(path) {
|
|
399
|
+
if (!path) {
|
|
400
|
+
return [{ name: 'Root', path: '', level: 0 }];
|
|
401
|
+
}
|
|
402
|
+
const parts = path.split('/');
|
|
403
|
+
const breadcrumbs = [{ name: 'Root', path: '', level: 0 }];
|
|
404
|
+
let currentPath = '';
|
|
405
|
+
parts.forEach((part, index) => {
|
|
406
|
+
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
407
|
+
breadcrumbs.push({
|
|
408
|
+
name: part,
|
|
409
|
+
path: currentPath,
|
|
410
|
+
level: index + 1
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
return breadcrumbs;
|
|
414
|
+
}
|
|
415
|
+
// Helper methods
|
|
416
|
+
isValidNamespacePath(path) {
|
|
417
|
+
if (!path)
|
|
418
|
+
return true; // Empty path is valid (root)
|
|
419
|
+
return /^[a-zA-Z0-9\-_]+(\/[a-zA-Z0-9\-_]+)*$/.test(path);
|
|
420
|
+
}
|
|
421
|
+
getNameFromPath(path) {
|
|
422
|
+
if (!path)
|
|
423
|
+
return 'Root';
|
|
424
|
+
const parts = path.split('/');
|
|
425
|
+
return parts[parts.length - 1];
|
|
426
|
+
}
|
|
427
|
+
determineNodeType(path) {
|
|
428
|
+
if (!path)
|
|
429
|
+
return 'root';
|
|
430
|
+
const parts = path.split('/');
|
|
431
|
+
switch (parts.length) {
|
|
432
|
+
case 1: return 'author';
|
|
433
|
+
case 2: return 'category';
|
|
434
|
+
case 3: return 'subcategory';
|
|
435
|
+
case 4: return 'technology-or-version';
|
|
436
|
+
default: return 'artifact';
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
calculateLevel(path) {
|
|
440
|
+
return path ? path.split('/').length : 0;
|
|
441
|
+
}
|
|
442
|
+
generateContentPreview(content) {
|
|
443
|
+
const lines = content.split('\n');
|
|
444
|
+
const preview = lines.slice(0, 5).join('\n');
|
|
445
|
+
return preview.length > 200 ? preview.substring(0, 200) + '...' : preview;
|
|
446
|
+
}
|
|
447
|
+
getLastActivity(artifacts) {
|
|
448
|
+
if (!artifacts || artifacts.length === 0)
|
|
449
|
+
return null;
|
|
450
|
+
const dates = artifacts
|
|
451
|
+
.map(artifact => artifact.lastUpdated || artifact.publishedAt)
|
|
452
|
+
.filter(date => date)
|
|
453
|
+
.sort((a, b) => new Date(b).getTime() - new Date(a).getTime());
|
|
454
|
+
return dates.length > 0 ? dates[0] : null;
|
|
455
|
+
}
|
|
456
|
+
countTotalNodes(node) {
|
|
457
|
+
if (!node)
|
|
458
|
+
return 0;
|
|
459
|
+
let count = 1; // Count this node
|
|
460
|
+
if (node.children) {
|
|
461
|
+
count += node.children.reduce((sum, child) => sum + this.countTotalNodes(child), 0);
|
|
462
|
+
}
|
|
463
|
+
return count;
|
|
464
|
+
}
|
|
465
|
+
calculateMaxDepth(node) {
|
|
466
|
+
if (!node || !node.children || node.children.length === 0) {
|
|
467
|
+
return node ? node.level || 0 : 0;
|
|
468
|
+
}
|
|
469
|
+
return Math.max(...node.children.map((child) => this.calculateMaxDepth(child)));
|
|
470
|
+
}
|
|
471
|
+
hasMoreContent(node, requestedDepth) {
|
|
472
|
+
if (!node)
|
|
473
|
+
return false;
|
|
474
|
+
// Check if any children were not fully explored
|
|
475
|
+
if (node.children) {
|
|
476
|
+
return node.children.some((child) => child.children === null || this.hasMoreContent(child, requestedDepth));
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
countArtifactsRecursively(node) {
|
|
481
|
+
let count = node.artifacts?.length || 0;
|
|
482
|
+
if (node.children) {
|
|
483
|
+
count += node.children.reduce((sum, child) => sum + this.countArtifactsRecursively(child), 0);
|
|
484
|
+
}
|
|
485
|
+
return count;
|
|
486
|
+
}
|
|
487
|
+
collectMetadataRecursively(node, stats) {
|
|
488
|
+
// Collect from direct artifacts
|
|
489
|
+
if (node.artifacts) {
|
|
490
|
+
node.artifacts.forEach((artifact) => {
|
|
491
|
+
if (artifact.technology)
|
|
492
|
+
stats.technologies.add(artifact.technology);
|
|
493
|
+
if (artifact.metadata?.category)
|
|
494
|
+
stats.categories.add(artifact.metadata.category);
|
|
495
|
+
if (artifact.metadata?.author)
|
|
496
|
+
stats.authors.add(artifact.metadata.author);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
// Collect from children
|
|
500
|
+
if (node.children) {
|
|
501
|
+
node.children.forEach((child) => this.collectMetadataRecursively(child, stats));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
getLastActivityRecursively(node) {
|
|
505
|
+
const activities = [];
|
|
506
|
+
// Get activity from direct artifacts
|
|
507
|
+
const nodeActivity = this.getLastActivity(node.artifacts || []);
|
|
508
|
+
if (nodeActivity)
|
|
509
|
+
activities.push(nodeActivity);
|
|
510
|
+
// Get activity from children
|
|
511
|
+
if (node.children) {
|
|
512
|
+
node.children.forEach((child) => {
|
|
513
|
+
const childActivity = this.getLastActivityRecursively(child);
|
|
514
|
+
if (childActivity)
|
|
515
|
+
activities.push(childActivity);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
if (activities.length === 0)
|
|
519
|
+
return null;
|
|
520
|
+
return activities.sort((a, b) => new Date(b).getTime() - new Date(a).getTime())[0];
|
|
521
|
+
}
|
|
522
|
+
calculatePopularityScore(node) {
|
|
523
|
+
// Simple popularity calculation based on artifact count and recency
|
|
524
|
+
const artifactCount = this.countArtifactsRecursively(node);
|
|
525
|
+
const lastActivity = this.getLastActivityRecursively(node);
|
|
526
|
+
let score = artifactCount * 10; // Base score from content
|
|
527
|
+
if (lastActivity) {
|
|
528
|
+
const daysSinceActivity = (Date.now() - new Date(lastActivity).getTime()) / (1000 * 60 * 60 * 24);
|
|
529
|
+
const recencyScore = Math.max(0, 100 - daysSinceActivity);
|
|
530
|
+
score += recencyScore;
|
|
531
|
+
}
|
|
532
|
+
return Math.round(score);
|
|
533
|
+
}
|
|
534
|
+
findInterestingPaths(node, interestingPaths) {
|
|
535
|
+
if (!node)
|
|
536
|
+
return;
|
|
537
|
+
const artifactCount = this.countArtifactsRecursively(node);
|
|
538
|
+
// Consider paths with significant content as interesting
|
|
539
|
+
if (artifactCount >= 5 && node.path) {
|
|
540
|
+
interestingPaths.push(`${node.path} (${artifactCount} artifacts)`);
|
|
541
|
+
}
|
|
542
|
+
// Recursively check children
|
|
543
|
+
if (node.children) {
|
|
544
|
+
node.children.forEach((child) => this.findInterestingPaths(child, interestingPaths));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
//# sourceMappingURL=ExploreNamespaceController.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
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 AuthorIndexTool extends BaseToolController {
|
|
5
|
+
constructor(ucmClient: UcmApiClient, logger: ILogger, publishingAuthorId?: string);
|
|
6
|
+
get name(): string;
|
|
7
|
+
get description(): string;
|
|
8
|
+
get inputSchema(): any;
|
|
9
|
+
protected handleExecute(params: any): Promise<any>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=AuthorIndexTool.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
export class AuthorIndexTool extends BaseToolController {
|
|
3
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
4
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
5
|
+
}
|
|
6
|
+
get name() {
|
|
7
|
+
return 'mcp_ucm_get_author_index';
|
|
8
|
+
}
|
|
9
|
+
get description() {
|
|
10
|
+
return `Discover a dynamic markdown index guide for AI assistants working with an author\'s content. Your author id is '${this.publishingAuthorId}'`;
|
|
11
|
+
}
|
|
12
|
+
get inputSchema() {
|
|
13
|
+
return {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
author: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: `Author identifier: ${this.publishingAuthorId || '1234567890'}`
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
required: ['author']
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async handleExecute(params) {
|
|
25
|
+
const { author } = params;
|
|
26
|
+
this.logger.debug('AuthorIndexTool', `Retrieving author index for: ${author}`);
|
|
27
|
+
try {
|
|
28
|
+
// Get author index content from API (author-level index, not repository-specific)
|
|
29
|
+
const authorIndexContent = await this.ucmClient.getAuthorIndex(author);
|
|
30
|
+
this.logger.info('AuthorIndexTool', 'Author index retrieved successfully', '', {
|
|
31
|
+
author,
|
|
32
|
+
contentLength: authorIndexContent.length,
|
|
33
|
+
source: `UCM API /api/v1/authors/${author}/index`
|
|
34
|
+
});
|
|
35
|
+
return authorIndexContent;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
let errorMessage = `Failed to retrieve author index for ${author}.`;
|
|
39
|
+
if (this.publishingAuthorId && author != this.publishingAuthorId) {
|
|
40
|
+
errorMessage += `Did you mean '${this.publishingAuthorId}'`;
|
|
41
|
+
error.message = error.message += `Did you mean '${this.publishingAuthorId}'`;
|
|
42
|
+
}
|
|
43
|
+
this.logger.error('AuthorIndexTool', `${errorMessage}`, '', error);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=AuthorIndexTool.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
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 HealthCheckController extends BaseToolController {
|
|
5
|
+
constructor(ucmClient: UcmApiClient, logger: ILogger, publishingAuthorId?: string);
|
|
6
|
+
get name(): string;
|
|
7
|
+
get description(): string;
|
|
8
|
+
get inputSchema(): any;
|
|
9
|
+
protected handleExecute(_params: any): Promise<any>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=HealthCheckController.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
export class HealthCheckController extends BaseToolController {
|
|
3
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
4
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
5
|
+
}
|
|
6
|
+
get name() {
|
|
7
|
+
return 'mcp_ucm_health_check';
|
|
8
|
+
}
|
|
9
|
+
get description() {
|
|
10
|
+
return 'Check MCP server and UCM API connectivity status';
|
|
11
|
+
}
|
|
12
|
+
get inputSchema() {
|
|
13
|
+
return {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {},
|
|
16
|
+
required: []
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async handleExecute(_params) {
|
|
20
|
+
try {
|
|
21
|
+
// Check UCM API connectivity
|
|
22
|
+
const ucmHealth = await this.ucmClient.healthCheck();
|
|
23
|
+
return {
|
|
24
|
+
status: 'healthy',
|
|
25
|
+
mcpServer: {
|
|
26
|
+
status: 'running',
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
uptime: process.uptime()
|
|
29
|
+
},
|
|
30
|
+
ucmApi: {
|
|
31
|
+
status: 'connected',
|
|
32
|
+
response: ucmHealth
|
|
33
|
+
},
|
|
34
|
+
config: {
|
|
35
|
+
defaultRepository: 'main',
|
|
36
|
+
author: this.publishingAuthorId
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return {
|
|
42
|
+
status: 'unhealthy',
|
|
43
|
+
mcpServer: {
|
|
44
|
+
status: 'running',
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
uptime: process.uptime()
|
|
47
|
+
},
|
|
48
|
+
ucmApi: {
|
|
49
|
+
status: 'disconnected',
|
|
50
|
+
error: error instanceof Error ? error.message : String(error)
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=HealthCheckController.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
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 ListRepositoriesTool extends BaseToolController {
|
|
5
|
+
constructor(ucmClient: UcmApiClient, logger: ILogger, publishingAuthorId?: string);
|
|
6
|
+
get name(): string;
|
|
7
|
+
get description(): string;
|
|
8
|
+
get inputSchema(): any;
|
|
9
|
+
protected handleExecute(params: any): Promise<any>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=ListRepositoriesTool.d.ts.map
|