search_gallery
Search AI image prompts using semantic understanding to find visually and conceptually similar results. Browse image URLs for inspiration and style exploration.
Instructions
Search AI image prompts with semantic understanding — finds visually and conceptually similar results, not just keyword matches. Results include image URLs — render them as markdown images () so users can visually browse and pick styles. Use when users need inspiration, want to explore styles, or say "generate an image" without a specific idea.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Search keywords (e.g., "cyberpunk", "product photo", "portrait"). Supports semantic search — natural language descriptions work well. Leave empty to browse by category or get random picks. | |
| category | No | Filter by category. Available: Photography, Illustration & 3D, Product & Brand, Food & Drink, Poster Design, UI & Graphic | |
| limit | No | Number of results (1-20, default 5) | |
| offset | No | Pagination offset | |
| sortBy | No | Sort order when browsing without search query (default: rank) | rank |
Implementation Reference
- src/tools/search-gallery.ts:35-92 (handler)The async handler function that executes the search_gallery tool logic. It tries semantic search via API first, then falls back to local keyword-based search in the curated prompt library. Handles three cases: random picks (no criteria), API semantic search (query without category), and local search (with category or as fallback).
async ({ query, category, limit, offset, sortBy }) => { // No search criteria — return random picks from local library if (!query && !category && offset === 0) { const random = getRandomPrompts(limit) const stats = getLibraryStats() const header = `Curated Prompt Library: ${stats.total} trending prompts\nCategories: ${Object.entries(stats.categories).map(([k, v]) => `${k} (${v})`).join(', ')}\n\nHere are ${limit} random picks — show the preview images to the user:\n` return { content: [{ type: 'text' as const, text: header + formatLocalResults(random), }], } } // Has query and no category filter → try semantic search via API if (query && query.trim() && !category) { const apiResults = await apiSearchPosts(config.meigenBaseUrl, query, limit, offset) if (apiResults && apiResults.length > 0) { const text = `Found ${apiResults.length} results for "${query}" (semantic search):\n\n${formatApiResults(apiResults)}\n\nShow the preview images above to the user so they can visually browse. Use get_inspiration(imageId) to get the full prompt and all images for any entry the user likes.` return { content: [{ type: 'text' as const, text, }], } } // API failed or no results — fall through to local search } // Local search (keyword-based): with category filter, or as API fallback const results = searchPrompts({ query, category, limit, offset, sortBy }) if (results.length === 0) { const suggestion = category ? `No results for "${query || ''}" in category "${category}". Try a different keyword or remove the category filter.` : `No results for "${query}". Try broader keywords like "portrait", "landscape", "product", "anime".` return { content: [{ type: 'text' as const, text: suggestion, }], } } const searchDesc = [ query ? `"${query}"` : null, category ? `category: ${category}` : null, ].filter(Boolean).join(', ') const text = `Found ${results.length} results${searchDesc ? ` for ${searchDesc}` : ''}:\n\n${formatLocalResults(results)}\n\nShow the preview images above to the user so they can visually browse. Use get_inspiration(imageId) to get the full prompt and all images for any entry the user likes.` return { content: [{ type: 'text' as const, text, }], } } - src/tools/search-gallery.ts:16-27 (schema)Input schema for search_gallery tool using Zod. Defines fields: query (optional string), category (enum of 6 categories), limit (1-20, default 5), offset (min 0, default 0), sortBy (rank/likes/views/date, default rank).
export const searchGallerySchema = { query: z.string().optional() .describe('Search keywords (e.g., "cyberpunk", "product photo", "portrait"). Supports semantic search — natural language descriptions work well. Leave empty to browse by category or get random picks.'), category: z.enum(['Photography', 'Illustration & 3D', 'Product & Brand', 'Food & Drink', 'Poster Design', 'UI & Graphic']).optional() .describe('Filter by category. Available: Photography, Illustration & 3D, Product & Brand, Food & Drink, Poster Design, UI & Graphic'), limit: z.number().min(1).max(20).optional().default(5) .describe('Number of results (1-20, default 5)'), offset: z.number().min(0).optional().default(0) .describe('Pagination offset'), sortBy: z.enum(['rank', 'likes', 'views', 'date']).optional().default('rank') .describe('Sort order when browsing without search query (default: rank)'), } - src/tools/search-gallery.ts:29-94 (registration)The registerSearchGallery function that registers the 'search_gallery' tool with the MCP server using server.tool(), including the schema, readOnlyHint, and the handler.
export function registerSearchGallery(server: McpServer, config: MeiGenConfig) { server.tool( 'search_gallery', 'Search AI image prompts with semantic understanding — finds visually and conceptually similar results, not just keyword matches. Results include image URLs — render them as markdown images () so users can visually browse and pick styles. Use when users need inspiration, want to explore styles, or say "generate an image" without a specific idea.', searchGallerySchema, { readOnlyHint: true }, async ({ query, category, limit, offset, sortBy }) => { // No search criteria — return random picks from local library if (!query && !category && offset === 0) { const random = getRandomPrompts(limit) const stats = getLibraryStats() const header = `Curated Prompt Library: ${stats.total} trending prompts\nCategories: ${Object.entries(stats.categories).map(([k, v]) => `${k} (${v})`).join(', ')}\n\nHere are ${limit} random picks — show the preview images to the user:\n` return { content: [{ type: 'text' as const, text: header + formatLocalResults(random), }], } } // Has query and no category filter → try semantic search via API if (query && query.trim() && !category) { const apiResults = await apiSearchPosts(config.meigenBaseUrl, query, limit, offset) if (apiResults && apiResults.length > 0) { const text = `Found ${apiResults.length} results for "${query}" (semantic search):\n\n${formatApiResults(apiResults)}\n\nShow the preview images above to the user so they can visually browse. Use get_inspiration(imageId) to get the full prompt and all images for any entry the user likes.` return { content: [{ type: 'text' as const, text, }], } } // API failed or no results — fall through to local search } // Local search (keyword-based): with category filter, or as API fallback const results = searchPrompts({ query, category, limit, offset, sortBy }) if (results.length === 0) { const suggestion = category ? `No results for "${query || ''}" in category "${category}". Try a different keyword or remove the category filter.` : `No results for "${query}". Try broader keywords like "portrait", "landscape", "product", "anime".` return { content: [{ type: 'text' as const, text: suggestion, }], } } const searchDesc = [ query ? `"${query}"` : null, category ? `category: ${category}` : null, ].filter(Boolean).join(', ') const text = `Found ${results.length} results${searchDesc ? ` for ${searchDesc}` : ''}:\n\n${formatLocalResults(results)}\n\nShow the preview images above to the user so they can visually browse. Use get_inspiration(imageId) to get the full prompt and all images for any entry the user likes.` return { content: [{ type: 'text' as const, text, }], } } ) } - src/server.ts:264-267 (registration)Registration call that wires registerSearchGallery into the server setup, alongside other free features (enhance_prompt, list_models, get_inspiration, manage_preferences).
// Free features (no configuration required) registerEnhancePrompt(server) registerSearchGallery(server, config) registerListModels(server, apiClient, config) - src/lib/prompt-library.ts:113-168 (helper)The searchPrompts helper function that performs local keyword-based search and category filtering on the curated prompt library (data/trending-prompts.json). Used by the handler as a fallback when API search fails or is not applicable.
export function searchPrompts(options: SearchOptions): TrendingPrompt[] { const prompts = loadPrompts() const { query, category, limit = 10, offset = 0, sortBy = 'rank' } = options let filtered = prompts // Category filter if (category) { const cat = category.toLowerCase() filtered = filtered.filter(p => p.categories.some(c => c.toLowerCase() === cat) ) } // Keyword search if (query && query.trim()) { const keywords = query.toLowerCase().split(/\s+/).filter(Boolean) filtered = filtered.map(p => { const searchText = [ p.prompt, p.author_name, p.author, ...p.categories, ].join(' ').toLowerCase() // Calculate match score let score = 0 for (const kw of keywords) { if (searchText.includes(kw)) { score += 1 // Higher score for prompt text match if (p.prompt.toLowerCase().includes(kw)) score += 2 // Higher score for category match if (p.categories.some(c => c.toLowerCase().includes(kw))) score += 3 } } return { prompt: p, score } }) .filter(r => r.score > 0) .sort((a, b) => b.score - a.score || a.prompt.rank - b.prompt.rank) .map(r => r.prompt) } else { // No keyword — sort by specified field filtered = [...filtered].sort((a, b) => { switch (sortBy) { case 'likes': return b.likes - a.likes case 'views': return b.views - a.views case 'date': return b.date.localeCompare(a.date) case 'rank': default: return a.rank - b.rank } }) } return filtered.slice(offset, offset + limit) }