Skip to main content
Glama
wanjau2

Immich MCP Server

by wanjau2

Immich MCP Server

셀프 호스팅 Immich 사진 라이브러리를 ChatGPT(및 기타 MCP 클라이언트)에 Streamable HTTP로 노출하여, "3월 키갈리 현장 방문 사진 찾아줘" 같은 질문에 실제 NAS에서 답변을 얻을 수 있게 합니다.

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

ChatGPT 사용자 지정 커넥터는 원격 HTTPS 엔드포인트만 허용합니다. stdio나 localhost 옵션은 없습니다. 따라서 서버는 인터넷에서 접근 가능해야 하므로 터널이 필요하며, 자기 방어를 위해 bearer 토큰이 필요합니다.


목차


Related MCP server: immich-photo-manager

도구

도구

용도

search

이미지 콘텐츠에 대한 CLIP 의미 검색

fetch

UUID로 하나의 에셋에 대한 전체 EXIF 정보

search_by_metadata

날짜, 장소, 카메라, 인물, 즐겨찾기로 필터링

list_albums

모든 앨범과 개수

get_album

하나의 앨범 상세 정보와 내용

list_people

인식된 얼굴과 필터링용 ID

library_stats

사진/비디오 개수 및 디스크 사용량

server_info

Immich 버전 및 활성화된 기능

create_share_link

특정 에셋에 대한 공개 링크 — 기본적으로 꺼짐

search와 fetch는 의도적으로 명명되었습니다. ChatGPT의 딥 리서치 모드는 다른 모든 도구를 무시하므로, 개발자 모드를 사용할 수 없을 때 이 두 도구가 부담을 짊어집니다.

특정 앨범으로 접근 제한

기본적으로 서버는 API 키가 볼 수 있는 모든 것을 노출합니다. 하위 집합만 공유하려면 ALLOWED_ALBUM_IDS에 앨범 UUID를 넣으세요:

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

그러면 모든 도구가 해당 앨범 내의 에셋으로 제한됩니다. Immich의 스마트 검색에는 앨범 필터가 없으므로, 서버가 직접 허용된 에셋 ID 집합을 구축하고 그 외의 것은 삭제합니다. 여기에는 UUID로 직접 fetch하는 것도 포함되며, 에셋 대신 거부 응답을 반환합니다. 이 집합은 SCOPE_TTL초 동안 캐시되므로 허용된 앨범에 추가된 사진은 재시작 없이 나타납니다.

두 가지 계층을 결합하는 것이 좋습니다: Immich API 키를 전용 사용자로 범위를 지정하고, ALLOWED_ALBUM_IDS를 설정하는 것입니다. 키는 이 서버가 도달할 수 있는 것을 제한하고, 앨범 목록은 실제로 노출하는 것을 제한합니다.

다양한 클라이언트 연결

서버는 세 가지 방법으로 토큰을 받습니다. MCP 클라이언트마다 보낼 수 있는 것이 다르기 때문입니다.

클라이언트

URL

인증

ChatGPT 커넥터

https://host/mcp

Authorization: Bearer <token>

Claude 커넥터

https://host/<token>/mcp

OAuth 필드 비워두기

Claude Code, Cursor, Zed

https://host/mcp

--header / 설정 헤더

URL만 가능한 모든 것

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

—

Claude의 사용자 지정 커넥터 대화상자는 OAuth 클라이언트 ID와 시크릿만 제공하며, 이 서버는 이를 구현하지 않습니다. 그래서 경로 형식을 사용합니다. 높은 엔트로피 토큰을 사용하면 보안은 동등하지만, 경로와 쿼리 문자열은 헤더보다 프록시 로그와 셸 히스토리에 더 쉽게 기록되므로, 클라이언트가 허용하는 경우 헤더를 선호하세요.

프로젝트 구조

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/이 중요합니다. 이 두 파일이 app/ 내부 대신 Dockerfile 옆에 있으면 빌드가 실패하며 "/app/immich_mcp.py": not found 오류가 발생합니다.


