Skip to main content
Glama

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
NameRequiredDescriptionDefault
targetPathYesFolder where to save the image (relative to the current workspace)
urlYesURL of the image
workspacePathYesThe current workspace absolute path

Implementation Reference

  • 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, }; }

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/srigi/mcp-google-images-search'

If you have feedback or need assistance with the MCP directory API, please join our Discord server