Skip to main content
Glama

read_report_image

Read-onlyIdempotent

Retrieve a report image from an OpenROAD flow run, returning base64-encoded data and metadata. Specify platform, design, run slug, and image name to get the image content.

Instructions

Read a report image and return base64-encoded data with metadata.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
platformYes
designYes
run_slugYes
image_nameYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Main handler: ReadReportImageTool.execute() reads a .webp report image from disk, validates paths for security, checks file existence/size, loads/compresses the image, base64-encodes it, and returns a ReadImageResult with metadata.
    class ReadReportImageTool(BaseTool):
        """Tool for reading report images and returning base64-encoded data with metadata."""
    
        async def execute(self, platform: str, design: str, run_slug: str, image_name: str) -> str:
            """Read a specific report image and return base64-encoded data."""
            try:
                reports_base, run_path = _resolve_run_path(platform, design, run_slug)
                validate_path_segment(image_name, "image_name")
    
                if not image_name.endswith(".webp"):
                    raise ValidationError(f"Image filename must have .webp extension: {image_name}")
    
                if not run_path.exists():
                    logger.warning(f"Run slug not found: {run_slug}")
                    return self._format_result(
                        ReadImageResult(
                            error="RunSlugNotFound",
                            message=f"Run slug '{run_slug}' not found in {reports_base}. "
                            "Use list_report_images to see available runs.",
                        )
                    )
    
                image_path = run_path / image_name
    
                validate_safe_path_containment(image_path, run_path, "image file")
    
                if not image_path.exists():
                    logger.warning(f"Image not found: {image_name}")
                    available_images = [f.name for f in run_path.rglob("*.webp")]
                    return self._format_result(
                        ReadImageResult(
                            error="ImageNotFound",
                            message=f"Image '{image_name}' not found in {run_path}. "
                            f"Available images: {', '.join(available_images) if available_images else 'none'}. "
                            "Use list_report_images to see all available images.",
                        )
                    )
    
                if not image_path.is_file():
                    logger.warning(f"Image path is not a file: {image_path}")
                    return self._format_result(
                        ReadImageResult(
                            error="InvalidImagePath",
                            message=f"Image path {image_name} is not a regular file.",
                        )
                    )
                file_size_mb = image_path.stat().st_size / (1024 * 1024)
                if file_size_mb > MAX_IMAGE_SIZE_MB:
                    logger.warning(f"Image too large: {file_size_mb:.2f}MB > {MAX_IMAGE_SIZE_MB}MB")
                    return self._format_result(
                        ReadImageResult(
                            error="FileTooLarge",
                            message=f"Image size ({file_size_mb:.2f}MB) exceeds maximum "
                            f"allowed size ({MAX_IMAGE_SIZE_MB}MB).",
                        )
                    )
    
                (
                    image_bytes,
                    compression_applied,
                    original_size,
                    compressed_size,
                    original_width,
                    original_height,
                    width,
                    height,
                ) = load_and_compress_image(image_path)
    
                image_data_b64 = base64.b64encode(image_bytes).decode("utf-8")
    
                stat = image_path.stat()
                file_stage, file_type = classify_image_type(image_name)
    
                compression_ratio = compressed_size / original_size if compression_applied else None
    
                metadata = ImageMetadata(
                    filename=image_name,
                    format="webp",
                    size_bytes=compressed_size,
                    width=width,
                    height=height,
                    modified_time=datetime.fromtimestamp(stat.st_mtime).isoformat(),
                    stage=file_stage,
                    type=file_type,
                    compression_applied=compression_applied,
                    original_size_bytes=original_size if compression_applied else None,
                    original_width=original_width if compression_applied else None,
                    original_height=original_height if compression_applied else None,
                    compression_ratio=compression_ratio,
                )
    
                result = ReadImageResult(
                    image_data=image_data_b64,
                    metadata=metadata,
                )
    
                return self._format_result(result)
            except ValidationError as e:
                return self._format_result(ReadImageResult(error=type(e).__name__, message=str(e)))
            except Exception as e:
                logger.exception(f"Failed to read report image: {e}")
                return self._format_result(
                    ReadImageResult(
                        error="UnexpectedError",
                        message=f"Failed to read report image: {str(e)}",
                    )
                )
  • Tool instance creation: read_report_image_tool = ReadReportImageTool(manager) instantiates the tool with the shared OpenROADManager.
    # Initialize report image tool instances
    list_report_images_tool = ListReportImagesTool(manager)
    read_report_image_tool = ReadReportImageTool(manager)
  • Registration: The read_report_image function is decorated with @mcp.tool(...) and delegates to read_report_image_tool.execute(). This is the FastMCP tool registration entry point.
    @mcp.tool(
        annotations=ToolAnnotations(
            readOnlyHint=True,
            destructiveHint=False,
            idempotentHint=True,
            openWorldHint=False,
        )
    )
    async def read_report_image(platform: str, design: str, run_slug: str, image_name: str) -> str:
        """Read a report image and return base64-encoded data with metadata."""
        return await read_report_image_tool.execute(platform, design, run_slug, image_name)
  • Schema: ReadImageResult model defines the output shape with fields image_data (base64 str or None), metadata (ImageMetadata or None), and message (str or None).
    class ReadImageResult(BaseResult):
        """Result from reading a report image."""
    
        image_data: str | None = None
        metadata: ImageMetadata | None = None
        message: str | None = None
  • Schema: ImageMetadata model defines the metadata fields returned alongside the image data, including filename, dimensions, compression info, and stage/type classification.
    class ImageMetadata(BaseModel):
        """Metadata for a report image."""
    
        filename: str
        format: str
        size_bytes: int
        width: int | None = None
        height: int | None = None
        modified_time: str
        stage: str
        type: str
        compression_applied: bool = False
        original_size_bytes: int | None = None
        original_width: int | None = None
        original_height: int | None = None
        compression_ratio: float | None = None
Behavior3/5

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

Annotations already indicate readOnly, destructiveHint false, and idempotent true. The description adds that output is base64 with metadata, but does not disclose other traits like file size limits or encoding details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single sentence, 12 words, front-loaded with the core action and output. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 4 required parameters with no descriptions and the presence of an output schema, the description is too minimal. It does not help the agent understand how to fill parameters or what to expect beyond base64.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description must compensate but fails to explain any of the 4 required parameters (platform, design, run_slug, image_name). It only hints they are identifiers.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reads a report image and returns base64-encoded data with metadata. It distinguishes from the sibling 'list_report_images' which likely only lists names.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives like 'list_report_images' or potential prerequisites. The agent is left to infer usage context.

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/luarss/openroad-mcp'

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