Skip to main content
Glama
Immich MCP Server

Immich MCP Server

by wanjau2

Immich MCP 服务器

将自托管的 Immich 照片库通过 Streamable HTTP 暴露给 ChatGPT(以及任何其他 MCP 客户端),这样你就可以提出诸如“查找三月份基加利现场考察的照片”之类的问题,并从你自己的 NAS 获得真实答案。

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

ChatGPT 自定义连接器只接受 远程 HTTPS 端点——没有 stdio 或 localhost 选项。因此服务器必须能从互联网访问,所以才需要隧道,并且必须自我保护,所以才需要 bearer token。


目录


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。密钥限制了此服务器可能触及的范围;相册列表则限制了它实际暴露的内容。

连接不同的客户端

服务器接受三种方式的 token,因为 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 和密钥,而此服务器未实现这些——因此采用了路径形式。在高熵 token 下安全性是等效的,但路径和查询字符串比请求头更容易出现在代理日志和 shell 历史中,因此如果客户端允许,优先使用请求头。

项目结构

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/ 很重要。如果这两个文件最终位于 Dockerfile 旁边而不是 app/ 内部,构建将失败,并显示 "/app/immich_mcp.py": not found。


第一部分——在笔记本电脑上运行

可选,但比每次配置更改都重建镜像要快得多。这里的所有内容在 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

第二部分——部署到 NAS

有四件事会改变:容器如何访问 Immich、加入哪个 Docker 网络、文件所有权以及镜像的构建方式。

0. 首先轮换两个密钥

如果 Immich API 密钥或 bearer token 已被粘贴到聊天、电子邮件或共享文档中,请视为已泄露。此端点即将面向互联网。

  • Immich → 账户设置 → API 密钥 → 删除旧的,创建新的

  • 新的 bearer token:openssl rand -hex 32

不要复制笔记本电脑上的 .env。它指向 LAN 地址的 IMMICH_URL 虽然能工作,但会无缘无故地将照片元数据路由到 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-immich_server-1 而不是 immich_server。从 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,那么剩下的步骤就很简单了。跳过这一步意味着稍后会出现隧道 502 错误,看起来像是 Cloudflare 的问题,但实际上不是。

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 检查你的 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

预期输出为 status 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>"

在这里发现的问题是配置问题。同样的发现在下一节之后看起来就像是隧道问题。


第三部分——暴露并连接 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 token 是唯一的门禁——这就是为什么轮换它很重要。

如果可能,请从 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 token。 编辑 .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 错误,或密钥无法读取该专辑

预检:API 密钥在 /users/me 上被拒绝 403

缺少 user.read 作用域 — 无害,没有工具需要它

预检:仅可见 1 个资产

密钥属于一个几乎没有任何资产的用户

每次请求都返回 401

.env 与连接器之间的 Bearer 令牌不匹配

在 NAS 上工作正常,通过隧道返回 502

cloudflared 无法解析 immich_mcp — 使用同一网络,或使用主机 IP

隧道返回登录页面

附加了访问策略;请移除它

ChatGPT:"search action not found"

在深度研究模式下添加;启用开发者模式

连接器已添加但从未触发

描述过于模糊,或工具未在聊天中启用

search 始终不返回任何内容

Immich 机器学习已禁用 — 检查 server_info

Immich 拒绝密钥(日志中显示 401)

密钥已撤销,或属于不同的 Immich 用户

Related MCP Connectors

Related MCP Servers