UNPKG

@utaba/ucm-mcp-server

Version:

Universal Context Manager MCP Server - AI Productivity Platform

173 lines 9.75 kB
import { BaseToolController } from '../base/BaseToolController.js'; import { parsePath } from '../../utils/PathUtils.js'; import { McpError, McpErrorCode } from '../../utils/McpErrorHandler.js'; export class EditArtifactMetadataTool extends BaseToolController { constructor(ucmClient, logger, publishingAuthorId) { super(ucmClient, logger, publishingAuthorId); } get name() { return 'ucm_edit_artifact_metadata'; } get description() { return 'Edit artifact metadata and move artifacts. Supports updating description, namespace, filename, MIME type, technology, and tags. Move operations affect all versions while metadata-only updates apply to latest version only.'; } get inputSchema() { return { type: 'object', properties: { path: { type: 'string', description: `Full artifact path (e.g., "${this.publishingAuthorId || '1234567890'}/main/commands/user/CreateUserCommand.ts")`, minLength: 1, maxLength: 250 }, updateDescription: { type: 'string', description: 'New description for the artifact (up to 1000 characters)', maxLength: 1000 }, updateNamespace: { type: 'string', description: 'New namespace path to move artifact to (e.g., "utaba/main/services/user")', maxLength: 200 }, updateFilename: { type: 'string', description: 'New filename for the artifact (e.g., "UserService.ts")', maxLength: 100 }, updateMimeType: { type: 'string', description: 'New MIME type for the artifact (e.g., "application/typescript")', maxLength: 100 }, updateTechnology: { type: 'string', description: 'New technology stack (e.g., "typescript", "python", "nextjs")', maxLength: 50 }, updateTags: { type: 'string', description: 'New comma-separated tags for categorization', maxLength: 500 } }, required: ['path'], additionalProperties: false }; } async handleExecute(params) { const { path, updateDescription, updateNamespace, updateFilename, updateMimeType, updateTechnology, updateTags } = params; this.logger.debug('EditArtifactMetadataTool', `Editing artifact metadata: ${path}`, '', { hasDescriptionUpdate: !!updateDescription, hasNamespaceUpdate: !!updateNamespace, hasFilenameUpdate: !!updateFilename }); try { // Parse the current path to extract components const pathComponents = parsePath(path); // Validate path components exist including repository if (!pathComponents.author || !pathComponents.repository || !pathComponents.category || !pathComponents.subcategory || !pathComponents.filename) { throw new McpError(McpErrorCode.InvalidParams, 'Path must contain author, repository, category, subcategory, and filename (e.g., "utaba/main/commands/user/CreateUserCommand.ts")'); } // Validate that at least one update field is provided const hasUpdates = !!(updateDescription || updateNamespace || updateFilename || updateMimeType || updateTechnology || updateTags); if (!hasUpdates) { throw new McpError(McpErrorCode.InvalidParams, 'At least one update field must be provided (updateDescription, updateNamespace, updateFilename, updateMimeType, updateTechnology, or updateTags)'); } // Prepare the edit data const editData = {}; if (updateDescription !== undefined) editData.updateDescription = updateDescription; if (updateNamespace !== undefined) editData.updateNamespace = updateNamespace; if (updateFilename !== undefined) editData.updateFilename = updateFilename; if (updateMimeType !== undefined) editData.updateMimeType = updateMimeType; if (updateTechnology !== undefined) editData.updateTechnology = updateTechnology; if (updateTags !== undefined) editData.updateTags = updateTags; // Call the API to edit the artifact metadata const result = await this.ucmClient.editArtifactMetadata(pathComponents.author, pathComponents.repository, pathComponents.category, pathComponents.subcategory, pathComponents.filename, editData); // Handle response structure - API might return wrapped in 'data' or direct const responseData = result.data || result; // Build successful response with operation details const isMove = !!(updateNamespace || updateFilename); const operation = isMove ? 'move' : 'metadata_update'; const response = { success: true, operation, artifact: { originalPath: path, newPath: isMove ? `${updateNamespace || `${pathComponents.author}/${pathComponents.repository}/${pathComponents.category}/${pathComponents.subcategory}`}/${updateFilename || pathComponents.filename}` : path, updatedFields: Object.keys(editData), affectedVersions: responseData.affectedVersions || (isMove ? 'all' : 'latest'), }, details: responseData.details || 'Artifact metadata updated successfully', _links: responseData._links || {} }; this.logger.info('EditArtifactMetadataTool', `Artifact edited successfully: ${path}`, '', { operation, updatedFields: Object.keys(editData), affectedVersions: response.artifact.affectedVersions }); return response; } catch (error) { this.logger.error('EditArtifactMetadataTool', `Failed to edit artifact: ${path}`, '', error); // Enhanced error handling for edit operations const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage?.includes('not found')) { throw new McpError(McpErrorCode.InvalidParams, `Artifact not found at path: ${path}. Please verify the path is correct and the artifact exists.`); } if (errorMessage?.includes('permission') || errorMessage?.includes('unauthorized')) { throw new McpError(McpErrorCode.InvalidParams, `Insufficient permissions to edit artifact at: ${path}. You must have write access to both source and destination namespaces for move operations.`); } if (errorMessage?.includes('destination already exists')) { throw new McpError(McpErrorCode.InvalidParams, 'Destination artifact already exists. Choose a different filename or namespace for move operations.'); } if (errorMessage?.includes('Invalid namespace format')) { throw new McpError(McpErrorCode.InvalidParams, `Invalid namespace format in updateNamespace. Must follow pattern "author/repository/category/subcategory". Example: "${this.publishingAuthorId || '0000000000'}/main/services/user"`); } if (errorMessage?.includes('Author not found')) { throw new McpError(McpErrorCode.InvalidParams, `Author not found. Ensure the path uses a valid author identifier.`); } throw error; } } validateParams(params) { super.validateParams(params); const { path, updateNamespace, updateFilename, updateDescription } = params; // Validate path format if (!path || typeof path !== 'string') { throw new McpError(McpErrorCode.InvalidParams, 'path is required and must be a string'); } // Basic path validation - should contain at least author/repository/category/subcategory/filename const pathParts = path.split('/'); if (pathParts.length < 5) { throw new McpError(McpErrorCode.InvalidParams, 'path must contain at least author/repository/category/subcategory/filename'); } // Validate updateNamespace format if provided if (updateNamespace && updateNamespace.length > 0) { const namespaceParts = updateNamespace.split('/'); if (namespaceParts.length !== 4) { throw new McpError(McpErrorCode.InvalidParams, 'updateNamespace must follow format "author/repository/category/subcategory"'); } } // Validate updateFilename if provided if (updateFilename && updateFilename.length > 0) { if (!/^[^\/\\:*?"<>|]+\.[a-zA-Z0-9]+$/.test(updateFilename)) { throw new McpError(McpErrorCode.InvalidParams, 'updateFilename must be a valid filename with extension (e.g., "UserService.ts")'); } } // Validate description length if (updateDescription && updateDescription.length > 1000) { throw new McpError(McpErrorCode.InvalidParams, 'updateDescription cannot exceed 1000 characters'); } } } //# sourceMappingURL=EditArtifactMetadataTool.js.map