<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eugene Lobachev</title>
    <description>The latest articles on DEV Community by Eugene Lobachev (@eugene_lobachev).</description>
    <link>https://dev.to/eugene_lobachev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3406471%2Feca9d00e-2d8d-4d83-a15d-6406e12d9cb8.jpg</url>
      <title>DEV Community: Eugene Lobachev</title>
      <link>https://dev.to/eugene_lobachev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eugene_lobachev"/>
    <language>en</language>
    <item>
      <title>Building a Paid MCP Server</title>
      <dc:creator>Eugene Lobachev</dc:creator>
      <pubDate>Fri, 01 Aug 2025 18:40:40 +0000</pubDate>
      <link>https://dev.to/eugene_lobachev/building-a-paid-mcp-service-2nh4</link>
      <guid>https://dev.to/eugene_lobachev/building-a-paid-mcp-service-2nh4</guid>
      <description>&lt;p&gt;MCP servers are growing very fast - it feels like the dotcom bubble but everything happens 10x faster. Not having your own MCP server can soon feel like not having a website back in the day. In this tutorial I’ll show how to create your own MCP that can also manage payment flows.&lt;/p&gt;

&lt;p&gt;Let’s say you want to create an MCP that &lt;em&gt;generates images&lt;/em&gt;. I know, most AI chatbots already have image generation built in - but I don’t want to show another "a+b" example. I want a real use case that you can easily replace with e.g. video generation which has very high demand right now.&lt;/p&gt;

&lt;p&gt;We’ll use OpenAI for image generation and &lt;a href="https://walleot.com/developers" rel="noopener noreferrer"&gt;Walleot&lt;/a&gt; as the payment provider (it lets you charge any amount, compared to Stripe which has a minimum).&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10+&lt;/li&gt;
&lt;li&gt;OpenAI API key&lt;/li&gt;
&lt;li&gt;Walleot API key&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt; to manage Python projects&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1) Project setup
&lt;/h2&gt;

&lt;p&gt;So, we start the project the same way the official &lt;a href="https://pypi.org/project/mcp/" rel="noopener noreferrer"&gt;MCP Python SDK&lt;/a&gt; is suggesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv init mcp-server-demo
cd mcp-server-demo

# Add MCP SDK and CLI
uv add "mcp[cli]"

# Image generation and payments
uv add openai paymcp

# Optional: fetch image bytes, env vars, local resize
uv add requests python-dotenv Pillow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file if you like to keep keys locally during development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY=sk-...
WALLEOT_API_KEY=...
ENV=development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2) Minimal MCP server
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;server.py&lt;/code&gt; the same way the official MCP Python SDK recommends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from mcp.server.fastmcp import FastMCP, Context, Image

# Create an MCP server
mcp = FastMCP("Image generator", capabilities={"elicitation": {}})

# Define your AI tool
@mcp.tool()
async def generate(prompt: str, ctx: Context):  # important to have ctx: Context here
    """Generates an image and returns it as an MCP resource"""
    # Your image generation logic will appear here
    return None  # later we’ll return an image here

# Run the server
if __name__ == "__main__":
    mcp.run(transport="streamable-http")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see - we added &lt;em&gt;elicitation&lt;/em&gt; capability - a new feature that allows the MCP server to ask the user for additional data during execution. Not all clients support it yet, and for older clients you don’t need this capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  3) OpenAI image generation helper
&lt;/h2&gt;

&lt;p&gt;Next, let’s add image generation logic. Since we use the OpenAI API, create a file named &lt;code&gt;openai_client.py&lt;/code&gt; with this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from typing import Optional
import os
import base64
import requests
from openai import AsyncOpenAI

_client: Optional[AsyncOpenAI] = None


def _get_client() -&amp;gt; AsyncOpenAI:
    global _client
    if _client is None:
        api_key = os.environ.get("OPENAI_API_KEY")
        if not api_key:
            raise RuntimeError(
                "Missing OPENAI_API_KEY. Set it in your environment before calling generate_image()."
            )
        _client = AsyncOpenAI(api_key=api_key)
    return _client


async def generate_image(prompt: str) -&amp;gt; str:
    """Generate an image and return base64 (PNG by default)."""
    client = _get_client()
    res = await client.images.generate(
        model="dall-e-2",  # use any image model you have access to
        prompt=prompt
    )

    b64 = getattr(res.data[0], "b64_json", None) if res.data else None
    if not b64:
        url = getattr(res.data[0], "url", None) if res.data else None
        if not url:
            raise RuntimeError("No image returned (neither b64_json nor url)")
        resp = requests.get(url, timeout=30)
        resp.raise_for_status()
        b64 = base64.b64encode(resp.content).decode("ascii")
    return b64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function returns a &lt;em&gt;base64 image&lt;/em&gt; instead of a link to increase compatibility with different clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  4) Use the helper in the tool and return an MCP resource
&lt;/h2&gt;

