advancedSearch
Search for food products with multiple filters including category, brand, Nutri-Score, Eco-Score, NOVA group, allergen-free, labels, countries, and sort by popularity or scores.
Instructions
Advanced product search with multiple filters: category, brand, nutri-score, eco-score, NOVA group, allergen-free, labels, country
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Search query (product name, ingredients, etc.) | |
| category | No | Filter by category | |
| brand | No | Filter by brand | |
| nutriscoreGrade | No | Filter by Nutri-Score grade | |
| ecoscoreGrade | No | Filter by Eco-Score grade | |
| novaGroup | No | Filter by NOVA group (food processing level) | |
| allergenFree | No | Filter by allergen-free (e.g., "gluten", "milk", "eggs") | |
| labels | No | Filter by labels (e.g., "organic", "fair-trade", "vegan") | |
| countries | No | Filter by country (e.g., "united-states", "france") | |
| sortBy | No | ||
| page | No | ||
| pageSize | No |
Implementation Reference
- src/tools/helpers.ts:122-181 (handler)The actual implementation of the advancedSearch function. Builds a Lucene query from multiple filters (category, brand, nutriscore, ecoscore, novaGroup, allergenFree, labels, countries, sortBy) and calls the Search-a-licious API with fallback to the standard search API.
export async function advancedSearch(params: { query?: string; category?: string; brand?: string; nutriscoreGrade?: string; ecoscoreGrade?: string; novaGroup?: string; allergenFree?: string; labels?: string; countries?: string; sortBy?: string; page: number; pageSize: number; }): Promise<SearchResult> { // Build Lucene query from filters const queryParts: string[] = []; if (params.query) queryParts.push(params.query); if (params.category) queryParts.push(`categories_tags:"en:${params.category}"`); if (params.brand) queryParts.push(`brands:"${params.brand}"`); if (params.nutriscoreGrade) queryParts.push(`nutriscore_grade:${params.nutriscoreGrade}`); if (params.ecoscoreGrade) queryParts.push(`ecoscore_grade:${params.ecoscoreGrade}`); if (params.novaGroup) queryParts.push(`nova_group:${params.novaGroup}`); if (params.labels) queryParts.push(`labels_tags:"en:${params.labels}"`); if (params.countries) queryParts.push(`countries_tags:"en:${params.countries}"`); // Handle allergen-free filter (search for products WITHOUT the allergen) if (params.allergenFree) { queryParts.push(`-allergens_tags:"en:${params.allergenFree}"`); } const searchQuery = queryParts.join(' '); const url = new URL(`${SEARCH_API_URL}/search`); url.searchParams.set('q', searchQuery || '*'); url.searchParams.set('page', params.page.toString()); url.searchParams.set('page_size', params.pageSize.toString()); if (params.sortBy) url.searchParams.set('sort_by', params.sortBy); const response = await fetch(url.toString()); if (!response.ok) { logger.warn('Search-a-licious failed, falling back to standard API'); return fallbackSearch(params); } const data = await response.json(); if (data.errors) { logger.warn('Search-a-licious returned errors, falling back to standard API'); return fallbackSearch(params); } return { products: (data.hits || []).map(mapToSearchProduct), count: data.count || 0, page: data.page || params.page, pageSize: data.page_size || params.pageSize, pageCount: data.page_count || Math.ceil((data.count || 0) / params.pageSize) }; } - src/tools/category-tools.ts:23-36 (schema)Zod schema for advancedSearch input validation, defining optional filters: query, category, brand, nutriscoreGrade (a-e), ecoscoreGrade (a-e), novaGroup (1-4), allergenFree, labels, countries, sortBy, page, pageSize.
const advancedSearchSchema = { query: z.string().optional().describe('Search query (product name, ingredients, etc.)'), category: z.string().optional().describe('Filter by category'), brand: z.string().optional().describe('Filter by brand'), nutriscoreGrade: z.enum(['a', 'b', 'c', 'd', 'e']).optional().describe('Filter by Nutri-Score grade'), ecoscoreGrade: z.enum(['a', 'b', 'c', 'd', 'e']).optional().describe('Filter by Eco-Score grade'), novaGroup: z.enum(['1', '2', '3', '4']).optional().describe('Filter by NOVA group (food processing level)'), allergenFree: z.string().optional().describe('Filter by allergen-free (e.g., "gluten", "milk", "eggs")'), labels: z.string().optional().describe('Filter by labels (e.g., "organic", "fair-trade", "vegan")'), countries: z.string().optional().describe('Filter by country (e.g., "united-states", "france")'), sortBy: z.enum(['popularity', 'nutriscore_score', 'ecoscore_score', 'created_t', 'last_modified_t']).optional(), page: z.number().default(1), pageSize: z.number().default(10) }; - src/tools/category-tools.ts:71-94 (registration)Registration of 'advancedSearch' tool on the MCP server, wiring the Zod schema and the handler callback that calls the advancedSearch helper function.
server.registerTool('advancedSearch', { description: 'Advanced product search with multiple filters: category, brand, nutri-score, eco-score, NOVA group, allergen-free, labels, country', inputSchema: advancedSearchSchema }, async (params) => { try { const results = await advancedSearch({ query: params.query, category: params.category, brand: params.brand, nutriscoreGrade: params.nutriscoreGrade, ecoscoreGrade: params.ecoscoreGrade, novaGroup: params.novaGroup, allergenFree: params.allergenFree, labels: params.labels, countries: params.countries, sortBy: params.sortBy, page: params.page ?? 1, pageSize: params.pageSize ?? 10 }); return { content: [{ type: 'text' as const, text: JSON.stringify(results, null, 2) }] }; } catch (error: any) { return { content: [{ type: 'text' as const, text: `Error: ${error.message}` }], isError: true }; } }); - src/tools/helpers.ts:186-207 (helper)The fallbackSearch helper function used by advancedSearch when the Search-a-licious API fails or returns errors. Uses the standard Open Food Facts search API.
export async function fallbackSearch(params: { query?: string; page: number; pageSize: number; }): Promise<SearchResult> { const url = new URL(`${BASE_URL}/cgi/search.pl`); url.searchParams.set('search_terms', params.query || ''); url.searchParams.set('page', params.page.toString()); url.searchParams.set('page_size', params.pageSize.toString()); url.searchParams.set('json', '1'); const response = await fetch(url.toString()); const data = await response.json(); return { products: (data.products || []).map(mapToSearchProduct), count: data.count || 0, page: params.page, pageSize: params.pageSize, pageCount: Math.ceil((data.count || 0) / params.pageSize) }; }