← Home AgentMall/MCP Build
Roadmap →
Spoke 3 · MCP Server Build Guide

How to build an MCP server in 2026.

A working commerce MCP server, deployed to a live URL, reachable by Claude, ChatGPT, Gemini, Cursor, and every other MCP-compatible client — built in an afternoon with Python, FastMCP, and a free Vercel account. Complete code. No stubs. No "fill this in later."

~2 hrs
Build, deploy, and connect to Claude
$0
Hosting cost on Vercel Hobby tier
4
Total files in the project
576+
MCP clients that will reach your server

A live server every AI agent can call.

No hand-waving. By the end of this guide you have a deployed URL that any MCP-compatible AI can hit to browse products, search the catalog, and initiate checkout.

You are building a Python MCP server that exposes three capabilities: a product catalog as a browsable Resource, a keyword + filter search as a Tool, and a checkout-initiation Tool that returns an order summary and a checkout URL. It runs locally with one command. It deploys to Vercel's free tier with one command. After deployment, you wire it into Claude Desktop in roughly two minutes and watch the agent call your server in real time.

The stack is Python plus FastMCP plus Vercel. FastMCP is the dominant MCP framework — it powers an estimated 70% of MCP servers across all languages and is downloaded over a million times per day. It generates the JSON-RPC handlers, input schemas, and protocol lifecycle automatically. You write decorated Python functions; FastMCP does the rest.

The outcome you will have at the end of this page: one live URL of the form https://your-project.vercel.app/mcp that any AI agent supporting Streamable HTTP transport — Claude, ChatGPT, Gemini CLI, Cursor, Windsurf, Cline, and the rest of the 576+ MCP clients tracked by PulseMCP — can reach without any further integration work on your end. One server. Every client.

The protocol layer for agentic commerce.

