PayMCP: Accept Payments in AI Agents with MCP
What you'll learn: How to add payment processing to your MCP tools and AI agents using PayMCP. Includes setup for Stripe, PayPal, Coinbase, and patterns for pay-per-request billing and subscriptions.
What is PayMCP?
PayMCP is a lightweight Python SDK that adds a provider-agnostic payment layer to MCP (Model Context Protocol) tools and agents. It enables you to:
- Charge per API/tool call
- Gate features behind subscriptions
- Support multiple payment providers
- Build monetized AI services
Use Cases
| Use Case | Description | |----------|-------------| | AI Tool Marketplace | Charge for premium AI capabilities | | Subscription Services | Gate advanced features behind plans | | API Monetization | Bill per request to your MCP servers | | Agent Services | Let AI agents pay for and charge for services |
Prerequisites
Before starting, ensure you have:
- Python 3.9+ installed
- Payment provider account (Stripe, PayPal, etc.)
- API keys from your chosen provider
- Basic understanding of MCP servers
Installation
Install PayMCP and MCP SDK:
pip install mcp paymcp
For Redis-backed production deployments:
pip install mcp paymcp redis
Quick Start
Step 1: Set Up Environment Variables
# Stripe (recommended for getting started)
export STRIPE_API_KEY="sk_test_..."
# Or PayPal
export PAYPAL_CLIENT_ID="..."
export PAYPAL_CLIENT_SECRET="..."
Step 2: Create Your First Paid Tool
import os
from mcp.server.fastmcp import FastMCP, Context
from paymcp import PayMCP, Mode, price
from paymcp.providers import StripeProvider
# Create MCP server
mcp = FastMCP("my-paid-tools")
# Initialize PayMCP with Stripe
PayMCP(
mcp,
providers=[
StripeProvider(api_key=os.getenv("STRIPE_API_KEY")),
],
mode=Mode.TWO_STEP
)
# Create a paid tool
@mcp.tool()
@price(amount=0.10, currency="USD")
async def summarize_text(text: str, ctx: Context) -> str:
"""Summarize text for $0.10 per request"""
# Your summarization logic
return f"Summary: {text[:200]}..."
if __name__ == "__main__":
mcp.run()
Step 3: Run Your Server
python server.py
Supported Payment Providers
PayMCP supports multiple payment providers out of the box:
| Provider | Pay-Per-Request | Subscriptions | Setup Complexity | |----------|-----------------|---------------|------------------| | Stripe | Yes | Yes | Easy | | PayPal | Yes | No | Medium | | Coinbase Commerce | Yes | No | Easy | | Adyen | Yes | No | Medium | | Square | Yes | No | Medium | | Walleot | Yes | No | Easy |
Stripe Configuration
from paymcp.providers import StripeProvider
provider = StripeProvider(
api_key=os.getenv("STRIPE_API_KEY")
)
PayPal Configuration
from paymcp.providers import PayPalProvider
provider = PayPalProvider(
client_id=os.getenv("PAYPAL_CLIENT_ID"),
client_secret=os.getenv("PAYPAL_CLIENT_SECRET"),
sandbox=True # Set to False for production
)
Coinbase Commerce Configuration
from paymcp.providers import CoinbaseCommerceProvider
provider = CoinbaseCommerceProvider(
api_key=os.getenv("COINBASE_COMMERCE_API_KEY")
)
Payment Patterns
Pattern 1: Pay-Per-Request
Charge users each time they call a tool:
@mcp.tool()
@price(amount=0.05, currency="USD")
async def analyze_image(image_url: str, ctx: Context) -> str:
"""Analyze an image - $0.05 per analysis"""
# Analysis logic
return "Analysis results..."
Pattern 2: Subscription Gating
Require an active subscription to use premium tools:
from paymcp import subscription
@mcp.tool()
@subscription(plan="price_pro_monthly")
async def generate_report(data: dict, ctx: Context) -> str:
"""Generate detailed report (Pro subscribers only)"""
return "Detailed report..."
When using subscription tools, PayMCP automatically provides:
list_subscriptionsβ Show available plansstart_subscriptionβ Create or resume subscriptioncancel_subscriptionβ Cancel at period end
Pattern 3: Tiered Pricing
Different prices for different tiers:
@mcp.tool()
@price(amount=0.01, currency="USD")
async def basic_search(query: str, ctx: Context) -> str:
"""Basic search - $0.01"""
return "Basic results..."
@mcp.tool()
@price(amount=0.10, currency="USD")
async def advanced_search(query: str, ctx: Context) -> str:
"""Advanced search with AI - $0.10"""
return "Advanced results..."
Pattern 4: Dynamic Pricing
Calculate price based on input:
@mcp.tool()
async def process_document(document: str, ctx: Context) -> str:
"""Process document - price varies by length"""
word_count = len(document.split())
price_usd = max(0.01, word_count * 0.001) # $0.001 per word, min $0.01
# Custom billing logic
return f"Processed {word_count} words"
Payment Modes
PayMCP supports different payment flow patterns:
| Mode | Description | Best For |
|------|-------------|----------|
| TWO_STEP | First call returns payment URL, second confirms | Most use cases |
| RESUBMIT | Adds payment_id parameter to tool | Simple flows |
| ELICITATION | Requests payment within same call | Streaming |
| PROGRESS | Keeps connection open while polling | Real-time feedback |
| DYNAMIC_TOOLS | Exposes temporary helper tools | Complex flows |
Example: TWO_STEP Mode (Default)
PayMCP(
mcp,
providers=[StripeProvider(api_key=os.getenv("STRIPE_API_KEY"))],
mode=Mode.TWO_STEP
)
User flow:
- User calls
summarize_text("Hello world") - Returns:
"Please complete payment at: https://checkout.stripe.com/..." - User completes payment
- User calls
summarize_text("Hello world")again - Returns:
"Summary: Hello world..."
Production Setup
Redis State Storage
For horizontal scaling and persistent state:
from redis.asyncio import from_url
from paymcp import PayMCP, RedisStateStore
redis = await from_url("redis://localhost:6379")
PayMCP(
mcp,
providers=[StripeProvider(api_key=os.getenv("STRIPE_API_KEY"))],
state_store=RedisStateStore(redis)
)
Multiple Providers
Support multiple payment methods:
PayMCP(
mcp,
providers=[
StripeProvider(api_key=os.getenv("STRIPE_API_KEY")),
PayPalProvider(
client_id=os.getenv("PAYPAL_CLIENT_ID"),
client_secret=os.getenv("PAYPAL_CLIENT_SECRET")
),
CoinbaseCommerceProvider(api_key=os.getenv("COINBASE_API_KEY"))
],
mode=Mode.TWO_STEP
)
Authentication & Identity
PayMCP extracts user identity from:
ctx.authInfofrom MCP context- Bearer token in Authorization header
Required JWT claims:
subβ Used as userIdemailβ Recommended for provider matching
# PayMCP automatically extracts identity
# No additional code needed if your MCP client sends auth
Security Considerations
Critical: Deployment Mode
Warning: PayMCP is NOT compatible with STDIO mode deployments where users run servers locally. This would expose payment provider API keys.
Safe deployment patterns:
- Remote MCP servers (HTTP/SSE)
- Hosted services
- Serverless functions
Best Practices
- Never expose API keys in client-side code
- Use environment variables for all secrets
- Enable webhook verification for payment confirmation
- Implement rate limiting to prevent abuse
- Log all transactions for audit purposes
Creating Custom Payment Providers
Extend BasePaymentProvider to add new providers:
from paymcp.providers import BasePaymentProvider
class MyCustomProvider(BasePaymentProvider):
def __init__(self, api_key: str):
self.api_key = api_key
async def create_payment(
self,
amount: float,
currency: str,
description: str
) -> tuple[str, str]:
"""Create a payment and return (payment_id, payment_url)"""
# Integration with your payment system
payment_id = "pay_123"
payment_url = "https://payment.example.com/pay/123"
return payment_id, payment_url
async def get_payment_status(self, payment_id: str) -> str:
"""Check payment status: 'pending', 'completed', 'failed'"""
# Check your payment system
return "completed"
Real-World Example: AI Writing Tool
Complete example of a monetized AI writing service:
import os
from mcp.server.fastmcp import FastMCP, Context
from paymcp import PayMCP, Mode, price, subscription
from paymcp.providers import StripeProvider
mcp = FastMCP("ai-writer")
PayMCP(
mcp,
providers=[StripeProvider(api_key=os.getenv("STRIPE_API_KEY"))],
mode=Mode.TWO_STEP
)
# Free tool
@mcp.tool()
async def check_grammar(text: str, ctx: Context) -> str:
"""Free grammar check"""
return f"Grammar checked: {text}"
# Pay-per-request
@mcp.tool()
@price(amount=0.05, currency="USD")
async def rewrite_paragraph(text: str, style: str, ctx: Context) -> str:
"""Rewrite paragraph in specified style - $0.05"""
return f"Rewritten ({style}): {text}"
# Premium subscription
@mcp.tool()
@subscription(plan="price_unlimited_monthly")
async def write_article(topic: str, length: int, ctx: Context) -> str:
"""Generate full article - Pro subscribers only"""
return f"Article about {topic}..."
if __name__ == "__main__":
mcp.run()
Troubleshooting
Payment Not Processing
Symptoms: Payment URL generated but payment fails
Solutions:
- Verify API keys are correct
- Check provider dashboard for errors
- Ensure webhook endpoints are configured
- Verify user has valid payment method
State Not Persisting
Symptoms: Users have to repay on server restart
Solutions:
- Use Redis state storage for production
- Configure Redis connection properly
- Check Redis connectivity
Identity Not Resolving
Symptoms: Users can't be matched to payments
Solutions:
- Ensure JWT contains
subclaim - Add
emailclaim for provider matching - Verify auth flow is sending tokens
Related Payment MCP Projects
The MCP payments ecosystem is growing:
- PayPal Agent Toolkit β Official PayPal MCP integration
- Coinbase Payments MCP β On-chain payments for AI agents
- Latinum MCP Wallet β Autonomous AI agent payments
Next Steps
- Explore MCP servers: Browse the directory
- Learn MCP basics: Setup guide
- Build custom servers: MCP programming guide
Resources
Last updated: December 2025 | Have questions? Browse more MCP guides