Build a Paid MCP Server: Charge Users from Inside Claude

2026-08-28 · AgentPay VN

mcp serverai monetizationvietqr paymentsclaude integrationpython sdk

The Problem: AI Agents That Earn Their Keep

You've built something valuable—a Claude MCP server that helps customers generate reports, design graphics, or run analyses. It works beautifully. Claude users love it. But then reality hits: how do you actually charge them?

Traditional payment flows break the agent experience. You can't embed Stripe forms into an LLM conversation. You can't ask Claude to handle OAuth redirects. The moment your user needs to "leave Claude and open a payment page," you've lost momentum, lost context, and probably lost the sale.

Meanwhile, for Vietnamese merchants and SaaS builders, there's another friction point: payment infrastructure maturity. VietQR standardized QR payments across every bank in Vietnam, but integrating it into an AI agent workflow? That's been missing—until now.

AgentPay VN solves this cleanly: a Python SDK + MCP server that lets Claude agents request payments, generate QR codes, and confirm settlement—all without your code ever touching the money. The funds flow straight to your merchant bank account.


Why MCP Servers Need Payment Flows

Model Context Protocol (MCP) servers are how Claude accesses external tools. Whether you're offering image generation, data processing, or scheduled reports, most high-value features need monetization.

Three reasons payment belongs inside MCP:

  1. Zero Friction: Users don't context-switch. Payment happens as part of the conversation.
  2. Agent Awareness: Claude can decide when to charge (e.g., "This report requires 5 credits—I'll generate a payment QR for you").
  3. Direct Settlement: With VietQR, funds hit your bank account instantly. No escrow, no platform holds.

AgentPay VN specifically: - Never holds funds – your bank receives money directly. - Transparent settlement – bank feeds auto-confirm when cash clears. - Open-source (MIT) – audit everything, deploy anywhere. - Simple 3-step flow – create request → send QR → await confirmation.


Installation & Setup

Step 1: Install the SDK

pip install agentpay-vn

That's it. No API keys to beg for, no dashboard onboarding. The SDK is lightweight (~50KB).

Step 2: Get Your Merchant Info

You'll need: - Merchant ID (issued by your bank when you enable VietQR) - Bank Account Number (where payments land) - Bank Code (VCB, ACB, etc.)

If you don't have VietQR yet, contact your bank. Most Vietnamese banks offer it free.

Step 3: Configure Environment

Create .env:

AGENTPAY_MERCHANT_ID=your_merchant_id
AGENTPAY_BANK_ACCOUNT=1234567890
AGENTPAY_BANK_CODE=VCB
AGENTPAY_WEBHOOK_URL=https://your-domain.com/webhook

Building Your First Paid MCP Server

The Core Flow: 3 Steps to Payment

Every transaction follows this pattern:

  1. create_payment_request() – Generate a payment object with amount, description, and unique ID.
  2. send_checkout_url() – Format the QR code URL and send it to Claude (who shows it to the user).
  3. await_settlement() – Poll the bank feed until payment confirms.

Code Example: A Report Generation Server

Imagine a service where Claude can generate custom market reports for Vietnamese e-commerce sellers. Each report costs 50,000 VND. Here's how to build it:

# paid_mcp_server.py
import os
import uuid
from datetime import datetime
from agentpay_vn import PaymentClient, PaymentRequest
from mcp.server import Server
from mcp.types import Tool, TextContent

# Initialize the payment client
payment_client = PaymentClient(
    merchant_id=os.getenv("AGENTPAY_MERCHANT_ID"),
    bank_account=os.getenv("AGENTPAY_BANK_ACCOUNT"),
    bank_code=os.getenv("AGENTPAY_BANK_CODE"),
)

# Create MCP server
server = Server("paid-report-generator")

# Store active requests in memory (use Redis/DB in production)
active_requests = {}

