Immich MCP Server
Used to expose the MCP server to the internet via a Cloudflare Tunnel so it can be reached by ChatGPT.
Used in verification steps to check the health endpoint of the MCP server.
Used to build and run the MCP server container alongside the Immich server.
Provides tools for searching and retrieving photos and videos from a self-hosted Immich photo library, including CLIP semantic search, EXIF metadata retrieval, filtering by date/place/camera/person, album management, person recognition, library statistics, and public share link creation.
Used to generate a random bearer token for securing the MCP server.
Used to run a smoke test script that validates the MCP server's functionality.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Immich MCP Serveruse immich search to find recent photos of the garden"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Immich MCP Server
Exposes a self-hosted Immich photo library to ChatGPT (and any other MCP client) over Streamable HTTP, so you can ask questions like "find the photos from the Kigali site visit in March" and get real answers from your own NAS.
ChatGPT ──HTTPS──▶ Cloudflare Tunnel ──▶ immich_mcp:8080 ──▶ immich_server:2283
bearer token MCP → REST x-api-keyChatGPT custom connectors only accept a remote HTTPS endpoint — there is no stdio or localhost option. So the server has to be reachable from the internet, hence the tunnel, and it has to defend itself, hence the bearer token.
Contents
Related MCP server: immich-photo-manager
Tools
Tool | Purpose |
| CLIP semantic search over image content |
| Full EXIF for one asset by UUID |
| Filter by date, place, camera, person, favorite |
| All albums with counts |
| One album's details and contents |
| Recognized faces, with IDs for filtering |
| Photo/video counts and disk usage |
| Immich version and enabled features |
| The actual image, inline — for clients that can see pictures |
| A signed, expiring URL to one image — for clients that can't |
| Public link to specific assets — off by default |
Seeing the actual photos
Most tools return text. Two return pixels:
get_image sends the image bytes inline. Claude displays these and can answer
visual questions about them. ChatGPT's connectors currently don't render inline
images, so it's of little use there.
get_image_link returns a URL like
https://your-server/img/<asset-id>/<size>/<expiry>/<signature>.jpg that opens
in any browser. This is the one that works with ChatGPT — the model hands you a
link and you click it.
The link carries an HMAC signature over the asset ID, size, and expiry, so it
needs no bearer token. Tampering with any part returns 403, and the album scope
is re-checked when the link is used rather than trusted from when it was issued.
A leaked link therefore exposes exactly one image at one size until it expires
(IMAGE_LINK_TTL, default 24 hours) — a far smaller blast radius than the
bearer token.
Set MCP_PUBLIC_URL to this server's public address or get_image_link can't
build URLs. Sizes are thumbnail, preview, and original; get_image refuses
anything over MAX_IMAGE_BYTES and suggests a smaller size.
search and fetch are named deliberately: ChatGPT's Deep Research mode ignores
every other tool, so those two carry the load if Developer Mode is unavailable.
Restricting access to specific albums
By default the server exposes everything the API key can see. To share only a
subset, put album UUIDs in ALLOWED_ALBUM_IDS:
python list_albums.py # prints every album with its UUIDALLOWED_ALBUM_IDS=a1b2c3d4-...,e5f6g7h8-...Every tool is then confined to assets inside those albums. Immich's smart search
has no album filter, so the server builds the set of allowed asset IDs itself and
drops anything outside it — including direct fetch by UUID, which returns a
refusal rather than the asset. The set is cached for SCOPE_TTL seconds so
photos added to an allowed album appear without a restart.
Two layers are worth combining: scope the Immich API key to a dedicated user,
and set ALLOWED_ALBUM_IDS. The key limits what this server could ever reach;
the album list limits what it actually exposes.
Connecting different clients
The server accepts the token three ways, because MCP clients differ in what they can send:
Client | URL | Auth |
ChatGPT connector |
|
|
Claude connector |
| leave OAuth fields blank |
Claude Code, Cursor, Zed |
|
|
Anything URL-only |
| — |
Claude's custom connector dialog offers only OAuth Client ID and Secret, which this server doesn't implement — hence the path form. Security is equivalent given a high-entropy token, but paths and query strings land in proxy logs and shell history more readily than headers, so prefer the header where the client allows it.
Project layout
immich-mcp/
├── Dockerfile
├── docker-compose.yml
├── .env <- you create this, never commit it
├── .env.example
├── .dockerignore
├── preflight.py <- validate config before building
├── run_local.py <- run without Docker, with auto-reload
├── list_albums.py <- discover album UUIDs for scoping
├── smoke_test.py <- full MCP handshake test
├── cloudflared/
│ └── config.example.yml
└── app/
├── immich_mcp.py
└── requirements.txtapp/ matters. If those two files end up next to the Dockerfile instead of
inside app/, the build fails with "/app/immich_mcp.py": not found.
Part 1 — Get it working on your laptop
Optional, but a much faster loop than rebuilding an image for every config change. Everything here also works on the NAS.
1. Get an Immich API key
Immich → Account Settings → API Keys → New API Key. Scope it read-only unless you plan to enable share links.
Immich 3.x scopes keys per endpoint. The tools here need read access to assets,
albums, people, and search. /users/me is not required — preflight reports a
missing user.read scope as a warning, not a failure.
2. Configure
cp .env.example .env
openssl rand -hex 32 # paste into MCP_BEARER_TOKEN
$EDITOR .envFrom the laptop, IMMICH_URL must be the NAS LAN address — immich_server
is a Docker container name that only resolves on the NAS itself:
IMMICH_URL=http://192.168.0.43:2283Note there's no second http://. A doubled scheme is an easy paste error and
produces a confusing hostname failure.
3. Preflight
pip install httpx python-dotenv
python preflight.pyChecks the project layout, validates .env, opens a TCP connection to Immich,
confirms the API key can read assets, and reports whether smart search and face
recognition are actually enabled. It names the exact fix for each failure.
Worth reading the asset count it reports. If it says 1 asset visible and your library has thousands, the key belongs to a user who owns almost nothing — everything downstream will work perfectly and return nothing.
4. Run without Docker
pip install -r app/requirements.txt
python run_local.pyServes on http://127.0.0.1:8099 with auto-reload. In a second terminal:
python smoke_test.py http://127.0.0.1:8099 <your-bearer-token>This runs the exact handshake ChatGPT does — initialize, tools/list, then a live tool call — and confirms unauthenticated requests get a 401.
5. Build the image (optional on the laptop)
If you want to verify the Docker build before shipping it, delete the
networks: block from docker-compose.yml first — immich_default only exists
on the NAS.
docker compose up -d --build
curl.exe http://127.0.0.1:8099/healthzPart 2 — Deploy to the NAS
Four things change: how the container reaches Immich, which Docker network it joins, file ownership, and how the image gets built.
0. Rotate both secrets first
If the Immich API key or bearer token has been pasted into a chat, an email, or a shared doc, treat it as burned. This endpoint is about to face the internet.
Immich → Account Settings → API Keys → delete the old one, create a new one
New bearer token:
openssl rand -hex 32
Do not copy your laptop .env across. It points IMMICH_URL at a LAN
address, which works but routes photo metadata out to the LAN and back for no
reason. Start from .env.example on the NAS.
1. Copy the project across
Put it alongside your other stacks, e.g. /volume1/docker/immich-mcp/. Either
drag it in through File Station or:
scp -r . wanjau@<nas-ip>:/volume1/docker/immich-mcp/Verify app/ survived — File Station drag-and-drop sometimes flattens
directories:
ls -la /volume1/docker/immich-mcp/app/2. Find the real container name and network
SSH in (Control Panel → Terminal & SNMP → Enable SSH), then:
sudo docker ps --format '{{.Names}}\t{{.Image}}' | grep -i immich
sudo docker network ls | grep -i immichContainer Manager often prefixes names with the project, so you may get
immich-immich_server-1 rather than immich_server. Prove the name resolves
from inside Docker, which is what actually matters:
sudo docker run --rm --network <network-name> curlimages/curl:latest \
-s -o /dev/null -w '%{http_code}\n' http://<container-name>:2283/api/server/versionA 200 here means the rest of this is boring. Skipping it means a tunnel 502
later that looks like a Cloudflare problem but isn't.
3. Write .env on the NAS
cd /volume1/docker/immich-mcp
cp .env.example .env
vi .env
chmod 600 .envNow IMMICH_URL uses the container name from step 2, keeping traffic inside
Docker:
IMMICH_URL=http://immich_server:2283
IMMICH_API_KEY=<the new key>
MCP_BEARER_TOKEN=<the new token>
PUBLIC_URL=https://photos.yourdomain.com
ALLOWED_ALBUM_IDS=4. Fix the network name and user
In docker-compose.yml, set the network to whatever step 2 reported:
networks:
immich-net:
external: true
name: immich_default # <- from step 2The Dockerfile creates a user with UID 1027, the usual Synology convention.
Check yours with id; if it differs, either edit the Dockerfile or override in
compose with user: "1026:100". Only matters once you mount volumes — a
mismatch is harmless for now.
5. Preflight and pick albums, from inside a container
DSM's Python is awkward to install packages into, and running preflight on the host wouldn't test container-to-container name resolution anyway:
cd /volume1/docker/immich-mcp
sudo docker run --rm -it \
--network <network-name> \
-v "$PWD":/work -w /work \
python:3.12-slim sh -c "pip install -q httpx python-dotenv && python preflight.py"Same one-liner runs list_albums.py. Copy the UUIDs you want exposed into
ALLOWED_ALBUM_IDS.
6. Build and start
sudo docker compose up -d --build
sudo docker compose logs -f immich-mcpWatch for three lines:
Immich MCP server configured for http://immich_server:2283
Scope: restricted to <album-id>
Album scope refreshed: 1 album(s), N asset(s)If N is 0, the album ID is wrong or the key can't read that album.
Container Manager GUI works too — Project → Create → point at the folder — but it
sometimes struggles with external: true networks. Use SSH if it errors.
7. Verify on the NAS before exposing anything
curl -s http://127.0.0.1:8099/healthz | head -c 300Expect status ok, the Immich version, and your scope summary. Then the full handshake:
sudo docker run --rm -it --network host \
-v "$PWD":/work -w /work \
python:3.12-slim sh -c "pip install -q httpx && \
python smoke_test.py http://127.0.0.1:8099 <bearer-token>"A problem found here is a config problem. The same problem found after the next section looks like a tunnel problem.
Part 3 — Expose it and connect ChatGPT
1. Add the Cloudflare Tunnel hostname
Zero Trust dashboard → Networks → Tunnels → your tunnel → Public Hostname → Add:
Subdomain:
immich-mcpDomain:
yourdomain.comService:
HTTP→immich_mcp:8080
If cloudflared runs as a container it must share a network with immich_mcp for
that name to resolve; if it runs on the host, use http://127.0.0.1:8099. See
cloudflared/config.example.yml for the config-file equivalent.
Do not attach an Access policy. ChatGPT cannot complete an interactive Access login. The bearer token is the only gate — which is why rotating it mattered.
Verify from off the LAN if you can; a phone hotspot is a good test:
python smoke_test.py https://immich-mcp.yourdomain.com <bearer-token>2. Create the connector
Settings → Connectors → Advanced settings → enable Developer Mode (requires a paid plan), then Create:
Name: Immich Photos
Description: this matters — the model reads it to decide whether to invoke the connector. Something like "Personal photo and video library. Use for finding, describing, or listing photos, albums, and recognized people."
URL:
https://immich-mcp.yourdomain.com/mcpAuthentication: API key / custom header →
Authorization: Bearer <token>
Then enable the connector in the chat composer and test with an explicit tool name:
Use immich search to find photos of the drying racks
Operating it
Name the tool in your prompt. ChatGPT won't reliably guess when to reach for a custom connector. "Use immich search to find photos of the drying racks" works where "find my drying rack photos" often doesn't.
ChatGPT can't see your photos. Tool results are text — descriptions and
metadata, not pixels. create_share_link bridges that gap, but a share link is
public to anyone holding the URL, which is why it's disabled by default.
Updating code. Edit app/immich_mcp.py, then sudo docker compose up -d --build.
Rotating the bearer token. Edit .env, docker compose up -d --force-recreate,
then update the connector in ChatGPT. There's a window where ChatGPT is broken —
do it when you're not mid-conversation.
Adding photos to a scoped album. Nothing to do. The scope cache rebuilds
every SCOPE_TTL seconds (default 300).
Auto-start after a reboot. restart: unless-stopped handles it, but
Container Manager projects sometimes need auto-restart ticked in the GUI. Reboot
once deliberately, at a time that suits you, rather than discovering it while
you're away.
Pin your Immich version. The API shifts between releases — /server/statistics
was /server-info/statistics not long ago. Your instance publishes the exact spec
at https://photos.yourdomain.com/api/docs; check there before debugging a 404.
Troubleshooting
Symptom | Cause |
Build: | Files are flat; they belong in |
| Wrong network name — redo Part 2 step 2 |
| Can't reach Immich — wrong |
Container exits immediately | Missing required env var — check |
| Album ID wrong, or the key can't read that album |
Preflight: API key rejected 403 on | Missing |
Preflight: only 1 asset visible | Key belongs to a user who owns almost nothing |
401 on every request | Bearer token mismatch between |
Works on the NAS, 502 through the tunnel | cloudflared can't resolve |
Tunnel returns a login page | An Access policy is attached; remove it |
ChatGPT: "search action not found" | Added in Deep Research mode; enable Developer Mode |
Connector added but never fires | Description too vague, or the tool isn't toggled on in the chat |
| Immich machine learning disabled — check |
Immich rejects the key (401 in logs) | Key revoked, or belongs to a different Immich user |
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
AI-native digital asset management: semantic search, generative image edits, and CDN delivery.
Generate, manage and explore your Switch AI image and video library, scoped to your account.
Holiday photo MCP server: list and fetch personal holiday photos inline in Claude chat.
- PexafyOAuthcom.pexafy
Semantic search over free-to-use stock photos from 9 libraries: by words, image, or similar.
Related MCP Servers
- FlicenseNot 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.39-
- AlicenseAqualityAmaintenanceManage your self-hosted Immich photo library through conversation — natural language search via CLIP, geographic album curation, duplicate detection with perceptual hashing,9445MIT
- AlicenseAqualityAmaintenanceEnables AI assistants to query and export from the macOS Apple Photos library using natural language, backed by osxphotos.2110314MIT

CoreViz MCPofficial
AlicenseNot gradedqualityDmaintenanceExposes a visual library with semantic search, tagging, editing, and management of photos as tools for AI agents like Claude Code.3048MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/wanjau2/Immich-MCP-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server