Vaani-Pay MCP Server
Click on "Install 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., "@Vaani-Pay MCP ServerShow me my recent transactions"
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.
Vaani Pay Assistant
A secure, real-time, multi-user, bilingual (English + Hindi) payments support chatbot: users register/log in with their own account, then ask about their own payments, orders, refunds, transactions, fraud risk, and statistics — with strict per-user data isolation enforced at the MCP tool layer, a real SQLite database, and a live WebSocket status stream showing what the agent is doing.
This started as a static-demo hackathon build (hardcoded users, fixed tokens, English-only) and has been upgraded to a database-driven, multi-user, secure, bilingual platform without changing the working parts that already worked: the WebSocket protocol, the MCP tool architecture, and the core agent logic are the same shape as before — only the data source and auth model underneath changed.
Architecture
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. Accounts & data privacy
Real accounts.
POST /auth/registercreates a user row in SQLite with a securely hashed password (PBKDF2-HMAC-SHA256, per-password random salt, 260k iterations — seeapp/security.py). Plain-text passwords are never stored or logged anywhere.Real login sessions.
POST /auth/loginverifies credentials and issues an opaque, unguessable session token (app/security.py'sgenerate_token(), 256 bits of entropy) stored in thesessionstable with an expiry (SESSION_TTL_HOURSin.env, default 24h). Expired or unknown tokens are rejected everywhere they're checked.Every MCP tool that looks up a specific resource (
get_payment_status,get_order_details,get_refund_status,check_fraud_risk) requires arequesting_user_idparameter and verifies, inmcp_server/data_layer.py, that the resource actually belongs to that user — now via a parameterized SQLWHERE ... AND user_id = ?clause — before returning anything.If the resource belongs to someone else, or doesn't exist at all, the same generic response is returned in both cases:
"Access denied. You are not authorized to access this information."Returning different messages for "not found" vs "someone else's data" would let a user enumerate valid IDs by observing which error they get — this closes that side channel.requesting_user_idis always the caller's authenticated identity (resolved once at WebSocket auth time / REST request time — seeapp/auth.py), never a value parsed from a chat message, URL parameter, or request body.app/nlu.py's extraction schema has nouser_idfield at all, so there's no way for a message (even an adversarial one) to smuggle a different identity into a tool call.get_customer_details,get_transaction_history, andget_payment_statisticstake no resource ID at all — they always return the caller's own data, so there's no ID-manipulation surface for these three tools whatsoever.Account deletion (
DELETE /users/me) requires re-entering the current password as confirmation, then deletes the user row —ON DELETE CASCADEforeign keys remove all of that user's sessions, chat history, payments, orders, refunds, and transactions along with it.
Verify data isolation directly:
python3 test_offline.pyThis runs real tool calls (against the actual MCP server, reading from the real SQLite database) as two different demo users and asserts that cross-user access attempts are denied, that each user's transaction history contains only their own data, and that bilingual replies render correctly.
2. Registration, login & account management
POST /auth/register— name, email, password, optional phone, and a language preference. Passwords must be at least 8 characters and contain a mix of letters and numbers (app/security.py).POST /auth/login— returns a session token + user profile.POST /auth/logout— revokes the current session token server-side.GET /users/me/PUT /users/me— view/update profile (name, phone).POST /users/me/change-password— requires the current password; changing it invalidates all existing sessions (forces re-login everywhere) so a leaked old token stops working.GET /users/me/preferences/PUT /users/me/preferences— read/update the language preference (en/hi), persisted in the database so it survives logout/login.DELETE /users/me— permanent account deletion (password + explicitconfirm: truerequired).
All of this is also reachable from the chat UI itself via the ⚙️ button in the header (profile view/edit, language switcher, change password, logout, delete account).
3. Chat UI
static/index.html — a single-page app:
Login / Sign Up tabs shown before any chat is possible.
Profile & Settings panel (name/phone editing, password change, language switcher, logout, account deletion with a confirmation step).
Collapsible suggestion menu above the input, rendered from the same translated string dictionary as the rest of the UI.
Chat bubbles, live status line, and header status indicator — unchanged from the original design.
4. Real-time communication
Chat still happens over a single WebSocket (/ws) — the protocol shape
is unchanged, only the auth token is now a real DB-backed session token
instead of a static value:
{"type": "auth", "token": "<session token from /auth/login>"}
↓
{"type": "auth_success", "user_id": "...", "name": "...", "language": "en"}For every chat message, the server streams status events in this order, then the final (localized) answer:
🔍 Understanding your request...
🔧 Checking payment information...
✓ Payment information retrieved
🤖 Generating response...
<final answer, in the user's selected language>Every chat turn (both user and assistant messages) is also persisted to
the chat_history table (app/main.py's _persist_chat_turn), scoped to
the authenticated user.
5. MCP-based architecture
mcp_server/server.py exposes exactly these 7 tools, split into domain
modules under mcp_server/tools/:
Tool | File |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
All backed by the SQLite database (mcp_server/data_layer.py →
app/db.py) instead of static JSON. Tool signatures, the agent, and the
frontend are unchanged from the original design — only the data source
underneath data_layer.py changed, exactly as the original architecture
was designed to allow.
6. Bilingual support (English + Hindi)
UI strings:
app/i18n.py'sUI_STRINGSdictionary, served viaGET /i18n/{lang}. The frontend fetches this once at load and on every language change, and applies it viadata-i18n/data-i18n-placeholderattributes — no translated string is hardcoded in the HTML/JS.AI assistant replies:
app/i18n.py'sAGENT_STRINGS(fixed messages like greetings) andREPLY_TEMPLATES(interpolated messages like payment status).app/agent.pyrenders every reply through these — nothing in the agent hardcodes English text directly.NLU:
app/nlu.py's prompt explicitly asks the Grok model to handle Hindi/English/mixed input and always translate to English internally for intent/entity extraction, so the assistant understands a question either way and replies in the user's preferred language.Persistence: the language preference lives on
users.languagein the database (set at signup, changeable any time viaPUT /users/me/preferences), so it survives logout/login.Dynamic switching: changing language in Settings updates the UI immediately and reconnects the WebSocket so the very next chat reply comes back in the new language — no page reload needed.
7. Security requirements
Requirement | Where it's implemented |
Authentication |
|
Authorization |
|
User/session isolation |
|
MCP-level permission checks | Enforced inside the MCP tools themselves ( |
Input validation |
|
SQL injection protection | Every query in |
Rate limiting |
|
Secure CORS |
|
Secure password hashing |
|
Token expiration |
|
Generic auth error messages |
|
Secure error handling |
|
Protection against ID manipulation | A user can type any |
8. Database schema
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_atSee app/db.py's SCHEMA for the full DDL with foreign keys and indexes.
9. Wallet: Payment Accounts, Add Money & Send Money
Every registered user gets a real, usable wallet — not just a payment
history viewer. This is the single biggest addition on top of the
database/auth upgrade, and it's built as its own module
(app/wallet.py) that both the REST API and the AI/MCP tools call into,
so there is exactly one place that enforces the money-movement rules.
Automatic account creation. POST /auth/register creates the user
row AND a payment_accounts row in the same database transaction (see
app/auth.py's register() calling app/wallet.py's
insert_account_row()) — a user can never exist without a wallet, and a
wallet is never created as a separate, independently-failable step. Each
account gets:
a unique Payment ID (
PAY..., internal identifier),a unique 12-digit account number,
a fixed IFSC (
VPAY0000001— Vaani Pay is a single-branch virtual wallet, so every account shares one IFSC, the same way a real neobank's virtual accounts often do),a starting balance of ₹0.
The registration response includes a message: "Your payment account has been successfully created." confirmation plus the new account details,
shown to the user immediately (both in the API response and in the
sign-up screen's confirmation message).
Add Money. POST /wallet/add-money (or the "Add Money" button in the
Wallet screen, or asking the AI assistant "add ₹5,000 to my account") —
validates the amount (> ₹0, ≤ ₹2,00,000 per transaction —
MAX_ADD_MONEY in app/wallet.py), then atomically updates the balance
and appends a CREDIT row to wallet_transactions. There is no real
payment gateway wired in for the hackathon build — this is an explicitly
simulated top-up, matching the brief's "safe simulated funding flow"
requirement.
Send Money — always a two-step confirm. Neither the REST API nor the AI assistant ever moves money in one call:
POST /wallet/transfers(initiate_transferinapp/wallet.py) validates the recipient and the sender's balance, and creates aPENDINGwallet_transactionsrow — no balance changes yet. It returns a confirmation preview (recipient, masked account number, IFSC, amount, fee, total debit) — this is what renders the "Confirm Transfer" screen.POST /wallet/transfers/{id}/confirm(confirm_transfer) is the only call that actually moves money. It re-validates the sender's balance and account status at confirm time (not just at initiate time, in case something changed in between — e.g. two transfers initiated back-to-back), then debits the sender and, if the recipient is a real Vaani Pay account, credits them, inside one atomic SQLite transaction guarded by a process-wide lock. If anything fails partway through, the whole thing rolls back — a transfer can never end up debited-but-not-credited.POST /wallet/transfers/{id}/cancelcancels a still-PENDINGtransfer without touching any balance.
Sending to an account number that isn't in our system still succeeds (as a simulated external transfer — the sender is debited, there's just no Vaani Pay account to credit), matching the brief's "credit the recipient's balance if the recipient exists in the simulated system" requirement.
Recipient validation. POST /wallet/validate-recipient
(validate_recipient) checks: account number format (9–18 digits), IFSC
format (^[A-Z]{4}0[A-Z0-9]{6}$), that the IFSC matches the account
number if it's an internal account, and — critically — that the sender
isn't sending to their own account number. If only a recipient name is
given (no account number), it looks the name up in the caller's own
saved beneficiaries and resolves automatically if there's exactly one
match.
Saved beneficiaries. After a successful transfer, the UI offers "Save
this recipient?" — POST /beneficiaries stores it for the authenticated
user only (never global/shared), so next time the user (or the AI
assistant, when asked to "send ₹2,000 to Rahul") can resolve a transfer
by name alone.
Transaction history & filters. GET /wallet/transactions?filter=...
(all / add_money / sent / received / failed / pending) — all
computed live from wallet_transactions, never hardcoded. The Wallet
screen's History tab and the AI's "show my wallet transactions" /
"how much did I spend this month" both read from the exact same function
(get_wallet_transactions / get_spending_summary in app/wallet.py).
Balance is always derived, never set directly. There is intentionally
no set_balance() function anywhere in the codebase — the only ways a
balance changes are as a side effect of add_money() or
confirm_transfer(), both of which also append an immutable
wallet_transactions row in the same atomic step. The frontend only ever
displays whatever GET /wallet/account returns; it cannot influence it.
Wallet security specifically
Rule | How it's enforced |
A user can never modify their own balance directly | No public function sets balance except as a side effect of Add Money / confirm_transfer, both of which are amount-validated and produce an audit row |
A user can never modify another user's balance | Every wallet function takes the caller's authenticated |
A user can never confirm/cancel someone else's transfer |
|
Self-transfers are blocked |
|
Amounts can't be manipulated in-flight | The amount used to actually debit/credit at |
The AI can't move money without explicit confirmation |
|
Atomicity |
|
10. Bilingual payment flow
The wallet is fully bilingual, using the same app/i18n.py mechanism as
the rest of the app — Add Money, Send Money (all three steps), the
confirmation screen, transaction statuses, and every AI response about
balance/transfers are all rendered through t()/tpl() with no
hardcoded English anywhere in app/wallet.py, app/agent.py, or
static/index.html's wallet UI. For example, asking the AI "Rahul ko
₹2,000 bhejo" (Hindi/Hinglish) walks through the exact same
resolve → confirm → execute flow as the English version, with every
message — including the confirmation screen — rendered in Hindi.
Setup
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)The database is created (and, only if empty, seeded with two demo users — see below) automatically on first run; no separate migration step is required for local development. To create it explicitly ahead of time:
python3 -m app.dbVerify before touching the browser:
python3 diagnose_setup.pyRun
uvicorn app.main:app --reload --port 8000Open http://localhost:8000. Sign up for a new account, or log in with one of the seeded demo accounts:
Password | |
|
|
|
|
Then try the suggestion menu, ask questions like "check payment status
pay_1001" or "मेरा भुगतान pay_1001 का स्टेटस क्या है?", switch
languages from Settings, or try signing in as one user and asking about
the other user's payment/order/refund IDs (pay_1003, ord_2002,
rfnd_3002 belong to Priya) to see the access-denied response.
To try the wallet: open the 💰 Wallet button in the header. Both demo accounts start with a balance (₹8,500 for Ramesh, ₹8,000 for Priya) and one demo transfer already in their history. Try "Add Money", or "Send Money" to the other demo account's account number (visible in their own Wallet screen), or ask the AI assistant directly: "what's my balance?", "add ₹5,000 to my account", "send ₹2,000 to Priya Stores" (it will ask for her account number + IFSC the first time, then offer to save her as a beneficiary after a successful transfer — after that, just her name is enough), or "Mera current balance kitna hai?" in Hindi.
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