@server.call_tool()
def generate_ecommerce_report(product_category: str) -> str:
    """
    Generates a custom market report for Vietnamese e-commerce sellers.
    Costs 50,000 VND. Initiates payment flow.
    """

    # Step 1: Create payment request
    request_id = str(uuid.uuid4())  # Unique identifier for this transaction

    payment_req = PaymentRequest(
        amount=50000,  # VND
        description=f"Market report: {product_category}",
        request_id=request_id,
        return_url="https://your-domain.com/report-confirmed",
    )

    # Create the request with AgentPay
    payment_data = payment_client.create_payment_request(payment_req)

    # Store for later verification
    active_requests[request_id] = {
        "category": product_category,
        "created_at": datetime.now(),
        "status": "pending",
    }

    # Step 2: Generate checkout URL with embedded QR
    checkout_url = payment_client.send_checkout_url(payment_data)

    # Return QR to Claude (Claude displays to user)
    return f"""
    Payment Required: 50,000 VND

    Please scan this QR code to pay:
    {checkout_url}

    Transaction ID: {request_id}

    After payment, I'll generate your {product_category} market report within 2 minutes.
    """

@server.call_tool()
def check_payment_status(request_id: str) -> str:
    """
    Check if payment has been received for a report request.
    """
    if request_id not in active_requests:
        return f"No active request found with ID: {request_id}"

    # Step 3: Poll the bank feed for settlement confirmation
    is_settled = payment_client.await_settlement(
        request_id=request_id,
        timeout_seconds=300  # Wait up to 5 minutes
    )

    if is_settled:
        active_requests[request_id]["status"] = "paid"

        # NOW generate the actual report
        category = active_requests[request_id]["category"]
        report_content = generate_report_content(category)

        return f"""
        ✅ Payment confirmed!

        Here's your market report for {category}:

        {report_content}
        """
    else:
        return "⏳ Payment not yet received. Please verify your transfer and try again."

def generate_report_content(category: str) -> str:
    """Placeholder: Replace with actual report logic."""
    return f"""
    ## Market Report: {category.title()}

    **Market Size**: $2.4B (2024 projection)
    **Growth Rate**: 18% YoY
    **Top Platforms**: Shopee, Lazada, TikTok Shop

    [Full report would follow...]
    """

if __name__ == "__main__":
    server.run()

Line-by-line breakdown:

MCP Configuration for Claude

Register this server with Claude via .config/claude_desktop_config.json:

{
  "mcpServers": {
    "paid-report-generator": {
      "command": "python",
      "args": ["/path/to/paid_mcp_server.py"],
      "env": {
        "AGENTPAY_MERCHANT_ID": "your_merchant_id",
        "AGENTPAY_BANK_ACCOUNT": "1234567890",
        "AGENTPAY_BANK_CODE": "VCB",
        "AGENTPAY_WEBHOOK_URL": "https://your-domain.com/webhook"
      }
    }
  }
}

Now restart Claude Desktop, and the generate_ecommerce_report and check_payment_status tools appear in Claude's tool list.


Real-World Walkthrough: The Online Course Bot

Let's trace a real conversation:

User (in Claude): "I want a course on Vietnamese TikTok Shop seller tactics. Do you have anything?"

Claude (via MCP): Calls generate_ecommerce_report("TikTok Shop Seller Masterclass").

MCP Server: 1. Creates PaymentRequest(amount=150000, description="TikTok Shop Masterclass", request_id="abc123"). 2. Returns QR code URL.

Claude: "Great! Here's a 150,000 VND course. Scan this QR to unlock it:" [QR code displayed]

User: Scans QR with their banking app → transfers 150,000 VND to your merchant account.

Bank: Processes the transfer within 30 seconds.

MCP Server (awaiting settlement): Detects the incoming transaction via bank feed.

Claude (after poll): "✅ Payment confirmed! Here's your course content: [full curriculum, video links, assignments]"

