Свой MCP-сервер на VPS: как я подружил ИИ-ассистента с почтой и тремя агентами Grok Build

17 и 18 октября я поднял на своём VPS два небольших MCP-сервера по открытому стандарту Model Context Protocol. Первый даёт моему ИИ-ассистенту Grok Bot читать почту. Второй работает как доска задач: через неё ассистент раздаёт работу трём агентам Grok Build на разных машинах.

Расскажу, как это устроено, как агенты берут и сдают задачи и на какие грабли я наступил по дороге.

Что такое MCP

Model Context Protocol, или MCP, это открытый стандарт подключения ИИ-ассистентов к внешним инструментам и данным. Описание и спецификация лежат на modelcontextprotocol.io.

MCP-сервер это небольшая программа, которая отдаёт ассистенту «инструменты»: функции с описанием, что каждая делает. Ассистент вызывает их по HTTP (streamable HTTP) или через stdio. Для веб-разработчика удалённый MCP-сервер выглядит как маленький бэкенд за nginx, только вместо REST-эндпоинтов у него набор функций.

Почта: три ящика, а IMAP закрыт

Ассистенту нужно было читать три моих ящика: Gmail, Яндекс и почту на Beget. С Gmail всё просто, для него есть готовый коннектор. А вот до IMAP Яндекса и Beget облачная машина ассистента достучаться не могла: исходящие IMAP-порты там закрыты, наружу работает только HTTPS.

Значит, нужен мост: до него ассистент доходит по HTTPS, а он уже сам ходит в почту по IMAP. Таким мостом стал мой VPS.

Почтовый MCP-сервер на Python: только чтение

На VPS с Ubuntu я поднял маленький MCP-сервер на Python: FastMCP из пакета mcp, режим stateless streamable HTTP. В оба ящика он ходит по IMAP только на чтение. Папки открываются через EXAMINE, письма читаются через BODY.PEEK, поэтому они не помечаются прочитанными.

Инструментов пять:

  • list_accounts и list_folders: ящики и папки в них;
  • search: поиск писем;
  • get_message: письмо целиком;
  • get_attachment: вложение из письма.

Сам сервер слушает только localhost. Снаружи перед ним стоит nginx с сертификатом Let’s Encrypt на отдельном поддомене и авторизацией по bearer-токену. Сервис работает через systemd под отдельным системным пользователем, с hardening.

Один рабочий момент случился на сертификате. Let’s Encrypt не выдавал его, пока в DNS не добавили CAA-запись для letsencrypt.org.

Доска задач: второй MCP-сервер для агентов Grok Build

На том же VPS работает второй MCP-сервер, task-board. Внутри у него SQLite, снаружи тот же стек: nginx, systemd и отдельный пользователь. Поддомен тот же, только путь свой.

Зачем он понадобился. Мой основной ассистент Grok Bot держит весь контекст: почту, клиентов, планы. А работу на машинах делают агенты Grok Build, и их у меня три: на Linux-десктопе, на VPS и на MacBook. Через доску Grok Bot ставит им задачи, а они забирают их и сдают результат.

Как проходит задача: от create_task до done

Grok Bot создаёт задачу вызовом create_task. Агент смотрит список через list_tasks и забирает задачу через claim_task. Захват атомарный, так что двое одну задачу не возьмут. По ходу работы агент пишет промежуточные отчёты через post_update.

Если агенту что-то непонятно, он вызывает complete_task со статусом needs_input и задаёт вопрос. Ассистент спрашивает меня и передаёт ответ через reply. Когда работа готова, агент закрывает задачу через complete_task со статусом done или failed.

Статусов шесть: open, claimed, needs_input, done, failed и cancelled.

Всего у доски 8 инструментов. Кроме уже названных, есть get_task, чтобы прочитать задачу целиком, и cancel_task для отмены.

Безопасность: два токена и rate limit

Токенов два, у каждого своя роль: bot и builder. С токеном builder нельзя создавать и отменять задачи, это делает только ассистент. На неверный токен сервер отвечает 401, а на nginx стоит rate limit.

Токены не светятся в чатах. Каждый агент забирает свой токен с сервера по SSH и держит его в переменной окружения. В конфиге остаётся только ссылка ${TASKBOARD_TOKEN}.

Как подключить доску к Grok Build

В Grok Build MCP-сервер подключается секцией в ~/.grok/config.toml, подробно об этом в документации xAI про MCP-серверы:

[mcp_servers.taskboard]
url = "https://<ваш-домен>/tasks/mcp"
headers = { "Authorization" = "Bearer ${TASKBOARD_TOKEN}" }

Значение ${TASKBOARD_TOKEN} Grok подставляет из окружения при загрузке конфига, так что сам токен в файле не лежит. Проверить подключение можно так:

grok mcp list
grok mcp doctor taskboard

Порядок работы с доской я описываю агенту в файле правил ~/.grok/rules/taskboard.md. Grok подгружает такие файлы в каждую сессию, подробнее в документации про правила проекта.

Тест на трёх машинах и грабли по дороге

Каждой машине я поставил тестовую задачу. Все три агента взяли её и сдали за секунды. Быстрее всех справился десктоп: 11 секунд от claim до done.

Без граблей не обошлось:

  • Grok, запущенный под sudo, искал токен в /root, а не в домашнем каталоге.
  • На MacBook SSH-ключ был с паролем, и забрать токен с сервера с первого раза не получилось. Помогла команда ssh-add --apple-use-keychain.
  • Сессия Grok, открытая до правки конфига, новый сервер не видит. Нужно открыть новую.
  • Доска сама не уведомляет агентов. Они проверяют её по команде «проверь доску» или по cron через headless-режим grok -p.

В итоге у меня один ассистент держит весь контекст и раздаёт работу агентам на разных машинах через собственный защищённый сервер. Как ИИ вообще встроен в мою работу, я рассказывал в посте «Как я использую ИИ в работе».

Источники и ссылки


Комментарии загружаются…