MCP + x402: Building Paid API Services from Zero to One
Imagine an AI agent that can autonomously fetch real-time GitHub trending repos, check crypto prices, and query HackerNews—all by paying fractions of a cent per request. No API keys, no subscriptions, no friction. This is now possible by combining MCP (Model Context Protocol) with x402 micro-payments.
In this guide, I'll walk you through building a production-ready paid API service from scratch, including the MCP server implementation, x402 payment integration, and deployment via Cloudflare Tunnel.
Part 1: Understanding the Building Blocks
What is MCP (Model Context Protocol)?
MCP is an open protocol that enables AI models to interact with external data sources and tools. Think of it as "USB for AI"—a standardized way to connect AI assistants to any data source.
MCP defines two key components:
- MCP Host: The AI application (Claude Desktop, Cursor, etc.)
- MCP Server: The service exposing tools and resources
MCP supports multiple transport modes, with StreamableHTTP being the modern standard for server-to-server communication:
┌─────────────┐ StreamableHTTP ┌─────────────┐
│ MCP Host │ ◄──────────────────► │ MCP Server │
│ (AI Client) │ JSON-RPC over HTTP │ (Your API) │
└─────────────┘ └─────────────┘
What is x402 (HTTP 402)?
x402 is an emerging standard that uses the HTTP 402 "Payment Required" status code for machine-native payments. Instead of asking users to subscribe or create accounts, your API simply returns a 402 response with payment instructions.
// Client requests data
GET /api/github/trending
// Server responds with payment required
HTTP/1.1 402 Payment Required
{
"x402Version": 1,
"maxAmountRequired": "0.005",
"payTo": "0xd931e9e57a14c4c800f6b6337a9c6608850c301d",
"asset": "USDC",
"network": "base-mainnet"
}
// Client pays, retries with proof → Server serves data
On Base chain, each transaction costs ~$0.001 and settles in ~2 seconds, making micro-payments economically viable.
Part 2: Building an MCP Server with TypeScript
Let's build a real MCP Server that exposes 16 developer data tools. We'll use the official @modelcontextprotocol/sdk package.
Project Setup
mkdir mcp-data-api && cd mcp-data-api
npm init -y
npm install @modelcontextprotocol/sdk zod axios
Define Tools Schema
// src/tools.ts
import { z } from "zod";
export const toolSchemas = {
// GitHub Tools
github_trending: {
name: "github_trending",
description: "Get trending GitHub repositories by language",
inputSchema: z.object({
language: z.string().optional().default("typescript"),
limit: z.number().min(1).max(100).default(10)
})
},
github_user_info: {
name: "github_user_info",
description: "Get GitHub user profile information",
inputSchema: z.object({
username: z.string()
})
},
// NPM Tools
npm_downloads: {
name: "npm_downloads",
description: "Get npm package download statistics",
inputSchema: z.object({
package: z.string()
})
},
npm_package_info: {
name: "npm_package_info",
description: "Get detailed npm package information",
inputSchema: z.object({
package: z.string()
})
},
// Crypto Tools
crypto_price: {
name: "crypto_price",
description: "Get current cryptocurrency prices",
inputSchema: z.object({
symbol: z.string().default("BTC")
})
},
crypto_gas_price: {
name: "crypto_gas_price",
description: "Get current gas prices on various chains",
inputSchema: z.object({
chain: z.string().default("ethereum")
})
},
// HackerNews Tools
hn_top_stories: {
name: "hn_top_stories",
description: "Get top HackerNews stories",
inputSchema: z.object({
limit: z.number().min(1).max(100).default(10)
})
},
hn_item: {
name: "hn_item",
description: "Get HackerNews item by ID",
inputSchema: z.object({
id: z.number()
})
},
// ... 8 more tools (Weather, News, etc.)
};
Implement Tool Handlers
// src/handlers.ts
import axios from "axios";
export async function handleGitHubTrending(args: { language?: string; limit?: number }) {
const { language = "typescript", limit = 10 } = args;
const response = await axios.get(
`https://api.github.com/search/repositories`,
{
params: {
q: `language:${language}`,
sort: "stars",
order: "desc",
per_page: limit
}
}
);
return {
content: response.data.items.map(repo => ({
name: repo.full_name,
description: repo.description,
stars: repo.stargazers_count,
url: repo.html_url
}))
};
}
export async function handleCryptoPrice(args: { symbol: string }) {
const { symbol = "BTC" } = args;
const response = await axios.get(
`https://api.coingecko.com/api/v3/simple/price`,
{
params: {
ids: symbol.toLowerCase(),
vs_currencies: "usd",
include_24hr_change: true
}
}
);
return {
content: response.data
};
}
// ... implement other handlers
Create the MCP Server with StreamableHTTP
// src/server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/transport.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import * as handlers from "./handlers.js";
import { toolSchemas } from "./tools.js";
const server = new Server(
{
name: "mcp-data-api",
version: "1.0.0"
},
{
capabilities: {
tools: {}
}
}
);
// Register all tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: Object.values(toolSchemas).map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: {
type: "object",
properties: tool.inputSchema.parse({}).constructor._def.schema
}
}))
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const handler = handlers[name];
if (!handler) {
throw new Error(`Unknown tool: ${name}`);
}
const result = await handler(args || {});
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`
}
],
isError: true
};
}
});
// Export for StreamableHTTP transport
export { server };
Start the Server with StreamableHTTP
// src/index.ts
import { serve } from "@modelcontextprotocol/sdk/server/streamable-http.js";
import { server } from "./server.js";
const port = process.env.PORT || 3000;
serve(server, { port }, () => {
console.log(`🚀 MCP Server running on http://localhost:${port}/mcp`);
console.log(`📡 StreamableHTTP transport enabled`);
});
Part 3: Integrating x402 Micro-Payments
Now let's add payment capabilities. We'll use the @x402/sdk package to integrate payments on Base chain.
Install x402 Dependencies
npm install @x402/sdk viem
Create Payment Middleware
// src/payments.ts
import { createPublicClient, http } from "viem";
import { base } from "viem/chains";
import { X402Client } from "@x402/sdk/client";
// Payment configuration
const PAYMENT_ADDRESS = "0xd931e9e57a14c4c800f6b6337a9c6608850c301d";
const USDC_ADDRESS = "0xA0b86991C6218b36c1d19D4a2e9Eb0cE3606EB48"; // Base USDC
// Price map (in USDC, as string to avoid floating point issues)
const PRICES = {
github_trending: "3000000", // 0.003 USDC (6 decimals)
github_user_info: "1000000", // 0.001 USDC
npm_downloads: "1000000",
npm_package_info: "1000000",
crypto_price: "2000000", // 0.002 USDC
crypto_gas_price: "2000000",
hn_top_stories: "1000000",
hn_item: "500000", // 0.0005 USDC
};
const publicClient = createPublicClient({
chain: base,
transport: http()
});
export function createX402Client() {
return new X402Client({
payTo: PAYMENT_ADDRESS,
asset: USDC_ADDRESS,
chainId: 8453, // Base mainnet
});
}
export function getPriceForTool(toolName: string): bigint {
return BigInt(PRICES[toolName] || "1000000");
}
export { PAYMENT_ADDRESS };
Create Payment-Enabled MCP Server
// src/server-paid.ts
import { serve } from "@modelcontextprotocol/sdk/server/streamable-http.js";
import { server } from "./server.js";
import { createX402Client, getPriceForTool, PAYMENT_ADDRESS } from "./payments.js";
import { createPublicClient, http } from "viem";
import { base } from "viem/chains";
// Initialize x402 client
const x402Client = createX402Client();
const publicClient = createPublicClient({
chain: base,
transport: http()
});
// Wrap the server with payment logic
async function handleToolCallWithPayment(request) {
const { name, arguments: args } = request.params;
// Check if payment header is present
const paymentHeader = request.headers?.["x-payment"];
if (!paymentHeader) {
// Return 402 with payment instructions
const price = getPriceForTool(name);
return {
status: 402,
headers: {
"X-Payment-Required": "true",
"X-Payment-Address": PAYMENT_ADDRESS,
"X-Payment-Amount": price.toString(),
"X-Payment-Asset": "USDC",
"X-Payment-Network": "base-mainnet"
},
body: {
error: "Payment required",
price: price.toString(),
payTo: PAYMENT_ADDRESS,
instructions: `Send ${price} USDC to ${PAYMENT_ADDRESS} on Base chain`
}
};
}
// Verify payment was successful
try {
const paymentProof = JSON.parse(Buffer.from(paymentHeader, "base64").toString());
const isValid = await x402Client.verifyPayment(paymentProof);
if (!isValid) {
return {
status: 402,
body: { error: "Invalid payment proof" }
};
}
} catch (error) {
return {
status: 402,
body: { error: "Payment verification failed" }
};
}
// Process the tool call
// ... (same as before)
}
// Start with payment middleware
serve(server, {
port: 3000,
onRequest: handleToolCallWithPayment
});
Part 4: Deploying with Cloudflare Tunnel
Your MCP server needs to be publicly accessible for AI agents to use it. We'll use Cloudflare Tunnel for a quick, free, and secure public URL.
Run Cloudflared Tunnel
# Install cloudflared
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/
# Start tunnel to your local MCP server
cloudflared tunnel --url http://localhost:3000
You'll get a URL like https://random-words.trycloudflare.com. This is your public MCP endpoint!
Persistent Tunnel Setup
# Create a named tunnel
cloudflared tunnel create mcp-server
cloudflared tunnel route dns mcp-server mcp.yourdomain.com
# Create config file
cat > ~/.cloudflared/config.yml << EOF
tunnel: <TUNNEL_ID>
credentials-file: /root/.cloudflared/<TUNNEL_ID>.json
ingress:
- hostname: mcp.yourdomain.com
service: http://localhost:3000
- service: http_status:404
EOF
# Run as service
sudo cloudflared service install
sudo systemctl start cloudflared
Future: Render Deployment
For production, consider deploying to Render with auto-scaling:
# render.yaml
services:
- type: web
name: mcp-data-api
env: node
region: oregon
plan: starter
buildCommand: npm install && npm run build
startCommand: npm start
healthCheckPath: /health
envVars:
- key: PORT
value: 10000
- key: PAYMENT_ADDRESS
sync: false
- key: X402_FACILITATOR_URL
sync: false
Part 5: Real-World Usage
Connect from Claude Desktop
# ~/.claude/mcp.json
{
"mcpServers": {
"data-api": {
"transport": "streamable-http",
"url": "https://mcp.yourdomain.com/mcp",
"headers": {
"x-payment": "base64_encoded_payment_proof"
}
}
}
}
Test with cURL
# Without payment (returns 402)
curl https://mcp.yourdomain.com/mcp \
-X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"github_trending","arguments":{}}}'
# Response:
# HTTP/1.1 402 Payment Required
# {"error": "Payment required", "price": "3000000", ...}
Open Source Projects
I've open-sourced the complete implementation. Check out these repositories:
📦 mcp-data-api
16 developer data tools as an MCP Server
Features: GitHub API, NPM registry, HackerNews, Crypto prices, Gas tracking, and more—all with StreamableHTTP transport.
💰 x402-data-api
HTTP 402 micro-payment data API on Base chain
Production-ready x402 implementation with USDC payments. Every request costs $0.001-0.01.
📊 apiwatch
API health monitoring and alerting
Monitor your MCP and x402 endpoints. Get notified when services go down.
Conclusion
By combining MCP and x402, we've built a truly autonomous API service:
- MCP provides the standardized tool interface for AI agents
- x402 enables frictionless micro-payments without accounts
- Base chain ensures fast, cheap transactions
- StreamableHTTP makes everything work over standard HTTP
The future of AI-to-AI commerce is here. Start building your paid API service today!
🚀 Show Your Support
If you found this guide useful, we'd really appreciate your support on these awesome lists!
⭐ awesome-mcp — Add our MCP Server implementation
https://github.com/punkpeye/awesome-mcp/pull/189
⭐ awesome-x402 — Add our x402 implementation
https://github.com/nicktaras/awesome-x402/pull/6
⭐ awesome-mcp-servers — Add our data tools
Every ⭐ and 👍 helps other developers discover these tools. Thanks for your support!