Skip to main content
Glama

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 JSON

Related 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

get_payment_status

payment_tools.py

check_fraud_risk

payment_tools.py

get_order_details

order_tools.py

get_refund_status

refund_tools.py

get_customer_details

customer_tools.py

get_transaction_history

customer_tools.py

get_payment_statistics

analytics_tools.py

get_balance

wallet_tools.py

add_money

wallet_tools.py

get_transactions

wallet_tools.py

validate_recipient

wallet_tools.py

create_transfer

wallet_tools.py

confirm_transfer

wallet_tools.py

cancel_transfer

wallet_tools.py

get_spending_summary

wallet_tools.py

Всё это работает на базе SQLite (mcp_server/data_layer.pyapp/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. Требования к безопасности

Требование

Где реализовано

Аутентификация

app/auth.py — хеширование паролей, токены сессий с истечением срока. WebSocket не обработает ни одно сообщение чата, и ни одна REST-конечная точка не вернёт данные, пока токен не будет проверен.

Авторизация

mcp_server/data_layer.py — каждый запрос ресурса фильтруется по user_id прямо в SQL.

Изоляция пользователя/сессии

app/session_store.py — каждое WebSocket-соединение получает собственное состояние разговора в памяти; user_id/language устанавливаются один раз при аутентификации и никогда не перезаписываются из текста чата.

Проверки прав на уровне MCP

Применяются внутри самих MCP-инструментов (mcp_server/tools/*.pydata_layer.py), а не только на границе приложения — см. diagnose_setup.py и test_offline.py, которые вызывают MCP-сервер напрямую и подтверждают отказ.

Проверка входных данных

app/main.py (Pydantic-модели для каждого REST-тела; проверки типа и длины сообщений в WebSocket) и app/auth.py (формат email, политика паролей).

Защита от SQL-инъекций

Каждый запрос в app/db.py / mcp_server/data_layer.py использует параметризованные плейсхолдеры ? — SQL, собираемый из строк, нигде не используется.

Ограничение частоты запросов

app/main.py — скользящее окно ограничения на IP для /auth/register и /auth/login.

Безопасный CORS

app/main.py — явный список разрешённых источников (CORS_ALLOWED_ORIGINS в .env), по умолчанию только localhost; никогда не * с учётными данными.

Безопасное хеширование паролей

app/security.py — PBKDF2-HMAC-SHA256, случайная соль для каждого пароля, 260 000 итераций.

Истечение срока токена

app/auth.py — сессии истекают через SESSION_TTL_HOURS; при смене пароля все существующие сессии становятся недействительными.

Общие сообщения об ошибках аутентификации

app/auth.py — одинаковое сообщение для «такого email нет» и «неверный пароль»; одинаковое сообщение для «не найдено» и «чужой ресурс».

Безопасная обработка ошибок

_safe_error_message() / глобальный обработчик исключений в app/main.py — неожиданные ошибки полностью логируются на стороне сервера, клиент получает только общее сообщение.

Защита от манипуляции идентификаторами

Пользователь может ввести любой payment_id/order_id/refund_id — инструмент всегда возвращает данные только в том случае, если они принадлежат его аутентифицированному аккаунту (обеспечивается SQL).

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-ассистент никогда не перемещают деньги одним вызовом:

  1. POST /wallet/transfers (initiate_transfer в app/wallet.py) проверяет получателя и баланс отправителя и создаёт запись PENDING в wallet_transactionsбаланс пока не меняется. Он возвращает предпросмотр подтверждения (получатель, замаскированный номер счёта, IFSC, сумма, комиссия, общее списание) — именно он отображается на экране «Confirm Transfer».

  2. POST /wallet/transfers/{id}/confirm (confirm_transfer) — это единственный вызов, который реально перемещает деньги. Он повторно проверяет баланс отправителя и статус счёта в момент подтверждения (а не только в момент инициации, на случай если что-то изменилось между шагами — например, две инициации переводов подряд), затем списывает средства с отправителя и, если получатель — реальный счёт Vaani Pay, зачисляет их ему, всё это в одной атомарной транзакции SQLite, защищённой общепроцессной блокировкой. Если что-то ломается на полпути, всё откатывается целиком — перевод никогда не может остаться списанным, но не зачисленным.

  3. 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, обе операции проверяют сумму и создают запись аудита

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

Каждая функция кошелька принимает аутентифицированный user_id вызывающего пользователя и ищет payment_accounts через WHERE user_id = ? — никогда через идентификатор счёта, предоставленный клиентом

Пользователь никогда не может подтвердить/отменить чужой перевод

confirm_transfer/cancel_transfer проверяют, что счёт отправителя транзакции PENDING принадлежит вызывающему пользователю, используя один и тот же общий ответ «не найдено», независимо от того, не существует ли транзакция или она принадлежит другому лицу (проверено в test_offline.py и diagnose_setup.py)

Переводы самому себе заблокированы

validate_recipient сравнивает номер счёта получателя с собственным номером счёта отправителя, прежде чем разрешить перевод

Суммы нельзя изменить в процессе

Сумма, фактически используемая для списания/зачисления в момент confirm_transfer, — это сумма, сохранённая в строке PENDING, созданной в момент initiate_transfer, и никогда не считывается повторно из запроса на подтверждение

ИИ не может перемещать деньги без явного подтверждения

MCP-инструменты create_transfer/add_money никогда не списывают и не зачисляют средства сами по себе; конечный автомат диалога в app/agent.py требует явного ответа «да» на показанное подтверждение перед вызовом confirm_transfer

Атомарность

confirm_transfer выполняет проверку баланса + оба обновления баланса + обновление статуса внутри одной транзакции SQLite (tx() из app/db.py), плюс блокировку на уровне процесса — см. docstring модуля app/wallet.py

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. Зарегистрируйте новую учётную запись или войдите с помощью одной из созданных демонстрационных учётных записей:

Email

Пароль

ramesh@example.com

Demo@1234

priya@example.com

Demo@1234

Затем попробуйте меню подсказок, задавайте вопросы вроде "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?" на хинди.

F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables 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.
    21
    Apache 2.0
  • A
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to query orders, transaction details, warnings/anomalies, and payment methods from your Nexi XPay merchant account.
    4
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Integrates Ant International's AlipayPlus payment APIs, enabling AI assistants to handle payment and refund operations seamlessly.
    6
    8
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI to query a business database for customers, orders, and revenue using natural language through safe, well-defined tools.

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

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