thermoworks_analyze_live
Monitor live BBQ temperatures from ThermoWorks devices and receive cooking progress analysis with recommendations based on protein type and target temperatures.
Instructions
Get live temperature from a connected ThermoWorks device and analyze cooking progress.
Combines real-time device data with the BBQ cooking knowledge base to provide actionable recommendations.
Requires authentication first via thermoworks_authenticate.
Args:
device_serial: Serial number of the device
probe_id: Probe number to analyze (default: '1')
protein_type: Type of protein being cooked
target_temp: Target temperature (optional, uses protein default)
response_format: 'markdown' or 'json'
Returns: Current temperature, progress percentage, trend analysis, and recommendations.
Examples:
"How's my brisket doing?" -> Analyzes probe 1 against brisket targets
"Check the turkey on probe 2" -> protein_type='turkey_whole', probe_id='2'
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| device_serial | Yes | Serial number of the device to analyze | |
| probe_id | No | Probe number to analyze (e.g., '1', '2', '3', '4' for Signals) | 1 |
| protein_type | Yes | Type of protein being cooked (e.g., 'beef_brisket') | |
| target_temp | No | Target temperature. If not provided, uses recommended temp for the protein. | |
| response_format | No | Output format | markdown |
Implementation Reference
- src/index.ts:1337-1461 (handler)Primary handler implementation: fetches live temperature readings from ThermoWorks device using getDeviceReadings, determines protein target temperature, analyzes progress with analyzeTemperature helper, and returns formatted markdown or JSON response with recommendations.async (params: AnalyzeLiveTemperatureInput) => { try { const client = getThermoWorksClient(); if (!client.isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Use `thermoworks_authenticate` first.", }, ], }; } // Get live reading const reading = await client.getDeviceReadings(params.device_serial); if (!reading) { return { isError: true, content: [ { type: "text", text: `No readings available for device ${params.device_serial}. Make sure the device is powered on.`, }, ], }; } // Find the specified probe const probeData = reading.probes[params.probe_id]; if (!probeData) { const availableProbes = Object.keys(reading.probes).join(", "); return { isError: true, content: [ { type: "text", text: `Probe ${params.probe_id} not found. Available probes: ${availableProbes}`, }, ], }; } // Get target temperature const proteinType = params.protein_type as ProteinType; const { targetTemp, pullTemp, doneness } = getTargetTemperature(proteinType); const target = params.target_temp || targetTemp; // Analyze the temperature const analysis = analyzeTemperature( probeData.temp, target, proteinType, undefined, // No cook method from device undefined, // No start time undefined // No previous readings yet ); const profile = getProteinProfile(proteinType); if (params.response_format === "json") { const output = { device: { serial: reading.serial, name: reading.name, probe: params.probe_id, probeName: probeData.name, }, reading: { currentTemp: probeData.temp, unit: reading.unit, timestamp: reading.timestamp, }, target: { temp: target, pullTemp, doneness, }, analysis, protein: { type: proteinType, displayName: profile.displayName, }, }; return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } let markdown = `## 🔴 Live Analysis: ${profile.displayName}\n\n`; markdown += `**Device:** ${reading.name} - ${probeData.name || `Probe ${params.probe_id}`}\n`; markdown += `**Current Temp:** ${probeData.temp}°${reading.unit}\n`; markdown += `**Target:** ${target}°F (${DONENESS_INFO[doneness]?.displayName || doneness})\n`; markdown += `**Pull At:** ${pullTemp}°F\n\n`; markdown += `### Progress\n\n`; markdown += `**${analysis.percentComplete}% complete** (${analysis.tempDelta}°F to go)\n\n`; if (analysis.inStallZone) { markdown += `⚠️ **In the stall zone!** Temperature may plateau.\n\n`; } if (analysis.recommendations.length > 0) { markdown += `### Recommendations\n\n`; for (const rec of analysis.recommendations) { markdown += `${rec}\n`; } } return { content: [{ type: "text", text: markdown }], }; } catch (error) { const message = error instanceof Error ? error.message : "Failed to analyze"; return { isError: true, content: [{ type: "text", text: `Error: ${message}` }], }; } }
- src/schemas/auth.ts:57-79 (schema)Zod input schema defining parameters: device_serial (required), probe_id (default '1'), protein_type, optional target_temp and response_format.* Schema for analyzing live temperature from connected device */ export const AnalyzeLiveTemperatureSchema = z .object({ device_serial: z .string() .describe("Serial number of the device to analyze"), probe_id: z .string() .default("1") .describe("Probe number to analyze (e.g., '1', '2', '3', '4' for Signals)"), protein_type: z .string() .describe("Type of protein being cooked (e.g., 'beef_brisket')"), target_temp: z .number() .optional() .describe("Target temperature. If not provided, uses recommended temp for the protein."), response_format: ResponseFormatSchema.describe("Output format"), }) .strict(); export type AnalyzeLiveTemperatureInput = z.infer<typeof AnalyzeLiveTemperatureSchema>;
- src/index.ts:1306-1462 (registration)MCP server tool registration including title, description, input schema reference, annotations, and inline handler function.server.registerTool( "thermoworks_analyze_live", { title: "Analyze Live Temperature", description: `Get live temperature from a connected ThermoWorks device and analyze cooking progress. Combines real-time device data with the BBQ cooking knowledge base to provide actionable recommendations. Requires authentication first via thermoworks_authenticate. Args: - device_serial: Serial number of the device - probe_id: Probe number to analyze (default: '1') - protein_type: Type of protein being cooked - target_temp: Target temperature (optional, uses protein default) - response_format: 'markdown' or 'json' Returns: Current temperature, progress percentage, trend analysis, and recommendations. Examples: - "How's my brisket doing?" -> Analyzes probe 1 against brisket targets - "Check the turkey on probe 2" -> protein_type='turkey_whole', probe_id='2'`, inputSchema: AnalyzeLiveTemperatureSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, }, async (params: AnalyzeLiveTemperatureInput) => { try { const client = getThermoWorksClient(); if (!client.isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Use `thermoworks_authenticate` first.", }, ], }; } // Get live reading const reading = await client.getDeviceReadings(params.device_serial); if (!reading) { return { isError: true, content: [ { type: "text", text: `No readings available for device ${params.device_serial}. Make sure the device is powered on.`, }, ], }; } // Find the specified probe const probeData = reading.probes[params.probe_id]; if (!probeData) { const availableProbes = Object.keys(reading.probes).join(", "); return { isError: true, content: [ { type: "text", text: `Probe ${params.probe_id} not found. Available probes: ${availableProbes}`, }, ], }; } // Get target temperature const proteinType = params.protein_type as ProteinType; const { targetTemp, pullTemp, doneness } = getTargetTemperature(proteinType); const target = params.target_temp || targetTemp; // Analyze the temperature const analysis = analyzeTemperature( probeData.temp, target, proteinType, undefined, // No cook method from device undefined, // No start time undefined // No previous readings yet ); const profile = getProteinProfile(proteinType); if (params.response_format === "json") { const output = { device: { serial: reading.serial, name: reading.name, probe: params.probe_id, probeName: probeData.name, }, reading: { currentTemp: probeData.temp, unit: reading.unit, timestamp: reading.timestamp, }, target: { temp: target, pullTemp, doneness, }, analysis, protein: { type: proteinType, displayName: profile.displayName, }, }; return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } let markdown = `## 🔴 Live Analysis: ${profile.displayName}\n\n`; markdown += `**Device:** ${reading.name} - ${probeData.name || `Probe ${params.probe_id}`}\n`; markdown += `**Current Temp:** ${probeData.temp}°${reading.unit}\n`; markdown += `**Target:** ${target}°F (${DONENESS_INFO[doneness]?.displayName || doneness})\n`; markdown += `**Pull At:** ${pullTemp}°F\n\n`; markdown += `### Progress\n\n`; markdown += `**${analysis.percentComplete}% complete** (${analysis.tempDelta}°F to go)\n\n`; if (analysis.inStallZone) { markdown += `⚠️ **In the stall zone!** Temperature may plateau.\n\n`; } if (analysis.recommendations.length > 0) { markdown += `### Recommendations\n\n`; for (const rec of analysis.recommendations) { markdown += `${rec}\n`; } } return { content: [{ type: "text", text: markdown }], }; } catch (error) { const message = error instanceof Error ? error.message : "Failed to analyze"; return { isError: true, content: [{ type: "text", text: `Error: ${message}` }], }; } } );
- src/smithery.ts:325-360 (helper)Simplified handler and registration for Smithery deployment using inline Zod schema and abbreviated logic.server.tool( "thermoworks_analyze_live", "Analyze live temp against cooking targets", { device_serial: z.string(), probe_id: z.string().default("1"), protein_type: z.string(), target_temp: z.number().optional(), }, async ({ device_serial, probe_id, protein_type, target_temp }) => { try { const client = getThermoWorksClient(); if (!client.isAuthenticated()) { return { content: [{ type: "text", text: "Not authenticated" }], isError: true }; } const reading = await client.getDeviceReadings(device_serial); if (!reading) return { content: [{ type: "text", text: "No reading" }], isError: true }; const probe = reading.probes[probe_id]; if (!probe) return { content: [{ type: "text", text: `No probe ${probe_id}` }], isError: true }; const { targetTemp } = getTargetTemperature(protein_type as ProteinType); const target = target_temp || targetTemp; const analysis = analyzeTemperature(probe.temp, target, protein_type as ProteinType); let text = `## ${getProteinProfile(protein_type as ProteinType).displayName}\n\n`; text += `**Current:** ${probe.temp}°${reading.unit} | **Target:** ${target}°F\n`; text += `**Progress:** ${analysis.percentComplete}%\n`; if (analysis.inStallZone) text += `⚠️ In stall zone\n`; return { content: [{ type: "text", text }] }; } catch (error) { const message = error instanceof Error ? error.message : "Error"; return { content: [{ type: "text", text: message }], isError: true }; } } );