loading…
Search for a command to run...
loading…
Enables AI assistants to query and export from the macOS Apple Photos library using natural language, backed by osxphotos.
Enables AI assistants to query and export from the macOS Apple Photos library using natural language, backed by osxphotos.
A Model Context Protocol (MCP) server that enables AI assistants like Claude to query and export from the macOS Apple Photos library, backed by the osxphotos library.
Read-only against the Photos library. Exports write files to a directory you choose, but the library itself is never modified.
This server acts as a bridge between AI assistants and Apple Photos. Once configured, you can ask Claude (or any MCP-compatible AI) to:
The AI assistant communicates with this server, which uses osxphotos to read the Photos library SQLite database directly. All data stays local on your machine.
If you're using Claude Code (in Terminal or VS Code), just ask Claude to install it:
Install the sweetrb/apple-photos-mcp MCP server so you can help me query my Apple Photos library
Claude will handle the installation and configuration automatically. After install, you'll need to install osxphotos (Python) and grant Full Disk Access — see Requirements below.
Install as a Claude Code plugin for automatic configuration and enhanced AI behavior:
/plugin marketplace add sweetrb/apple-photos-mcp
/plugin install apple-photos
This method also installs a skill that teaches Claude when and how to use Apple Photos effectively.
1. Install the server:
npm install -g github:sweetrb/apple-photos-mcp
2. Install osxphotos (the Python library this server depends on):
pip3 install osxphotos
Or, if you cloned the repo, run npm run setup to create a project-local Python venv with osxphotos pre-installed.
3. Add to Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"apple-photos": {
"command": "npx",
"args": ["apple-photos-mcp"]
}
}
}
4. Grant Full Disk Access to the app hosting the MCP server (Claude Desktop, Terminal, VS Code, etc.) — see Full Disk Access below.
5. Restart Claude Desktop and start using natural language:
"How many photos are in my library?"
pip3 install osxphotos or via npm run setup if installing from source.~/Pictures/Photos Library.photoslibrary)| Feature | Description |
|---|---|
| Library Stats | Total counts of photos, movies, albums, folders, keywords, persons |
| Query | Search by date range, album, keyword, person, favorite/hidden flags, photo/movie type, title/description substring |
| Photo Details | Full metadata for one photo: dimensions, location, place, EXIF-derived flags (HDR, live, portrait, panorama, raw, edited, etc.) |
| List Albums | All albums with their folder paths and photo counts |
| List Folders | All folders with parent and album/subfolder counts |
| List Keywords | Keywords sorted by usage count |
| List Persons | People detected by Photos face recognition, sorted by photo count |
| Feature | Description |
|---|---|
| Export Originals | Copy original photos to a destination directory |
| Export Edited | Copy the edited version instead of the original |
| Live Photos | Optionally include the live-photo video alongside the still |
| Raw Files | Optionally include the raw (NEF, CR2, etc.) sidecar |
| Multi-photo Export | Export multiple UUIDs in a single call |
| Auto iCloud Download | If an original isn't on disk, export falls back to Photos.app to download it on demand — no extra parameter needed |
| Feature | Description |
|---|---|
| Health Check | Verify osxphotos is installed and the library can be opened |
This section documents all available tools. AI agents should use these tool names and parameters exactly as specified.
health-checkVerify osxphotos is installed and the Photos library can be opened.
Parameters: None
Returns: osxphotos version, library path, and total photo count — or an error if the library is inaccessible.
library-infoHigh-level stats about the Photos library.
| Parameter | Type | Required | Description |
|---|---|---|---|
library |
string | No | Path to a non-default .photoslibrary (defaults to system library) |
Returns: Library path, Photos DB version, Photos.app version, counts of photos / movies / albums / folders / keywords / persons.
querySearch the library with combinable filters. Returns photo summaries with UUIDs — use get-photo for full details on a specific match.
| Parameter | Type | Required | Description |
|---|---|---|---|
uuid |
string[] | No | Specific UUIDs to fetch |
album |
string[] | No | Album name(s); ANY-match |
keyword |
string[] | No | Keyword(s); ANY-match |
person |
string[] | No | Person name(s); ANY-match |
fromDate |
string | No | ISO 8601 lower bound on photo date (e.g. "2025-06-01") |
toDate |
string | No | ISO 8601 upper bound on photo date |
favorite |
boolean | No | Only favorites |
notFavorite |
boolean | No | Exclude favorites |
hidden |
boolean | No | Only hidden photos |
notHidden |
boolean | No | Exclude hidden photos (default behavior) |
photos |
boolean | No | Include still photos |
movies |
boolean | No | Include movies |
title |
string | No | Substring match on title |
description |
string | No | Substring match on description |
limit |
number | No | Cap the number of results |
library |
string | No | Path to a non-default .photoslibrary |
Example - Recent favorites of Sarah:
{
"person": ["Sarah"],
"favorite": true,
"fromDate": "2025-06-01",
"limit": 50
}
Example - Sunset keyword across two albums:
{
"keyword": ["sunset"],
"album": ["Vacation 2024", "Beach Trips"]
}
Returns: Photo summaries (UUID, filename, date, dimensions, favorite/hidden flags, albums, keywords, persons).
get-photoGet full metadata for a single photo by UUID.
| Parameter | Type | Required | Description |
|---|---|---|---|
uuid |
string | Yes | Photo UUID |
library |
string | No | Path to a non-default .photoslibrary |
Example:
{
"uuid": "33AC0410-D367-43AE-A839-12C7EF482020"
}
Returns: All metadata for the photo: dimensions, original dimensions, dates (taken/added/modified), title, description, location (lat/lon), place (name/country), albums, keywords, persons, labels, type flags (HDR / live / raw / edited / portrait / panorama / selfie / screenshot / slow-mo / time-lapse / burst), file paths (original, edited, raw, live-photo video), file size, UTI.
list-albumsList all albums in the library.
| Parameter | Type | Required | Description |
|---|---|---|---|
library |
string | No | Path to a non-default .photoslibrary |
Returns: Each album's title, folder path, photo count, shared status, and UUID.
list-foldersList all folders in the library.
| Parameter | Type | Required | Description |
|---|---|---|---|
library |
string | No | Path to a non-default .photoslibrary |
Returns: Each folder's title, parent folder, album count, and subfolder count.
list-keywordsList keywords sorted by usage count.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
number | No | Cap to top-N keywords |
library |
string | No | Path to a non-default .photoslibrary |
Returns: Keywords with their photo counts, sorted descending.
list-personsList people detected by Photos face recognition, sorted by photo count.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
number | No | Cap to top-N persons |
library |
string | No | Path to a non-default .photoslibrary |
Returns: Persons with their photo counts, sorted descending. Unidentified faces appear as _UNKNOWN_.
exportExport one or more photos by UUID to a destination directory.
| Parameter | Type | Required | Description |
|---|---|---|---|
uuid |
string[] | Yes | Photo UUID(s) to export (non-empty) |
dest |
string | Yes | Destination directory (created if missing) |
edited |
boolean | No | Export the edited version instead of the original |
live |
boolean | No | Also export the live-photo video |
raw |
boolean | No | Also export the raw image |
overwrite |
boolean | No | Overwrite existing files at the destination |
library |
string | No | Path to a non-default .photoslibrary |
Example - Originals to a folder:
{
"uuid": ["33AC0410-...", "EEFCEF1D-..."],
"dest": "~/Desktop/exports"
}
Example - Edited versions plus raw and live-photo video:
{
"uuid": ["33AC0410-..."],
"dest": "~/Desktop/exports",
"edited": true,
"raw": true,
"live": true,
"overwrite": true
}
Returns: Destination path, count of files exported, count skipped, list of exported file paths, and any errors per UUID.
iCloud-only originals: If a photo's original isn't on disk (Photos is using "Optimize Mac Storage"), the export automatically falls back to Photos.app via AppleScript, which downloads the original on demand — same behavior as opening the photo in Photos. This is slower than a direct file copy; expect waits proportional to download size for large batches. Photos that genuinely can't be exported (e.g. edited=true requested but no edits exist) are still skipped with a per-UUID reason.
User: "How many photos do I have?"
AI: [calls library-info]
"You have 30,968 items: 30,435 photos and 533 movies across 46 albums..."
User: "Find my favorite sunset photos"
AI: [calls query with keyword=["sunset"], favorite=true]
"Found 12 favorite sunset photos. Here are the most recent..."
User: "Tell me about the first one"
AI: [calls get-photo with uuid="..."]
"Taken on 2025-09-14 at 19:47, in Big Sur..."
User: "Export all photos of Mollee from the beach to ~/Desktop/mollee-beach"
AI: [calls query with person=["Mollee"], keyword=["beach"]]
"Found 109 photos."
AI: [calls export with the UUIDs and dest="~/Desktop/mollee-beach"]
"Exported 109 files to ~/Desktop/mollee-beach."
User: "What are my top 10 keywords?"
AI: [calls list-keywords with limit=10]
"Photo Stream (1561), Mollee (109), beach (109), 2015 Feb Keweenaw..."
User: "Who appears most in my photos?"
AI: [calls list-persons with limit=10]
"Rita Sweet (29), Robert B Sweet (28), Jennifer Sweet (24)..."
By default, all operations use the system Photos library. To work with a different .photoslibrary:
User: "Show albums in my old archive at /Volumes/Archive/Photos.photoslibrary"
AI: [calls list-albums with library="/Volumes/Archive/Photos.photoslibrary"]
"32 albums in the archive..."
npm install -g github:sweetrb/apple-photos-mcp
pip3 install osxphotos
git clone https://github.com/sweetrb/apple-photos-mcp.git
cd apple-photos-mcp
npm install
npm run setup # creates ./venv and installs osxphotos
npm run build
If installed from source, use this configuration:
{
"mcpServers": {
"apple-photos": {
"command": "node",
"args": ["/path/to/apple-photos-mcp/build/index.js"]
}
}
}
The server prefers a project-local venv at ./venv/bin/python3 if present, and otherwise falls back to system python3. This means a global npm install works as long as osxphotos is on the system Python.
The Photos library SQLite database lives in a protected directory (~/Pictures/Photos Library.photoslibrary/database/). osxphotos reads this database directly — it does not go through Photos.app — so the host process needs Full Disk Access.
/Applications/Claude.app/Applications/Utilities/Terminal.app/Applications/Visual Studio Code.app/Applications/iTerm.appThe health-check tool will fail and report a permissions error. No tool will be able to open the library.
This package is a TypeScript MCP server with a Python sidecar:
src/utils/photos_reader.py) uses osxphotos to read the Photos library and returns JSON.child_process.execFileSync.This is the same pattern used by apple-numbers-mcp for the numbers-parser Python library.
export writes files to the destination directory you specify. Confirm destinations before running on shared machines.| Limitation | Reason |
|---|---|
| macOS only | Apple Photos and osxphotos are macOS-specific |
| Read-only | osxphotos reads the Photos library; this server does not modify it |
| Full Disk Access required | The Photos library SQLite database is in a protected directory |
| iCloud-only export is slower | Originals that aren't on disk are downloaded on demand via Photos.app/AppleScript. The export still succeeds, but takes longer than a local copy and requires Photos.app to be installed and signed in to iCloud |
| Photos.app may lock the library | If Photos.app is mid-write, opening the library can fail; close Photos.app and retry |
| Person filter requires named faces | osxphotos cannot filter by unnamed/unrecognized faces |
pip3 install osxphotos (global install) or npm run setup (project-local venv)../venv/ in the project directory.~/Pictures/Photos Library.photoslibrary.query to get current UUIDs.edited=true or raw=true was requested but the photo doesn't have one. Retry without that flag.npm install # Install dependencies
npm run setup # Create ./venv with osxphotos
npm run build # Compile TypeScript
npm test # Run unit tests
npm run typecheck # Type-check without emitting
npm run lint # Check code style
npm run format # Format code
The Python sidecar is a thin CLI that the TypeScript layer shells out to:
./venv/bin/python3 src/utils/photos_reader.py library-info
./venv/bin/python3 src/utils/photos_reader.py query --keyword sunset --limit 5
./venv/bin/python3 src/utils/photos_reader.py export --uuid <uuid> --dest /tmp/out
Rob Sweet - President, Superior Technologies Research
A software consulting, contracting, and development company.
MIT License - see LICENSE for details. This project is not affiliated with Apple Inc. or the osxphotos project.
Contributions are welcome! Please open an issue or PR at github.com/sweetrb/apple-photos-mcp.
Run in your terminal:
claude mcp add apple-photos-mcp -- npx Security
Low riskAutomated heuristic from public metadata — not a security guarantee.