find_meals_for_wine
Input a wine type (e.g., Barolo, Chardonnay) and receive the top 10 matching dishes with pairing scores. Ideal when you have a wine and want to know what to cook.
Instructions
Find dishes that pair well with a specific wine type or style. Provide a wine name (e.g. "Barolo", "Chardonnay", "Rioja") and get the top 10 matching dishes with scores. Best for: "I have a Barolo, what should I cook?" | Auth: API key (Bearer sk_live_...) or x402 payment (USDC on Base) | Price: $0.01/call
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| wine_type | Yes | Name of the wine type or style (e.g. "Barolo", "Chardonnay", "Rioja Reserva") | |
| language | No | Language code for results (e.g. "en", "nl", "fr"). Defaults to "en". |
Implementation Reference
- src/tools/find-meals-for-wine.ts:100-133 (handler)Main execution function for the find_meals_for_wine tool. Takes client, config, and validated input; finds a wine type by name, then fetches meal pairings from /api/v1/pairing/by-wine/:id/meals, and formats the result.
export async function executeFindMealsForWine( client: SommelierXClient, config: ServerConfig, input: FindMealsForWineInput, ): Promise<string> { const language = input.language ?? config.defaultLanguage; // Step 1: Find the wine type const wineType = await findWineType(client, input.wine_type, language); if (!wineType) { return [ `Could not find a wine type matching "${input.wine_type}" in the SommelierX database.`, '', 'Try using a more specific wine style name like "Barolo", "Chablis", or "Rioja Reserva".', 'Common wine types include: Chardonnay, Sauvignon Blanc, Pinot Noir, Cabernet Sauvignon,', 'Merlot, Syrah, Riesling, Champagne, Prosecco, and many regional styles.', ].join('\n'); } // Step 2: Get meal recommendations let mealsResult: MealsByWineResult; try { mealsResult = await client.get<MealsByWineResult>( `/api/v1/pairing/by-wine/${wineType.id}/meals`, { language, perPage: '10' }, ); } catch (error: unknown) { const message = error instanceof Error ? error.message : 'Unknown error'; return `Error finding meals for "${wineType.name}": ${message}`; } return formatMealsForWineResponse(wineType, mealsResult.data); } - Zod schema defining input for find_meals_for_wine: wine_type (required string, 2-200 chars) and language (optional string, 2-10 chars).
export const findMealsForWineSchema = z.object({ wine_type: z .string() .min(2, 'Wine type must be at least 2 characters') .max(200) .describe('Name of the wine type or style (e.g. "Barolo", "Chardonnay", "Rioja Reserva")'), language: z .string() .min(2) .max(10) .optional() .describe('Language code for results (e.g. "en", "nl", "fr"). Defaults to "en".'), }); export type FindMealsForWineInput = z.infer<typeof findMealsForWineSchema>; - src/index.ts:97-106 (registration)Registration of the find_meals_for_wine tool with the MCP server, including description, schema shape, and handler lambda.
server.tool( 'find_meals_for_wine', 'Find dishes that pair well with a specific wine type or style. Provide a wine name (e.g. "Barolo", "Chardonnay", "Rioja") and get the top 10 matching dishes with scores. Best for: "I have a Barolo, what should I cook?" | Auth: API key (Bearer sk_live_...) or x402 payment (USDC on Base) | Price: $0.01/call', findMealsForWineSchema.shape, async (input) => { const parsed = findMealsForWineSchema.parse(input); const result = await executeFindMealsForWine(client, config, parsed); return { content: [{ type: 'text' as const, text: result }] }; }, ); - Helper function that fetches all wine types from /api/v1/wines/types and performs case-insensitive matching (exact, starts-with, contains, reverse) to find the best matching wine type item.
async function findWineType( client: SommelierXClient, query: string, language: string, ): Promise<WineTypeItem | null> { try { const response = await client.get<WineTypesResponse>('/api/v1/wines/types', { language }); // Normalize response: client may return array or object with .data let wineTypes: WineTypeItem[]; if (Array.isArray(response)) { wineTypes = response; } else if ('data' in response && Array.isArray(response.data)) { wineTypes = response.data; } else { return null; } const queryLower = query.toLowerCase(); // Try exact match first const exact = wineTypes.find((w) => w.name.toLowerCase() === queryLower); if (exact) return exact; // Try starts-with match const startsWith = wineTypes.find((w) => w.name.toLowerCase().startsWith(queryLower)); if (startsWith) return startsWith; // Try substring match const contains = wineTypes.find((w) => w.name.toLowerCase().includes(queryLower)); if (contains) return contains; // Try reverse: query contains wine name const reverseMatch = wineTypes.find((w) => queryLower.includes(w.name.toLowerCase())); if (reverseMatch) return reverseMatch; return null; } catch { return null; } } - Helper function that formats the meal recommendations into a human-readable string, showing rank, name, match percentage, and optional description.
function formatMealsForWineResponse( wineType: WineTypeItem, meals: MealMatch[], ): string { const lines: string[] = []; const colorSuffix = wineType.color ? ` (${wineType.color})` : ''; lines.push(`Dishes that pair well with ${wineType.name}${colorSuffix}:`); lines.push(''); if (meals.length === 0) { lines.push('No meal recommendations found for this wine type.'); return lines.join('\n'); } for (let i = 0; i < meals.length; i++) { const meal = meals[i]; const rank = i + 1; lines.push(`${rank}. ${meal.name}`); lines.push(` Match: ${meal.score.match_percentage}%`); if (meal.description) { lines.push(` ${meal.description}`); } lines.push(''); } return lines.join('\n'); }