Your bank account: Now holds 150,000 VND, minus a small interchange fee (~0.5%).

Total time: ~2 minutes. Zero friction. Zero PCI compliance burden (no card data touched).


Advanced Tips & Best Practices

Do's and Don'ts

✅ Do ❌ Don't
Store request_id + amount in a database for audit trail Poll await_settlement() every second (use exponential backoff)
Use separate merchant accounts for testing vs. production Hardcode credentials in code (always use .env or secrets manager)
Validate that amount matches your pricing before generating QR Trust the request_id alone—verify bank amount when settlement arrives
Implement a webhook handler for bank notifications Rely only on client-side polling for high-volume transactions
Set generous timeouts (5–10 min) for settlement polling Assume QR code is valid forever—regenerate if it expires

Webhook Integration (Production)

For higher reliability, listen for bank webhooks instead of polling:

from flask import Flask, request

app = Flask(__name__)

@app.route("/webhook", methods=["POST"])
def handle_settlement():
    """
    Bank posts settlement confirmation here.
    """
    payload = request.json
    request_id = payload["request_id"]
    amount = payload["amount"]
    status = payload["status"]  # "settled", "failed", etc.

    if status == "settled":
        # Unlock content for this request_id
        unlock_premium_content(request_id)
        return {"acknowledged": True}, 200

    return {"error": "Unknown status"}, 400

This is more reliable than polling for production workloads.

Handling Failed Payments

if not is_settled:
    # Payment timed out or failed
    # Keep the request in "pending" state for 24 hours
    # Let user retry or refund after 24h
    return "⏳ Payment still pending. If you've paid, please wait a few minutes."

Comparison: AgentPay VN vs. Alternatives

Factor AgentPay VN Stripe PayPal
VietQR Support ✅ Native ❌ Needs workaround ❌ Limited
Settlement Speed Instant (bank-direct) 2-7 days 1-3 days
Holds Money? ❌ No ✅ Yes (escrow) ✅ Yes
Setup Complexity 5 min 30 min 20 min
MCP Integration ✅ Built-in SDK ❌ Manual HTTP calls ❌ Manual HTTP calls
Vietnam-Optimized ✅ Yes ⚠️ Foreign entity ⚠️ Foreign entity
Open-Source ✅ MIT ❌ Proprietary ❌ Proprietary

FAQ

Q: Does AgentPay ever hold my money?

A: No. The payment QR points directly at your merchant bank account. AgentPay only orchestrates the flow—the bank handles settlement. Your funds arrive in your account within minutes of the user's transfer.

Q: What if a user scans the QR but doesn't complete payment?

A: The await_settlement() call will timeout (default 5 minutes). Claude can then offer to regenerate the QR or suggest alternative payment methods. No charge occurs without confirmed settlement.

Q: Can I use AgentPay for non-VietQR payments?

A: Currently, AgentPay VN is optimized for VietQR (the national standard). For international payments, integrate Stripe or PayPal separately, though you'll lose the in-conversation flow benefits.

Q: Is there a fee?

A: AgentPay's SDK is free (MIT open-source). You pay only the bank's VietQR interchange fee (typically 0.3–0.5% per transaction), which is far lower than Stripe (2.9%) or PayPal (3.5%).


Key Takeaways


Getting Started

Ready to build? Here's your launch checklist:

  1. Install: pip install agentpay-vn
  2. Register MCP server: Add config to Claude Desktop (claude_desktop_config.json)
  3. Set .env: Merchant ID, bank account, bank code
  4. Code your first tool: Use the report generator example as a template
  5. Test: Chat with Claude, trigger a payment request, verify settlement
  6. Deploy: Run on a server, configure webhooks for production reliability

Full documentation and examples: https://agentpay.servicesai.vn/v1/docs

GitHub repo (star it!): https://github.com/phuocdu/agentpay-vn

Your AI agent is now ready to earn its keep. 🚀

Get started →

← All posts