Build a Paid MCP Server: Charge Users Inside Claude
The Problem: Free Tools Don't Scale
You've built something useful—a Claude MCP server that helps users do research, generate content, or manage data. Your tool sees 500 requests a week. But you're covering the infrastructure costs yourself, and every popular feature you add burns more cloud credits. You wonder: "How do I charge users without becoming a fintech company?"
This is the exact gap AgentPay VN fills. You can now accept payments directly inside Claude conversations without ever touching the money, holding customer data, or managing a payment processor integration yourself. Your users get their VietQR checkout link, scan with their bank app, and your MCP server immediately knows payment succeeded—all in under 3 seconds.
Let's build it.
Why MCP Payments Are Different
Traditional SaaS requires users to leave your product, go to a website, enter a credit card, and return. With MCP servers running inside Claude, payments become conversational. A user asks your tool for something premium. You present a QR code. They scan. Settlement confirms in seconds. They're back to the conversation—no friction, no page redirects, no signup forms.
AgentPay VN handles this because:
- Open-source MIT license – Deploy anywhere, modify freely
- Zero-knowledge architecture – The QR points directly to the merchant's bank account; AgentPay never holds funds
- Bank-level settlement confirmation – A feed from your bank tells your code when money arrived
- Lightweight – Just 3 lines of flow logic: create request → send URL → await settlement
How AgentPay VN Works (The 3-Line Flow)
Understanding the architecture first saves debugging later:
- Create Payment Request – You specify the amount and metadata (user ID, feature name, etc.)
- Send Checkout URL – The response includes a
checkout_urlthat renders a VietQR QR code - Await Settlement – Poll or webhook to confirm the user's bank transfer arrived
Your user sees a simple QR in Claude. They scan with their bank app. Their bank sends the payment directly to your registered merchant bank account. AgentPay's bank feed watches your account and tells your MCP server "payment cleared." Your code then unlocks the feature.
No intermediary holds money. No credit card friction. Pure bank transfer.
Installation & Setup
Step 1: Install the SDK
pip install agentpay-vn
This gives you the Python client to communicate with AgentPay's API.
Step 2: Register Your Bank Account
Visit https://agentpay.servicesai.vn/v1/docs and register your VietQR merchant account. You'll receive:
merchant_id(unique identifier for your account)api_key(authentication token)- A bank feed configured to your registered merchant bank account
Step 3: Configure Your MCP Server Environment
Store your credentials securely:
export AGENTPAY_MERCHANT_ID="your_merchant_id"
export AGENTPAY_API_KEY="your_api_key"
Building Your First Paid MCP Tool
Code Example 1: Creating a Payment Request
Here's a complete MCP server that charges users for a "premium research report" feature:
import os
import json
from agentpay_vn import AgentPayClient
from datetime import datetime, timedelta
# Initialize the AgentPay client with your credentials
client = AgentPayClient(
merchant_id=os.getenv("AGENTPAY_MERCHANT_ID"),
api_key=os.getenv("AGENTPAY_API_KEY")
)
def create_premium_research_request(user_id: str, research_topic: str) -> dict:
"""
Create a payment request for premium research.
User pays 50,000 VND for detailed analysis.
"""
# Step 1: Create payment request with AgentPay
payment_request = client.create_payment_request(
amount=50000, # 50,000 VND
description=f"Premium research report: {research_topic}",
metadata={
"user_id": user_id,
"feature": "premium_research",
"topic": research_topic,
"timestamp": datetime.now().isoformat()
},
expires_in=600 # QR code valid for 10 minutes
)
# Step 2: Extract checkout URL for display to user
checkout_url = payment_request["checkout_url"]
request_id = payment_request["request_id"]
# Step 3: Store request ID in your database for settlement tracking
# (pseudocode: save_to_db(request_id, user_id, "pending"))
return {
"status": "awaiting_payment",
"request_id": request_id,
"checkout_url": checkout_url,
"amount": 50000,
"currency": "VND",
"message": f"Scan the QR code to pay 50,000 VND for premium research on '{research_topic}'"
}
# Example usage in your MCP handler
result = create_premium_research_request(
user_id="claude_user_456",
research_topic="Vietnamese E-Commerce Market 2024"
)
print(json.dumps(result, indent=2))
Line-by-line explanation:
- Lines 8-10: Initialize AgentPayClient with your stored credentials
- Lines 12-31: Define a function that accepts user ID and research topic
- Lines 17-27: Call
create_payment_request()with: amount: 50,000 VND (adjust to your pricing)description: Human-readable text for the user's bank receiptmetadata: JSON object storing context (user ID, feature name) for later matchingexpires_in: Seconds until QR code expires- Lines 30-32: Extract the
checkout_url(the QR code URL Claude will display) andrequest_id(for settlement confirmation) - Line 35: Return a response object Claude can render as a message with the QR code embedded
Code Example 2: Confirming Settlement
Once the user scans and pays, your server needs to confirm the settlement:
import asyncio
from agentpay_vn import AgentPayClient
client = AgentPayClient(
merchant_id=os.getenv("AGENTPAY_MERCHANT_ID"),
api_key=os.getenv("AGENTPAY_API_KEY")
)
async def wait_for_settlement(request_id: str, user_id: str, timeout_seconds: int = 300):
"""
Poll AgentPay until the payment settles or timeout.
Returns True if payment confirmed, False if timeout.
"""
start_time = time.time()
poll_interval = 2 # Check every 2 seconds
while time.time() - start_time < timeout_seconds:
try:
# Query payment status from AgentPay
status = client.get_payment_status(request_id)
if status["state"] == "settled":
# Payment confirmed! User funds arrived at your merchant account
print(f"✓ Payment settled for user {user_id}")
print(f" Amount: {status['amount']} VND")
print(f" Bank reference: {status['bank_reference']}")
# Update your database: mark user as paid, unlock feature
# (pseudocode: unlock_premium_feature(user_id))
return True
elif status["state"] == "expired":
print(f"✗ QR code expired for user {user_id}")
return False
elif status["state"] == "pending":
# Payment not yet confirmed, keep polling
print(f"⏳ Awaiting settlement... ({time.time() - start_time:.0f}s)")
except Exception as e:
print(f"Error checking payment: {e}")
# Wait before next check
await asyncio.sleep(poll_interval)
print(f"✗ Timeout waiting for settlement of request {request_id}")
return False
# Usage in your MCP tool
# After displaying QR to user, await settlement
result = await wait_for_settlement(
request_id="req_abc123xyz",
user_id="claude_user_456",
timeout_seconds=600 # Wait up to 10 minutes
)
if result:
print("Unlock premium research now!")
else:
print("Payment not received. Please retry.")
Key points:
- Lines 10-31: The polling loop checks settlement status every 2 seconds
- Line 18: Call
get_payment_status(request_id)to query AgentPay's bank feed - Lines 20-24: If state is "settled", the user's bank transfer cleared—unlock the feature
- Lines 29-32: If state is "pending", keep waiting; if "expired", the QR code timed out
- Line 45-51: In real usage, wrap this in your MCP tool handler so Claude can display status updates
Configuring Your MCP Server for Claude
Your MCP server exposes tools to Claude via JSON schema. Here's the configuration:
{
"tools": [
{
"name": "request_premium_research",
"description": "Request a premium research report. Initiates a payment request for 50,000 VND.",
"inputSchema": {
"type": "object",
"properties": {
"topic": {
"type": "string",
"description": "The research topic (e.g., 'E-Commerce Market Trends')"
},
"depth": {
"type": "string",
"enum": ["standard", "deep"],
"description": "Research depth level"
}
},
"required": ["topic"]
}
},
{
"name": "check_payment_status",
"description": "Check if a premium research payment has settled.",
"inputSchema": {
"type": "object",
"properties": {
"request_id": {
"type": "string",
"description": "The payment request ID from AgentPay"
}
},
"required": ["request_id"]
}
}
]
}
When Claude calls request_premium_research, your Python handler runs create_premium_research_request() and returns the QR code URL. Claude renders it inline. When the user says "check status", Claude calls check_payment_status(), which polls AgentPay and confirms if funds arrived.
Real-World Walkthrough: Online Course Platform
Imagine you run an AI-assisted course platform. Users ask Claude questions about Vietnamese business law. Free tier answers are brief; premium tier ($2 = ~50,000 VND) unlocks detailed analysis with case studies.
Flow:
- User (in Claude): "I need detailed analysis of new e-commerce tax rules."
- Claude checks user's subscription status in your database
- User is free tier; Claude calls
request_premium_researchtool - Your MCP server creates payment request → returns QR code URL
- Claude displays: "Scan this QR to unlock detailed analysis (50,000 VND)"
- User scans QR with their bank app → transfers money
- Backend polls AgentPay → detects settlement via bank feed
- Your system unlocks premium access for that user's ID
- Claude now calls internal tool to generate deep-dive analysis
- User reads it in the same conversation
No credit card entry. No account creation. No payment processor dashboard. Just a bank transfer and a QR code.
Do's & Don'ts
| DO | DON'T |
|---|---|
Store request_id + user_id mapping in your DB |
Display QR without storing request metadata |
| Poll settlement status regularly (2-5 sec intervals) | Trust payment instantly without confirmation |
| Set reasonable QR expiry (5-10 minutes) | Leave QR codes valid indefinitely |
| Log settlement confirmations for auditing | Ignore bank feed data—it's your proof of payment |
| Handle timeout gracefully (offer retry button) | Force users to wait >10 minutes for confirmation |
Advanced Tips
Webhook-Based Settlement (Instead of Polling)
For production, configure AgentPay to send webhooks when payments settle:
from flask import Flask, request
app = Flask(__name__)
@app.route("/agentpay/webhook", methods=["POST"])
def handle_settlement_webhook():
"""
AgentPay sends POST to this endpoint when payment settles.
Signature verification prevents spoofing.
"""
payload = request.json
# Verify signature (provided in docs)
if not verify_agentpay_signature(request.headers["X-Signature"], payload):
return {"error": "Invalid signature"}, 403
request_id = payload["request_id"]
user_id = payload["metadata"]["user_id"]
amount = payload["amount"]
bank_ref = payload["bank_reference"]
# Unlock feature immediately
unlock_premium_feature(user_id)
# Log transaction
log_settlement(request_id, user_id, amount, bank_ref)
return {"status": "processed"}, 200
Webhooks are faster and more reliable than polling, especially for high-volume services.
Tiered Pricing
Modify create_premium_research_request() to accept a pricing parameter:
pricing_tiers = {
"standard": 50000, # 50k VND
"deep": 100000, # 100k VND
"enterprise": 500000 # 500k VND
}
def create_payment_by_tier(user_id: str, tier: str):
amount = pricing_tiers.get(tier, 50000)
return client.create_payment_request(
amount=amount,
description=f"Premium research ({tier} tier)",
metadata={"user_id": user_id, "tier": tier}
)
Claude can dynamically choose tiers based on user requests.
Rate Limiting & Fraud Prevention
Store payment requests in a database with timestamps. Reject users who spam payment requests:
import time
from collections import defaultdict
payment_history = defaultdict(list)
def check_rate_limit(user_id: str, max_requests: int = 5, window_seconds: int = 300):
"""
Allow max 5 payment requests per user every 5 minutes.
"""
now = time.time()
# Remove old requests outside window
payment_history[user_id] = [
ts for ts in payment_history[user_id]
if now - ts < window_seconds
]
if len(payment_history[user_id]) >= max_requests:
return False, "Too many payment requests. Try again later."
payment_history[user_id].append(now)
return True, "OK"
FAQ
Q: Does AgentPay hold my money? No. The QR code points directly to your merchant bank account. AgentPay is a payment request orchestrator, not a payment processor. You receive 100% of funds; there are no hidden fees or cuts.
Q: How do I verify settlement actually happened?
AgentPay monitors your merchant bank account via a configured bank feed (read-only access). When your bank reports a new incoming transfer matching the requested amount, AgentPay's API confirms it. You can also manually check your bank account for the transfer; the bank_reference field correlates transactions.
Q: What if the user's bank transfer fails? The payment request expires (default 10 minutes). Your polling or webhook handler detects the expiry. You can offer the user to retry by generating a fresh QR code. Failed transfers never leave your merchant account, so no refunds are needed.
Q: Can I use this for non-Vietnamese users? Currently, AgentPay VN is optimized for VietQR (Vietnamese bank transfers). International users would need a Vietnamese bank account or a local intermediary. Check docs for roadmap updates on multi-currency support.
Key Takeaways
- Monetize Claude conversations instantly: No credit card required, no payment processor integration
- 3-line architecture: Create request → Send QR → Confirm settlement
- Zero-knowledge design: Money goes straight to your bank; AgentPay never touches it
- Settlement is certain: Bank feed confirmation = guaranteed funds arrival
- Integrate in minutes:
pip install agentpay-vn+ 2 API calls + 1 MCP config - Open-source freedom: MIT license, deploy anywhere, full transparency
Next Steps
- Install AgentPay VN:
pip install agentpay-vn - Register your merchant account: Visit https://agentpay.servicesai.vn/v1/docs
- Deploy the sample code: Adapt the Python examples above to your MCP server
- Test with a small amount: Create a payment request for 10,000 VND, scan the QR with your bank app, and confirm settlement
- Read the full docs: https://agentpay.servicesai.vn/v1/docs for webhooks, error handling, and advanced features
- Join the community: Star the repo at https://github.com/phuocdu/agentpay-vn and contribute improvements
You now have everything needed to build a profitable MCP server that charges users directly inside Claude—with zero payment processor overhead.