Skip to main content
Glama

MCP-сервер NiChart DLMUSE

Размещает cbica/nichart_dlmuse (T1 MRI skull-strip + MUSE ROI сегментация) за MCP-сервером на GPU-инстансе EC2, чтобы Claude Code мог запускать сегментацию удалённо, а не каждому пользователю требовался локальный GPU.

Почему это устроено именно так

Аргументы инструментов MCP — это JSON. Файл .nii.gz весит десятки мегабайт бинарных данных, а сегментация занимает ~1-2 минуты на GPU — это слишком медленно и слишком много для одного блокирующего вызова инструмента. Поэтому:

  • Передача файлов происходит вне протокола MCP — через обычную аутентифицированную конечную точку POST /upload. Через аргумент инструмента MCP передаётся только небольшой upload_id.

  • Задачи асинхронны: run_dlmuse_segmentation ставит задачу в очередь и сразу возвращается; get_job_status опрашивает статус; get_job_result получает CSV прямо в ответе, а также ссылки для скачивания файлов масок.

  • Один воркер, сериализация: это один общий GPU, поэтому одновременно выполняется только один docker run, ожидающий в очереди за asyncio.Queue.

  • Bearer-токен аутентификации для каждого члена команды на каждой конечной точке, кроме /healthz.

  • Приватный инстанс, без публичного веб-уровня: приложение привязывается только к 127.0.0.1, а security group открывает только SSH (22). Пользователи подключаются через SSH-проброс порта со своего ноутбука на этот loopback-порт с помощью своего приватного ключа — нет ни домена, ни чего-либо доступного из интернета для атаки.

  • TLS — это самоподписанный сертификат, сгенерированный один раз на инстансе, поскольку публично выпущенный сертификат (Let's Encrypt и т.п.) требует доступности инстанса из интернета для проверки домена, а этот инстанс недоступен. Каждый пользователь доверяет этому сертификату на своём ноутбуке (см. ниже).

  • Сканы не хранятся вечно: загруженные файлы и результаты задач удаляются через RETENTION_HOURS (по умолчанию 24 часа).

Related MCP server: agent-bridge-mcp

Архитектура

Claude Code (laptop)                    SSH tunnel                  EC2 (private, SG: 22 only)
  │  ssh -i key.pem -L 8420:127.0.0.1:8420 user@instance ─────────────────►  │
  │                                                                          │
  │  1. curl https://127.0.0.1:8420/upload ─────(via tunnel)──────────►  MCP server :8420 (127.0.0.1, self-signed TLS)
  │  2. run_dlmuse_segmentation ────────────────(via tunnel)──────────►         │
  │  3. get_job_status (poll) ──────────────────(via tunnel)──────────►  asyncio job queue (1 worker)
  │  4. get_job_result ─────────────────────────(via tunnel)──────────►         │
                                                                   docker run --gpus all cbica/nichart_dlmuse

Структура репозитория

server/app.py     MCP tools (run_dlmuse_segmentation, get_job_status, get_job_result)
                  + HTTP routes (/upload, /download/{job_id}/{filename}, /healthz)
server/jobs.py    job queue/worker, docker invocation, root-owned-output cleanup
server/auth.py    bearer-token ASGI middleware
server/config.py  env-driven settings
deploy/           EC2 provisioning script (installs Docker, GPU toolkit, self-signed cert, systemd unit)

Развёртывание на EC2 (однократно)

Требуется существующий GPU-инстанс EC2 (рекомендуется AWS Deep Learning AMI — драйвер NVIDIA и Docker обычно уже установлены).

  1. Скопируйте этот репозиторий на инстанс (git clone / scp -r).

  2. cp .env.example .env и заполните как минимум TOKENS — пары name:token для каждого члена команды, разделённые запятыми. Сгенерируйте токены с помощью openssl rand -hex 32.

  3. В security group инстанса разрешите только входящий 22 (SSH), ограничив его IP-адресами вашей команды или bastion-хостом. Больше ничего не открывайте — ни 443, ни 8420. Приложение привязывается к 127.0.0.1 и доступно только через SSH-туннель.

  4. Запустите скрипт подготовки:

    sudo ./deploy/setup_ec2.sh

    Он идемпотентен — устанавливает Docker/nvidia-container-toolkit только при отсутствии, загружает образ DLMUSE, создаёт отдельного служебного пользователя nichart-mcp, разворачивает код в /opt/nichart-mcp, генерирует самоподписанный TLS-сертификат (SAN = 127.0.0.1/localhost) и устанавливает systemd-сервис nichart-mcp.

  5. Проверьте, находясь на самом инстансе:

    curl --cacert /opt/nichart-mcp/tls/server.crt https://127.0.0.1:8420/healthz
  6. Скопируйте /opt/nichart-mcp/tls/server.crt с инстанса, чтобы передать его каждому члену команды (например, scp -i key.pem ec2-user@<instance-ip>:/opt/nichart-mcp/tls/server.crt .).

Чтобы позже добавить или отозвать пользователя: отредактируйте TOKENS в /opt/nichart-mcp/.env на инстансе, затем выполните sudo systemctl restart nichart-mcp.

Обновление кода

Повторно запустите sudo ./deploy/setup_ec2.sh из обновлённой копии на инстансе — он повторно синхронизирует /opt/nichart-mcp (не трогая существующий TLS-сертификат), переустанавливает зависимости и перезапускает сервис.

Подключение с ноутбука

Каждому члену команды понадобятся: его SSH-приватный ключ (для Windows PuTTY-ключа .ppk один раз преобразуйте его с помощью puttygen key.ppk -O private-openssh -o key.pem, чтобы стандартный клиент ssh мог его использовать), его bearer-токен и файл server.crt из шага 6 настройки.

1. Доверьтесь самоподписанному сертификату один раз, чтобы curl/Claude Code перестали его отклонять:

  • macOS: security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain-db server.crt

  • Linux: sudo cp server.crt /usr/local/share/ca-certificates/nichart-mcp.crt && sudo update-ca-certificates

  • Windows: certutil -addstore -f "ROOT" server.crt

2. Откройте SSH-туннель (не закрывайте его в терминале, пока используете Claude Code):

ssh -i key.pem -N -L 8420:127.0.0.1:8420 <ssh_user>@<instance-ip>

Если у инстанса нет публичного IP-адреса и вы подключаетесь только через bastion-хост, добавьте -J <bastion_user>@<bastion_host>.

3. Зарегистрируйте MCP-сервер в Claude Code один раз:

claude mcp add --transport http nichart-dlmuse https://127.0.0.1:8420/mcp \
  --header "Authorization: Bearer <their-token>"

Использование

При открытом туннеле попросите Claude Code сегментировать скан; он сделает следующее:

  1. Загрузит файл (через туннель, так что 127.0.0.1:8420 корректен, даже если файл предназначен удалённому инстансу):

    curl -X POST -H "Authorization: Bearer <token>" \
      -F "file=@/path/to/scan.nii.gz" \
      https://127.0.0.1:8420/upload
    # -> {"upload_id": "..."}
  2. Вызовет инструмент run_dlmuse_segmentation с этим upload_id -> получит job_id.

  3. Будет опрашивать get_job_status(job_id), пока status == "done" (обычно ~1-2 минуты на GPU).

  4. Вызовет get_job_result(job_id) -> CSV с объёмами ROI прямо в ответе, плюс ссылки на /download/{job_id}/{filename} для NIfTI-файлов масок ICV и MUSE (загрузите их с https://127.0.0.1:8420/download/..., используя тот же bearer-токен и тот же туннель).

Эксплуатационные заметки

  • Конкуренция за GPU: одновременно выполняется только одна задача по замыслу (один общий GPU). При активной работе команда увидит очередь задач; get_job_status сообщает queue_position.

  • Состояние задач хранится в памяти: systemctl restart nichart-mcp приводит к потере записей о выполняющихся задачах (загруженный скан и частичные результаты на диске не затрагиваются, но задачу придётся отправить заново). Для небольшой команды это нормально; если этого станет недостаточно, замените in-memory dict в server/jobs.py на Redis/RQ.

  • PHI: сканы — это реальные данные пациентов. RETENTION_HOURS ограничивает время их хранения на диске, но убедитесь, что это соответствует вашим требованиям к обработке данных, прежде чем использовать систему на реальных пациентах. Рассмотрите возможность включения шифрования EBS на томе инстанса, если вы ещё этого не сделали.

  • Контейнер DLMUSE работает под root внутри (он жёстко прописывает запись в /app/pipeline.log, поэтому не может работать с --user). Его результаты принадлежат root; очистка в server/jobs.py использует одноразовый контейнер alpine, чтобы принудительно удалить эти каталоги.

Локальная разработка

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
cp .env.example .env   # fill in TOKENS
TOKENS=dev:devtoken DATA_DIR=/tmp/nichart-dev .venv/bin/python -m server.app

Это запускает полный сервер (аутентификация, загрузка, очередь задач, MCP-инструменты) локально. Для фактического выполнения сегментации всё равно требуется Docker с доступом к GPU — на машине без GPU задачи будут падать на шаге docker run, но всё остальное (маршрутизация, аутентификация, очередь, отчёты о статусе/ошибках) можно проверить.

Related MCP Connectors

Related MCP Servers