mcp-image-downloader

#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; import sharp from 'sharp'; import fs from 'fs-extra'; import path from 'path'; interface DownloadImageArgs { url: string; outputPath: string; } interface OptimizeImageArgs { inputPath: string; outputPath: string; width?: number; height?: number; quality?: number; } class ImageDownloaderServer { private server: Server; constructor() { this.server = new Server( { name: 'mcp-image-downloader', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private isDownloadImageArgs(args: unknown): args is DownloadImageArgs { if (!args || typeof args !== 'object') return false; const a = args as Record<string, unknown>; return ( typeof a.url === 'string' && typeof a.outputPath === 'string' ); } private isOptimizeImageArgs(args: unknown): args is OptimizeImageArgs { if (!args || typeof args !== 'object') return false; const a = args as Record<string, unknown>; return ( typeof a.inputPath === 'string' && typeof a.outputPath === 'string' && (a.width === undefined || typeof a.width === 'number') && (a.height === undefined || typeof a.height === 'number') && (a.quality === undefined || typeof a.quality === 'number') ); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'download_image', description: 'Download an image from a URL to a specified path', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL of the image to download', }, outputPath: { type: 'string', description: 'Path where to save the image', }, }, required: ['url', 'outputPath'], }, }, { name: 'optimize_image', description: 'Create an optimized version of an image', inputSchema: { type: 'object', properties: { inputPath: { type: 'string', description: 'Path to the input image', }, outputPath: { type: 'string', description: 'Path where to save the optimized image', }, width: { type: 'number', description: 'Target width (maintains aspect ratio if only width is specified)', }, height: { type: 'number', description: 'Target height (maintains aspect ratio if only height is specified)', }, quality: { type: 'number', description: 'JPEG/WebP quality (1-100)', minimum: 1, maximum: 100, }, }, required: ['inputPath', 'outputPath'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case 'download_image': if (!this.isDownloadImageArgs(request.params.arguments)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for download_image'); } return this.handleDownloadImage(request.params.arguments); case 'optimize_image': if (!this.isOptimizeImageArgs(request.params.arguments)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for optimize_image'); } return this.handleOptimizeImage(request.params.arguments); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } private async handleDownloadImage(args: DownloadImageArgs) { try { // Ensure output directory exists await fs.ensureDir(path.dirname(args.outputPath)); // Download image const response = await axios({ method: 'GET', url: args.url, responseType: 'arraybuffer', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' }, timeout: 30000, }); // Save image await fs.writeFile(args.outputPath, response.data); return { content: [ { type: 'text', text: `Successfully downloaded image to ${args.outputPath}`, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; console.error('Download error:', errorMessage); return { content: [ { type: 'text', text: `Failed to download image: ${errorMessage}`, }, ], isError: true, }; } } private async handleOptimizeImage(args: OptimizeImageArgs) { try { // Ensure output directory exists await fs.ensureDir(path.dirname(args.outputPath)); // Read input image let transformer = sharp(args.inputPath); // Resize if dimensions specified if (args.width || args.height) { transformer = transformer.resize(args.width, args.height, { fit: 'inside', withoutEnlargement: true, }); } // Set quality if specified if (args.quality) { transformer = transformer.jpeg({ quality: args.quality }).webp({ quality: args.quality }); } // Save optimized image await transformer.toFile(args.outputPath); return { content: [ { type: 'text', text: `Successfully optimized image to ${args.outputPath}`, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; console.error('Optimization error:', errorMessage); return { content: [ { type: 'text', text: `Failed to optimize image: ${errorMessage}`, }, ], isError: true, }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Image Downloader MCP server running on stdio'); } } const server = new ImageDownloaderServer(); server.run().catch(console.error);