mcp-builder

Build and deploy MCP servers in Python using FastMCP with tools, resources, and prompts. Scaffolds a working Python MCP server from a description; supports tools (callable functions), resources (readable data), and prompts (reusable templates) Includes local testing modes (direct run, dev mode with auto-reload, HTTP transport) and MCP Inspector integration Deploys to FastMCP Cloud, Docker, or Cloudflare Workers; pre-deploy checklist catches common issues like missing module-level server instances and hardcoded secrets Provides 30+ documented error patterns, CLI command reference, production patterns (auth, middleware, storage), and async test templates

INSTALLATION
npx skills add https://github.com/jezweb/claude-skills --skill mcp-builder
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

MCP Builder

Build a working MCP server from a description of the tools you need. Produces a deployable Python server using FastMCP.

Workflow

Step 1: Define What to Expose

Ask what the server needs to provide:

  • Tools -- Functions Claude can call (API wrappers, calculations, file operations)
  • Resources -- Data Claude can read (database records, config, documents)
  • Prompts -- Reusable prompt templates with parameters

A brief like "MCP server for querying our customer database" is enough.

Step 2: Scaffold the Server

pip install fastmcp

Create the server file. The server instance MUST be at module level:

from fastmcp import FastMCP

# MUST be at module level for FastMCP Cloud

mcp = FastMCP("My Server")

@mcp.tool()

async def search_customers(query: str) -> str:

    """Search customers by name or email."""

    # Implementation here

    return f"Found customers matching: {query}"

@mcp.resource("customers://{customer_id}")

async def get_customer(customer_id: str) -> str:

    """Get customer details by ID."""

    return f"Customer {customer_id} details"

if __name__ == "__main__":

    mcp.run()

Step 3: Add Companion CLI Scripts (Optional)

For Claude Code terminal use, add scripts alongside the MCP server:

my-mcp-server/

├── src/index.ts          # MCP server (for Claude.ai)

├── scripts/

│   ├── search.ts         # CLI version of search tool

│   └── _shared.ts        # Shared auth/config

├── SCRIPTS.md            # Documents available scripts

└── package.json

CLI scripts provide file I/O, batch processing, and richer output that MCP can't.

See assets/SCRIPTS-TEMPLATE.md and assets/script-template.ts for TypeScript templates.

Step 4: Test Locally

Quick test -- run directly:

python server.py

Dev mode with inspector UI (recommended):

fastmcp dev server.py

# Opens inspector at http://localhost:5173

# Hot reload, detailed logging, tool/resource inspection

HTTP mode for remote clients:

python server.py --transport http --port 8000

Automated test script using FastMCP Client:

import asyncio

from fastmcp import Client

async def test_server(server_path):

    async with Client(server_path) as client:

        # List everything

        tools = await client.list_tools()

        resources = await client.list_resources()

        prompts = await client.list_prompts()

        print(f"Tools: {[t.name for t in tools]}")

        print(f"Resources: {[r.uri for r in resources]}")

        print(f"Prompts: {[p.name for p in prompts]}")

        # Call first tool

        if tools:

            result = await client.call_tool(tools[0].name, {})

            print(f"Tool result: {result}")

        # Read first resource

        if resources:

            data = await client.read_resource(resources[0].uri)

            print(f"Resource data: {data}")

asyncio.run(test_server("server.py"))

Step 5: Pre-Deploy Checklist

Run these checks before deploying. All required checks must pass.

Required (will cause deploy failure):

  • Server file exists
  • Python syntax valid: python3 -m py_compile server.py
  • Module-level server object (not inside a function):
grep -q "^mcp = FastMCP\|^server = FastMCP\|^app = FastMCP" server.py
  • requirements.txt exists with PyPI packages only (no git+, -e, .whl, .tar.gz)
  • No hardcoded secrets (check for api_key = "..." patterns excluding os.getenv/os.environ)

Advisory (warnings):

  • fastmcp listed in requirements.txt
  • .gitignore includes .env
  • No circular imports
  • Git repository initialised with remote
  • Server can load: timeout 5 fastmcp inspect server.py

Step 6: Deploy

FastMCP Cloud (simplest):

git add . && git commit -m "Ready for deployment"

git push -u origin main

# Visit https://fastmcp.cloud, connect repo, add env vars, deploy

# URL: https://your-project.fastmcp.app/mcp

Cloud requirements:

  • Module-level server object named mcp, server, or app
  • PyPI dependencies only in requirements.txt
  • Public GitHub repository
  • Environment variables for secrets (no hardcoded values)
  • Auto-deploys on push to main, PR preview deployments

Docker (self-hosted):

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["python", "server.py", "--transport", "http", "--port", "8000"]

Cloudflare Workers (edge):

See the cloudflare-worker-builder skill for Workers-based MCP servers.

Critical Patterns

Module-Level Server Instance

FastMCP Cloud requires the server instance at module level:

# CORRECT

mcp = FastMCP("My Server")

@mcp.tool()

def my_tool(): ...

# WRONG -- Cloud can't find the server

def create_server():

    mcp = FastMCP("My Server")

    return mcp

# FIX for factory pattern -- export at module level

def create_server() -> FastMCP:

    mcp = FastMCP("server")

    return mcp

mcp = create_server()

Type Annotations Required

FastMCP uses type annotations to generate tool schemas:

@mcp.tool()

async def search(

    query: str,           # Required parameter

    limit: int = 10,      # Optional with default

    tags: list[str] = []  # Complex types supported

) -> str:

    """Docstring becomes the tool description."""

    ...

Error Handling

Return errors as strings, don't raise exceptions:

@mcp.tool()

