apple-photos-mcp 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -22152,6 +22152,14 @@ async function checkDependencies() {
22152
22152
  }
22153
22153
  }
22154
22154
 
22155
+ // src/utils/permissionErrors.ts
22156
+ function looksLikePermissionError(message) {
22157
+ if (/not permitted|permission|full disk|denied|unable to open/i.test(message)) {
22158
+ return true;
22159
+ }
22160
+ return /error copying\b.*photos\.sqlite/is.test(message);
22161
+ }
22162
+
22155
22163
  // src/utils/exportPath.ts
22156
22164
  import { existsSync as existsSync2, realpathSync } from "node:fs";
22157
22165
  import { homedir } from "node:os";
@@ -22226,7 +22234,7 @@ function assertWritesEnabled(env = process.env) {
22226
22234
 
22227
22235
  // src/services/photosManager.ts
22228
22236
  function augmentPermissionError(message) {
22229
- if (/not permitted|permission|full disk|denied|unable to open/i.test(message)) {
22237
+ if (looksLikePermissionError(message)) {
22230
22238
  return `${message}
22231
22239
 
22232
22240
  This looks like a macOS permission issue: ${FDA_REMEDIATION}`;
@@ -22702,9 +22710,6 @@ function withErrorHandling(handler, prefix) {
22702
22710
  // src/tools/doctor.ts
22703
22711
  import { existsSync as existsSync4 } from "node:fs";
22704
22712
  var PHOTOS_APP_PATH = "/System/Applications/Photos.app";
22705
- function looksLikePermissionError(message) {
22706
- return /not permitted|permission|full disk|denied|unable to open/i.test(message);
22707
- }
22708
22713
  async function runDoctor(manager2) {
22709
22714
  const checks = [];
22710
22715
  try {
@@ -69,11 +69,81 @@ level and tools fail with a permissions-flavored error. Typical messages:
69
69
 
70
70
  - `operation not permitted`
71
71
  - `unable to open database file`
72
+ - `Error copying …/Photos Library.photoslibrary/database/Photos.sqlite to /tmp/osxphotos_…/Photos.sqlite`
72
73
  - a generic "Library not found" / permission error from `health-check`
73
74
 
75
+ The `Error copying …/Photos.sqlite to /tmp/…` form is the most misleading: osxphotos
76
+ copies the live library database to a temp directory before opening it, and when
77
+ the host process lacks FDA, macOS denies the **read of the source** — surfaced as a
78
+ generic copy failure with no "permission" wording. It is still an FDA denial. (As of
79
+ v2.1.1 `doctor` classifies this message as a Full Disk Access failure and points here
80
+ rather than reporting "did not look permission-related".)
81
+
74
82
  These are macOS denying access, not a bug in the server. The fix is always the
75
83
  same: grant FDA to the host app and **fully restart it** (see above).
76
84
 
85
+ ### Gotcha: the "host app" for a Claude-spawned server may be a *nested* bundle
86
+
87
+ If you granted Full Disk Access to `/Applications/Claude.app` (or to your
88
+ terminal), fully restarted, even rebooted — and osxphotos-backed tools **still**
89
+ fail with `Error copying …/Photos.sqlite …` while the exact same
90
+ `osxphotos.PhotosDB()` call succeeds when you run the venv Python directly — the
91
+ grant almost certainly landed on the wrong identity.
92
+
93
+ macOS attributes a locally-spawned MCP subprocess's file access to the
94
+ **responsible process**, which is not always the app icon you clicked:
95
+
96
+ - **Claude Code (CLI/desktop Code tab)** spawns MCP servers under a **per-version
97
+ nested helper bundle**, not `/Applications/Claude.app`. Look for it at
98
+ `~/Library/Application Support/Claude/claude-code/<version>/claude.app`. This
99
+ bundle needs its **own** FDA grant, and it **silently resets on every Claude
100
+ auto-update** — the tell is a new, un-checked lowercase **"claude"** row
101
+ appearing in the Full Disk Access list.
102
+ - **A terminal-launched server** is attributed to the terminal app
103
+ (Terminal/iTerm/VS Code), not to `node`.
104
+
105
+ Fix it: open **System Settings → Privacy & Security → Full Disk Access**, look for
106
+ a **"claude"** entry (distinct from "Claude") or add the nested
107
+ `…/claude-code/<version>/claude.app` bundle via **+**, toggle it **on**, then
108
+ restart the MCP server (a fresh session, or quit/relaunch the host). If it worked
109
+ before and broke after an update, re-check this list first — the grant was reset,
110
+ not lost for good.
111
+
112
+ ### Still failing after granting every host app? Check whether TCC attributed it to `node` itself
113
+
114
+ Every fix above assumes responsibility climbs from the subprocess up to *some*
115
+ app — Claude Desktop, the nested Code bundle, or your terminal. On at least one
116
+ confirmed setup it didn't: TCC attributed the check directly to the `node`
117
+ binary, and none of the wrapper-app grants ever touched it, because
118
+ responsibility never climbed past `node` to see them.
119
+
120
+ Don't guess between the theories above — the macOS unified log is
121
+ authoritative. Reproduce the failure while streaming:
122
+
123
+ ```
124
+ log stream --style compact --predicate 'subsystem == "com.apple.TCC"' --info
125
+ ```
126
+
127
+ Look for a line like:
128
+
129
+ ```
130
+ Handling access request to kTCCServiceSystemPolicyAllFiles,
131
+ from Sub:{/usr/local/bin/node}
132
+ Resp:{TCCDProcess: identifier=node, pid=…, responsible_path=/usr/local/bin/node, binary_path=/usr/local/bin/node},
133
+ ReqResult(Auth Right: Denied …)
134
+ ```
135
+
136
+ If `Resp:` names `node` (not `claude.app`, not your terminal), grant FDA to the
137
+ **node binary itself**: **+** → ⌘⇧G → type the exact path from `responsible_path`
138
+ above (commonly `/usr/local/bin/node`, `/opt/homebrew/bin/node`, or a
139
+ pinned-runtime path) → toggle on → fully quit and relaunch the host app.
140
+
141
+ A stable, Developer-ID-signed Node build (check `codesign -dv $(which node)` —
142
+ look for a real `TeamIdentifier`, not `flags=0x2(adhoc)`) keeps this grant
143
+ across Node upgrades. An ad-hoc-signed build (common with some Homebrew
144
+ formulae) gets a new code identity on every rebuild, so the grant can need
145
+ re-doing after `brew upgrade`.
146
+
77
147
  ## Note: exporting iCloud-only originals also needs Photos automation
78
148
 
79
149
  Full Disk Access covers reading and querying the library, and exporting any
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-photos-mcp",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "MCP server for Apple Photos - query, search, export, and inspect the macOS Photos library via Claude and other AI assistants, plus opt-in album/metadata write tools (read-only by default)",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "scripts": {
75
75
  "setup": "bash scripts/setup.sh",
76
- "build": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --format=esm --outfile=build/index.js --banner:js=\"import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);\"",
76
+ "build": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=build/index.js --banner:js=\"import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);\"",
77
77
  "start": "node build/index.js",
78
78
  "dev": "tsc --watch",
79
79
  "lint": "eslint src",
@@ -81,11 +81,13 @@
81
81
  "format": "prettier --write src",
82
82
  "format:check": "prettier --check src",
83
83
  "typecheck": "tsc --noEmit",
84
+ "sync:skills": "node scripts/sync-skills.mjs",
84
85
  "test": "vitest run",
85
86
  "test:watch": "vitest",
86
87
  "test:coverage": "vitest run --coverage",
87
88
  "test:integration": "vitest run --config vitest.integration.config.ts",
88
89
  "test:all": "vitest run && vitest run --config vitest.integration.config.ts",
89
- "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin .agents/plugins codex .hermes-plugin .antigravity-plugin"
90
+ "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin .agents/plugins codex .hermes-plugin .antigravity-plugin",
91
+ "preinstall": "node -e \"const fs=require('fs');const ua=process.env.npm_config_user_agent||'';if(fs.existsSync('.git')&&!ua.startsWith('pnpm')){console.error('\\nThis repo uses pnpm. npm/yarn resolve dependencies off-lockfile and the committed bundle will mismatch CI.\\n\\n corepack enable && pnpm install --frozen-lockfile\\n');process.exit(1)}\""
90
92
  }
91
93
  }
package/requirements.txt CHANGED
@@ -1,5 +1,7 @@
1
- osxphotos>=0.69.0,<0.76
1
+ # Exact-pinned (2026-07-20) so the sidecar environment is reproducible and
2
+ # Dependabot's pip ecosystem can propose auditable bumps.
3
+ osxphotos==0.75.9
2
4
  # Direct dependency of the opt-in write tools (also an osxphotos transitive
3
5
  # dep). Pinned because photos_reader.py uses its script_loader.run_script for
4
- # bulk album-id calls — keep the floor/ceiling in step when bumping.
5
- photoscript>=0.5.3,<0.6
6
+ # bulk album-id calls — keep the two pins in step when bumping.
7
+ photoscript==0.5.3