Skip to main content
Glama

bbq_detect_stall

Detect temperature stalls during BBQ cooking by analyzing temperature trends. Identifies plateaus in large cuts like brisket and pork shoulder, then provides recommendations to manage the cook.

Instructions

Analyze temperature readings to detect if a cook is experiencing a stall.

The stall is a phenomenon where internal temperature plateaus, common with large cuts like brisket and pork shoulder. This tool analyzes temperature trend to detect stalls and provides recommendations.

Args:

  • protein_type: Type of protein being cooked

  • current_temp: Current internal temperature in °F

  • readings: Array of at least 3 readings with {temp, timestamp}

  • response_format: 'markdown' or 'json'

Examples:

  • "Is my brisket stalling?" -> Provide current_temp and readings array

  • "Temp hasn't moved in 2 hours" -> Include readings over that period

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
protein_typeYesType of protein being cooked
current_tempYesCurrent internal temperature in Fahrenheit
readingsYesAt least 3 temperature readings to analyze trend (most recent last)
response_formatNoOutput formatmarkdown

Implementation Reference

  • src/index.ts:621-684 (registration)
    Primary registration of the bbq_detect_stall tool, including metadata, schema reference, and inline handler function
    server.registerTool( "bbq_detect_stall", { title: "Detect Temperature Stall", description: `Analyze temperature readings to detect if a cook is experiencing a stall. The stall is a phenomenon where internal temperature plateaus, common with large cuts like brisket and pork shoulder. This tool analyzes temperature trend to detect stalls and provides recommendations. Args: - protein_type: Type of protein being cooked - current_temp: Current internal temperature in °F - readings: Array of at least 3 readings with {temp, timestamp} - response_format: 'markdown' or 'json' Examples: - "Is my brisket stalling?" -> Provide current_temp and readings array - "Temp hasn't moved in 2 hours" -> Include readings over that period`, inputSchema: DetectStallSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }, async (params: DetectStallInput) => { try { const readings = params.readings.map((r) => ({ temp: r.temp, timestamp: new Date(r.timestamp), })); const result = detectStall(params.protein_type, params.current_temp, readings); if (params.response_format === "json") { const output = { currentTemp: params.current_temp, isStalled: result.isStalled, stallDurationMinutes: result.stallDurationMinutes, inStallZone: result.inStallZone, recommendation: result.recommendation, proteinType: params.protein_type, readingsAnalyzed: readings.length, }; return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } const markdown = formatStallDetectionMarkdown(result, params.current_temp); 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 detecting stall: ${message}` }], }; } } );
  • Handler function that executes the tool: parses input, calls detectStall helper, formats JSON or Markdown response, handles errors
    async (params: DetectStallInput) => { try { const readings = params.readings.map((r) => ({ temp: r.temp, timestamp: new Date(r.timestamp), })); const result = detectStall(params.protein_type, params.current_temp, readings); if (params.response_format === "json") { const output = { currentTemp: params.current_temp, isStalled: result.isStalled, stallDurationMinutes: result.stallDurationMinutes, inStallZone: result.inStallZone, recommendation: result.recommendation, proteinType: params.protein_type, readingsAnalyzed: readings.length, }; return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } const markdown = formatStallDetectionMarkdown(result, params.current_temp); 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 detecting stall: ${message}` }], }; } }
  • Zod schema for input validation: protein_type, current_temp, readings array (min 3), response_format
    export const DetectStallSchema = z .object({ protein_type: ProteinTypeSchema.describe("Type of protein being cooked"), current_temp: z.number().min(100).max(250).describe("Current internal temperature in Fahrenheit"), readings: z .array( z.object({ temp: z.number().describe("Temperature reading"), timestamp: z.string().describe("Time of reading in ISO 8601 format"), }) ) .min(3) .describe("At least 3 temperature readings to analyze trend (most recent last)"), response_format: ResponseFormatSchema.describe("Output format"), }) .strict(); export type DetectStallInput = z.infer<typeof DetectStallSchema>;
  • Core stall detection logic: determines if in stall zone based on protein profile, checks temp rise rate over recent readings (&lt;3°F/hr), estimates stall duration, provides tailored recommendations
    export function detectStall( proteinType: ProteinType, currentTemp: number, readings: Array<{ temp: number; timestamp: Date }> ): { isStalled: boolean; stallDurationMinutes: number; inStallZone: boolean; recommendation: string; } { const profile = getProteinProfile(proteinType); // Check if this protein type typically stalls if (!profile.stallRange) { return { isStalled: false, stallDurationMinutes: 0, inStallZone: false, recommendation: `${profile.displayName} typically doesn't experience a stall.`, }; } const inStallZone = currentTemp >= profile.stallRange.start && currentTemp <= profile.stallRange.end; if (!inStallZone) { if (currentTemp < profile.stallRange.start) { return { isStalled: false, stallDurationMinutes: 0, inStallZone: false, recommendation: `Approaching stall zone (${profile.stallRange.start}-${profile.stallRange.end}°F). Be prepared for a plateau.`, }; } else { return { isStalled: false, stallDurationMinutes: 0, inStallZone: false, recommendation: "You've pushed through the stall! Temperature should rise steadily now.", }; } } // Analyze readings to determine if actually stalled (temp not rising) const recentReadings = readings.slice(-6); // Last 6 readings if (recentReadings.length < 3) { return { isStalled: false, stallDurationMinutes: 0, inStallZone: true, recommendation: "In the stall zone but need more readings to confirm stall status.", }; } // Calculate temperature change over the readings const tempChange = recentReadings[recentReadings.length - 1].temp - recentReadings[0].temp; const timeSpanMinutes = (recentReadings[recentReadings.length - 1].timestamp.getTime() - recentReadings[0].timestamp.getTime()) / (1000 * 60); const tempRatePerHour = timeSpanMinutes > 0 ? (tempChange / timeSpanMinutes) * 60 : 0; // Consider stalled if temp is rising less than 3°F per hour in the stall zone const isStalled = tempRatePerHour < 3; // Calculate how long the stall has lasted let stallDurationMinutes = 0; if (isStalled && readings.length >= 3) { // Find when temp first entered stall zone for (let i = readings.length - 1; i >= 0; i--) { if (readings[i].temp >= profile.stallRange.start && readings[i].temp <= profile.stallRange.end) { stallDurationMinutes = (new Date().getTime() - readings[i].timestamp.getTime()) / (1000 * 60); } else { break; } } } let recommendation: string; if (isStalled) { if (stallDurationMinutes < 60) { recommendation = "Stall detected! This is normal - evaporative cooling is slowing the temp rise. The stall can last 2-4 hours."; } else if (stallDurationMinutes < 180) { recommendation = `Stall has lasted ${Math.round(stallDurationMinutes)} minutes. Consider wrapping in butcher paper (Texas crutch) to push through faster.`; } else { recommendation = `Extended stall (${Math.round(stallDurationMinutes)} minutes). Wrapping is strongly recommended, or you can ride it out - eventually, it will break through.`; } } else { recommendation = "In the stall zone but temperature is still rising. Keep monitoring - you may push through without a major plateau."; } return { isStalled, stallDurationMinutes: Math.round(stallDurationMinutes), inStallZone, recommendation, }; }
  • Formats stall detection result into user-friendly Markdown response
    * Format stall detection result as Markdown */ export function formatStallDetectionMarkdown( result: { isStalled: boolean; stallDurationMinutes: number; inStallZone: boolean; recommendation: string; }, currentTemp: number ): string { let output = `## 🛑 Stall Analysis\n\n`; output += `**Current Temperature:** ${currentTemp}°F\n`; output += `**In Stall Zone:** ${result.inStallZone ? "Yes" : "No"}\n`; output += `**Stalled:** ${result.isStalled ? "Yes" : "No"}\n`; if (result.isStalled) { output += `**Stall Duration:** ${result.stallDurationMinutes} minutes\n`; } output += `\n### Recommendation\n\n${result.recommendation}\n`; if (result.isStalled) { output += `\n### Options to Push Through\n\n`; output += `1. **Wrap (Texas Crutch):** Wrap in butcher paper or foil to trap moisture and speed cooking\n`; output += `2. **Increase Heat:** Bump smoker to 275-300°F temporarily\n`; output += `3. **Ride It Out:** Be patient - stall will eventually break\n`; } return output; }

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/jweingardt12/bbq-mcp'

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