Skip to main content
Glama

read_content

Retrieve raw file content from your local knowledge base to access stored information and data within your semantic graph system.

Instructions

Read a file's raw content by path or permalink

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYes
projectNo

Implementation Reference

  • Main handler function for the read_content MCP tool. Handles file reading for text, images (with optimization and resizing), and binary files, including path validation, entity resolution, and appropriate content formatting.
    @mcp.tool(description="Read a file's raw content by path or permalink")
    async def read_content(
        path: str, project: Optional[str] = None, context: Context | None = None
    ) -> dict:
        """Read a file's raw content by path or permalink.
    
        This tool provides direct access to file content in the knowledge base,
        handling different file types appropriately. Uses stateless architecture -
        project parameter optional with server resolution.
    
        Supported file types:
        - Text files (markdown, code, etc.) are returned as plain text
        - Images are automatically resized/optimized for display
        - Other binary files are returned as base64 if below size limits
    
        Args:
            path: The path or permalink to the file. Can be:
                - A regular file path (docs/example.md)
                - A memory URL (memory://docs/example)
                - A permalink (docs/example)
            project: Project name to read from. Optional - server will resolve using hierarchy.
                    If unknown, use list_memory_projects() to discover available projects.
            context: Optional FastMCP context for performance caching.
    
        Returns:
            A dictionary with the file content and metadata:
            - For text: {"type": "text", "text": "content", "content_type": "text/markdown", "encoding": "utf-8"}
            - For images: {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": "base64_data"}}
            - For other files: {"type": "document", "source": {"type": "base64", "media_type": "content_type", "data": "base64_data"}}
            - For errors: {"type": "error", "error": "error message"}
    
        Examples:
            # Read a markdown file
            result = await read_content("docs/project-specs.md")
    
            # Read an image
            image_data = await read_content("assets/diagram.png")
    
            # Read using memory URL
            content = await read_content("memory://docs/architecture")
    
            # Read configuration file
            config = await read_content("config/settings.json")
    
            # Explicit project specification
            result = await read_content("docs/project-specs.md", project="my-project")
    
        Raises:
            HTTPError: If project doesn't exist or is inaccessible
            SecurityError: If path attempts path traversal
        """
        track_mcp_tool("read_content")
        logger.info("Reading file", path=path, project=project)
    
        async with get_client() as client:
            active_project = await get_active_project(client, project, context)
    
            url = memory_url_path(path)
    
            # Validate path to prevent path traversal attacks
            project_path = active_project.home
            if not validate_project_path(url, project_path):
                logger.warning(
                    "Attempted path traversal attack blocked",
                    path=path,
                    url=url,
                    project=active_project.name,
                )
                return {
                    "type": "error",
                    "error": f"Path '{path}' is not allowed - paths must stay within project boundaries",
                }
    
            # Resolve path to entity ID
            try:
                entity_id = await resolve_entity_id(client, active_project.external_id, url)
            except ToolError:
                # Convert resolution errors to "Resource not found" for consistency
                raise ToolError(f"Resource not found: {url}")
    
            # Call the v2 resource endpoint
            response = await call_get(client, f"/v2/projects/{active_project.external_id}/resource/{entity_id}")
            content_type = response.headers.get("content-type", "application/octet-stream")
            content_length = int(response.headers.get("content-length", 0))
    
            logger.debug("Resource metadata", content_type=content_type, size=content_length, path=path)
    
            # Handle text or json
            if content_type.startswith("text/") or content_type == "application/json":
                logger.debug("Processing text resource")
                return {
                    "type": "text",
                    "text": response.text,
                    "content_type": content_type,
                    "encoding": "utf-8",
                }
    
            # Handle images
            elif content_type.startswith("image/"):
                logger.debug("Processing image")
                img = PILImage.open(io.BytesIO(response.content))
                img_bytes = optimize_image(img, content_length)
    
                return {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/jpeg",
                        "data": base64.b64encode(img_bytes).decode("utf-8"),
                    },
                }
    
            # Handle other file types
            else:
                logger.debug(f"Processing binary resource content_type {content_type}")
                if content_length > 350000:  # pragma: no cover
                    logger.warning("Document too large for response", size=content_length)
                    return {
                        "type": "error",
                        "error": f"Document size {content_length} bytes exceeds maximum allowed size",
                    }
                return {
                    "type": "document",
                    "source": {
                        "type": "base64",
                        "media_type": content_type,
                        "data": base64.b64encode(response.content).decode("utf-8"),
                    },
                }
  • Import statement that registers the read_content tool by importing the decorated function, as part of all MCP tools registration.
    from basic_memory.mcp.tools.delete_note import delete_note
    from basic_memory.mcp.tools.read_content import read_content
  • Helper function for image optimization used by read_content to resize and compress images to fit within size limits.
    def optimize_image(img, content_length, max_output_bytes=350000):
        """Iteratively optimize image with aggressive size reduction"""
        stats = {
            "dimensions": {"width": img.width, "height": img.height},
            "mode": img.mode,
            "estimated_memory": (img.width * img.height * len(img.getbands())),
        }
    
        initial_quality, initial_size = calculate_target_params(content_length)
    
        logger.debug(
            "Starting optimization",
            image_stats=stats,
            content_length=content_length,
            initial_quality=initial_quality,
            initial_size=initial_size,
            max_output_bytes=max_output_bytes,
        )
    
        quality = initial_quality
        size = initial_size
    
        # Convert to RGB if needed
        if img.mode in ("RGBA", "LA") or (img.mode == "P" and "transparency" in img.info):
            img = img.convert("RGB")
            logger.debug("Converted to RGB mode")
    
        iteration = 0
        min_size = 300  # Absolute minimum size
        min_quality = 20  # Absolute minimum quality
    
        while True:
            iteration += 1
            buf = io.BytesIO()
            resized = resize_image(img, size)
    
            resized.save(
                buf,
                format="JPEG",
                quality=quality,
                optimize=True,
                progressive=True,
                subsampling="4:2:0",
            )
    
            output_size = buf.getbuffer().nbytes
            reduction_ratio = output_size / content_length
    
            logger.debug(
                "Optimization attempt",
                iteration=iteration,
                quality=quality,
                size=size,
                output_bytes=output_size,
                target_bytes=max_output_bytes,
                reduction_ratio=f"{reduction_ratio:.2f}",
            )
    
            if output_size < max_output_bytes:
                logger.info(
                    "Image optimization complete",
                    final_size=output_size,
                    quality=quality,
                    dimensions={"width": resized.width, "height": resized.height},
                    reduction_ratio=f"{reduction_ratio:.2f}",
                )
                return buf.getvalue()
    
            # Very aggressive reduction for large files
            if content_length > 2000000:  # 2MB+   # pragma: no cover
                quality = max(min_quality, quality - 20)
                size = max(min_size, int(size * 0.6))
            elif content_length > 1000000:  # 1MB+ # pragma: no cover
                quality = max(min_quality, quality - 15)
                size = max(min_size, int(size * 0.7))
            else:
                quality = max(min_quality, quality - 10)  # pragma: no cover
                size = max(min_size, int(size * 0.8))  # pragma: no cover
    
            logger.debug("Reducing parameters", new_quality=quality, new_size=size)  # pragma: no cover
    
            # If we've hit minimum values and still too big
            if quality <= min_quality and size <= min_size:  # pragma: no cover
                logger.warning(
                    "Reached minimum parameters",
                    final_size=output_size,
                    over_limit_by=output_size - max_output_bytes,
                )
                return buf.getvalue()
  • Helper function to resize images while maintaining aspect ratio.
    def resize_image(img, max_size):
        """Resize image maintaining aspect ratio"""
        original_dimensions = {"width": img.width, "height": img.height}
    
        if img.width > max_size or img.height > max_size:
            ratio = min(max_size / img.width, max_size / img.height)
            new_size = (int(img.width * ratio), int(img.height * ratio))
            logger.debug("Resizing image", original=original_dimensions, target=new_size, ratio=ratio)
            return img.resize(new_size, PILImage.Resampling.LANCZOS)
    
        logger.debug("No resize needed", dimensions=original_dimensions)
        return img
  • Helper function to calculate initial optimization parameters based on file size.
    def calculate_target_params(content_length):
        """Calculate initial quality and size based on input file size"""
        target_size = 350000  # Reduced target for more safety margin
        ratio = content_length / target_size
    
        logger.debug(
            "Calculating target parameters",
            content_length=content_length,
            ratio=ratio,
            target_size=target_size,
        )
    
        if ratio > 4:
            # Very large images - start very aggressive
            return 50, 600  # Lower initial quality and size
        elif ratio > 2:
            return 60, 800
        else:
            return 70, 1000

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/basicmachines-co/basic-memory'

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