How to Accept Stripe's Machine Payments Protocol (MPP) in Your MCP Server
Monetize your MCP server by accepting per-tool-call payments using Stripe's Machine Payments Protocol. This guide covers pricing schemas, payment verification, and webhook handling for MPP-enabled MCP servers.
Accept Stripe's Machine Payments Protocol in Your MCP Server
Stripe's Machine Payments Protocol (MPP) lets you charge AI agents per tool call on your MCP server. Instead of billing human users, you bill the machine wallets held by AI agent frameworks — fully automated, with Stripe handling fraud, settlement, and compliance.
This guide walks through adding MPP to an existing MCP server.
What You're Building
AI Agent (Client) → MPP-enabled MCP Server → Your Business Logic
↓
Stripe verifies machine wallet
Charges settled per tool call
Funds deposited to your Stripe accountPrerequisites
- ▸Stripe account with MPP enabled (request access at stripe.com/mpp)
- ▸An existing MCP server (TypeScript or Python)
- ▸Stripe CLI installed
- ▸Node.js 18+
Step 1: Enable MPP on Your Stripe Account
MPP is currently in private beta. Once approved:
- ▸Go to Stripe Dashboard → Settings → Machine Payments
- ▸Enable MPP for your account
- ▸Create a Merchant MPP Profile — this establishes your server identity in the Stripe MPP registry
- ▸Note your code
mpp_merchant_id
Step 2: Install the MPP Server SDK
npm install @stripe/mcp-mpp-server stripeStep 3: Add Pricing Metadata to Your Tools
The MPP protocol extends the standard MCP tool schema with an
x-mpp-pricingimport { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { MPPMiddleware } from '@stripe/mcp-mpp-server';
const server = new Server(
{ name: 'my-paid-mcp-server', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
// Define tools with MPP pricing
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'search_records',
description: 'Search our proprietary database of records',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
limit: { type: 'number', description: 'Max results' }
},
required: ['query']
},
// MPP pricing extension
'x-mpp-pricing': {
model: 'per_call',
amount: 5, // 5 cents per call
currency: 'usd',
unit: 'cent'
}
},
{
name: 'generate_report',
description: 'Generate a full analytical report',
inputSchema: { ... },
'x-mpp-pricing': {
model: 'per_call',
amount: 50, // 50 cents per call
currency: 'usd',
unit: 'cent'
}
},
{
name: 'get_status',
description: 'Check server status — free tool',
inputSchema: { type: 'object', properties: {} }
// No x-mpp-pricing = free tool
}
]
};
});Step 4: Add the MPP Middleware
The MPP middleware intercepts
call_toolimport Stripe from 'stripe';
import { MPPMiddleware, MPPConfig } from '@stripe/mcp-mpp-server';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const mppConfig: MPPConfig = {
stripe,
merchantId: process.env.MPP_MERCHANT_ID!,
// Tool name → pricing (must match your ListTools response)
pricing: {
search_records: { amount: 5, currency: 'usd', unit: 'cent' },
generate_report: { amount: 50, currency: 'usd', unit: 'cent' }
},
// Called when payment succeeds — proceed with tool logic
onPaymentSuccess: async (toolName, walletId, chargeId) => {
console.log(`Charged ${walletId} for ${toolName}: charge ${chargeId}`);
},
// Called when payment fails — return error to client
onPaymentFailure: async (toolName, walletId, error) => {
console.error(`Payment failed for ${toolName}: ${error.message}`);
}
};
const mpp = new MPPMiddleware(mppConfig);
// Wrap your tool handler with MPP middleware
server.setRequestHandler(CallToolRequestSchema, mpp.wrap(async (request) => {
const { name, arguments: args } = request.params;
if (name === 'search_records') {
const results = await myDatabase.search(args.query, args.limit ?? 10);
return { content: [{ type: 'text', text: JSON.stringify(results) }] };
}
if (name === 'generate_report') {
const report = await myReportEngine.generate(args);
return { content: [{ type: 'text', text: report }] };
}
if (name === 'get_status') {
return { content: [{ type: 'text', text: 'OK' }] };
}
throw new Error(`Unknown tool: ${name}`);
}));Step 5: Handle Webhooks for Settlement Events
Stripe sends webhook events for each machine payment. Set up a webhook endpoint to track revenue and handle edge cases:
import express from 'express';
const app = express();
app.post('/stripe/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature']!;
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'machine_payment.succeeded':
const payment = event.data.object;
// Update your usage records
await db.logPayment({
toolName: payment.metadata.tool_name,
walletId: payment.machine_wallet,
amount: payment.amount,
chargeId: payment.id
});
break;
case 'machine_payment.failed':
// Log failures for debugging
console.error('Payment failed:', event.data.object);
break;
case 'machine_wallet.balance_low':
// Optionally: proactively notify the client
break;
}
res.json({ received: true });
});
app.listen(3001);Register your webhook endpoint:
stripe listen --forward-to /stripe/webhooks
# In production:
stripe webhooks create --url https://yourserver.com/stripe/webhooks \
--events machine_payment.succeeded,machine_payment.failed,machine_wallet.balance_lowStep 6: Environment Variables
STRIPE_SECRET_KEY=sk_live_your_key
STRIPE_WEBHOOK_SECRET=whsec_your_secret
MPP_MERCHANT_ID=mpp_merchant_abc123Step 7: Register in the Stripe MPP Registry
For clients to discover and trust your server, register it:
curl https://api.stripe.com/v1/mpp/registry/servers \
-u sk_live_your_key: \
-d "merchant_id=mpp_merchant_abc123" \
-d "server_url=https://yourserver.com/mcp" \
-d "display_name=My Data Server" \
-d "description=Proprietary business records search" \
-d "category=data"Once approved (usually within 24 hours), your server appears in the Stripe MPP registry and clients can discover it with verified trust badges.
Pricing Models
MPP supports several pricing models in
x-mpp-pricing| Model | Description | Example |
|---|---|---|
code | Flat fee per tool invocation | code |
code | Charge based on input/output tokens | code |
code | Charge per result returned | code |
code | Free for wallets with active subscription | code |
Security Considerations
- ▸Always verify payment server-side: Never trust the client's claim that payment succeeded — let the MPP middleware verify with Stripe
- ▸Idempotency keys: The middleware handles idempotency automatically, preventing double-charges on retries
- ▸Webhook signature verification: Always verify headers — never skip this stepcode
stripe-signature - ▸Rate limiting: Apply per-wallet rate limits independently of payment to prevent abuse
- ▸Audit logging: Log every tool call with its for dispute resolutioncode
chargeId
Testing
Use Stripe test mode with test machine wallets:
# Create a test machine wallet
curl https://api.stripe.com/v1/machine_wallets \
-u sk_test_your_test_key: \
-d "display_name=Test Agent" \
-d "budget[amount]=10000" \
-d "budget[currency]=usd"Test cards and wallets don't trigger real charges — perfect for development and CI.
Monitoring Revenue
View MPP revenue in Stripe Dashboard → Machine Payments → Revenue, or pull reports via API:
curl https://api.stripe.com/v1/machine_payments \
-u sk_live_your_key: \
-d "merchant=mpp_merchant_abc123" \
-d "created[gte]=1700000000"