Skip to main content
Glama
mitetenov

Bedolaga MCP Server

by mitetenov

Bedolaga MCP Server

MCP-сервер для получения пользовательских фактов из Bedolaga Bot по Telegram ID или внутреннему user_id.

Сервер read-only: через Bedolaga MCP нельзя менять баланс, создавать или продлевать подписки, применять промокоды, оформлять возвраты, выводить реферальные средства или выполнять иные действия от имени пользователя.

Breaking migration (1.0.0)

Начиная с версии 1.0.0 публичный контракт инструментов изменён, а старые имена удалены. Обновите конфигурацию клиента:

Старый инструмент

Что вместо него

bedolaga_balance

заменён на bedolaga_user_get

bedolaga_transactions

заменён на bedolaga_billing_get

bedolaga_subscription

не имеет аналога в Bedolaga MCP. Фактический статус подписки и состояние VPN-панели проверяются через отдельный mcp-remnawave, а не через этот сервер

Также в 1.0.0 удалён устаревший HTTP-путь /mcp: sessionful Streamable HTTP теперь обслуживается на корневом endpoint /, как у mcp-remnawave. Каждая публикация образа получает три тега: :latest, :{version} и :{sha}.

Версия 1.0.0 — первый контракт с корректными API routes, structured результатами и явной границей ответственности с Remnawave.

Related MCP server: Monobank MCP Server

Инструменты (Tools)

Сервер предоставляет ровно восемь инструментов, доступных через MCP-протокол. Все инструменты readonly — данные не изменяются.

Контракт идентичности

Каждый инструмент принимает ровно одно из двух полей:

  • telegram_id — целое число, Telegram ID пользователя (положительное);

  • user_id — целое число, внутренний ID пользователя в Bedolaga (положительное), используется для email-only тикетов кабинета.

Если не передано ни одного поля или переданы оба — инструмент возвращает ошибку invalid_input. Идентичность никогда не берётся от модели: supportBot всегда пинит фактического отправителя — положительный telegram_id из аутентифицированного Telegram update, либо внутренний user_id кабинета для email-only тикета.

bedolaga_user_get

Получить аккаунт и баланс текущего пользователя Bedolaga.

Параметры:

Параметр

Тип

Обязательный

Описание

telegram_id

int

ровно одно из двух

Telegram ID пользователя

user_id

int

ровно одно из двух

Внутренний ID пользователя Bedolaga (email-only тикет)

JSON-поля ответа (data):

Поле

Тип

Описание

found

bool

Признак, что пользователь найден

telegram_id

int | null

Telegram ID пользователя

display_name

string | null

Безопасное отображаемое имя

status

string | null

Статус аккаунта Bedolaga

balance_kopeks

int | null

Баланс в копейках

balance_rubles

float | null

Баланс в рублях (всегда kopeks / 100)

has_made_first_topup

bool | null

Признак первого пополнения в прошлом

has_had_paid_subscription

bool | null

Признак наличия платной покупки в прошлом

referral_code

string | null

Реферальный код

was_referred

bool | null

Пользователь пришёл по приглашению

promo_group

object | null

Название промогруппы и проценты скидок

created_at / last_activity

string | null

Даты создания и последней активности

Поле promo_group содержит только name, server_discount_percent, traffic_discount_percent, device_discount_percent.

Пример интерпретации (синтетический): balance_kopeks: 350000 и balance_rubles: 3500.0 означают баланс 3 500 рублей. has_had_paid_subscription: false означает, что платных покупок ещё не было.

bedolaga_billing_get

Одним вызовом показать баланс, недавние финансовые события и внутренние записи покупок Bedolaga — чтобы отличить пополнение от покупки.

Параметры:

Параметр

Тип

Обязательный

Описание

telegram_id

int

ровно одно из двух

Telegram ID пользователя

user_id

int

ровно одно из двух

Внутренний ID пользователя Bedolaga (email-only тикет)

