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