The Model Context Protocol is an open standard, launched by Anthropic in November 2024, that standardizes how AI applications connect to external data, tools, and services. Think of it as a USB-C port for AI: one connector, every device. Instead of building a custom integration for Claude, then another for ChatGPT, then another for Gemini, you build one MCP server and every MCP-compatible client can reach it ([modelcontextprotocol.io](https://modelcontextprotocol.io/introduction)). The current stable spec is 2025-11-25, the one-year anniversary release.

The client-server model is straightforward: your server exposes capabilities (tools, resources, prompts); the AI client discovers them at runtime through a JSON-RPC initialization handshake; the model decides when to call a tool based on user intent; your server executes; the result flows back into the conversation. The transport layer is either stdio (local subprocess) or Streamable HTTP (hosted server) — both carry the same JSON-RPC 2.0 wire format. The flow looks like this:

Agent (Claude / ChatGPT / Gemini)
      ↓
MCP Client (built into the host app)
      ↓
HTTP or stdio  ←  JSON-RPC 2.0
      ↓
MCP Server (your code)
      ↓
Your API / database / business logic

The three primitives an MCP server exposes are Resources (read-only data the host injects into model context — product catalog, customer record), Tools (executable functions the model calls — search, add-to-cart, checkout), and Prompts (reusable templates the user triggers — "recommend a product"). This build uses Resources and Tools; Prompts are optional and not required to ship.

Five libraries. One config. That's it.

Every piece exists because it solves a specific problem. Nothing is decorative.

FastMCP (Python)

The framework. Bundled with the official mcp SDK. Generates JSON-RPC handlers, input schemas from Python type hints, and the full initialization handshake — automatically.

mcp[cli] >=1.14.1

FastAPI

The ASGI wrapper. Mounts FastMCP's Streamable HTTP app at /mcp, lets you add CORS, custom middleware (API keys, rate limiting), and a separate /health endpoint.

fastapi >=0.115.0

Uvicorn

The ASGI server. Runs the FastAPI app locally and on Vercel's Python runtime. --reload in dev, plain in prod.

uvicorn[standard] >=0.34.0

Pydantic

Validates tool input. Pulled in transitively by FastAPI and FastMCP; you almost never call it directly, but pin it to keep schema generation stable across upgrades.

pydantic >=2.10.0

Vercel

The serverless host. Hobby tier: 1M invocations + 100 GB bandwidth + 300s function duration on Fluid Compute. Free. Deploy with vercel --prod.

Free Hobby plan

mcp-remote

Local stdio-to-HTTP proxy. Claude Desktop doesn't natively accept a remote URL in most versions; mcp-remote bridges Desktop's stdio expectation to your hosted HTTP endpoint.

npx mcp-remote@latest
Why these versions

Pin the minimum versions shown here in requirements.txt. The MCP spec evolves quickly — three revisions since launch — and these versions all speak the 2025-11-25 spec cleanly. Re-verify on PyPI before publishing; the ecosystem moves fast.

Four files. Nothing more.

Create a project directory and these four files. That is the entire surface area of the build — no folders to navigate, no nested modules, no scaffolding to learn.

my-mcp-server/
├── server.py          # FastMCP app — resources, tools, FastAPI mount
├── requirements.txt   # Pinned dependencies
├── vercel.json        # Vercel deployment config
└── mcp.json           # Server manifest (for clients + directories)

On disk:

mkdir my-mcp-server
cd my-mcp-server
python -m venv .venv
source .venv/bin/activate     # macOS / Linux
# .venv\Scripts\activate      # Windows

Every line. Copy-paste ready.

This is the centerpiece of the guide. Four files, every line shown, no truncation. The complete, runnable build.

server.py

The full MCP server. Catalog resource, search tool, checkout tool, API-key middleware, FastAPI mount, health check, local-run entry point. Roughly 220 lines including comments.

"""
server.py — Commerce MCP Server
================================
A FastMCP server exposing a product catalog as an MCP Resource and
providing product search + checkout initiation as MCP Tools.

Transport: Streamable HTTP (deployable to Vercel)
Auth:      Optional API key via X-API-Key header
"""

from __future__ import annotations

import json
import os
from typing import Optional

import uvicorn
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from mcp.server.fastmcp import FastMCP
from starlette.middleware.base import BaseHTTPMiddleware

# ─────────────────────────────────────────────
# 1. Environment
# ─────────────────────────────────────────────
API_KEY = os.getenv("API_KEY", "dev-secret-key")
REQUIRE_AUTH = os.getenv("REQUIRE_AUTH", "false").lower() == "true"


# ─────────────────────────────────────────────
# 2. Product Data
#    In production, replace with a database call.
# ─────────────────────────────────────────────
PRODUCTS = [
    {
        "sku": "WIDGET-001",
        "name": "Ergonomic Mesh Chair",
        "price": 349.99,
        "description": "Lumbar support, adjustable armrests, breathable mesh back. Ships in 3-5 days.",
        "availability": "in_stock",
        "category": "furniture",
        "image_url": "https://example.com/images/widget-001.jpg",
    },
    {
        "sku": "GADGET-001",
        "name": "4K Webcam Pro",
        "price": 89.99,
        "description": "2160p 30fps, built-in microphone, auto light correction. USB-C + USB-A.",
        "availability": "in_stock",
        "category": "electronics",
        "image_url": "https://example.com/images/gadget-001.jpg",
    },
    {
        "sku": "GADGET-002",
        "name": "Mechanical Keyboard TKL",
        "price": 125.00,
        "description": "Tenkeyless layout, Cherry MX Blue switches, PBT keycaps, USB-C detachable cable.",
        "availability": "low_stock",
        "category": "electronics",
        "image_url": "https://example.com/images/gadget-002.jpg",
    },
    {
        "sku": "SUPPLY-001",
        "name": "Cable Management Kit (50pc)",
        "price": 19.99,
        "description": "Velcro ties, cable sleeves, adhesive clips. Compatible with most desk setups.",
        "availability": "in_stock",
        "category": "accessories",
        "image_url": "https://example.com/images/supply-001.jpg",
    },
]


# ─────────────────────────────────────────────
# 3. FastMCP Server
#
#    stateless_http=True is REQUIRED for Vercel.
#    Each request is independent; no session state
#    held between requests. Compatible with all
#    standard MCP clients.
# ─────────────────────────────────────────────
mcp = FastMCP(
    "Commerce Catalog Server",
    stateless_http=True,
)


# ─────────────────────────────────────────────
# 4. Resource: Product Catalog
# ─────────────────────────────────────────────
@mcp.resource(
    "products://catalog",
    name="Product Catalog",
    description=(
        "Full product catalog. SKU, name, price, description, "
        "availability, category, image_url for every product."
    ),
    mime_type="application/json",
)
def get_product_catalog() -> str:
    """Returns the complete product catalog as a JSON string."""
    return json.dumps(PRODUCTS, indent=2)


# ─────────────────────────────────────────────
# 5. Tool: Search Products
# ─────────────────────────────────────────────
@mcp.tool()
def search_products(
    keyword: str,
    category: Optional[str] = None,
    max_price: Optional[float] = None,
) -> dict:
    """
    Search the product catalog by keyword. Optionally filter by category
    and/or a maximum price in USD.

    Args:
        keyword:   Search term, matched against name and description.
        category:  Optional filter. Valid: furniture, electronics, accessories.
        max_price: Optional maximum price in USD (inclusive).

    Returns:
        Dict with 'count' (int) and 'results' (list of matching products).
    """
    kw = keyword.lower()
    results = []
    for product in PRODUCTS:
        if kw not in product["name"].lower() and kw not in product["description"].lower():
            continue
        if category and product["category"].lower() != category.lower():
            continue
        if max_price is not None and product["price"] > max_price:
            continue
        results.append(product)

    return {
        "count": len(results),
        "query": {"keyword": keyword, "category": category, "max_price": max_price},
        "results": results,
    }


# ─────────────────────────────────────────────
# 6. Tool: Initiate Checkout
# ─────────────────────────────────────────────
@mcp.tool()
def initiate_checkout(sku: str, quantity: int) -> dict:
    """
    Initiate a checkout session for a SKU + quantity.

    Args:
        sku:      Product SKU from search_products results.
        quantity: Integer >= 1.

    Returns:
        On success: dict with success=True, order_id, checkout_url, summary.
        On failure: dict with success=False, error message.
    """
    if quantity < 1:
        return {"success": False, "error": "Quantity must be at least 1."}

    product = next((p for p in PRODUCTS if p["sku"].upper() == sku.upper()), None)
    if product is None:
        return {
            "success": False,
            "error": f"SKU '{sku}' not found. Use search_products to find valid SKUs.",
        }
    if product["availability"] not in ("in_stock", "low_stock"):
        return {
            "success": False,
            "error": f"Product '{product['name']}' is currently {product['availability']}.",
        }

    total = round(product["price"] * quantity, 2)
    order_id = f"ORD-{sku.upper()}-{quantity:03d}"

    # Production: replace with stripe.checkout.Session.create(...)
    return {
        "success": True,
        "order_id": order_id,
        "sku": product["sku"],
        "product_name": product["name"],
        "quantity": quantity,
        "unit_price": product["price"],
        "total_usd": total,
        "checkout_url": f"https://checkout.example.com/session/{order_id}",
        "summary": (
            f"Order {order_id}: {quantity}x {product['name']} "
            f"@ ${product['price']:.2f} = ${total:.2f} total."
        ),
    }


# ─────────────────────────────────────────────
# 7. Optional API-key middleware
# ─────────────────────────────────────────────
class ApiKeyMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        if request.url.path.startswith("/mcp"):
            provided = request.headers.get("X-API-Key", "")
            if REQUIRE_AUTH and provided != API_KEY:
                return JSONResponse(
                    status_code=401,
                    content={"error": "Unauthorized. Provide a valid X-API-Key header."},
                )
        return await call_next(request)


# ─────────────────────────────────────────────
# 8. FastAPI app — CORS, middleware, health, mount
# ─────────────────────────────────────────────
app = FastAPI(
    title="Commerce MCP Server",
    description="MCP server exposing product catalog, search, and checkout.",
    version="1.0.0",
)

# CORS — wide open for development.
# Restrict allow_origins in production.
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["GET", "POST", "DELETE"],
    allow_headers=["*"],
    expose_headers=["Mcp-Session-Id"],
)

app.add_middleware(ApiKeyMiddleware)


@app.get("/health")
async def health_check():
    return {"status": "ok", "server": "Commerce MCP Server", "version": "1.0.0"}


# Mount the FastMCP Streamable HTTP app at /mcp
app.mount("/mcp", mcp.streamable_http_app())


# ─────────────────────────────────────────────
# 9. Local run
# ─────────────────────────────────────────────
if __name__ == "__main__":
    uvicorn.run(
        "server:app",
        host="0.0.0.0",
        port=8000,
        reload=True,
    )

requirements.txt

Pinned to versions that all speak the 2025-11-25 spec. Re-verify on PyPI before publishing.

mcp[cli]>=1.14.1
fastapi>=0.115.0
uvicorn[standard]>=0.34.0
pydantic>=2.10.0
httpx>=0.28.1

vercel.json

The simple version. Routes every request to server.py so FastAPI's router (including the mounted /mcp sub-app) handles dispatch.

{
  "rewrites": [
    { "source": "/(.*)", "destination": "/server.py" }
  ]
}
Why this vercel.json

Vercel's Python runtime auto-detects requirements.txt and the ASGI app exported as app in server.py. The rewrites rule sends every incoming path into the function so the FastAPI mount at /mcp matches correctly. If you later need longer execution time, add "functions": { "server.py": { "maxDuration": 300 } } and enable Fluid Compute in the Vercel dashboard.

mcp.json

The server manifest. Used by MCP directories, the Desktop Extension installer, and any client that fetches a manifest before connecting.

{
  "mcpb_version": "0.1",
  "name": "commerce-catalog-server",
  "display_name": "Commerce Product Catalog",
  "version": "1.0.0",
  "description": "MCP server exposing a product catalog, keyword search, and checkout initiation for an e-commerce store.",
  "author": {
    "name": "Your Name",
    "email": "you@example.com",
    "url": "https://yourstore.com"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/yourusername/commerce-mcp-server"
  },
  "license": "MIT",
  "tools": [
    {
      "name": "search_products",
      "description": "Search the product catalog by keyword, optionally filtering by category and maximum price."
    },
    {
      "name": "initiate_checkout",
      "description": "Initiate a checkout session for a given product SKU and quantity. Returns a checkout URL."
    }
  ],
  "keywords": ["e-commerce", "products", "catalog", "checkout", "commerce"],
  "compatibility": {
    "claude_desktop": ">=1.0.0",
    "platforms": ["darwin", "win32", "linux"],
    "python": ">=3.10"
  }
}

Five commands to a live URL.

No GitHub required — the Vercel CLI uploads the project directly from your machine.

  1. Install the Vercel CLI

    Once per machine. Requires Node.js 18+.

    npm i -g vercel
  2. Log in to Vercel

    Opens a browser tab. Sign in with GitHub, Google, or email. Free Hobby account is fine.

    vercel login
  3. Deploy from the project root

    Run this in the directory with server.py, requirements.txt, and vercel.json. First run asks you to confirm the project name and scope.

    vercel --prod
  4. Note the production URL

    The CLI prints a URL of the form https://your-project.vercel.app. Copy it.

    ✅  Production: https://commerce-mcp-yourname.vercel.app
  5. Your MCP endpoint is at /mcp

    Append /mcp to the production URL. Test the health endpoint first to confirm the function is alive.

    curl https://your-project.vercel.app/health
    # {"status":"ok","server":"Commerce MCP Server","version":"1.0.0"}
    
    # MCP endpoint (clients connect here)
    https://your-project.vercel.app/mcp
Free-tier reality check

Vercel Hobby gives one million function invocations and 100 GB bandwidth per month, with up to 300 seconds of function duration when Fluid Compute is enabled in the dashboard. That covers roughly 5,000 to 10,000 agent interactions per month at zero cost. Re-verify against Vercel's live limits page before relying on these numbers; cloud pricing shifts.

Wire it up. Ask. Watch it work.

Claude Desktop is the fastest way to verify your server end-to-end. The config file lives in a known location on each OS; you'll edit it once, fully quit Claude, and the next launch shows your server in the tools menu.

Config file paths

OSPath
macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json
Linux~/.config/Claude/claude_desktop_config.json

Option A — Local stdio (server runs on your machine)

Claude Desktop spawns your server as a subprocess. Best for fast iteration while you're still editing server.py. Replace /path/to/my-mcp-server with the absolute path to your project directory.

{
  "mcpServers": {
    "commerce-catalog": {
      "command": "uvicorn",
      "args": ["server:app", "--host", "127.0.0.1", "--port", "8001"],
      "env": {
        "API_KEY": "your-secret-key-here",
        "REQUIRE_AUTH": "false"
      },
      "cwd": "/path/to/my-mcp-server"
    }
  }
}

Option B — Remote HTTP via mcp-remote proxy

Claude Desktop does not natively accept a remote URL in most versions; mcp-remote is a small npm package that bridges Desktop's stdio expectation to your hosted HTTP endpoint. Use this once your server is deployed to Vercel.

{
  "mcpServers": {
    "commerce-catalog-remote": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://your-project.vercel.app/mcp",
        "--header",
        "Authorization: Bearer ${MCP_API_KEY}"
      ],
      "env": {
        "MCP_API_KEY": "your-secret-key"
      }
    }
  }
}

