pair_wine_with_meal
Find ideal wine pairings for any meal. Enter a dish name to get the top 5 wine recommendations.
Instructions
Find the best wine pairings for a specific dish or meal. Provide the meal name (e.g. "risotto ai funghi", "grilled salmon") and get the top 5 wine matches. The server searches for the meal in the database. Best for: "What wine goes with this dish?" | Auth: API key (Bearer sk_live_...) or x402 payment (USDC on Base) | Price: $0.01/call
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| meal_name | Yes | Name of the dish or meal (e.g. "risotto ai funghi", "grilled salmon") | |
| language | No | Language code for results (e.g. "en", "nl", "fr"). Defaults to "en". |
Implementation Reference
- src/tools/pair-wine-with-meal.ts:38-81 (handler)Main handler: searches for a meal by name via GET /api/v1/meals, then fetches wine pairings via POST /api/v1/pairing/by-meal/{mealId}, and formats results.
export async function executePairWineWithMeal( client: SommelierXClient, config: ServerConfig, input: PairWineWithMealInput, ): Promise<string> { const language = input.language ?? config.defaultLanguage; // Step 1: Search for the meal let mealResult: MealListResult; try { mealResult = await client.get<MealListResult>('/api/v1/meals', { search: input.meal_name, language, perPage: '5', }); } catch (error: unknown) { const message = error instanceof Error ? error.message : 'Unknown error'; return `Error searching for meal: ${message}`; } if (!mealResult.data || mealResult.data.length === 0) { return [ `Could not find a meal matching "${input.meal_name}" in the SommelierX database.`, '', 'Try using the search_meals tool to find available meals,', 'or use pair_wine_with_ingredients with the dish\'s individual ingredients.', ].join('\n'); } const meal = mealResult.data[0]; // Step 2: Get wine pairings for this meal let pairingResult: MealPairingResult; try { pairingResult = await client.post<MealPairingResult>(`/api/v1/pairing/by-meal/${meal.id}`, { language, }); } catch (error: unknown) { const message = error instanceof Error ? error.message : 'Unknown error'; return `Error calculating wine pairing for "${meal.name}": ${message}`; } return formatMealPairingResponse(meal, pairingResult.results); } - Formats the top 5 wine pairings into a human-readable string with name, color, match %, region, grapes, score breakdown, and description.
function formatMealPairingResponse( meal: { name: string; description?: string }, wines: WineMatch[], ): string { const lines: string[] = []; lines.push(`Wine pairing for: ${meal.name}`); if (meal.description) { lines.push(meal.description); } lines.push(''); const topWines = wines.slice(0, 5); if (topWines.length === 0) { lines.push('No wine matches found for this meal.'); return lines.join('\n'); } lines.push('Top wine matches:'); lines.push(''); for (let i = 0; i < topWines.length; i++) { const wine = topWines[i]; const rank = i + 1; lines.push(`${rank}. ${wine.name} (${wine.color})`); lines.push(` Match: ${wine.score.match_percentage}%`); if (wine.region) { lines.push(` Region: ${wine.region}`); } if (wine.grapes && wine.grapes.length > 0) { lines.push(` Grapes: ${wine.grapes.join(', ')}`); } if (wine.score.basic_score !== undefined) { const parts: string[] = []; parts.push(`basic ${wine.score.basic_score}`); parts.push(`balance ${wine.score.balance_score ?? 0}`); if (wine.score.aromatic_score != null) { parts.push(`aromatic ${wine.score.aromatic_score}`); } lines.push(` Score breakdown: ${parts.join(' | ')}`); } if (wine.description) { lines.push(` ${wine.description}`); } lines.push(''); } return lines.join('\n'); } - Zod schema defining input validation: required meal_name (2-200 chars) and optional language (2-10 chars).
export const pairWineWithMealSchema = z.object({ meal_name: z .string() .min(2, 'Meal name must be at least 2 characters') .max(200) .describe('Name of the dish or meal (e.g. "risotto ai funghi", "grilled salmon")'), language: z .string() .min(2) .max(10) .optional() .describe('Language code for results (e.g. "en", "nl", "fr"). Defaults to "en".'), }); export type PairWineWithMealInput = z.infer<typeof pairWineWithMealSchema>; - src/index.ts:84-93 (registration)Registers the 'pair_wine_with_meal' MCP tool with the server, wiring schema and handler.
server.tool( 'pair_wine_with_meal', 'Find the best wine pairings for a specific dish or meal. Provide the meal name (e.g. "risotto ai funghi", "grilled salmon") and get the top 5 wine matches. The server searches for the meal in the database. Best for: "What wine goes with this dish?" | Auth: API key (Bearer sk_live_...) or x402 payment (USDC on Base) | Price: $0.01/call', pairWineWithMealSchema.shape, async (input) => { const parsed = pairWineWithMealSchema.parse(input); const result = await executePairWineWithMeal(client, config, parsed); return { content: [{ type: 'text' as const, text: result }] }; }, ); - src/tools/index.ts:10-13 (registration)Barrel export re-exporting the schema and handler from the pair-wine-with-meal module.
export { pairWineWithMealSchema, executePairWineWithMeal, } from './pair-wine-with-meal.js';