# Directory Structure ``` ├── .gitignore ├── Dockerfile ├── LICENSE ├── package.json ├── README.ja.md ├── README.md ├── smithery.yaml ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # image-mcp-server [日本語の README](README.ja.md) <a href="https://glama.ai/mcp/servers/@champierre/image-mcp-server"> <img width="380" height="200" src="https://glama.ai/mcp/servers/@champierre/image-mcp-server/badge" alt="Image Analysis MCP Server" /> </a> [](https://smithery.ai/server/@champierre/image-mcp-server) An MCP server that receives image URLs or local file paths and analyzes image content using the GPT-4o-mini model. ## Features - Receives image URLs or local file paths as input and provides detailed analysis of the image content - High-precision image recognition and description using the GPT-4o-mini model - Image URL validity checking - Image loading from local files and Base64 encoding ## Installation ### Installing via Smithery To install Image Analysis Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@champierre/image-mcp-server): ```bash npx -y @smithery/cli install @champierre/image-mcp-server --client claude ``` ### Manual Installation ```bash # Clone the repository git clone https://github.com/champierre/image-mcp-server.git # or your forked repository cd image-mcp-server # Install dependencies npm install # Compile TypeScript npm run build ``` ## Configuration To use this server, you need an OpenAI API key. Set the following environment variable: ``` OPENAI_API_KEY=your_openai_api_key ``` ## MCP Server Configuration To use with tools like Cline, add the following settings to your MCP server configuration file: ### For Cline Add the following to `cline_mcp_settings.json`: ```json { "mcpServers": { "image-analysis": { "command": "node", "args": ["/path/to/image-mcp-server/dist/index.js"], "env": { "OPENAI_API_KEY": "your_openai_api_key" } } } } ``` ### For Claude Desktop App Add the following to `claude_desktop_config.json`: ```json { "mcpServers": { "image-analysis": { "command": "node", "args": ["/path/to/image-mcp-server/dist/index.js"], "env": { "OPENAI_API_KEY": "your_openai_api_key" } } } } ``` ## Usage Once the MCP server is configured, the following tools become available: - `analyze_image`: Receives an image URL and analyzes its content. - `analyze_image_from_path`: Receives a local file path and analyzes its content. ### Usage Examples **Analyzing from URL:** ``` Please analyze this image URL: https://example.com/image.jpg ``` **Analyzing from local file path:** ``` Please analyze this image: /path/to/your/image.jpg ``` ### Note: Specifying Local File Paths When using the `analyze_image_from_path` tool, the AI assistant (client) must specify a **valid file path in the environment where this server is running**. - **If the server is running on WSL:** - If the AI assistant has a Windows path (e.g., `C:\...`), it needs to convert it to a WSL path (e.g., `/mnt/c/...`) before passing it to the tool. - If the AI assistant has a WSL path, it can pass it as is. - **If the server is running on Windows:** - If the AI assistant has a WSL path (e.g., `/home/user/...`), it needs to convert it to a UNC path (e.g., `\\wsl$\Distro\...`) before passing it to the tool. - If the AI assistant has a Windows path, it can pass it as is. **Path conversion is the responsibility of the AI assistant (or its execution environment).** The server will try to interpret the received path as is. ### Note: Type Errors During Build When running `npm run build`, you may see an error (TS7016) about missing TypeScript type definitions for the `mime-types` module. ``` src/index.ts:16:23 - error TS7016: Could not find a declaration file for module 'mime-types'. ... ``` This is a type checking error, and since the JavaScript compilation itself succeeds, it **does not affect the server's execution**. If you want to resolve this error, install the type definition file as a development dependency. ```bash npm install --save-dev @types/mime-types # or yarn add --dev @types/mime-types ``` ## Development ```bash # Run in development mode npm run dev ``` ## License MIT ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2020", "module": "NodeNext", "moduleResolution": "NodeNext", "esModuleInterop": true, "strict": true, "outDir": "dist", "declaration": true, "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile FROM node:lts-alpine # Set working directory WORKDIR /app # Copy package files COPY package.json package-lock.json* ./ # Install dependencies without running scripts RUN npm install --ignore-scripts # Copy rest of the source code COPY . . # Build the project (TypeScript compilation) RUN npm run build # Expose port if necessary (not specified in MCP, so optional) # EXPOSE 3000 # Start the MCP server CMD ["npm", "run", "start"] ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml startCommand: type: stdio configSchema: # JSON Schema defining the configuration options for the MCP. type: object required: - openaiApiKey properties: openaiApiKey: type: string description: Your OpenAI API key required for image analysis. commandFunction: # A JS function that produces the CLI command based on the given config to start the MCP on stdio. |- (config) => ({ command: 'node', args: ['dist/index.js'], env: { OPENAI_API_KEY: config.openaiApiKey } }) exampleConfig: openaiApiKey: your_openai_api_key_here ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "image-mcp-server", "version": "1.0.0", "description": "MCP server for image analysis using GPT-4o-mini", "main": "dist/index.js", "type": "module", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "ts-node --esm src/index.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/champierre/image-mcp-server.git" }, "keywords": [], "author": "Junya Ishihara", "license": "MIT", "bugs": { "url": "https://github.com/champierre/image-mcp-server/issues" }, "homepage": "https://github.com/champierre/image-mcp-server#readme", "dependencies": { "@modelcontextprotocol/sdk": "^1.7.0", "@types/node": "^22.13.13", "axios": "^1.8.4", "dotenv": "^16.4.7", "mime-types": "^3.0.1", "openai": "^4.89.0", "ts-node": "^10.9.2", "typescript": "^5.8.2" }, "devDependencies": { "@types/mime-types": "^2.1.4" } } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/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 { OpenAI } from 'openai'; import axios from 'axios'; import * as dotenv from 'dotenv'; import * as fs from 'fs'; // Import fs for file reading import * as path from 'path'; // Import path for path operations import * as os from 'os'; // Import os module import * as mime from 'mime-types'; // Revert to import statement // Load environment variables from .env file dotenv.config(); // Get OpenAI API key from environment variables const OPENAI_API_KEY = process.env.OPENAI_API_KEY; if (!OPENAI_API_KEY) { throw new Error('OPENAI_API_KEY environment variable is required'); } // Initialize OpenAI client const openai = new OpenAI({ apiKey: OPENAI_API_KEY, }); // --- Argument Type Guards --- const isValidAnalyzeImageArgs = ( args: any ): args is { imageUrl: string } => typeof args === 'object' && args !== null && typeof args.imageUrl === 'string'; const isValidAnalyzeImagePathArgs = ( args: any ): args is { imagePath: string } => // New type guard for path tool typeof args === 'object' && args !== null && typeof args.imagePath === 'string'; // --- End Argument Type Guards --- class ImageAnalysisServer { private server: Server; constructor() { this.server = new Server( { name: 'image-analysis-server', version: '1.1.0', // Version bump }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupToolHandlers() { // Define tool list this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'analyze_image', description: 'Receives an image URL and analyzes the image content using GPT-4o-mini', inputSchema: { type: 'object', properties: { imageUrl: { type: 'string', description: 'URL of the image to analyze', }, }, required: ['imageUrl'], }, }, // --- New Tool Definition --- { name: 'analyze_image_from_path', description: 'Loads an image from a local file path and analyzes its content using GPT-4o-mini. AI assistants need to provide a valid path for the server execution environment (e.g., Linux path if the server is running on WSL).', inputSchema: { type: 'object', properties: { imagePath: { type: 'string', description: 'Local file path of the image to analyze (must be accessible from the server execution environment)', }, }, required: ['imagePath'], }, }, // --- End New Tool Definition --- ], })); // Tool execution handler this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const toolName = request.params.name; const args = request.params.arguments; try { let analysis: string; if (toolName === 'analyze_image') { if (!isValidAnalyzeImageArgs(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for analyze_image: imageUrl (string) is required' ); } const imageUrl = args.imageUrl; await this.validateImageUrl(imageUrl); // Validate URL accessibility analysis = await this.analyzeImageWithGpt4({ type: 'url', data: imageUrl }); } else if (toolName === 'analyze_image_from_path') { if (!isValidAnalyzeImagePathArgs(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for analyze_image_from_path: imagePath (string) is required' ); } const imagePath = args.imagePath; // Basic security check: prevent absolute paths trying to escape common roots (adjust as needed) // This is a VERY basic check and might need refinement based on security requirements. if (path.isAbsolute(imagePath) && !imagePath.startsWith(process.cwd()) && !imagePath.startsWith(os.homedir()) && !imagePath.startsWith('/mnt/')) { // Allow relative paths, paths within cwd, home, or WSL mounts. Adjust if needed. console.warn(`Potential unsafe path access attempt blocked: ${imagePath}`); throw new McpError(ErrorCode.InvalidParams, 'Invalid or potentially unsafe imagePath provided.'); } const resolvedPath = path.resolve(imagePath); // Resolve relative paths if (!fs.existsSync(resolvedPath)) { throw new McpError(ErrorCode.InvalidParams, `File not found at path: ${resolvedPath}`); } const imageDataBuffer = fs.readFileSync(resolvedPath); const base64String = imageDataBuffer.toString('base64'); const mimeType = mime.lookup(resolvedPath) || 'application/octet-stream'; // Detect MIME type or default if (!mimeType.startsWith('image/')) { throw new McpError(ErrorCode.InvalidParams, `File is not an image: ${mimeType}`); } analysis = await this.analyzeImageWithGpt4({ type: 'base64', data: base64String, mimeType: mimeType }); } else { throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${toolName}` ); } // Return successful analysis return { content: [ { type: 'text', text: analysis, }, ], }; } catch (error) { console.error(`Error calling tool ${toolName}:`, error); // Return error content return { content: [ { type: 'text', text: `Tool execution error (${toolName}): ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); } // Method to check if the image URL is valid (existing) private async validateImageUrl(url: string): Promise<void> { try { const response = await axios.head(url); const contentType = response.headers['content-type']; if (!contentType || !contentType.startsWith('image/')) { throw new Error(`URL is not an image: ${contentType}`); } } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`Cannot access image URL: ${error.message}`); } throw error; } } // Method to analyze images with GPT-4o-mini (modified: accepts URL or Base64) private async analyzeImageWithGpt4( imageData: { type: 'url', data: string } | { type: 'base64', data: string, mimeType: string } ): Promise<string> { try { let imageInput: any; if (imageData.type === 'url') { imageInput = { type: 'image_url', image_url: { url: imageData.data } }; } else { // Construct data URI for OpenAI API imageInput = { type: 'image_url', image_url: { url: `data:${imageData.mimeType};base64,${imageData.data}` } }; } const response = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [ { role: 'system', content: 'Analyze the image content in detail and provide an explanation in English.', }, { role: 'user', content: [ { type: 'text', text: 'Please analyze the following image and explain its content in detail.' }, imageInput, // Use the constructed image input ], }, ], max_tokens: 1000, }); return response.choices[0]?.message?.content || 'Could not retrieve analysis results.'; } catch (error) { console.error('OpenAI API error:', error); throw new Error(`OpenAI API error: ${error instanceof Error ? error.message : String(error)}`); } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Image Analysis MCP server (v1.1.0) running on stdio'); // Updated version } } const server = new ImageAnalysisServer(); server.run().catch(console.error); ```