Provider-agnostic payment layer for MCP (Model Context Protocol) tools and agents.
paymcp is a lightweight SDK that helps you add monetization to your MCPβbased tools, servers, or agents. It supports multiple payment providers and integrates seamlessly with MCP's tool/resource interface.
See the full documentation.
- β
Add
@price(...)decorators to your MCP tools to enable payments - π Choose between different modes (two_step, resubmit, elicit, progress, dynamic_tools, etc.)
- π Built-in support for major providers (see list) β plus a pluggable interface for custom providers.
- βοΈ Easy integration with
FastMCPor other MCP servers
Install the SDK from PyPI:
pip install mcp paymcpInitialize PayMCP:
import os
from mcp.server.fastmcp import FastMCP, Context
from paymcp import Mode, price
from paymcp.providers import StripeProvider
mcp = FastMCP("AI agent name")
PayMCP(
mcp,
providers=[
StripeProvider(api_key=os.getenv("STRIPE_API_KEY")),
],
mode=Mode.TWO_STEP # optional, TWO_STEP (default) / RESUBMIT / ELICITATION / PROGRESS / DYNAMIC_TOOLS
)Use the @price decorator on any tool:
@mcp.tool()
@price(amount=0.99, currency="USD")
def add(a: int, b: int, ctx: Context) -> int: # `ctx` is required by the PayMCP tool signature β include it even if unused
"""Adds two numbers and returns the result."""
return a + bDemo server: For a complete setup, see the example repo: python-paymcp-server-demo.
In version 0.4.2, the paymentFlow parameter was renamed to mode, which better reflects its purpose. The old name remains supported for backward compatibility.
The mode parameter controls how the user is guided through the payment process. Choose the strategy that fits your use case:
-
Mode.TWO_STEP(default)
Splits the tool into two separate MCP methods.
The first step returns apayment_urland anext_stepmethod for confirmation.
The second method (e.g.confirm_add_payment) verifies payment and runs the original logic.
Supported in most clients. -
Mode.RESUBMITAdds an optionalpayment_idto the original tool signature.- First call: the tool is invoked without
payment_idβ PayMCP returns apayment_url+payment_idand instructs a retry after payment. - Second call: the same tool is invoked again with the returned
payment_idβ PayMCP verifies payment serverβside and, if paid, executes the original tool logic.
- First call: the tool is invoked without
Similar compatibility to TWO_STEP, but with a simpler surface
-
Mode.ELICITATIONSends the user a payment link when the tool is invoked. If the client supports it, a payment UI is displayed immediately. Once the user completes payment, the tool proceeds. -
Mode.PROGRESS
Shows payment link and a progress indicator while the system waits for payment confirmation in the background. The result is returned automatically once payment is completed. -
Mode.DYNAMIC_TOOLSSteer the client and the LLM by changing the visible tool set at specific points in the flow (e.g., temporarily exposeconfirm_payment_*), thereby guiding the next valid action.
All modes require the MCP client to support the corresponding interaction pattern. When in doubt, start with TWO_STEP.
By default, when using the TWO_STEP or RESUBMIT modes, PayMCP stores payment_id and pending tool arguments in memory using a process-local Map. This is not durable and will not work across server restarts or multiple server instances (no horizontal scaling).
To enable durable and scalable state storage, you can provide a custom StateStore implementation. PayMCP includes a built-in RedisStateStore, which works with any Redis-compatible client.
from redis.asyncio import from_url
from paymcp import PayMCP, RedisStateStore
redis = await from_url("redis://localhost:6379")
PayMCP(
mcp,
providers=[
StripeProvider(api_key=os.getenv("STRIPE_API_KEY")),
],
state_store=RedisStateStore(redis)
)Built-in support is available for the following providers. You can also write a custom provider.
Any provider must subclass BasePaymentProvider and implement create_payment(...) and get_payment_status(...).
from paymcp.providers import BasePaymentProvider
class MyProvider(BasePaymentProvider):
def create_payment(self, amount: float, currency: str, description: str):
# Return (payment_id, payment_url)
return "unique-payment-id", "https://example.com/pay"
def get_payment_status(self, payment_id: str) -> str:
return "paid"
PayMCP(mcp, providers=[MyProvider(api_key="...")])