fix: get_thumbnail size limits, default=small, quality quirk docs (v0.3.5)
- Default size changed large → small (avoids MCP buffer overflows) - Hard limit: return Error: when thumbnail exceeds ~2 MB base64 (1.5 MB raw) - Soft limit: add "warning" field to JSON when thumbnail exceeds ~500 KB base64 (375 KB raw), advising to use size='small' - Constants _THUMB_ABORT_BYTES / _THUMB_WARN_BYTES moved to module level - 6 new tests for size cap/warning/default/DSM-error paths (113 total) - SPEC.md: document quality-ignored quirk, size ranges, soft+hard limits - CLAUDE.md: DSM Quirks entry for Thumb quality/size behaviour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,11 @@ _VALID_SORT_DIR = frozenset({"asc", "desc"})
|
||||
# Cap on items returned by list_dir — DSM hard limit is 10000, we enforce lower.
|
||||
_MAX_LIMIT = 500
|
||||
|
||||
# get_thumbnail: abort if raw bytes exceed this (≈2 MB base64 after encoding).
|
||||
_THUMB_ABORT_BYTES = 1_500_000
|
||||
# get_thumbnail: include a warning field if raw bytes exceed this (≈500 KB base64).
|
||||
_THUMB_WARN_BYTES = 375_000
|
||||
|
||||
|
||||
def _fmt_size(size: int | None) -> str:
|
||||
"""Format a byte count as a human-readable string."""
|
||||
@@ -1126,8 +1131,8 @@ def register_filestation(
|
||||
# ── thumbnail + favorites tools ───────────────────────────────────────
|
||||
|
||||
@mcp.tool()
|
||||
async def get_thumbnail(path: str, size: str = "large"):
|
||||
"""Fetch a thumbnail for an image. Returns JSON: filename, size_bytes, content_base64."""
|
||||
async def get_thumbnail(path: str, size: str = "small"):
|
||||
"""Fetch a thumbnail for an image/video. Returns JSON with filename and base64 content."""
|
||||
import base64
|
||||
import json as _json
|
||||
|
||||
@@ -1142,14 +1147,32 @@ def register_filestation(
|
||||
except SynologyError as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
raw_len = len(img_bytes)
|
||||
|
||||
# Hard limit: refuse to return a payload that would exceed ~2 MB base64.
|
||||
if raw_len > _THUMB_ABORT_BYTES:
|
||||
b64_kb = raw_len * 4 // 3 // 1024
|
||||
return (
|
||||
f"Error: Thumbnail too large ({b64_kb} KB base64) — "
|
||||
f"use size='small' to get a smaller version."
|
||||
)
|
||||
|
||||
filename = path.rsplit("/", 1)[-1]
|
||||
return _json.dumps(
|
||||
{
|
||||
"filename": filename,
|
||||
"size_bytes": len(img_bytes),
|
||||
"content_base64": base64.b64encode(img_bytes).decode(),
|
||||
}
|
||||
)
|
||||
payload: dict = {
|
||||
"filename": filename,
|
||||
"size_bytes": raw_len,
|
||||
"content_base64": base64.b64encode(img_bytes).decode(),
|
||||
}
|
||||
|
||||
# Soft warning: note large payload without refusing it.
|
||||
if raw_len > _THUMB_WARN_BYTES:
|
||||
b64_kb = raw_len * 4 // 3 // 1024
|
||||
payload["warning"] = (
|
||||
f"Thumbnail is large ({b64_kb} KB base64). "
|
||||
"Consider using size='small' for faster responses."
|
||||
)
|
||||
|
||||
return _json.dumps(payload)
|
||||
|
||||
@mcp.tool()
|
||||
async def list_favorites():
|
||||
|
||||
Reference in New Issue
Block a user