limit

int

Нет

Лимит операций в списке (по умолчанию 20, максимум 50)

JSON-поля ответа (data):

Поле

Тип

Описание

balance_kopeks / balance_rubles

int / float | null

Текущий баланс

transactions

array

Операции от новых к старым, не более limit

latest_completed_deposit

object | null

Сводка последнего завершённого пополнения

latest_completed_subscription_purchase

object | null

Сводка последней завершённой покупки подписки

purchased_after_latest_deposit

bool | null

Завершённая покупка позже последнего завершённого пополнения

bot_subscriptions

array

Внутренние записи подписок Bedolaga

meta

string

Фиксированное пояснение «deposit ≠ purchase»

Каждая операция в transactions:

Поле

Тип

Описание

id

number | null

Внутренний ID транзакции

category

string

Нормализованная категория: deposit, subscription_purchase, gift_purchase, withdrawal, refund, failed_refund, referral_reward, poll_reward, unknown

direction

string

credit / debit / unknown

raw_type

string | null

Исходное безопасное имя типа

amount_kopeks / amount_rubles

int / float | null

Абсолютная сумма

payment_method

string | null

Платёжный метод

is_completed

bool | null

Завершённость операции

description

string | null

Описание

created_at / completed_at

string | null

Время создания и завершения

Каждая запись в bot_subscriptions содержит id, bot_record_status, bot_record_effective_status, is_trial, tariff_id, tariff_name, start_date, end_date, autopay_enabled, autopay_days_before и фиксированную note. Сервер предпочитает полный upstream-список subscriptions, удаляет повторяющиеся записи по id и сохраняет fallback на одиночное legacy-поле subscription. Поле называется bot_record_status намеренно: это внутренняя запись Bedolaga, а не статус VPN-панели. bot_record_effective_status — тоже бот-сторонний эффективный статус (вычисленный ботом из status и end_date), а не состояние панели.

Пример интерпретации (синтетический): latest_completed_deposit: {amount_kopeks: 350000} и purchased_after_latest_deposit: false — деньги зачислены на баланс, но отдельная покупка после пополнения не завершена.

bedolaga_referrals_get

Получить реферальную сводку текущего пользователя.

Параметры:

Параметр

Тип

Обязательный

Описание

telegram_id

int

ровно одно из двух

Telegram ID пользователя

user_id

int

ровно одно из двух

Внутренний ID пользователя Bedolaga (email-only тикет)

JSON-поля ответа (data):

Поле

Тип

Описание

referral_code

string | null

Реферальный код владельца аккаунта

was_referred

bool | null

Владелец пришёл по приглашению

effective_referral_commission_percent

number | null

Эффективная комиссия

invited_count

int | null

Всего приглашено

active_referrals

int | null

Активных приглашённых

total_earned_kopeks / total_earned_rubles

int / float | null

Заработок за всё время

month_earned_kopeks / month_earned_rubles

int / float | null

Заработок за текущий месяц

recent_referral_rewards

array

Последние начисления владельца

meta

string

Фиксированное пояснение

Возвращается статистика только владельца аккаунта. Telegram ID, внутренние ID, username, имена, баланс и активность приглашённых пользователей никогда не возвращаются.

bedolaga_subscription_get

Получить бот-сторонние записи подписок и даты жизненного цикла (created_at, start_date, end_date, is_trial, autopay_enabled).

Параметры: telegram_id или user_id (ровно одно).

Возвращает has_subscription_records, active_record_count, список subscriptions и фиксированный meta. Поле bot_record_status — внутренняя запись бота, а не статус VPN-панели (фактическое состояние проверяется через Remnawave MCP).

bedolaga_tickets_get

Получить сводку собственных обращений в поддержку (id, title, status, priority, даты создания/обновления/закрытия) без текстов сообщений и медиа.

Параметры: telegram_id или user_id (ровно одно), limit (по умолчанию 10, максимум 50).

