Skip to main content
Glama
wanjau2

Immich MCP Server

by wanjau2

Immich MCP サーバー

セルフホスト型の Immich フォトライブラリを、Streamable HTTP 経由で ChatGPT(およびその他の MCP クライアント)に公開します。これにより、「3月のキガリサイト訪問時の写真を探して」といった質問に対して、自宅のNASから実際の回答を得られるようになります。

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

ChatGPT のカスタムコネクタは リモートの HTTPS エンドポイント のみを受け付けます。stdio や localhost のオプションはありません。そのため、サーバーはインターネットから到達可能である必要があり、トンネルが必要です。また、ベアラートークンによる防御も必要です。


目次


Related MCP server: immich-photo-manager

ツール

ツール

目的

search

画像内容に対するCLIPセマンティック検索

fetch

UUIDによる1アセットの完全なEXIF情報

search_by_metadata

日付、場所、カメラ、人物、お気に入りによるフィルタリング

list_albums

カウント付きの全アルバム

get_album

1つのアルバムの詳細と内容

list_people

認識された顔(フィルタリング用のID付き)

library_stats

写真/動画の数とディスク使用量

server_info

Immichのバージョンと有効な機能

create_share_link

特定のアセットへの公開リンク — デフォルトではオフ

search と fetch は意図的に命名されています。ChatGPTのDeep Researchモードは他のすべてのツールを無視するため、Developer Modeが利用できない場合、これら2つが処理を担当します。

特定のアルバムへのアクセス制限

デフォルトでは、サーバーはAPIキーが参照できるすべてを公開します。サブセットのみを共有するには、アルバムUUIDを ALLOWED_ALBUM_IDS に設定します。

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

すべてのツールは、それらのアルバム内のアセットに制限されます。Immichのスマート検索にはアルバムフィルターがないため、サーバーは許可されたアセットIDのセットを自身で構築し、それ以外のものを除外します。UUIDによる直接の fetch も含まれ、その場合はアセットではなく拒否が返されます。このセットは SCOPE_TTL 秒間キャッシュされるため、許可されたアルバムに写真を追加しても、再起動せずに反映されます。

2つのレイヤーを組み合わせることをお勧めします。Immich APIキーを専用ユーザーにスコープ設定し、かつ ALLOWED_ALBUM_IDS を設定します。キーはこのサーバーが到達できる範囲を制限し、アルバムリストは実際に公開する内容を制限します。

異なるクライアントの接続

サーバーはトークンを3つの方法で受け付けます。これは、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 Client IDとSecretのみを提供しますが、このサーバーはそれを実装していません。そのため、パス形式を使用します。高いエントロピーのトークンを使用すればセキュリティは同等ですが、パスとクエリ文字列はヘッダーよりもプロキシログやシェル履歴に残りやすいため、クライアントが許可する場合はヘッダーを優先してください。

プロジェクト構成

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/ が重要です。これら2つのファイルが app/ 内ではなくDockerfileの隣に配置されると、ビルドは "/app/immich_mcp.py": not found で失敗します。


パート1 — ノートPCで動作させる

必須ではありませんが、設定変更のたびにイメージを再ビルドするよりもはるかに高速なループです。ここで説明する内容はすべて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

ノートPCからは、IMMICH_URL をNASのLANアドレスにする必要があります。immich_server はNAS上でのみ解決されるDockerコンテナ名です。

IMMICH_URL=http://192.168.0.43:2283

http:// が2つないことに注意してください。スキームの重複はよくある貼り付けミスで、混乱を招くホスト名エラーの原因になります。

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 で提供されます。2つ目のターミナルで:

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

これはChatGPTが行うのとまったく同じハンドシェイク(初期化、tools/list、そしてライブツールコール)を実行し、認証されていないリクエストが401を返すことを確認します。

5. イメージをビルドする(ノートPCでは任意)

Dockerビルドを出荷前に確認したい場合は、まず docker-compose.yml から networks: ブロックを削除してください。immich_default はNAS上にのみ存在します。

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

パート2 — NASにデプロイする

変更されるのは4つです。コンテナがImmichに到達する方法、参加するDockerネットワーク、ファイルの所有権、イメージのビルド方法です。

0. まず両方のシークレットをローテーションする

Immich APIキーまたはベアラートークンがチャット、メール、共有ドキュメントに貼り付けられた場合は、漏洩したものとして扱ってください。このエンドポイントはまもなくインターネットに公開されます。

  • Immich → アカウント設定 → APIキー → 古いキーを削除し、新しいものを作成する

  • 新しいベアラートークン: openssl rand -hex 32

ノートPCの .env をコピーしないでください。IMMICH_URL がLANアドレスを指しており、これは機能しますが、フォトメタデータをLAN経由で無駄に往復させることになります。NAS上では .env.example から始めてください。

1. プロジェクトをコピーする

他のスタックと一緒に配置します(例: /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はUID 1027(Synologyの一般的な慣例)のユーザーを作成します。id で確認してください。異なる場合は、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

次の3行に注目してください。

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ログインを完了できません。ベアラートークンが唯一のゲートです。そのため、ローテーションが重要だったのです。

可能であればLAN外から確認してください。スマートフォンのテザリングが良いテストになります。

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

2. コネクタを作成する

設定 → コネクタ → 詳細設定 → Developer Mode を有効にする(有料プランが必要)。その後、作成:

  • 名前: 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 を実行します。

ベアラートークンのローテーション。 .env を編集し、docker compose up -d --force-recreate を実行し、ChatGPTのコネクタを更新します。ChatGPTが使えなくなる期間があります。会話の途中で行わないでください。

スコープ設定されたアルバムへの写真追加。 何もする必要はありません。スコープキャッシュは SCOPE_TTL 秒(デフォルト300)ごとに再構築されます。

再起動後の自動起動。 restart: unless-stopped で処理されますが、Container ManagerのプロジェクトはGUIで自動再起動をオンにする必要がある場合があります。都合の良い時間に、意図的に一度再起動して確認してください。不在時に問題が発生するのを防げます。

Immichのバージョンを固定してください。 APIはリリース間で変化します。/server/statistics は少し前まで /server-info/statistics でした。あなたのインスタンスは https://photos.yourdomain.com/api/docs で正確な仕様を公開しています。404をデバッグする前に、そこで確認してください。


トラブルシューティング

症状

原因

ビルド: "/app/immich_mcp.py": not found、コンテキストは 2 B

ファイルはフラットです。app/ に属する必要があります

network immich_default not found

間違ったネットワーク名 — パート2のステップ2をやり直してください

/healthz が 503 を返す

Immich に到達できません — IMMICH_URL が間違っているか、コンテナが同じネットワーク上にありません

コンテナがすぐに終了する

必要な環境変数が不足 — docker compose logs で確認してください

Scope refreshed: 0 assets

アルバム ID が間違っているか、キーがそのアルバムを読み取れません

Preflight: API キーが /users/me で 403 拒否

user.read スコープが不足 — 無害です。どのツールも必要としません

Preflight: アセットが 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