writeImageMetadata
Add metadata like tags, descriptions, people, and location to image files for better organization and searchability.
Instructions
Writes metadata (tags, description, people, location) to an image file.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filePath | Yes | ||
| metadata | Yes | ||
| overwrite | No |
Implementation Reference
- Main handler function for the writeImageMetadata MCP tool. Validates parameters, checks for relative paths, calls the core metadata writer service, constructs success messages based on file type, and handles a wide range of custom errors with appropriate responses.
export async function writeImageMetadataHandler( params: WriteImageMetadataParams, context: { app: { metadataWriter: MetadataWriterService } } ): Promise<WriteImageMetadataResult> { try { // 检查是否为相对路径 if (isRelativePath(params.filePath)) { throw new RelativePathError(params.filePath); } // 检查参数有效性(额外业务逻辑校验) validateMetadataParams(params); // 从上下文中获取MetadataWriterService实例 const { metadataWriter } = context.app; // 调用核心服务写入所有元数据(包括标签、描述、人物、地点) await metadataWriter.writeMetadataForImage( params.filePath, params.metadata, params.overwrite ); // 获取文件扩展名(小写) const fileExt = params.filePath.toLowerCase().split('.').pop() || ''; // 根据文件扩展名构建适当的成功消息 let successMessage = '元数据已成功写入图片。'; // 针对特定文件类型自定义消息 if (fileExt === 'jpg' || fileExt === 'jpeg') { successMessage = '元数据已成功写入JPG图片。'; } else if (fileExt === 'png') { successMessage = '元数据已成功写入PNG图片。'; } else if (fileExt === 'heic') { successMessage = '元数据已成功写入HEIC图片。'; } // 返回成功响应 return { success: true, filePath: params.filePath, message: successMessage, }; } catch (error) { // 如果已经是JsonRpcError类型,获取其信息 if (error instanceof JsonRpcError) { return { success: false, filePath: params.filePath, message: error.message }; } // 将捕获的错误映射到合适的错误信息 if (error instanceof FileNotFoundError) { return { success: false, filePath: error.filePath, message: `文件未找到: ${error.message}` }; } if (error instanceof FileAccessError) { const operation = error.operation === 'write' ? '写入' : '读取'; return { success: false, filePath: error.filePath, message: `文件${operation}权限错误: ${error.message}` }; } if (error instanceof UnsupportedFileFormatError) { return { success: false, filePath: error.filePath, message: `不支持的文件格式: ${error.format} - ${error.message}` }; } if (error instanceof RelativePathError) { return { success: false, filePath: error.filePath, message: `不允许使用相对路径: ${error.message}` }; } if (error instanceof ExifToolTimeoutError) { return { success: false, filePath: error.filePath, message: `ExifTool执行超时: ${error.message}` }; } if (error instanceof ExifToolProcessError) { return { success: false, filePath: params.filePath, message: `ExifTool处理错误: ${error.message}` }; } if (error instanceof MetadataWriteError) { return { success: false, filePath: error.filePath, message: `元数据写入失败: ${error.message}` }; } if (error instanceof InvalidMetadataFormatError) { return { success: false, filePath: params.filePath, message: `无效的元数据格式: ${error.message}` }; } // 其他未知错误 const errorMessage = error instanceof Error ? error.message : String(error); return { success: false, filePath: params.filePath, message: `写入元数据时发生内部错误: ${errorMessage}` }; } } - src/main.ts:47-56 (schema)Zod schema defining the input parameters for the writeImageMetadata tool, used during MCP server registration for validation.
const writeImageMetadataParams = { filePath: z.string().min(1, "filePath is required."), metadata: z.object({ tags: z.array(z.string()).optional(), description: z.string().optional(), people: z.array(z.string()).optional(), location: z.string().optional(), }), overwrite: z.boolean().default(true), }; - src/main.ts:62-98 (registration)MCP tool registration using server.tool(), providing name, description, Zod schema, and an async handler wrapper that calls the main handler and formats the response.
server.tool( 'writeImageMetadata', // 工具名称 'Writes metadata (tags, description, people, location) to an image file.', // 工具描述 writeImageMetadataParams, // 参数 schema async (params, _extra) => { try { // 在调用处理函数时传入参数和上下文 const result = await writeImageMetadataHandler(params, { app: appContext }); // 返回完整的结果作为JSON字符串 return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { // 处理JsonRpcError,将其转换为正确的响应格式 if (error instanceof JsonRpcError) { // 使用SDK的错误处理机制传递自定义错误 throw { code: error.code, message: error.message, data: error.data }; } // 对于其他未处理的错误,返回通用内部错误 console.error('Tool执行时发生未捕获错误:', error); throw { code: -32603, // JSON-RPC标准内部错误码 message: '内部服务器错误', data: { errorMessage: error instanceof Error ? error.message : String(error) } }; } } ); - TypeScript type definitions for tool input parameters and output result, providing type safety in the handler implementation.
export interface WriteImageMetadataParams { filePath: string; metadata: ImageMetadataArgs; overwrite: boolean; } // 输出结果类型定义 export interface WriteImageMetadataResult { success: boolean; filePath: string; message: string; } - Helper function that performs detailed business logic validation on metadata parameters, enforcing length limits and counts to prevent invalid data.
function validateMetadataParams(params: WriteImageMetadataParams): void { // 常量定义 const MAX_DESCRIPTION_LENGTH = 10000; const MAX_LOCATION_LENGTH = 1000; const MAX_TAG_LENGTH = 100; const MAX_PERSON_NAME_LENGTH = 100; const MAX_TAGS_COUNT = 50; const MAX_PEOPLE_COUNT = 50; // 检查标签数组中是否有空字符串或过长的标签 if (params.metadata?.tags) { // 检查标签数量是否过多 if (params.metadata.tags.length > MAX_TAGS_COUNT) { throw new InvalidMetadataFormatError( `标签数量过多,最多允许${MAX_TAGS_COUNT}个标签` ); } // 检查每个标签 const invalidTags = params.metadata.tags.filter(tag => tag.trim() === ''); if (invalidTags.length > 0) { throw new InvalidMetadataFormatError( '标签不能为空字符串' ); } // 检查标签长度 const longTags = params.metadata.tags.filter(tag => tag.length > MAX_TAG_LENGTH); if (longTags.length > 0) { throw new InvalidMetadataFormatError( `标签长度不能超过${MAX_TAG_LENGTH}个字符` ); } } // 检查人物数组中是否有空字符串或过长的名称 if (params.metadata?.people) { // 检查人物数量是否过多 if (params.metadata.people.length > MAX_PEOPLE_COUNT) { throw new InvalidMetadataFormatError( `人物数量过多,最多允许${MAX_PEOPLE_COUNT}个人物` ); } // 检查每个人物名称 const invalidPeople = params.metadata.people.filter(person => person.trim() === ''); if (invalidPeople.length > 0) { throw new InvalidMetadataFormatError( '人物名称不能为空字符串' ); } // 检查人物名称长度 const longNames = params.metadata.people.filter(person => person.length > MAX_PERSON_NAME_LENGTH); if (longNames.length > 0) { throw new InvalidMetadataFormatError( `人物名称长度不能超过${MAX_PERSON_NAME_LENGTH}个字符` ); } } // 检查描述长度是否过长 if (params.metadata?.description) { if (params.metadata.description.length > MAX_DESCRIPTION_LENGTH) { throw new InvalidMetadataFormatError( `描述文本过长,最大允许${MAX_DESCRIPTION_LENGTH}个字符` ); } } // 检查地点文本长度是否过长 if (params.metadata?.location) { if (params.metadata.location.length > MAX_LOCATION_LENGTH) { throw new InvalidMetadataFormatError( `地点文本过长,最大允许${MAX_LOCATION_LENGTH}个字符` ); } } }