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 は、安全にハッシュ化されたパスワード(PBKDF2-HMAC-SHA256、パスワードごとのランダムソルト、260k 回の反復 — app/security.py を参照)とともに SQLite にユーザー行を作成します。平文パスワードはどこにも保存もログ記録もされません。

  • 実際のログインセッション。 POST /auth/login は認証情報を検証し、不透明で推測不可能なセッショントークン(app/security.pygenerate_token()、256 ビットのエントロピー)を発行します。これは sessions テーブルに有効期限付き(.env 内の SESSION_TTL_HOURS、デフォルト 24 時間)で保存されます。期限切れまたは不明なトークンは、チェックされるすべての場所で拒否されます。

  • 特定のリソースを参照するすべての MCP ツールget_payment_statusget_order_detailsget_refund_statuscheck_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 は常に呼び出し元の認証済み ID です(WebSocket 認証時 / REST リクエスト時に一度だけ解決されます — app/auth.py を参照)。チャットメッセージ、URL パラメータ、リクエストボディから解析された値が使われることは決してありません。app/nlu.py の抽出スキーマには user_id フィールドがまったくないため、メッセージ(敵対的なものであっても)が別の ID をツール呼び出しに紛れ込ませる方法はありません。

  • get_customer_detailsget_transaction_historyget_payment_statisticsリソース ID を一切受け取りません。常に呼び出し元自身のデータを返すため、これら 3 つのツールには ID 操作の攻撃面がまったくありません。

  • アカウント削除DELETE /users/me)では、確認として現在のパスワードを再入力する必要があります。その後、ユーザー行が削除されます — ON DELETE CASCADE 外部キーにより、そのユーザーのセッション、チャット履歴、支払い、注文、返金、取引もすべて一緒に削除されます。

データ分離を直接検証:

python3 test_offline.py

これは、実際の MCP サーバーに対して実際のツール呼び出しを実行し(実際の SQLite データベースから読み取り)、2 人の異なるデモユーザーとして、ユーザー間のアクセス試行が拒否されること、各ユーザーの取引履歴に自分のデータのみが含まれること、およびバイリンガルの応答が正しくレンダリングされることを検証します。

2. 登録、ログイン、アカウント管理

  • POST /auth/register — 名前、メール、パスワード、任意の電話番号、言語設定。パスワードは 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 が必要)。

これらはすべて、チャット UI 自体からも、ヘッダーの ⚙️ ボタンで利用できます(プロフィールの表示/編集、言語切り替え、パスワード変更、ログアウト、アカウント削除)。

3. チャット UI

static/index.html — シングルページアプリケーションです:

  • チャットが可能になる前に、ログインタブ / サインアップタブが表示されます。

  • プロフィールと設定パネル(名前/電話番号の編集、パスワード変更、言語切り替え、ログアウト、確認ステップ付きのアカウント削除)。

  • 入力欄の上にある折りたたみ可能な候補メニュー。UI の他の部分と同じ翻訳文字列辞書からレンダリングされます。

  • チャットバブル、ライブステータス行、ヘッダーのステータスインジケーター — 元のデザインから変更なし。

4. リアルタイム通信

チャットは引き続き単一の WebSocket(/ws)を介して行われます。プロトコルの形式は変更されていません。認証トークンが静的値ではなく、実際に DB で管理されたセッショントークンになっただけです:

