bbq_analyze_temperature
Analyze BBQ temperature readings to assess progress, detect stalls, estimate remaining time, and provide actionable recommendations for optimal cooking results.
Instructions
Analyze current temperature reading and provide progress assessment, trend analysis, and recommendations.
Use this tool to interpret live temperature data from a thermometer. It provides:
Progress percentage toward target
Temperature trend (rising, falling, stalled, stable)
Rate of temperature change per hour
Estimated time remaining
Stall detection
Actionable recommendations
Args:
current_temp: Current internal temperature in °F
target_temp: Target internal temperature in °F
protein_type: Type of protein being cooked
cook_method: Cooking method (optional)
cook_start_time: When cook started, ISO 8601 format (optional)
previous_readings: Array of {temp, timestamp} for trend analysis (optional)
response_format: 'markdown' or 'json'
Examples:
"My brisket is at 165°F, target is 203°F" -> current_temp=165, target_temp=203, protein_type='beef_brisket'
"Temperature hasn't moved in 2 hours" -> Include previous_readings for stall detection
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| current_temp | Yes | Current internal temperature reading in Fahrenheit | |
| target_temp | Yes | Target internal temperature in Fahrenheit | |
| protein_type | Yes | Type of protein being cooked | |
| cook_method | No | Cooking method being used | |
| cook_start_time | No | When the cook started in ISO 8601 format (e.g., '2024-12-25T06:00:00') | |
| previous_readings | No | Previous temperature readings to calculate trend (most recent last) | |
| response_format | No | Output format | markdown |
Implementation Reference
- src/services/cooking.ts:175-283 (handler)Core implementation of temperature analysis logic. Calculates progress percentage, detects trends (rising/falling/stable/stalled), stall zone detection, estimated time remaining, and generates context-aware recommendations.export function analyzeTemperature( currentTemp: number, targetTemp: number, proteinType: ProteinType, cookMethod?: CookMethod, cookStartTime?: Date, previousReadings?: Array<{ temp: number; timestamp: Date }> ): TemperatureAnalysis { const profile = getProteinProfile(proteinType); const tempDelta = targetTemp - currentTemp; const startingTemp = 40; // Assume refrigerator temp as starting point const totalTempRange = targetTemp - startingTemp; const tempProgress = currentTemp - startingTemp; const percentComplete = Math.min(100, Math.max(0, (tempProgress / totalTempRange) * 100)); // Determine trend from previous readings let trend: "rising" | "falling" | "stable" | "stalled" = "stable"; let trendRatePerHour = 0; if (previousReadings && previousReadings.length >= 2) { const recentReadings = previousReadings.slice(-5); // Look at last 5 readings const firstReading = recentReadings[0]; const lastReading = recentReadings[recentReadings.length - 1]; const tempChange = lastReading.temp - firstReading.temp; const timeChange = (lastReading.timestamp.getTime() - firstReading.timestamp.getTime()) / (1000 * 60 * 60); // hours if (timeChange > 0) { trendRatePerHour = tempChange / timeChange; if (Math.abs(trendRatePerHour) < 2) { // Less than 2°F/hour trend = profile.stallRange && currentTemp >= profile.stallRange.start && currentTemp <= profile.stallRange.end ? "stalled" : "stable"; } else if (trendRatePerHour > 0) { trend = "rising"; } else { trend = "falling"; } } } // Check if in stall zone const inStallZone = profile.stallRange !== undefined && currentTemp >= profile.stallRange.start && currentTemp <= profile.stallRange.end; // Estimate time remaining let estimatedMinutesRemaining: number | null = null; if (trendRatePerHour > 0 && trend === "rising") { const hoursRemaining = tempDelta / trendRatePerHour; estimatedMinutesRemaining = Math.round(hoursRemaining * 60); } else if (trend === "stalled") { // During stall, estimate based on typical stall duration estimatedMinutesRemaining = null; // Can't reliably estimate during stall } // Generate recommendations const recommendations: string[] = []; if (inStallZone && trend === "stalled") { recommendations.push("🛑 You're in the stall zone! Temperature may plateau for 2-4 hours."); recommendations.push( "💡 Consider wrapping in butcher paper or foil (Texas crutch) to push through faster." ); } else if (inStallZone && trend === "rising") { recommendations.push("📈 Temperature is rising through the stall zone - looking good!"); } if (tempDelta <= profile.carryoverDegrees + 5) { recommendations.push( `🎯 Getting close! Consider pulling at ${targetTemp - profile.carryoverDegrees}°F to account for carryover.` ); } if (tempDelta <= 0) { recommendations.push("✅ Target temperature reached! Time to rest."); if (profile.requiresRest) { recommendations.push(`⏰ Rest for ${profile.restTimeMinutes} minutes before slicing.`); } } if (trend === "falling") { recommendations.push("⚠️ Temperature is dropping - check your heat source!"); if (cookMethod?.includes("smoke")) { recommendations.push("🔥 You may need to add more fuel or adjust airflow."); } } if (percentComplete < 25 && previousReadings && previousReadings.length > 0) { recommendations.push("🕐 Still in early stages - patience is key!"); } return { currentTemp, targetTemp, tempDelta, percentComplete: Math.round(percentComplete * 10) / 10, trend, trendRatePerHour: Math.round(trendRatePerHour * 10) / 10, estimatedMinutesRemaining, inStallZone, recommendations, }; }
- src/index.ts:267-350 (registration)Primary MCP server registration of the 'bbq_analyze_temperature' tool, including full description, input schema reference, annotations, and handler function that delegates to core analysis logic.server.registerTool( "bbq_analyze_temperature", { title: "Analyze Temperature Progress", description: `Analyze current temperature reading and provide progress assessment, trend analysis, and recommendations. Use this tool to interpret live temperature data from a thermometer. It provides: - Progress percentage toward target - Temperature trend (rising, falling, stalled, stable) - Rate of temperature change per hour - Estimated time remaining - Stall detection - Actionable recommendations Args: - current_temp: Current internal temperature in °F - target_temp: Target internal temperature in °F - protein_type: Type of protein being cooked - cook_method: Cooking method (optional) - cook_start_time: When cook started, ISO 8601 format (optional) - previous_readings: Array of {temp, timestamp} for trend analysis (optional) - response_format: 'markdown' or 'json' Examples: - "My brisket is at 165°F, target is 203°F" -> current_temp=165, target_temp=203, protein_type='beef_brisket' - "Temperature hasn't moved in 2 hours" -> Include previous_readings for stall detection`, inputSchema: AnalyzeTemperatureSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }, async (params: AnalyzeTemperatureInput) => { try { const previousReadings = params.previous_readings?.map((r) => ({ temp: r.temp, timestamp: new Date(r.timestamp), })); const cookStart = params.cook_start_time ? new Date(params.cook_start_time) : undefined; const analysis = analyzeTemperature( params.current_temp, params.target_temp, params.protein_type, params.cook_method, cookStart, previousReadings ); if (params.response_format === "json") { const output = { currentTemp: analysis.currentTemp, targetTemp: analysis.targetTemp, tempDelta: analysis.tempDelta, percentComplete: analysis.percentComplete, trend: analysis.trend, trendRatePerHour: analysis.trendRatePerHour, estimatedMinutesRemaining: analysis.estimatedMinutesRemaining, inStallZone: analysis.inStallZone, recommendations: analysis.recommendations, }; return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } const markdown = formatTemperatureAnalysisMarkdown(analysis); return { content: [{ type: "text", text: markdown }], }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error occurred"; return { isError: true, content: [{ type: "text", text: `Error analyzing temperature: ${message}` }], }; } } );
- src/schemas/index.ts:98-120 (schema)Zod input schema for the bbq_analyze_temperature tool, defining validation and descriptions for all parameters including current_temp, target_temp, protein_type, and optional fields for trend analysis.export const AnalyzeTemperatureSchema = z .object({ current_temp: z.number().min(-40).max(500).describe("Current internal temperature reading in Fahrenheit"), target_temp: z.number().min(100).max(250).describe("Target internal temperature in Fahrenheit"), protein_type: ProteinTypeSchema.describe("Type of protein being cooked"), cook_method: CookMethodSchema.optional().describe("Cooking method being used"), cook_start_time: z .string() .optional() .describe("When the cook started in ISO 8601 format (e.g., '2024-12-25T06:00:00')"), previous_readings: z .array( z.object({ temp: z.number().describe("Temperature reading"), timestamp: z.string().describe("Time of reading in ISO 8601 format"), }) ) .optional() .describe("Previous temperature readings to calculate trend (most recent last)"), response_format: ResponseFormatSchema.describe("Output format"), }) .strict();
- src/services/formatting.ts:93-136 (helper)Helper function that formats the TemperatureAnalysis result into a human-readable Markdown response with progress, trend visualization, ETA, stall warnings, and recommendations.export function formatTemperatureAnalysisMarkdown(analysis: TemperatureAnalysis): string { let output = `## 🌡️ Temperature Analysis\n\n`; output += `**Current:** ${analysis.currentTemp}°F → **Target:** ${analysis.targetTemp}°F\n`; output += `**Progress:** ${analysis.percentComplete}% complete\n`; output += `**Remaining:** ${analysis.tempDelta}°F to go\n\n`; const trendEmoji = analysis.trend === "rising" ? "📈" : analysis.trend === "falling" ? "📉" : analysis.trend === "stalled" ? "⏸️" : "➡️"; output += `### Trend: ${trendEmoji} ${analysis.trend.charAt(0).toUpperCase() + analysis.trend.slice(1)}\n\n`; if (analysis.trendRatePerHour !== 0) { output += `**Rate:** ${analysis.trendRatePerHour > 0 ? "+" : ""}${analysis.trendRatePerHour}°F/hour\n`; } if (analysis.estimatedMinutesRemaining !== null) { const hours = Math.floor(analysis.estimatedMinutesRemaining / 60); const minutes = analysis.estimatedMinutesRemaining % 60; const timeString = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`; output += `**ETA:** ~${timeString} remaining\n`; } else if (analysis.inStallZone) { output += `**ETA:** Cannot estimate during stall\n`; } if (analysis.inStallZone) { output += `\n⚠️ **Currently in the stall zone!**\n`; } if (analysis.recommendations.length > 0) { output += `\n### Recommendations\n\n`; for (const rec of analysis.recommendations) { output += `${rec}\n`; } } return output; }
- src/smithery.ts:135-154 (registration)Smithery-specific registration of the tool using inline Zod schema and simplified handler.server.tool( "bbq_analyze_temperature", "Analyze temperature and get progress/recommendations", { current_temp: z.number().describe("Current temperature in °F"), target_temp: z.number().describe("Target temperature in °F"), protein_type: z.string().describe("Type of protein"), previous_readings: z.array(z.object({ temp: z.number(), timestamp: z.string() })).optional(), }, async ({ current_temp, target_temp, protein_type, previous_readings }) => { try { const readings = previous_readings?.map((r) => ({ temp: r.temp, timestamp: new Date(r.timestamp) })); const analysis = analyzeTemperature(current_temp, target_temp, protein_type as ProteinType, undefined, undefined, readings); const markdown = formatTemperatureAnalysisMarkdown(analysis); return { content: [{ type: "text", text: markdown }] }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return { content: [{ type: "text", text: `Error: ${message}` }], isError: true }; } }