Skip to main content
Glama
Aryan-Jhaveri

Canada's Food Guide MCP Server

cli.py40.5 kB
import argparse import sys import sqlite3 import json import uuid from typing import List, Dict, Any, Optional from .api.search import RecipeSearcher from .api.recipe import RecipeFetcher from .models.filters import SearchFilters from .utils.downloader import RecipeDownloader from .db.connection import get_db_connection from .db.schema import ( initialize_database, create_virtual_recipe_session, cleanup_virtual_session, list_active_virtual_sessions, store_recipe_in_virtual_session, get_virtual_session_recipes ) from .models.db_models import RecipeInput, FavoriteInput, SessionInput, RecipeQueryInput ## db update class FoodGuideCLI: def __init__(self): self.searcher = RecipeSearcher() self.fetcher = RecipeFetcher() self.downloader = RecipeDownloader() # Database management methods def initialize_db(self): """Initialize the recipe database.""" result = initialize_database() if "success" in result: #print(f"✅ {result['success']}") else: #print(f"❌ {result['error']}") return result def create_session(self, session_id: str): """Create a virtual recipe session.""" result = create_virtual_recipe_session(session_id) if "success" in result: #print(f"✅ {result['success']}") else: #print(f"❌ {result['error']}") return result def cleanup_session(self, session_id: str): """Clean up a virtual recipe session.""" result = cleanup_virtual_session(session_id) if "success" in result: #print(f"✅ {result['success']}") elif "message" in result: #print(f"ℹ️ {result['message']}") else: #print(f"❌ {result['error']}") return result def list_sessions(self): """List all active virtual sessions.""" result = list_active_virtual_sessions() if "sessions" in result: sessions = result["sessions"] if sessions: #print("📋 Active sessions:") for session in sessions: #print(f" • {session['session_id']}: {session['recipe_count']} recipes") else: #print("ℹ️ No active sessions found") else: #print(f"❌ {result['error']}") return result def store_recipe(self, session_id: str, recipe_url: str): """Fetch and store a recipe in a virtual session.""" #print(f"🔄 Fetching recipe from: {recipe_url}") # Fetch the recipe recipe = self.fetcher.fetch_recipe(recipe_url) if not recipe: #print("❌ Failed to fetch recipe") return {"error": "Failed to fetch recipe"} # Convert recipe to dict format recipe_data = { "title": recipe.title, "slug": getattr(recipe, 'slug', ''), "url": recipe_url, "ingredients": recipe.ingredients or [], "instructions": recipe.instructions or [], "prep_time": getattr(recipe, 'prep_time', ''), "cook_time": getattr(recipe, 'cook_time', ''), "servings": getattr(recipe, 'servings', None), "categories": getattr(recipe, 'categories', []), "tips": getattr(recipe, 'tips', []), "recipe_highlights": getattr(recipe, 'recipe_highlights', []), "image_url": getattr(recipe, 'image_url', ''), "source": "Health Canada's Food Guide", "website": "https://food-guide.canada.ca/", "attribution": "Recipe sourced from Canada's official Food Guide" } # Generate recipe ID and store recipe_id = str(uuid.uuid4()) result = store_recipe_in_virtual_session(session_id, recipe_id, recipe_data) if "success" in result: #print(f"✅ {result['success']}") #print(f" Recipe ID: {recipe_id}") #print(f" Title: {result.get('title', 'Unknown')}") #print(f" Ingredients: {result.get('ingredients_count', 0)}") #print(f" Instructions: {result.get('instructions_count', 0)}") else: #print(f"❌ {result['error']}") return result def get_session_recipes(self, session_id: str, recipe_id: Optional[str] = None): """Get recipes from a virtual session.""" result = get_virtual_session_recipes(session_id, recipe_id) if "recipes" in result: recipes = result["recipes"] if recipes: #print(f"📋 Found {len(recipes)} recipe(s) in session '{session_id}':") for recipe in recipes: #print(f"\n 🍽️ {recipe.get('title', 'Unknown')}") #print(f" Recipe ID: {recipe.get('recipe_id', 'Unknown')}") #print(f" Servings: {recipe.get('base_servings', 'Unknown')}") #print(f" Ingredients: {len(recipe.get('ingredients', []))}") #print(f" Instructions: {len(recipe.get('instructions', []))}") else: #print(f"ℹ️ No recipes found in session '{session_id}'") else: #print(f"❌ {result['error']}") return result def parse_ingredients(self, session_id: str, recipe_id: Optional[str] = None): """Parse ingredients in a session to extract amounts, units, and names.""" from .db.ingredient_parser import _parse_ingredient_text from .db.schema import get_virtual_session_data try: # Get virtual session data session = get_virtual_session_data(session_id) if not session: #print(f"❌ Virtual session {session_id} not found") return {"error": f"Virtual session {session_id} not found"} # Get ingredients to process ingredients_to_process = [] for ingredient_id, ingredient_data in session['ingredients'].items(): if recipe_id and ingredient_data['recipe_id'] != recipe_id: continue ingredients_to_process.append((ingredient_id, ingredient_data)) if not ingredients_to_process: #print("❌ No ingredients found for parsing") return {"error": "No ingredients found for parsing"} #print(f"🔄 Parsing {len(ingredients_to_process)} ingredients...") parsing_results = [] updated_count = 0 failed_count = 0 for ingredient_id, ingredient_data in ingredients_to_process: # Parse from ingredient_list_org ingredient_text = ingredient_data.get('ingredient_list_org', '') if not ingredient_text: failed_count += 1 continue parsed_data = _parse_ingredient_text(ingredient_text) # Update virtual session data with parsed components ingredient_data['ingredient_name'] = parsed_data['clean_name'] ingredient_data['amount'] = parsed_data['amount'] ingredient_data['unit'] = parsed_data['unit'] parsing_results.append({ 'ingredient_id': ingredient_id, 'original_text': ingredient_text, 'parsed_amount': parsed_data['amount'], 'parsed_unit': parsed_data['unit'], 'clean_name': parsed_data['clean_name'], 'is_section_header': parsed_data['is_section_header'] }) if parsed_data['amount'] is not None: updated_count += 1 #print(f" ✅ {ingredient_text} → {parsed_data['amount']} {parsed_data['unit']} {parsed_data['clean_name']}") else: failed_count += 1 #print(f" ⚠️ {ingredient_text} → No amount found") #print(f"\n📊 Parsing Summary:") #print(f" Total ingredients: {len(ingredients_to_process)}") #print(f" Successfully parsed: {updated_count}") #print(f" Failed to parse: {failed_count}") #print(f" Success rate: {(updated_count/len(ingredients_to_process)*100):.1f}%") return { "success": f"Processed {len(ingredients_to_process)} ingredients", "total_ingredients": len(ingredients_to_process), "successfully_parsed": updated_count, "failed_to_parse": failed_count, "parsing_details": parsing_results } except Exception as e: #print(f"❌ Error parsing ingredients: {e}") return {"error": f"Unexpected error parsing ingredients: {e}"} def scale_recipe(self, session_id: str, recipe_id: str, target_servings: int): """Scale a recipe to a target number of servings.""" from .db.math_tools import _scale_ingredient_amount from .db.schema import get_virtual_session_data try: # Get virtual session data session = get_virtual_session_data(session_id) if not session: #print(f"❌ Virtual session {session_id} not found") return {"error": f"Virtual session {session_id} not found"} # Get original recipe data if recipe_id not in session['recipes']: #print(f"❌ Recipe {recipe_id} not found in session") return {"error": f"Recipe {recipe_id} not found in session {session_id}"} recipe_data = session['recipes'][recipe_id] original_servings = recipe_data.get('base_servings') or 1 scale_factor = target_servings / original_servings #print(f"🔄 Scaling '{recipe_data.get('title', 'Unknown')}' from {original_servings} to {target_servings} servings") #print(f" Scale factor: {scale_factor:.3f}") # Get ingredients for this recipe recipe_ingredients = [ (ing_id, ing_data) for ing_id, ing_data in session['ingredients'].items() if ing_data['recipe_id'] == recipe_id ] # Sort by ingredient order recipe_ingredients.sort(key=lambda x: x[1]['ingredient_order']) scaled_ingredients = [] scaling_log = [] for ingredient_id, ingredient_data in recipe_ingredients: original_text = ingredient_data.get('ingredient_list_org', '') ingredient_name = ingredient_data.get('ingredient_name', '') parsed_amount = ingredient_data.get('amount') parsed_unit = ingredient_data.get('unit') # Use parsed data if available, otherwise fall back to text parsing if parsed_amount is not None and parsed_unit is not None: # Direct calculation using parsed data scaled_amount_value = float(parsed_amount) * scale_factor scaled_text = f"{scaled_amount_value} {parsed_unit} {ingredient_name}" #print(f" ✅ {parsed_amount} {parsed_unit} → {scaled_amount_value:.2f} {parsed_unit}: {ingredient_name}") scaling_log.append(f"{parsed_amount} {parsed_unit} → {scaled_amount_value:.2f} {parsed_unit}: {ingredient_name}") else: # Fall back to text parsing scaled_text, amount_info = _scale_ingredient_amount(original_text, scale_factor) if amount_info['found_amount']: #print(f" ✅ {amount_info['original_amount']} → {amount_info['scaled_amount']}: {amount_info['ingredient_base']}") scaling_log.append(f"{amount_info['original_amount']} → {amount_info['scaled_amount']}: {amount_info['ingredient_base']}") else: #print(f" ⚠️ No scaling: {original_text}") scaled_ingredients.append({ 'original_text': original_text, 'scaled_text': scaled_text, 'ingredient_name': ingredient_name }) #print(f"\n📊 Scaling Summary:") #print(f" Ingredients processed: {len(recipe_ingredients)}") #print(f" Successfully scaled: {len(scaling_log)}") return { "success": f"Recipe scaled from {original_servings} to {target_servings} servings", "recipe_title": recipe_data.get('title', 'Unknown'), "scale_factor": scale_factor, "scaled_ingredients": scaled_ingredients, "scaling_summary": scaling_log } except Exception as e: #print(f"❌ Error scaling recipe: {e}") return {"error": f"Unexpected error scaling recipe: {e}"} # Favorites management methods def add_favorite(self, recipe_url: str, recipe_title: str = None, user_session: str = "default", custom_notes: str = None): """Add a recipe to favorites.""" try: with get_db_connection() as conn: cursor = conn.cursor() favorite_id = str(uuid.uuid4()) cursor.execute(""" INSERT OR IGNORE INTO user_favorites (favorite_id, recipe_url, recipe_title, user_session, custom_notes) VALUES (?, ?, ?, ?, ?) """, ( favorite_id, recipe_url, recipe_title, user_session, custom_notes )) if cursor.rowcount > 0: conn.commit() #print(f"✅ Recipe added to favorites: {recipe_title or recipe_url}") return {"success": f"Recipe added to favorites: {recipe_title}"} else: #print(f"ℹ️ Recipe was already in favorites") return {"message": "Recipe was already in favorites"} except sqlite3.Error as e: #print(f"❌ SQLite error adding favorite: {e}") return {"error": f"SQLite error adding favorite: {e}"} except Exception as e: #print(f"❌ Unexpected error adding favorite: {e}") return {"error": f"Unexpected error adding favorite: {e}"} def remove_favorite(self, recipe_url: str, user_session: str = None): """Remove a recipe from favorites.""" try: with get_db_connection() as conn: cursor = conn.cursor() where_clause = "recipe_url = ?" params = [recipe_url] if user_session: where_clause += " AND user_session = ?" params.append(user_session) cursor.execute(f""" DELETE FROM user_favorites WHERE {where_clause} """, params) if cursor.rowcount > 0: conn.commit() #print(f"✅ Recipe removed from favorites") return {"success": f"Recipe removed from favorites"} else: #print(f"ℹ️ Recipe was not in favorites") return {"message": "Recipe was not in favorites"} except sqlite3.Error as e: #print(f"❌ SQLite error removing favorite: {e}") return {"error": f"SQLite error removing favorite: {e}"} except Exception as e: #print(f"❌ Unexpected error removing favorite: {e}") return {"error": f"Unexpected error removing favorite: {e}"} def list_favorites_cmd(self, user_session: str = None): """List user's favorite recipes.""" try: with get_db_connection() as conn: cursor = conn.cursor() if user_session: cursor.execute(""" SELECT * FROM user_favorites WHERE user_session = ? ORDER BY added_at DESC """, (user_session,)) else: cursor.execute(""" SELECT * FROM user_favorites ORDER BY added_at DESC """) favorites = [dict(row) for row in cursor.fetchall()] if favorites: #print(f"⭐ Found {len(favorites)} favorite recipe(s):") for i, fav in enumerate(favorites, 1): #print(f"\n{i}. {fav['recipe_title'] or 'Untitled'}") #print(f" URL: {fav['recipe_url']}") #print(f" Added: {fav['added_at']}") if fav['custom_notes']: #print(f" Notes: {fav['custom_notes']}") if fav['user_session']: #print(f" Session: {fav['user_session']}") else: #print("ℹ️ No favorites found") return {"favorites": favorites} except sqlite3.Error as e: #print(f"❌ SQLite error listing favorites: {e}") return {"error": f"SQLite error listing favorites: {e}"} except Exception as e: #print(f"❌ Unexpected error listing favorites: {e}") return {"error": f"Unexpected error listing favorites: {e}"} def create_parser(self): """Create the argument parser with database commands.""" parser = argparse.ArgumentParser(description='Search Canada Food Guide recipes') subparsers = parser.add_subparsers(dest='command', help='Available commands') # Search subcommand search_parser = subparsers.add_parser('search', help='Search for recipes') # Add positional argument for search text search_parser.add_argument('search_text', nargs='*', default='', help='Text to search for in recipes') # Add filter arguments search_parser.add_argument('--fruits', nargs='*', help='Filter by fruits (e.g., apple, banana)') search_parser.add_argument('--vegetables', nargs='*', help='Filter by vegetables (e.g., carrot, broccoli)') search_parser.add_argument('--proteins', nargs='*', help='Filter by proteins (e.g., chicken, tofu)') search_parser.add_argument('--whole-grains', dest='whole_grains', nargs='*', help='Filter by whole grains (e.g., rice, quinoa)') search_parser.add_argument('--meals', nargs='*', help='Filter by meal type (e.g., breakfast, dinner)') search_parser.add_argument('--appliances', nargs='*', help='Filter by cooking appliance (e.g., oven, stovetop)') search_parser.add_argument('--collections', nargs='*', help='Filter by collections (e.g., vegetarian, kid-friendly)') # Other options search_parser.add_argument('--max-pages', type=int, default=5, help='Maximum pages to search (default: 5)') # Download command download_parser = subparsers.add_parser('download', help='Download recipes') download_parser.add_argument('--url', help='Recipe URL to download') download_parser.add_argument('--search', help='Search and download') download_parser.add_argument('--download-all', action='store_true', help='Download all search results') download_parser.add_argument('--format', choices=['json', 'md'], default='json', help='Output format') download_parser.add_argument('--#print', action='store_true', help='#print recipe data as JSON to console instead of saving to file') download_parser.add_argument('--fruits', nargs='+', help='Fruit filter IDs') download_parser.add_argument('--vegetables', nargs='+', help='Vegetable filter IDs') download_parser.add_argument('--collection', help='Collection ID') download_parser.add_argument('--max-pages', type=int, default=5, help='Max pages to search') # List filters command list_filters_parser = subparsers.add_parser('list-filters', help='List available filters') list_filters_parser.add_argument('--filter-type', choices=['vegetables', 'fruits', 'proteins', 'whole_grains', 'meal', 'cooking_appliance'], help='Filter type to list') list_filters_parser.add_argument('--collection', action='store_true', help='List available collections') # Database commands db_parser = subparsers.add_parser('db', help='Database management commands') db_subparsers = db_parser.add_subparsers(dest='db_command', help='Database operations') # Initialize database db_subparsers.add_parser('init', help='Initialize the database') # Session management session_parser = db_subparsers.add_parser('session', help='Session management') session_subparsers = session_parser.add_subparsers(dest='session_command', help='Session operations') create_session_parser = session_subparsers.add_parser('create', help='Create a session') create_session_parser.add_argument('session_id', help='Session ID to create') cleanup_session_parser = session_subparsers.add_parser('cleanup', help='Clean up a session') cleanup_session_parser.add_argument('session_id', help='Session ID to clean up') session_subparsers.add_parser('list', help='List all active sessions') # Recipe storage store_parser = db_subparsers.add_parser('store', help='Store a recipe in session') store_parser.add_argument('session_id', help='Session ID') store_parser.add_argument('recipe_url', help='Recipe URL to store') # Get recipes from session get_parser = db_subparsers.add_parser('get', help='Get recipes from session') get_parser.add_argument('session_id', help='Session ID') get_parser.add_argument('--recipe-id', help='Specific recipe ID (optional)') # Parse ingredients parse_parser = db_subparsers.add_parser('parse', help='Parse ingredients in session') parse_parser.add_argument('session_id', help='Session ID') parse_parser.add_argument('--recipe-id', help='Specific recipe ID (optional)') # Scale recipe scale_parser = db_subparsers.add_parser('scale', help='Scale recipe servings') scale_parser.add_argument('session_id', help='Session ID') scale_parser.add_argument('recipe_id', help='Recipe ID') scale_parser.add_argument('target_servings', type=int, help='Target number of servings') # Favorites management fav_parser = db_subparsers.add_parser('favorites', help='Manage favorites') fav_subparsers = fav_parser.add_subparsers(dest='fav_command', help='Favorites operations') add_fav_parser = fav_subparsers.add_parser('add', help='Add recipe to favorites') add_fav_parser.add_argument('recipe_url', help='Recipe URL') add_fav_parser.add_argument('--title', help='Recipe title') add_fav_parser.add_argument('--session', default='default', help='User session') add_fav_parser.add_argument('--notes', help='Custom notes') remove_fav_parser = fav_subparsers.add_parser('remove', help='Remove recipe from favorites') remove_fav_parser.add_argument('recipe_url', help='Recipe URL') remove_fav_parser.add_argument('--session', help='User session') list_fav_parser = fav_subparsers.add_parser('list', help='List favorite recipes') list_fav_parser.add_argument('--session', help='Filter by user session') return parser def handle_db_command(self, args): """Handle database commands.""" if args.db_command == 'init': self.initialize_db() elif args.db_command == 'session': if args.session_command == 'create': self.create_session(args.session_id) elif args.session_command == 'cleanup': self.cleanup_session(args.session_id) elif args.session_command == 'list': self.list_sessions() elif args.db_command == 'store': self.store_recipe(args.session_id, args.recipe_url) elif args.db_command == 'get': self.get_session_recipes(args.session_id, getattr(args, 'recipe_id', None)) elif args.db_command == 'parse': self.parse_ingredients(args.session_id, getattr(args, 'recipe_id', None)) elif args.db_command == 'scale': self.scale_recipe(args.session_id, args.recipe_id, args.target_servings) elif args.db_command == 'favorites': if args.fav_command == 'add': self.add_favorite(args.recipe_url, args.title, args.session, args.notes) elif args.fav_command == 'remove': self.remove_favorite(args.recipe_url, args.session) elif args.fav_command == 'list': self.list_favorites_cmd(args.session) def handle_search_command(self, args): """Handle the search command with proper error handling""" # Initialize filters filters = SearchFilters() # Track validation results valid_filters_added = False invalid_filters = [] # Helper function to add filters with validation def add_filter_with_validation(filter_type, filter_values, display_name): nonlocal valid_filters_added if filter_values: for value in filter_values: filter_id = filters._resolve_filter_id(filter_type, value) if filter_id: filters.add_filter(filter_type, value) valid_filters_added = True #print(f"✓ Added {display_name} filter: {value}") else: invalid_filters.append((display_name, value)) #print(f"✗ Warning: Filter '{value}' not found in {display_name}") # Add filters with validation add_filter_with_validation('fruits', args.fruits, 'fruits') add_filter_with_validation('vegetables', args.vegetables, 'vegetables') add_filter_with_validation('proteins', args.proteins, 'proteins') add_filter_with_validation('whole_grains', getattr(args, 'whole_grains', None), 'whole grains') add_filter_with_validation('meals_and_course', args.meals, 'meals') add_filter_with_validation('cooking_appliance', args.appliances, 'appliances') # Handle collections separately if getattr(args, 'collections', None): for collection in args.collections: collection_key = filters._normalize_key(collection) if collection_key in filters._collections_data: filters.add_collection(collection) valid_filters_added = True #print(f"✓ Added collection filter: {collection}") else: invalid_filters.append(('collections', collection)) #print(f"✗ Warning: Collection '{collection}' not found") # Get search text Join search words into a single string search_text = ' '.join(args.search_text) if args.search_text else '' # Check if we have any valid search criteria if not search_text.strip() and not valid_filters_added: #print("\n❌ Error: No valid search criteria provided!") if invalid_filters: #print("\nInvalid filters found:") for filter_type, value in invalid_filters: #print(f" • {filter_type}: '{value}'") #print("\n💡 Suggestions:") self.show_filter_suggestions(filters, invalid_filters) #print("\nUsage examples:") #print(" python main.py search apple") #print(" python main.py search --fruits apple") #print(" python main.py search \"healthy breakfast\" --meals breakfast") #print(" python main.py search --fruits apple --vegetables carrot") sys.exit(1) # Exit with error code # Show what we're searching for #print(f"\n🔍 Searching for: '{search_text}'") if valid_filters_added: #print("📋 Active filters:", filters.get_filters_dict()) # Perform search recipes = self.searcher.search_recipes( search_text=search_text, filters=filters if valid_filters_added else None, max_pages=getattr(args, 'max_pages', 35) ) # Display results if recipes: #print(f"\n✅ Found {len(recipes)} recipes:") for i, recipe in enumerate(recipes, 1): #print(f"{i}. {recipe['title']}") #print(f" URL: {recipe['url']}") else: #print("\n❌ No recipes found.") if valid_filters_added or search_text: #print("💡 Try broadening your search criteria or using different filters.") def show_filter_suggestions(self, filters, invalid_filters): """Show suggestions for invalid filters""" # Group invalid filters by type invalid_by_type = {} for filter_type, value in invalid_filters: if filter_type not in invalid_by_type: invalid_by_type[filter_type] = [] invalid_by_type[filter_type].append(value) for filter_type, invalid_values in invalid_by_type.items(): #print(f"\nAvailable {filter_type}:") if filter_type == 'collections': available = list(filters._collections_data.keys()) else: # Map display names to internal filter types type_mapping = { 'fruits': 'Fruits', 'vegetables': 'Vegetables', 'proteins': 'Proteins', 'whole grains': 'Whole grains', 'meals': 'Meal', 'appliances': 'Cooking appliance' } internal_type = type_mapping.get(filter_type, filter_type) if internal_type in filters._filters_data: available = [f.label for f in filters._filters_data[internal_type].values()] else: available = [] if available: # Show first 10 options, sorted available_sorted = sorted(available)[:10] for item in available_sorted: #print(f" • {item}") if len(available) > 10: #print(f" ... and {len(available) - 10} more") # Try to suggest similar items for invalid_value in invalid_values: suggestions = self.find_similar_items(invalid_value, available) if suggestions: #print(f" 💡 Did you mean: {', '.join(suggestions[:3])}?") def find_similar_items(self, invalid_value, available_items, max_suggestions=3): """Find similar items using simple string matching""" invalid_lower = invalid_value.lower() suggestions = [] # Look for items that start with the invalid value for item in available_items: if item.lower().startswith(invalid_lower): suggestions.append(item) # Look for items that contain the invalid value if len(suggestions) < max_suggestions: for item in available_items: if invalid_lower in item.lower() and item not in suggestions: suggestions.append(item) return suggestions[:max_suggestions] def download_command(self, args): """Handle download command - UPDATED to support multiple URLs and #print option""" if args.url: # Ensure args.url is a list of complete URLs, not split characters if isinstance(args.url, str): # If somehow it's a single string, wrap it in a list urls = [args.url] else: urls = args.url # Debug: #print what URLs we're processing #print(f"Debug: Processing URLs: {urls}") # Download/#print multiple recipes if args.#print: #print(f"Fetching {len(urls)} recipe(s) for display...") else: #print(f"Downloading {len(urls)} recipe(s)...") successful_operations = 0 failed_operations = 0 for i, url in enumerate(urls, 1): # Validate URL format before processing if not url.startswith(('http://', 'https://')): #print(f"❌ Invalid URL format: {url}") failed_operations += 1 continue #print(f"\n[{i}/{len(urls)}] Fetching recipe from: {url}") try: recipe = self.fetcher.fetch_recipe(url) if recipe: if args.#print: # #print to console #print(f"\n📄 Recipe Data for: {recipe.title}") #print("=" * 50) self.downloader.#print_recipe(recipe) #print("=" * 50) successful_operations += 1 else: # Save to file filepath = self.downloader.save_recipe(recipe, format=args.format) #print(f"✅ Saved to: {filepath}") successful_operations += 1 else: #print(f"❌ Failed to fetch recipe from: {url}") failed_operations += 1 except Exception as e: #print(f"❌ Error processing {url}: {e}") failed_operations += 1 # Summary operation_type = "displayed" if args.#print else "downloaded" #print(f"\n📊 Operation Summary:") #print(f"✅ Successfully {operation_type}: {successful_operations}") if failed_operations > 0: #print(f"❌ Failed: {failed_operations}") elif args.search: # Search and download/#print results = self.search_command(args) if results and args.download_all: if args.#print: #print(f"\nFetching {len(results)} recipes for display...") else: #print(f"\nDownloading {len(results)} recipes...") successful_operations = 0 failed_operations = 0 for i, recipe_meta in enumerate(results, 1): #print(f"\n[{i}/{len(results)}] Processing: {recipe_meta['title']}") try: recipe = self.fetcher.fetch_recipe(recipe_meta['url']) if recipe: if args.#print: # #print to console #print(f"\n📄 Recipe Data for: {recipe.title}") #print("=" * 50) self.downloader.#print_recipe(recipe) #print("=" * 50) successful_operations += 1 else: # Save to file filepath = self.downloader.save_recipe(recipe, format=args.format) #print(f"✅ Saved to: {filepath}") successful_operations += 1 else: #print(f"❌ Failed to fetch: {recipe_meta['title']}") failed_operations += 1 except Exception as e: #print(f"❌ Error processing {recipe_meta['title']}: {e}") failed_operations += 1 # Summary operation_type = "displayed" if args.#print else "downloaded" #print(f"\n📊 Operation Summary:") #print(f"✅ Successfully {operation_type}: {successful_operations}") if failed_operations > 0: #print(f"❌ Failed: {failed_operations}") def list_filters_command(self, args): """List all available filters.""" filters = SearchFilters(auto_update=True) if args.filter_type: # Show specific filter type available = filters.get_available_filters(args.filter_type) if available: #print(f"\nAvailable {args.filter_type}:") for item in sorted(available): #print(f" - {item}") else: #print(f"Unknown filter type: {args.filter_type}") else: # Show all filter types #print("\nAvailable filter types:") for category in ["vegetables", "fruits", "proteins", "whole grains", "meal", "cooking appliance"]: #print(f"\n{category.title()}:") items = filters.get_available_filters(category) for item in sorted(items[:]): #print(f" - {item}") #print("\nCollections:") for collection in filters.get_available_collections(): #print(f" - {collection.replace('_', ' ').title()}") def run(self): """Run the CLI application.""" parser = self.create_parser() args = parser.parse_args() if args.command == 'search': self.handle_search_command(args) elif args.command == 'download': self.download_command(args) elif args.command == 'list-filters': self.list_filters_command(args) elif args.command == 'db': self.handle_db_command(args) else: parser.#print_help() def main(): """Main entry point""" cli = FoodGuideCLI() cli.run() if __name__ == '__main__': main()

Latest Blog Posts

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/Aryan-Jhaveri/mcp-foodguidecanada'

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