bedolaga_payment_status_get

Получить историю финансовых операций и статус завершённости в учётной системе бота (completed / not_completed / unknown).

Параметры: telegram_id или user_id (ровно одно), limit (по умолчанию 5, максимум 20).

Статус not_completed означает лишь отсутствие завершения операции в биллинге бота, а не сбой или ожидание на стороне платёжного шлюза.

bedolaga_promocode_check

Проверить глобальное определение промокода, срок действия, активность, бонус и остаток применений.

Параметры: code (обязательно), telegram_id или user_id (ровно одно для пиннинга идентичности).

Возвращает маскированный код (code_masked), признак globally_valid, reason_code (not_found, inactive, not_yet_valid, expired_or_exhausted, lookup_incomplete) и user_eligibility: "unknown".

bedolaga_gifts_get

Получить историю покупок подарков владельца аккаунта.

Параметры: telegram_id или user_id (ровно одно), limit (по умолчанию 20, максимум 50).

Отображает только факт покупки подарков (бухгалтерия); токены подарков, получатели и статус активации не раскрываются.

Decision table

Как LLM (supportBot) должен использовать данные Bedolaga и Remnawave по сценариям:

Сценарий

Что видно в Bedolaga MCP

Действие LLM

Deposit без покупки

deposit есть, purchased_after_latest_deposit: false

Объяснить, что деньги зачислены на баланс, но отдельная покупка не завершена; направить завершить покупку из баланса. Не заявлять о неисправной подписке

Покупка с рабочей панелью

Завершённая subscription_payment есть

Проверить фактическое состояние панели через Remnawave MCP

Покупка без записи в панели

Завершённая subscription_payment есть

Эскалировать как подтверждённое расхождение с кратким factual summary

Нет пополнения

deposit нет

Не заявлять, что платёжный провайдер не списал деньги (Bedolaga подтверждает только отсутствие зачисления в своей учётной системе); эскалировать, если пользователь сообщает о фактическом списании

Реферальный вопрос

bedolaga_referrals_get

Маршрутизировать только в Bedolaga MCP

Вопрос о ноде / HWID

Маршрутизировать только в Remnawave MCP (состояние нод и устройств Bedolaga не знает)

Формат результата

Каждый инструмент возвращает JSON в текстовом MCP content с единой оболочкой:

  • успех: ok: true, source: "bedolaga-mcp", tool, data, meta;

  • ошибка: ok: false, source, tool, error.code, безопасный error.message, error.retryable.

Сырое тело ответа Bedolaga API и исключения Python модели не возвращаются. Инструменты не возвращают email, ссылку подписки, crypto link, ключи, внешние платёжные ID, receipt-идентификаторы, Remnawave-идентификаторы и персональные данные рефералов.

Error codes

Код

Retryable

Когда возникает

invalid_input

нет

Переданы оба или ни одного identity-поля; недопустимое значение

not_configured

нет

Отсутствует/некорректна конфигурация окружения

identity_unavailable

нет

Идентичность нельзя сопоставить с пользователем Bedolaga

user_not_found

нет

Пользователь не найден (upstream 404 исключительно при поиске пользователя)

unauthorized

нет

Неверные/отсутствующие API-креденшелы (upstream 401/403)

rate_limited

да

Достигнут rate limit (upstream 429)

upstream_timeout

да

Таймаут или сбой сети до ответа

upstream_unavailable

да

Upstream недоступен (5xx, сбой запроса или upstream 404 для непользовательских ресурсов — не означает отсутствие аккаунта)

invalid_upstream_response

нет

Тело ответа — невалидный JSON или не объект

internal_error

нет

Непредвиденная внутренняя ошибка

Пользовательское сообщение строится только из безопасного error.message и никогда не раскрывает HTTP body или внутренний URL. Ошибка user_not_found возникает только при прямом поиске учётной записи (get_user_by_telegram_id / get_user_by_id). При статусе 404 для зависимых ресурсов (транзакции, рефералы, подписки, тикеты, промокоды) возвращается upstream_unavailable, что указывает на недоступность конкретного ресурса и не свидетельствует об отсутствии аккаунта пользователя.

