@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,70 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
export class ListRepositoriesTool extends BaseToolController {
|
|
3
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
4
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
5
|
+
}
|
|
6
|
+
get name() {
|
|
7
|
+
return 'mcp_ucm_list_repositories';
|
|
8
|
+
}
|
|
9
|
+
get description() {
|
|
10
|
+
return 'List all repositories for a specific author with pagination and statistics';
|
|
11
|
+
}
|
|
12
|
+
get inputSchema() {
|
|
13
|
+
return {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
author: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: `Author identifier (e.g., "${this.publishingAuthorId || '1234567890'}")`,
|
|
19
|
+
minLength: 1,
|
|
20
|
+
maxLength: 50
|
|
21
|
+
},
|
|
22
|
+
offset: {
|
|
23
|
+
type: 'number',
|
|
24
|
+
description: 'Number of items to skip for pagination (default: 0)',
|
|
25
|
+
minimum: 0
|
|
26
|
+
},
|
|
27
|
+
limit: {
|
|
28
|
+
type: 'number',
|
|
29
|
+
description: 'Maximum number of items to return (default: 20, max: 100)',
|
|
30
|
+
minimum: 1,
|
|
31
|
+
maximum: 100
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
required: ['author']
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async handleExecute(params) {
|
|
38
|
+
const { author, offset, limit } = params;
|
|
39
|
+
this.logger.debug('ListRepositoriesTool', `Listing repositories for author: ${author}`, '', {
|
|
40
|
+
offset: offset || 0,
|
|
41
|
+
limit: limit || 'default'
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
// Use listArtifacts with just author to get repositories
|
|
45
|
+
const response = await this.ucmClient.listArtifacts(author, undefined, // no repository - this will list repositories
|
|
46
|
+
undefined, // no category
|
|
47
|
+
undefined, // no subcategory
|
|
48
|
+
offset, limit);
|
|
49
|
+
this.logger.info('ListRepositoriesTool', `Listed ${response.data.length} repositories for author: ${author}`, '', {
|
|
50
|
+
total: response.pagination?.total,
|
|
51
|
+
offset: response.pagination?.offset,
|
|
52
|
+
limit: response.pagination?.limit
|
|
53
|
+
});
|
|
54
|
+
// Return the full response with pagination metadata and context
|
|
55
|
+
return {
|
|
56
|
+
author,
|
|
57
|
+
listingType: 'repositories',
|
|
58
|
+
data: response.data,
|
|
59
|
+
pagination: response.pagination,
|
|
60
|
+
hasMore: response.pagination.offset + response.pagination.limit < response.pagination.total,
|
|
61
|
+
_links: response._links
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
this.logger.error('ListRepositoriesTool', `Failed to list repositories for author: ${author}`, '', error);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=ListRepositoriesTool.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 QuickstartTool 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=QuickstartTool.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
export class QuickstartTool extends BaseToolController {
|
|
3
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
4
|
+
super(ucmClient, logger, publishingAuthorId);
|
|
5
|
+
}
|
|
6
|
+
get name() {
|
|
7
|
+
return 'mcp_ucm_quickstart';
|
|
8
|
+
}
|
|
9
|
+
get description() {
|
|
10
|
+
return 'Get the UCM quickstart guide to understand how to use the UCM system and MCP tools effectively';
|
|
11
|
+
}
|
|
12
|
+
get inputSchema() {
|
|
13
|
+
return {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {},
|
|
16
|
+
required: []
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async handleExecute(params) {
|
|
20
|
+
this.logger.debug('QuickstartTool', 'Retrieving UCM quickstart guide');
|
|
21
|
+
try {
|
|
22
|
+
// Get quickstart content from API
|
|
23
|
+
const quickstartContent = await this.ucmClient.getQuickstart();
|
|
24
|
+
this.logger.info('QuickstartTool', 'Quickstart guide retrieved successfully', '', {
|
|
25
|
+
contentLength: quickstartContent.length,
|
|
26
|
+
source: 'UCM API /api/v1/quickstart'
|
|
27
|
+
});
|
|
28
|
+
return quickstartContent;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
this.logger.error('QuickstartTool', 'Failed to retrieve quickstart guide', '', error);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=QuickstartTool.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
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 ValidatePathController 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 performPathValidation;
|
|
11
|
+
private validateBasicFormat;
|
|
12
|
+
private parsePathComponents;
|
|
13
|
+
private determinePathType;
|
|
14
|
+
private isVersionedPath;
|
|
15
|
+
private looksLikeVersion;
|
|
16
|
+
private validateComponents;
|
|
17
|
+
private validateSemantics;
|
|
18
|
+
private checkPathExistence;
|
|
19
|
+
private checkParentPathExistence;
|
|
20
|
+
private findSimilarPaths;
|
|
21
|
+
private generateSuggestions;
|
|
22
|
+
private applyStrictModeValidation;
|
|
23
|
+
private validateComponentConsistency;
|
|
24
|
+
private isValidAuthorId;
|
|
25
|
+
private isValidSubcategory;
|
|
26
|
+
private isValidTechnology;
|
|
27
|
+
private calculatePathSimilarity;
|
|
28
|
+
private generateSimilarityReason;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=ValidatePathController.d.ts.map
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { ValidationUtils } from '../../utils/ValidationUtils.js';
|
|
3
|
+
import { parsePath } from '../../utils/PathUtils.js';
|
|
4
|
+
export class ValidatePathController extends BaseToolController {
|
|
5
|
+
constructor(ucmClient, logger) {
|
|
6
|
+
super(ucmClient, logger);
|
|
7
|
+
}
|
|
8
|
+
get name() {
|
|
9
|
+
return 'mcp_ucm_validate_path';
|
|
10
|
+
}
|
|
11
|
+
get description() {
|
|
12
|
+
return 'Validate UCM artifact path format and optionally check for existence in the repository';
|
|
13
|
+
}
|
|
14
|
+
get inputSchema() {
|
|
15
|
+
return {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
path: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Artifact path to validate (e.g., "utaba/commands/create-user/typescript/1.0.0")',
|
|
21
|
+
minLength: 1,
|
|
22
|
+
maxLength: 200
|
|
23
|
+
},
|
|
24
|
+
checkExistence: {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
default: false,
|
|
27
|
+
description: 'Check if the artifact actually exists in the repository'
|
|
28
|
+
},
|
|
29
|
+
validateComponents: {
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
default: true,
|
|
32
|
+
description: 'Validate individual path components (author, category, etc.)'
|
|
33
|
+
},
|
|
34
|
+
suggestCorrections: {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
default: true,
|
|
37
|
+
description: 'Provide suggestions for fixing invalid paths'
|
|
38
|
+
},
|
|
39
|
+
checkSimilar: {
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
default: false,
|
|
42
|
+
description: 'Find similar existing paths if validation fails'
|
|
43
|
+
},
|
|
44
|
+
strictMode: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
default: false,
|
|
47
|
+
description: 'Enable strict validation rules (more restrictive)'
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
required: ['path']
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async handleExecute(params) {
|
|
54
|
+
const { path, checkExistence = false, validateComponents = true, suggestCorrections = true, checkSimilar = false, strictMode = false } = params;
|
|
55
|
+
this.logger.debug('ValidatePathController', `Validating path: ${path}`);
|
|
56
|
+
try {
|
|
57
|
+
// Perform comprehensive validation
|
|
58
|
+
const validationResult = await this.performPathValidation(path, {
|
|
59
|
+
checkExistence,
|
|
60
|
+
validateComponents,
|
|
61
|
+
suggestCorrections,
|
|
62
|
+
checkSimilar,
|
|
63
|
+
strictMode
|
|
64
|
+
});
|
|
65
|
+
this.logger.info('ValidatePathController', `Path validation completed: ${validationResult.isValid ? 'VALID' : 'INVALID'}`);
|
|
66
|
+
return validationResult;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
this.logger.error('ValidatePathController', `Path validation failed for: ${path}`, '', error);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async performPathValidation(path, options) {
|
|
74
|
+
const result = {
|
|
75
|
+
path,
|
|
76
|
+
isValid: true,
|
|
77
|
+
errors: [],
|
|
78
|
+
warnings: [],
|
|
79
|
+
suggestions: [],
|
|
80
|
+
pathAnalysis: {},
|
|
81
|
+
existence: null,
|
|
82
|
+
similar: [],
|
|
83
|
+
metadata: {
|
|
84
|
+
timestamp: new Date().toISOString(),
|
|
85
|
+
validationType: options.strictMode ? 'strict' : 'standard',
|
|
86
|
+
checksPerformed: []
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
// 1. Basic format validation
|
|
90
|
+
await this.validateBasicFormat(path, result, options);
|
|
91
|
+
// 2. Component validation
|
|
92
|
+
if (options.validateComponents && result.pathAnalysis.components) {
|
|
93
|
+
await this.validateComponents(result.pathAnalysis.components, result, options);
|
|
94
|
+
}
|
|
95
|
+
// 3. Semantic validation
|
|
96
|
+
await this.validateSemantics(path, result, options);
|
|
97
|
+
// 4. Existence check
|
|
98
|
+
if (options.checkExistence) {
|
|
99
|
+
await this.checkPathExistence(path, result);
|
|
100
|
+
}
|
|
101
|
+
// 5. Find similar paths if invalid and requested
|
|
102
|
+
if (!result.isValid && options.checkSimilar) {
|
|
103
|
+
await this.findSimilarPaths(path, result);
|
|
104
|
+
}
|
|
105
|
+
// 6. Generate suggestions if requested
|
|
106
|
+
if (options.suggestCorrections && (!result.isValid || result.warnings.length > 0)) {
|
|
107
|
+
await this.generateSuggestions(path, result, options);
|
|
108
|
+
}
|
|
109
|
+
// Set overall validity
|
|
110
|
+
result.isValid = result.errors.length === 0;
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
async validateBasicFormat(path, result, options) {
|
|
114
|
+
result.metadata.checksPerformed.push('basic-format');
|
|
115
|
+
// Basic checks
|
|
116
|
+
if (!path || typeof path !== 'string') {
|
|
117
|
+
result.errors.push('Path must be a non-empty string');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (path.trim() !== path) {
|
|
121
|
+
result.errors.push('Path cannot have leading or trailing whitespace');
|
|
122
|
+
}
|
|
123
|
+
if (path.includes('//')) {
|
|
124
|
+
result.errors.push('Path cannot contain double slashes');
|
|
125
|
+
}
|
|
126
|
+
if (path.includes('..')) {
|
|
127
|
+
result.errors.push('Path cannot contain relative path components (..)');
|
|
128
|
+
}
|
|
129
|
+
if (path.startsWith('/') || path.endsWith('/')) {
|
|
130
|
+
result.errors.push('Path cannot start or end with forward slash');
|
|
131
|
+
}
|
|
132
|
+
// Character validation
|
|
133
|
+
const validCharPattern = /^[a-zA-Z0-9\-_.\/]+$/;
|
|
134
|
+
if (!validCharPattern.test(path)) {
|
|
135
|
+
result.errors.push('Path contains invalid characters. Only alphanumeric, hyphens, underscores, dots, and forward slashes are allowed');
|
|
136
|
+
}
|
|
137
|
+
// Parse path components
|
|
138
|
+
const components = path.split('/');
|
|
139
|
+
result.pathAnalysis = {
|
|
140
|
+
components: this.parsePathComponents(components),
|
|
141
|
+
componentCount: components.length,
|
|
142
|
+
pathType: this.determinePathType(components),
|
|
143
|
+
isVersioned: this.isVersionedPath(components)
|
|
144
|
+
};
|
|
145
|
+
// Length validation
|
|
146
|
+
if (path.length > 200) {
|
|
147
|
+
result.errors.push('Path exceeds maximum length of 200 characters');
|
|
148
|
+
}
|
|
149
|
+
if (components.length < 3) {
|
|
150
|
+
result.errors.push('Path must have at least 3 components (author/category/subcategory)');
|
|
151
|
+
}
|
|
152
|
+
if (components.length > 5) {
|
|
153
|
+
result.errors.push('Path has too many components. Maximum is 5 (author/category/subcategory/technology/version)');
|
|
154
|
+
}
|
|
155
|
+
// Strict mode additional checks
|
|
156
|
+
if (options.strictMode) {
|
|
157
|
+
this.applyStrictModeValidation(path, components, result);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
parsePathComponents(components) {
|
|
161
|
+
const parsed = {
|
|
162
|
+
author: components[0] || null,
|
|
163
|
+
category: components[1] || null,
|
|
164
|
+
subcategory: components[2] || null,
|
|
165
|
+
technology: null,
|
|
166
|
+
version: null
|
|
167
|
+
};
|
|
168
|
+
if (components.length === 4) {
|
|
169
|
+
// Could be either technology-agnostic (author/category/subcategory/version)
|
|
170
|
+
// or missing version (author/category/subcategory/technology)
|
|
171
|
+
if (this.looksLikeVersion(components[3])) {
|
|
172
|
+
parsed.version = components[3];
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
parsed.technology = components[3];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else if (components.length === 5) {
|
|
179
|
+
// author/category/subcategory/technology/version
|
|
180
|
+
parsed.technology = components[3];
|
|
181
|
+
parsed.version = components[4];
|
|
182
|
+
}
|
|
183
|
+
return parsed;
|
|
184
|
+
}
|
|
185
|
+
determinePathType(components) {
|
|
186
|
+
if (components.length === 4 && this.looksLikeVersion(components[3])) {
|
|
187
|
+
return 'technology-agnostic-versioned';
|
|
188
|
+
}
|
|
189
|
+
else if (components.length === 4) {
|
|
190
|
+
return 'technology-specific-unversioned';
|
|
191
|
+
}
|
|
192
|
+
else if (components.length === 5) {
|
|
193
|
+
return 'technology-specific-versioned';
|
|
194
|
+
}
|
|
195
|
+
else if (components.length === 3) {
|
|
196
|
+
return 'subcategory-level';
|
|
197
|
+
}
|
|
198
|
+
else if (components.length === 2) {
|
|
199
|
+
return 'category-level';
|
|
200
|
+
}
|
|
201
|
+
else if (components.length === 1) {
|
|
202
|
+
return 'author-level';
|
|
203
|
+
}
|
|
204
|
+
return 'unknown';
|
|
205
|
+
}
|
|
206
|
+
isVersionedPath(components) {
|
|
207
|
+
const lastComponent = components[components.length - 1];
|
|
208
|
+
return this.looksLikeVersion(lastComponent);
|
|
209
|
+
}
|
|
210
|
+
looksLikeVersion(component) {
|
|
211
|
+
return /^[0-9]+\.[0-9]+\.[0-9]+/.test(component);
|
|
212
|
+
}
|
|
213
|
+
async validateComponents(components, result, options) {
|
|
214
|
+
result.metadata.checksPerformed.push('component-validation');
|
|
215
|
+
// Validate author
|
|
216
|
+
if (components.author) {
|
|
217
|
+
if (!this.isValidAuthorId(components.author)) {
|
|
218
|
+
result.errors.push(`Invalid author ID format: ${components.author}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Validate category
|
|
222
|
+
if (components.category) {
|
|
223
|
+
try {
|
|
224
|
+
ValidationUtils.validateCategory(components.category);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
result.errors.push(`Invalid category: ${components.category}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Validate subcategory
|
|
231
|
+
if (components.subcategory) {
|
|
232
|
+
if (!this.isValidSubcategory(components.subcategory)) {
|
|
233
|
+
result.errors.push(`Invalid subcategory format: ${components.subcategory}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Validate technology
|
|
237
|
+
if (components.technology) {
|
|
238
|
+
if (!this.isValidTechnology(components.technology)) {
|
|
239
|
+
result.errors.push(`Invalid technology identifier: ${components.technology}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Validate version
|
|
243
|
+
if (components.version) {
|
|
244
|
+
try {
|
|
245
|
+
ValidationUtils.validateVersion(components.version);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
result.errors.push(`Invalid version format: ${components.version}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Component consistency checks
|
|
252
|
+
this.validateComponentConsistency(components, result);
|
|
253
|
+
}
|
|
254
|
+
async validateSemantics(path, result, options) {
|
|
255
|
+
result.metadata.checksPerformed.push('semantic-validation');
|
|
256
|
+
const components = result.pathAnalysis.components;
|
|
257
|
+
// Check for common naming patterns and best practices
|
|
258
|
+
if (components.subcategory) {
|
|
259
|
+
if (components.subcategory.includes('_') && components.subcategory.includes('-')) {
|
|
260
|
+
result.warnings.push('Subcategory uses both underscores and hyphens - consider consistent naming');
|
|
261
|
+
}
|
|
262
|
+
if (components.subcategory.length > 50) {
|
|
263
|
+
result.warnings.push('Subcategory name is very long - consider shortening for better usability');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Technology-specific validations
|
|
267
|
+
if (components.technology) {
|
|
268
|
+
const knownTechnologies = [
|
|
269
|
+
'typescript', 'javascript', 'python', 'java', 'csharp', 'go', 'rust',
|
|
270
|
+
'nextjs', 'react', 'vue', 'angular', 'nodejs', 'deno'
|
|
271
|
+
];
|
|
272
|
+
if (!knownTechnologies.includes(components.technology.toLowerCase())) {
|
|
273
|
+
result.warnings.push(`Technology '${components.technology}' is not in the list of commonly used technologies`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Version semantic checks
|
|
277
|
+
if (components.version) {
|
|
278
|
+
if (components.version.startsWith('0.')) {
|
|
279
|
+
result.warnings.push('Version indicates pre-release (0.x.x) - ensure this is intentional');
|
|
280
|
+
}
|
|
281
|
+
if (components.version.includes('alpha') || components.version.includes('beta')) {
|
|
282
|
+
result.warnings.push('Version indicates pre-release software');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Path semantics
|
|
286
|
+
if (result.pathAnalysis.pathType === 'technology-specific-unversioned') {
|
|
287
|
+
result.warnings.push('Path appears to specify technology but no version - consider adding version');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async checkPathExistence(path, result) {
|
|
291
|
+
result.metadata.checksPerformed.push('existence-check');
|
|
292
|
+
try {
|
|
293
|
+
const parsed = parsePath(path);
|
|
294
|
+
if (!parsed.filename) {
|
|
295
|
+
throw new Error('Filename is required to check artifact existence');
|
|
296
|
+
}
|
|
297
|
+
const artifact = await this.ucmClient.getArtifact(parsed.author, parsed.category, parsed.subcategory, parsed.filename, parsed.version);
|
|
298
|
+
result.existence = {
|
|
299
|
+
exists: true,
|
|
300
|
+
artifact: {
|
|
301
|
+
id: artifact.id,
|
|
302
|
+
name: artifact.metadata?.name,
|
|
303
|
+
lastUpdated: artifact.lastUpdated,
|
|
304
|
+
publishedAt: artifact.publishedAt
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
result.existence = {
|
|
310
|
+
exists: false,
|
|
311
|
+
error: error instanceof Error ? error.message : String(error)
|
|
312
|
+
};
|
|
313
|
+
if (result.pathAnalysis.isVersioned) {
|
|
314
|
+
// Try to check if a non-versioned path exists
|
|
315
|
+
await this.checkParentPathExistence(path, result);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async checkParentPathExistence(path, result) {
|
|
320
|
+
try {
|
|
321
|
+
const pathParts = path.split('/');
|
|
322
|
+
const parentPath = pathParts.slice(0, -1).join('/');
|
|
323
|
+
if (parentPath) {
|
|
324
|
+
const parsed = parsePath(parentPath);
|
|
325
|
+
const versions = await this.ucmClient.getArtifactVersions(parsed.author, parsed.category, parsed.subcategory);
|
|
326
|
+
if (versions && versions.length > 0) {
|
|
327
|
+
result.existence.parentExists = true;
|
|
328
|
+
result.existence.availableVersions = versions.map(v => v.metadata?.version).filter(Boolean);
|
|
329
|
+
result.suggestions.push(`Artifact exists but not this version. Available versions: ${result.existence.availableVersions.join(', ')}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
// Parent doesn't exist either
|
|
335
|
+
result.existence.parentExists = false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async findSimilarPaths(path, result) {
|
|
339
|
+
result.metadata.checksPerformed.push('similar-paths');
|
|
340
|
+
try {
|
|
341
|
+
// Extract search terms from the path
|
|
342
|
+
const components = result.pathAnalysis.components;
|
|
343
|
+
const searchTerms = [
|
|
344
|
+
components.author,
|
|
345
|
+
components.category,
|
|
346
|
+
components.subcategory
|
|
347
|
+
].filter(Boolean).join(' ');
|
|
348
|
+
if (searchTerms) {
|
|
349
|
+
const searchResults = await this.ucmClient.searchArtifacts({
|
|
350
|
+
category: components.category,
|
|
351
|
+
subcategory: components.subcategory,
|
|
352
|
+
limit: 5
|
|
353
|
+
});
|
|
354
|
+
result.similar = searchResults.map(artifact => ({
|
|
355
|
+
path: artifact.path,
|
|
356
|
+
name: artifact.metadata?.name,
|
|
357
|
+
similarity: this.calculatePathSimilarity(path, artifact.path),
|
|
358
|
+
reason: this.generateSimilarityReason(path, artifact.path)
|
|
359
|
+
})).filter(item => item.similarity > 0.3)
|
|
360
|
+
.sort((a, b) => b.similarity - a.similarity);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
result.similar = [];
|
|
365
|
+
this.logger.debug('ValidatePathController', 'Could not find similar paths', '', error instanceof Error ? error.message : String(error));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async generateSuggestions(path, result, options) {
|
|
369
|
+
const suggestions = result.suggestions;
|
|
370
|
+
// Format-based suggestions
|
|
371
|
+
if (result.errors.some((e) => e.includes('invalid characters'))) {
|
|
372
|
+
suggestions.push('Remove special characters and use only alphanumeric, hyphens, underscores, and dots');
|
|
373
|
+
}
|
|
374
|
+
if (result.errors.some((e) => e.includes('double slashes'))) {
|
|
375
|
+
suggestions.push('Remove double slashes from the path');
|
|
376
|
+
}
|
|
377
|
+
// Component-based suggestions
|
|
378
|
+
const components = result.pathAnalysis.components;
|
|
379
|
+
if (components.author && !this.isValidAuthorId(components.author)) {
|
|
380
|
+
suggestions.push('Author ID should be lowercase alphanumeric with hyphens/underscores only');
|
|
381
|
+
}
|
|
382
|
+
if (result.pathAnalysis.pathType === 'technology-specific-unversioned') {
|
|
383
|
+
suggestions.push('Consider adding a version number as the last component');
|
|
384
|
+
}
|
|
385
|
+
// Existence-based suggestions
|
|
386
|
+
if (result.existence && !result.existence.exists && result.existence.availableVersions) {
|
|
387
|
+
const versions = result.existence.availableVersions;
|
|
388
|
+
if (versions.length > 0) {
|
|
389
|
+
suggestions.push(`Try one of these existing versions: ${versions.slice(0, 3).join(', ')}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Similar path suggestions
|
|
393
|
+
if (result.similar && result.similar.length > 0) {
|
|
394
|
+
const topSimilar = result.similar[0];
|
|
395
|
+
suggestions.push(`Did you mean: ${topSimilar.path}? (${topSimilar.reason})`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
applyStrictModeValidation(path, components, result) {
|
|
399
|
+
// Strict mode rules
|
|
400
|
+
for (const component of components) {
|
|
401
|
+
if (component.length < 2) {
|
|
402
|
+
result.errors.push('In strict mode, all path components must be at least 2 characters long');
|
|
403
|
+
}
|
|
404
|
+
if (component.includes('_') && component.includes('-')) {
|
|
405
|
+
result.errors.push('In strict mode, components cannot mix underscores and hyphens');
|
|
406
|
+
}
|
|
407
|
+
if (!/^[a-z0-9\-_\.]+$/.test(component)) {
|
|
408
|
+
result.errors.push('In strict mode, components must be lowercase');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// Must be versioned in strict mode
|
|
412
|
+
if (!result.pathAnalysis.isVersioned) {
|
|
413
|
+
result.errors.push('In strict mode, paths must include a version number');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
validateComponentConsistency(components, result) {
|
|
417
|
+
// Check for naming consistency
|
|
418
|
+
if (components.subcategory && components.technology) {
|
|
419
|
+
if (components.subcategory.toLowerCase().includes(components.technology.toLowerCase())) {
|
|
420
|
+
result.warnings.push('Subcategory name includes technology - this may be redundant');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Check for reasonable relationships
|
|
424
|
+
if (components.category === 'commands' && components.technology === 'html') {
|
|
425
|
+
result.warnings.push('HTML technology unusual for commands category');
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// Helper validation methods
|
|
429
|
+
isValidAuthorId(authorId) {
|
|
430
|
+
return /^[a-zA-Z0-9\-_]+$/.test(authorId) && authorId.length >= 2 && authorId.length <= 50;
|
|
431
|
+
}
|
|
432
|
+
isValidSubcategory(subcategory) {
|
|
433
|
+
return /^[a-zA-Z0-9\-_]+$/.test(subcategory) && subcategory.length >= 2 && subcategory.length <= 50;
|
|
434
|
+
}
|
|
435
|
+
isValidTechnology(technology) {
|
|
436
|
+
return /^[a-zA-Z0-9\-_]+$/.test(technology) && technology.length >= 2 && technology.length <= 20;
|
|
437
|
+
}
|
|
438
|
+
calculatePathSimilarity(path1, path2) {
|
|
439
|
+
const components1 = path1.split('/');
|
|
440
|
+
const components2 = path2.split('/');
|
|
441
|
+
let matches = 0;
|
|
442
|
+
const maxLength = Math.max(components1.length, components2.length);
|
|
443
|
+
for (let i = 0; i < Math.min(components1.length, components2.length); i++) {
|
|
444
|
+
if (components1[i].toLowerCase() === components2[i].toLowerCase()) {
|
|
445
|
+
matches++;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return matches / maxLength;
|
|
449
|
+
}
|
|
450
|
+
generateSimilarityReason(originalPath, similarPath) {
|
|
451
|
+
const orig = originalPath.split('/');
|
|
452
|
+
const sim = similarPath.split('/');
|
|
453
|
+
if (orig[0] === sim[0]) {
|
|
454
|
+
if (orig[1] === sim[1]) {
|
|
455
|
+
return 'Same author and category';
|
|
456
|
+
}
|
|
457
|
+
return 'Same author';
|
|
458
|
+
}
|
|
459
|
+
if (orig[1] === sim[1]) {
|
|
460
|
+
return 'Same category';
|
|
461
|
+
}
|
|
462
|
+
return 'Similar structure';
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
//# sourceMappingURL=ValidatePathController.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface ArtifactMetadata {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
version: string;
|
|
5
|
+
contractVersion?: string;
|
|
6
|
+
author: string;
|
|
7
|
+
repository: string;
|
|
8
|
+
category: string;
|
|
9
|
+
subcategory: string;
|
|
10
|
+
technology?: string;
|
|
11
|
+
tags?: string[];
|
|
12
|
+
dependencies?: {
|
|
13
|
+
services?: string[];
|
|
14
|
+
commands?: string[];
|
|
15
|
+
external?: Record<string, string>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface ArtifactData {
|
|
19
|
+
id: string;
|
|
20
|
+
path: string;
|
|
21
|
+
metadata: ArtifactMetadata;
|
|
22
|
+
content?: string;
|
|
23
|
+
examples?: any[];
|
|
24
|
+
lastUpdated: string;
|
|
25
|
+
publishedAt: string;
|
|
26
|
+
}
|
|
27
|
+
export interface AuthorData {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
artifactCount?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface UcmApiResponse<T = any> {
|
|
35
|
+
data: T;
|
|
36
|
+
success: boolean;
|
|
37
|
+
message?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=UcmApiTypes.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare enum McpErrorCode {
|
|
2
|
+
ParseError = -32700,// Invalid JSON received
|
|
3
|
+
InvalidRequest = -32600,// Request is not valid MCP
|
|
4
|
+
MethodNotFound = -32601,// Method does not exist
|
|
5
|
+
InvalidParams = -32602,// Invalid method parameters
|
|
6
|
+
InternalError = -32603,// Internal server error
|
|
7
|
+
ExternalServiceUnavailable = -32001
|
|
8
|
+
}
|
|
9
|
+
export interface McpErrorResponse {
|
|
10
|
+
error: {
|
|
11
|
+
code: McpErrorCode;
|
|
12
|
+
message: string;
|
|
13
|
+
data?: any;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare class McpError extends Error {
|
|
17
|
+
code: McpErrorCode;
|
|
18
|
+
data?: any | undefined;
|
|
19
|
+
constructor(code: McpErrorCode, message: string, data?: any | undefined);
|
|
20
|
+
toResponse(): McpErrorResponse;
|
|
21
|
+
}
|
|
22
|
+
export declare class McpErrorHandler {
|
|
23
|
+
static formatError(error: any): McpError;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=McpErrorHandler.d.ts.map
|