1부 — 노트북에서 작동시키기

선택 사항이지만, 모든 설정 변경마다 이미지를 다시 빌드하는 것보다 훨씬 빠른 루프입니다. 여기에 있는 모든 내용은 NAS에서도 동일하게 작동합니다.

1. Immich API 키 받기

Immich → 계정 설정 → API 키 → 새 API 키. 공유 링크를 활성화할 계획이 없다면 읽기 전용으로 범위를 지정하세요.

Immich 3.x는 엔드포인트별로 키를 범위 지정합니다. 여기 있는 도구는 에셋, 앨범, 인물, 검색에 대한 읽기 접근이 필요합니다. /users/me는 필요하지 않습니다. 사전 점검 시 user.read 범위가 없으면 경고만 표시하고 실패하지 않습니다.

2. 설정

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

노트북에서 IMMICH_URL은 NAS LAN 주소여야 합니다. immich_server는 NAS 자체에서만 확인되는 Docker 컨테이너 이름입니다:

IMMICH_URL=http://192.168.0.43:2283

http://가 두 번 나오지 않도록 주의하세요. 체계가 중복되면 붙여넣기 오류가 발생하기 쉬우며 혼란스러운 호스트 이름 오류가 발생합니다.

3. 사전 점검

pip install httpx python-dotenv
python preflight.py

프로젝트 레이아웃을 확인하고, .env의 유효성을 검사하며, Immich에 TCP 연결을 열고, API 키가 에셋을 읽을 수 있는지 확인하며, 스마트 검색과 얼굴 인식이 실제로 활성화되어 있는지 보고합니다. 각 실패에 대해 정확한 수정 방법을 알려줍니다.

보고된 에셋 개수를 읽어볼 가치가 있습니다. 1개의 에셋이 보임이라고 표시되는데 라이브러리에 수천 개가 있다면, 키가 거의 아무것도 소유하지 않은 사용자에게 속한 것입니다. 이후 모든 다운스트림은 완벽하게 작동하지만 아무것도 반환하지 않습니다.

4. Docker 없이 실행

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

http://127.0.0.1:8099에서 자동 리로드로 제공합니다. 두 번째 터미널에서:

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

이는 ChatGPT가 수행하는 정확한 핸드셰이크(초기화, tools/list, 그 다음 실제 도구 호출)를 실행하고, 인증되지 않은 요청이 401을 받는지 확인합니다.

5. 이미지 빌드 (노트북에서는 선택 사항)

배포 전에 Docker 빌드를 확인하려면 먼저 docker-compose.yml에서 networks: 블록을 삭제하세요. immich_default는 NAS에만 존재합니다.

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

2부 — NAS에 배포하기

네 가지가 변경됩니다: 컨테이너가 Immich에 도달하는 방식, 연결되는 Docker 네트워크, 파일 소유권, 이미지 빌드 방식.

0. 먼저 두 비밀을 교체하세요

Immich API 키나 bearer 토큰이 채팅, 이메일, 공유 문서에 붙여넣어진 경우 손상된 것으로 간주하세요. 이 엔드포인트는 곧 인터넷에 노출될 예정입니다.

  • Immich → 계정 설정 → API 키 → 이전 키 삭제, 새 키 생성

  • 새 bearer 토큰: openssl rand -hex 32

노트북의 .env를 복사하지 마세요. IMMICH_URL은 LAN 주소를 가리키는데, 작동은 하지만 사진 메타데이터가 LAN을 통해 나갔다가 돌아오는 불필요한 경로를 만듭니다. NAS에서는 .env.example부터 시작하세요.

1. 프로젝트를 NAS로 복사

다른 스택과 함께 배치하세요. 예: /volume1/docker/immich-mcp/. File Station으로 드래그하거나 다음 명령어 사용:

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

app/이 그대로 있는지 확인하세요. File Station 드래그 앤 드롭 시 디렉터리가 평탄화되는 경우가 있습니다:

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

