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 +9 -4
- package/docs/FULL-DISK-ACCESS.md +70 -0
- package/package.json +5 -3
- package/requirements.txt +5 -3
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 (
|
|
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 {
|
package/docs/FULL-DISK-ACCESS.md
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
5
|
-
photoscript
|
|
6
|
+
# bulk album-id calls — keep the two pins in step when bumping.
|
|
7
|
+
photoscript==0.5.3
|