show_ordinals
Retrieve Bitcoin Ordinal inscription data and save images locally for analysis. Use this tool to fetch technical metadata and prepare files for OCR processing.
Instructions
š¹ Bitcoin Ordinals Expert Assistant
š MANDATORY TWO-RESPONSE PROCESS FOR IMAGES
I strictly follow a two-step structured workflow for processing Bitcoin Ordinal inscriptions. STRICT ADHERENCE IS REQUIRED.
š Required Tools & Workflow
š Tools Used:
1ļøā£ show_ordinals
Fetches inscription data.
If the inscription is an image, it MUST:
Save it locally and return the
local_file_path.(Code currently does NOT return a separate displayable source, only the local path)
2ļøā£ image_processor
MUST be used for extracting textual/visual data from the image.
Takes
local_file_pathas input.
š MANDATORY TWO-STEP RESPONSE WORKFLOW (FOR IMAGES)
š RESPONSE 1: Display Attempt + Technical Details (NON-TERMINAL)
ā Execute
ā Return an using the local_file_path as the src attribute.
ā Follow with detailed technical metadata.
ā STOP HERE. NO ANALYSIS YET.
ā RESPONSE FORMAT (STRICTLY ENFORCED):
š¹ End FIRST response here. No OCR, No AI-generated descriptions. This response is intended to be NON-TERMINAL to allow for Step 2.
š RESPONSE 2: OCR & Content Analysis (Using Designated Tools ONLY)
ā AFTER Response 1, consult available tools.
ā Execute
ā Strictly use ONLY tool-generated analysis.
ā If BOTH tools fail, state failure explicitly.
ā Return a new markdown response with the extracted data.
ā RESPONSE FORMAT (STRICTLY ENFORCED):
ā No hallucinations. No AI-generated assumptions.
š FALLBACK MECHANISM (IF OCR TOOLS FAIL)
If BOTH
image_processorANDvisionfail, use a verified fallback OCR tool (if available).If no fallback exists, explicitly state failure instead of hallucinating content.
šÆ Summary: Strict 2-Response Workflow
ā Step 1: Execute ā Attempt Image Display (using local path) + Metadata. (Response intended as Non-Terminal) ā Step 2: Execute ā Analysis Description. (Separate Response)
š” This ensures clarity, prevents AI hallucinations, and provides structured responses. š
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| txid | Yes | The unique 64-character hexadecimal identifier of the Bitcoin transaction to inspect for Ordinals. Example: 'f1d2d3...a8b9c0'. | |
| inscription_index | No | Optional. The specific index (starting from 0) of the inscription to retrieve within the transaction, if there are multiple. | |
| content_type_filter | No | Optional. Filter inscriptions by content type, e.g., 'image/png', 'text/plain', 'application/json'. |
Implementation Reference
- src/servers/base.ts:565-639 (handler)Main handler function for the 'show_ordinals' tool. Validates input, fetches and decodes Ordinal inscription from transaction witness data using OrdinalsClient, handles image saving and HTML display, formats text/JSON content.protected async handleDecodeWitness(args: unknown) { const result = DecodeWitnessSchema.safeParse(args); if (!result.success) { throw new McpError( ErrorCode.InvalidParams, `Invalid parameters: ${result.error.message}` ); } const txid = result.data.txid; logger.info(`Processing Ordinals for transaction: ${txid}`); try { // Call decodeWitness method with file option to store images in ~/.cache const decodedWitness = await this.client.decodeWitness(txid, "file"); // Check if an image is present in the results // Image paths now never have file:// prefix const images = decodedWitness.filter(content => content.startsWith('data:image/') || (content.length > 0 && !content.startsWith('{') && !content.startsWith('[')) ); // If we have an image, process it for display if (images.length > 0) { const imageData = images[0]; const txInfo = await this.client.getTransaction(txid, false); // Mark this txid as successfully decoded BEFORE processing the image // to avoid timing issues with get_transaction call this.markSuccessfulDecoding(txid); logger.info(`Transaction ${txid} marked as successfully decoded before image processing`); // Use processAndSaveImage method for display return await this.processAndSaveImage(imageData, txid, txInfo); } // For other content types (JSON, text, etc.) const formattedContent = decodedWitness.map(content => { if (content.startsWith('{') || content.startsWith('[')) { try { const jsonData = JSON.parse(content); return `\`\`\`json\n${JSON.stringify(jsonData, null, 2)}\n\`\`\``; } catch (e) { return content; } } return content; }).join('\n\n'); return { content: [ { type: "text", text: formattedContent } ] as TextContent[], }; } catch (error) { // In case of error during the decoding process logger.error({ error }, `Error decoding Ordinals for ${txid}`); return { content: [ { type: "text", text: `Error decoding Ordinals for transaction ${txid}:\n` + `${error instanceof Error ? error.message : String(error)}\n\n` + "This may be due to a blockchain API access issue, an unsupported transaction format, " + "or the transaction may not exist." } ] as TextContent[], }; } }
- src/mcp_inscription_types.ts:189-192 (schema)Zod schema defining input validation for the 'show_ordinals' tool: requires txid (64-char hex), optional formatImageOption ('base64' or 'file'). Matches the inline tool inputSchema.export const DecodeWitnessSchema = z.object({ txid: z.string().length(64, "Invalid transaction ID"), formatImageOption: z.enum(["base64", "file"]).optional().default("base64"), });
- src/servers/base.ts:129-152 (registration)Tool registration in ListToolsRequestSchema handler. Defines 'show_ordinals' name, detailed usage instructions for Ordinals analysis workflow, and input schema.{ name: "show_ordinals", description: "## š¹ Bitcoin Ordinals Expert Assistant \n\n### **š MANDATORY TWO-RESPONSE PROCESS FOR IMAGES** \nI strictly follow a **two-step structured workflow** for processing Bitcoin Ordinal inscriptions. **STRICT ADHERENCE IS REQUIRED.** \n\n---\n\n## š **Required Tools & Workflow** \n\n### **š Tools Used:** \n1ļøā£ **`show_ordinals` (MANDATORY FIRST STEP)** \n - Fetches **inscription data**. \n - If the inscription is an **image**, it **MUST**: \n - **Save it locally** and return the **`local_file_path`**.\n - *(Code currently does NOT return a separate displayable source, only the local path)*\n\n2ļøā£ **`image_processor` / `vision` (OCR & Visual Analysis ONLY)** \n - **MUST be used** for extracting textual/visual data from the image.\n - Takes **`local_file_path`** as input.\n\n---\n\n## **š MANDATORY TWO-STEP RESPONSE WORKFLOW (FOR IMAGES)** \n\n### **š RESPONSE 1: Display Attempt + Technical Details (NON-TERMINAL)** \nā **Execute `show_ordinals` FIRST.** \nā **Return an `<img>` tag** using the **`local_file_path` provided by `show_ordinals`** as the `src` attribute.\nā **Follow with detailed technical metadata.** \nā **STOP HERE. NO ANALYSIS YET.** \n\n**ā RESPONSE FORMAT (STRICTLY ENFORCED):** \n```html\n<!-- Attempting display using local path. Note: May not render in standard browsers due to security restrictions. -->\n<img src=\"[local_file_path from show_ordinals]\" alt=\"Ordinal Image Inscription\" style=\"max-width: 100%; height: auto;\" />\n\n### š Technical Details \n* **MIME Type:** [MIME type]\n* **Size:** [Size]\n* **Transaction ID:** [Tx ID]\n* **Status:** [Status]\n* **Inscription Input:** [Input]\n* **Local Path (for analysis):** `[local_file_path from show_ordinals]`\n* [Other available details]\n``` \nš¹ **End FIRST response here.** **No OCR, No AI-generated descriptions. This response is intended to be NON-TERMINAL to allow for Step 2.** \n\n---\n\n### **š RESPONSE 2: OCR & Content Analysis (Using Designated Tools ONLY)** \nā **AFTER Response 1, consult available tools.**\nā **Execute `image_processor` OR `vision` using the `local_file_path` from Response 1.**\nā **Strictly use ONLY tool-generated analysis.** \nā **If BOTH tools fail, state failure explicitly.** \nā **Return a new markdown response with the extracted data.** \n\n**ā RESPONSE FORMAT (STRICTLY ENFORCED):** \n```markdown\n### š Detailed Image Analysis (Result from '[Executed Tool Name]')\n\n[Extracted OCR text, visual description, or failure message from the tool ONLY]\n```\nā **No hallucinations. No AI-generated assumptions.** \n\n---\n\n### **š FALLBACK MECHANISM (IF OCR TOOLS FAIL)** \n- If **BOTH** `image_processor` AND `vision` **fail**, use a **verified fallback OCR tool** (if available).\n- If **no fallback exists**, explicitly **state failure** instead of hallucinating content.\n\n---\n\n### **šÆ Summary: Strict 2-Response Workflow** \nā **Step 1:** **Execute `show_ordinals`** ā Attempt Image Display (using local path) + Metadata. (Response intended as Non-Terminal)\nā **Step 2:** **Execute `image_processor` / `vision`** ā Analysis Description. (Separate Response)\n\n---\n\nš” **This ensures clarity, prevents AI hallucinations, and provides structured responses.** š", inputSchema: { type: "object", properties: { txid: { type: "string", description: "The unique 64-character hexadecimal identifier of the Bitcoin transaction to inspect for Ordinals. Example: 'f1d2d3...a8b9c0'." }, inscription_index: { type: "integer", description: "Optional. The specific index (starting from 0) of the inscription to retrieve within the transaction, if there are multiple." }, content_type_filter: { type: "string", description: "Optional. Filter inscriptions by content type, e.g., 'image/png', 'text/plain', 'application/json'." } }, required: ["txid"] } } as Tool, ], }));
- src/servers/base.ts:370-558 (helper)Supporting function called by handler for image inscriptions. Saves image to local temp dir, computes metadata, returns formatted response with <img src="local_path" /> and instructions.protected async processAndSaveImage( imageData: string, txid: string, txInfo: TransactionWithOrdinal ): Promise<{ content: TextContent[], endProcessing?: boolean }> { try { let filePath: string; let mimeType: string; let buffer: Buffer; // Get the client's configured image directory (always ~/.cache/mcp-inscription) const imageTempDir = this.client.getConfig().imageTempDir; logger.debug(`Using image directory from client config: ${imageTempDir}`); // Process file path if (imageData.startsWith('file://')) { // Convert file:// URI to OS-compatible path const filePathRaw = imageData.substring(7); // Normalize path based on operating system filePath = path.normalize(decodeURIComponent(filePathRaw)); // On Windows, if path starts with /, add drive letter if (process.platform === 'win32' && filePath.startsWith('/')) { filePath = filePath.substring(1); } if (!fs.existsSync(filePath)) { logger.warn(`File not found: ${filePath}, attempting to retrieve raw data`); // Fallback - try to use inscription data directly from txInfo if (txInfo.ordinal?.content && txInfo.ordinal.content.type.startsWith('image/')) { const contentBuffer = Buffer.from(txInfo.ordinal.content.data, 'hex'); const extension = this.getMimeExtension(txInfo.ordinal.content.type); const filename = `${txid}${extension}`; // Create directory if it doesn't exist if (!fs.existsSync(imageTempDir)) { fs.mkdirSync(imageTempDir, { recursive: true }); } filePath = path.join(imageTempDir, filename); // Write the file fs.writeFileSync(filePath, contentBuffer); buffer = contentBuffer; mimeType = txInfo.ordinal.content.type; } else { throw new Error(`File not found and unable to retrieve image data: ${filePath}`); } } else { buffer = fs.readFileSync(filePath); mimeType = this.getMimeTypeFromPath(filePath); } } // Process data URI else if (imageData.startsWith('data:image/')) { const mimeMatch = imageData.match(/^data:([^;]+);base64,(.*)$/); if (!mimeMatch) { throw new Error("Invalid Data URI format"); } mimeType = mimeMatch[1]; const base64Data = mimeMatch[2]; const extension = this.getMimeExtension(mimeType); const filename = `${txid}${extension}`; // Create directory if it doesn't exist if (!fs.existsSync(imageTempDir)) { fs.mkdirSync(imageTempDir, { recursive: true }); } filePath = path.join(imageTempDir, filename); buffer = Buffer.from(base64Data, 'base64'); // Write the file fs.writeFileSync(filePath, buffer); } // Fallback - try to use inscription data directly else if (txInfo.ordinal?.content && txInfo.ordinal.content.type.startsWith('image/')) { const contentBuffer = Buffer.from(txInfo.ordinal.content.data, 'hex'); mimeType = txInfo.ordinal.content.type; const extension = this.getMimeExtension(mimeType); const filename = `${txid}${extension}`; // Create directory if it doesn't exist if (!fs.existsSync(imageTempDir)) { fs.mkdirSync(imageTempDir, { recursive: true }); } filePath = path.join(imageTempDir, filename); // Write the file fs.writeFileSync(filePath, contentBuffer); buffer = contentBuffer; } else { throw new Error("Unsupported image data format"); } // Path to display in metadata - use filePath directly without file:// prefix let displayPath: string = filePath; // CRITICAL: Ensure the path used in HTML is the absolute path with correct slashes // and NEVER add file:// prefix as it causes issues with image display let imgSrc = filePath.replace(/\\/g, '/'); // Explicitly remove any file:// prefix if present imgSrc = imgSrc.replace(/^file:\/\/+/, ''); // Return formatted response with specific format to identify non-empty result // and include detailed inscription information const fileSizeKB = Math.round(buffer.length / 1024); const inputInfo = txInfo.inputs .filter(input => input.witness && input.witness.length > 0) .map(input => input.txid ? `${input.txid}:${input.vout}` : 'coinbase')[0] || "Unknown"; // Log the paths to help with debugging logger.debug({ filePath, displayPath, imgSrc, mimeType, size: fileSizeKB, }, "Image processing complete"); return { content: [ { type: "text", text: `DECODE_WITNESS_RESULT={ "status": "SUCCESS", "txid": "${txid}", "content_type": "image", "size": "${fileSizeKB} KB", "mime_type": "${mimeType}" }\n\n` + `### Ordinal Inscription Details ###\n` + `⢠Type: Ordinal Inscription (${mimeType})\n` + `⢠Size: ${fileSizeKB} KB\n` + `⢠Transaction ID: ${txid}\n` + `⢠Status: ${txInfo.status.confirmed ? `Confirmed in block #${txInfo.status.blockHeight}` : "Unconfirmed"}\n` + `⢠Transaction Size: ${txInfo.size} bytes\n` + `⢠Fee: ${txInfo.fee} sats\n` + `⢠Input containing inscription: ${inputInfo}\n` + `⢠Image Path: ${displayPath}\n\n` + `<img src="${imgSrc}" />\n\n` + `Please describe this Ordinal inscription image in detail. What does it show? What are its main visual elements? Include the image again in your response using:\n\n\`\`\`html\n<img src="${imgSrc}" />\n\`\`\``, isComplete: true, isTerminal: true } ] as TextContent[], endProcessing: true }; } catch (error) { logger.error({ error, txid }, "Error processing image"); // In case of error, try to return a data URI if available if (imageData.startsWith('data:image/')) { const mimeMatch = imageData.match(/^data:([^;]+);base64,/); if (mimeMatch) { const mimeType = mimeMatch[1]; const fileSizeEstimateKB = Math.round((imageData.length - imageData.indexOf(',') - 1) * 3 / 4 / 1024); const inputInfo = txInfo.inputs .filter(input => input.witness && input.witness.length > 0) .map(input => input.txid ? `${input.txid}:${input.vout}` : 'coinbase')[0] || "Unknown"; logger.debug("Using data URI directly in error handler"); return { content: [ { type: "text", text: `DECODE_WITNESS_RESULT={ "status": "SUCCESS", "txid": "${txid}", "content_type": "image", "size": "${fileSizeEstimateKB} KB", "mime_type": "${mimeType}" }\n\n` + `### Ordinal Inscription Details ###\n` + `⢠Type: Ordinal Inscription (${mimeType})\n` + `⢠Size: ~${fileSizeEstimateKB} KB\n` + `⢠Transaction ID: ${txid}\n` + `⢠Status: ${txInfo.status.confirmed ? `Confirmed in block #${txInfo.status.blockHeight}` : "Unconfirmed"}\n` + `⢠Transaction Size: ${txInfo.size} bytes\n` + `⢠Fee: ${txInfo.fee} sats\n` + `⢠Input containing inscription: ${inputInfo}\n` + `⢠Format: Data URI\n\n` + `<img src="${imageData}" />\n\n` + `Please describe this Ordinal inscription image in detail. What does it show? What are its main visual elements? Include the image again in your response using:\n\n\`\`\`html\n<img src="${imageData}" />\n\`\`\``, isComplete: true, isTerminal: true } ] as TextContent[], endProcessing: true }; } } // If all fails, throw the error to be handled by the caller throw error; } }
- src/ordinals_client.ts:396-457 (helper)Core utility in OrdinalsClient that performs witness decoding for inscriptions. Called by handler with 'file' option. Extracts content, saves images to cache dir, returns formatted strings (paths or data URIs).async decodeWitness(txid: string, formatImageOption: FormatImageOption = "base64"): Promise<string[]> { try { logger.debug(`Decoding witness (Ordinals) for transaction: ${txid}, formatImageOption=${formatImageOption}`); // Check the cache first const cached = this.transactionCache.get(txid); let extractedOrdinals: ExtractedOrdinal[] = []; if (cached) { logger.debug(`Using cached ordinals data for tx ${txid}`); extractedOrdinals = cached.extractedOrdinals; } else { // If not in cache, we need to retrieve the transaction first // getTransaction will extract all inscriptions and put them in cache await this.getTransaction(txid, false); // Now we can get the data from the cache const newCached = this.transactionCache.get(txid); if (newCached) { extractedOrdinals = newCached.extractedOrdinals; } else { logger.warn(`Transaction ${txid} not found in cache after getTransaction call`); } } // Format and return the found inscriptions const decodedData: string[] = []; if (extractedOrdinals.length === 0) { logger.info(`No Ordinal inscriptions found in transaction ${txid}`); return decodedData; } // Convert each extracted inscription to the requested format extractedOrdinals.forEach((ordinal, index) => { const formattedContent = this.formatInscriptionContent( ordinal.contentType, ordinal.content, formatImageOption, txid, index ); if (formattedContent) { decodedData.push(formattedContent); } }); logger.info(`Successfully decoded ${decodedData.length} ordinal inscription(s) from tx ${txid}`); return decodedData; } catch (error: unknown) { logger.error({ error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, txid }, "Failed to decode witness data"); if (error instanceof BitcoinError) throw error; throw handleBlockchainError(error, "Witness Data Decode", txid); } }