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

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

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
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the tool reads raw content but doesn't mention permissions, rate limits, error handling, or output format. This is a significant gap for a read operation that could involve file access constraints.

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?

The description is a single, efficient sentence with zero waste. It is front-loaded with the core purpose and includes a useful detail ('by path or permalink'), making it appropriately sized and easy to parse.

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

Completeness3/5

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

Given the tool's moderate complexity (2 parameters, no annotations, but has an output schema), the description is incomplete. It covers the basic purpose but lacks usage guidelines, parameter details, and behavioral context. The output schema mitigates some gaps, but overall it's minimally viable with clear deficiencies.

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?

Schema description coverage is 0%, so the description must compensate but only mentions 'path or permalink' without explaining the 'project' parameter. It adds minimal meaning beyond the schema, failing to clarify parameter roles or usage, which is inadequate given the coverage gap.

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 action ('Read') and resource ('a file's raw content'), specifying it can be done 'by path or permalink'. It distinguishes from siblings like 'read_note' or 'view_note' by focusing on raw file content rather than notes, though it doesn't explicitly compare them.

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 is provided on when to use this tool versus alternatives like 'read_note', 'view_note', or 'fetch'. The description implies usage for reading files but doesn't specify contexts, prerequisites, or exclusions, leaving the agent to infer based on tool names alone.

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

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