Skip to main content
Glama
rogertheunissenmerge-oss

SommelierX Wine Pairing MCP

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

TableJSON Schema
NameRequiredDescriptionDefault
meal_nameYesName of the dish or meal (e.g. "risotto ai funghi", "grilled salmon")
languageNoLanguage code for results (e.g. "en", "nl", "fr"). Defaults to "en".

Implementation Reference

  • 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 }] };
      },
    );
  • Barrel export re-exporting the schema and handler from the pair-wine-with-meal module.
    export {
      pairWineWithMealSchema,
      executePairWineWithMeal,
    } from './pair-wine-with-meal.js';
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Since no annotations are provided, the description takes on the full burden of behavioral disclosure. It details authentication methods (API key or x402 payment) and cost ($0.01/call), and notes that the server searches the database. These go beyond the schema and help the agent understand side effects and requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is relatively concise with two main sentences plus a line for 'Best for', auth, and price. It efficiently communicates the core functionality, though the extra details could be separated for readability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simplicity of the tool (2 parameters, no output schema), the description covers purpose, usage, auth, and cost. It does not describe the output format in detail, but the phrase 'top 5 wine matches' provides enough context for the agent to anticipate the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description adds example values for parameters but does not provide additional semantic depth beyond what the schema already specifies (e.g., language defaults to 'en').

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Find the best wine pairings for a specific dish or meal.' It provides concrete examples ('risotto ai funghi', 'grilled salmon') and specifies the output ('top 5 wine matches'). This distinguishes it from siblings like 'pair_wine_with_ingredients' which pair by ingredients, not meal name.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description includes a 'Best for' section ('What wine goes with this dish?') that clarifies the primary use case. It does not explicitly list when not to use the tool or mention alternatives, but the provided context is sufficient for correct usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/rogertheunissenmerge-oss/mcp-server'

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