@utaba/ucm-mcp-server
Version:
Universal Context Manager MCP Server - AI Productivity Platform
173 lines • 9.75 kB
JavaScript
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