Skip to main content
Glama
sweetrb

apple-photos-mcp

by sweetrb

query

Search Apple Photos by album, person, keyword, date, location, OCR text, and media type. Get matching photo summaries, total count, and UUIDs for retrieval.

Instructions

Use when: you need to find photos matching one or more filters — album, keyword, person, ML label, place, GPS radius (near), folder, taken-date or import-date range (addedAfter/addedInLast for 'recently imported'), year, file size, media type (screenshot, screen recording, selfie, panorama, live, portrait, time-lapse, slow-mo, burst, video), aesthetic score (minScore), OCR-detected text (detectedText), favorite/hidden flags, or title/description substrings — and get back a list of matches. This is the primary search/discovery tool; start here when you don't already have a UUID. Hidden photos are excluded unless hidden=true. Pass newestFirst=true with a limit to get the N most recent matches. Returns: count (the TOTAL number of matches), returned (the number of summaries in this response — capped at limit, default 500), and photo summaries (UUID, filename, date, dimensions, favorite/hidden/movie flags) — feed a UUID into get-photo/get-photos for full metadata, get-thumbnail to see it, or export to copy files. Do not use when: you already have UUIDs and want full metadata — use get-photo / get-photos; you want to see an image — use get-thumbnail; or you just want the catalog of album/keyword/person names — use list-albums / list-keywords / list-persons.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
liveNoOnly live photos
nearNoGPS-radius filter: "lat,lon,radiusKm" — only photos within radiusKm of the point (great-circle distance). Composes (AND) with every other filter. Requires location data: photos without GPS coordinates never match
uuidNoSpecific UUIDs to fetch
yearNoTaken in calendar year(s); ANY-match (e.g. [2024, 2025])
albumNoAlbum name(s); ANY-match
burstNoOnly burst photos
labelNoML classification label(s) from Photos object detection (the labels field of get-photo, e.g. Dog, Beach, Text); ANY-match, exact whole-string
limitNoCap the number of results returned (default 500 when omitted; count still reports the total matches)
placeNoPlace-name substring(s) from reverse geocoding (city, region, landmark). NOTE: multiple values are ANDed, not ORed — a photo must match every value
titleNoSubstring match on title
videoNoOnly videos/movies (alias of movies)
folderNoFolder name(s)/path(s) — matches photos in albums that live inside the folder; ANY-match (see list-folders for names)
hiddenNoOnly hidden photos
moviesNoInclude movies
personNoPerson name(s); ANY-match
photosNoInclude still photos
selfieNoOnly selfies (front-camera photos)
slowMoNoOnly slow-motion videos
toDateNoISO 8601 upper bound on photo date. A bare date (e.g. 2025-06-30) includes that whole day; pass a full datetime (e.g. 2025-06-30T18:00:00) for a precise exclusive bound
keywordNoKeyword(s); ANY-match
libraryNoPath to a .photoslibrary (default: system Photos library)
maxSizeNoOriginal file size at most this many bytes
minSizeNoOriginal file size at least this many bytes
favoriteNoOnly favorites
fromDateNoISO 8601 lower bound on photo date
minScoreNoOnly photos whose Photos-computed overall aesthetic score (0–1) is at least this — e.g. 0.7 for 'the good ones'. Post-filter over the other filters' matches; photos without a computed score never match
panoramaNoOnly panoramas
portraitNoOnly portrait-mode (depth-effect) photos
noKeywordNoOnly photos carrying no keyword at all
notHiddenNoExclude hidden photos (default behavior)
timelapseNoOnly time-lapse videos
addedAfterNoISO 8601 inclusive lower bound on IMPORT date (dateAdded — when the photo entered the library, not when it was taken)
screenshotNoOnly screenshots
addedBeforeNoISO 8601 upper bound on IMPORT date. A bare date includes that whole day; a full datetime is a precise exclusive bound
addedInLastNoImported within the trailing duration — "<number><unit>", unit s(econds) / m(inutes) / h(ours) / d(ays) / w(eeks), e.g. "7d" or "24h". The natural way to express "recently imported"
descriptionNoSubstring match on description
hasLocationNotrue = only photos WITH GPS coordinates; false = only photos WITHOUT; omit for no location filter
newestFirstNoSort matches by taken date, newest first, BEFORE limit is applied — so limit means 'the N most recent matches' instead of 'N in database order'
notFavoriteNoExclude favorites
detectedTextNoCase-insensitive substring match over the text Photos' own OCR indexed in each photo (macOS 13+) — receipts, signs, screenshots. Post-filter that reads per-photo search info over every other filter's matches, so combine it with narrowing filters (dates, album) on big libraries
screenRecordingNoOnly screen recordings

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
countNo
photosNo
returnedNo

Schema Changelog

Changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. Changed1 schema field changedv2.1.9
    • changedOutput schema / additionalProperties
      Previous value: -falseNew value: +true
  2. Changed3 schema fields changedv2.1.0
    • addedInput schema / properties / detectedText
      Added value: +{
      +  "description": "Case-insensitive substring match over the text Photos' own OCR indexed in each photo (macOS 13+) — receipts, signs, screenshots. Post-filter that reads per-photo search info over every other filter's matches, so combine it with narrowing filters (dates, album) on big libraries",
      +  "maxLength": 256,
      +  "minLength": 1,
      +  "type": "string"
      +}
    • addedInput schema / properties / minScore
      Added value: +{
      +  "description": "Only photos whose Photos-computed overall aesthetic score (0–1) is at least this — e.g. 0.7 for 'the good ones'. Post-filter over the other filters' matches; photos without a computed score never match",
      +  "maximum": 1,
      +  "minimum": 0,
      +  "type": "number"
      +}
    • addedInput schema / properties / near
      Added value: +{
      +  "description": "GPS-radius filter: \"lat,lon,radiusKm\" — only photos within radiusKm of the point (great-circle distance). Composes (AND) with every other filter. Requires location data: photos without GPS coordinates never match",
      +  "maxLength": 128,
      +  "pattern": "^\\s*-?\\d+(\\.\\d+)?\\s*,\\s*-?\\d+(\\.\\d+)?\\s*,\\s*\\d+(\\.\\d+)?\\s*$",
      +  "type": "string"
      +}
  3. Changed22 schema fields changedv1.5.0
    • addedInput schema / properties / addedAfter
      Added value: +{
      +  "description": "ISO 8601 inclusive lower bound on IMPORT date (dateAdded — when the photo entered the library, not when it was taken)",
      +  "maxLength": 64,
      +  "type": "string"
      +}
    • addedInput schema / properties / addedBefore
      Added value: +{
      +  "description": "ISO 8601 upper bound on IMPORT date. A bare date includes that whole day; a full datetime is a precise exclusive bound",
      +  "maxLength": 64,
      +  "type": "string"
      +}
    • addedInput schema / properties / addedInLast
      Added value: +{
      +  "description": "Imported within the trailing duration — \"<number><unit>\", unit s(econds) / m(inutes) / h(ours) / d(ays) / w(eeks), e.g. \"7d\" or \"24h\". The natural way to express \"recently imported\"",
      +  "maxLength": 32,
      +  "pattern": "^\\s*\\d+(\\.\\d+)?\\s*[smhdw]\\s*$",
      +  "type": "string"
      +}
    • addedInput schema / properties / burst
      Added value: +{
      +  "description": "Only burst photos",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / folder
      Added value: +{
      +  "description": "Folder name(s)/path(s) — matches photos in albums that live inside the folder; ANY-match (see list-folders for names)",
      +  "items": {
      +    "maxLength": 1024,
      +    "type": "string"
      +  },
      +  "maxItems": 100,
      +  "type": "array"
      +}
    • addedInput schema / properties / hasLocation
      Added value: +{
      +  "description": "true = only photos WITH GPS coordinates; false = only photos WITHOUT; omit for no location filter",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / label
      Added value: +{
      +  "description": "ML classification label(s) from Photos object detection (the labels field of get-photo, e.g. Dog, Beach, Text); ANY-match, exact whole-string",
      +  "items": {
      +    "maxLength": 1024,
      +    "type": "string"
      +  },
      +  "maxItems": 100,
      +  "type": "array"
      +}
    • addedInput schema / properties / live
      Added value: +{
      +  "description": "Only live photos",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / maxSize
      Added value: +{
      +  "description": "Original file size at most this many bytes",
      +  "exclusiveMinimum": 0,
      +  "type": "integer"
      +}
    • addedInput schema / properties / minSize
      Added value: +{
      +  "description": "Original file size at least this many bytes",
      +  "exclusiveMinimum": 0,
      +  "type": "integer"
      +}
    • addedInput schema / properties / newestFirst
      Added value: +{
      +  "description": "Sort matches by taken date, newest first, BEFORE limit is applied — so limit means 'the N most recent matches' instead of 'N in database order'",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / noKeyword
      Added value: +{
      +  "description": "Only photos carrying no keyword at all",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / panorama
      Added value: +{
      +  "description": "Only panoramas",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / place
      Added value: +{
      +  "description": "Place-name substring(s) from reverse geocoding (city, region, landmark). NOTE: multiple values are ANDed, not ORed — a photo must match every value",
      +  "items": {
      +    "maxLength": 1024,
      +    "type": "string"
      +  },
      +  "maxItems": 100,
      +  "type": "array"
      +}
    • addedInput schema / properties / portrait
      Added value: +{
      +  "description": "Only portrait-mode (depth-effect) photos",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / screenRecording
      Added value: +{
      +  "description": "Only screen recordings",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / screenshot
      Added value: +{
      +  "description": "Only screenshots",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / selfie
      Added value: +{
      +  "description": "Only selfies (front-camera photos)",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / slowMo
      Added value: +{
      +  "description": "Only slow-motion videos",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / timelapse
      Added value: +{
      +  "description": "Only time-lapse videos",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / video
      Added value: +{
      +  "description": "Only videos/movies (alias of movies)",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / year
      Added value: +{
      +  "description": "Taken in calendar year(s); ANY-match (e.g. [2024, 2025])",
      +  "items": {
      +    "maximum": 9999,
      +    "minimum": 0,
      +    "type": "integer"
      +  },
      +  "maxItems": 100,
      +  "type": "array"
      +}
  4. Changed3 schema fields changedv1.2.0
    • changedInput schema / properties / limit / description
      Previous value: -"Cap the number of results"New value: +"Cap the number of results returned (default 500 when omitted; count still reports the total matches)"
    • changedInput schema / properties / toDate / description
      Previous value: -"ISO 8601 upper bound on photo date"New value: +"ISO 8601 upper bound on photo date. A bare date (e.g. 2025-06-30) includes that whole day; pass a full datetime (e.g. 2025-06-30T18:00:00) for a precise exclusive bound"
    • addedOutput schema / properties / returned
      Added value: +{
      +  "type": "number"
      +}
  5. Changed14 schema fields changedv1.1.3
    • addedInput schema / properties / album / items / maxLength
      Added value: +1024
    • addedInput schema / properties / album / maxItems
      Added value: +100
    • addedInput schema / properties / description / maxLength
      Added value: +2048
    • addedInput schema / properties / fromDate / maxLength
      Added value: +64
    • addedInput schema / properties / keyword / items / maxLength
      Added value: +1024
    • addedInput schema / properties / keyword / maxItems
      Added value: +100
    • addedInput schema / properties / library / maxLength
      Added value: +4096
    • addedInput schema / properties / limit / maximum
      Added value: +100000
    • addedInput schema / properties / person / items / maxLength
      Added value: +1024
    • addedInput schema / properties / person / maxItems
      Added value: +100
    • addedInput schema / properties / title / maxLength
      Added value: +1024
    • addedInput schema / properties / toDate / maxLength
      Added value: +64
    • addedInput schema / properties / uuid / items / maxLength
      Added value: +256
    • addedInput schema / properties / uuid / maxItems
      Added value: +1000
  6. Changed1 schema field changedv1.1.0
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "$schema": "http://json-schema.org/draft-07/schema#",
      +  "additionalProperties": false,
      +  "properties": {
      +    "count": {
      +      "type": "number"
      +    },
      +    "photos": {
      +      "items": {
      +        "additionalProperties": true,
      +        "properties": {},
      +        "type": "object"
      +      },
      +      "type": "array"
      +    }
      +  },
      +  "type": "object"
      +}
  7. First observedv0.1.4

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full transparency burden and does it thoroughly. It discloses default exclusions (hidden photos unless hidden=true), count vs. returned semantics, newestFirst ordering behavior, post-filter effects for minScore and detectedText, AND/OR semantics for place vs. other filters, and the requirement for GPS data with near. No contradictions with annotations since none exist.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but highly structured with clear sections (Use when, Returns, Do not use when). Every sentence provides unique, actionable guidance for a complex 41-parameter tool, and the front-loaded use case ensures immediate understanding. The length is justified by the tool's scope.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the description is remarkably complete: it covers purpose, scope, filtering capabilities, return value semantics, exclusions, and clear alternatives. The presence of an output schema and full parameter documentation in the schema complements the description, so nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Even though the schema has 100% coverage, the description adds significant semantic value beyond individual parameter descriptions. It groups filter categories, explains ANY-match vs. ANDed behavior, defines post-filter operations, gives natural-language examples (e.g., "recently imported" as addedInLast), and clarifies return fields. This goes well beyond the schema baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with "Use when: you need to find photos matching one or more filters" and explicitly names the resource (photos) and the action (search/query). It distinguishes itself from siblings by stating "This is the primary search/discovery tool; start here when you don't already have a UUID" and by contrasting with get-photo/get-photos, get-thumbnail, and list-* tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit "Use when" and "Do not use when" sections listing precise alternatives: use get-photo/get-photos for UUID-based metadata, get-thumbnail for viewing images, and list-albums/list-keywords/list-persons for catalog names. This gives the agent unambiguous selection criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sweetrb/apple-photos-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server