UNPKG

mcp-openverse

Version:

MCP server for searching openly-licensed images via Openverse API

257 lines 10.2 kB
#!/usr/bin/env node import { FastMCP } from 'fastmcp'; import { z } from 'zod'; const OPENVERSE_API_BASE = 'https://api.openverse.org/v1'; const DEFAULT_PAGE_SIZE = 20; // Create the MCP server const server = new FastMCP({ name: 'openverse-images', version: '0.1.0' }); // Schema definitions const searchImagesSchema = z.object({ query: z.string().describe('Search terms (required)'), page: z.number().optional().describe('Page number (default: 1)'), page_size: z.number().optional().describe('Results per page (default: 20, max: 500)'), license: z.string().optional().describe('License filter (e.g., by, by-sa, cc0)'), license_type: z.string().optional().describe('License type (commercial or modification)'), creator: z.string().optional().describe('Filter by creator name'), source: z.string().optional().describe('Filter by source (e.g., flickr, wikimedia)'), extension: z.string().optional().describe('File type (jpg, png, gif, svg)'), aspect_ratio: z.string().optional().describe('Image shape (tall, wide, square)'), size: z.string().optional().describe('Image size (small, medium, large)'), mature: z.boolean().optional().describe('Include mature content (default: false)') }); const imageDetailsSchema = z.object({ image_id: z.string().describe('Openverse image ID (UUID format)') }); const relatedImagesSchema = z.object({ image_id: z.string().describe('Openverse image ID'), page: z.number().optional().describe('Page number (default: 1)'), page_size: z.number().optional().describe('Results per page (default: 10)') }); const essayImagesSchema = z.object({ essay_topic: z.string().describe('Main topic/title of the essay'), concepts: z.array(z.string()).describe('List of key concepts to find images for'), style: z.enum(['photo', 'illustration', 'any']).optional().describe('Preferred image style (default: any)'), max_images: z.number().optional().describe('Maximum images to return (default: 10)') }); // Tool: search_images server.addTool({ name: 'search_images', description: 'Search for openly-licensed images on Openverse', parameters: searchImagesSchema, execute: async (args) => { const params = { q: args.query, page: String(args.page || 1), page_size: String(Math.min(args.page_size || DEFAULT_PAGE_SIZE, 500)), mature: String(args.mature || false) }; // Add optional parameters if (args.license) params.license = args.license; if (args.license_type) params.license_type = args.license_type; if (args.creator) params.creator = args.creator; if (args.source) params.source = args.source; if (args.extension) params.extension = args.extension; if (args.aspect_ratio) params.aspect_ratio = args.aspect_ratio; if (args.size) params.size = args.size; try { const queryParams = new URLSearchParams(params); const response = await fetch(`${OPENVERSE_API_BASE}/images/?${queryParams}`, { headers: { 'User-Agent': 'MCP-Openverse/1.0' } }); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } const data = await response.json(); return JSON.stringify(data, null, 2); } catch (error) { return JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }); } } }); // Tool: get_image_details server.addTool({ name: 'get_image_details', description: 'Get detailed information about a specific image', parameters: imageDetailsSchema, execute: async (args) => { try { const response = await fetch(`${OPENVERSE_API_BASE}/images/${args.image_id}/`, { headers: { 'User-Agent': 'MCP-Openverse/1.0' } }); if (!response.ok) { throw new Error(`Failed to fetch image details: ${response.status} ${response.statusText}`); } const data = await response.json(); return JSON.stringify(data, null, 2); } catch (error) { return JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }); } } }); // Tool: get_related_images server.addTool({ name: 'get_related_images', description: 'Get images related to a specific image', parameters: relatedImagesSchema, execute: async (args) => { const params = new URLSearchParams({ page: String(args.page || 1), page_size: String(args.page_size || 10) }); try { const response = await fetch(`${OPENVERSE_API_BASE}/images/${args.image_id}/related/?${params}`, { headers: { 'User-Agent': 'MCP-Openverse/1.0' } }); if (!response.ok) { throw new Error(`Failed to fetch related images: ${response.status} ${response.statusText}`); } const data = await response.json(); return JSON.stringify(data, null, 2); } catch (error) { return JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }); } } }); // Tool: get_image_stats server.addTool({ name: 'get_image_stats', description: 'Get statistics about image providers and counts', parameters: z.object({}), execute: async () => { try { const response = await fetch(`${OPENVERSE_API_BASE}/images/stats/`, { headers: { 'User-Agent': 'MCP-Openverse/1.0' } }); if (!response.ok) { throw new Error(`Failed to fetch stats: ${response.status} ${response.statusText}`); } const data = await response.json(); return JSON.stringify(data, null, 2); } catch (error) { return JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }); } } }); // Tool: search_images_for_essay server.addTool({ name: 'search_images_for_essay', description: 'Search for images suitable for illustrating an essay', parameters: essayImagesSchema, execute: async (args) => { const { essay_topic, concepts = [], style = 'any', max_images = 10 } = args; const results = { topic: essay_topic, images_by_concept: {}, featured_images: [], total_images: 0 }; // Helper function to search images const searchImages = async (query, pageSize, filters = {}) => { const params = { q: query, page_size: String(pageSize), mature: 'false', ...filters }; if (style === 'photo') { params.extension = 'jpg,png'; } const queryParams = new URLSearchParams(params); try { const response = await fetch(`${OPENVERSE_API_BASE}/images/?${queryParams}`, { headers: { 'User-Agent': 'MCP-Openverse/1.0' } }); if (!response.ok) { return { results: [] }; } return await response.json(); } catch { return { results: [] }; } }; try { // Search for main topic const mainSearch = await searchImages(essay_topic, Math.min(5, max_images)); if (mainSearch.results) { results.featured_images = mainSearch.results.slice(0, 3).map((img) => ({ id: img.id, title: img.title || '', url: img.url, thumbnail: img.thumbnail || '', creator: img.creator || 'Unknown', license: img.license || '', attribution: img.attribution || '', source: img.source || '' })); results.total_images += results.featured_images.length; } // Search for each concept const imagesPerConcept = concepts.length ? Math.max(1, Math.floor(max_images / concepts.length)) : max_images; for (const concept of concepts) { if (results.total_images >= max_images) break; const conceptSearch = await searchImages(`${concept} ${essay_topic}`, imagesPerConcept); if (conceptSearch.results && conceptSearch.results.length > 0) { results.images_by_concept[concept] = conceptSearch.results .slice(0, imagesPerConcept) .map((img) => ({ id: img.id, title: img.title || '', url: img.url, thumbnail: img.thumbnail || '', creator: img.creator || 'Unknown', license: img.license || '', attribution: img.attribution || '', source: img.source || '' })); results.total_images += results.images_by_concept[concept].length; } } return JSON.stringify(results, null, 2); } catch (error) { return JSON.stringify({ error: error instanceof Error ? error.message : 'Failed to search for essay images', topic: essay_topic }); } } }); // Start the server server.start({ transportType: 'stdio' }).catch(console.error); //# sourceMappingURL=index.js.map