Skip to main content
Glama
wanjau2

Immich MCP Server

by wanjau2

Servidor MCP de Immich

Expone una biblioteca de fotos de Immich autoalojada a ChatGPT (y a cualquier otro cliente MCP) a través de HTTP Streamable, para que puedas hacer preguntas como "encuentra las fotos de la visita al sitio de Kigali en marzo" y obtener respuestas reales desde tu propio NAS.

ChatGPT  ──HTTPS──▶  Cloudflare Tunnel  ──▶  immich_mcp:8080  ──▶  immich_server:2283
          bearer token                        MCP → REST            x-api-key

Los conectores personalizados de ChatGPT solo aceptan un endpoint HTTPS remoto — no hay opción stdio ni localhost. Por lo tanto, el servidor debe ser accesible desde internet, de ahí el túnel, y debe defenderse, de ahí el token de portador.


Contenido


Related MCP server: immich-photo-manager

Herramientas

Herramienta

Propósito

search

Búsqueda semántica CLIP sobre el contenido de imagen

fetch

EXIF completo para un activo por UUID

search_by_metadata

Filtrar por fecha, lugar, cámara, persona, favorito

list_albums

Todos los álbumes con recuentos

get_album

Detalles y contenido de un álbum

list_people

Rostros reconocidos, con IDs para filtrar

library_stats

Recuentos de fotos/vídeos y uso de disco

server_info

Versión de Immich y funciones habilitadas

create_share_link

Enlace público a activos específicos — desactivado por defecto

search y fetch están nombrados deliberadamente: el modo Deep Research de ChatGPT ignora cualquier otra herramienta, así que esas dos asumen la carga si el Modo Desarrollador no está disponible.

Restringir el acceso a álbumes específicos

Por defecto, el servidor expone todo lo que la clave API puede ver. Para compartir solo un subconjunto, coloca los UUID de los álbumes en ALLOWED_ALBUM_IDS:

python list_albums.py          # prints every album with its UUID
ALLOWED_ALBUM_IDS=a1b2c3d4-...,e5f6g7h8-...

Cada herramienta se limita entonces a los activos dentro de esos álbumes. La búsqueda inteligente de Immich no tiene filtro de álbumes, por lo que el servidor construye el conjunto de IDs de activos permitidos por sí mismo y descarta cualquier cosa fuera de él, incluyendo la recuperación directa por UUID mediante fetch, que devuelve una negativa en lugar del activo. El conjunto se almacena en caché durante SCOPE_TTL segundos, por lo que las fotos añadidas a un álbum permitido aparecen sin necesidad de reiniciar.

Vale la pena combinar dos capas: limita la clave API de Immich a un usuario dedicado y establece ALLOWED_ALBUM_IDS. La clave limita lo que este servidor podría alcanzar; la lista de álbumes limita lo que realmente expone.

Conectar diferentes clientes

El servidor acepta el token de tres maneras, porque los clientes MCP difieren en lo que pueden enviar:

Cliente

URL

Autenticación

Conector ChatGPT

https://host/mcp

Authorization: Bearer <token>

Conector Claude

https://host/<token>/mcp

dejar campos OAuth en blanco

Claude Code, Cursor, Zed

https://host/mcp

--header / cabeceras de config

Cualquier solo URL

https://host/mcp?key=<token>

—

El diálogo del conector personalizado de Claude solo ofrece ID de Cliente OAuth y Secreto, que este servidor no implementa, de ahí la forma de ruta. La seguridad es equivalente dado un token de alta entropía, pero las rutas y cadenas de consulta llegan a los registros del proxy y al historial del shell más fácilmente que las cabeceras, así que prefiere la cabecera cuando el cliente lo permita.

Estructura del proyecto

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.txt

app/ importa. Si esos dos archivos terminan junto al Dockerfile en lugar de dentro de app/, la compilación falla con "/app/immich_mcp.py": not found.


Parte 1 — Hazlo funcionar en tu portátil

Opcional, pero un ciclo mucho más rápido que reconstruir una imagen para cada cambio de configuración. Todo aquí también funciona en el NAS.

1. Obtén una clave API de Immich

Immich → Configuración de la cuenta → Claves API → Nueva clave API. Limítala a solo lectura a menos que planees habilitar enlaces compartidos.

Immich 3.x limita las claves por endpoint. Las herramientas aquí necesitan acceso de lectura a activos, álbumes, personas y búsqueda. /users/me no es necesario — el pre-vuelo informa de un ámbito user.read faltante como una advertencia, no como un fallo.

2. Configura

cp .env.example .env
openssl rand -hex 32          # paste into MCP_BEARER_TOKEN
$EDITOR .env

Desde el portátil, IMMICH_URL debe ser la dirección LAN del NAS — immich_server es un nombre de contenedor Docker que solo se resuelve en el propio NAS:

IMMICH_URL=http://192.168.0.43:2283

Ten en cuenta que no hay un segundo http://. Un esquema duplicado es un error fácil de pegar y produce un fallo de nombre de host confuso.

3. Pre-vuelo

