Skip to main content
Glama

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_path as 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):

<!-- Attempting display using local path. Note: May not render in standard browsers due to security restrictions. -->
<img src="[local_file_path from show_ordinals]" alt="Ordinal Image Inscription" style="max-width: 100%; height: auto;" />

### ๐Ÿ“Š Technical Details  
* **MIME Type:** [MIME type]
* **Size:** [Size]
* **Transaction ID:** [Tx ID]
* **Status:** [Status]
* **Inscription Input:** [Input]
* **Local Path (for analysis):** `[local_file_path from show_ordinals]`
* [Other available details]

๐Ÿ”น 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):

### ๐Ÿ“ Detailed Image Analysis (Result from '[Executed Tool Name]')

[Extracted OCR text, visual description, or failure message from the tool ONLY]

โš  No hallucinations. No AI-generated assumptions.


๐Ÿ“Œ FALLBACK MECHANISM (IF OCR TOOLS FAIL)

  • If BOTH image_processor AND vision fail, 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

TableJSON Schema
NameRequiredDescriptionDefault
txidYesThe unique 64-character hexadecimal identifier of the Bitcoin transaction to inspect for Ordinals. Example: 'f1d2d3...a8b9c0'.
inscription_indexNoOptional. The specific index (starting from 0) of the inscription to retrieve within the transaction, if there are multiple.
content_type_filterNoOptional. Filter inscriptions by content type, e.g., 'image/png', 'text/plain', 'application/json'.

Implementation Reference

  • 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[],
        };
      }
    }
  • 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"),
    });
  • 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,
      ],
    }));
  • 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;
      }
    }
  • 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);
      }
    }
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/Laz1mov/mcp-inscription'

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