async def get_data(id: str) -> str:

    try:

        result = await fetch_data(id)

        return json.dumps(result)

    except NotFoundError:

        return f"Error: No data found for ID {id}"

Cloud-Ready Server Pattern

import os

from fastmcp import FastMCP

mcp = FastMCP("production-server")

API_KEY = os.getenv("API_KEY")

@mcp.tool()

async def production_tool(data: str) -> dict:

    if not API_KEY:

        return {"error": "API_KEY not configured"}

    return {"status": "success", "data": data}

if __name__ == "__main__":

    mcp.run()

Common Errors and Fixes

These are the errors you will hit. Fix them before deploying.

Error

Cause

Fix

RuntimeError: No server object found at module level

Server inside a function

Export mcp = FastMCP(...) at module level

RuntimeError: no running event loop

Missing async/await

Use async def for async operations

TypeError: missing required argument 'context'

Context not type-hinted

Add context: Context with type hint

ValueError: Invalid resource URI

Missing URI scheme

Use data://, file://, info://, api://

Resource template parameter mismatch

Name mismatch

user://{user_id} needs def get_user(user_id: str)

Pydantic validation error

Wrong type hints

Ensure hints match actual data types

Transport mismatch

Client/server protocol differ

Match both to stdio or both to http

Import errors with editable package

Package not installed

pip install -e . or add to PYTHONPATH

DeprecationWarning: mcp.settings

Old API

Use os.getenv() instead

Port already in use

Stale process

lsof -ti:8000 | xargs kill -9

Schema generation failure

Non-JSON types

Use JSON-compatible types (no NumPy arrays)

JSON serialization error

datetime/bytes in response

Convert to .isoformat() or string

Circular import

Factory in __init__.py

Use direct imports, avoid factory pattern

Python 3.12+ datetime warning

datetime.utcnow() deprecated

Use datetime.now(timezone.utc)

Import-time execution

Async resource at module level

Use lazy init pattern

Production Patterns

Self-Contained Server

Keep all utilities in one file to avoid circular imports:

from fastmcp import FastMCP

import os

mcp = FastMCP("my-server")

# Config

class Config:

    API_KEY = os.getenv("API_KEY", "")

    BASE_URL = os.getenv("BASE_URL", "https://api.example.com")

# Helpers

def format_success(data): return {"status": "success", "data": data}

def format_error(msg): return {"status": "error", "message": msg}

@mcp.tool()

async def my_tool(query: str) -> dict:

    if not Config.API_KEY:

        return format_error("API_KEY not configured")

    return format_success({"query": query})

Lazy Initialisation

Don't create async resources at module level. Initialise on first use:

_db = None

async def get_db():

    global _db

    if _db is None:

        _db = await create_connection(Config.DB_URL)

    return _db

Health Check Resource

@mcp.resource("health://status")

async def health_check() -> dict:

    return {

        "status": "healthy",

        "version": "1.0.0",

        "checks": {

            "api": "connected",

            "database": "connected"

        }

    }

Connection Pooling

import httpx

_client = None

def get_client() -> httpx.AsyncClient:

    global _client

    if _client is None:

        _client = httpx.AsyncClient(

            base_url=Config.BASE_URL,

            headers={"Authorization": f"Bearer {Config.API_KEY}"},

            limits=httpx.Limits(max_connections=20, max_keepalive_connections=5),

            timeout=30.0

        )

    return _client

Retry with Backoff

async def retry_with_backoff(func, max_retries=3, initial_delay=1.0):

    for attempt in range(max_retries):

        try:

            return await func()

        except Exception as e:

            if attempt == max_retries - 1:

                raise

            delay = initial_delay * (2 ** attempt)

            await asyncio.sleep(delay)

Context Features (Advanced)

Context Injection

from fastmcp import Context

@mcp.tool()

async def tool_with_context(param: str, context: Context) -> dict:

    # Context parameter MUST have type hint

    pass

Progress Tracking

@mcp.tool()

async def long_task(items: list[str], context: Context) -> str:

    for i, item in enumerate(items):

        await context.report_progress(i + 1, len(items), f"Processing {item}")

        await process(item)

    return "Done"

Sampling (LLM from within tools)

@mcp.tool()

async def summarise(text: str, context: Context) -> str:

    result = await context.request_sampling(

        messages=[{"role": "user", "content": f"Summarise: {text}"}],

        max_tokens=200

    )

    return result

CLI Quick Reference

fastmcp dev server.py              # Dev mode with inspector UI

fastmcp run server.py              # Run (stdio)

fastmcp run server.py --transport http --port 8000  # Run (HTTP)

fastmcp inspect server.py          # Inspect without running

fastmcp install server.py          # Install to Claude Desktop

fastmcp deploy server.py --name my-server  # Deploy to Cloud

Environment variables: FASTMCP_LOG_LEVEL (DEBUG/INFO/WARNING/ERROR), FASTMCP_ENV (development/staging/production).

Integration Patterns (Optional)

For specific integration approaches, see references/integration-patterns.md:

  • Manual API -- httpx.AsyncClient with reusable client
  • OpenAPI auto-generation -- FastMCP.from_openapi(spec, client, route_maps=[...])
  • FastAPI conversion -- FastMCP.from_fastapi(app)

Asset Files

  • assets/basic-server.py -- Minimal FastMCP server template
  • assets/self-contained-server.py -- Server with storage and middleware
  • assets/tools-examples.py -- Tool patterns and type annotations
  • assets/resources-examples.py -- Resource URI patterns
  • assets/prompts-examples.py -- Prompt template patterns
  • assets/client-example.py -- MCP client usage
  • assets/SCRIPTS-TEMPLATE.md -- CLI companion docs template
  • assets/script-template.ts -- TypeScript CLI script template
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card