persist_image
Save images from URLs to specified folders within your workspace, automatically creating directories as needed for organized storage.
Instructions
Store image at URL to folder relative to current workspace. If targetPath does not exist, the tool will create it automatically.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| targetPath | Yes | Folder where to save the image (relative to the current workspace) | |
| url | Yes | URL of the image | |
| workspacePath | Yes | The current workspace absolute path |
Implementation Reference
- src/tools/persist_image/index.ts:17-79 (handler)Main handler function that orchestrates image persistence: prepares target path, fetches image, handles errors, and returns success metadata.export const handler: ToolCallback<typeof schema> = async ({ url, targetPath, workspacePath }) => { logger().info('handler called', { url, targetPath, workspacePath }); const [prepareTargetPathErr, fullTargetPath] = await tryCatch<PersistImageError, string>(prepareTargetPath(workspacePath, targetPath)); if (prepareTargetPathErr != null) { logger().error('prepareTargetPath error', { error: prepareTargetPathErr }); return { _meta: { error: { type: 'PersistImageError', message: prepareTargetPathErr.message, code: prepareTargetPathErr.code, }, }, content: [ { type: 'text' as const, text: `Error: ${prepareTargetPathErr.message}`, }, ], }; } const [fetchImageErr, fetchResult] = await tryCatch<PersistImageError, FetchResult>(fetchImage(url, fullTargetPath)); if (fetchImageErr != null) { logger().error('fetchImage error', { error: fetchImageErr }); return { _meta: { error: { type: 'PersistImageError', message: fetchImageErr.message, code: fetchImageErr.code, }, }, content: [ { type: 'text' as const, text: `Error: ${fetchImageErr.message}`, }, ], }; } const _meta = { success: true, filePersistPath: fetchResult.filePersistPath, size: fetchResult.size, mimeType: fetchResult.mimeType, }; logger().info('handler success', { fetchResult, _meta }); return { _meta, content: [ { type: 'text' as const, text: `All done! Download result:\n- filePersistPath: ${relative(workspacePath, fetchResult.filePersistPath)}\n- size: ${Math.round((fetchResult.size / 1024) * 100) / 100}KB`, }, ], }; };
- Zod schema defining the input parameters: url, targetPath, workspacePath.export const schema = { url: z.string().url().describe('URL of the image'), targetPath: z.string().describe('Folder where to save the image (relative to the current workspace)'), workspacePath: z.string().describe('The current workspace absolute path'), } as const;
- src/index.ts:21-26 (registration)MCP server tool registration for 'persist_image' with description, schema, and handler.server.tool( 'persist_image', 'Store image at URL to folder relative to current workspace. If targetPath does not exist, the tool will create it automatically.', persistImageSchema, persistImageHandler, );
- Helper function to prepare and create the target directory path within the workspace.export async function prepareTargetPath(workspacePath: string, targetPath: string): Promise<string> { logger().info('prepareTargetPath()', { workspacePath, targetPath }); // validates that the target path is within project bounds, and returns the full path of the directory where the file should be saved if (targetPath.startsWith('..')) { throw new PersistImageError('Target path must be within the project directory', 'INVALID_PATH'); } const fullTargetPath = resolve(workspacePath, targetPath); const [fsAccessErr] = await tryCatch(fs.access(fullTargetPath)); logger().debug('prepareTargetPath() resolve & fs.access', { fullTargetPath, fsAccessErr }); if (fsAccessErr == null) { logger().info('prepareTargetPath() existing directory found'); return fullTargetPath; } const [fsMkdirErr] = await tryCatch(fs.mkdir(fullTargetPath, { recursive: true })); logger().debug('prepareTargetPath() fs.mkdir', { fsMkdirErr }); if (fsMkdirErr == null) { logger().info('prepareTargetPath() directory created successfully'); return fullTargetPath; } throw new PersistImageError(`Failed to create directory: ${fsAccessErr.message}`, 'DIRECTORY_CREATE_FAILED'); }
- Core helper to fetch image from URL, validate MIME type, persist to file, and return file metadata.export async function fetchImage(url: string, fullTargetPath: string): Promise<FetchResult> { logger().info('fetchImage()', { url, fullTargetPath }); const [fetchErr, response] = await tryCatch(fetch(url, { headers: { 'User-Agent': USER_AGENT } })); logger().info('fetchImage() response', { fetchErr, response }); if (fetchErr != null) { throw new PersistImageError(`Failed to fetch image: ${fetchErr.message}`, 'FETCH_FAILED'); } if (!response.ok) { throw new PersistImageError(`HTTP error ${response.status}: ${response.statusText}`, 'HTTP_ERROR'); } if (response.body == null) { throw new PersistImageError('No response body received', 'NO_RESPONSE_BODY'); } const contentType = response.headers.get('content-type') || ''; if (!ALLOWED_IMAGE_TYPES.has(contentType)) { throw new PersistImageError(`Invalid content type: ${contentType}. Only image files are allowed.`, 'INVALID_CONTENT_TYPE'); } const fileName = getFilename(url, contentType); const filePersistPath = resolve(fullTargetPath, fileName); const writeStream = createWriteStream(filePersistPath); logger().info('fetchImage() fileName', { fileName, filePersistPath, writeStream }); const [pipelineErr] = await tryCatch(pipeline(response.body, writeStream)); logger().info('fetchImage() pipeline', { pipelineErr }); if (pipelineErr != null) { throw new PersistImageError(`Failed to save file: ${pipelineErr.message}`, 'SAVE_FAILED'); } const [fsStatErr, fileStats] = await tryCatch(fs.stat(filePersistPath)); logger().info('fetchImage() fs.stat', { fsStatErr, fileStats }); if (fsStatErr != null) { throw new PersistImageError(`Failed to get file stats: ${fsStatErr.message}`, 'STAT_FAILED'); } return { filePersistPath, mimeType: contentType, size: fileStats.size, }; }