@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,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