extract_image_metadata
Extract EXIF, GPS, IPTC, and XMP metadata from an image by providing its URL. Returns structured data for further processing.
Instructions
Extract metadata from image (EXIF, GPS, IPTC, XMP). Price: $0.002 USDC via x402
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| imageUrl | Yes | URL or file path to the image | |
| includeOptions | No | ||
| paymentHeader | No | x402 payment header (if paying for access) | |
| payer | No | Caller wallet address (for freemium tracking) |
Implementation Reference
- src/handlers/extract.ts:6-53 (handler)The handler function `handleExtract` that executes the extract_image_metadata tool logic. It validates payment/freemium, then calls `extractMetadata` from exif.ts to extract EXIF, GPS, IPTC, XMP, color, thumbnail, OCR, and deep hash data.
export async function handleExtract( input: ExtractInput, paymentHeader?: string, payer?: string ): Promise<{ success: boolean; data?: ImageMetadata; price?: number; paymentStatus?: string; freemiumRemaining?: number; error?: string; }> { try { const { imageUrl, includeOptions } = input; const options = includeOptions || {}; const tier = getTierFromOptions(options); const price = calculatePrice(tier); if (paymentHeader || !checkFreemium(payer).allowed) { const payment = await verifyPayment(paymentHeader, tier, payer); if (!payment.valid) { return { success: false, price, paymentStatus: 'failed', freemiumRemaining: 0, error: payment.error || 'Payment verification failed', }; } } const freemium = checkFreemium(payer); const data = await extractMetadata(imageUrl, options); return { success: true, data, price, paymentStatus: freemium.allowed ? 'free' : 'paid', freemiumRemaining: freemium.allowed ? freemium.remaining : 0, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error', }; } } - src/types.ts:1-18 (schema)Zod schemas `ExtractOptionsSchema` and `ExtractInputSchema` defining the input validation for the extract_image_metadata tool, including optional booleans for includeGps, includeColor, includeThumbnail, includeOcr, includeDeepHash.
import { z } from 'zod'; export const ExtractOptionsSchema = z.object({ includeGps: z.boolean().default(true), includeColor: z.boolean().default(true), includeThumbnail: z.boolean().default(false), includeOcr: z.boolean().default(false), includeDeepHash: z.boolean().default(false), }); export type ExtractOptions = z.infer<typeof ExtractOptionsSchema>; export const ExtractInputSchema = z.object({ imageUrl: z.string(), includeOptions: ExtractOptionsSchema.optional(), }); export type ExtractInput = z.infer<typeof ExtractInputSchema>; - src/index.ts:31-64 (registration)Registration of the extract_image_metadata tool in the MCP ListToolsRequestSchema handler, defining its name, description, and inputSchema.
server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'extract_image_metadata', description: `Extract metadata from image (EXIF, GPS, IPTC, XMP). Price: $${PRICING.standard.price} USDC via x402`, inputSchema: { type: 'object', properties: { imageUrl: { type: 'string', description: 'URL or file path to the image', }, includeOptions: { type: 'object', properties: { includeGps: { type: 'boolean', default: true }, includeColor: { type: 'boolean', default: true }, includeThumbnail: { type: 'boolean', default: false }, includeOcr: { type: 'boolean', default: false }, includeDeepHash: { type: 'boolean', default: false }, }, }, paymentHeader: { type: 'string', description: 'x402 payment header (if paying for access)', }, payer: { type: 'string', description: 'Caller wallet address (for freemium tracking)', }, }, required: ['imageUrl'], }, - src/index.ts:139-144 (handler)The CallToolRequestSchema switch-case that routes the 'extract_image_metadata' name to the handleExtract function via ExtractInputSchema parsing.
case 'extract_image_metadata': { const { paymentHeader: _, payer: __, ...rest } = args as Record<string, unknown>; const input = ExtractInputSchema.parse(rest); const result = await handleExtract(input, paymentHeader, payer); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } - src/lib/exif.ts:104-175 (helper)The core `extractMetadata` function that reads image EXIF/IPTC/XMP data using ExifReader and sharp, extracts GPS, color, thumbnail, deep hash, and animation info as requested.
export async function extractMetadata( imagePath: string, options: { includeGps?: boolean; includeColor?: boolean; includeThumbnail?: boolean; includeOcr?: boolean; includeDeepHash?: boolean; } = {} ): Promise<ImageMetadata> { const buffer = await sharp(imagePath).toBuffer(); const tags = ExifReader.load(buffer); const fileInfo = await sharp(imagePath).metadata(); const metadata: ImageMetadata = { file: { width: fileInfo.width || 0, height: fileInfo.height || 0, format: fileInfo.format || 'unknown', colorDepth: fileInfo.channels, dpi: fileInfo.density, fileSize: buffer.length, mimeType: `image/${fileInfo.format}`, }, }; if (tags.exif) { metadata.exif = extractExif(tags.exif as unknown as Record<string, unknown>); } if (options.includeGps && tags.gps) { metadata.gps = extractGps(tags.gps as unknown as Record<string, unknown>); } if (options.includeColor) { const hasIccProfile = !!fileInfo.icc; metadata.color = { colorProfile: fileInfo.icc ? 'Embedded' : undefined, hasIccProfile, }; } if (tags.iptc) { metadata.iptc = extractIptc(tags.iptc as unknown as Record<string, unknown>); } if (tags.xmp) { metadata.xmp = extractXmp(tags.xmp as unknown as Record<string, unknown>); } if (fileInfo.pages) { metadata.animation = { frameCount: fileInfo.pages, }; } if (options.includeThumbnail) { const thumbnailBuffer = await sharp(imagePath) .resize(200, 200, { fit: 'inside' }) .toBuffer(); metadata.thumbnail = `data:image/jpeg;base64,${thumbnailBuffer.toString('base64')}`; } if (options.includeDeepHash) { const crypto = await import('crypto'); const hash = crypto.createHash('sha256').update(buffer).digest('hex'); metadata.deepHash = hash; } return metadata; }