Skip to content

v2.1.6

Choose a tag to compare

@github-actions github-actions released this 04 Aug 02:28
f0c6211

Added

  • version-guard now requires every version bump to be documented under a real ## [X.Y.Z] CHANGELOG heading. The guard already refused a bump to a version that was already on npm, but it never checked that the new version was described anywhere. Notes parked under ## [Unreleased] are orphaned the moment the release ships: nothing in the release path renames that section — the version lifecycle script only syncs the plugin manifests — so the published version goes out undocumented while its release notes sit under a heading still claiming they are unreleased. apple-notes-mcp shipped 2.6.10 and 2.6.11 exactly that way before this check existed. A bump whose version has no matching heading now hard-fails the PR, with an error naming the heading to add. Keep an empty ## [Unreleased] at the top regardless — dependabot-rebuild.yml hard-exits without that marker, and since it already inserts a real heading, bot PRs pass unchanged. The guard file lives in .github/, which does not ship, so this owes no version bump. Matches apple-mail-mcp#124, keeping the guard identical across the four servers. (#61)

Fixed

  • version-guard no longer demands a version bump for byte-neutral src/ changes. The shipped-bytes detector treated every non-test file under src/ as shipped, but TypeScript there reaches users only after esbuild inlines it into build/index.js — so a comment-, formatting- or type-only edit that leaves the committed bundle byte-identical was hard-blocked, leaving only two bad options: publish a release of literally nothing, or do not write the comment. src/**/*.ts is now a first-cause detector that implies a bump only when build/** changed too. The exemption is sound rather than merely convenient: ci.yml's "Verify committed build/ matches source" step rebuilds and requires git diff --quiet build/, and it runs in the test job whose test (22)/test (24) contexts are required by branch protection — so at merge time an unchanged build/ provably matches src/. Everything else under src/ (the verbatim-shipped *_reader.py sidecars), requirements.txt and build/** stay unconditional detectors, and the rule is written fail-safe: only .ts counts as bundle-only, so any new file type under src/ still requires a bump.
  • Timeout errors no longer point at an environment variable that cannot help. APPLE_PHOTOS_MCP_TIMEOUT sets only the default sidecar budget — ten tools pass an explicit timeoutMs that runPhotosReader uses in preference to it (get-selected-photos 2 min; find-duplicates, create-album, add-to-album, set-photo-metadata, set-keywords, set-photo-date 5 min; remove-from-album, import-photos 10 min; export 30 min). The single shared timeout string named the variable unconditionally, so a find-duplicates timeout on a big library told the user to raise a setting that is inert for that call: they raise it, nothing changes, and no doc admitted those budgets are fixed. The message now branches — the default path keeps "Raise APPLE_PHOTOS_MCP_TIMEOUT (ms)…", a fixed-budget call instead says its timeout is fixed at Nms and that the variable does not apply — and README, CLAUDE.md and docs/LIMITATIONS.md list which tools carry which budget.
  • The "Python 3 not found on PATH" setup error now names the doctor tool, matching every other setup-failure string in the server (setupHint(), the missing-photoscript error, the write-gate error, the Full-Disk-Access remediation) and the invariant stated in src/utils/docsUrls.ts. It is the one first-run failure that ended with a docs URL alone, steering the user toward a manual install guess instead of the tool that distinguishes a Python problem from an osxphotos or Full-Disk-Access one. doctor's own copy of the string is deliberately left alone — a doctor pointer inside doctor is circular.

Documentation

  • Retired the pre-2.0 "read-only except export" model from every surface that still carried it. Seven opt-in tools have mutated the Photos library since 2.1.0, but export's own MCP Safety: line still called itself "the only side-effecting tool", and so did src/utils/exportPath.ts's module docstring, CLAUDE.md's behavioral-contract bullet, SECURITY.md's posture bullet and CONTRIBUTING.md's sidecar guidelines. Each is now scoped to what remains true — export is the only tool that writes files outside the library — and points at the APPLE_PHOTOS_MCP_ENABLE_WRITES gate. Swept with them: README's "All five tools below" heading a section documenting seven; docs/WRITE-BACKEND.md's Writes row (missing set-photo-date and import-photos) and its cost argument weighed against "five opt-in tools"; README's doctor entry listing four checks when doctor has run six since 2.0.0; docs/FULL-DISK-ACCESS.md's "no tool works … every tool ultimately reads the SQLite database" (the write path needs macOS Automation, not FDA — a -1743 failure is not an FDA problem) and its three-item "full setup diagnostic"; SECURITY.md's Supported Versions table still covering only 1.x.x; CLAUDE.md's "Every tool accepts an optional library path" (ten of the twenty-one do not, and the same file said so 47 lines earlier); the find-duplicates remediation that routed quarantining through Photos.app by hand rather than create-album + add-to-album; and the Antigravity marketplace blurb still advertising the Hermes packaging removed in 2.1.5.
  • Documented three limits and one failure mode that only existed in the source. get-thumbnail's 8 MB response cap (derivative selection pre-filters on it, but a sips-rendered fallback from a very high-resolution original can still exceed it); the schema maxima on find-duplicates.limit (10000), list-keywords.limit / list-persons.limit (100000), set-photo-metadata's title (255) and description (2048), and set-keywords' per-keyword 255; and remove-from-album's apple-photos-mcp-tmp-<hex> scratch album — a rebuild killed part-way (its fixed 10-minute budget expiring while copying a very large album) leaves the original album and every photo intact, but can strand that scratch album, one per interrupted attempt, since the name is collision-checked per call.
  • docs/WRITE-BACKEND.md now lists get-selected-photos as the one photoscript-backed read, so the Path→Backend table accounts for every AppleScript caller rather than implying the split is purely reads-vs-writes.
  • README's link to docs/WRITE-BACKEND.md is now an absolute GitHub URL, matching every other cross-file link in the file — it was the last relative one, and relative links do not resolve on npmjs.com.
  • Recorded the export progress contract as a deliberate decision. The terminal done === total progress notification is best effort: an MCP client deletes a request's progress handler when the response arrives and discards later notifications for that token, so a notification emitted immediately before the result is intermittently thrown away (measured: server sent four, client delivered one). Per-photo notifications are the progress signal; the tool result's exportedCount / skippedCount is the completion signal. The notification is still emitted on purpose — nothing depends on it, it usually arrives, and removing it would bake a workaround for a client-side lifecycle bug into this server's public behaviour. Noted at the emission site and in CLAUDE.md so it is not "cleaned up" later.

Security

  • Floored all three dev-only brace-expansion majors on their complete fixes for GHSA-mh99-v99m-4gvg / CVE-2026-14257 (high)1.1.161.1.18, 2.1.32.1.4, and both 5.0.7 and 5.0.85.0.9. Three separate majors are reachable through the dev toolchain (eslintminimatch@3 on v1, minimatch@9 on v2, minimatch@10 on v5), and they are not API-compatible — minimatch 3 requires the v1 CommonJS API, so a single floor spanning them fails with expand is not a function. Each major therefore carries its own two-sided floor; the bounds must be two-sided because a bare <5.0.9 also matches 1.1.18 and 2.1.4 under semver and would drag the CommonJS path onto the v5 ESM API. The advisory's own first-patched versions (1.1.17 / 2.1.3 / 5.0.8) are not sufficient: they bound the accumulator in combine but never thread maxLength into expandSequence, so the sequence path ({1..N}, {a..z..k}) stays capped only by item count and a padded sequence still materialises ~100,000 intermediate strings before the outer bound truncates (measured 4,606 ms / 176 MB RSS on 1.1.17 vs 9 ms / 61 MB on 1.1.18, identical final output). Two of the four paths resolved here (1.1.16, 5.0.7) were below even the advisory's floor. Adopted only after every release cleared this repo's 24-hour minimumReleaseAge gate, with no minimumReleaseAgeExclude carve-out and no audit suppression — pnpm audit will keep reporting the advisory until GitHub's metadata (which still lists 5.0.8 as first-patched, and so marks the entire v1 line vulnerable under semver) catches up. Dev toolchain only: brace-expansion is not in the shipped bundle, so the published package is unaffected, the committed bundle is byte-identical, and no version bump is owed. Matches apple-mail-mcp#123 — thanks to @jjoanna2-debug for the original finding.