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:
2026-04-15 06:39:57 +02:00
parent 4c6de3bfc7
commit 4430807b55
6 changed files with 157 additions and 12 deletions
@@ -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():