DEV Community

Cover image for How to build a paid MCP tool that AI agents pay to use
M M
M M

Posted on

How to build a paid MCP tool that AI agents pay to use

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
Enter fullscreen mode Exit fullscreen mode

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",
});
Enter fullscreen mode Exit fullscreen mode

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),
      },
    ],
  })
);
Enter fullscreen mode Exit fullscreen mode

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),
        },
      ],
    };
  })
);
Enter fullscreen mode Exit fullscreen mode

The paid() wrapper is doing all the work. When an agent calls review_code:

  1. monapi checks if the request includes a valid payment
  2. No payment → the agent gets a 402 response with the price ($0.05), token (USDC), network (Base), and wallet address
  3. The agent pays and retries
  4. 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.`,
  };
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Run it:

MONAPI_WALLET=0xYourWalletAddress npx tsx server.ts
Enter fullscreen mode Exit fullscreen mode

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"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Go to production

One change for real payments:

const paid = monapiMcp({
  wallet: process.env.MONAPI_WALLET!,
  price: 0.05,
  network: "base", // was "base-sepolia"
});
Enter fullscreen mode Exit fullscreen mode

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
  • npmnpm install @monapi/sdk

Or use the CLI to scaffold it:

npx monapi init
Enter fullscreen mode Exit fullscreen mode

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)