2. 실제 컨테이너 이름과 네트워크 찾기

SSH로 접속(제어판 → 터미널 및 SNMP → SSH 활성화) 후:

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

Container Manager는 종종 프로젝트 이름을 접두사로 붙이므로 immich_server 대신 immich-immich_server-1이 나올 수 있습니다. Docker 내부에서 이름이 확인되는지 확인하세요. 이것이 실제로 중요합니다:

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

여기서 200이 나오면 나머지는 지루합니다. 건너뛰면 나중에 Cloudflare 문제처럼 보이지만 실제로는 아닌 터널 502 오류가 발생합니다.

3. NAS에서 .env 작성

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

이제 IMMICH_URL은 2단계의 컨테이너 이름을 사용하여 트래픽을 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. 네트워크 이름과 사용자 수정

docker-compose.yml에서 네트워크를 2단계에서 확인된 것으로 설정하세요:

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

Dockerfile은 일반적인 Synology 관례인 UID 1027로 사용자를 생성합니다. id 명령어로 자신의 UID를 확인하세요; 다르다면 Dockerfile을 수정하거나 compose에서 user: "1026:100"으로 재정의하세요. 볼륨을 마운트할 때만 중요합니다 — 현재는 불일치해도 문제없습니다.

5. 컨테이너 내부에서 사전 점검 및 앨범 선택

DSM의 Python은 패키지 설치가 어렵고, 호스트에서 사전 점검을 실행해도 컨테이너 간 이름 확인을 테스트하지 못합니다:

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"

같은 한 줄 명령어로 list_albums.py도 실행됩니다. 노출하려는 UUID를 복사하여 ALLOWED_ALBUM_IDS에 넣으세요.

6. 빌드 및 시작

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

세 줄을 확인하세요:

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

N이 0이면 앨범 ID가 잘못되었거나 키가 해당 앨범을 읽을 수 없는 것입니다.

Container Manager GUI도 작동합니다(프로젝트 → 생성 → 폴더 지정) 하지만 external: true 네트워크에서 때때로 문제가 발생합니다. 오류가 발생하면 SSH를 사용하세요.

7. 노출 전에 NAS에서 확인

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

상태 ok, Immich 버전, 그리고 범위 요약이 표시되어야 합니다. 그 다음 전체 핸드셰이크:

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

여기서 발견된 문제는 설정 문제입니다. 다음 섹션 후에 동일한 문제가 발견되면 터널 문제처럼 보입니다.


3부 — 노출하고 ChatGPT 연결하기

1. Cloudflare Tunnel 호스트 이름 추가

Zero Trust 대시보드 → 네트워크 → 터널 → 사용자 터널 → 공개 호스트 이름 → 추가:

  • 서브도메인: immich-mcp

  • 도메인: yourdomain.com

  • 서비스: HTTP → immich_mcp:8080

cloudflared가 컨테이너로 실행되는 경우 이름 확인을 위해 immich_mcp와 네트워크를 공유해야 합니다; 호스트에서 실행되는 경우 http://127.0.0.1:8099를 사용하세요. 설정 파일에 해당하는 내용은 cloudflared/config.example.yml을 참조하세요.

Access 정책을 첨부하지 마세요. ChatGPT는 대화형 Access 로그인을 완료할 수 없습니다. bearer 토큰이 유일한 게이트입니다 — 따라서 교체하는 것이 중요했던 이유입니다.

가능하면 LAN 외부에서도 확인하세요. 휴대폰 핫스팟이 좋은 테스트 방법입니다:

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

2. 커넥터 생성

설정 → 커넥터 → 고급 설정 → 개발자 모드 활성화(유료 플랜 필요), 그 다음 생성:

  • 이름: Immich Photos

  • 설명: 중요합니다. 모델이 이를 읽고 커넥터를 호출할지 결정합니다. "개인 사진 및 비디오 라이브러리입니다. 사진, 앨범, 인식된 인물을 찾거나 설명하거나 나열하는 데 사용하세요." 같은 내용

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

  • 인증: API 키 / 사용자 지정 헤더 → Authorization: Bearer <token>