&lt;p&gt;Now we use this function in our main code. To keep it working smoothly with clients like Claude Desktop, I also resize the image to 100x100. You can skip this if your client has no size limits.&lt;/p&gt;

&lt;p&gt;Update &lt;code&gt;server.py:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from mcp.server.fastmcp import FastMCP, Context, Image
from openai_client import generate_image
from io import BytesIO
from PIL import Image as PILImage
import base64

mcp = FastMCP("Image generator", capabilities={"elicitation": {}})

@mcp.tool()
async def generate(prompt: str, ctx: Context):  # important to have ctx: Context here
    """Generates high quality image and returns it as MCP resource"""
    b64 = await generate_image(prompt)

    # Decode base64 and resize locally
    raw = base64.b64decode(b64)
    img = PILImage.open(BytesIO(raw))
    img.thumbnail((100, 100))

    buffer = BytesIO()
    img.save(buffer, format="PNG")
    buffer.seek(0)

    return Image(data=buffer.getvalue(), format="png")

if __name__ == "__main__":
    mcp.run(transport="streamable-http")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5) Run and test
&lt;/h2&gt;

&lt;p&gt;This runs MCP server with MCP Inspector where you can test your tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv run mcp dev server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, install for Claude Desktop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv run mcp install server.py --with openai --with paymcp --with requests --with Pillow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6) Add payments
&lt;/h2&gt;

&lt;p&gt;Now the easy but important step - add payments.&lt;/p&gt;

&lt;p&gt;First, import and initialize PayMCP in &lt;code&gt;server.py&lt;/code&gt;, then price your tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from paymcp import PayMCP, PaymentFlow, price

#...
# Initialize payments with Walleot
PayMCP(
    mcp,
    providers={"walleot": {"apiKey": os.getenv("WALLEOT_API_KEY")}},
    payment_flow=PaymentFlow.ELICITATION  # change to PaymentFlow.TWO_STEP for Claude Desktop
)

@mcp.tool()
@price(0.2, "USD")
async def generate(prompt: str, ctx: Context):  # important to have ctx: Context here
   #....

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it! Once you add &lt;code&gt;WALLEOT_API_KEY&lt;/code&gt; to your environment, your MCP will start asking for payment before running the image generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  7) Run as a server or install in a client
&lt;/h2&gt;

&lt;p&gt;Run as a server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv run server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install to Claude Desktop the same way as before (but don’t forget to change payment_flow to &lt;code&gt;PaymentFlow.TWO_STEP&lt;/code&gt; for clients that do not support elicitation yet, e.g. Claude Desktop):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv run mcp install server.py --with openai --with paymcp --with requests --with Pillow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Full code
&lt;/h3&gt;

&lt;p&gt;If you want this as a single file example, here is &lt;code&gt;server.py&lt;/code&gt; with everything wired together. You will still need openai_client.py as shown above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# server.py
from mcp.server.fastmcp import FastMCP, Image, Context
from paymcp import PayMCP, PaymentFlow, price
from openai_client import generate_image
import base64
from io import BytesIO
import os
from PIL import Image as PILImage

# Load .env in development
env = os.getenv("ENV", "development")
if env == "development":
    from dotenv import load_dotenv
    load_dotenv()

mcp = FastMCP("Image generator", capabilities={"elicitation": {}})

# Payments
PayMCP(
    mcp,
    providers={"walleot": {"apiKey": os.getenv("WALLEOT_API_KEY")}},
    payment_flow=PaymentFlow.ELICITATION
)

@mcp.tool()
@price(0.2, "USD")
async def generate(prompt: str, ctx: Context):  # important to have ctx: Context here
    """Generates high quality image and returns it as MCP resource"""
    b64 = await generate_image(prompt)

    # Decode base64 and resize locally
    raw = base64.b64decode(b64)
    img = PILImage.open(BytesIO(raw))
    img.thumbnail((100, 100))

    buffer = BytesIO()
    img.save(buffer, format="PNG")
    buffer.seek(0)

    return Image(data=buffer.getvalue(), format="png")

if __name__ == "__main__":
    mcp.run(transport="streamable-http")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Troubleshooting
&lt;/h3&gt;

&lt;p&gt;Missing &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; - check your env vars or &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;No image returned - verify your OpenAI model access or try another model name.&lt;/p&gt;

&lt;p&gt;Payment not triggered - check &lt;code&gt;WALLEOT_API_KEY,&lt;/code&gt; PayMCP init, and the &lt;code&gt;@price(...)&lt;/code&gt; decorator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;p&gt;Full code: &lt;a href="https://github.com/blustAI/python-paymcp-server-demo" rel="noopener noreferrer"&gt;python-paymcp-server-demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PayMCP library: This is a very first version. If you face issues or want to contribute, feel free to open PRs here — &lt;a href="https://github.com/blustAI/paymcp" rel="noopener noreferrer"&gt;https://github.com/blustAI/paymcp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>walleot</category>
    </item>
  </channel>
</rss>
