Build a Paid MCP Server: Charge Users from Claude
The Problem: Your AI Agent Creates Value—But Can't Get Paid
Imagine you've built an exceptional Claude-powered MCP server. It drafts contracts, analyzes legal documents, generates investment reports—work that normally costs hundreds per hour. Users love it. But here's the catch: they never pay.
Software-as-a-service models assume recurring subscriptions. Agents, however, live inside Claude and other LLMs. Users invoke them for discrete tasks. How do you charge for a single contract draft? A one-time document review? A homework helper that works 5 minutes per day?
Traditional payment processors (Stripe, PayPal) require user accounts, redirects, and friction. By the time your agent redirects a user to a checkout page, the conversation thread dies. The user disappears.
AgentPay VN solves this: generate a QR code, embed it in the chat, user scans and pays—in 10 seconds flat. The agent resumes immediately upon settlement confirmation. No accounts. No redirects. No abandonment.
Why VietQR? Why AgentPay VN?
VietQR (Việt QR) is Vietnam's instant bank-to-bank payment standard. Scan once, money lands in your account in seconds. It's the infrastructure that powers every Shopee, Grab, and café in Vietnam.
AgentPay VN wraps this power into three lines of Python:
request = create_payment_request(amount=50000, description="Contract analysis")
checkout_url = request.checkout_url # Send this to Claude as a clickable QR link
await await_settlement(request.id) # Block until payment confirmed
Key facts: - Open-source MIT license – full transparency, zero vendor lock-in - No escrow – money goes straight to your bank account - Bank-verified settlement – instant confirmation via bank feed - MCP-ready – Claude integration in ~50 lines of config
If your users are in Vietnam or Southeast Asia, this is the fastest payment path to production.
Architecture: How It Works
Three-step flow:
- Create Payment Request → AgentPay generates a unique checkout URL pointing to your merchant bank account
- Send Checkout Link → Agent embeds the link or QR code in the Claude chat
- Await Settlement → Agent waits for bank confirmation, then proceeds (unlock file, generate next section, etc.)
Critical: AgentPay never touches the money. It's middleware between your Claude agent and the banking system. You retain full custody. This is not a payment processor in the traditional sense—it's a bridge.
Step 1: Install and Configure AgentPay VN
Local Setup
pip install agentpay-vn
Then set environment variables:
export AGENTPAY_MERCHANT_ID="your_merchant_id"
export AGENTPAY_API_KEY="your_api_key"
export AGENTPAY_BANK_ACCOUNT="your_vietqr_link"
Get these from https://agentpay.servicesai.vn/v1/docs (free registration for testing).
MCP Server Registration
Create a file claude_config.json in your project:
{
"mcpServers": {
"agentpay": {
"command": "agentpay-mcp",
"args": ["serve"],
"env": {
"AGENTPAY_MERCHANT_ID": "your_merchant_id",
"AGENTPAY_API_KEY": "your_api_key",
"AGENTPAY_BANK_ACCOUNT": "https://pay.vietqr.io/xxx"
}
}
}
}
Add this to Claude's MCP configuration. Restart Claude Desktop. You now have three new tools:
- create_payment_request(amount, description, user_id)
- check_payment_status(request_id)
- await_settlement(request_id, timeout_seconds)
Step 2: Build Your Paid Agent (Code Walkthrough)
Let's build a paid homework helper. Users pay ₫50,000 (~$2 USD) per problem solved.
Handler Function
from agentpay_vn import create_payment_request, await_settlement
import time
async def solve_homework_problem(problem_text: str, user_id: str):
"""
Solve a homework problem only after payment confirmed.
"""
# Step 1: Create payment request
# amount_vnd=50000 (₫50,000), description shown on QR screen
payment_req = create_payment_request(
amount_vnd=50000,
description="Homework problem solution",
metadata={"user_id": user_id, "problem_hash": hash(problem_text)}
)
# Step 2: Tell the user to pay
# In Claude, this becomes a clickable link + QR code
print(f"🔒 Solution locked. Pay here to unlock: {payment_req.checkout_url}")
print(f"Payment ID: {payment_req.id}")
# Step 3: Wait for settlement (block until confirmed)
# timeout_seconds=300 = 5 minute window; adjust for your UX
try:
settlement = await_settlement(
request_id=payment_req.id,
timeout_seconds=300
)
print(f"✅ Payment confirmed! Settlement ID: {settlement.id}")
except TimeoutError:
print("❌ Payment not received within 5 minutes. Try again.")
return None
# Step 4: Solve the problem (payment confirmed)
solution = solve_with_claude(problem_text)
return solution
def solve_with_claude(problem_text: str) -> str:
"""
Placeholder: call Claude API to generate solution.
"""
return f"Solution to '{problem_text[:50]}...' [Premium content unlocked]\n\nStep 1...\nStep 2...\n"
Line-by-line breakdown:
create_payment_request()– generates a unique checkout URL linked to your bank account. Themetadatadict stores context (who paid, what for) for reconciliation.- The
checkout_urlis what you send to Claude. The user scans the QR code with their phone, authenticates with their bank, and ₫50,000 transfers instantly. await_settlement()is a blocking call. It polls AgentPay's bank feed until the exact amount lands in your account. Then it returns.- Once
settlementsucceeds, you proceed—unlock the content, continue the conversation, etc.
Integrate into MCP Tool
In your MCP server's tools.py:
from mcp.server.models import Tool, TextContent
from mcp.types import ToolUseBlock
TOOLS = [
{
"name": "solve_homework",
"description": "Solve a math or physics problem. Requires ₫50,000 payment.",
"inputSchema": {
"type": "object",
"properties": {
"problem": {
"type": "string",
"description": "The homework problem to solve"
}
},
"required": ["problem"]
}
}
]
async def handle_tool_call(tool_name: str, arguments: dict):
if tool_name == "solve_homework":
result = await solve_homework_problem(
problem_text=arguments["problem"],
user_id="claude_user_id" # Extract from context
)
return result
When Claude invokes solve_homework, your agent shows the payment QR, waits for settlement, then returns the solution.
Real-World Example: A Café Loyalty Agent
Imagine a Slack bot for a Vietnamese café chain. Customers text:
"Buy me a coffee at the Nguyen Hue branch"
The agent responds:
"Sure! Your order: 1x Espresso (₫45,000). Scan to pay: [QR code]. Estimated pickup: 12 minutes."
Customer scans. Money goes to the café's Vietcombank account. Agent receives settlement confirmation within 3 seconds. It automatically: 1. Notifies the barista (via kitchen display system) 2. Sends a pickup reminder to the customer 3. Logs the transaction for loyalty points
No Stripe fees. No PayPal disputes. No user account creation. Just: scan → pay → brew.
Do's and Don'ts
| Do | Don't |
|---|---|
| Set timeout_seconds based on real behavior (300 for patient users, 60 for impatient) | Don't rely on await_settlement() in a 1-second response; it will timeout |
Store request.id in a database for reconciliation and refunds |
Don't lose the request ID; you'll need it to dispute or refund |
| Use metadata to tag payments by user, feature, or content | Don't store sensitive data (passwords, SSNs) in metadata |
| Test with small amounts (₫1,000) first | Don't go live with real money without testing the full flow |
Catch TimeoutError and retry gracefully |
Don't spam users with repeat payment links; offer alternatives |
| Verify settlement before delivering premium content | Don't trust the checkout_url alone; always confirm with await_settlement() |
Advanced: Tiered Pricing and Retries
For a more sophisticated agent, implement pricing tiers:
PRICES = {
"basic": 20000, # ₫20k – high school homework
"advanced": 100000, # ₫100k – university essays
"premium": 500000 # ₫500k – research papers
}
async def solve_homework_tiered(problem: str, tier: str, user_id: str):
amount = PRICES.get(tier, PRICES["basic"])
# Create + display payment
payment = create_payment_request(
amount_vnd=amount,
description=f"Homework solution ({tier})"
)
print(f"Pay ₫{amount:,} → {payment.checkout_url}")
# Wait with retry logic
max_attempts = 3
for attempt in range(1, max_attempts + 1):
try:
await_settlement(payment.id, timeout_seconds=300)
break # Success
except TimeoutError:
if attempt < max_attempts:
print(f"Attempt {attempt} failed. Retrying...")
time.sleep(10)
else:
print("Payment failed after 3 attempts. Canceling.")
return None
# Proceed with solution
return generate_solution(problem, tier)
FAQ
Q: What if the user's bank is down?
A: await_settlement() will timeout after your specified duration (default 300s). The payment request remains valid; the user can retry. Your agent should gracefully offer a retry link.
Q: Can I offer refunds? A: AgentPay doesn't handle refunds (it's a payment bridge, not a processor). You'll refund manually via your bank or a separate refund tool. Log the original transaction ID for reconciliation.
Q: Does this work outside Vietnam? A: VietQR is Vietnam-only. Users must have Vietnamese bank accounts. If you need global payments, consider Stripe for non-VN users and AgentPay VN for Vietnam.
Q: What are the fees? A: AgentPay VN itself has no fees (it's open-source). Your bank may charge a small transaction fee per QR payment (typically ₫1,000–5,000). Check with your merchant bank.
Key Takeaways
- AgentPay VN enables instant in-chat payments without redirects or user accounts. Perfect for AI agents that need to monetize discrete tasks.
- The 3-line flow (create → send → await) is the entire payment logic. No webhooks, no queues.
- Money never touches AgentPay—it goes straight to your bank account, verified by bank feeds.
- MCP integration is 50 lines of config—Claude instantly gains payment capabilities.
- Test with small amounts first; the API is simple but real money is involved.
- Use metadata to track who paid for what, enabling loyalty, refunds, and analytics.
Get Started Now
- Install:
pip install agentpay-vn - Read the docs: https://agentpay.servicesai.vn/v1/docs
- Explore the source: https://github.com/phuocdu/agentpay-vn
- Register for a test merchant account and deploy your first paid agent today.
Your Claude agents are valuable. Now they can get paid for it—in seconds, with a QR code.