Skip to main content
Glama
TSchwierz

weather-packing-bot

by TSchwierz

weather-packing-bot

Бот, работающий по расписанию, который публикует в Slack сообщение с многодневным прогнозом погоды и рекомендациями по сбору вещей для регулярного маршрута между тремя городами. Он также представляет собой небольшую эталонную реализацию пары MCP-сервера и MCP-клиента (Model Context Protocol): данные прогноза предоставляются отдельным MCP-сервером и потребляются MCP-клиентом, а не запрашиваются напрямую скриптом бота.

Обзор

Бот запускается по еженедельному расписанию (по пятницам, 15:00 CEST) и публикует в Slack сообщение о погоде на период с этого дня до вечера следующего понедельника. Прогноз и рекомендации по сбору вещей учитывают маршрут: они определяют, в каком из трёх городов ожидается путешественник в каждый день этого окна, а не показывают погоду во всех городах без разбора.

GitHub Actions (scheduled, Fridays)
        │
        ▼
bot/packing_advisor.py            MCP client
        │  spawns as subprocess, communicates over stdio
        ▼
mcp_server/weather_server.py      MCP server, queries Open-Meteo
        │
        ▼
   Slack incoming webhook

Related MCP server: Weather Prediction MCP Server

Зачем нужны MCP-сервер и MCP-клиент

Сервер предоставляет инструменты для работы с погодой, не зная, кто их вызывает и зачем, — это может быть данный бот, ассистент в IDE или любой другой MCP-совместимый клиент. Клиент не импортирует Python-функции сервера напрямую: он обнаруживает доступные инструменты во время выполнения (session.list_tools()) и вызывает их по имени через протокол (session.call_tool(...)). Именно это разделение отличает данную архитектуру от простого вызова функции в том же процессе.

Компоненты

mcp_server/weather_server.py

MCP-сервер (использующий класс MCPServer из пакета mcp), который оборачивает API Open-Meteo и предоставляет четыре инструмента:

Tool

Description

list_cities()

Возвращает названия настроенных городов.

get_forecast(city, days=3)

Ежедневная сводка погоды для одного города.

get_forecast_all(days=3)

Ежедневная сводка погоды для каждого настроенного города.

render_temperature_chart(days=4)

Компактная текстовая диаграмма по городам и дням (температурная полоса, значок погоды, осадки), отформатированная для кодового блока Slack.

Города определяются в словаре COORDINATES и могут свободно расширяться.

bot/packing_advisor.py

MCP-клиент, который:

  1. Вычисляет окно прогноза — от текущей даты до следующего понедельника включительно — с помощью forecast_window().

  2. Запускает weather_server.py в качестве подпроцесса, выполняет рукопожатие MCP и вызывает get_forecast_all и render_temperature_chart для этого окна.

  3. Сопоставляет каждую дату в окне со словарём WEEKLY_ITINERARY, который ставит в соответствие дню недели город (или города), где ожидается путешественник в этот день, чтобы на результат влияли только релевантные города.

  4. Передаёт сопоставленные дни в recommend_packing() — функцию на основе правил, использующую пороговые значения температуры и осадков (без внешнего вызова ИИ).

  5. Формирует сообщение в формате Slack Block Kit — разбивку по дням, список вещей и диаграмму — и публикует его через входящий вебхук.

Маршрут поездок

WEEKLY_ITINERARY в bot/packing_advisor.py описывает повторяющийся еженедельный маршрут:

WEEKLY_ITINERARY = {
    4: [("Nijmegen", "arrival")],       # Friday
    5: [("Nijmegen", "")],              # Saturday
    6: [("Nijmegen", "")],              # Sunday
    0: [("Den Bosch", "day"), ("Kerpen", "evening, back home")],  # Monday
}

В понедельник две записи, потому что это переходный день: маршрут помещает путешественника днём в Ден-Бос, а к вечеру — обратно в Керпен. Этот словарь — единственное место, которое нужно изменить, если маршрут изменится.

Диаграмма

render_temperature_chart создаёт текстовую моноширинную диаграмму, а не изображение. Это намеренное ограничение: входящий вебхук Slack может отправлять только текст или JSON Block Kit — он не может загружать бинарные файлы. Публикация реального изображения потребовала бы токена Slack-бота (установленного Slack-приложения с областью files:write) и вызова files.upload через Web API Slack вместо вебхука. Это возможное расширение, но оно требует управления дополнительными учётными данными; текущая реализация полностью избегает этого требования.

Настройка

Предварительные требования

  • Приложение Slack с URL входящего вебхука.

  • Репозиторий GitHub для размещения кода и запуска запланированного рабочего процесса.

Локальный запуск

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # fill in SLACK_WEBHOOK_URL
export $(grep -v '^#' .env | xargs)
python bot/packing_advisor.py

SIMULATE_DATE (см. .env.example) можно задать в виде ISO-даты, чтобы проверить логику маршрута и окна прогноза, не дожидаясь реальной пятницы.

MCP-сервер также можно изучить отдельно:

pip install "mcp[cli]"
mcp dev mcp_server/weather_server.py

Это открывает MCP Inspector в браузере; это удобно для просмотра схем инструментов и прямого вызова таких инструментов, как get_forecast или render_temperature_chart.

Конфигурация репозитория GitHub

  • Добавьте SLACK_WEBHOOK_URL в качестве секрета репозитория (Настройки → Секреты и переменные → Actions → Секреты).

  • Никакой другой конфигурации репозитория не требуется: маршрут определён в коде, а не в виде переменной репозитория.

Расписание

.github/workflows/weekly-forecast.yml запускается каждую пятницу в 13:00 UTC (15:00 CEST) по расписанию cron: "0 13 * * 5". Расписания cron в GitHub Actions не учитывают переход на летнее время, поэтому в период CET (зимой) запуск происходит в 14:00 по местному времени вместо 15:00; выражение cron можно корректировать по сезонам, если это смещение важно. Рабочий процесс также поддерживает ручной запуск (workflow_dispatch) с необязательным входным параметром simulate_date; это удобно для тестирования, прежде чем полагаться на расписание.

Известные ограничения и возможные расширения

  • Сообщение Slack включает текстовую диаграмму, а не изображение, по причине, описанной выше. PNG-диаграмма через matplotlib в сочетании с токеном Slack-бота и files.upload — возможный следующий шаг.

  • recommend_packing() основан на правилах. Замена этой функции вызовом LLM (например, Anthropic API) позволила бы получать более естественный текстовый вывод; результаты инструментов MCP — это обычные словари, поэтому их можно напрямую передавать в такой вызов.

  • Маршрут — это фиксированный еженедельный шаблон. Источник с интеграцией календаря (например, чтение фактических дат поездок из календаря) позволил бы выйти за рамки повторяющегося еженедельного маршрута.

Примечания об исходном материале

В исходном черновом скрипте сервера было несколько проблем, исправленных при разработке данной реализации:

  • Неиспользуемый и несуществующий импорт httpx2.

  • get_params() формировал словарь параметров, но не возвращал его.

  • Клиент Open-Meteo создавался только внутри if __name__ == "__main__", из-за чего он был недоступен функциям-инструментам, которые ссылались на него на уровне модуля.

  • process_response пытался выполнить присваивание в list, используя объект dict_keys в качестве индекса, что недопустимо.

  • Функции-инструменты возвращали объекты pandas DataFrame и скалярные типы numpy, которые не сериализуются в JSON; результаты инструментов MCP должны быть сериализуемыми, поскольку они передаются в виде JSON.

Related MCP Connectors

Related MCP Servers