@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,605 @@
|
|
|
1
|
+
import { BaseToolController } from '../base/BaseToolController.js';
|
|
2
|
+
import { ValidationUtils } from '../../utils/ValidationUtils.js';
|
|
3
|
+
import { parsePath } from '../../utils/PathUtils.js';
|
|
4
|
+
export class PublishArtifactController extends BaseToolController {
|
|
5
|
+
constructor(ucmClient, logger) {
|
|
6
|
+
super(ucmClient, logger);
|
|
7
|
+
}
|
|
8
|
+
get name() {
|
|
9
|
+
return 'mcp_ucm_publish_artifact';
|
|
10
|
+
}
|
|
11
|
+
get description() {
|
|
12
|
+
return 'Publish a new artifact or version to the UCM repository with comprehensive validation and metadata';
|
|
13
|
+
}
|
|
14
|
+
get inputSchema() {
|
|
15
|
+
return {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
path: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Target artifact path (e.g., "author/category/subcategory/technology/version" or "author/category/subcategory/version")',
|
|
21
|
+
minLength: 10,
|
|
22
|
+
maxLength: 200
|
|
23
|
+
},
|
|
24
|
+
content: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Artifact source content (code, documentation, etc.)',
|
|
27
|
+
minLength: 1,
|
|
28
|
+
maxLength: 1048576 // 1MB limit
|
|
29
|
+
},
|
|
30
|
+
metadata: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
name: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Human-readable name for the artifact',
|
|
36
|
+
minLength: 1,
|
|
37
|
+
maxLength: 100
|
|
38
|
+
},
|
|
39
|
+
description: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Detailed description of the artifact',
|
|
42
|
+
minLength: 10,
|
|
43
|
+
maxLength: 2000
|
|
44
|
+
},
|
|
45
|
+
version: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
pattern: '^[0-9]+\\.[0-9]+\\.[0-9]+(?:-[a-zA-Z0-9\\-_]+)?$',
|
|
48
|
+
description: 'Semantic version (e.g., "1.0.0", "2.1.0-beta")'
|
|
49
|
+
},
|
|
50
|
+
contractVersion: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
pattern: '^[0-9]+\\.[0-9]+$',
|
|
53
|
+
description: 'API contract version (e.g., "1.0")'
|
|
54
|
+
},
|
|
55
|
+
dependencies: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
services: {
|
|
59
|
+
type: 'array',
|
|
60
|
+
items: { type: 'string' },
|
|
61
|
+
description: 'Required service interfaces'
|
|
62
|
+
},
|
|
63
|
+
commands: {
|
|
64
|
+
type: 'array',
|
|
65
|
+
items: { type: 'string' },
|
|
66
|
+
description: 'Required command dependencies'
|
|
67
|
+
},
|
|
68
|
+
external: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
additionalProperties: { type: 'string' },
|
|
71
|
+
description: 'External package dependencies'
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
tags: {
|
|
76
|
+
type: 'array',
|
|
77
|
+
items: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
pattern: '^[a-zA-Z0-9\\-_]+$'
|
|
80
|
+
},
|
|
81
|
+
maxItems: 20,
|
|
82
|
+
description: 'Searchable tags for categorization'
|
|
83
|
+
},
|
|
84
|
+
license: {
|
|
85
|
+
type: 'string',
|
|
86
|
+
description: 'License identifier (e.g., "MIT", "Apache-2.0")'
|
|
87
|
+
},
|
|
88
|
+
maintainers: {
|
|
89
|
+
type: 'array',
|
|
90
|
+
items: { type: 'string' },
|
|
91
|
+
description: 'List of maintainer IDs'
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
required: ['name', 'description', 'version']
|
|
95
|
+
},
|
|
96
|
+
examples: {
|
|
97
|
+
type: 'array',
|
|
98
|
+
items: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
title: { type: 'string' },
|
|
102
|
+
description: { type: 'string' },
|
|
103
|
+
code: { type: 'string' },
|
|
104
|
+
language: { type: 'string' },
|
|
105
|
+
complexity: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
enum: ['beginner', 'intermediate', 'advanced']
|
|
108
|
+
},
|
|
109
|
+
runnable: { type: 'boolean', default: true }
|
|
110
|
+
},
|
|
111
|
+
required: ['title', 'code']
|
|
112
|
+
},
|
|
113
|
+
description: 'Usage examples and documentation'
|
|
114
|
+
},
|
|
115
|
+
publishOptions: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
properties: {
|
|
118
|
+
validateContent: {
|
|
119
|
+
type: 'boolean',
|
|
120
|
+
default: true,
|
|
121
|
+
description: 'Perform content validation before publishing'
|
|
122
|
+
},
|
|
123
|
+
generateDocs: {
|
|
124
|
+
type: 'boolean',
|
|
125
|
+
default: false,
|
|
126
|
+
description: 'Auto-generate documentation from content'
|
|
127
|
+
},
|
|
128
|
+
notifyMaintainers: {
|
|
129
|
+
type: 'boolean',
|
|
130
|
+
default: false,
|
|
131
|
+
description: 'Notify existing maintainers of new version'
|
|
132
|
+
},
|
|
133
|
+
replaceExisting: {
|
|
134
|
+
type: 'boolean',
|
|
135
|
+
default: false,
|
|
136
|
+
description: 'Replace existing version if it exists (dangerous)'
|
|
137
|
+
},
|
|
138
|
+
draft: {
|
|
139
|
+
type: 'boolean',
|
|
140
|
+
default: false,
|
|
141
|
+
description: 'Publish as draft (not publicly visible)'
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
required: ['path', 'content', 'metadata']
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async handleExecute(params) {
|
|
150
|
+
const { path, content, metadata, examples = [], publishOptions = {} } = params;
|
|
151
|
+
// Validate inputs
|
|
152
|
+
await this.validatePublishRequest(path, content, metadata, examples, publishOptions);
|
|
153
|
+
this.logger.debug('PublishArtifactController', `Publishing artifact to: ${path}`);
|
|
154
|
+
try {
|
|
155
|
+
// Pre-publish validation and preparation
|
|
156
|
+
const validationResult = await this.performPrePublishValidation(path, content, metadata, examples, publishOptions);
|
|
157
|
+
if (!validationResult.valid) {
|
|
158
|
+
throw this.formatError(new Error(`Validation failed: ${validationResult.errors.join(', ')}`));
|
|
159
|
+
}
|
|
160
|
+
// Check for existing artifact and handle conflicts
|
|
161
|
+
await this.handleExistingArtifact(path, publishOptions);
|
|
162
|
+
// Prepare publish data
|
|
163
|
+
const publishData = await this.preparePublishData(content, metadata, examples, publishOptions);
|
|
164
|
+
// Parse the path to get component parts
|
|
165
|
+
const parsed = parsePath(path);
|
|
166
|
+
if (!parsed.author || !parsed.category || !parsed.subcategory || !parsed.filename) {
|
|
167
|
+
throw this.formatError(new Error(`Validation failed: You must supply author,category,subcategory,filename. 1 or more of these parameters are missing.`));
|
|
168
|
+
}
|
|
169
|
+
// Publish to UCM API
|
|
170
|
+
const publishResult = await this.ucmClient.publishArtifact(parsed.author, parsed.category, parsed.subcategory, publishData);
|
|
171
|
+
// Post-publish processing
|
|
172
|
+
const postProcessResult = await this.performPostPublishProcessing(publishResult, publishOptions);
|
|
173
|
+
const response = {
|
|
174
|
+
success: true,
|
|
175
|
+
artifact: {
|
|
176
|
+
id: publishResult.id,
|
|
177
|
+
path: publishResult.path,
|
|
178
|
+
name: metadata.name,
|
|
179
|
+
version: metadata.version,
|
|
180
|
+
author: metadata.author || this.extractAuthorFromPath(path),
|
|
181
|
+
category: metadata.category || this.extractCategoryFromPath(path),
|
|
182
|
+
publishedAt: publishResult.publishedAt || new Date().toISOString()
|
|
183
|
+
},
|
|
184
|
+
validation: validationResult,
|
|
185
|
+
publishing: {
|
|
186
|
+
method: publishOptions.replaceExisting ? 'replaced' : 'created',
|
|
187
|
+
draft: publishOptions.draft || false,
|
|
188
|
+
contentSize: content.length,
|
|
189
|
+
examplesCount: examples.length
|
|
190
|
+
},
|
|
191
|
+
postProcessing: postProcessResult,
|
|
192
|
+
urls: {
|
|
193
|
+
view: this.buildViewUrl(publishResult.path),
|
|
194
|
+
download: this.buildDownloadUrl(publishResult.path),
|
|
195
|
+
api: this.buildApiUrl(publishResult.path)
|
|
196
|
+
},
|
|
197
|
+
metadata: {
|
|
198
|
+
timestamp: new Date().toISOString(),
|
|
199
|
+
clientInfo: 'UCM-MCP-Server',
|
|
200
|
+
publishDurationMs: 0 // Would be measured in real implementation
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
this.logger.info('PublishArtifactController', `Successfully published artifact: ${path}`);
|
|
204
|
+
return response;
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
this.logger.error('PublishArtifactController', `Failed to publish artifact: ${path}`, '', error);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async validatePublishRequest(path, content, metadata, examples, publishOptions) {
|
|
212
|
+
// Validate path format
|
|
213
|
+
ValidationUtils.validateArtifactPath(path);
|
|
214
|
+
// Validate content size
|
|
215
|
+
ValidationUtils.validateContentSize(content);
|
|
216
|
+
// Validate metadata fields
|
|
217
|
+
if (!metadata.name || typeof metadata.name !== 'string') {
|
|
218
|
+
throw this.formatError(new Error('Metadata name is required'));
|
|
219
|
+
}
|
|
220
|
+
if (!metadata.description || metadata.description.length < 10) {
|
|
221
|
+
throw this.formatError(new Error('Metadata description must be at least 10 characters'));
|
|
222
|
+
}
|
|
223
|
+
if (!metadata.version) {
|
|
224
|
+
throw this.formatError(new Error('Metadata version is required'));
|
|
225
|
+
}
|
|
226
|
+
ValidationUtils.validateVersion(metadata.version);
|
|
227
|
+
// Validate path components match metadata
|
|
228
|
+
const pathComponents = this.parseArtifactPath(path);
|
|
229
|
+
if (metadata.author && metadata.author !== pathComponents.author) {
|
|
230
|
+
throw this.formatError(new Error('Metadata author must match path author'));
|
|
231
|
+
}
|
|
232
|
+
if (metadata.category && metadata.category !== pathComponents.category) {
|
|
233
|
+
throw this.formatError(new Error('Metadata category must match path category'));
|
|
234
|
+
}
|
|
235
|
+
// Validate examples if provided
|
|
236
|
+
if (examples && examples.length > 0) {
|
|
237
|
+
for (let i = 0; i < examples.length; i++) {
|
|
238
|
+
const example = examples[i];
|
|
239
|
+
if (!example.title || !example.code) {
|
|
240
|
+
throw this.formatError(new Error(`Example ${i + 1} missing required title or code`));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Validate tags if provided
|
|
245
|
+
if (metadata.tags) {
|
|
246
|
+
for (const tag of metadata.tags) {
|
|
247
|
+
if (typeof tag !== 'string' || !tag.match(/^[a-zA-Z0-9\-_]+$/)) {
|
|
248
|
+
throw this.formatError(new Error(`Invalid tag format: ${tag}`));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async performPrePublishValidation(path, content, metadata, examples, publishOptions) {
|
|
254
|
+
const validationResult = {
|
|
255
|
+
valid: true,
|
|
256
|
+
errors: [],
|
|
257
|
+
warnings: [],
|
|
258
|
+
suggestions: [],
|
|
259
|
+
contentAnalysis: {},
|
|
260
|
+
securityScan: {}
|
|
261
|
+
};
|
|
262
|
+
// Content validation
|
|
263
|
+
if (publishOptions.validateContent !== false) {
|
|
264
|
+
const contentValidation = await this.validateContent(content, metadata);
|
|
265
|
+
validationResult.contentAnalysis = contentValidation;
|
|
266
|
+
if (!contentValidation.valid) {
|
|
267
|
+
validationResult.valid = false;
|
|
268
|
+
validationResult.errors.push(...contentValidation.errors);
|
|
269
|
+
}
|
|
270
|
+
validationResult.warnings.push(...contentValidation.warnings);
|
|
271
|
+
validationResult.suggestions.push(...contentValidation.suggestions);
|
|
272
|
+
}
|
|
273
|
+
// Security validation
|
|
274
|
+
const securityScan = await this.performSecurityScan(content);
|
|
275
|
+
validationResult.securityScan = securityScan;
|
|
276
|
+
if (securityScan.hasVulnerabilities) {
|
|
277
|
+
validationResult.valid = false;
|
|
278
|
+
validationResult.errors.push(...securityScan.vulnerabilities);
|
|
279
|
+
}
|
|
280
|
+
// Dependency validation
|
|
281
|
+
if (metadata.dependencies) {
|
|
282
|
+
const depValidation = await this.validateDependencies(metadata.dependencies);
|
|
283
|
+
if (!depValidation.valid) {
|
|
284
|
+
validationResult.warnings.push(...depValidation.warnings);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Quality checks
|
|
288
|
+
const qualityChecks = this.performQualityChecks(metadata, content, examples);
|
|
289
|
+
validationResult.warnings.push(...qualityChecks.warnings);
|
|
290
|
+
validationResult.suggestions.push(...qualityChecks.suggestions);
|
|
291
|
+
return validationResult;
|
|
292
|
+
}
|
|
293
|
+
async handleExistingArtifact(path, publishOptions) {
|
|
294
|
+
try {
|
|
295
|
+
const parsed = parsePath(path);
|
|
296
|
+
if (!parsed.filename) {
|
|
297
|
+
throw new Error('Filename is required to check existing artifact');
|
|
298
|
+
}
|
|
299
|
+
const existingArtifact = await this.ucmClient.getArtifact(parsed.author, parsed.category, parsed.subcategory, parsed.filename, parsed.version);
|
|
300
|
+
if (existingArtifact && !publishOptions.replaceExisting) {
|
|
301
|
+
throw this.formatError(new Error(`Artifact already exists at path: ${path}. Use replaceExisting option to overwrite.`));
|
|
302
|
+
}
|
|
303
|
+
if (existingArtifact && publishOptions.replaceExisting) {
|
|
304
|
+
this.logger.warn('PublishArtifactController', `Replacing existing artifact at: ${path}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
// If artifact doesn't exist, that's what we want for new publications
|
|
309
|
+
if (error instanceof Error && error.message?.includes('not found')) {
|
|
310
|
+
return; // This is expected for new artifacts
|
|
311
|
+
}
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async preparePublishData(content, metadata, examples, publishOptions) {
|
|
316
|
+
const publishData = {
|
|
317
|
+
content,
|
|
318
|
+
metadata: {
|
|
319
|
+
...metadata,
|
|
320
|
+
// Add computed metadata
|
|
321
|
+
contentHash: this.calculateContentHash(content),
|
|
322
|
+
contentType: this.detectContentType(content),
|
|
323
|
+
publishedBy: 'MCP-Server', // In real implementation, would use authenticated user
|
|
324
|
+
publishedAt: new Date().toISOString(),
|
|
325
|
+
draft: publishOptions.draft || false
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
// Add examples if provided
|
|
329
|
+
if (examples && examples.length > 0) {
|
|
330
|
+
publishData.examples = examples.map((example, index) => ({
|
|
331
|
+
...example,
|
|
332
|
+
id: index + 1,
|
|
333
|
+
language: example.language || this.detectLanguageFromContent(example.code)
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
// Generate documentation if requested
|
|
337
|
+
if (publishOptions.generateDocs) {
|
|
338
|
+
publishData.generatedDocs = await this.generateDocumentation(content, metadata);
|
|
339
|
+
}
|
|
340
|
+
return publishData;
|
|
341
|
+
}
|
|
342
|
+
async performPostPublishProcessing(publishResult, publishOptions) {
|
|
343
|
+
const postProcessing = {
|
|
344
|
+
actions: [],
|
|
345
|
+
notifications: [],
|
|
346
|
+
indexing: {
|
|
347
|
+
searchIndexed: false,
|
|
348
|
+
cacheUpdated: false
|
|
349
|
+
},
|
|
350
|
+
analytics: {
|
|
351
|
+
recorded: false
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
// Index for search
|
|
355
|
+
try {
|
|
356
|
+
await this.indexForSearch(publishResult);
|
|
357
|
+
postProcessing.indexing.searchIndexed = true;
|
|
358
|
+
postProcessing.actions.push('Indexed for search');
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
this.logger.warn('PublishArtifactController', 'Failed to index for search', '', error);
|
|
362
|
+
}
|
|
363
|
+
// Update caches
|
|
364
|
+
try {
|
|
365
|
+
await this.updateCaches(publishResult);
|
|
366
|
+
postProcessing.indexing.cacheUpdated = true;
|
|
367
|
+
postProcessing.actions.push('Updated caches');
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
this.logger.warn('PublishArtifactController', 'Failed to update caches', '', error);
|
|
371
|
+
}
|
|
372
|
+
// Send notifications if requested
|
|
373
|
+
if (publishOptions.notifyMaintainers) {
|
|
374
|
+
try {
|
|
375
|
+
const notifications = await this.sendMaintainerNotifications(publishResult);
|
|
376
|
+
postProcessing.notifications = notifications;
|
|
377
|
+
postProcessing.actions.push('Notified maintainers');
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
this.logger.warn('PublishArtifactController', 'Failed to send notifications', '', error);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Record analytics
|
|
384
|
+
try {
|
|
385
|
+
await this.recordPublishAnalytics(publishResult);
|
|
386
|
+
postProcessing.analytics.recorded = true;
|
|
387
|
+
postProcessing.actions.push('Recorded analytics');
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
this.logger.warn('PublishArtifactController', 'Failed to record analytics', '', error);
|
|
391
|
+
}
|
|
392
|
+
return postProcessing;
|
|
393
|
+
}
|
|
394
|
+
// Validation helper methods
|
|
395
|
+
async validateContent(content, metadata) {
|
|
396
|
+
const validation = {
|
|
397
|
+
valid: true,
|
|
398
|
+
errors: [],
|
|
399
|
+
warnings: [],
|
|
400
|
+
suggestions: []
|
|
401
|
+
};
|
|
402
|
+
// Check content type consistency
|
|
403
|
+
const detectedType = this.detectContentType(content);
|
|
404
|
+
const pathType = this.extractTechnologyFromMetadata(metadata);
|
|
405
|
+
if (pathType && detectedType !== pathType && detectedType !== 'text') {
|
|
406
|
+
validation.warnings.push(`Content type (${detectedType}) may not match expected type (${pathType})`);
|
|
407
|
+
}
|
|
408
|
+
// Check for potential issues
|
|
409
|
+
if (content.includes('TODO') || content.includes('FIXME')) {
|
|
410
|
+
validation.warnings.push('Content contains TODO or FIXME comments');
|
|
411
|
+
}
|
|
412
|
+
if (content.length < 100) {
|
|
413
|
+
validation.suggestions.push('Consider adding more comprehensive implementation');
|
|
414
|
+
}
|
|
415
|
+
// Syntax validation for code content
|
|
416
|
+
if (this.isCodeContent(detectedType)) {
|
|
417
|
+
const syntaxValidation = this.validateSyntax(content, detectedType);
|
|
418
|
+
if (!syntaxValidation.valid) {
|
|
419
|
+
validation.errors.push(...syntaxValidation.errors);
|
|
420
|
+
validation.valid = false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return validation;
|
|
424
|
+
}
|
|
425
|
+
async performSecurityScan(content) {
|
|
426
|
+
const scan = {
|
|
427
|
+
hasVulnerabilities: false,
|
|
428
|
+
vulnerabilities: [],
|
|
429
|
+
warnings: [],
|
|
430
|
+
score: 100
|
|
431
|
+
};
|
|
432
|
+
// Check for potential security issues
|
|
433
|
+
const securityPatterns = [
|
|
434
|
+
{ pattern: /password\s*=\s*['"]/i, message: 'Hardcoded password detected' },
|
|
435
|
+
{ pattern: /api[_-]?key\s*=\s*['"]/i, message: 'Hardcoded API key detected' },
|
|
436
|
+
{ pattern: /secret\s*=\s*['"]/i, message: 'Hardcoded secret detected' },
|
|
437
|
+
{ pattern: /eval\s*\(/i, message: 'Use of eval() function detected' },
|
|
438
|
+
{ pattern: /innerHTML\s*=/i, message: 'Potential XSS vulnerability with innerHTML' }
|
|
439
|
+
];
|
|
440
|
+
for (const { pattern, message } of securityPatterns) {
|
|
441
|
+
if (pattern.test(content)) {
|
|
442
|
+
scan.hasVulnerabilities = true;
|
|
443
|
+
scan.vulnerabilities.push(message);
|
|
444
|
+
scan.score -= 20;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return scan;
|
|
448
|
+
}
|
|
449
|
+
async validateDependencies(dependencies) {
|
|
450
|
+
const validation = {
|
|
451
|
+
valid: true,
|
|
452
|
+
warnings: []
|
|
453
|
+
};
|
|
454
|
+
// Check if external dependencies are reasonable
|
|
455
|
+
if (dependencies.external) {
|
|
456
|
+
const externalCount = Object.keys(dependencies.external).length;
|
|
457
|
+
if (externalCount > 10) {
|
|
458
|
+
validation.warnings.push(`High number of external dependencies (${externalCount})`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return validation;
|
|
462
|
+
}
|
|
463
|
+
performQualityChecks(metadata, content, examples) {
|
|
464
|
+
const checks = {
|
|
465
|
+
warnings: [],
|
|
466
|
+
suggestions: []
|
|
467
|
+
};
|
|
468
|
+
// Check metadata completeness
|
|
469
|
+
if (!metadata.tags || metadata.tags.length === 0) {
|
|
470
|
+
checks.suggestions.push('Consider adding tags for better discoverability');
|
|
471
|
+
}
|
|
472
|
+
if (!metadata.dependencies) {
|
|
473
|
+
checks.suggestions.push('Consider documenting dependencies if any exist');
|
|
474
|
+
}
|
|
475
|
+
// Check for examples
|
|
476
|
+
if (!examples || examples.length === 0) {
|
|
477
|
+
checks.suggestions.push('Consider adding usage examples');
|
|
478
|
+
}
|
|
479
|
+
// Check content quality
|
|
480
|
+
if (content.length < 500) {
|
|
481
|
+
checks.suggestions.push('Consider providing more detailed implementation');
|
|
482
|
+
}
|
|
483
|
+
return checks;
|
|
484
|
+
}
|
|
485
|
+
// Helper methods
|
|
486
|
+
parseArtifactPath(path) {
|
|
487
|
+
const parts = path.split('/');
|
|
488
|
+
if (parts.length === 4) {
|
|
489
|
+
// author/category/subcategory/version (technology-agnostic)
|
|
490
|
+
return {
|
|
491
|
+
author: parts[0],
|
|
492
|
+
category: parts[1],
|
|
493
|
+
subcategory: parts[2],
|
|
494
|
+
technology: null,
|
|
495
|
+
version: parts[3]
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
else if (parts.length === 5) {
|
|
499
|
+
// author/category/subcategory/technology/version
|
|
500
|
+
return {
|
|
501
|
+
author: parts[0],
|
|
502
|
+
category: parts[1],
|
|
503
|
+
subcategory: parts[2],
|
|
504
|
+
technology: parts[3],
|
|
505
|
+
version: parts[4]
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
throw new Error('Invalid artifact path format');
|
|
509
|
+
}
|
|
510
|
+
extractAuthorFromPath(path) {
|
|
511
|
+
return path.split('/')[0];
|
|
512
|
+
}
|
|
513
|
+
extractCategoryFromPath(path) {
|
|
514
|
+
return path.split('/')[1];
|
|
515
|
+
}
|
|
516
|
+
extractTechnologyFromMetadata(metadata) {
|
|
517
|
+
return metadata.technology || null;
|
|
518
|
+
}
|
|
519
|
+
calculateContentHash(content) {
|
|
520
|
+
// Simple hash calculation (in real implementation, would use crypto)
|
|
521
|
+
let hash = 0;
|
|
522
|
+
for (let i = 0; i < content.length; i++) {
|
|
523
|
+
const char = content.charCodeAt(i);
|
|
524
|
+
hash = ((hash << 5) - hash) + char;
|
|
525
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
526
|
+
}
|
|
527
|
+
return hash.toString(16);
|
|
528
|
+
}
|
|
529
|
+
detectContentType(content) {
|
|
530
|
+
if (content.includes('interface ') || content.includes('export class'))
|
|
531
|
+
return 'typescript';
|
|
532
|
+
if (content.includes('function ') || content.includes('const '))
|
|
533
|
+
return 'javascript';
|
|
534
|
+
if (content.includes('def ') || content.includes('import '))
|
|
535
|
+
return 'python';
|
|
536
|
+
if (content.includes('```') || content.includes('# '))
|
|
537
|
+
return 'markdown';
|
|
538
|
+
return 'text';
|
|
539
|
+
}
|
|
540
|
+
detectLanguageFromContent(code) {
|
|
541
|
+
return this.detectContentType(code);
|
|
542
|
+
}
|
|
543
|
+
isCodeContent(contentType) {
|
|
544
|
+
return ['typescript', 'javascript', 'python', 'java', 'cpp'].includes(contentType);
|
|
545
|
+
}
|
|
546
|
+
validateSyntax(content, contentType) {
|
|
547
|
+
// Simplified syntax validation
|
|
548
|
+
const validation = {
|
|
549
|
+
valid: true,
|
|
550
|
+
errors: []
|
|
551
|
+
};
|
|
552
|
+
// Basic bracket matching
|
|
553
|
+
const brackets = { '(': ')', '[': ']', '{': '}' };
|
|
554
|
+
const stack = [];
|
|
555
|
+
for (const char of content) {
|
|
556
|
+
if (char in brackets) {
|
|
557
|
+
stack.push(brackets[char]);
|
|
558
|
+
}
|
|
559
|
+
else if (Object.values(brackets).includes(char)) {
|
|
560
|
+
if (stack.length === 0 || stack.pop() !== char) {
|
|
561
|
+
validation.valid = false;
|
|
562
|
+
validation.errors.push('Mismatched brackets detected');
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (stack.length > 0) {
|
|
568
|
+
validation.valid = false;
|
|
569
|
+
validation.errors.push('Unclosed brackets detected');
|
|
570
|
+
}
|
|
571
|
+
return validation;
|
|
572
|
+
}
|
|
573
|
+
async generateDocumentation(content, metadata) {
|
|
574
|
+
// Simplified documentation generation
|
|
575
|
+
return {
|
|
576
|
+
generated: true,
|
|
577
|
+
sections: ['overview', 'usage', 'api'],
|
|
578
|
+
content: `# ${metadata.name}\n\n${metadata.description}\n\n## Usage\n\nSee examples for usage instructions.`
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
buildViewUrl(path) {
|
|
582
|
+
return `/browse/artifacts/${path}`;
|
|
583
|
+
}
|
|
584
|
+
buildDownloadUrl(path) {
|
|
585
|
+
return `/api/v1/authors/${path}/download`;
|
|
586
|
+
}
|
|
587
|
+
buildApiUrl(path) {
|
|
588
|
+
return `/api/v1/authors/${path}`;
|
|
589
|
+
}
|
|
590
|
+
// Post-processing methods (simplified for demo)
|
|
591
|
+
async indexForSearch(publishResult) {
|
|
592
|
+
// Would integrate with search indexing system
|
|
593
|
+
}
|
|
594
|
+
async updateCaches(publishResult) {
|
|
595
|
+
// Would update various caches
|
|
596
|
+
}
|
|
597
|
+
async sendMaintainerNotifications(publishResult) {
|
|
598
|
+
// Would send notifications to maintainers
|
|
599
|
+
return ['Notification sent to maintainers'];
|
|
600
|
+
}
|
|
601
|
+
async recordPublishAnalytics(publishResult) {
|
|
602
|
+
// Would record analytics data
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
//# sourceMappingURL=PublishArtifactController.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { IMcpTool } from '../../interfaces/IMcpTool.js';
|
|
2
|
+
import { UcmApiClient } from '../../clients/UcmApiClient.js';
|
|
3
|
+
import { ILogger } from '../../interfaces/ILogger.js';
|
|
4
|
+
export declare abstract class BaseToolController implements IMcpTool {
|
|
5
|
+
protected ucmClient: UcmApiClient;
|
|
6
|
+
protected logger: ILogger;
|
|
7
|
+
protected publishingAuthorId?: string | undefined;
|
|
8
|
+
constructor(ucmClient: UcmApiClient, logger: ILogger, publishingAuthorId?: string | undefined);
|
|
9
|
+
abstract get name(): string;
|
|
10
|
+
abstract get description(): string;
|
|
11
|
+
abstract get inputSchema(): any;
|
|
12
|
+
execute(params: any): Promise<any>;
|
|
13
|
+
protected abstract handleExecute(params: any): Promise<any>;
|
|
14
|
+
protected validateParams(params: any): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=BaseToolController.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { McpError, McpErrorCode } from '../../utils/McpErrorHandler.js';
|
|
2
|
+
export class BaseToolController {
|
|
3
|
+
ucmClient;
|
|
4
|
+
logger;
|
|
5
|
+
publishingAuthorId;
|
|
6
|
+
constructor(ucmClient, logger, publishingAuthorId) {
|
|
7
|
+
this.ucmClient = ucmClient;
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
this.publishingAuthorId = publishingAuthorId;
|
|
10
|
+
}
|
|
11
|
+
async execute(params) {
|
|
12
|
+
try {
|
|
13
|
+
this.validateParams(params);
|
|
14
|
+
return await this.handleExecute(params);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
this.logger.error('BaseToolController', `Tool ${this.name} execution failed`, '', error);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
validateParams(params) {
|
|
22
|
+
// Basic validation - can be overridden by specific tools
|
|
23
|
+
if (this.inputSchema.required) {
|
|
24
|
+
for (const field of this.inputSchema.required) {
|
|
25
|
+
if (params[field] === undefined || params[field] === null) {
|
|
26
|
+
throw new McpError(McpErrorCode.InvalidParams, `Required parameter '${field}' is missing`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=BaseToolController.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 DeleteArtifactTool 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=DeleteArtifactTool.d.ts.map
|