Decision: Add Pillow as a Dependency for Inline-Image Size Handling¶
Date: 2026-08-07
Snapshot commit: branch feat/jay/issue-400 — see src/mcp_gee_sweet/tools/docs/images.py
Background¶
Issue #400: insert_inline_image (and its sibling image-insert paths — insert_local_images,
and the markdown/HTML image embedding shared by create_doc/create_doc_from_file/
write_doc_content) fails with Google's raw, opaque HttpError 400 "...The provided image is
too large." for any image over Google Docs' documented ~25-megapixel inline-image ceiling
(confirmed: https://developers.google.com/workspace/docs/api/how-tos/images). The ask covers
both a clear pre-validated error and an opt-in automatic downscale.
Reading real pixel dimensions and, for the opt-in path, actually resizing arbitrary PNG/JPEG/GIF image bytes requires a real image-decoding library. This repo had no such dependency before this issue.
Options Considered¶
Option A: Hand-rolled header parsing, no downscale¶
Parse PNG (IHDR chunk), JPEG (SOF markers), and GIF (logical screen descriptor) headers
by hand to read width/height without a new dependency. Covers the pre-validation half of
the ask (clear error naming the limit and the image's actual size) but not the auto-downscale
half — an actual resize requires decoding and re-encoding pixel data, which header parsing
can't do.
Pros: zero new dependency, smaller Docker image, no new CVE surface. Cons: only satisfies part of the confirmed scope (user explicitly asked for both the error path and the opt-in auto-downscale, not one or the other).
Option B: Add Pillow (chosen)¶
Use Pillow (PIL.Image) for both dimension reads and the downscale resize — one code path
instead of two, and no hand-rolled per-format header parsing to maintain.
Pros: covers the full confirmed scope with one library; ships as precompiled wheels for
all platforms this project targets (no C toolchain needed at install/build time, including in
the Docker image); mature, widely-used, actively maintained.
Cons: a new mandatory (not optional-extra) dependency — every install pulls it in, not just
callers who use auto_downscale.
Decision¶
Use Option B. Added Pillow>=11.0.0 to pyproject.toml's core dependencies (not an
optional extra) — confirmed with the user before adding it, given it affects every install's
dependency footprint and the Docker image, not just this one ticket. tools/docs/images.py
is the single module that imports it; every insertInlineImage call site goes through that
module rather than importing PIL directly.
Scope boundary this decision implies¶
Validation + auto-downscale only apply where a call site already has (or already fetches) the
image's own bytes or Drive-reported dimensions without new networking: a local file path, an
already-uploaded Drive file (drive_file_id / "drive:" reference). A bare http(s):// URI
is out of scope for both — fetching arbitrary external content just to validate it, or
re-hosting a downscaled copy somewhere Google can fetch it from, would be a materially bigger
behavior change than this fix warrants. Those sources instead get the minimum fallback from
the issue's own third ask-bullet: the raw HttpError is caught and its message rewritten to
name the known cause, without image-specific numbers.
When to Re-evaluate¶
If a future ticket wants uri-source validation/downscaling too (would need to add an HTTP
fetch and, for downscale, a re-upload/re-hosting step), that's new scope on top of this
decision, not an extension of it — revisit whether Pillow is still the right tool once that
shape is known, though it very likely still is.