You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
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.16 → 1.1.18, 2.1.3 → 2.1.4, and both 5.0.7 and 5.0.8 → 5.0.9. Three separate majors are reachable through the dev toolchain (eslint → minimatch@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.