Build MCP servers in Python with FastMCP. Define tools / resources / prompts, build the server, test locally, deploy to FastMCP Cloud or Docker. Use whenever the user mentions building an MCP server, exposing tools to LLMs, FastMCP, building a Claude integration, or troubleshooting FastMCP module-level server, storage, lifespan, middleware, OAuth, or deployment errors.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Build MCP servers in Python with FastMCP. Define tools / resources / prompts, build the server, test locally, deploy to FastMCP Cloud or Docker. Use whenever the user mentions building an MCP server, exposing tools to LLMs, FastMCP, building a Claude integration, or troubleshooting FastMCP module-level server, storage, lifespan, middleware, OAuth, or deployment errors.
compatibility
claude-code-only
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()asyncdefsearch_customers() -> :
() -> :
__name__ == :
mcp.run()
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
"__main__"
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
asyncdeftest_server(server_path):
asyncwith 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 toolif tools:
result = await client.call_tool(tools[0].name, {})
print(f"Tool result: {result}")
# Read first resourceif 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.
FastMCP uses type annotations to generate tool schemas:
@mcp.tool()asyncdefsearch(
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()asyncdefget_data(id: str) -> str:
try:
result = await fetch_data(id)
return json.dumps(result)
except NotFoundError:
returnf"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()asyncdefproduction_tool(data: str) -> dict:
ifnot 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:
asyncdefretry_with_backoff(func, max_retries=3, initial_delay=1.0):
for attempt inrange(max_retries):
try:
returnawait 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()asyncdeftool_with_context(param: str, context: Context) -> dict:
# Context parameter MUST have type hintpass
Progress Tracking
@mcp.tool()asyncdeflong_task(items: list[str], context: Context) -> str:
for i, item inenumerate(items):
await context.report_progress(i + 1, len(items), f"Processing {item}")
await process(item)
return"Done"
Sampling (LLM from within tools)
@mcp.tool()asyncdefsummarise(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