image_management.pyโข4.04 kB
"""Image management tools for Obsidian MCP server."""
import base64
from typing import Optional, Union, Dict, Any
from fastmcp import Context, Image
from ..utils.filesystem import get_vault
from ..utils import validate_note_path, sanitize_path
from ..constants import ERROR_MESSAGES
async def read_image(
path: str,
include_metadata: bool = False,
max_width: int = 1600,
ctx: Optional[Context] = None
) -> Union[Image, Dict[str, Any]]:
"""
Read an image file from the Obsidian vault and return it as an Image object.
Use this tool when you need to retrieve image files from the vault. The image
is returned as an Image object that can be displayed directly in MCP clients.
Images larger than max_width are automatically resized to prevent memory issues.
Args:
path: Path to the image file relative to vault root (e.g., "attachments/screenshot.png")
include_metadata: Whether to include file metadata (returns dict with image and metadata)
max_width: Maximum width for automatic resizing in pixels (default: 1600)
ctx: MCP context for progress reporting
Returns:
If include_metadata is False: Image object for direct display
If include_metadata is True: Dictionary containing image object and metadata
Example:
>>> # For display only
>>> await read_image("images/diagram.png")
<Image object>
>>> # With metadata
>>> await read_image("images/diagram.png", include_metadata=True)
{
"image": <Image object>,
"path": "images/diagram.png",
"mime_type": "image/png",
"size": 1234,
"resized": true,
"dimensions": {
"original": {"width": 2400, "height": 1800},
"resized": {"width": 1200, "height": 900}
}
}
"""
# Validate path (reuse existing validation but allow image extensions)
if not path:
raise ValueError("Path cannot be empty")
# Check for common image extensions
valid_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.ico'}
ext = path.lower().split('.')[-1] if '.' in path else ''
if f'.{ext}' not in valid_extensions:
raise ValueError(f"Invalid image file extension. Supported: {', '.join(valid_extensions)}")
# Don't sanitize path for images - it adds .md extension
if ctx:
ctx.info(f"Reading image: {path}")
vault = get_vault()
try:
image_data = await vault.read_image(path, max_width=max_width)
except FileNotFoundError:
raise FileNotFoundError(ERROR_MESSAGES["note_not_found"].format(path=path))
# Convert base64 content back to bytes for Image object
image_bytes = base64.b64decode(image_data["content"])
# Extract format from mime type
mime_to_format = {
"image/png": "png",
"image/jpeg": "jpeg",
"image/jpg": "jpeg",
"image/gif": "gif",
"image/webp": "webp",
"image/svg+xml": "svg",
"image/bmp": "bmp",
"image/x-icon": "ico"
}
format_type = mime_to_format.get(image_data["mime_type"], "png")
if include_metadata:
# If metadata is requested, return both Image and metadata in a dict
result = {
"image": Image(data=image_bytes, format=format_type),
"path": image_data["path"],
"mime_type": image_data["mime_type"],
"size": image_data["size"]
}
if "original_size" in image_data:
result["original_size"] = image_data["original_size"]
if "resized" in image_data:
result["resized"] = image_data["resized"]
if "dimensions" in image_data:
result["dimensions"] = image_data["dimensions"]
return result
else:
# Return just the Image object for display
return Image(data=image_bytes, format=format_type)