{"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 テーブル(app/main.py_persist_chat_turn)に永続化されます。

5. MCP ベースのアーキテクチャ

mcp_server/server.py は、mcp_server/tools/ 配下のドメインモジュールに分割された、ちょうど以下の 7 つのツールを公開しています:

ツール

ファイル

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

これらはすべて、静的 JSON の代わりに SQLite データベース(mcp_server/data_layer.pyapp/db.py)によって支えられています。ツールのシグネチャ、エージェント、フロントエンドは元の設計から変更されていません — data_layer.py の下にあるデータソースだけが変更されており、これは元のアーキテクチャが許可するように設計されていたとおりです。

6. バイリンガル対応(英語 + ヒンディー語)

  • UI 文字列: app/i18n.pyUI_STRINGS 辞書で、GET /i18n/{lang} を介して提供されます。フロントエンドは、ロード時と言語変更のたびにこれを取得し、data-i18n/data-i18n-placeholder 属性を介して適用します — 翻訳文字列が HTML/JS にハードコードされることはありません。

  • AI アシスタントの応答: app/i18n.pyAGENT_STRINGS(挨拶などの固定メッセージ)と REPLY_TEMPLATES(支払いステータスなどの補間メッセージ)。app/agent.py はすべての応答をこれらを通じてレンダリングします — エージェント内で英語テキストが直接ハードコードされることはありません。

  • NLU: app/nlu.py のプロンプトは、Grok モデルにヒンディー語/英語/混合入力の処理を明示的に求め、意図/エンティティ抽出のために内部では常に英語に翻訳するよう指示します。これにより、アシスタントはどちらの言語の質問でも理解し、ユーザーの希望言語で応答します。

  • 永続化: 言語設定はデータベースの users.language に保持されます(サインアップ時に設定され、PUT /users/me/preferences でいつでも変更可能)。そのため、ログアウト/ログイン後も維持されます。

  • 動的切り替え: 設定で言語を変更すると、UI が即座に更新され、WebSocket が再接続されるため、次のチャット応答は新しい言語で返されます — ページの再読み込みは不要です。

7. セキュリティ要件

要件

実装箇所

認証

app/auth.py — パスワードのハッシュ化、有効期限付きセッショントークン。トークンが検証されるまで、WebSocket はチャットメッセージを処理せず、REST エンドポイントもデータを返しません。

認可

mcp_server/data_layer.py — すべてのリソース参照は SQL 自体で user_id によるフィルタリングを行います。

ユーザー/セッションの分離

app/session_store.py — 各 WebSocket 接続は独自のインメモリ会話状態を持ちます。user_id/language は認証時に一度だけ設定され、チャットテキストから上書きされることはありません。

MCP レベルの権限チェック

アプリの境界だけでなく、MCP ツール自体(mcp_server/tools/*.pydata_layer.py)の中で強制されます — diagnose_setup.pytest_offline.py は MCP サーバーを直接呼び出して拒否を確認します。

入力検証

app/main.py(すべての REST リクエストボディに対する Pydantic モデル、WebSocket でのメッセージ種別/長さチェック)および app/auth.py(メール形式、パスワードポリシー)。

SQL インジェクション対策

app/db.py / mcp_server/data_layer.py のすべてのクエリはパラメータ化された ? プレースホルダを使用しており、文字列連結で SQL を構築する箇所はありません。

レート制限

app/main.py/auth/register/auth/login に対する IP ごとのスライディングウィンドウ方式のレート制限。

安全な CORS

app/main.py — 明示的な許可リスト(.envCORS_ALLOWED_ORIGINS)、デフォルトは localhost のみ。認証情報付きで * を使用することはありません。

安全なパスワードハッシュ化

app/security.py — PBKDF2-HMAC-SHA256、パスワードごとのランダムソルト、26万回の反復。

トークンの有効期限

app/auth.py — セッションは SESSION_TTL_HOURS 後に失効します。パスワードを変更すると既存のセッションはすべて無効になります。

汎用的な認証エラーメッセージ

app/auth.py — 「そのようなメールは存在しない」と「パスワードが違う」で同一のエラーを返し、「見つからない」と「他人のリソース」でも同一のエラーを返します。

安全なエラーハンドリング

app/main.py_safe_error_message() / グローバル例外ハンドラ — 予期しないエラーはサーバー側で完全にログ記録され、クライアントには汎用メッセージのみが返されます。

ID 改ざんへの対策

ユーザーは任意の 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 は、app/db.pySCHEMA を参照してください。

9. ウォレット: 支払い口座、入金、送金

登録されたすべてのユーザーには、単なる支払い履歴ビューアーではなく、実際に使えるウォレットが与えられます。これはデータベース/認証のアップグレードに加えて最大の追加機能であり、REST API と AI/MCP ツールの両方が呼び出す独立したモジュール(app/wallet.py)として構築されています。そのため、資金移動ルールを強制する場所はちょうど1つだけです。

自動アカウント作成。 POST /auth/register は、同じデータベーストランザクション内でユーザー行と payment_accounts 行を作成します(app/auth.pyregister()app/wallet.pyinsert_account_row() を呼び出します)。ユーザーがウォレットなしで存在することは決してなく、ウォレットが独立した失敗し得るステップとして作成されることもありません。各アカウントには次のものが付与されます:

  • 一意の 支払い IDPAY...、内部識別子)、

  • 一意の12桁の 口座番号

  • 固定の IFSCVPAY0000001 — Vaani Pay は単一支店の仮想ウォレットであるため、実際のネオバンクの仮想口座と同様に、すべてのアカウントが1つの IFSC を共有します)、

  • 初期残高 ₹0

登録応答には、message: "Your payment account has been successfully created." という確認と新しいアカウントの詳細が含まれ、ユーザーに即座に表示されます(API 応答とサインアップ画面の確認メッセージの両方)。

入金。 POST /wallet/add-money(またはウォレット画面の「入金」ボタン、または AI アシスタントに「口座に ₹5,000 を追加して」と頼む方法)は、金額を検証し(> ₹0、1 トランザクションあたり ≤ ₹2,00,000 — app/wallet.pyMAX_ADD_MONEY)、その後、残高を原子的に更新して wallet_transactionsCREDIT 行を追加します。ハッカソンビルドには実際の決済ゲートウェイは組み込まれていません。これは明示的にシミュレートされたチャージであり、ブリーフの「安全なシミュレートされた資金フロー」要件に一致します。

送金 — 常に2段階の確認。 REST API も AI アシスタントも、1回の呼び出しで資金を移動することはありません:

  1. POST /wallet/transfersapp/wallet.pyinitiate_transfer)は受取人と送金元の残高を検証し、PENDINGwallet_transactions 行を作成します — まだ残高は変更されません。確認プレビュー(受取人、マスクされた口座番号、IFSC、金額、手数料、合計引き落とし額)を返します。これが「送金を確認」画面を描画するものです。

  2. POST /wallet/transfers/{id}/confirmconfirm_transfer)は、実際に資金を移動する唯一の呼び出しです。送金元の残高とアカウントステータスを確認時点で再検証し(開始時だけでなく、その間に何かが変わった場合に備えて — たとえば2つの送金が続けて開始された場合など)、その後、送金元から引き落とし、受取人が実際の Vaani Pay アカウントである場合はそのアカウントに入金します。これらはプロセス全体のロックで保護された1つの原子的な SQLite トランザクション内で行われます。途中で何かが失敗した場合は全体がロールバックされ、送金が「引き落とし済みだが入金されていない」状態になることは決してありません。

  3. POST /wallet/transfers/{id}/cancel は、まだ PENDING の送金を残高に一切触れずにキャンセルします。

当システムに存在しない口座番号への送金も成功します(シミュレートされた外部送金として — 送金元からは引き落とされますが、入金先の Vaani Pay アカウントがないだけです)。これはブリーフの「受取人がシミュレートされたシステム内に存在する場合は受取人の残高に入金する」という要件に一致します。

受取人検証。 POST /wallet/validate-recipientvalidate_recipient)は以下をチェックします: 口座番号の形式(9〜18桁)、IFSC 形式(^[A-Z]{4}0[A-Z0-9]{6}$)、内部アカウントの場合は IFSC が口座番号と一致すること、そして — 重要なのは — 送金元が自分自身の口座番号に送金していないこと。受取人の名前だけが指定された場合(口座番号なし)、呼び出し元自身の保存済み受取人から名前を検索し、完全一致がちょうど1つある場合に自動的に解決します。

保存済み受取人。 送金が成功すると、UI は「この受取人を保存しますか?」と表示します — POST /beneficiaries認証済みユーザーのみに対して受取人を保存します(グローバル/共有は一切ありません)。これにより、次回以降、ユーザーは(または AI アシスタントが「Rahul に ₹2,000 を送って」と頼まれたときは)名前だけで送金先を解決できます。

取引履歴とフィルター。 GET /wallet/transactions?filter=...all / add_money / sent / received / failed / pending)— これらはすべて wallet_transactions からライブで計算され、ハードコードは一切ありません。ウォレット画面の履歴タブと AI の「自分のウォレット取引を表示して」/「今月いくら使った?」はどちらもまったく同じ関数(app/wallet.pyget_wallet_transactions / get_spending_summary)を参照します。

残高は常に導出され、直接設定されることはありません。 コードベースのどこにも set_balance() 関数は意図的に存在しません — 残高が変わる唯一の方法は、add_money() または confirm_transfer() の副作用としてです。どちらも同じ原子的なステップで不変の wallet_transactions 行を追加します。フロントエンドは GET /wallet/account が返すものを表示するだけで、それに影響を与えることはできません。

ウォレットのセキュリティ(具体的には)

ルール

適用方法

ユーザーは自分の残高を直接変更することは決してできない

公開関数が残高を設定するのは、Add Money / confirm_transfer の副作用としてのみであり、どちらも金額検証済みで監査行を生成する

ユーザーは他のユーザーの残高を変更することは決してできない

すべてのウォレット関数は、呼び出し元の認証済み user_id を受け取り、WHERE user_id = ?payment_accounts を検索する — クライアントが指定したアカウント ID を経由することは決してない

ユーザーは他人の送金を確認/キャンセルすることは決してできない

confirm_transfer/cancel_transfer は、PENDING トランザクションの送金元アカウントが呼び出し元ユーザーに属することを検証し、トランザクションが存在しない場合も他人に属する場合も、同じ汎用の「not found」応答を使用する(test_offline.pydiagnose_setup.py で検証済み)

自己送金はブロックされる

validate_recipient は、送金を続行する前に、受取人の口座番号を送金元自身の口座番号と比較する

金額は処理中に改ざんできない

confirm_transfer 時に実際に引き落とし/入金される金額は、initiate_transfer 時に作成された PENDING 行に保存された金額であり — 確認リクエストから再読み取りされることは決してない

AI は明示的な確認なしにお金を動かすことはできない

create_transfer/add_money MCP ツールは単独では引き落とし/入金を行わない。app/agent.py の会話ステートマシンは、confirm_transfer を呼び出す前に、表示された確認に対する明示的な「yes」の返答を要求する

原子性

confirm_transfer は、残高チェック + 両方の残高更新 + ステータス更新を 1 つの SQLite トランザクション(app/db.pytx())内で実行し、さらにプロセス全体のロックをかける — app/wallet.py のモジュール docstring を参照

10. バイリンガル送金フロー

ウォレットは完全なバイリンガル対応で、アプリの他の部分と同じ app/i18n.py の仕組みを使用している — Add Money、Send Money(全 3 ステップ)、確認画面、トランザクションステータス、残高/送金に関するすべての AI 応答は、app/wallet.pyapp/agent.pystatic/index.html のウォレット UI のどこにもハードコードされた英語を使わず、すべて t()/tpl() を通じてレンダリングされる。たとえば、AI に 「Rahul ko ₹2,000 bhejo」(ヒンディー語/ヒングリッシュ)と尋ねると、英語版とまったく同じ resolve → confirm → execute のフローをたどり、確認画面を含むすべてのメッセージがヒンディー語でレンダリングされる。

セットアップ

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)

データベースは初回実行時に自動的に作成される(空の場合にのみ、2 つのデモユーザーがシードされる — 下記参照)。ローカル開発では、別途マイグレーション手順は不要。事前に明示的に作成するには:

python3 -m app.db

ブラウザを操作する前に検証:

python3 diagnose_setup.py

実行

uvicorn app.main:app --reload --port 8000

http://localhost:8000 を開きます。新しいアカウントにサインアップするか、シードされたデモアカウントのいずれかでログインします:

メール

パスワード

ramesh@example.com

Demo@1234

priya@example.com

Demo@1234

次に、提案メニューを試すか、「check payment status pay_1001」「मेरा भुगतान pay_1001 का स्टेटस क्या है?」 のような質問をしたり、Settings から言語を切り替えたり、あるいはあるユーザーとしてサインインして、他のユーザーの支払い/注文/返金 ID(pay_1003ord_2002rfnd_3002 は Priya のもの)について質問して、アクセス拒否の応答を確認してみてください。

ウォレットを試すには:ヘッダーにある 💰 Wallet ボタンを開きます。両方のデモアカウントは、残高(Ramesh は ₹8,500、Priya は ₹8,000)と、履歴にすでに 1 件のデモ送金がある状態で開始します。「Add Money」を試すか、もう一方のデモアカウントの口座番号(各ユーザーの Wallet 画面で確認できます)に「Send Money」を試すか、AI アシスタントに直接尋ねてみてください:「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