Транспорты

Сервер поддерживает два транспорта на одном server factory и одном реестре инструментов:

Транспорт

Launcher

Порт

Протокол

Streamable HTTP (основной)

http_server.py

3100 по умолчанию

Dual-era MCP на / (см. ниже), GET /health, DELETE / (только legacy-сессии)

Stdio

bedolaga_server.py

MCP stdio handshake (тот же factory)

Эндпоинт / — единственный, но обслуживает две эры протокола одновременно; SDK v2 сам определяет, к какой эре относится каждый запрос, по заголовку MCP-Protocol-Version:

  • Современный протокол 2026-07-28 — stateless/sessionless. Каждый POST на / самодостаточен: сервер никогда не выдаёт Mcp-Session-Id и не хранит состояние между запросами. Официальные клиенты MCP SDK v2 (см. «Официальный клиент SDK v2» ниже) используют этот режим автоматически.

  • Legacy-клиенты с initialize-handshake (протоколы вплоть до 2025-11-25, включая 2024-11-05) получают заголовок Mcp-Session-Id в ответ на initialize и обязаны передавать его во всех последующих запросах. DELETE / с этим заголовком завершает именно эту сессию; на другие сессии и на современных клиентов это не влияет.

GET /health отдаёт liveness процесса и версию сервера, не раскрывая конфигурацию и секреты.

Версионная совместимость

Компонент

Версия

Bedolaga Bot API (upstream)

commit 49b05d5, приложение 4.1.0

bedolaga-mcp

1.2.0

Python MCP SDK (mcp)

2.0.0

Поддерживаемые протоколы MCP

2026-07-28 (современный, stateless) + legacy initialize-handshake вплоть до 2025-11-25

supportBot

2.0.1

mcp-remnawave

v3.2.1

Контракт инструментов проверен против указанного upstream-коммита и эталона mcp-remnawave v3.2.1.

Требования

  • Python 3.11+

  • Docker (опционально)

  • Развёрнутый Bedolaga Bot с Web API

  • API-ключ от Bedolaga (выдаётся в админ-панели бота)

Быстрый старт

1. Клонировать

git clone https://github.com/mitetenov/bedolaga-mcp.git
cd bedolaga-mcp

2. Настроить

cp .env.example .env
# Заполнить BEDOLAGA_API_URL и BEDOLAGA_API_KEY

3. Запустить

Streamable HTTP (рекомендуется):

# Установить зависимости
pip install -r requirements.txt

# Запустить HTTP-сервер
BEDOLAGA_API_URL=https://your-bot.example.com \
BEDOLAGA_API_KEY=your-key \
python3 http_server.py

Сервер будет слушать на http://0.0.0.0:3100, MCP endpoint — корень /.

Stdio:

BEDOLAGA_API_URL=https://your-bot.example.com \
BEDOLAGA_API_KEY=your-key \
python3 bedolaga_server.py

Через Docker:

docker compose up -d

Docker-образ по умолчанию запускает Streamable HTTP сервер на порту 3100.

Подключение как MCP-сервер

Streamable HTTP