Restart and test

Save the config file. Fully quit Claude Desktop — Cmd+Q on macOS, not just closing the window — and relaunch. Look for the hammer icon in the chat input area; that confirms your server is connected. Then type:

Search my product catalog for anything under $50.

Claude will call search_products(keyword="", max_price=50) against your server and respond with the matching products from your PRODUCTS list. That is the entire loop. Once that works, your server is reachable, your tools are discoverable, and you've shipped a working MCP server.

Claude.ai web UI

If you have Claude Pro/Max/Team/Enterprise, you can skip the config file entirely. Navigate to Customize → Connectors → Add custom connector and paste your remote MCP URL. The web UI handles the connection natively ([Anthropic support](https://support.anthropic.com/en/articles/11175166-about-custom-integrations-using-remote-mcp)).

The exact errors and exact fixes.

These five account for the majority of first-build failures. Every error message below is copy-pasteable — search your logs for the literal string.

Error 1

Transport mismatch (stdio vs HTTP)

Error message · from ~/Library/Logs/Claude/mcp-server-YOURSERVER.log

[info]  [myserver] Server transport closed
[info]  [myserver] Server transport closed unexpectedly,
        this is likely due to the process exiting early.
[error] [myserver] Server disconnected.

Cause

You launched the server expecting HTTP but configured Claude Desktop to spawn it as a stdio subprocess (or vice versa). Claude Desktop only supports stdio for local subprocess servers — if your server starts an HTTP listener instead of reading from stdin, the client never receives the MCP handshake.

Fix

For Claude Desktop local subprocess use, run uvicorn against the FastAPI app (Option A in Section 7) or use mcp.run(transport="stdio") in a stdio-only entry point. For deployed servers, always use mcp-remote as the bridge (Option B). Never mix transports in one config block ([MCP transport spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports)).

Error 2

"Method not found" (-32601)

Error message

{"jsonrpc":"2.0","id":19,"error":{"code":-32601,"message":"Method not found"}}

Cause

The client called tools/list (or similar) before completing the initialization handshake, or the server is missing the notifications/initialized step. Most commonly seen when connecting to a legacy SSE-only server or when a custom client skips the handshake.

Fix

Enforce the correct order: initialize request → initialize response → notifications/initialized → then any other method. FastMCP handles this automatically — if you see this error from a FastMCP server, confirm your @mcp.tool() decorators run before mcp.run() or before the FastAPI mount ([Stack Overflow walkthrough](https://stackoverflow.com/questions/79550897/mcp-server-always-get-initialization-error)).

Error 3

JSON-RPC parse error (stdout pollution)

Error message · from Claude Desktop logs

Error: Could not parse message from server:
SyntaxError: Unexpected token S in JSON at position 0

Cause

In stdio mode, stdout is the JSON-RPC wire. Any print() in Python or console.log() in Node corrupts the stream — the client receives invalid JSON and the connection breaks immediately.

Fix

All debug output goes to stderr, never stdout. In Python:

# WRONG — corrupts the JSON-RPC stream
print("Server starting up...")

# CORRECT — all debug output goes to stderr
import sys
print("Server starting up...", file=sys.stderr)
logging.basicConfig(stream=sys.stderr)

Use npx mcp-stdio-guard -- python server.py during development to catch stdout pollution before Claude sees it ([Microsoft DAB MCP docs](https://learn.microsoft.com/en-us/azure/data-api-builder/mcp/stdio-transport)).

Error 4

Claude Desktop not detecting server

Error message · from ~/Library/Logs/Claude/mcp.log

Error: spawn npx ENOENT
[error] [commerce-catalog] Server disconnected.

Cause

The claude_desktop_config.json uses a relative path, has a JSON syntax error, or the binary (node, npx, python) is not on the PATH Claude Desktop inherits from macOS — which differs from your shell PATH.

Fix

Use absolute paths for the command. Symlink Homebrew binaries into /opt/homebrew/bin if installed keg-only. Or set PATH explicitly in the config's env key:

"env": {
  "PATH": "/Users/you/.nvm/versions/node/v23.3.0/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
}

Validate JSON syntax before saving — a missing comma silently prevents all servers from loading. Fully quit Claude (Cmd+Q on macOS) after every config change ([Nish Tahir's MCP setup notes](https://nishtahir.com/notes-on-setting-up-claude-desktop-mcp-servers/)).

Error 5

Pydantic / schema validation errors

Error message

{"modelMessage": "Parameter 'agent' must be of type undefined, got object"}

# or:

pydantic.v1.error_wrappers.ValidationError: 1 validation error for MyTool
  field_name
    value is not a valid integer (type=type_error.integer)

Cause

Tool annotations don't match what the client sent, or you used a Pydantic model as a top-level parameter (not supported by all clients).

Fix

Use primitive types (str, int, float, bool) or Optional[T] for tool parameters. Expand model fields into individual arguments rather than passing a whole model:

# WRONG — Pydantic model as top-level parameter
@mcp.tool()
def create_order(order: OrderModel) -> str: ...

# CORRECT — flat primitives
@mcp.tool()
def create_order(
    product_id: str,
    quantity: int,
    price: float,
    customer_email: str,
) -> str: ...

Use async def for tool handlers that make HTTP or DB calls — mixing sync code in an async server blocks the event loop ([Cursor forum thread](https://forum.cursor.com/t/issue-with-mcp-server-and-pydantic-model-object-as-tool-parameter-in-cursor/77110)).

Three production MCP servers already shipping.

Real code, real users, real revenue. Worth studying before you customize your own build.

Stripe Official MCP

Remote endpoint · OAuth · TypeScript

Stripe's official MCP server exposes their full API to AI agents — payment intents, customers, invoices, subscriptions, refunds, products, and prices — plus a knowledge-base search tool that queries Stripe's docs and support articles. It's available as a hosted remote endpoint at https://mcp.stripe.com with OAuth authentication, and as a local stdio server via npx -y @stripe/mcp@latest --api-key=sk_test_YOUR_KEY. The big lesson: Stripe separates read tools from write tools at the protocol level, gating destructive operations differently. Merchants connect once; agents call payment APIs without re-implementing Stripe in every client ([Stripe MCP docs](https://docs.stripe.com/mcp), stripe/ai on GitHub).

Shopify Community MCP

Storefront + Admin GraphQL · TypeScript

The community luckyfarnon/Shopify-MCP wraps Shopify's GraphQL Admin API. It exposes tools for products (CRUD plus variants, SEO, inventory), collections, blogs, articles, pages, and unified store search — all with cursor-based pagination so agents can browse large catalogs page by page. Quick start: npx shopify-mcp --accessToken=YOUR_TOKEN --domain=yourstore.myshopify.com. The pattern to steal: every list tool returns a cursor and pageInfo object, which means agents can paginate a 10,000-product catalog without overflowing context. GraphQL over REST is the right call for nested product/variant data.

WooCommerce MCP Server

Self-hosted · WordPress REST · Node.js

techspawn/woocommerce-mcp-server covers full WooCommerce store management via the WordPress REST API — products, orders, customers, shipping zones, taxes, coupons, payment gateways, and analytics reports. 50+ tools total. Compatible with Windows, macOS, Linux. The architecture lesson: full CRUD parity (every entity has get/create/update/delete) plus report tools (get_sales_report, get_stock_report) and metadata tools (get_product_meta) — when an MCP server matches the underlying API one-to-one, the agent can do anything the merchant can.

Starter to copy

For the fastest path from clone to deployed Stripe-connected server: vercel-labs/mcp-for-next.js-with-stripe. Next.js + @vercel/mcp-adapter + @stripe/agent-toolkit, pre-wired for Vercel Fluid Compute. Clone, set STRIPE_SECRET_KEY, vercel deploy. Done.

Three frameworks. Pick one.

FastMCP is the default recommendation in this build. If your stack or constraints push elsewhere, the other two are valid choices.

Framework Setup Complexity Deployment Target Community Size Best For
FastMCP (Python)
pip install "mcp[cli]"
Low — 10 lines of decorated Python; auto-generated schemas Vercel, Railway, Render, Fly.io, any ASGI host 22,600+ GitHub stars on jlowin/fastmcp; ~70% of all MCP servers Python founders building a server from scratch — fastest path to a working build
MCP SDK (raw)
Python or TypeScript
Medium — manage handlers, lifecycles, and schemas manually Any host that supports the language runtime Official Anthropic SDK; smaller direct user base — most devs use FastMCP on top Teams needing fine-grained control over the protocol (custom transports, advanced auth flows, edge cases)
Vercel AI SDK MCP
@vercel/mcp-adapter
Low — drop into an existing Next.js project as an API route Vercel (best); other Node.js hosts with Redis for SSE Maintained by Vercel Labs; growing fast with Next.js ecosystem adoption Teams already shipping Next.js apps — add MCP to a live product without spinning up a separate server

FastMCP wins for this build because it's Python-native, schema-free (type hints become the schema), and works identically on local stdio and remote Streamable HTTP transports. The raw SDK is correct when you're shipping a public-facing server with custom OAuth flows or non-standard transport bridges. The Vercel adapter is correct when your product is already a Next.js app — adding MCP becomes one more route file.

Eight questions every builder asks first.

Answers derived from the official MCP spec, real production servers, and the Anthropic engineering team's public guidance.

What's the difference between MCP tools and resources?

Resources are read-only data the host application injects into the model's context — application-controlled, like a product catalog the AI reads before answering. Tools are executable functions the model decides to call — model-controlled, like search_products or initiate_checkout. Resources feed context; tools take action.

Do I need a paid Vercel plan?

No. Vercel's Hobby (free) tier covers one million function invocations and 100 GB bandwidth per month, and Fluid Compute extends function duration up to 300 seconds on free. That handles 5,000 to 10,000 agent interactions per month at zero cost. Upgrade when you exceed that or when you need always-warm functions ([Vercel Limits](https://vercel.com/docs/limits)). Re-verify before relying on these figures; pricing shifts.

Can agents from other companies (GPT-4o, Gemini) use my MCP server?

Yes. MCP is an open protocol. ChatGPT supports remote MCP servers, Google's Gemini CLI supports stdio MCP, Mistral's Le Chat supports remote MCP, and 576+ clients are tracked by PulseMCP. A Streamable HTTP server like the one in this guide is reachable by every MCP-compatible client — one server, every client.

What's the difference between stdio and HTTP transport?

stdio runs your server as a local subprocess — the AI client spawns it and talks over stdin/stdout. Use for Claude Desktop local integrations. Streamable HTTP exposes your server at a URL — the current standard for remote and hosted deployments. The build in this guide uses Streamable HTTP because it deploys to Vercel. HTTP+SSE was deprecated in the 2025-03-26 spec revision and should not be used for new builds ([MCP transport docs](https://modelcontextprotocol.io/docs/concepts/transports)).

How do I add authentication?

Start with an API key via the X-API-Key header — the ApiKeyMiddleware in this build handles it. Set REQUIRE_AUTH=true and a strong API_KEY value before going to production. The MCP spec recommends OAuth 2.1 for public-facing servers; FastMCP includes a built-in JWTVerifier when you need it. API key first, OAuth when you list your server on a marketplace like PulseMCP or Smithery ([MCP authorization](https://modelcontextprotocol.io/docs/tutorials/security/authorization)).

What is stateless_http=True and when do I need it?

stateless_http=True tells FastMCP to treat every request as independent — no session state held between requests. It's required for serverless platforms like Vercel where function instances spin up and down between calls. All standard MCP clients work with stateless mode. Leave it off only if you're running a long-lived process on a dedicated host and need cross-request state (rare in practice — most MCP servers are functionally stateless).

How do I handle rate limiting?

Add slowapi to the FastAPI app. Default to 60 requests per minute per IP, or key by API key for stricter per-user limits. For a standalone FastMCP server (no FastAPI wrapper) use FastMCP 2.x's built-in RateLimitingMiddleware with a token-bucket pattern (rate, capacity). Both return HTTP 429 when limits are exceeded.

from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address, default_limits=["60/minute"])
app.state.limiter = limiter
Is FastMCP production-ready?

Yes. FastMCP powers an estimated 70% of MCP servers across all languages, is downloaded over a million times per day, and is bundled in the official Anthropic Python SDK. Stripe, Shopify community servers, and Vercel's templates all use it. It generates JSON-RPC handlers, input schemas, and lifecycle management automatically — you write decorated Python, FastMCP handles the protocol. 22,600+ stars on GitHub as of May 2026.

The infrastructure layer is being built right now.

Picks-and-shovels, literally.

The MCP server you deploy this week is reachable by every AI agent shipping in 2026 — Claude, ChatGPT, Gemini, Cursor, Windsurf, and the 576+ clients already tracked. Every major model vendor has standardized on the same protocol. The integration layer that took five years and a thousand custom partnerships to build for REST APIs in the 2010s is being built in months for agents in the 2020s. The builders who ship MCP servers now own the on-ramps every agent uses to reach the rest of the internet. This is the moment.

Read the AgentMall 30-Day Roadmap →

Sources referenced on this page: modelcontextprotocol.io · MCP Spec 2025-11-25 · Official Python SDK · jlowin/fastmcp · Vercel MCP Deploy Docs · Stripe MCP · Shopify Community MCP · WooCommerce MCP · vercel-labs/mcp-for-next.js-with-stripe · PulseMCP Client Tracker. Re-verify versions, pricing, and limits against the live primary sources before deploying — the MCP ecosystem moves fast.

More Spokes Coming

Get notified when the
next spoke drops.

New AgentMall spokes ship every few weeks. Drop your email and we'll let you know — no pitch, no sequence, just the update.