MCP is everywhere right now. Everyone is building MCP servers — for search, for databases, for file systems. But there's one thing nobody is talking about: how do you get paid when an AI agent uses your MCP tool?
Today we'll build a complete MCP server with a paid tool. An AI agent calls your tool, pays per use, and gets the result. No API keys, no subscriptions, no signup.
This isn't theoretical. We'll build it, run it, and test it.
What we're building
A code review MCP server. An AI agent sends a code snippet, pays $0.05, and gets a structured review back — issues found, suggestions, severity ratings.
Why code review? Because it's expensive to run (it calls an LLM under the hood), so charging for it makes sense. And it's a tool that AI agents actually want — coding agents like Cursor, Windsurf, and Claude Code could use a second opinion on the code they write.
How payment works in MCP
Quick primer if you haven't seen this before.
MCP tools normally work like this: agent calls tool → tool returns result. Free.
With x402 payment, there's one extra step: agent calls tool → server says "pay $0.05 first" → agent pays in USDC → server runs the tool → agent gets the result.
This happens automatically. x402-compatible agents (Coinbase AgentKit, Cloudflare Workers, Vercel AI SDK) handle payment without any human intervention. The agent has a wallet, sees the price, decides if it's worth it, pays, and gets the data.
Prerequisites
- Node.js 18+
- A wallet address (Coinbase Wallet or MetaMask — free, takes 2 minutes)
Step 1: Set up the project
mkdir code-review-mcp && cd code-review-mcp
npm init -y
npm install @modelcontextprotocol/sdk @monapi/sdk @x402/mcp zod
npm install -D tsx typescript @types/node
Add "type": "module" to your package.json.
Step 2: Build the MCP server
Create server.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { monapiMcp } from "@monapi/sdk/mcp";
import { z } from "zod";
const server = new McpServer({
name: "code-review",
version: "1.0.0",
});
const paid = monapiMcp({
wallet: process.env.MONAPI_WALLET!,
price: 0.05,
network: "base-sepolia",
});
This sets up the MCP server and creates a payment wrapper. Every tool wrapped with paid() will cost $0.05. We're using base-sepolia (testnet) for development.
Step 3: Add a free discovery tool
server.tool(
"list_capabilities",
{},
async () => ({
content: [
{
type: "text",
text: JSON.stringify({
name: "code-review",
description: "Automated code review for any language",
tools: {
review_code: {
price: "$0.05",
description: "Analyze code for bugs, security issues, and style",
},
},
}, null, 2),
},
],
})
);
No paid() wrapper means no payment required. This tool is free.
Why does this exist? An AI agent connecting to your MCP server for the first time has no idea what you offer or what it costs. The discovery tool answers both questions in machine-readable JSON. The agent can parse this, decide if review_code is worth $0.05, and proceed.
This is the equivalent of a storefront window. If you lock everything behind a paywall, agents will disconnect and move on.
Step 4: Add the paid tool
server.tool(
"review_code",
{
code: z.string().describe("The code snippet to review"),
language: z.string().describe("Programming language"),
},
paid(async (args) => {
const review = analyzeCode(args.code, args.language);
return {
content: [
{
type: "text",
text: JSON.stringify(review, null, 2),
},
],
};
})
);
The paid() wrapper is doing all the work. When an agent calls review_code:
- monapi checks if the request includes a valid payment
- No payment → the agent gets a 402 response with the price ($0.05), token (USDC), network (Base), and wallet address
- The agent pays and retries
- Payment verified → your handler runs and returns the review
You write the handler. monapi handles everything else.
Step 5: The analysis logic
function analyzeCode(code: string, language: string) {
const issues: Array<{
severity: "error" | "warning" | "info";
line: string;
message: string;
suggestion: string;
}> = [];
const lines = code.split("\n");
for (const line of lines) {
if (line.includes("eval(")) {
issues.push({
severity: "error",
line: line.trim(),
message: "Use of eval() is a security risk",
suggestion: "Use JSON.parse() for data, or a sandboxed interpreter",
});
}
if (/password\s*=\s*["']/.test(line)) {
issues.push({
severity: "error",
line: line.trim(),
message: "Hardcoded password detected",
suggestion: "Use environment variables: process.env.PASSWORD",
});
}
if (line.includes("catch") && line.includes("{}")) {
issues.push({
severity: "warning",
line: line.trim(),
message: "Empty catch block swallows errors silently",
suggestion: "Log the error or re-throw",
});
}
if (line.includes(".forEach") && line.includes("await")) {
issues.push({
severity: "warning",
line: line.trim(),
message: "await inside forEach doesn't work as expected",
suggestion: "Use for...of for sequential, or Promise.all() for parallel",
});
}
}
return {
language,
linesAnalyzed: lines.length,
issues,
summary:
issues.length === 0
? "No issues found. Code looks clean."
: `Found ${issues.length} issue(s): ${issues.filter((i) => i.severity === "error").length} errors, ${issues.filter((i) => i.severity === "warning").length} warnings, ${issues.filter((i) => i.severity === "info").length} info.`,
};
}
This is deliberately simple — pattern matching for common issues. In production, you'd call an LLM (OpenAI, Anthropic, etc.) to do the actual review. That's exactly why charging $0.05 makes sense: if you're spending $0.02 on an LLM call under the hood, you need to charge more than that.
Step 6: Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
Run it:
MONAPI_WALLET=0xYourWalletAddress npx tsx server.ts
In production, you'd register it in an MCP client config:
{
"mcpServers": {
"code-review": {
"command": "npx",
"args": ["tsx", "/path/to/server.ts"],
"env": {
"MONAPI_WALLET": "0xYourWalletAddress"
}
}
}
}
Go to production
One change for real payments:
const paid = monapiMcp({
wallet: process.env.MONAPI_WALLET!,
price: 0.05,
network: "base", // was "base-sepolia"
});
Same code, real money. Payments settle in under 2 seconds on Base. USDC is always $1.
What makes this different from a regular paid API
An Express API with monapi works for HTTP clients — agents that make web requests. But MCP is a different protocol. MCP tools are discovered and called by AI assistants directly, inside their runtime. Claude, GPT, Cursor — they all speak MCP.
By building a paid MCP tool instead of (or in addition to) a paid HTTP API, you're available where agents already are. They don't need to know your URL. They discover you through their MCP configuration.
Pricing your MCP tools
Some rules of thumb:
- Discovery/metadata tools: Free. Always. Agents need to evaluate you before paying.
- Cheap lookups (cached data, simple transforms): $0.001–$0.01
- Moderate work (API calls, database queries): $0.01–$0.05
- Expensive operations (LLM calls, heavy compute): $0.05–$0.50
Price based on your cost, not your value. Agents are cost-optimizing machines. If your tool costs $0.50 and a competitor does the same for $0.10, the agent picks the cheaper one every time. Race to the best cost/quality ratio, not the highest margin.
Full source
Everything in this post is a complete, runnable project.
- GitHub — full SDK with MCP support
- monapi.dev — docs and quickstart
-
npm —
npm install @monapi/sdk
Or use the CLI to scaffold it:
npx monapi init
It auto-detects MCP from your package.json and generates the boilerplate.
This is part 2 of a series on monetizing APIs for AI agents. Part 1: How to monetize your API with one line of code
Top comments (0)