pip install httpx python-dotenv
python preflight.py

Comprueba la estructura del proyecto, valida .env, abre una conexión TCP a Immich, confirma que la clave API puede leer activos e informa si la búsqueda inteligente y el reconocimiento facial están realmente habilitados. Nombra la solución exacta para cada fallo.

Vale la pena leer el recuento de activos que informa. Si dice 1 activo visible y tu biblioteca tiene miles, la clave pertenece a un usuario que posee casi nada — todo el flujo posterior funcionará perfectamente y no devolverá nada.

4. Ejecuta sin Docker

pip install -r app/requirements.txt
python run_local.py

Sirve en http://127.0.0.1:8099 con recarga automática. En un segundo terminal:

python smoke_test.py http://127.0.0.1:8099 <your-bearer-token>

Esto ejecuta el protocolo de enlace exacto que hace ChatGPT: initialize, tools/list, luego una llamada a herramienta en vivo — y confirma que las solicitudes no autenticadas reciben un 401.

5. Construye la imagen (opcional en el portátil)

Si quieres verificar la compilación Docker antes de enviarla, elimina el bloque networks: de docker-compose.yml primero — immich_default solo existe en el NAS.

docker compose up -d --build
curl.exe http://127.0.0.1:8099/healthz

Parte 2 — Despliega en el NAS

Cuatro cosas cambian: cómo el contenedor llega a Immich, qué red Docker une, la propiedad de los archivos y cómo se construye la imagen.

0. Rota ambos secretos primero

Si la clave API de Immich o el token de portador se han pegado en un chat, un correo electrónico o un documento compartido, trátalo como quemado. Este endpoint está a punto de enfrentarse a internet.

  • Immich → Configuración de la cuenta → Claves API → elimina la antigua, crea una nueva

  • Nuevo token de portador: openssl rand -hex 32

No copies tu .env del portátil. Apunta IMMICH_URL a una dirección LAN, lo que funciona pero enruta los metadatos de las fotos a la LAN y de vuelta sin motivo. Comienza desde .env.example en el NAS.

1. Copia el proyecto

Colócalo junto a tus otras pilas, por ejemplo, /volume1/docker/immich-mcp/. Ya sea arrastrándolo a través de File Station o:

scp -r . wanjau@<nas-ip>:/volume1/docker/immich-mcp/

Verifica que app/ sobrevivió — arrastrar y soltar en File Station a veces aplana directorios:

ls -la /volume1/docker/immich-mcp/app/

2. Encuentra el nombre real del contenedor y la red

Conéctate por SSH (Panel de control → Terminal y SNMP → Habilitar SSH), luego:

sudo docker ps --format '{{.Names}}\t{{.Image}}' | grep -i immich
sudo docker network ls | grep -i immich

Container Manager a menudo prefija los nombres con el proyecto, por lo que puedes obtener immich-immich_server-1 en lugar de immich_server. Comprueba que el nombre se resuelve desde dentro de Docker, que es lo que realmente importa:

sudo docker run --rm --network <network-name> curlimages/curl:latest \
  -s -o /dev/null -w '%{http_code}\n' http://<container-name>:2283/api/server/version

Un 200 aquí significa que el resto de esto es aburrido. Omitirlo significa un 502 del túnel más tarde que parece un problema de Cloudflare pero no lo es.

3. Escribe .env en el NAS

cd /volume1/docker/immich-mcp
cp .env.example .env
vi .env
chmod 600 .env

Ahora IMMICH_URL usa el nombre del contenedor del paso 2, manteniendo el tráfico dentro de 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. Corrige el nombre de la red y el usuario

En docker-compose.yml, establece la red a la que informó el paso 2:

networks:
  immich-net:
    external: true
    name: immich_default        # <- from step 2

El Dockerfile crea un usuario con UID 1027, la convención habitual de Synology. Comprueba la tuya con id; si difiere, edita el Dockerfile o anula en compose con user: "1026:100". Solo importa una vez que montes volúmenes — una discrepancia es inofensiva por ahora.

5. Pre-vuelo y selecciona álbumes, desde dentro de un contenedor

El Python de DSM es complicado para instalar paquetes, y ejecutar el pre-vuelo en el host no probaría la resolución de nombres de contenedor a contenedor de todos modos:

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"

El mismo comando de una línea ejecuta list_albums.py. Copia los UUID que quieras exponer en ALLOWED_ALBUM_IDS.

6. Construye e inicia

sudo docker compose up -d --build
sudo docker compose logs -f immich-mcp

Busca tres líneas:

Immich MCP server configured for http://immich_server:2283
Scope: restricted to <album-id>
Album scope refreshed: 1 album(s), N asset(s)

Si N es 0, el ID del álbum es incorrecto o la clave no puede leer ese álbum.

La interfaz gráfica de Container Manager también funciona: Proyecto → Crear → apuntar a la carpeta — pero a veces tiene problemas con redes external: true. Usa SSH si da error.

7. Verifica en el NAS antes de exponer nada

curl -s http://127.0.0.1:8099/healthz | head -c 300

