Vaani-Pay MCP Server
Vaani Pay Assistant
Безопасный, работающий в реальном времени, многопользовательский, двуязычный (английский + хинди) чат-бот поддержки платежей: пользователи регистрируются и входят в систему под своей учётной записью, а затем спрашивают о своих платежах, заказах, возвратах, транзакциях, рисках мошенничества и статистике — со строгой изоляцией данных для каждого пользователя на уровне MCP-инструментов, настоящей базой данных SQLite и живым потоком статусов по WebSocket, показывающим, что делает агент.
Всё начиналось как статическая демонстрационная сборка для хакатона (захардкоженные пользователи, фиксированные токены, только английский) и было обновлено до платформы на базе данных, многопользовательской, безопасной и двуязычной, без изменения рабочих частей, которые уже работали: протокол WebSocket, архитектура MCP-инструментов и базовая логика агента имеют ту же форму, что и раньше, — под ними изменились только источник данных и модель аутентификации.
Архитектура
Browser (chat UI + login/signup/profile)
│ REST (/auth, /users/me, /transactions) │ WebSocket (/ws)
▼ ▼
FastAPI — auth endpoints, profile endpoints FastAPI — WebSocket handler
│ │
▼ ▼
app/auth.py (register / login / sessions) AI Agent (app/agent.py)
│ NLU (Grok API) → intent + entities
│ Tool selection → MCP tool
▼ │
app/db.py — SQLite │ MCP (stdio transport)
users, sessions, chat_history, ▼
payments, orders, refunds, transactions MCP Server (mcp_server/server.py)
▲ get_payment_status │ get_order_details
│ get_refund_status │ get_customer_details
└───────────── same DB, same ownership ──── get_transaction_history │ check_fraud_risk
checks on every query get_payment_statistics
│
▼
mcp_server/data_layer.py
— ownership check on every lookup,
now backed by SQLite instead of JSONRelated MCP server: nexi-xpay-mcp-server
1. Аккаунты и конфиденциальность данных
Настоящие аккаунты.
POST /auth/registerсоздаёт строку пользователя в SQLite с надёжно хешированным паролем (PBKDF2-HMAC-SHA256, случайная соль для каждого пароля, 260 000 итераций — см.app/security.py). Пароли в открытом виде нигде не хранятся и не логируются.Настоящие сессии входа.
POST /auth/loginпроверяет учётные данные и выдаёт непрозрачный, не поддающийся угадыванию токен сессии (generate_token()изapp/security.py, 256 бит энтропии), хранящийся в таблицеsessionsсо сроком действия (SESSION_TTL_HOURSв.env, по умолчанию 24 часа). Просроченные или неизвестные токены отклоняются везде, где проверяются.Каждый MCP-инструмент, который ищет конкретный ресурс (
get_payment_status,get_order_details,get_refund_status,check_fraud_risk), требует параметрrequesting_user_idи проверяет вmcp_server/data_layer.py, что ресурс действительно принадлежит этому пользователю — теперь через параметризованный SQL-запрос с условиемWHERE ... AND user_id = ?— прежде чем что-либо вернуть.Если ресурс принадлежит кому-то другому или вообще не существует, в обоих случаях возвращается тот же самый общий ответ:
"Access denied. You are not authorized to access this information."Если бы для случаев «не найдено» и «чужие данные» возвращались разные сообщения, пользователь мог бы перебирать валидные ID, наблюдая, какая ошибка приходит, — одинаковый ответ закрывает этот побочный канал.requesting_user_id— это всегда аутентифицированная личность вызывающей стороны (определяется один раз при аутентификации через WebSocket или при REST-запросе — см.app/auth.py), а не значение, извлечённое из сообщения чата, URL-параметра или тела запроса. Схема извлечения вapp/nlu.pyвообще не содержит поляuser_id, поэтому сообщение (даже злонамеренное) не может протащить другую личность в вызов инструмента.get_customer_details,get_transaction_historyиget_payment_statisticsвообще не принимают ID ресурса — они всегда возвращают данные самого вызывающего пользователя, так что для этих трёх инструментов не существует никакой поверхности для манипуляции ID.Удаление аккаунта (
DELETE /users/me) требует повторного ввода текущего пароля в качестве подтверждения, после чего удаляет строку пользователя — внешние ключиON DELETE CASCADEудаляют вместе с ней все сессии, историю чатов, платежи, заказы, возвраты и транзакции этого пользователя.
Проверьте изоляцию данных напрямую:
python3 test_offline.pyЭто выполняет реальные вызовы инструментов (к настоящему MCP-серверу, читая из настоящей базы данных SQLite) от имени двух разных демонстрационных пользователей и проверяет, что попытки межпользовательского доступа отклоняются, что история транзакций каждого пользователя содержит только его собственные данные и что двуязычные ответы отображаются корректно.
2. Регистрация, вход и управление аккаунтом
POST /auth/register— имя, email, пароль, необязательный телефон и языковая настройка. Пароль должен быть не короче 8 символов и содержать смесь букв и цифр (app/security.py).POST /auth/login— возвращает токен сессии + профиль пользователя.POST /auth/logout— отзывает текущий токен сессии на стороне сервера.GET /users/me/PUT /users/me— просмотр/обновление профиля (имя, телефон).POST /users/me/change-password— требует текущий пароль; его смена делает недействительными все существующие сессии (вынуждает повторный вход везде), поэтому утёкший старый токен перестаёт работать.GET /users/me/preferences/PUT /users/me/preferences— чтение/обновление языковой настройки (en/hi), сохраняемой в базе данных, так что она переживает выход и повторный вход.DELETE /users/me— окончательное удаление аккаунта (требуются пароль и явноеconfirm: true).
Всё это также доступно из самого чат-интерфейса через кнопку ⚙️ в шапке (просмотр/редактирование профиля, переключатель языка, смена пароля, выход, удаление аккаунта).
3. Чат-интерфейс
static/index.html — одностраничное приложение:
Вкладки входа и регистрации отображаются до того, как станет возможен чат.
Панель профиля и настроек (редактирование имени/телефона, смена пароля, переключатель языка, выход, удаление аккаунта с шагом подтверждения).
Сворачиваемое меню подсказок над полем ввода, отображаемое из того же словаря переведённых строк, что и остальной интерфейс.
Пузыри чата, строка статуса в реальном времени и индикатор статуса в шапке — без изменений относительно исходного дизайна.
4. Связь в реальном времени
Чат по-прежнему работает через один WebSocket (/ws) — форма протокола не изменилась, только теперь вместо статического значения токен аутентификации — это настоящий токен сессии на основе базы данных:
{"type": "auth", "token": "<session token from /auth/login>"}
↓
{"type": "auth_success", "user_id": "...", "name": "...", "language": "en"}Для каждого сообщения чата сервер потоково передаёт события статуса в следующем порядке, а затем финальный (локализованный) ответ:
🔍 Understanding your request...
🔧 Checking payment information...
✓ Payment information retrieved
🤖 Generating response...
<final answer, in the user's selected language>Каждый ход чата (и сообщения пользователя, и сообщения ассистента) также сохраняется в таблицу chat_history (_persist_chat_turn из app/main.py) с привязкой к аутентифицированному пользователю.
5. Архитектура на основе MCP
mcp_server/server.py предоставляет ровно эти 7 инструментов, разбитых по доменным модулям в mcp_server/tools/:
Tool | File |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Всё это работает на базе SQLite (mcp_server/data_layer.py → app/db.py) вместо статических JSON-данных. Сигнатуры инструментов, агент и фронтенд не изменились по сравнению с исходным дизайном — поменялся только источник данных под data_layer.py, ровно так, как и предусматривала исходная архитектура.
6. Двуязычная поддержка (английский + хинди)
Строки интерфейса: словарь
UI_STRINGSизapp/i18n.py, отдаваемый черезGET /i18n/{lang}. Фронтенд получает его один раз при загрузке и при каждом изменении языка и применяет через атрибутыdata-i18n/data-i18n-placeholder— ни одна переведённая строка не захардкожена в HTML/JS.Ответы ИИ-ассистента:
AGENT_STRINGSизapp/i18n.py(фиксированные сообщения вроде приветствий) иREPLY_TEMPLATES(интерполируемые сообщения вроде статуса платежа).app/agent.pyформирует каждый ответ через них — нигде в агенте английский текст не захардкожен напрямую.NLU: промпт в
app/nlu.pyявно просит модель Grok обрабатывать ввод на хинди/английском или смешанный ввод и всегда переводить его на английский внутри для извлечения намерений и сущностей, так что ассистент понимает вопрос в любом случае и отвечает на предпочитаемом пользователем языке.Хранение: языковая настройка хранится в поле
users.languageв базе данных (задаётся при регистрации и может быть изменена в любой момент черезPUT /users/me/preferences), поэтому она переживает выход и повторный вход.Динамическое переключение: смена языка в настройках немедленно обновляет интерфейс и переподключает WebSocket, так что уже следующий ответ чата приходит на новом языке — без перезагрузки страницы.
7. Требования к безопасности
Требование | Где реализовано |
Аутентификация |
|
Авторизация |
|
Изоляция пользователя/сессии |
|
Проверки прав на уровне MCP | Применяются внутри самих MCP-инструментов ( |
Проверка входных данных |
|
Защита от SQL-инъекций | Каждый запрос в |
Ограничение частоты запросов |
|
Безопасный CORS |
|
Безопасное хеширование паролей |
|
Истечение срока токена |
|
Общие сообщения об ошибках аутентификации |
|
Безопасная обработка ошибок |
|
Защита от манипуляции идентификаторами | Пользователь может ввести любой |
8. Схема базы данных
users id, name, email, phone, password_hash, language, created_at, updated_at, last_login
sessions token, user_id, created_at, expires_at
chat_history id, user_id, conversation_id, role, message, timestamp
payments payment_id, user_id, status, amount, method, failure_reason, date
orders order_id, user_id, status, total, items (JSON), date
refunds refund_id, user_id, payment_id, amount, status, date
transactions txn_id, user_id, type, amount, status, date
-- Wallet: the real money-movement system (see section 9 below)
payment_accounts id, user_id, payment_id, account_number, ifsc, balance, currency, status, created_at
wallet_transactions id, transaction_id, sender_account_id, receiver_account_id, amount, transaction_type,
status, description, sender_name, receiver_name, recipient_account_number,
recipient_ifsc, failure_reason, created_at, updated_at
beneficiaries id, user_id, recipient_name, account_number, ifsc, created_atПолный DDL с внешними ключами и индексами см. в SCHEMA в app/db.py.
9. Кошелёк: платёжные счета, пополнение и отправка денег
Каждый зарегистрированный пользователь получает настоящий, рабочий
кошелёк, а не просто просмотр истории платежей. Это самое большое
добавление поверх обновления базы данных и аутентификации, и оно
реализовано как отдельный модуль (app/wallet.py), который вызывают и
REST API, и AI/MCP-инструменты, так что правила движения денежных
средств соблюдаются ровно в одном месте.
Автоматическое создание счёта. POST /auth/register создаёт запись
пользователя И запись payment_accounts в той же транзакции базы
данных (см. register() в app/auth.py, вызывающий
insert_account_row() из app/wallet.py) — пользователь не может
существовать без кошелька, а кошелёк никогда не создаётся как
отдельный, независимо завершаемый с ошибкой шаг. Каждый счёт получает:
уникальный платёжный ID (
PAY..., внутренний идентификатор),уникальный 12-значный номер счёта,
фиксированный IFSC (
VPAY0000001— Vaani Pay — это виртуальный кошелёк с одним отделением, поэтому у всех счетов один общий IFSC, как это часто бывает у виртуальных счетов настоящих необанков),начальный баланс ₹0.
Ответ регистрации включает подтверждение message: "Your payment account has been successfully created." плюс новые данные счёта, которые сразу
показываются пользователю (и в ответе API, и в сообщении подтверждения
на экране регистрации).
Пополнение. POST /wallet/add-money (или кнопка «Add Money» на
экране кошелька, или просьба к AI-ассистенту «add ₹5,000 to my account») —
проверяет сумму (> ₹0, ≤ ₹2,00,000 за транзакцию —
MAX_ADD_MONEY в app/wallet.py), затем атомарно обновляет баланс
и добавляет запись CREDIT в wallet_transactions. Для хакатона в
систему не подключён реальный платёжный шлюз — это явно
симулированное пополнение, соответствующее требованию брифa
"безопасный симулированный процесс пополнения".
Отправка денег — всегда двухшаговое подтверждение. Ни REST API, ни AI-ассистент никогда не перемещают деньги одним вызовом:
POST /wallet/transfers(initiate_transferвapp/wallet.py) проверяет получателя и баланс отправителя и создаёт записьPENDINGвwallet_transactions— баланс пока не меняется. Он возвращает предпросмотр подтверждения (получатель, замаскированный номер счёта, IFSC, сумма, комиссия, общее списание) — именно он отображается на экране «Confirm Transfer».POST /wallet/transfers/{id}/confirm(confirm_transfer) — это единственный вызов, который реально перемещает деньги. Он повторно проверяет баланс отправителя и статус счёта в момент подтверждения (а не только в момент инициации, на случай если что-то изменилось между шагами — например, две инициации переводов подряд), затем списывает средства с отправителя и, если получатель — реальный счёт Vaani Pay, зачисляет их ему, всё это в одной атомарной транзакции SQLite, защищённой общепроцессной блокировкой. Если что-то ломается на полпути, всё откатывается целиком — перевод никогда не может остаться списанным, но не зачисленным.POST /wallet/transfers/{id}/cancelотменяет перевод, который всё ещё в статусеPENDING, не затрагивая никакие балансы.
Отправка на номер счёта, которого нет в нашей системе, всё равно проходит успешно (как симулированный внешний перевод — средства списываются у отправителя, но счёта Vaani Pay для зачисления нет), что соответствует требованию брифa: «зачислить средства получателю, если получатель существует в симулированной системе».
Проверка получателя. POST /wallet/validate-recipient
(validate_recipient) проверяет: формат номера счёта (9–18 цифр),
формат IFSC (^[A-Z]{4}0[A-Z0-9]{6}$), что IFSC совпадает с номером
счёта, если это внутренний счёт, и — что критически важно — что
отправитель не переводит средства на собственный номер счёта. Если
указано только имя получателя (без номера счёта), выполняется поиск
имени среди сохранённых получателей самого вызывающего пользователя, и
если есть ровно одно совпадение, оно автоматически разрешается.
Сохранённые получатели. После успешного перевода интерфейс
предлагает «Save this recipient?» — POST /beneficiaries сохраняет
получателя только для аутентифицированного пользователя (никогда не
глобально и не для всех), так что в следующий раз пользователь (или AI-
ассистент, когда его просят «send ₹2,000 to Rahul») может выполнить
перевод по одному имени.
История транзакций и фильтры. GET /wallet/transactions?filter=...
(all / add_money / sent / received / failed / pending) — всё
вычисляется в реальном времени из wallet_transactions, никогда не
захардкожено. Вкладка History на экране кошелька и AI-запросы «show my
wallet transactions» / «how much did I spend this month» читают данные
из одной и той же функции (get_wallet_transactions /
get_spending_summary в app/wallet.py).
Баланс всегда вычисляется производно, никогда не устанавливается
напрямую. В кодовой базе намеренно нет функции set_balance() —
баланс меняется только как побочный эффект add_money() или
confirm_transfer(), и обе эти функции в том же атомарном шаге также
добавляют неизменяемую запись в wallet_transactions. Фронтенд всегда
отображает только то, что возвращает GET /wallet/account; влиять на
баланс он не может.
Безопасность кошелька, в частности
Правило | Как обеспечивается |
Пользователь никогда не может напрямую изменить свой собственный баланс | Ни одна публичная функция не устанавливает баланс, кроме как побочный эффект «Пополнения» / confirm_transfer, обе операции проверяют сумму и создают запись аудита |
Пользователь никогда не может изменить баланс другого пользователя | Каждая функция кошелька принимает аутентифицированный |
Пользователь никогда не может подтвердить/отменить чужой перевод |
|
Переводы самому себе заблокированы |
|
Суммы нельзя изменить в процессе | Сумма, фактически используемая для списания/зачисления в момент |
ИИ не может перемещать деньги без явного подтверждения | MCP-инструменты |
Атомарность |
|
10. Двуязычный платёжный процесс
Кошелёк полностью двуязычный и использует тот же механизм app/i18n.py, что и остальное приложение — «Пополнение счёта», «Отправка денег» (все три шага), экран подтверждения, статусы транзакций и все ответы ИИ о балансе/переводах отображаются через t()/tpl() без захардкоженного английского где-либо в app/wallet.py, app/agent.py или в UI кошелька в static/index.html. Например, запрос к ИИ "Rahul ko ₹2,000 bhejo" (хинди/хинглиш) проходит через тот же самый процесс «определение → подтверждение → выполнение», что и английская версия, при этом каждое сообщение — включая экран подтверждения — отображается на хинди.
Настройка
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Add your Grok API key (get one at https://console.x.ai)База данных создаётся (и, только если она пуста, заполняется двумя демонстрационными пользователями — см. ниже) автоматически при первом запуске; для локальной разработки отдельный шаг миграции не требуется. Чтобы создать её явно заранее:
python3 -m app.dbПроверьте перед открытием браузера:
python3 diagnose_setup.pyЗапуск
uvicorn app.main:app --reload --port 8000Откройте http://localhost:8000. Зарегистрируйте новую учётную запись или войдите с помощью одной из созданных демонстрационных учётных записей:
Пароль | |
|
|
|
|
Затем попробуйте меню подсказок, задавайте вопросы вроде "check payment status pay_1001" или "मेरा भुगतान pay_1001 का स्टेटस क्या है?", переключите язык в настройках или попробуйте войти как один пользователь и спросить о платежах/заказах/возвратах другого пользователя (pay_1003, ord_2002, rfnd_3002 принадлежат Прие), чтобы увидеть ответ об отказе в доступе.
Чтобы попробовать кошелёк: нажмите кнопку 💰 «Кошелёк» в шапке. Обе демо-учётные записи имеют начальный баланс (₹8 500 у Рамеша, ₹8 000 у Прии) и одну демонстрационную транзакцию в истории. Попробуйте «Пополнить» или «Отправить деньги» на номер счёта другой демо-учётной записи (он виден на экране кошелька этой записи) или напрямую спросите ИИ-ассистента: "what's my balance?", "add ₹5,000 to my account", "send ₹2,000 to Priya Stores" (в первый раз он попросит номер её счёта и IFSC, а после успешного перевода предложит сохранить её как получателя — после этого достаточно просто её имени) или "Mera current balance kitna hai?" на хинди.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
AlicenseNot gradedqualityAmaintenanceEnables AI agents to interact with Juspay's payment processing APIs and merchant dashboard for managing orders, transactions, refunds, customers, gateways, and reporting through natural language.21Apache 2.0- AlicenseAqualityDmaintenanceEnables AI assistants to query orders, transaction details, warnings/anomalies, and payment methods from your Nexi XPay merchant account.4MIT

AlipayPlus MCP Serverofficial
AlicenseAqualityDmaintenanceIntegrates Ant International's AlipayPlus payment APIs, enabling AI assistants to handle payment and refund operations seamlessly.68MIT- FlicenseNot gradedqualityCmaintenanceEnables AI to query a business database for customers, orders, and revenue using natural language through safe, well-defined tools.
Related MCP Connectors
Taiwan payments (ECPay 綠界 + NewebPay 藍新) & e-invoices for AI agents. Stateless, never holds funds.
Korea payments for AI agents — card, KakaoPay/NaverPay, 가상계좌 via Toss Payments. Never holds funds.
Let AI agents add Yolfi crypto checkout, paylinks, webhooks, and status checks.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/divyaupadhyay56/Vaani-Pay'
If you have feedback or need assistance with the MCP directory API, please join our Discord server