그런 다음 채팅 작성기에서 커넥터를 활성화하고 명시적인 도구 이름으로 테스트하세요:

immich search를 사용하여 건조대 사진을 찾아줘


운영

프롬프트에 도구 이름을 명시하세요. ChatGPT는 언제 사용자 지정 커넥터를 사용해야 할지 신뢰성 있게 추측하지 않습니다. "immich search를 사용하여 건조대 사진을 찾아줘"는 잘 작동하는 반면, "내 건조대 사진 찾아줘"는 종종 작동하지 않습니다.

ChatGPT는 사진을 볼 수 없습니다. 도구 결과는 텍스트(설명과 메타데이터)이며 픽셀이 아닙니다. create_share_link가 그 격차를 메워주지만, 공유 링크는 URL을 가진 모든 사람에게 공개되므로 기본적으로 비활성화되어 있습니다.

코드 업데이트. app/immich_mcp.py를 편집한 후 sudo docker compose up -d --build를 실행하세요.

Bearer 토큰 교체. .env를 편집하고 docker compose up -d --force-recreate를 실행한 후 ChatGPT에서 커넥터를 업데이트하세요. ChatGPT가 중단되는 시간이 있습니다 — 대화 중이 아닐 때 수행하세요.

범위가 지정된 앨범에 사진 추가. 할 일이 없습니다. 범위 캐시는 SCOPE_TTL초마다시 재구성됩니다(기본값 300).

재부트 후 자동 시작. restart: unless-stopped가 처리하지만, Container Manger 프로젝트는 때로 GUI에서 자동 재시작을 체크해야 합니다. 자리 비운 사이에 발견하기보다는 편한 시간에 의도적으로 한 번 재부팅하세요.

Imich 버전을 고정하세요. API는 릴리스 사이에 변경됩니다. /server/statistics는 얼마 전까지만 해도 /server-info/statistics였습니다. 인스턴스는 https://photos.youromain.com/api/docs에서 정확한 스펙을 제공합니다; 404를 디버깅하기 전에 거기서 확인하세요.


증상

원인

빌드: "/app/immich_mcp.py": not found, context is 2 B

파일이 평면 구조이며 app/ 디렉토리에 속해야 함

network immich_default not found

잘못된 네트워크 이름 — 2부 2단계 다시 수행

/healthz가 503 반환

Immich에 연결할 수 없음 — IMMICH_URL이 잘못되었거나 컨테이너가 동일한 네트워크에 있지 않음

컨테이너가 즉시 종료됨

필수 환경 변수 누락 — docker compose logs 확인

Scope refreshed: 0 assets

앨범 ID가 잘못되었거나 키가 해당 앨범을 읽을 수 없음

사전 점검: API 키가 /users/me에서 403 거부됨

user.read 범위 누락 — 무해하며, 어떤 도구도 필요로 하지 않음

사전 점검: 1개의 자산만 표시됨

키가 거의 아무것도 소유하지 않은 사용자에 속함

모든 요청에서 401 발생

.env와 커넥터 간 Bearer 토큰 불일치

NAS에서는 작동하지만 터널을 통해 502 발생

cloudflared가 immich_mcp를 확인할 수 없음 — 동일한 네트워크 또는 호스트 IP 사용

터널이 로그인 페이지 반환

Access 정책이 연결되어 있음; 제거

ChatGPT: "search action not found"

Deep Research 모드에서 추가됨; Developer Mode 활성화

커넥터가 추가되었지만 실행되지 않음

설명이 너무 모호하거나 채팅에서 도구가 활성화되지 않음

search가 아무것도 반환하지 않음

Immich 머신 러닝 비활성화됨 — server_info 확인

Immich가 키를 거부함 (로그에서 401)

키가 해지되었거나 다른 Immich 사용자에 속함

Related MCP Connectors

Related MCP Servers