poll_error_endpoint_and_fix
Fetch JSON error reports from Opentrons robots and automatically fix protocol files to resolve execution issues.
Instructions
Fetch specific JSON error report and automatically fix protocols
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| json_filename | No | Name of JSON file to fetch | error_report_20250622_124746.json |
| original_protocol_path | No | Path to original protocol file | /Users/gene/Developer/failed-protocol-5.py |
Implementation Reference
- index.js:2059-2153 (handler)Main handler function for 'poll_error_endpoint_and_fix' tool. Fetches error JSON from a local endpoint, stops any running Opentrons robot protocol, reads the original failed protocol file, uses Claude AI via Anthropic API to generate a fixed protocol, and returns the error report with the fixed code.async pollErrorEndpointAndFix(args) { const { json_filename = "error.json", original_protocol_path = "/Users/gene/Developer/failed-protocol-5.py" } = args; try { const axios = (await import('axios')).default; const baseUrl = 'http://192.168.0.145:8080'; const jsonUrl = `${baseUrl}/${json_filename}`; console.error(`🔍 Fetching JSON error report: ${jsonUrl}`); // Fetch the specific JSON file const response = await fetch(jsonUrl); if (!response.ok) { throw new Error(`Failed to fetch ${json_filename}: ${response.status} ${response.statusText}`); } let errorText = await response.text(); // Validate and pretty-print JSON try { const parsed = JSON.parse(errorText); errorText = JSON.stringify(parsed, null, 2); console.error(`✅ Successfully fetched and parsed ${json_filename}`); } catch (parseError) { throw new Error(`Invalid JSON in ${json_filename}: ${parseError.message}`); } // Get current run info and stop it const robotIp = "192.168.0.83"; let stopStatus = "⚠️ No running protocol found"; let currentRunId = null; let lastCompletedStep = null; try { const runsResponse = await axios.get(`http://${robotIp}:31950/runs`); const activeRun = runsResponse.data.data.find(run => run.status === "running" || run.status === "paused" ); if (activeRun) { currentRunId = activeRun.id; // Get detailed run info including protocol name and current status const runDetailResponse = await axios.get(`http://${robotIp}:31950/runs/${currentRunId}`); const runDetail = runDetailResponse.data.data; const protocolName = runDetail.protocolId || 'Unknown Protocol'; const currentStatus = runDetail.status; const currentCommand = runDetail.current ? runDetail.current.command : 'None'; if (runDetail.commands) { const completedCommands = runDetail.commands.filter(cmd => cmd.status === "succeeded"); const failedCommands = runDetail.commands.filter(cmd => cmd.status === "failed"); lastCompletedStep = completedCommands.length; console.log(`Protocol: ${protocolName}, Status: ${currentStatus}, Step: ${lastCompletedStep}`); } // Stop the run await axios.post(`http://${robotIp}:31950/runs/${currentRunId}/actions`, { data: { actionType: "stop" } }); stopStatus = `✅ Robot stopped Protocol: ${protocolName} Run ID: ${currentRunId} Status: ${currentStatus} → stopped Completed steps: ${lastCompletedStep || 0} Current command: ${currentCommand} Failed commands: ${failedCommands?.length || 0}`; } } catch (stopError) { stopStatus = `❌ Stop failed: ${stopError.message}`; } // Read original protocol and generate fix const originalProtocol = fs.readFileSync(original_protocol_path, 'utf8'); const fixedProtocol = await this.generateFixedProtocol(errorText, originalProtocol, lastCompletedStep, currentRunId); return { content: [{ type: "text", text: `🚨 **JSON ERROR REPORT**: ${json_filename}\n\n📄 **CONTENT**:\n${errorText}\n\n${stopStatus}\n\n🔧 **FIXED PROTOCOL**:\n\n\`\`\`python\n${fixedProtocol}\n\`\`\`` }] }; } catch (error) { return { content: [{ type: "text", text: `❌ **Failed to fetch error report**: ${error.message}` }] }; } }
- index.js:223-232 (schema)Tool schema definition including name, description, and inputSchema with optional json_filename and original_protocol_path parameters (both with defaults).{ name: "poll_error_endpoint_and_fix", description: "Fetch specific JSON error report and automatically fix protocols", inputSchema: { type: "object", properties: { json_filename: { type: "string", default: "error_report_20250622_124746.json", description: "Name of JSON file to fetch" }, original_protocol_path: { type: "string", default: "/Users/gene/Developer/failed-protocol-5.py", description: "Path to original protocol file" } } }
- index.js:268-269 (registration)Registration of the tool handler in the CallToolRequestSchema switch statement.case "poll_error_endpoint_and_fix": return this.pollErrorEndpointAndFix(args);
- index.js:2172-2285 (helper)Supporting helper function that generates a fixed protocol by calling Anthropic Claude API (model claude-3-5-sonnet-20241022) with a detailed prompt including the error, original protocol, a working example, and run context. Uses ANTHROPIC_API_KEY env var.async generateFixedProtocol(errorText, originalProtocol, lastCompletedStep = null, currentRunId = null) { const anthropicKey = process.env.ANTHROPIC_API_KEY; if (!anthropicKey) { throw new Error("ANTHROPIC_API_KEY not configured"); } const workingExample = `from opentrons import protocol_api metadata = { 'protocolName': 'Pierce BCA Protein Assay Kit Aliquoting', 'author': 'OpentronsAI', 'description': 'Automated liquid handling for protein concentration determination using Pierce BCA Protein Assay Kit', 'source': 'OpentronsAI' } requirements = { 'robotType': 'Flex', 'apiLevel': '2.22' } def run(protocol: protocol_api.ProtocolContext): # Load trash bin trash = protocol.load_trash_bin('A3') # Load labware reservoir = protocol.load_labware('nest_12_reservoir_15ml', 'D1', 'Source Reservoir') pcr_plate = protocol.load_labware('nest_96_wellplate_100ul_pcr_full_skirt', 'D2', 'PCR Plate') tiprack = protocol.load_labware('opentrons_flex_96_filtertiprack_50ul', 'D3', 'Filter Tips 50uL') # Load pipette p50_multi = protocol.load_instrument('flex_8channel_50', 'left', tip_racks=[tiprack]) # Define liquid master_mix = protocol.define_liquid( name='Master Mix', description='Pierce BCA Protein Assay Master Mix', display_color='#0066CC' ) # Load liquid into reservoir reservoir['A1'].load_liquid(liquid=master_mix, volume=1500) # Protocol steps protocol.comment("Starting Pierce BCA Protein Assay Kit aliquoting protocol") # Transfer 50 µL from reservoir to first 16 wells of PCR plate source_well = reservoir['A1'] destination_wells = pcr_plate.columns()[:2] # First 2 columns = 16 wells protocol.comment("Transferring 50 µL of master mix to first 16 wells of PCR plate") p50_multi.transfer( volume=50, source=source_well, dest=destination_wells, new_tip='once' ) protocol.comment("Protocol completed successfully")`; let contextInfo = ""; if (lastCompletedStep !== null && currentRunId !== null) { contextInfo = `\n\nRUN CONTEXT: - Run ID: ${currentRunId} - Successfully completed steps: ${lastCompletedStep} - The protocol should resume from or be modified to account for this point`; } const prompt = `Fix this Opentrons Flex protocol that failed with this error: ERROR: ${errorText} ORIGINAL FAILED PROTOCOL: ${originalProtocol} WORKING REFERENCE PROTOCOL: ${workingExample}${contextInfo} Generate a FIXED version of the original protocol that: 1. Fixes the specific error mentioned 2. Uses proper Flex deck positions (A1, B1, C1, D1, etc.) 3. Uses proper Flex pipettes and labware 4. Follows the working pattern from the reference 5. Maintains the same general purpose as the original 6. ${lastCompletedStep !== null ? `Accounts for the fact that ${lastCompletedStep} steps were already completed successfully` : 'Starts from the beginning'} Return ONLY the fixed Python code, no explanations or markdown.`; try { const response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "x-api-key": anthropicKey, "Content-Type": "application/json", "anthropic-version": "2023-06-01" }, body: JSON.stringify({ model: "claude-3-5-sonnet-20241022", max_tokens: 2000, messages: [{ role: "user", content: prompt }] }) }); if (!response.ok) { throw new Error(`Claude API error: ${response.status}`); } const result = await response.json(); return result.content[0].text; } catch (error) { return `❌ Failed to generate fix: ${error.message}\n\nORIGINAL PROTOCOL:\n${originalProtocol}`; } }