Сервер доступен по HTTP на порту 3100, endpoint — корень / (http://localhost:3100).

Hermes Agent

# ~/.hermes/config.yaml
mcp_servers:
  bedolaga:
    transport: streamable-http
    url: "http://localhost:3100"
    env:
      BEDOLAGA_API_URL: "https://your-bot.example.com"
      BEDOLAGA_API_KEY: "your-api-key"

Claude Desktop

{
  "mcpServers": {
    "bedolaga": {
      "type": "streamableHttp",
      "url": "http://localhost:3100"
    }
  }
}

Cursor / VS Code

{
  "mcpServers": {
    "bedolaga": {
      "transport": "streamable-http",
      "url": "http://localhost:3100"
    }
  }
}

Проверка через curl (legacy compatibility check)

Сырой JSON-RPC через curl использует legacy initialize-handshake (протокол 2024-11-05) — это ручная проверка обратной совместимости, а не то, как ходят современные клиенты. Современные клиенты MCP SDK v2 согласовывают протокол 2026-07-28 автоматически и Mcp-Session-Id не получают (см. «Официальный клиент SDK v2 (современный протокол)» ниже).

# Liveness
curl -s http://localhost:3100/health

# Legacy initialize handshake (получить session ID; работает для протоколов вплоть до 2025-11-25)
curl -s -X POST http://localhost:3100/ \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}},"id":1}' \
  -D - | grep -i mcp-session-id

# Список инструментов (с session ID)
curl -s -X POST http://localhost:3100/ \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: <SESSION_ID>" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":2}'

# Вызов инструментов
# Пользователь и баланс
curl -s -X POST http://localhost:3100/ \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: <SESSION_ID>" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"bedolaga_user_get","arguments":{"telegram_id":123456789}},"id":3}'

# Биллинг (операции и внутренние записи покупок)
curl -s -X POST http://localhost:3100/ \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: <SESSION_ID>" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"bedolaga_billing_get","arguments":{"telegram_id":123456789,"limit":20}},"id":4}'

# Реферальная сводка
curl -s -X POST http://localhost:3100/ \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: <SESSION_ID>" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"bedolaga_referrals_get","arguments":{"telegram_id":123456789}},"id":5}'

# Завершение legacy-сессии (для современного протокола 2026-07-28 не требуется и не применяется)
curl -s -X DELETE http://localhost:3100/ \
  -H "Mcp-Session-Id: <SESSION_ID>"

Официальный клиент SDK v2 (современный протокол)

Официальный клиент из Python MCP SDK v2 (mcp==2.0.0) сам согласовывает протокол — 2026-07-28, если сервер его поддерживает, иначе legacy-handshake — без ручного построения _meta или заголовков:

import asyncio

from mcp.client.client import Client


async def main() -> None:
    async with Client("http://localhost:3100/", mode="auto") as client:
        print("negotiated protocol:", client.protocol_version)  # "2026-07-28" against this server

        tools = await client.list_tools()
        print([tool.name for tool in tools.tools])

        result = await client.call_tool(
            "bedolaga_user_get", {"telegram_id": 123456789}
        )
        print(result.content)


asyncio.run(main())

mode="auto" — это то же самое согласование, которое использует supportBot: клиент сам решает, современный сервер перед ним или legacy, и не требует от вызывающего кода знать протокольную эру заранее.

Stdio транспорт

Hermes Agent

# ~/.hermes/config.yaml
mcp_servers:
  bedolaga:
    command: "python3"
    args: ["/path/to/bedolaga-mcp/bedolaga_server.py"]
    env:
      BEDOLAGA_API_URL: "https://your-bot.example.com"
      BEDOLAGA_API_KEY: "your-api-key"

Claude Desktop

{
  "mcpServers": {
    "bedolaga": {
      "command": "python3",
      "args": ["/path/to/bedolaga-mcp/bedolaga_server.py"],
      "env": {
        "BEDOLAGA_API_URL": "https://your-bot.example.com",
        "BEDOLAGA_API_KEY": "your-api-key"
      }
    }
  }
}

Cursor / VS Code

Добавить в .cursor/mcp.json или settings.json:

{
  "mcpServers": {
    "bedolaga": {
      "command": "python3",
      "args": ["/path/to/bedolaga-mcp/bedolaga_server.py"],
      "env": {
        "BEDOLAGA_API_URL": "https://your-bot.example.com",
        "BEDOLAGA_API_KEY": "your-api-key"
      }
    }
  }
}

Управление сессиями

Streamable HTTP транспорт — dual-era, и сессии применимы только к одной из двух эр:

  • Legacy initialize-handshake (протоколы вплоть до 2025-11-25): после initialize сервер возвращает заголовок Mcp-Session-Id, который клиент должен передавать во всех последующих запросах. DELETE / с этим заголовком завершает только указанную сессию; один клиент не может завершить или переиспользовать чужую сессию.

  • Современный протокол 2026-07-28: stateless/sessionless — сервер никогда не выдаёт Mcp-Session-Id, и DELETE / для таких клиентов не нужен и не применяется.

Переменные окружения

Переменная

Назначение

BEDOLAGA_API_URL

URL Bedolaga Web API

BEDOLAGA_API_KEY

API-ключ Bedolaga (передаётся upstream в X-API-Key)

MCP_HTTP_HOST

Адрес для bind (по умолчанию: 0.0.0.0)

MCP_HTTP_PORT

Порт HTTP-сервера (по умолчанию: 3100)

BEDOLAGA_TIMEOUT_MS

Таймаут upstream в миллисекундах (по умолчанию: 10000)

Для совместимости принимаются legacy-переменные HOST/PORT, если MCP_HTTP_HOST/MCP_HTTP_PORT не заданы.

Upstream API

Bedolaga Web API: X-API-Key в заголовке. Используемые маршруты:

  • GET /users/by-telegram-id/{telegram_id} — пользователь по Telegram ID;

  • GET /users/{user_id} — пользователь по внутреннему ID (email-only тикеты);

  • GET /transactions?user_id=... — операции с фильтрами и пагинацией;

  • GET /partners/referrers/{user_id} — реферальная карточка.

Подробнее: https://docs.bedolagam.ru

Ограничения первой версии

  • Нет provider-specific попыток платежей. Bedolaga возвращает только операции, ставшие записями в общей таблице transactions. Сырые попытки платёжного провайдера, которые не стали записью, недоступны.

  • Нет чтения Redis-корзины пользователя. Актуальный Web API не предоставляет безопасный read-only endpoint для этого. Текущую проблему «пополнил, а покупки нет» достоверно диагностируют по разнице между deposit и subscription_payment (см. decision table).

  • Email-only lookup поддерживается. Для тикетов кабинета без Telegram ID сервер принимает внутренний user_id (положительное целое) и резолвит его через GET /users/{user_id}. supportBot пинит внутренний user_id кабинета (абсолютное значение отрицательного synthetic conversation key) — для таких тикетов Bedolaga-данные доступны, а Remnawave-инструменты возвращают identity_unavailable, потому что у такого пользователя нет Telegram-идентичности и доказанной записи в панели.

Откат (rollback)

Выключение BEDOLAGA_MCP_ENABLED=false в supportBot возвращает его в Remnawave-only режим: Bedolaga MCP не подключается, его инструменты исчезают из allowlist, а вебхук/poller-обработка тикетов (BEDOLAGA_ENABLED) остаётся независимой. Откат не трогает пользовательскую базу и финансовые данные — Bedolaga MCP read-only и не хранит состояние.

Откат образа bedolaga-mcp до тега 1.1.0 (последний релиз до миграции на MCP SDK v2, только legacy-эра Streamable HTTP) тоже безопасен: клиент supportBot на MCP SDK v2 автоматически переходит (auto-fallback) на legacy initialize-handshake, если сервер не отвечает на современный протокол 2026-07-28, поэтому инструменты Bedolaga MCP остаются доступны без дополнительной настройки.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    Provides user balance information by connecting to a backend service through the users_balance tool. Built with TypeScript and Express for retrieving financial data.
    -
  • A
    license
    A
    quality
    A
    maintenance
    Enables integration with Monobank API to check currency exchange rates, view account balances, and retrieve transaction statements through natural language queries.
    3
    29 npm
    9
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables interaction with Telegram Bot API for sending messages, photos, editing messages, answering callbacks, and fetching updates.
    MIT