cook-mcp-windy
Version:
HowToCook MCP Server - Intelligent Chinese recipe management and meal planning for AI assistants
197 lines • 7.98 kB
JavaScript
import { z } from 'zod';
import { recipeDataManager } from '../data/recipeData.js';
import { Recipe, RecipeCategory } from '../types/index.js';
// Input schema for get_recipes_by_category tool
const GetRecipesByCategoryInput = z.object({
category: RecipeCategory.describe('Recipe category to filter by'),
limit: z.number().min(1).max(100).default(50).optional().describe('Maximum number of recipes to return'),
sortBy: z.enum(['name', 'difficulty', 'prepTime', 'cookTime', 'dateAdded']).default('name').optional().describe('Field to sort by'),
sortOrder: z.enum(['asc', 'desc']).default('asc').optional().describe('Sort order'),
includeStats: z.boolean().default(false).optional().describe('Whether to include category statistics')
});
// Response type for category recipes
const CategoryRecipesResponse = z.object({
category: RecipeCategory.describe('The requested category'),
recipes: z.array(z.any()).describe('List of recipes in this category'),
totalCount: z.number().describe('Total number of recipes in this category'),
statistics: z.object({
difficultyDistribution: z.record(z.number()).describe('Distribution of recipes by difficulty'),
averagePrepTime: z.string().describe('Average preparation time'),
averageCookTime: z.string().describe('Average cooking time'),
averageServings: z.number().describe('Average number of servings'),
commonTags: z.array(z.string()).describe('Most common tags in this category')
}).optional().describe('Category statistics if requested')
});
/**
* Tool definition for getting recipes by category
*/
export const getRecipesByCategoryTool = {
name: 'get_recipes_by_category',
description: 'Filter and retrieve recipes by category. Categories include: 水产 (seafood), 早餐 (breakfast), 调味料 (seasonings), 甜品 (desserts), 饮品 (beverages), 荤菜 (meat dishes), 半成品 (semi-prepared), 汤羹 (soups), 主食 (staples), 素菜 (vegetarian). Returns recipes with complete details and optional category statistics.',
inputSchema: {
type: 'object',
properties: {
category: { type: 'string', enum: ['水产', '早餐', '调味料', '甜品', '饮品', '荤菜', '半成品', '汤羹', '主食', '素菜'] },
limit: { type: 'number', minimum: 1, maximum: 100, default: 50 },
sortBy: { type: 'string', enum: ['name', 'difficulty', 'prepTime', 'cookTime', 'dateAdded'], default: 'name' },
sortOrder: { type: 'string', enum: ['asc', 'desc'], default: 'asc' },
includeStats: { type: 'boolean', default: false }
},
required: ['category']
}
};
/**
* Handler for get_recipes_by_category tool
*/
export async function handleGetRecipesByCategory(input) {
try {
// Validate input
const validatedInput = GetRecipesByCategoryInput.parse(input);
const { category, limit = 50, sortBy = 'name', sortOrder = 'asc', includeStats = false } = validatedInput;
// Get recipes by category
let recipes = recipeDataManager.getRecipesByCategory(category);
// Sort recipes
recipes = sortRecipes(recipes, sortBy, sortOrder);
// Apply limit
const limitedRecipes = recipes.slice(0, limit);
// Prepare response
const response = {
category,
recipes: limitedRecipes,
totalCount: recipes.length
};
// Add statistics if requested
if (includeStats && recipes.length > 0) {
response.statistics = calculateCategoryStatistics(recipes);
}
return {
success: true,
message: `Found ${limitedRecipes.length} recipes in category "${category}" (showing ${Math.min(limit, recipes.length)} of ${recipes.length})`,
data: response
};
}
catch (error) {
return {
success: false,
error: 'Failed to retrieve recipes by category',
details: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
}
/**
* Sort recipes by specified field and order
*/
function sortRecipes(recipes, sortBy, sortOrder) {
const sorted = [...recipes].sort((a, b) => {
let comparison = 0;
switch (sortBy) {
case 'name':
comparison = a.name.localeCompare(b.name, 'zh-CN');
break;
case 'difficulty':
const difficultyOrder = { '简单': 1, '中等': 2, '困难': 3 };
comparison = difficultyOrder[a.difficulty] - difficultyOrder[b.difficulty];
break;
case 'prepTime':
comparison = parseTimeInMinutes(a.prepTime) - parseTimeInMinutes(b.prepTime);
break;
case 'cookTime':
comparison = parseTimeInMinutes(a.cookTime) - parseTimeInMinutes(b.cookTime);
break;
case 'dateAdded':
comparison = new Date(a.dateAdded).getTime() - new Date(b.dateAdded).getTime();
break;
default:
comparison = a.name.localeCompare(b.name, 'zh-CN');
}
return sortOrder === 'desc' ? -comparison : comparison;
});
return sorted;
}
/**
* Calculate statistics for a category
*/
function calculateCategoryStatistics(recipes) {
// Difficulty distribution
const difficultyDistribution = {
'简单': 0,
'中等': 0,
'困难': 0
};
let totalPrepTime = 0;
let totalCookTime = 0;
let totalServings = 0;
const tagCounts = {};
recipes.forEach(recipe => {
// Count difficulties
if (recipe.difficulty && recipe.difficulty in difficultyDistribution) {
difficultyDistribution[recipe.difficulty]++;
}
// Sum times and servings
totalPrepTime += parseTimeInMinutes(recipe.prepTime);
totalCookTime += parseTimeInMinutes(recipe.cookTime);
totalServings += recipe.servings;
// Count tags
if (recipe.tags) {
recipe.tags.forEach(tag => {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
});
}
});
// Calculate averages
const averagePrepTime = formatTimeFromMinutes(Math.round(totalPrepTime / recipes.length));
const averageCookTime = formatTimeFromMinutes(Math.round(totalCookTime / recipes.length));
const averageServings = Math.round((totalServings / recipes.length) * 10) / 10; // Round to 1 decimal
// Get most common tags (top 5)
const commonTags = Object.entries(tagCounts)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.map(([tag]) => tag);
return {
difficultyDistribution,
averagePrepTime,
averageCookTime,
averageServings,
commonTags
};
}
/**
* Parse time string to minutes
*/
function parseTimeInMinutes(timeStr) {
const hourMatch = timeStr.match(/(\d+)小时/);
const minuteMatch = timeStr.match(/(\d+)分钟/);
let totalMinutes = 0;
if (hourMatch && hourMatch[1]) {
totalMinutes += parseInt(hourMatch[1]) * 60;
}
if (minuteMatch && minuteMatch[1]) {
totalMinutes += parseInt(minuteMatch[1]);
}
return totalMinutes;
}
/**
* Format minutes to Chinese time string
*/
function formatTimeFromMinutes(minutes) {
if (minutes < 60) {
return `${minutes}分钟`;
}
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
if (remainingMinutes === 0) {
return `${hours}小时`;
}
return `${hours}小时${remainingMinutes}分钟`;
}
/**
* Get available categories with counts
*/
export function getAvailableCategories() {
const stats = recipeDataManager.getStatistics();
return Object.entries(stats.categoryCounts).map(([category, count]) => ({
category: category,
count
}));
}
//# sourceMappingURL=getRecipesByCategory.js.map