Bedolaga MCP Server
Allows retrieving a user's balance in rubles from the Bedolaga Bot by Telegram ID.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Bedolaga MCP Servershow balance for telegram user 12345"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Bedolaga MCP Server
An MCP server for retrieving user facts from Bedolaga Bot by Telegram ID or internal user_id.
The server is read-only: via Bedolaga MCP you cannot change the balance, create or renew subscriptions, apply promo codes, issue refunds, withdraw referral funds, or perform any other actions on behalf of the user.
Breaking migration (1.0.0)
As of version 1.0.0, the public tool contract has changed and the old names have been removed. Update your client configuration:
Old tool | Replacement |
| replaced by |
| replaced by |
| has no equivalent in Bedolaga MCP. The actual subscription status and VPN panel state are checked via a separate mcp-remnawave, not through this server |
Also in 1.0.0, the legacy HTTP path /mcp has been removed: sessionful Streamable HTTP is now served at the root endpoint /, as with mcp-remnawave. Each image release gets three tags: :latest, :{version}, and :{sha}.
Version 1.0.0 is the first contract with correct API routes, structured results, and an explicit boundary of responsibility with Remnawave.
Related MCP server: Monobank MCP Server
Tools
The server provides exactly eight tools available via the MCP protocol. All tools are readonly — no data is modified.
Identity contract
Each tool accepts exactly one of two fields:
telegram_id— an integer, the user's Telegram ID (positive);user_id— an integer, the user's internal ID in Bedolaga (positive), used for email-only account tickets.
If neither field is passed or both are passed, the tool returns an invalid_input error. Identity is never taken from the model: supportBot always pins the actual sender — a positive telegram_id from the authenticated Telegram update, or the internal account user_id for an email-only ticket.
bedolaga_user_get
Get the account and balance of the current Bedolaga user.
Parameters:
Parameter | Type | Required | Description |
|
| exactly one of two | The user's Telegram ID |
|
| exactly one of two | The user's internal Bedolaga ID (email-only ticket) |
Response JSON fields (data):
Field | Type | Description |
|
| Whether the user was found |
|
| The user's Telegram ID |
|
| Safe display name |
|
| Bedolaga account status |
|
| Balance in kopecks |
|
| Balance in rubles (always |
|
| Whether the first top-up was made in the past |
|
| Whether there was a paid purchase in the past |
|
| Referral code |
|
| Whether the user came by invitation |
|
| Promo group name and discount percentages |
|
| Creation and last activity dates |
The promo_group field contains only name, server_discount_percent, traffic_discount_percent, device_discount_percent.
Example interpretation (synthetic): balance_kopeks: 350000 and balance_rubles: 3500.0 mean a balance of 3,500 rubles. has_had_paid_subscription: false means there have been no paid purchases yet.
bedolaga_billing_get
With a single call, show the balance, recent financial events, and internal Bedolaga purchase records — to distinguish a top-up from a purchase.
Parameters:
Parameter | Type | Required | Description |
|
| exactly one of two | The user's Telegram ID |
|
| exactly one of two | The user's internal Bedolaga ID (email-only ticket) |
|
| No | Limit of operations in the list (default 20, maximum 50) |
Response JSON fields (data):
Field | Type | Description |
|
| Current balance |
|
| Operations from newest to oldest, no more than |
|
| Summary of the latest completed deposit |
|
| Summary of the latest completed subscription purchase |
|
| A completed purchase after the latest completed deposit |
|
| Internal Bedolaga subscription records |
|
| Fixed note “deposit ≠ purchase” |
Each operation in transactions:
Field | Type | Description |
|
| Internal transaction ID |
|
| Normalized category: |
|
|
|
|
| Original safe type name |
|
| Absolute amount |
|
| Payment method |
|
| Whether the operation is completed |
|
| Description |
|
| Creation and completion time |
Each record in bot_subscriptions contains id, bot_record_status, bot_record_effective_status, is_trial, tariff_id, tariff_name, start_date, end_date, autopay_enabled, autopay_days_before, and a fixed note. The server prefers the full upstream subscriptions list, removes duplicate records by id, and keeps a fallback to the single legacy subscription field. The field is named bot_record_status deliberately: it is an internal Bedolaga record, not the VPN panel status. bot_record_effective_status is also a bot-side effective status (computed by the bot from status and end_date), not the panel state.
Example interpretation (synthetic): latest_completed_deposit: {amount_kopeks: 350000} and purchased_after_latest_deposit: false — the money has been credited to the balance, but a separate purchase after the deposit has not been completed.
bedolaga_referrals_get
Get the referral summary of the current user.
Parameters:
Parameter | Type | Required | Description |
|
| exactly one of two | The user's Telegram ID |
|
| exactly one of two | The user's internal Bedolaga ID (email-only ticket) |
Response JSON fields (data):
Field | Type | Description |
|
| Account owner's referral code |
|
| Owner joined via an invitation |
|
| Effective commission |
|
| Total invited |
|
| Active referrals |
|
| Lifetime earnings |
|
| Current month earnings |
|
| Owner's recent rewards |
|
| Fixed note |
Only the account owner's statistics are returned. Telegram IDs, internal IDs, usernames, names, balances, and activity of referred users are never returned.
bedolaga_subscription_get
Get bot-side subscription records and lifecycle dates (created_at, start_date, end_date, is_trial, autopay_enabled).
Parameters: telegram_id or user_id (exactly one).
Returns has_subscription_records, active_record_count, a subscriptions list, and a fixed meta. The bot_record_status field is the bot's internal record, not the VPN panel status (actual state is checked via Remnawave MCP).
bedolaga_tickets_get
Get a summary of the owner's own support tickets (id, title, status, priority, creation/update/closure dates) without message texts or media.
Parameters: telegram_id or user_id (exactly one), limit (default 10, max 50).
bedolaga_payment_status_get
Get the history of financial operations and completion status in the bot's accounting system (completed / not_completed / unknown).
Parameters: telegram_id or user_id (exactly one), limit (default 5, max 20).
The not_completed status only means the operation has not been completed in the bot's billing, not a failure or pending state on the payment gateway side.
bedolaga_promocode_check
Check the global promocode definition, validity period, activity, bonus, and remaining uses.
Parameters: code (required), telegram_id or user_id (exactly one for identity pinning).
Returns a masked code (code_masked), a globally_valid flag, reason_code (not_found, inactive, not_yet_valid, expired_or_exhausted, lookup_incomplete), and user_eligibility: "unknown".
bedolaga_gifts_get
Get the account owner's gift purchase history.
Parameters: telegram_id or user_id (exactly one), limit (default 20, max 50).
It only shows the fact of gift purchases (accounting); gift tokens, recipients, and activation status are not disclosed.
Decision table
How the LLM (supportBot) should use Bedolaga and Remnawave data by scenario:
Scenario | What is visible in Bedolaga MCP | LLM action |
Deposit without purchase |
| Explain that the money has been credited to the balance, but a separate purchase has not been completed; direct the user to complete the purchase from the balance. Do not claim the subscription is broken |
Purchase with working panel | Completed | Check the actual panel state via Remnawave MCP |
Purchase without panel record | Completed | Escalate as a confirmed discrepancy with a brief factual summary |
No deposit | No | Do not claim the payment provider did not charge the money (Bedolaga only confirms the absence of a credit in its own accounting system); escalate if the user reports an actual charge |
Referral question |
| Route only to Bedolaga MCP |
Node / HWID question | — | Route only to Remnawave MCP (Bedolaga does not know node and device state) |
Result format
Each tool returns JSON in the text MCP content with a unified envelope:
success:
ok: true,source: "bedolaga-mcp",tool,data,meta;error:
ok: false,source,tool,error.code, safeerror.message,error.retryable.
The raw Bedolaga API response body and Python model exceptions are not returned. Tools do not return email, subscription link, crypto link, keys, external payment IDs, receipt identifiers, Remnawave identifiers, or personal data of referrals.
Error codes
Code | Retryable | When it occurs |
| no | Both or neither identity field provided; invalid value |
| no | Missing/incorrect environment configuration |
| no | Identity cannot be matched to a Bedolaga user |
| no | User not found (upstream 404) |
| no | Invalid/missing API credentials (upstream 401/403) |
| yes | Rate limit reached (upstream 429) |
| yes | Timeout or network failure before response |
| yes | Upstream unavailable (5xx or unrecoverable error) |
| no | Response body is invalid JSON or not an object |
| no | Unexpected internal error |
The user-facing message is built only from the safe error.message and never exposes the HTTP body or internal URL.
Transports
The server supports two transports on a single server factory and a single tool registry:
Transport | Launcher | Port | Protocol |
Streamable HTTP (primary) |
| 3100 by default | Dual-era MCP on |
Stdio |
| — | MCP stdio handshake (same factory) |
The / endpoint is the only one, but it serves two protocol eras simultaneously; SDK v2 itself determines which era each request belongs to via the MCP-Protocol-Version header:
Modern protocol
2026-07-28— stateless/sessionless. Each POST to/is self-contained: the server never issuesMcp-Session-Idand does not store state between requests. Official MCP SDK v2 clients (see "Official SDK v2 client" below) use this mode automatically.Legacy clients with initialize handshake (protocols up to
2025-11-25, including2024-11-05) receive theMcp-Session-Idheader in response toinitializeand must pass it in all subsequent requests.DELETE /with this header terminates exactly that session; it does not affect other sessions or modern clients.
GET /health returns process liveness and the server version without exposing configuration or secrets.
Version compatibility
Component | Version |
Bedolaga Bot API (upstream) | commit |
bedolaga-mcp |
|
Python MCP SDK ( |
|
Supported MCP protocols |
|
supportBot |
|
mcp-remnawave |
|
The tool contract is verified against the specified upstream commit and the mcp-remnawave v3.2.1 reference.
Requirements
Python 3.11+
Docker (optional)
A deployed Bedolaga Bot with Web API
An API key from Bedolaga (issued in the bot's admin panel)
Quick start
1. Clone
git clone https://github.com/mitetenov/bedolaga-mcp.git
cd bedolaga-mcp2. Configure
cp .env.example .env
# Заполнить BEDOLAGA_API_URL и BEDOLAGA_API_KEY3. Run
Streamable HTTP (recommended):
# Установить зависимости
pip install -r requirements.txt
# Запустить HTTP-сервер
BEDOLAGA_API_URL=https://your-bot.example.com \
BEDOLAGA_API_KEY=your-key \
python3 http_server.pyThe server will listen on http://0.0.0.0:3100, MCP endpoint is the root /.
Stdio:
BEDOLAGA_API_URL=https://your-bot.example.com \
BEDOLAGA_API_KEY=your-key \
python3 bedolaga_server.pyVia Docker:
docker compose up -dThe Docker image by default runs the Streamable HTTP server on port 3100.
Connecting as an MCP server
Streamable HTTP
The server is available over HTTP on port 3100, endpoint is the root / (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"
}
}
}Checking via curl (legacy compatibility check)
Raw JSON-RPC via curl uses the legacy initialize-handshake (protocol 2024-11-05) — this is a manual backward-compatibility check, not how modern clients operate. Modern MCP SDK v2 clients negotiate protocol 2026-07-28 automatically and do not receive Mcp-Session-Id (see "Official SDK v2 client (modern protocol)" below).
# 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>"Official SDK v2 client (modern protocol)
The official client from Python MCP SDK v2 (mcp==2.0.0) negotiates the protocol itself — 2026-07-28 if the server supports it, otherwise legacy handshake — without manually constructing _meta or headers:
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" is the same negotiation that supportBot uses: the client itself decides whether it is dealing with a modern server or a legacy one, and does not require the calling code to know the protocol era in advance.
Stdio transport
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
Add to .cursor/mcp.json or 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"
}
}
}
}Session management
The Streamable HTTP transport is dual-era, and sessions apply to only one of the two eras:
Legacy initialize-handshake (protocols up to
2025-11-25): afterinitialize, the server returns theMcp-Session-Idheader, which the client must send in all subsequent requests.DELETE /with this header terminates only the specified session; one client cannot terminate or reuse another client's session.Modern protocol
2026-07-28: stateless/sessionless — the server never issuesMcp-Session-Id, andDELETE /is neither needed nor used for such clients.
Environment variables
Variable | Purpose |
| Bedolaga Web API URL |
| Bedolaga API key (passed upstream in |
| Address to bind (default: |
| HTTP server port (default: |
| Upstream timeout in milliseconds (default: 10000) |
For compatibility, the legacy variables HOST/PORT are accepted if MCP_HTTP_HOST/MCP_HTTP_PORT are not set.
Upstream API
Bedolaga Web API: X-API-Key in the header. Routes used:
GET /users/by-telegram-id/{telegram_id}— user by Telegram ID;GET /users/{user_id}— user by internal ID (email-only tickets);GET /transactions?user_id=...— transactions with filters and pagination;GET /partners/referrers/{user_id}— referral card.
More details: https://docs.bedolagam.ru
First-version limitations
No provider-specific payment attempts. Bedolaga returns only transactions that became records in the shared transactions table. Raw payment provider attempts that did not become a record are unavailable.
No reading of the user's Redis cart. The current Web API does not provide a safe read-only endpoint for this. The current "top-up made, but no purchase" issue is reliably diagnosed by the difference between
depositandsubscription_payment(see decision table).Email-only lookup is supported. For cabinet tickets without a Telegram ID, the server accepts an internal
user_id(a positive integer) and resolves it viaGET /users/{user_id}. supportBot pins the cabinet's internaluser_id(the absolute value of the negative synthetic conversation key) — for such tickets, Bedolaga data is available, while Remnawave tools returnidentity_unavailablebecause such a user has no Telegram identity and no proven record in the panel.
Rollback
Setting BEDOLAGA_MCP_ENABLED=false in supportBot returns it to Remnawave-only mode: Bedolaga MCP is not connected, its tools disappear from the allowlist, and webhook/poller ticket handling (BEDOLAGA_ENABLED) remains independent. The rollback does not touch the user base or financial data — Bedolaga MCP is read-only and stores no state.
Rolling back the bedolaga-mcp image to tag 1.1.0 (the last release before the migration to MCP SDK v2, legacy-era Streamable HTTP only) is also safe: the supportBot client on MCP SDK v2 automatically falls back (auto-fallback) to the legacy initialize-handshake if the server does not respond to the modern 2026-07-28 protocol, so Bedolaga MCP tools remain available without additional configuration.
This server cannot be deployed
Maintenance
Related MCP Connectors
Non-custodial crypto payments for AI assistants: balances, payments, and create payment links.
Accept crypto payments via the TgPay Merchant API — invoices, subscriptions, webhooks.
Pay-per-use web extract, token prices, and wallet balances via x402 USDC micropayments.
Telegram bridge for your MCP-compatible agent. Bidirectional, no LLM in our stack.
Related MCP Servers
FlicenseNot gradedqualityNot gradedmaintenanceProvides user balance information by connecting to a backend service through the users_balance tool. Built with TypeScript and Express for retrieving financial data.-- AlicenseAqualityAmaintenanceEnables integration with Monobank API to check currency exchange rates, view account balances, and retrieve transaction statements through natural language queries.329 npm9MIT
- AlicenseNot gradedqualityDmaintenanceEnables interaction with Telegram Bot API for sending messages, photos, editing messages, answering callbacks, and fetching updates.MIT
- AlicenseNot gradedqualityBmaintenanceEnables sending Telegram messages, photos, and documents, and retrieving bot information through the Telegram Bot API.23 npm1MIT