Skip to main content
Glama

Detect Temperature Stall

bbq_detect_stall
Read-onlyIdempotent

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 (<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;
    }
Behavior3/5

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

Annotations already declare readOnlyHint=true, destructiveHint=false, idempotentHint=true, and openWorldHint=false, so the agent knows this is a safe, deterministic read operation. The description adds useful context about the stall phenomenon and that it 'provides recommendations,' which goes beyond annotations. However, it does not detail behavioral aspects like rate limits, error conditions, or the format of recommendations, leaving some gaps.

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 well-structured and appropriately sized: it starts with the core purpose, explains the stall phenomenon, and includes an 'Args' section and examples. Most sentences earn their place, but the 'Args' section slightly duplicates schema information, and the examples could be more concise. Overall, it is front-loaded and efficient.

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 tool's moderate complexity (4 parameters, 100% schema coverage, annotations provided, no output schema), the description is mostly complete. It covers purpose, context, and usage examples. However, it lacks details on output structure (since no output schema exists) and does not fully address behavioral nuances like error handling or recommendation formats, leaving minor gaps.

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 description coverage is 100%, so the schema fully documents all parameters. The description adds minimal value beyond the schema: it mentions 'analyzes temperature trend' and provides examples that imply usage of current_temp and readings, but does not explain parameter interactions or semantics not covered by the schema. This meets the baseline of 3 when schema coverage is high.

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: 'Analyze temperature readings to detect if a cook is experiencing a stall.' It specifies the verb ('analyze'), resource ('temperature readings'), and distinct outcome ('detect stall'), differentiating it from siblings like bbq_analyze_temperature or bbq_analyze_device_reading by focusing on stall detection rather than general analysis.

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 provides clear context for when to use the tool: when analyzing temperature trends to detect stalls, common with large cuts like brisket and pork shoulder. The examples ('Is my brisket stalling?' and 'Temp hasn't moved in 2 hours') illustrate typical scenarios. However, it does not explicitly state when not to use it or name specific alternatives among siblings, such as bbq_analyze_temperature for non-stall analysis.

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

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