This example demonstrates how to build a payment-protected MCP server using ATXP (Authentic Transaction Protocol) on Cloudflare Workers. Users must pay in cryptocurrency (USDC on Base network) to use the MCP tools.
- 🛡️ OAuth Authentication - Users authenticate with ATXP before accessing tools
- 💰 Cryptocurrency Payments - Real USDC payments on Base network required for tool usage
- ⚡ Cloudflare Workers - Serverless deployment with Durable Objects for MCP agents
- 🔒 Production Ready - Secure HTTPS-only configuration for production deployment
- 🧪 Development Friendly - Local HTTP support for testing
- Main Worker: Handles ATXP authentication and OAuth challenges
- Durable Objects: Isolated MCP agent instances with payment protection
- ATXP Integration: Seamless payment flow with on-chain settlement
- Cloudflare account with Workers enabled
- ATXP account for payments (sign up at https://accounts.atxp.ai)
- Cryptocurrency wallet for receiving payments
Add the following to .env.
# Development security setting - allows HTTP for localhost testing
ALLOW_INSECURE_HTTP_REQUESTS_DEV_ONLY_PLEASE=true
# Your ATXP connection string for receiving payments and client testing
ATXP_CONNECTION_STRING=https://accounts.atxp.ai?connection_token=YOUR_TOKENUse wranger secret put ATXP_CONNECTION_STRING to set your connection string
Deploy to Cloudflare Workers:
npm run deploy# Start development server
npm run dev
# Test with ATXP client (in another terminal)
npm run test:local# Test deployed server
npm run test:remoteCreate your main handler in src/index.ts:
import { McpAgent } from "agents/mcp";
import { atxpCloudflare, type ATXPCloudflareOptions } from "@atxp/cloudflare";
import { ATXPPaymentDestination } from "@atxp/server";
const createOptions = (env: Env) => {
const paymentDestination = new ATXPPaymentDestination(
env.ATXP_CONNECTION_STRING,
);
paymentDestination.destination =
paymentDestination.destination.bind(paymentDestination);
return {
mcpAgent: MyMCP,
payeeName: "ATXP MCP Server Demo",
allowHttp: env.ALLOW_INSECURE_HTTP_REQUESTS_DEV_ONLY_PLEASE === "true",
paymentDestination,
} as ATXPCloudflareOptions;
};
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const cloudflareOptions = createOptions(env);
const handler = atxpCloudflare(cloudflareOptions);
const response = await handler.fetch(request, env, ctx);
return response;
}
};Add payment-protected tools in your MCP class:
import { requirePayment } from "@atxp/cloudflare";
import { BigNumber } from "bignumber.js";
// Add to MyMCP class init() method
this.server.tool(
"my_premium_tool",
{ input: z.string() },
async ({ input }) => {
if (!this.props) {
throw new Error("ATXP props are not initialized");
}
const options = createOptions(this.env);
await requirePayment(
{
price: new BigNumber(0.05), // 0.05 USDC
},
options,
this.props,
);
// Your tool logic here
return {
content: [{ type: "text", text: `Processed: ${input}` }]
};
}
);Main wrapper for creating ATXP-protected Cloudflare Workers:
atxpCloudflare({
mcpAgent: MyMCP, // Your MCP agent class
payeeName?: string, // Display name for OAuth
allowHttp?: boolean, // Allow HTTP for development
paymentDestination: ATXPPaymentDestination, // Payment destination instance
mountPaths?: { // Optional custom paths
mcp?: string,
sse?: string,
root?: string
}
})Payment enforcement in tool handlers:
await requirePayment(
{
price: BigNumber, // Payment amount in USDC
},
options: ATXPCloudflareOptions, // Options from createOptions()
props: ATXPMCPAgentProps // ATXP props from this.props
);ATXP_CONNECTION_STRING- ATXP connection string containing payment destination and network configurationALLOW_INSECURE_HTTP_REQUESTS_DEV_ONLY_PLEASE- HTTP allowance for development
When adding features:
- Ensure proper ATXP initialization in Durable Objects
- Use
ATXPAuthContextfor authentication data - Follow the environment variable naming convention
- Test both local and production deployments