Skip to main content
Glama

Open Food Facts MCP Server

by caleb-conner
handlers.ts13.7 kB
import { OpenFoodFactsClient } from './client.js'; import { Product } from './types.js'; export class ToolHandlers { constructor(private client: OpenFoodFactsClient) {} async handleGetProduct(barcode: string) { const response = await this.client.getProduct(barcode); if (response.status === 0 || !response.product) { return { content: [ { type: "text" as const, text: `Product not found for barcode: ${barcode}`, }, ], }; } const product = response.product; const formattedProduct = this.formatProduct(product); return { content: [ { type: "text" as const, text: formattedProduct, }, ], }; } async handleSearchProducts(params: any) { const response = await this.client.searchProducts(params); if (response.products.length === 0) { return { content: [ { type: "text" as const, text: "No products found matching your search criteria.", }, ], }; } const summary = `Found ${response.count} products (showing page ${response.page} of ${response.page_count}):\n\n`; const productList = response.products .map((product, index) => `${index + 1}. ${this.formatProductSummary(product)}`) .join('\n\n'); return { content: [ { type: "text" as const, text: summary + productList, }, ], }; } async handleAnalyzeProduct(barcode: string) { const response = await this.client.getProduct(barcode); if (response.status === 0 || !response.product) { return { content: [ { type: "text" as const, text: `Product not found for barcode: ${barcode}`, }, ], }; } const analysis = this.analyzeNutrition(response.product); return { content: [ { type: "text" as const, text: analysis, }, ], }; } async handleCompareProducts(barcodes: string[], focus: string = 'nutrition') { const responses = await this.client.getProductsByBarcodes(barcodes); const validProducts = responses.filter(r => r.status !== 0 && r.product); if (validProducts.length < 2) { return { content: [ { type: "text" as const, text: "Need at least 2 valid products to compare.", }, ], }; } const comparison = this.compareProductsByFocus(validProducts.map(r => r.product!), focus); return { content: [ { type: "text" as const, text: comparison, }, ], }; } async handleGetProductSuggestions(params: any) { const { category, dietary_preferences = [], max_results = 10, min_nutriscore } = params; const searchParams: any = { categories: category, page_size: Math.min(max_results * 2, 100), // Get extra to filter sort_by: 'popularity', }; if (min_nutriscore) { const validGrades = ['a', 'b', 'c', 'd', 'e']; const minIndex = validGrades.indexOf(min_nutriscore); const allowedGrades = validGrades.slice(0, minIndex + 1); searchParams.nutrition_grades = allowedGrades.join(','); } const response = await this.client.searchProducts(searchParams); let filteredProducts = response.products; // Filter by dietary preferences if (dietary_preferences.length > 0) { filteredProducts = this.filterByDietaryPreferences(filteredProducts, dietary_preferences); } const suggestions = filteredProducts .slice(0, max_results) .map((product, index) => `${index + 1}. ${this.formatProductSuggestion(product)}`) .join('\n\n'); return { content: [ { type: "text" as const, text: `Product suggestions in ${category}:\n\n${suggestions}`, }, ], }; } private formatProduct(product: Product): string { const sections = []; // Basic info sections.push(`**${product.product_name || 'Unknown Product'}**`); if (product.brands) sections.push(`Brand: ${product.brands}`); if (product.quantity) sections.push(`Quantity: ${product.quantity}`); if (product.categories) sections.push(`Categories: ${product.categories}`); // Nutritional scores const scores = []; if (product.nutriscore_grade) scores.push(`Nutri-Score: ${product.nutriscore_grade.toUpperCase()}`); if (product.nova_group) scores.push(`NOVA Group: ${product.nova_group}`); if (product.ecoscore_grade) scores.push(`Eco-Score: ${product.ecoscore_grade.toUpperCase()}`); if (scores.length > 0) sections.push(`\n**Scores:**\n${scores.join(' | ')}`); // Ingredients if (product.ingredients_text) { sections.push(`\n**Ingredients:**\n${product.ingredients_text}`); } // Key nutrients if (product.nutriments) { const keyNutrients = this.extractKeyNutrients(product.nutriments); if (keyNutrients) { sections.push(`\n**Nutrition (per 100g):**\n${keyNutrients}`); } } // Additional info const additional = []; if (product.packaging) additional.push(`Packaging: ${product.packaging}`); if (product.labels) additional.push(`Labels: ${product.labels}`); if (product.countries) additional.push(`Countries: ${product.countries}`); if (additional.length > 0) sections.push(`\n**Additional Info:**\n${additional.join('\n')}`); return sections.join('\n'); } private formatProductSummary(product: Product): string { const name = product.product_name || 'Unknown Product'; const brand = product.brands ? ` (${product.brands})` : ''; const nutriscore = product.nutriscore_grade ? ` [Nutri-Score: ${product.nutriscore_grade.toUpperCase()}]` : ''; return `**${name}**${brand}${nutriscore}\nBarcode: ${product.code}`; } private formatProductSuggestion(product: Product): string { const summary = this.formatProductSummary(product); const scores = []; if (product.nutriscore_grade) scores.push(`Nutri-Score: ${product.nutriscore_grade.toUpperCase()}`); if (product.nova_group) scores.push(`Processing: NOVA ${product.nova_group}`); if (product.ecoscore_grade) scores.push(`Eco: ${product.ecoscore_grade.toUpperCase()}`); return summary + (scores.length > 0 ? `\nScores: ${scores.join(' | ')}` : ''); } private analyzeNutrition(product: Product): string { const sections = [`**Nutritional Analysis: ${product.product_name || 'Unknown Product'}**\n`]; // Scores interpretation const scoreAnalysis = []; if (product.nutriscore_grade) { const grade = product.nutriscore_grade.toUpperCase(); const gradeDesc = this.getNutriscoreDescription(grade); scoreAnalysis.push(`• Nutri-Score ${grade}: ${gradeDesc}`); } if (product.nova_group) { const group = String(product.nova_group); const groupDesc = this.getNovaDescription(group); scoreAnalysis.push(`• NOVA Group ${group}: ${groupDesc}`); } if (product.ecoscore_grade) { const grade = product.ecoscore_grade.toUpperCase(); const gradeDesc = this.getEcoscoreDescription(grade); scoreAnalysis.push(`• Eco-Score ${grade}: ${gradeDesc}`); } if (scoreAnalysis.length > 0) { sections.push(`**Scores:**\n${scoreAnalysis.join('\n')}\n`); } // Detailed nutrition if (product.nutriments) { const nutrition = this.analyzeNutriments(product.nutriments); if (nutrition) { sections.push(`**Nutritional Breakdown:**\n${nutrition}\n`); } } return sections.join('\n'); } private compareProductsByFocus(products: Product[], focus: string): string { const comparison = [`**Product Comparison (${focus})**\n`]; products.forEach((product, index) => { comparison.push(`**${index + 1}. ${product.product_name || 'Unknown'}**`); switch (focus) { case 'nutrition': if (product.nutriscore_grade) { comparison.push(`Nutri-Score: ${product.nutriscore_grade.toUpperCase()}`); } if (product.nutriments) { const keyNutrients = this.extractKeyNutrients(product.nutriments); if (keyNutrients) comparison.push(`Nutrients:\n${keyNutrients}`); } break; case 'environmental': if (product.ecoscore_grade) { comparison.push(`Eco-Score: ${product.ecoscore_grade.toUpperCase()}`); } if (product.packaging) { comparison.push(`Packaging: ${product.packaging}`); } break; case 'processing': if (product.nova_group) { comparison.push(`NOVA Group: ${product.nova_group} (${this.getNovaDescription(String(product.nova_group))})`); } break; case 'ingredients': if (product.ingredients_text) { comparison.push(`Ingredients: ${product.ingredients_text.substring(0, 200)}${product.ingredients_text.length > 200 ? '...' : ''}`); } break; } comparison.push(''); }); return comparison.join('\n'); } private filterByDietaryPreferences(products: Product[], preferences: string[]): Product[] { return products.filter(product => { const labels = (product.labels || '').toLowerCase(); const categories = (product.categories || '').toLowerCase(); const ingredients = (product.ingredients_text || '').toLowerCase(); return preferences.every(pref => { switch (pref) { case 'vegan': return labels.includes('vegan') || categories.includes('vegan'); case 'vegetarian': return labels.includes('vegetarian') || categories.includes('vegetarian'); case 'gluten-free': return labels.includes('gluten') && labels.includes('free'); case 'organic': return labels.includes('organic') || labels.includes('bio'); case 'low-fat': return labels.includes('low-fat') || labels.includes('light'); case 'low-sugar': return labels.includes('sugar-free') || labels.includes('no-sugar'); case 'high-protein': return labels.includes('high-protein') || labels.includes('protein'); default: return true; } }); }); } private extractKeyNutrients(nutriments: Record<string, string | number>): string { const nutrients = []; if (nutriments.energy_100g || nutriments['energy-kcal_100g']) { const energy = nutriments['energy-kcal_100g'] || nutriments.energy_100g; nutrients.push(`Energy: ${energy} kcal`); } if (nutriments.fat_100g) nutrients.push(`Fat: ${nutriments.fat_100g}g`); if (nutriments.carbohydrates_100g) nutrients.push(`Carbs: ${nutriments.carbohydrates_100g}g`); if (nutriments.sugars_100g) nutrients.push(`Sugars: ${nutriments.sugars_100g}g`); if (nutriments.proteins_100g) nutrients.push(`Protein: ${nutriments.proteins_100g}g`); if (nutriments.salt_100g) nutrients.push(`Salt: ${nutriments.salt_100g}g`); if (nutriments.fiber_100g) nutrients.push(`Fiber: ${nutriments.fiber_100g}g`); return nutrients.join(' | '); } private analyzeNutriments(nutriments: Record<string, string | number>): string { const analysis = []; const energy = Number(nutriments['energy-kcal_100g'] || nutriments.energy_100g || 0); if (energy > 0) { let energyLevel = 'moderate'; if (energy < 150) energyLevel = 'low'; else if (energy > 400) energyLevel = 'high'; analysis.push(`• Energy: ${energy} kcal (${energyLevel})`); } const fat = Number(nutriments.fat_100g || 0); if (fat > 0) { let fatLevel = fat > 20 ? 'high' : fat < 3 ? 'low' : 'moderate'; analysis.push(`• Fat: ${fat}g (${fatLevel})`); } const sugar = Number(nutriments.sugars_100g || 0); if (sugar > 0) { let sugarLevel = sugar > 22.5 ? 'high' : sugar < 5 ? 'low' : 'moderate'; analysis.push(`• Sugars: ${sugar}g (${sugarLevel})`); } const salt = Number(nutriments.salt_100g || 0); if (salt > 0) { let saltLevel = salt > 1.5 ? 'high' : salt < 0.3 ? 'low' : 'moderate'; analysis.push(`• Salt: ${salt}g (${saltLevel})`); } return analysis.join('\n'); } private getNutriscoreDescription(grade: string): string { const descriptions: Record<string, string> = { A: 'Excellent nutritional quality', B: 'Good nutritional quality', C: 'Average nutritional quality', D: 'Poor nutritional quality', E: 'Very poor nutritional quality', }; return descriptions[grade] || 'Unknown quality'; } private getNovaDescription(group: string): string { const descriptions: Record<string, string> = { '1': 'Unprocessed or minimally processed foods', '2': 'Processed culinary ingredients', '3': 'Processed foods', '4': 'Ultra-processed foods', }; return descriptions[group] || 'Unknown processing level'; } private getEcoscoreDescription(grade: string): string { const descriptions: Record<string, string> = { A: 'Very low environmental impact', B: 'Low environmental impact', C: 'Moderate environmental impact', D: 'High environmental impact', E: 'Very high environmental impact', }; return descriptions[grade] || 'Unknown environmental impact'; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/caleb-conner/open-food-facts-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server