Espera estado ok, la versión de Immich y tu resumen de ámbito. Luego el protocolo de enlace completo:

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>"

Un problema encontrado aquí es un problema de configuración. El mismo problema encontrado después de la siguiente sección parece un problema de túnel.


Parte 3 — Exponlo y conecta ChatGPT

1. Añade el nombre de host del túnel Cloudflare

Panel de Zero Trust → Redes → Túneles → tu túnel → Nombre de host público → Añadir:

  • Subdominio: immich-mcp

  • Dominio: tudominio.com

  • Servicio: HTTP → immich_mcp:8080

Si cloudflared se ejecuta como contenedor, debe compartir una red con immich_mcp para que ese nombre se resuelva; si se ejecuta en el host, usa http://127.0.0.1:8099. Consulta cloudflared/config.example.yml para el equivalente en archivo de configuración.

No adjuntes una política de Access. ChatGPT no puede completar un inicio de sesión interactivo de Access. El token de portador es la única puerta — por eso rotarlo era importante.

Verifica desde fuera de la LAN si puedes; un punto de acceso del teléfono es una buena prueba:

python smoke_test.py https://immich-mcp.yourdomain.com <bearer-token>

2. Crea el conector

Configuración → Conectores → Configuración avanzada → habilita el Modo Desarrollador (requiere un plan de pago), luego Crear:

  • Nombre: Fotos de Immich

  • Descripción: esto importa — el modelo lo lee para decidir si invocar el conector. Algo como "Biblioteca personal de fotos y vídeos. Úsalo para encontrar, describir o listar fotos, álbumes y personas reconocidas."

  • URL: https://immich-mcp.tudominio.com/mcp

  • Autenticación: Clave API / cabecera personalizada → Authorization: Bearer <token>

Luego habilita el conector en el compositor del chat y prueba con un nombre de herramienta explícito:

Usa immich search para encontrar fotos de los bastidores de secado


Operación

Nombra la herramienta en tu mensaje. ChatGPT no adivinará de forma fiable cuándo recurrir a un conector personalizado. "Usa immich search para encontrar fotos de los bastidores de secado" funciona donde "encuentra mis fotos de los bastidores de secado" a menudo no funciona.

ChatGPT no puede ver tus fotos. Los resultados de las herramientas son texto — descripciones y metadatos, no píxeles. create_share_link salva esa brecha, pero un enlace compartido es público para cualquiera que tenga la URL, por lo que está desactivado por defecto.

Actualizar código. Edita app/immich_mcp.py, luego sudo docker compose up -d --build.

Rotar el token de portador. Edita .env, docker compose up -d --force-recreate, luego actualiza el conector en ChatGPT. Hay una ventana donde ChatGPT está roto — hazlo cuando no estés en medio de una conversación.

Añadir fotos a un álbum con ámbito. No hay que hacer nada. La caché de ámbito se reconstruye cada SCOPE_TTL segundos (por defecto 300).

Inicio automático después de un reinicio. restart: unless-stopped lo maneja, pero los proyectos de Container Manager a veces necesitan que se marque el reinicio automático en la interfaz gráfica. Reinicia una vez deliberadamente, en un momento que te convenga, en lugar de descubrirlo mientras estás fuera.

Fija tu versión de Immich. La API cambia entre versiones — /server/statistics era /server-info/statistics no hace mucho. Tu instancia publica la especificación exacta en https://photos.tudominio.com/api/docs; compruébalo antes de depurar un 404.

Síntoma

Causa

Build: "/app/immich_mcp.py": no encontrado, contexto de 2 B

Los archivos están planos; pertenecen a app/

network immich_default no encontrada

Nombre de red incorrecto — rehacer paso 2 de la parte 2

/healthz devuelve 503

No se puede alcanzar Immich — IMMICH_URL incorrecta, o los contenedores no están en la misma red

El contenedor sale inmediatamente

Falta variable de entorno requerida — consulta docker compose logs

Scope refreshed: 0 assets

ID del álbum incorrecto, o la clave no puede leer ese álbum

Preflight: clave API rechazada 403 en /users/me

Falta el ámbito user.read — inofensivo, ninguna herramienta lo necesita

Preflight: solo 1 activo visible

La clave pertenece a un usuario que no posee casi nada

401 en cada solicitud

Discrepancia del token Bearer entre .env y el conector

Funciona en el NAS, 502 a través del túnel

cloudflared no puede resolver immich_mcp — misma red, o usar la IP del host

El túnel devuelve una página de inicio de sesión

Hay una política de acceso adjunta; elimínala

ChatGPT: "acción de búsqueda no encontrada"

Añadido en modo Deep Research; habilita el modo Desarrollador

Conector añadido pero nunca se activa

Descripción demasiado vaga, o la herramienta no está activada en el chat

search nunca devuelve nada

Aprendizaje automático de Immich deshabilitado — verifica server_info

Immich rechaza la clave (401 en los registros)

Clave revocada, o pertenece a un usuario diferente de Immich

Related MCP Connectors

Related MCP Servers