immich-photo-manager
This server provides AI-powered management of self-hosted Immich photo libraries through natural language.
Server & Connectivity
Ping the server, get version, view library statistics (photos/videos/storage), retrieve connection info, and update credentials (URL & API key) without restarting.
Photo Search
AI Visual Search: CLIP-powered natural language search (e.g. "sunset at the beach") with optional location/date filters.
Metadata Search: Filter by EXIF data — city, state, country, camera make/model, date range, favorites, and media type.
Map Markers: Retrieve GPS-tagged assets with lat/lon coordinates to explore geographic distribution.
Asset Management
Fetch full metadata (EXIF, GPS, camera info) for specific assets.
Retrieve base64-encoded thumbnails or previews for single assets or in batch (no album required).
Album Management
List, create, update, and delete albums.
Add/remove assets from albums (photos are never deleted).
Get album details with all asset IDs and retrieve thumbnails for all photos in an album.
Sharing
Create public shared links for albums (with download/metadata options) and list all existing shared links.
Advanced Capabilities (via AI reasoning over the above tools)
Library health assessments, metadata repair suggestions, duplicate detection, and interactive HTML gallery generation.
Enables cross-source duplicate analysis to find re-encoded copies of photos imported from Apple Photos, using perceptual hashing to identify duplicates across different import sources.
Enables cross-source duplicate analysis to find re-encoded copies of photos imported from Google Photos, using perceptual hashing to identify duplicates across different import sources.
Provides intelligent photo management tools for Immich self-hosted photo libraries, including AI-powered search, geographic album creation, duplicate detection, metadata repair, storage optimization, and interactive gallery generation.
![]()
MCP server for intelligent photo management with Immich: your self-hosted library, understood.
An unofficial community plugin by Drolosoft. It is not affiliated with, sponsored by or endorsed by Immich or FUTO; "Immich" is their name and is used here only to say which server the plugin works with. It talks to your Immich server through its public API.
If your Immich library has grown past what you can manage by hand, immich-photo-manager gives any AI assistant direct access to your instance: search, organize, deduplicate, and curate albums through natural conversation. Works with Claude, Gemma, or any MCP-compatible client. Runs locally and talks only to your Immich; your originals stay on your server (see what leaves your network).
Tested, not assumed. Every push runs 364 unit tests on CI. Every release is also run live against real Immich 2.7.5 and 3.1.0 (Docker, all 94 tools over the MCP protocol's legacy era, state re-read after each write) before it is tagged; both protocol eras (the legacy handshake and stateless 2026-07-28) are pinned on every push by SDK-free wire tests (
tests/test_raw_wire_eras.py). The kit is intests/live/, reproducible by anyone. The demos indoc/demos/are transcripts of real sessions, Demo 11 is this exact flow prompt by prompt, and Demo 12 runs the video frames and PDF photobook on a real clip. Demos 13 to 18 cover everything added in 2.x (library discovery, OCR and people search, memories and stacks, partners and downloads, asset notes, the Docker image) from real sessions. Details: How it's tested.

What It Does
Say "create albums for all my trips" and watch it work:

GPS coordinates, CLIP visual search, and temporal matching, combined in one request to create dozens of curated albums. No scripts, no manual sorting.
Related MCP server: exif-mcp
Quick Start
Prerequisites
A running Immich instance (self-hosted, v1.90+)
An Immich API key (how to create one)
Python 3.10+ with
pip(download)
Install (Claude Code plugin)
git clone https://github.com/drolosoft/immich-photo-manager.git
cd immich-photo-manager
pip3 install -r src/requirements.txt # the plugin runs on your system python3
claude plugin marketplace add ./
claude plugin install immich-photo-managerThe install prints that two options are not set yet: the server URL and the API key. Inside a session, /plugin asks for them in a dialog (the key goes to your system's secure storage); from the shell you can pass them with --config immich_base_url=... --config immich_api_key=..., or leave both empty and let the setup below save them.
Open Claude Code (restart it if it was already open) and connect it to your Immich. Guided:
/setup-immich-photo-managerIt asks for your server URL and API key, checks them against the server, saves them, and shows your library numbers:

Or skip the guide and say it in one line (same thing underneath):
Update my Immich credentials to http://immich.local:2283 with API key <your API key>Either way the credentials are saved for every session from then on; repeat to change server or key. Confirm any time with:
What Immich version am I connected to?That's the whole install. Claude Desktop, Cowork or another MCP client instead of Claude Code? That is the plain MCP server without the skills: see Getting Started, route B.
Update the plugin
One line, no reinstall:
cd immich-photo-manager && git pull # the clone you installed from
claude plugin marketplace update drolosoft-marketplace
claude plugin update immich-photo-manager@drolosoft-marketplaceThen restart Claude Code. drolosoft-marketplace is the name the marketplace gets when you add it from the clone (claude plugin marketplace list shows it). Your saved credentials carry over.
After pulling a new version, run pip3 install -r src/requirements.txt again: 1.7.1 added the video (av) and PDF (fpdf2) libraries to the plugin's dependencies. On the uvx route, uvx --refresh immich-photo-manager --help once, then restart the client.
Upgrading from 2.0.12
Nothing to set up again. The options, the server command and the tool names are the same as in 2.0.12, and saved credentials carry over. What changes:
The recommended place for the server URL and the API key is the plugin's configuration dialog (
/plugin, then the plugin's options), which keeps the key in your system's secure storage. If you connected through/setup-immich-photo-manager, that keeps working and you do not have to move anything.The images in the repository were saved again without colour profiles or EXIF data. They look the same.
The README now lists everything the plugin runs, sends and fetches in one place.
What leaves your network
The plugin process runs on your machine and only talks to your Immich. But everything the assistant reads through it goes to the model you use: filenames, dates, EXIF, album lists, and, when you ask it to look at pictures, thumbnails (250px by default, 1440px previews on request). Originals are never fetched. With Claude that means those thumbnails leave your network; with a local model over MCP (LM Studio, Ollama) nothing does. Nothing is sent unless you ask for it: listing albums or fixing dates moves text only, "tell me what's in these photos" moves images.
A PDF report follows the same rule: export_pdf writes the file to disk on the machine running the server, and it is not sent anywhere unless you pass return_base64=true. The file goes where output_path says (default your Desktop); existing files are never overwritten. Frames that only go into the PDF never leave your machine and cost no tokens; only the frames you ask the model to look at do. When the assets carry GPS, the Places page draws a map with tiles from tile.openstreetmap.org, the only third-party call the server itself makes; pass map=false to skip it and keep everything inside your network.
What the plugin runs, sends and fetches
Runs: one local process, the MCP server, started as
python3on a file inside the plugin (src/plugin_entry.py). There are no hooks and no background jobs, and nothing is downloaded or installed when it starts. The Python libraries are the ones you install yourself fromsrc/requirements.txt.Your Immich server: every tool is a call to the public API of the Immich server you configure, with your API key in the
x-api-keyheader.upload_assetsends a local file you name to that server.Other servers, from the plugin: only
tile.openstreetmap.org, and only when a PDF report draws its Places map. The request carries the tile coordinates of the area, so it tells that server roughly where the photos were taken.map=falseskips it.Other servers, from the pages it writes: gallery pages load nothing from outside; their handwriting font is embedded in the page. The travel map, served from your own machine, loads Leaflet and two of its add-ons from
unpkg.com, each pinned to one version with anintegrityhash so the browser refuses a changed file, and its map tiles from OpenStreetMap, which shows those two servers the areas you look at. Thumbnails are embedded in the page, or loaded from your own Immich when you set up CORS.Files on your machine:
export_pdfanddownload_archivewrite where you say and never overwrite. Galleries and maps are HTML files written by the assistant. The duplicate report can read an import folder on disk to compare photos, and it removes files there only when you approve the exact list.Credentials: entered in the plugin's configuration dialog, the API key is kept in your system's secure storage. Given in conversation (
/setup-immich-photo-managerorupdate_credentials), the key passes through the model like any other message and is saved in two files only your user can read:config.jsonin the plugin's cache folder and~/.immich-photo-manager/config.json.Public links:
create_shared_linkmakes a link that anyone with the URL can open. It is created only when you ask for sharing;list_shared_linksshows the ones that exist anddelete_shared_linkremoves one.Deleting:
delete_assetsmoves assets to Immich's trash, whichrestore_assetsundoes. It deletes for good only withforce=true, andempty_trashis permanent too. The cleanup and duplicate skills show what they found and wait for your answer before they delete anything. The plugin never changes Claude's permission settings.
Connect, check, switch: all by talking
You never edit config files after setup. The connection is managed in conversation:
You say | What happens |
"What Immich version am I connected to?" | Reports the server version and the URL it's talking to |
"Update my Immich credentials to | Validates the key against that server, hot-swaps the live connection, persists it, no restart |
"Show my Immich connection" | URL + masked API key |
One connection at a time: to work with a second Immich (a test instance, a friend's server), say the update sentence again; say it once more to go back. Wrong URL or key? It tells you, and keeps the previous connection.
Try the full walkthrough: Demo 11, Album Walkthrough. Read an album item by item, find who repeats, create a sub-album, tag and describe every photo.
Works in Claude Code
The same plugin runs in Claude Code: search your library, curate albums, and generate galleries right from the terminal.

Full conversation transcript: Claude Code demo
Works with Any MCP Client
immich-photo-manager is an MCP server: it works with any AI assistant that speaks the Model Context Protocol, not just Claude.
Use the package entry point directly with uvx:
{
"mcpServers": {
"immich": {
"command": "uvx",
"args": ["immich-photo-manager"],
"env": {
"IMMICH_BASE_URL": "https://your-immich-server.com",
"IMMICH_API_KEY": "your-api-key"
}
}
}
}immich-photo-manager defaults to MCP stdio transport. Set MCP_TRANSPORT=http when you want to run the server as a Streamable HTTP service.
🐳 Run as a Docker container
The server also ships as a multi-arch image (amd64 + arm64) on GitHub Container Registry, serving MCP over HTTP on port 8626, both protocol eras, same 94 tools:
docker run -d -p 8626:8626 \
-e IMMICH_BASE_URL=https://your-immich-server.com \
-e IMMICH_API_KEY=your-api-key \
-v ./exports:/data \
ghcr.io/drolosoft/immich-photo-managerPoint any Streamable HTTP client at http://localhost:8626/mcp. Liveness is at /health (no credentials needed, wired as the image's HEALTHCHECK). The tools that write files (export_pdf, download_archive) drop them in /data, so mount a volume there. Reaching the container under a name other than localhost (a reverse proxy, another container) needs that name in -e MCP_ALLOWED_HOSTS=.... DNS-rebinding protection stays on.
The env variables are optional: a container started without them still serves, every tool answers "No Immich credentials configured" with the fix, and one update_credentials call (base_url + api_key) connects it, persisted under /data, so with the volume mounted it survives restarts and re-creations.
The same as a Compose service, next to an Immich stack or on its own:
services:
immich-mcp:
image: ghcr.io/drolosoft/immich-photo-manager
ports:
- "8626:8626"
environment:
IMMICH_BASE_URL: https://your-immich-server.com
IMMICH_API_KEY: your-api-key
# MCP_ALLOWED_HOSTS: photos-mcp.example.com # only behind a proxy / other hostname
volumes:
- ./exports:/data
restart: unless-stoppedClaude Desktop on macOS: the app does not see your shell's PATH, so write the full path to uvx in "command" (run which uvx in a terminal; typically /Users/<you>/.local/bin/uvx or /opt/homebrew/bin/uvx). Run uvx immich-photo-manager --help once in a terminal so the first download is done, then quit Claude Desktop with Cmd+Q and reopen it. If it still does not show up, the reason is in ~/Library/Logs/Claude/mcp-server-immich.log.
============================================================
IMMICH-PHOTO-MANAGER × GEMMA 4 (LM STUDIO)
============================================================
Immich: https://your-immich-server.com
Model: gemma4-26b-it (local, LM Studio)
Query: "Show me my Lanzarote albums"
1. Getting MCP tool schemas...
94 MCP tools available
2. Asking Gemma 4...
Gemma 4 chose: list_albums({})
3. Executing 'list_albums' against Immich...
Found 124 total albums, 14 Lanzarote albums:
- Lanzarote Amarillo (26 photos)
- Lanzarote Rojo (201 photos)
- Lanzarote Azul (187 photos)
- Lanzarote Marrón (208 photos)
- Lanzarote Negro (193 photos)
- Lanzarote Verde (201 photos)
- Lanzarote Gasolina (174 photos)
...
4. Gemma 4 interpreting results...
"I found 14 Lanzarote albums, 7 color-themed with
1,190 photos and 7 location-specific albums."
RESULT: Zero cloud dependency, fully self-hosted stack.Client | Status |
Claude Code | Tested |
Claude Desktop | Tested |
LM Studio (Gemma 4) | Tested |
Cursor, Windsurf, VS Code, Cline, Zed | Compatible (MCP stdio) |
Full transcript: Gemma 4 demo · Test script:
test-lmstudio-mcp.py
Highlights
AI-powered search: natural language photo search via CLIP ("sunset at the beach", "birthday cake")
Geographic albums: create albums organized by place, combining GPS + CLIP + temporal matching
Metadata repair: fix noon/midnight timestamps, infer missing GPS from neighboring photos, correct timezone offsets
Library cleanup: detect screenshots, duplicates, and low-quality images with multi-signal analysis; near-duplicates and bursts can be stacked behind the best shot instead of deleted, which is reversible
Duplicate detection: cross-source analysis using perceptual hashing (finds re-encoded copies across Apple Photos, Google Photos, and other imports)
Bulk rotation: rotate entire albums or selections at once (90°/180°/270°); non-destructive, accumulates across calls, one-click revert
PDF reports: album or selection to a PDF with metadata, video frames and Claude's captions, built on your machine; the photobook layout gives each chosen video moment a full page with its own caption, and the cover/index/places pages are optional
Video frames: cut evenly spaced frames out of any clip, or a segment (
start/end) down to one frame per second (interval), so Claude can describe what happens in it; Immich itself keeps one poster per videoPeople & face management: list, search, merge, and organize recognized people; reassign misidentified faces; view face thumbnails
Trash & asset lifecycle: safely delete assets to trash, permanently remove, restore from trash; complete asset lifecycle management
Library health: one command for asset inventory, metadata quality, storage breakdown, and recommendations
Tags & organization: create, apply, and manage tags across your library; bulk tag and untag assets
Server-aware: one call reports the Immich version, which features are switched on (OCR, smart search, faces, map) and the known behaviour of that major, so nothing is offered that the server cannot do
Text inside photos: OCR finds the text in a photo (a ticket, a street sign), and the explore, city and suggestion calls return the exact spellings the filters expect instead of guesses
Dates in one call: month-by-month buckets, a calendar heatmap that shows gaps and busy days, and Immich's "on this day" memories
Sharing, both directions: partner libraries (Immich's family sharing) and the comments and likes people leave on a shared album
Originals out: an album or a selection as one zip on your machine, with the size reported before the download starts
Notes between sessions: verdicts and actions stored on each asset, so the next cleanup skips what an earlier pass already reviewed
Runs in Docker: multi-arch image serving MCP over HTTP on port 8626, both protocol eras, same 94 tools
Interactive galleries: self-contained HTML pages with embedded thumbnails, 3 themes, 4 view modes, and a Cowork Actions Panel for batch operations

Select photos in the gallery, click an action, and paste the command into Claude. See Skills Reference for all 13 skills.
Tools
The 94 tools by area. Parameters, return shapes and examples for each one are in the MCP Tools Reference.
Search:
search_smart,search_metadata,search_explore,search_cities,search_places,search_suggestions,search_random,search_statistics,search_large_assets,list_assetsAlbums:
list_albums,get_album,create_album,update_album,delete_album,add_assets_to_album,remove_assets_from_albumAssets and metadata:
get_asset_info,update_asset_metadata,update_assets_metadata,rotate_assets,revert_asset_edits,get_map_markers,reverse_geocode,upload_assetImages and thumbnails:
get_asset_image,get_album_images,get_images_batch,get_asset_thumbnail,get_album_thumbnails,get_thumbnails_batchVideo and PDF:
get_video_frames,get_video_frames_json,get_export_preview,export_pdfPeople and faces:
list_people,get_person,update_person,merge_people,search_people,get_person_thumbnail,get_asset_faces,reassign_faceDuplicates and stacks:
get_duplicates,resolve_duplicates,create_stack,list_stacks,get_stack,update_stack,delete_stackTags:
list_tags,get_tag,create_tag,update_tag,delete_tag,tag_assets,untag_assetsDates:
get_timeline_buckets,get_timeline_bucket,get_calendar_heatmap,list_memories,create_memory,update_memory,delete_memorySharing:
list_shared_links,create_shared_link,get_shared_link,update_shared_link,delete_shared_link,list_users,list_partners,create_partner,update_partner,remove_partner,list_activities,create_activity,delete_activityDownload:
get_download_info,download_archiveTrash:
delete_assets,empty_trash,restore_trash,restore_assetsAsset notes:
review_assets,record_action,get_asset_notes,get_assets_notes,clear_asset_notesServer and connection:
ping,get_server_version,get_capabilities,get_statistics,get_connection_info,update_credentials
Why immich-photo-manager?
Immich is excellent at storing and viewing your photos. But managing a large library (deduplication, metadata repair, album curation, storage analysis) still requires manual effort or custom scripts.
Manual / scripts | immich-photo-manager | |
🔍 | Write API calls, parse JSON | Natural language: "find my sunset photos from Italy" |
🗺️ | Export GPS, cluster manually | Geographic albums: automatic GPS + CLIP + temporal matching |
🧹 | Hash files, diff checksums | Perceptual hashing: finds re-encoded duplicates across import sources |
🔧 | Edit EXIF one file at a time | Metadata repair: batch-fix timestamps, infer GPS, correct timezones |
📊 | Query database, build reports | Library health: one command for metadata quality, storage, recommendations |
🔄 | Rotate one photo at a time | Bulk rotation: rotate entire albums at once, non-destructive |
🏷️ | No tag management in UI | Tags: create, bulk apply/remove across assets |
📅 | Scroll the timeline looking for holes | Timeline map: month buckets and a calendar heatmap in one call, gaps included |
🔤 | Grep filenames and hope | OCR search: find a photo by the text inside it |
📦 | SSH in and zip the files by hand | Download archive: album originals as one zip, size known in advance |
🧠 | Re-decide the same photos every session | Asset notes: the verdict stays on the asset, the next pass skips it |
🛡️ | Manual review of every action | Safety first: shows findings, asks before acting |
How it's tested
Unit suite, every push: 364 pytest cases on Python 3.10 and 3.13 (HTTP mocked), plus ruff. Releases are tagged only when this gate is green.
Live, every tool, two Immich versions:
tests/live/starts real Immich 2.7.5 and 3.1.0 in Docker, fills them with a small library, and drives all 94 tools over the MCP protocol, re-reading state after each write. Run before every release; last full run 2026-09-03, 132/132 checks on both.In use: PyPI downloads, merged PRs from four outside contributors, and the demos in
doc/demos/are transcripts of real sessions.
Built with Claude
This is a Claude plugin, and Claude is a collaborator on the code: the design, the API compatibility decisions, and what to test are the author's; a good part of the implementation and the test harness were written with Claude Code. Every change ships through the same gate either way: tests on CI, and for anything touching Immich's API, the live run above.
Documentation
Document | Description |
Installation, manual MCP setup, deployment options, and troubleshooting | |
Detailed setup: git, Python, venv, HTTP/stdio launch, Open WebUI, and common issues | |
All 13 skills: workflows, triggers, parameters, output formats | |
All 94 MCP tools: parameters, return types, examples | |
How base64-embedded thumbnails solve the Cowork sandbox restriction | |
Dual-era support: legacy handshake and the stateless revision from one server, and how it is verified | |
Optional, enable direct URL thumbnail loading for browser-viewed galleries |
🦙 Glama Score
Contributing
Contributions are welcome: bug fixes, new skills, feature ideas. Open an issue or submit a PR.
If immich-photo-manager helps manage your library, consider giving it a star on GitHub. It helps others discover the project.
Support
If immich-photo-manager saved you time or made your photo library easier to manage, consider buying me a coffee. It keeps the next one coming!
License
MIT License: free to use, modify, and distribute.
Forged by Drolosoft · Tools we wish existed
Available Tools
94 toolsadd_assets_to_albumAdd assets to albumA
Add existing assets to an album. Use this to curate albums from search results or other asset lists. Assets can belong to multiple albums simultaneously. Side effect: modifies album membership.
Args:
album_id: Target album UUID.
asset_ids: List of asset UUIDs to add to the album.
Returns: JSON with album_id, count added, and per-asset success/error details.
Inputs: album_id (required), asset_ids (required). Returns: result
clear_asset_notesClear asset notesADestructive
Forget the plugin's notes on assets (reviews and actions). Only the plugin's own key is removed; metadata other apps stored stays. Side effect: deletes the notes on the server.
Args:
asset_ids: The assets to clear.
Returns: JSON with success, how many assets were cleared, and a failed array
of {asset_id, error}. Success is true only when nothing failed.
Inputs: asset_ids (required). Returns: result
create_activityCreate activityA
Post a comment (or a like) on a shared album or on one asset in it. Side effect: the activity appears for everyone the album is shared with.
Args:
album_id: The album to comment on.
comment: The comment text. Leave empty when sending a like.
asset_id: Attach the comment/like to this asset instead of the album.
like: True to send a like instead of a comment.
Returns: JSON with the created activity's id and type.
Inputs: album_id (required), like, comment, asset_id. Returns: result
create_albumCreate albumA
Create a new album, optionally pre-populated with assets. Use this to organize photos into collections. Side effect: creates a new album in Immich.
Args:
name: Album display name (e.g. 'Roma, Italia', 'Birthday 2024').
description: Optional album description text.
asset_ids: Optional list of asset UUIDs to add immediately on creation.
Returns: JSON with the new album's id, name, and asset count.
Inputs: name (required), asset_ids, description. Returns: result
create_memoryCreate memoryA
Create an "on this day" memory from chosen assets. Use this after curating a set of photos from the same past date (e.g. via search_metadata with a date range) to make them show up in Immich's memories feed. Side effect: creates a memory on the server.
Args:
memory_at: ISO date the memory is shown on (usually today's month and day).
year: The past year the memory looks back to (required by Immich).
asset_ids: Assets to include. May be empty, but an empty memory shows nothing.
Returns: JSON with the created memory's id, type, memory_at, the year it
remembers, is_saved, asset_count and a trimmed assets list (id, filename, date).
Inputs: year (required), memory_at (required), asset_ids. Returns: result
create_partnerCreate partnerA
Share this account's library with another user on the server. The other user will see these photos next to their own. Find the id with list_users. Side effect: grants the user read access to the whole library.
Args:
user_id: The user to share with.
Returns: JSON with the new partner entry.
Inputs: user_id (required). Returns: result
create_stackCreate stackA
Group near-identical assets (a burst, retries of the same shot) into one stack. The library then shows the stack as a single item fronted by its primary asset, which keeps every shot without the visual clutter — a gentler cleanup than deleting. The first id becomes the primary. Side effect: creates the stack on the server.
Args:
asset_ids: The assets to group, at least two. Order matters: the first is
the cover.
Returns: JSON with the new stack's id, primary_asset_id and asset list.
Inputs: asset_ids (required). Returns: result
create_tagCreate tagA
Create a new tag for categorizing assets. Use list_tags first to avoid duplicates. Side effect: creates a new tag in Immich.
Args:
name: Tag display name (e.g. 'Vacation', 'Family', 'Work'). Must be unique.
color: Optional hex color for the tag (e.g. '#FF5733').
Returns: JSON with the new tag's id, name, and color.
Inputs: name (required), color. Returns: result
delete_activityDelete activityADestructive
Remove one comment or like. Side effect: deletes it for everyone.
Args:
activity_id: The activity to remove (from list_activities).
Returns: JSON confirming the deletion.
Inputs: activity_id (required). Returns: result
delete_albumDelete albumADestructive
Delete an album container. The photos inside are NOT deleted — they remain in the library. Use this to remove unwanted album groupings. Side effect: permanently deletes the album (cannot be undone).
Args:
album_id: The album's UUID to delete.
Returns: JSON with success, the deleted album's id and album_id.
Inputs: album_id (required). Returns: result
delete_assetsDelete assetsADestructive
Delete assets (soft-delete to trash or permanent). Use this to remove unwanted photos/videos. Default is soft-delete (recoverable via restore_assets). With force=true, deletion is PERMANENT and IRREVERSIBLE. Side effect: moves/deletes assets.
Args:
asset_ids: List of asset UUIDs to delete.
force: false (default) = move to trash (recoverable). true = PERMANENTLY delete (no undo).
Returns: JSON with count deleted and whether force was used.
Inputs: asset_ids (required), force. Returns: result
delete_memoryDelete memoryADestructive
Delete a memory. The photos stay in the library — only the memory entry goes away. Side effect: removes the memory from the server.
Args:
memory_id: The memory to delete.
Returns: JSON confirming the deletion.
Inputs: memory_id (required). Returns: result
delete_stackDelete stackADestructive
Dissolve a stack. The assets are NOT deleted — they simply show as individual items again. Side effect: removes the grouping on the server.
Args:
stack_id: The stack to dissolve.
Returns: JSON confirming the deletion.
Inputs: stack_id (required). Returns: result
delete_tagDelete tagADestructive
Delete a tag and remove it from all assets. The assets themselves are unaffected. Side effect: permanently deletes the tag (cannot be undone).
Args:
tag_id: The tag's UUID to delete.
Returns: JSON with success, the deleted tag's id and tag_id.
Inputs: tag_id (required). Returns: result
download_archiveDownload archiveA
Download an album or a selection as one zip of the original files, written to a local path. Use get_download_info first when the size matters. The file is streamed to disk (safe for big albums) and an existing file is never overwritten. Side effect: writes a file on the machine running the server.
Args:
output_path: Where to write the zip (an existing file is refused).
album_id: Download the whole album.
asset_ids: Or download just these assets.
Returns: JSON with path, bytes written and how many assets went in, or an error.
Inputs: output_path (required), album_id, asset_ids. Returns: result
empty_trashEmpty trashADestructive
Permanently delete ALL assets currently in trash. DESTRUCTIVE and IRREVERSIBLE. Use this only after confirming the user wants to purge all trashed items. For deleting specific assets, use delete_assets instead. Side effect: permanently destroys all trashed assets and frees storage.
Returns: JSON with success confirmation.
Inputs: no named parameters. Returns: result
export_pdfExport PDFA
Build a PDF (cover, index, places, one section per asset) from an album or a
list of assets, on the machine running this server. Immich metadata (date, place,
camera, people, tags) is always included; pass captions {asset_id: text} with
what you saw to add your analysis. Video frames go straight into the PDF and cost
no tokens (up to 120 per video). The PDF never enters the conversation unless
return_base64=True. If the user asked for a PDF without saying how they want
it, call get_export_preview first and ask them about the choices it lists.
Keep the selection coherent: one story per PDF. Never mix unrelated assets
(two videos about different things, photos from different events) just to
show more; if the user's material spans several stories, offer one PDF per
story instead.
Args:
album_id: Album UUID, or asset_ids: explicit asset UUIDs (exactly one of the two).
output_path: Where to write (default ~/Desktop/<title>.pdf). Existing files are never overwritten.
title: Cover title (default: album name or "Immich export <date>").
captions: {asset_id: text} written after looking at the images.
layout: 'detail' (one asset per page with its data, default), 'grid' (six per page)
or 'photobook' (one asset per page, image as large as it fits, caption under
it; a video with several chosen frames unfolds into one full page per frame).
frames_per_video: Frames per video, evenly spaced (0-120, default 4; 0 = poster only).
frame_interval: One frame every N seconds instead of frames_per_video (same 120 cap).
frame_times: {asset_id: [seconds, ...]} exact moments for specific videos, chosen
after looking at their frames ("the representative frame"). Wins over
frames_per_video/frame_interval for the listed videos; others keep the spread.
frame_captions: {asset_id: [text, ...]} one caption per extracted frame, in frame
order (photobook prints each on its frame's page; other layouts ignore them).
image_size: 'original' (default): photos go in at the stored file's quality,
re-encoded to at most 3000px (a format the server cannot decode, like
some HEIC, falls back to preview with a note); 'preview' (1440px) or
'thumbnail' for smaller files.
frame_size: video frame size in the PDF: 'auto' (default, same as 'preview':
quality is free inside the PDF) or 'thumbnail' for a smaller file.
language: 'en' (default) or 'es' for the fixed labels on the pages (Index,
Places, Camera, page numbers); captions stay in whatever language you wrote.
map: Draw an OpenStreetMap map on the Places page when assets carry GPS
(default True; tiles come from tile.openstreetmap.org, the only
third-party call this server makes — pass map=False to skip it).
cover, index, places: Include each front-matter page (all default True;
turn them off for a print-ready photobook of bare pages).
footer: 'full' (plugin name, server and page number, default), 'pages'
(just the page number) or 'none'.
header: Repeat the title at the top of every page except the cover
(default False).
videos_position: Where the video pages (frame strips or frame pages) go:
'mixed' with the photos in the general order (default), 'first' or 'last'.
order: 'auto' (albums read oldest to newest, like the frames inside a video;
asset_ids keep the order you passed), 'oldest', 'newest' or 'given'.
confirm: Only asked for when explicit asset_ids mix videos more than 90 days
apart (different stories). Pass True only when the user themselves asked
to mix them; exporting a whole album never needs it.
limit: Max assets (1-500, default 100).
return_base64: Also return the PDF bytes (skipped above 2 MB; every MB is
roughly 350k tokens in the conversation).
Returns: JSON {path, pages, bytes, assets_included, assets_skipped:[{id, reason}], warnings:[...]}.
Inputs: map, cover, index, limit, +20 more. Returns: result
get_albumGet albumARead-only
Get full details for a specific album including all its asset IDs. Use this to inspect album contents or retrieve asset IDs for further operations (thumbnails, metadata, rotation). For listing all albums, use list_albums instead. Read-only.
Args:
album_id: The album's UUID (from list_albums or create_album).
Returns: JSON with album metadata, a flat list of all asset_ids, and an assets array
(id, filename, type, date, recognized people) so "who appears in this album / who
repeats" can be answered without further calls.
Inputs: album_id (required). Returns: result
get_album_imagesGet album imagesARead-only
Get an album's thumbnails as image blocks for inline visual display. Use this to visually browse an album in clients that render images. For HTML gallery generation with base64 data URIs (Cowork/skills), use get_album_thumbnails instead — it returns JSON with filenames and dates. Read-only.
Args:
album_id: The album's UUID.
size: 'thumbnail' (250px) or 'preview' (1440px). Default: 'thumbnail'.
limit: Max thumbnails to return (1-50, default 20).
Returns: A list of image blocks suitable for visual display.
Inputs: album_id (required), size, limit.
get_album_thumbnailsGet album thumbnailsARead-only
Get base64-encoded thumbnails for photos in an album. Use this to generate visual HTML galleries from an existing album. For thumbnails from search results (no album), use get_thumbnails_batch instead. Read-only.
Args:
album_id: The album's UUID.
size: 'thumbnail' (250px) or 'preview' (1440px). Default: 'thumbnail'.
limit: Max thumbnails to return (1-50, default 20).
Returns: JSON with album info and thumbnails array (each with asset_id, base64 data, filename, date).
Inputs: album_id (required), size, limit. Returns: result
get_asset_facesGet asset facesARead-only
Get all detected faces in a photo with their person assignments. Use this to see who is in a specific photo or to find face IDs for reassign_face. Read-only.
Args:
asset_id: The asset's UUID.
Returns: JSON array of face detections (each with face_id, person_id, person_name, bounding box).
Inputs: asset_id (required). Returns: result
get_asset_imageGet asset imageARead-only
Get a single asset's thumbnail as an image block for inline visual display. Use this in clients that render images (Open WebUI, Claude Desktop). For HTML gallery generation with base64 data URIs (Cowork/skills), use get_asset_thumbnail instead — it returns JSON. Read-only.
Args:
asset_id: The asset's UUID.
size: 'thumbnail' (250px, fast) or 'preview' (1440px, higher quality). Default: 'thumbnail'.
Returns: An image block (MCP ImageContent) for visual display.
Inputs: asset_id (required), size.
get_asset_infoGet asset infoARead-only
Get full metadata for a single asset. Use this when you need EXIF details, GPS coordinates, camera info, or file properties for a known asset ID. For finding assets, use search_metadata or search_smart instead. Read-only.
Args:
asset_id: The asset's UUID (from search results, album listings, or list_assets).
with_notes: Also include the plugin's notes on the asset (past review
verdicts and recorded actions, see get_asset_notes). One extra request.
Returns: JSON with EXIF data, GPS, dates, dimensions, file size, camera
make/model, and owner; plus a `notes` object (reviews and actions) when
with_notes is true.
Inputs: asset_id (required), with_notes. Returns: result
get_asset_notesGet asset notesARead-only
The plugin's notes on one asset: past review verdicts with reasons and recorded actions, newest last. Empty lists when it was never annotated. Read-only.
Args:
asset_id: The asset to read.
Returns: JSON with asset_id, reviews [{at, verdict, reason}] and
actions [{at, action, detail}].
Inputs: asset_id (required). Returns: result
get_assets_notesGet assets notesARead-only
Which of these assets already carry notes, and their last verdict — the call that lets a cleanup pass skip what an earlier session reviewed. Immich cannot search this metadata, so the server is asked once per asset (no tokens spent on the ones without notes). Read-only.
Args:
asset_ids: The candidates to check (an album's assets, a search result).
Returns: JSON with checked (how many were asked), annotated (one compact row
per asset that has notes — asset_id, last_verdict, last_reason, last_review_at,
and the reviews/actions counts) and a failed array of {asset_id, error} for the
assets that could not be read. Success is true only when nothing failed.
Inputs: asset_ids (required). Returns: result
get_asset_thumbnailGet asset thumbnailARead-only
Get a base64-encoded thumbnail image for a single asset. Use this to visually inspect one photo. For multiple photos, use get_thumbnails_batch (by IDs) or get_album_thumbnails (by album). Read-only.
Args:
asset_id: The asset's UUID.
size: 'thumbnail' (250px, fast) or 'preview' (1440px, higher quality). Default: 'thumbnail'.
Returns: JSON with 'data' (base64 string) and 'type' (MIME type, e.g. 'image/jpeg').
Inputs: asset_id (required), size. Returns: result
get_calendar_heatmapGet calendar heatmapARead-only
How many photos per day, over a date range — the data behind a calendar heatmap. Use this to find gaps (months with nothing), busy periods, or to check a library's health at a glance without listing assets. Immich 3.x answers natively; on Immich 2.x the same shape is built from the timeline (taken dates only), which costs one request per month in the range, so pass the narrowest range that answers the question. Read-only.
Args:
from_date: ISO date lower bound (e.g. '2026-01-01'). Omit for the server
default on Immich 3.x; on 2.x an omitted bound means the last 365 days,
because an open-ended range would walk every month of the library.
to_date: ISO date upper bound. Omit for the server default.
heatmap_type: 'Taken' (capture date, default) or 'Upload' (when it reached
Immich; 3.x only).
Returns: JSON with source ('immich' or 'timeline'), total and a series of
{date, count} for the days that have activity, oldest first (a day missing
from the series had nothing).
Inputs: to_date, from_date, heatmap_type. Returns: result
get_capabilitiesGet capabilitiesARead-only
What this Immich server can do: version, feature flags and known quirks. Use this once at the start of a session to learn whether OCR, smart search or facial recognition are available before offering them, and which behaviours differ between Immich 2.x and 3.x. Read-only.
Returns: JSON with server_version, immich_major, features (the server's own
flags: ocr, smartSearch, facialRecognition, map, trash...) and quirks (plain
sentences about version-specific behaviour the caller should know). When the
API key may not read the feature flags, features is empty and a note says so;
the version and the quirks still come back.
Inputs: no named parameters. Returns: result
get_connection_infoGet connection infoARead-only
Return the Immich base URL and a masked API key. Use this to populate gallery template placeholders (e.g. {{IMMICH_URL}}). The API key is intentionally masked for security — thumbnails use base64 data URIs, not direct API calls. Read-only.
Returns: JSON with base_url and api_key_masked (first 8 + last 4 chars only).
Inputs: no named parameters. Returns: result
get_download_infoGet download infoARead-only
How big the zip of an album or selection would be, BEFORE building it. Use this to warn the user about the size (originals and videos add up fast) and then decide whether to call download_archive. Read-only.
Args:
album_id: Size the whole album.
asset_ids: Or size just these assets.
Returns: JSON with total_size_mb, asset_count and the number of archives
Immich would split the download into.
Inputs: album_id, asset_ids. Returns: result
get_duplicatesGet duplicatesARead-only
Get ML-detected duplicate asset groups (same image stored more than once). Use this to review potential duplicates before resolving them with resolve_duplicates. Requires Immich ML service. Note: "duplicates" means the same picture, not the same person — for people use get_album (assets[].people) or get_asset_faces. Read-only.
Args:
album_id: Optional. Restrict to groups that touch this album; each group then also
reports which of its assets are inside/outside the album.
Returns: JSON array of duplicate groups (each with duplicateId, assets array, and similarity scores).
Inputs: album_id. Returns: result
get_export_previewGet export previewARead-only
List what export_pdf would include (id, type, filename, date, place, people, video duration) so you know which assets exist before looking at images and writing captions. Pass exactly one of album_id / asset_ids. Read-only.
The result also carries `options`: every choice export_pdf accepts, with its
default. When the user asked for a PDF without saying how they want it, show
them these choices and ask (layout, cover pages, which video moments, captions)
before exporting; when they did give specs, or just want "a PDF, defaults are
fine", export directly.
Args:
album_id: Album UUID, or
asset_ids: Explicit asset UUIDs (search results, a selection).
limit: Max assets (1-500, default 100).
Returns: JSON {title, count, assets:[...], warnings:[...]} or {"error": ...}.
Inputs: limit, album_id, asset_ids. Returns: result
get_images_batchGet images batchARead-only
Get thumbnails for arbitrary asset IDs as image blocks for inline visual display. Use this to visually show search results in clients that render images. For HTML gallery generation with base64 data URIs (Cowork/skills), use get_thumbnails_batch instead — it returns JSON with filenames and dates. Read-only.
Args:
asset_ids: List of asset UUIDs to fetch thumbnails for.
size: 'thumbnail' (250px) or 'preview' (1440px). Default: 'thumbnail'.
limit: Max thumbnails to return (1-50, default 20). Only the first N IDs are fetched.
Returns: A list of image blocks suitable for visual display.
Inputs: asset_ids (required), size, limit.
get_map_markersGet map markersARead-only
Get GPS map markers for all geotagged assets. Use this to discover where photos were taken or to build travel maps. For searching by city/country name, use search_metadata instead. Read-only. Returns up to 500 markers.
Args:
file_created_after: ISO date lower bound (e.g. '2023-01-01').
file_created_before: ISO date upper bound.
is_favorite: If true, only return favorites.
Returns: JSON with total count and markers array (each with asset ID, lat, lon).
Inputs: is_favorite, file_created_after, file_created_before. Returns: result
get_personGet personARead-only
Get full details for a specific person including name, birth date, and photo count. Use this after finding a person via list_people or search_people. Read-only.
Args:
person_id: The person's UUID (from list_people or search_people).
Returns: JSON with person details (id, name, birthDate, isHidden, photoCount, thumbnailPath).
Inputs: person_id (required). Returns: result
get_person_thumbnailGet person thumbnailARead-only
Get a base64-encoded face crop thumbnail for a person. Use this to visually identify a person before merging or renaming. Read-only.
Args:
person_id: The person's UUID.
Returns: JSON with 'data' (base64 string of face crop) and 'type' (MIME type).
Inputs: person_id (required). Returns: result
get_server_versionGet server versionARead-only
Get the Immich server version. Use this to check compatibility or report the running server version. Read-only.
Returns: JSON with major, minor, and patch version numbers.
Inputs: no named parameters. Returns: result
get_stackGet stackARead-only
One stack with its assets. Use this after list_stacks to see everything a group holds before changing its cover or dissolving it, or to check what create_stack actually grouped. Read-only.
Args:
stack_id: The stack to fetch.
Returns: JSON with id, primary_asset_id and the asset list.
Inputs: stack_id (required). Returns: result
get_statisticsGet statisticsARead-only
Get library statistics. Use this for a quick overview of library size without listing individual assets. Read-only.
Returns: JSON with total photo count, video count, and storage usage in bytes.
Inputs: no named parameters. Returns: result
get_tagGet tagBRead-only
Get details for a specific tag. Use this to inspect a tag's properties. Read-only.
Args:
tag_id: The tag's UUID (from list_tags).
Returns: JSON with tag id, name, color, and usage count.
Inputs: tag_id (required). Returns: result
get_thumbnails_batchGet thumbnails batchARead-only
Get base64-encoded thumbnails for arbitrary asset IDs without needing an album. Use this to visually display search results or any ad-hoc set of photos. For album-based thumbnails, use get_album_thumbnails. For a single photo, use get_asset_thumbnail. Read-only.
Args:
asset_ids: List of asset UUIDs to fetch thumbnails for.
size: 'thumbnail' (250px) or 'preview' (1440px). Default: 'thumbnail'.
limit: Max thumbnails to return (1-50, default 20). Only the first N IDs are fetched.
Returns: JSON with thumbnails array (each with asset_id, base64 data, filename, date).
Inputs: asset_ids (required), size, limit. Returns: result
get_timeline_bucketGet timeline bucketARead-only
The assets of one month bucket from get_timeline_buckets. Use the two tools together to walk a library month by month without expensive searches. Read-only.
Args:
time_bucket: The bucket key exactly as get_timeline_buckets returned it
(e.g. '2026-03-01').
album_id: Only assets in this album.
person_id: Only assets showing this person.
tag_id: Only assets carrying this tag.
is_favorite: If true, only favorites.
Returns: JSON with an assets array; each row has asset_id, date, is_image,
is_favorite, duration, city and country.
Inputs: time_bucket (required), tag_id, album_id, person_id, +1 more. Returns: result
get_timeline_bucketsGet timeline bucketsARead-only
Month-by-month map of the library: one bucket per month with its asset count. Use this before fetching assets — it shows in one cheap call which months hold photos and how many, ideal for finding gaps, busy periods, or navigating a large library without paging through everything. Read-only.
Args:
album_id: Only count assets in this album.
person_id: Only count assets showing this person.
tag_id: Only count assets carrying this tag.
is_favorite: If true, only count favorites.
order: 'desc' for newest month first (the default), 'asc' for oldest first.
Returns: JSON with total_buckets and a buckets array of {timeBucket, count},
newest month first unless order='asc'.
Inputs: order, tag_id, album_id, person_id, +1 more. Returns: result
get_video_framesGet video framesARead-only
Get frames of a video as image blocks, to "watch" a clip. Immich keeps one poster per video; this downloads the video and cuts frames locally (PyAV, a dependency since 1.7.1, or ffmpeg on PATH). Every frame is one image for the model. Workflow: 6 frames first; to look closer, narrow with start/end or use interval (down to 1 s). Above 12 frames the tool returns a JSON plan with frames_planned and estimated_tokens instead of images: show it to the user and call again with confirm=true only if they agree. Hard cap 120 per call. For base64 JSON with timestamps use get_video_frames_json. Read-only.
Args:
asset_id: The video asset's UUID.
count: Frames evenly spaced over the segment (default 6). Ignored when interval > 0.
size: 'thumbnail' (250px, ~1.6k tokens per frame) or 'preview' (1440px, ~6.4k). Default 'thumbnail'.
start: Segment start in seconds (default 0).
end: Segment end in seconds (0 = to the end).
interval: One frame every N seconds instead of count (1 = one per second, the maximum granularity).
confirm: Required (true) when more than 12 frames would be produced; ask the user first.
sheet: Pack the frames into contact sheets (30 per image, timestamps burned in):
a long video becomes one or two images instead of dozens, so no
confirmation is needed. Use it to skim, then cut the moments that matter.
Returns: JPEG image blocks in time order, or JSON (confirmation plan / error).
Inputs: asset_id (required), end, size, count, +4 more.
get_video_frames_jsonGet video frames JSONARead-only
Frames of a video as base64 JPEG with timestamps, for HTML galleries and skills. Same parameters, gate (confirm above 12) and cap (120) as get_video_frames. Read-only.
Args:
asset_id: The video asset's UUID.
count: Frames evenly spaced over the segment (default 6). Ignored when interval > 0.
size: 'thumbnail' (250px, ~1.6k tokens per frame) or 'preview' (1440px, ~6.4k). Default 'thumbnail'.
start: Segment start in seconds (default 0).
end: Segment end in seconds (0 = to the end).
interval: One frame every N seconds instead of count (1 = one per second, the maximum granularity).
confirm: Required (true) when more than 12 frames would be produced; ask the user first.
Returns: JSON {asset_id, duration, backend, count, frames:[{timestamp, data, type}]},
a confirmation plan {confirm_required, frames_planned, estimated_tokens, ...}, or {"error": ...}.
Inputs: asset_id (required), end, size, count, +3 more. Returns: result
list_activitiesList activitiesARead-only
Comments and likes on a shared album, newest context included. Use this to read what the people an album is shared with have said about it or about one of its photos. Read-only.
Args:
album_id: The album whose activity to read.
asset_id: Only activity on this asset within the album.
activity_type: 'comment' or 'like'. Omit for both.
Returns: JSON with total and an activities array (id, type, comment, asset_id,
user name, created_at).
Inputs: album_id (required), asset_id, activity_type. Returns: result
list_albumsList albumsARead-only
List albums in the library with summary info. Use this to discover existing albums before creating new ones or to find an album ID. On a library with thousands of albums, filter with name_contains or page with size instead of listing everything: the full list can be too large for the context. Read-only.
Args:
shared: true = only shared albums, false = only non-shared, omit = all albums.
name_contains: Only albums whose name contains this text (case-insensitive). Omit for all.
page: Page number, starting from 1 (default 1). Only used when size is given.
size: Albums per page (1-500). Omit or 0 = every album in one response (the default).
compact: true = only id, albumName and assetCount per album (smaller response).
Returns: JSON with total count (after the filter), the albums array (each with id, name,
description, assetCount, shared status), and page/size/pages when paging.
Inputs: page, size, shared, compact, +1 more. Returns: result
list_assetsList assetsARead-only
List assets with simple filters (no search query needed). Use this to browse the library by status (favorites, archived, trashed) or type. For finding specific content, use search_metadata (structured) or search_smart (visual AI). Read-only.
Args:
is_favorite: true = only favorites, false = only non-favorites, omit = all.
is_archived: true = only archived, false = only non-archived, omit = all.
is_trashed: true = only trashed items; false/omit = active library (Immich never mixes both).
asset_type: 'IMAGE' or 'VIDEO'. Omit for both.
page: Page number, starting from 1 (default 1).
size: Results per page (1-200, default 50).
Returns: JSON with total count, current page, and assets array with IDs, filenames, dates, and types.
Inputs: page, size, asset_type, is_trashed, +2 more. Returns: result
list_memoriesList memoriesARead-only
List memories — Immich's "on this day" collections of photos from past years. Use this to build a 'tal día como hoy' story, album or PDF: each memory carries the year it looks back to and the assets Immich picked for it. Read-only.
Args:
for_date: ISO date — return the memories Immich shows on that day
(e.g. today for the classic on-this-day feed). Omit for all memories.
is_saved: If true, only memories the user saved; if false, only unsaved.
size: Maximum memories to return (default 50).
Returns: JSON with total and a memories array; each has id, type, memory_at,
the year it remembers, is_saved, asset_count and a trimmed assets list
(id, filename, date).
Inputs: size, for_date, is_saved. Returns: result
list_partnersList partnersARead-only
Who shares their library with this account, and who this account shares with. Partner sharing is Immich's family feature: each side keeps its own library but can see the other's. Read-only.
Returns: JSON with shared_with_me and shared_by_me arrays
(id, name, email, in_timeline).
Inputs: no named parameters. Returns: result
list_peopleList peopleARead-only
List all recognized people (face clusters) in the library. Use this to browse who appears in the photo library or find a person's ID. For searching by name, use search_people instead. Read-only.
Args:
page: Page number, starting from 1 (default 1).
size: Results per page (default 50).
with_hidden: Include people marked as hidden (default false).
Returns: JSON with total count, page, and people array (each with id, name, thumbnailPath, photoCount).
Inputs: page, size, with_hidden. Returns: result
list_stacksList stacksARead-only
List every stack in the library. Use this to see what is already grouped before creating new stacks or to find a stack's id. Read-only.
Args:
primary_asset_id: Only the stack fronted by this asset.
Returns: JSON with total and a stacks array (id, primary_asset_id, assets).
Inputs: primary_asset_id. Returns: result
list_tagsList tagsARead-only
List all tags in the library. Use this to discover existing tags before creating new ones or to find a tag ID for tagging operations. Read-only.
Returns: JSON with total count and tags array (each with id, name, color).
Inputs: no named parameters. Returns: result
list_usersList usersARead-only
The users visible on this Immich server. Use this to find the id that create_partner needs, or to see who could be shared with. Read-only.
Returns: JSON with total and a users array of {id, name, email}.
Inputs: no named parameters. Returns: result
merge_peopleMerge peopleADestructive
Merge multiple person clusters into one. Use this when the same real person has been split into multiple face clusters. DESTRUCTIVE and IRREVERSIBLE: merged persons are permanently deleted and all their faces transfer to the target. Without confirm=true nothing happens: the call returns who would be kept and who would disappear, so the user can check the names before the merge. Side effect: with confirm=true, permanently deletes the merged persons; without it the call only previews and changes nothing.
Args:
person_id: The target person UUID to keep (receives all merged faces).
merge_ids: List of person UUIDs to absorb into the target. These persons are permanently deleted.
confirm: Pass true only after the user has seen the preview and agreed.
Returns: JSON with the preview (confirm_required, keep, merge, failed) or the
merge result.
Inputs: merge_ids (required), person_id (required), confirm. Returns: result
pingPingARead-only
Check Immich server connectivity. Use this to verify the server is reachable before running other operations. Read-only.
Returns: JSON with 'server' status ('pong' if healthy).
Inputs: no named parameters. Returns: result
reassign_faceReassign faceADestructive
Reassign a detected face to a different person. Use this to correct face recognition mistakes (e.g. a face wrongly attributed to Person A should be Person B). Get face_id from get_asset_faces first. Side effect: permanently changes face-to-person mapping.
Args:
face_id: The face detection UUID (from get_asset_faces results).
person_id: The correct person UUID to assign this face to.
Returns: JSON with the updated face assignment.
Inputs: face_id (required), person_id (required). Returns: result
record_actionRecord actionA
Remember something the plugin did to assets and why, for audit or undo: which album they went into and from what prompt, what date they had before a fix, why they were rotated. Side effect: writes the plugin's metadata key on each asset; other apps' keys are untouched.
Args:
asset_ids: The assets the action touched.
action: Short verb-like label (e.g. 'added_to_album', 'date_fixed', 'rotated').
detail: Free text with the context worth keeping (album name, previous
value, the user's request).
Returns: JSON with success, the number of assets recorded, the action, and a
failed array of {asset_id, error} for any asset that could not be written.
Success is true only when nothing failed.
Inputs: action (required), asset_ids (required), detail. Returns: result
remove_assets_from_albumRemove assets from albumADestructive
Remove assets from an album without deleting them. The photos remain in the library and other albums. Use this to un-curate mistakenly added assets. Side effect: modifies album membership.
Args:
album_id: Album UUID to remove assets from.
asset_ids: List of asset UUIDs to remove from this album.
Returns: JSON with album_id, count removed, and per-asset result details.
Inputs: album_id (required), asset_ids (required). Returns: result
remove_partnerRemove partnerADestructive
Stop sharing this account's library with a user. Their own photos are not touched. Side effect: revokes their access.
Args:
user_id: The user to unshare with.
Returns: JSON confirming the removal.
Inputs: user_id (required). Returns: result
resolve_duplicatesResolve duplicatesADestructive
Resolve duplicate groups by choosing which assets to keep and which to trash. Use this after reviewing results from get_duplicates. Trashed assets can still be recovered via restore_assets. Side effect: moves rejected duplicates to trash.
Args:
groups: List of dicts, each with: duplicateId (from get_duplicates), assetIds (UUIDs to KEEP), trashIds (UUIDs to TRASH).
Returns: JSON with count of resolved groups.
Inputs: groups (required). Returns: result
restore_assetsRestore assetsA
Restore specific assets from trash back to the active library. Use this to selectively recover accidentally deleted photos. For restoring everything at once, use restore_trash instead. Side effect: moves specified assets out of trash.
Args:
asset_ids: List of asset UUIDs currently in trash to restore.
Returns: JSON with count of restored assets.
Inputs: asset_ids (required). Returns: result
restore_trashRestore trashA
Restore ALL trashed assets back to the library. Use this to undo an accidental bulk deletion. For restoring specific assets only, use restore_assets instead. Side effect: moves all trashed assets back to the active library.
Returns: JSON with success confirmation.
Inputs: no named parameters. Returns: result
reverse_geocodeReverse geocodeARead-only
Resolve GPS coordinates to a place name using Immich's own offline geodata. Use this to name the location of a marker from get_map_markers or of an asset's EXIF coordinates — no external service is contacted. Read-only.
Args:
lat: Latitude in decimal degrees.
lon: Longitude in decimal degrees.
Returns: JSON with total and a places array of {city, state, country} candidates.
Inputs: lat (required), lon (required). Returns: result
revert_asset_editsRevert asset editsADestructive
Remove all non-destructive edits (rotation, crop, mirror) from assets, restoring original appearance. Use this to undo rotate_assets or any other display transforms. Provide EITHER asset_ids OR album_id. Side effect: deletes all edit records for the assets.
Args:
asset_ids: List of asset UUIDs to revert. Mutually exclusive with album_id.
album_id: Revert all assets in this album. Mutually exclusive with asset_ids.
Returns: JSON with reverted/failed counts.
Inputs: album_id, asset_ids. Returns: result
review_assetsReview assetsA
Remember a review verdict on assets, with the reason, so a later session does not redo the analysis and the why survives. Use this after deciding what to do with a photo in a cleanup or duplicate pass — together with a tag when the user must see the state in Immich (tags are visible there, notes are not). Side effect: writes the plugin's metadata key on each asset; other apps' keys are untouched.
Args:
asset_ids: The assets the verdict applies to.
verdict: One of 'keep', 'delete_candidate', 'duplicate_of', 'needs_check'.
reason: Free text explaining the verdict (e.g. 'near-identical to IMG_6367,
keep that one'). Short and concrete beats long.
Returns: JSON with success, the number of assets reviewed, the verdict, and a
failed array of {asset_id, error} for any asset that could not be written.
Success is true only when nothing failed.
Inputs: verdict (required), asset_ids (required), reason. Returns: result
rotate_assetsRotate assetsA
Apply a non-destructive clockwise rotation to one or more assets. Use this to fix orientation issues. The original file is never modified — rotation is a display transform only. Use revert_asset_edits to undo. Provide EITHER asset_ids OR album_id. Side effect: writes rotation edits to Immich; accumulates with existing rotation.
Args:
angle: Clockwise degrees, must be a multiple of 90 (90, 180, or 270). Default: 90.
asset_ids: List of asset UUIDs to rotate. Mutually exclusive with album_id.
album_id: Rotate all assets in this album. Mutually exclusive with asset_ids.
Returns: JSON with rotated/failed counts and the applied angle.
Inputs: angle, album_id, asset_ids. Returns: result
search_citiesSearch citiesARead-only
Every city that appears in the library, one representative asset each. Unlike search_explore this has no minimum-asset threshold, so it is the reliable way to answer 'which places are in this library?'. Read-only.
Returns: JSON with a cities array of {city, country, asset_id, date}.
Inputs: no named parameters. Returns: result
search_exploreSearch exploreARead-only
Overview of what the library contains, grouped by explore field: one representative asset per city and per detected concept (Immich's Explore page). Use this to get oriented in an unknown library before searching for anything specific — it answers 'what is in here?' in one call. A city only appears once it holds at least 5 assets (Immich's own threshold), so small libraries can come back empty. Read-only.
Returns: JSON with total (how many fields came back) and a fields array; each
field has its name (e.g. 'exifInfo.city') and items pairing each value with one
representative asset_id.
Inputs: no named parameters. Returns: result
search_large_assetsSearch large assetsARead-only
The biggest files in the library, largest first. Use this to find what is eating storage before a cleanup — videos and originals show up immediately. Read-only.
Args:
min_size_mb: Only assets at least this many megabytes (0 = no minimum).
size: How many assets to return (1-200, default 20).
asset_type: 'IMAGE' or 'VIDEO'. Omit for both.
Returns: JSON with total and an assets array of {asset_id, filename, size_mb,
date}, largest first.
Inputs: size, asset_type, min_size_mb. Returns: result
search_metadataSearch metadataARead-only
Search assets by EXIF metadata fields. Use this when you know specific criteria like city, camera model, or date range. For natural language visual queries (e.g. 'sunset at the beach'), use search_smart instead. For browsing without criteria, use list_assets. Read-only.
Args:
city: City name from EXIF GPS reverse-geocoding (case-sensitive, e.g. 'Barcelona').
state: State or region name.
country: Country name (e.g. 'Spain', 'Egypt').
make: Camera manufacturer (e.g. 'Apple', 'Canon', 'Sony').
model: Camera model string (e.g. 'iPhone 14 Pro', 'EOS R5').
taken_after: ISO date — return only assets captured after this date.
taken_before: ISO date — return only assets captured before this date.
is_favorite: If true, only return favorites.
asset_type: 'IMAGE' or 'VIDEO'. Omit for both.
ocr: Text recognized inside the image (tickets, signs, documents). Needs
OCR enabled on the server — check with get_capabilities.
person_ids: Only assets showing ALL of these people (ids from list_people).
tag_ids: Only assets carrying these tags (ids from list_tags).
album_ids: Only assets inside these albums.
page: Page number, starting from 1 (default 1).
size: Results per page (1-200, default 50).
Returns: JSON with total match count, current page, and assets array with IDs, filenames, and dates.
Inputs: ocr, city, make, page, +11 more. Returns: result
search_peopleSearch peopleARead-only
Search for people by name (partial match). Use this when you know the person's name. For browsing all people, use list_people instead. Read-only.
Args:
name: Full or partial name to match (case-insensitive).
with_hidden: Include hidden people in results (default false).
Returns: JSON array of matching people with id, name, and photo count.
Inputs: name (required), with_hidden. Returns: result
search_placesSearch placesARead-only
Look a place name up in Immich's built-in gazetteer (no assets involved). Use this to resolve a spelling or get coordinates for a place before a geographic search. Read-only.
Args:
name: Place name to look for (e.g. 'Lisbon').
Returns: JSON with a places array of {name, admin1name, admin2name, latitude,
longitude}.
Inputs: name (required). Returns: result
search_randomSearch randomARead-only
Random assets from the library, optionally filtered. Use this for sampling — a quick feel of what a filter matches, a surprise pick for a story, or spot checks over a big library. Read-only.
Args:
size: How many random assets to return (default 10, max 100).
city: Only assets from this city.
country: Only assets from this country.
make: Only assets from this camera make.
model: Only assets from this camera model.
is_favorite: If true, only favorites.
ocr: Only assets whose recognized text matches (needs OCR on the server).
Returns: JSON with the matching assets array.
Inputs: ocr, city, make, size, +3 more. Returns: result
search_smartSearch smartARead-only
AI-powered visual search using CLIP embeddings. Use this when describing what a photo looks like in natural language (e.g. 'sunset at the beach', 'dog playing fetch'). For structured criteria (city, camera, date), use search_metadata instead. Requires Immich ML service with Smart Search enabled. Read-only.
Args:
query: Natural language description of the visual content to find.
city: Optional city filter to narrow results geographically.
state: Optional state/region filter.
country: Optional country filter.
taken_after: ISO date — only assets captured after this date.
taken_before: ISO date — only assets captured before this date.
ocr: Text recognized inside the image, combined with the visual query.
Needs OCR enabled on the server — check with get_capabilities.
person_ids: Only assets showing ALL of these people (ids from list_people).
tag_ids: Only assets carrying these tags (ids from list_tags).
album_ids: Only assets inside these albums.
page: Page number, starting from 1 (default 1).
size: Results per page (1-200, default 50).
Returns: JSON with total count, page, and assets ranked by visual similarity to the query.
Inputs: query (required), ocr, city, page, +8 more. Returns: result
search_statisticsSearch statisticsARead-only
Count how many assets match a filter WITHOUT fetching them. Use this instead of search_metadata whenever only the number matters ('how many photos from Spain?', 'how many did I take in 2019?') — it costs one integer instead of pages of assets. Read-only.
Args:
city: Count assets from this city.
country: Count assets from this country.
state: Count assets from this state/region.
make: Count assets from this camera make.
model: Count assets from this camera model.
is_favorite: If true, count only favorites.
ocr: Count assets whose recognized text matches (needs OCR on the server).
created_after: ISO date lower bound on upload date (when it reached Immich).
created_before: ISO date upper bound on upload date.
taken_after: ISO date lower bound on capture date (when the photo was taken).
taken_before: ISO date upper bound on capture date.
Returns: JSON {total}.
Inputs: ocr, city, make, model, +7 more. Returns: result
search_suggestionsSearch suggestionsARead-only
Distinct values present in the library for one field — the exact spellings search_metadata expects. Use this before filtering by city or camera to avoid guessing (e.g. 'iPhone 14 Pro' vs 'iPhone14,3'). Read-only.
Args:
suggestion_type: One of 'country', 'state', 'city', 'camera-make',
'camera-model', 'camera-lens-model'.
country: Narrow city/state suggestions to this country.
state: Narrow city suggestions to this state.
make: Narrow model suggestions to this camera make.
model: Narrow lens suggestions to this camera model.
Returns: JSON with total and a suggestions array of strings.
Inputs: suggestion_type (required), make, model, state, +1 more. Returns: result
tag_assetsTag assetsA
Apply a tag to multiple assets at once. Use this to bulk-categorize photos (e.g. tag all vacation photos). Side effect: adds tag association to assets.
Args:
tag_id: The tag UUID to apply (from list_tags or create_tag).
asset_ids: List of asset UUIDs to tag. Must not be empty.
Returns: JSON with tag_id, count tagged, and per-asset results.
Inputs: tag_id (required), asset_ids (required). Returns: result
untag_assetsUntag assetsADestructive
Remove a tag from multiple assets. The tag itself remains; only the association is removed. Side effect: removes tag-to-asset links.
Args:
tag_id: The tag UUID to remove from assets.
asset_ids: List of asset UUIDs to untag. Must not be empty.
Returns: JSON with tag_id, count untagged, and per-asset results.
Inputs: tag_id (required), asset_ids (required). Returns: result
update_albumUpdate albumADestructive
Update an album's name or description. Use this to rename or re-describe an existing album. Side effect: modifies album metadata in Immich.
Args:
album_id: The album's UUID.
name: New album name. Leave empty to keep current name.
description: New description. Leave empty to keep current description.
Returns: JSON with the updated album object.
Inputs: album_id (required), name, description. Returns: result
update_asset_metadataUpdate asset metadataADestructive
Update metadata fields on a specific asset. Use this to fix dates, correct GPS, add descriptions, or change favorite/rating status. Only provided fields are modified. Side effect: permanently changes asset metadata in Immich.
Args:
asset_id: The asset's UUID.
date_time_original: ISO 8601 datetime (e.g. '2019-07-14T15:23:41.000Z').
latitude: GPS latitude, decimal degrees (-90.0 to 90.0).
longitude: GPS longitude, decimal degrees (-180.0 to 180.0).
description: Free-text description/caption for the asset.
is_favorite: Set favorite status (true/false).
rating: -1 to reject the photo, or 1 to 5 stars. A rating cannot be
cleared from here, and 0 is not a rating Immich 3.x accepts.
Returns: JSON with the updated asset object.
Inputs: asset_id (required), rating, latitude, longitude, +3 more. Returns: result
update_assets_metadataUpdate assets metadataADestructive
Update the same metadata fields on many assets in ONE call — the whole roll of a scanned album gets its real date, a trip's photos get their GPS, a selection becomes favorites. Same fields as update_asset_metadata; only the provided ones change. Side effect: permanently changes the metadata of every listed asset.
Args:
asset_ids: The assets to update.
date_time_original: ISO 8601 datetime applied to all of them.
latitude: GPS latitude, decimal degrees.
longitude: GPS longitude, decimal degrees.
description: Description/caption applied to all of them.
is_favorite: Set favorite status on all of them.
rating: -1 to reject them, or 1 to 5 stars. A rating cannot be cleared
from here, and 0 is not a rating Immich 3.x accepts.
Returns: JSON with success and the number of assets updated.
Inputs: asset_ids (required), rating, latitude, longitude, +3 more. Returns: result
update_credentialsUpdate credentialsADestructive
Update the Immich connection credentials. Use this when the API key has been rotated or the server URL changed. Validates credentials before applying. Side effect: persists new credentials to disk and hot-swaps the live connection.
Args:
base_url: Full Immich server URL including protocol (e.g. 'https://photos.example.com').
api_key: A valid Immich API key (generated in Immich > User Settings > API Keys).
Returns: JSON with success status, photo/video counts confirming access, and persistence path.
Inputs: api_key (required), base_url (required). Returns: result
update_memoryUpdate memoryADestructive
Update a memory: save it for later, move its date, or mark it seen. Side effect: modifies the memory on the server.
Args:
memory_id: The memory to update.
is_saved: True to save the memory, false to unsave it.
memory_at: New ISO date to show the memory on.
seen_at: ISO timestamp marking when the user viewed it.
Returns: JSON with the updated memory.
Inputs: memory_id (required), seen_at, is_saved, memory_at. Returns: result
update_partnerUpdate partnerADestructive
Show or hide a partner's photos inside the main timeline (they stay reachable either way). Only works on a partner who shares their library with this account (someone in shared_with_me), because the flag controls how THEIR photos appear in THIS timeline. Side effect: updates the setting on the server.
Args:
user_id: The partner whose setting changes.
in_timeline: True to mix their photos into the timeline, false to keep
them separate.
Returns: JSON with the updated partner entry.
Inputs: user_id (required), in_timeline (required). Returns: result
update_personUpdate personADestructive
Update a person's profile details. Use this to name unnamed faces, set birth dates, hide clutter faces, or change the representative thumbnail. Only provided fields are modified. Side effect: changes person metadata in Immich.
Args:
person_id: The person's UUID.
name: Display name (e.g. 'John Smith'). Set to name unnamed face clusters.
birth_date: ISO date (e.g. '1990-05-15').
is_hidden: Hide from the People view (useful for strangers/clutter).
is_favorite: Mark as a favorite person.
feature_face_asset_id: Asset UUID whose face crop becomes the person's thumbnail.
color: Hex color label for UI grouping.
Returns: JSON with the updated person object.
Inputs: person_id (required), name, color, is_hidden, +3 more. Returns: result
update_stackUpdate stackADestructive
Change which asset fronts a stack (the one the library shows). Side effect: updates the stack on the server.
Args:
stack_id: The stack to update.
primary_asset_id: The asset that should become the cover. It must already
belong to the stack.
Returns: JSON with the updated stack.
Inputs: stack_id (required), primary_asset_id (required). Returns: result
update_tagUpdate tagADestructive
Update a tag's color. Side effect: changes apply to all assets using this tag.
Immich's API cannot rename a tag (TagUpdateDto only carries color); to rename,
create_tag with the new name, tag_assets, then delete_tag the old one.
Args:
tag_id: The tag's UUID.
name: Not supported by Immich — passing it returns an error explaining the workaround.
color: New hex color (e.g. '#FF5733'). Omit to keep current.
Returns: JSON with the updated tag object.
Inputs: tag_id (required), name, color. Returns: result
upload_assetUpload assetA
Upload a local photo or video file to Immich. Use this to ingest new media into the library. Constraints: max 25MB, allowed types: jpg, jpeg, png, heic, mp4, mov, gif, webp. Symlinks are rejected for security. The original file is NOT modified or deleted. Side effect: creates a new asset in Immich.
Args:
file_path: Absolute path to the local file (e.g. '/tmp/photo.jpg'). Must exist.
album_id: Optional album UUID to add the uploaded asset to immediately.
Returns: JSON with new asset id, filename, size_mb, and album assignment status if applicable.
Inputs: file_path (required), album_id. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 94 tools
Most tools target a distinct resource+action and descriptions actively cross-reference each other (e.g. search_metadata vs search_smart vs list_assets), which greatly reduces misselection. However, several near-identical families exist — six thumbnail/image tools (get_asset_thumbnail, get_asset_image, get_thumbnails_batch, get_images_batch, get_album_thumbnails, get_album_images) and get_video_frames vs get_video_frames_json differ only by output encoding, which an agent can easily pick wrongly.
Overwhelmingly consistent snake_case verb_noun pattern (list_albums, create_album, update_album, delete_album; tag_assets/untag_assets; restore_trash/empty_trash). Minor deviations: the bare 'ping', and a '_json' suffix variant rather than a distinct verb, plus search_* is used for both query tools and plain lookups (search_cities, search_places).
94 tools is far beyond a well-scoped surface for a photo library manager, and the set includes many parallel variants (base64 vs image-block thumbnails, video frames JSON vs image, raw vs filtered listing). Individual tools are justified in isolation, but the aggregate is too large to navigate reliably and buries core operations.
Coverage is exhaustive: full CRUD across assets, albums, tags, people, memories, stacks, shared links, partners, activities, trash, duplicates, plus upload/download, PDF export, notes/review bookkeeping and server capability discovery. No meaningful lifecycle gaps remain.
Maintenance
Related MCP Connectors
Search photos and video moments by visual meaning, people, dates, and folders across local libraries, Apple Photos, and OneDrive. Manage collections, groups, favorites, people, and duplicate review. Synced metadata remains searchable while the desktop is offline; local and Apple Photos media access needs the owning device online with remote access enabled. OneDrive media can be accessed directly from the cloud. OAuth or Bearer API keys. Paid Cloud Access required; hosted visual search uses prepaid credits. Text-in-image search is semantic matching, not guaranteed exact OCR. Setup: https://organizewith.ai/en/guides/connect-ai-assistants
Connect AI assistants to your GitHub-hosted Obsidian vault to seamlessly access, search, and analy…
LLM chat, text tools, image generation, editing, batch image jobs, and asynchronous video generation
Transform video, audio and images, and generate media from prompts. FFmpeg, captions, models.
Related MCP Servers
- AlicenseCqualityNot gradedmaintenanceImage Tools MCP is a Model Context Protocol (MCP) service that retrieves image dimensions and compresses images from URLs and local files using the TinyPNG API. It supports converting images to formats like webp, jpeg/jpg, and png, providing detailed information on width, height, type, and compressi221 npm9MIT
- AlicenseAqualityDmaintenanceAn offline MCP server that allows LLMs or humans to extract and analyze metadata from images using the exifr library, supporting various image formats and metadata segments without external tools.1140BSD 2-Clause "Simplified"
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to search, browse, and retrieve metadata and images from your Google Photos library. It supports content-based filtering, album listing, and location extraction via STDIO and HTTP transports.56MIT
- FlicenseAqualityDmaintenanceAn MCP server for Immich self-hosted photo management that provides AI-accessible tools for browsing, searching, organizing, and managing photo libraries with duplicate detection and safe deletion workflows.431-