read-icc
Extract ICC color profile metadata from images to analyze color spaces and ensure accurate color representation across devices.
Instructions
Read ICC metadata from an image
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| image | Yes |
Implementation Reference
- src/tools/index.ts:154-170 (handler)Handler function that executes the 'read-icc' tool logic. Loads the image buffer, configures exifr options for ICC segment, parses metadata, and returns ICC data or appropriate error.async (args, extra) => { try { const { image } = args; const buf = await loadImage(image); const opts = buildSegmentOptions(segment); const meta = await exifr.parse(buf, opts); const segmentKey = segment.toLowerCase(); if (!meta || !meta[segmentKey]) { return createErrorResponse(`No ${segment} metadata found in image`); } return createSuccessResponse(meta); } catch (error) { return createErrorResponse(`Error reading ${segment} data: ${error instanceof Error ? error.message : String(error)}`); } }
- src/tools/index.ts:141-173 (registration)Registration block for segment-specific tools, including 'read-icc'. Maps 'read-icc' to 'ICC' segment and registers the tool with MCP server, schema, and handler.const segmentTools = [ { name: 'read-icc', segment: 'ICC' }, { name: 'read-iptc', segment: 'IPTC' }, { name: 'read-jfif', segment: 'JFIF' }, { name: 'read-ihdr', segment: 'IHDR' } ] as const; segmentTools.forEach(({ name, segment }) => { const segmentTool = server.tool(name, `Read ${segment} metadata from an image`, { image: ImageSourceSchema }, async (args, extra) => { try { const { image } = args; const buf = await loadImage(image); const opts = buildSegmentOptions(segment); const meta = await exifr.parse(buf, opts); const segmentKey = segment.toLowerCase(); if (!meta || !meta[segmentKey]) { return createErrorResponse(`No ${segment} metadata found in image`); } return createSuccessResponse(meta); } catch (error) { return createErrorResponse(`Error reading ${segment} data: ${error instanceof Error ? error.message : String(error)}`); } } ); tools[name] = segmentTool; });
- src/tools/index.ts:52-60 (schema)Zod schema defining the 'image' input parameter structure used by the 'read-icc' tool.// Define a Zod schema for the ImageSource type that's directly usable with McpServer.tool const ImageSourceSchema = z.object({ kind: z.enum(['path', 'url', 'base64', 'buffer']), path: z.string().optional(), url: z.string().optional(), data: z.string().optional(), buffer: z.string().optional() });
- src/tools/segments.ts:114-128 (helper)Helper function that builds exifr options specifically enabling the ICC segment parsing, used in the read-icc handler.export function buildSegmentOptions(segment: 'ICC' | 'IPTC' | 'JFIF' | 'IHDR'): ExifrOptions { const options: ExifrOptions = { tiff: false, xmp: false, icc: false, iptc: false, jfif: false, ihdr: false, }; const key = segment.toLowerCase() as 'icc' | 'iptc' | 'jfif' | 'ihdr'; options[key] = true; return options; }
- src/tools/loaders.ts:11-72 (helper)Helper function to load image from various sources (path, url, base64, buffer) into a buffer suitable for exifr parsing, called in the handler.export async function loadImage(src: ImageSourceType): Promise<Buffer | Uint8Array> { try { switch (src.kind) { case 'path': if (!src.path) { throw new Error('Path is required for kind="path"'); } return await fs.promises.readFile(src.path); case 'url': if (!src.url) { throw new Error('URL is required for kind="url"'); } if (src.url.startsWith('file://')) { // Handle file:// URLs by converting to filesystem path const filePath = fileURLToPath(src.url); return await fs.promises.readFile(filePath); } else { // Handle HTTP/HTTPS URLs const response = await fetch(src.url); if (!response.ok) { throw new Error(`Failed to fetch URL: ${response.status} ${response.statusText}`); } return new Uint8Array(await response.arrayBuffer()); } case 'base64': if (!src.data) { throw new Error('Data is required for kind="base64"'); } // Check for potential oversized base64 string (>30MB) if (src.data.length > 40000000) { // ~30MB in base64 throw new Error('PayloadTooLarge: Base64 data exceeds 30MB limit'); } // Handle data URIs or raw base64 if (src.data.startsWith('data:')) { const base64Data = src.data.split(',')[1]; return Buffer.from(base64Data, 'base64'); } else { return Buffer.from(src.data, 'base64'); } case 'buffer': if (!src.buffer) { throw new Error('Buffer is required for kind="buffer"'); } return Buffer.from(src.buffer, 'base64'); default: // This should never happen due to type constraints, but TypeScript needs it throw new Error(`Unsupported image source kind: ${(src as any).kind}`); } } catch (error) { if (error instanceof Error) { throw new Error(`Failed to load image: ${error.message}`); } throw error; } }