
Mcp Builder
- 1.2k installs
- 946 repo stars
- Updated July 2, 2026
- jezweb/claude-skills
mcp- provides documented workflows for Build MCP servers in Python with FastMCP. Define tools / resources / prompts, build the server, test locally, deploy to FastMCP Cloud or Docker. Use whenever th
About
The mcp- skill 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 MCP developers 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 bash pip install fastmcp Create the server file The server instance MUST be at module level python from fastmcp import FastMCP MUST be at module level for FastMCP Cloud mcp FastMCP My Server mcp tool async def
- **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
- Server file exists
- Python syntax valid: `python3 -m py_compile server.py`
Mcp Builder by the numbers
- 1,218 all-time installs (skills.sh)
- +20 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #900 of 16,565 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
mcp-builder capabilities & compatibility
- Capabilities
- **tools** functions claude can call (api wrap · **resources** data claude can read (database · **prompts** reusable prompt templates with pa · server file exists · python syntax valid: `python3 m py_compile serv
- Use cases
- documentation
What mcp-builder says it does
Produces a deployable Python server using FastMCP.
npx skills add https://github.com/jezweb/claude-skills --skill mcp-builderAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.2k |
|---|---|
| repo stars | ★ 946 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 2, 2026 |
| Repository | jezweb/claude-skills ↗ |
How do I use mcp- for the task described in its SKILL.md triggers?
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,.
Who is it for?
Teams invoking mcp- when the user request matches documented triggers and prerequisites.
Skip if: Skip when cached docs are missing, the request is a negative trigger, or another sibling skill owns the workflow.
When should I use this skill?
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 LL
What you get
Step-by-step guidance grounded in mcp- documentation and reference files.
- FastMCP server module
- Pooled API client with retries
Files
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 fastmcpCreate 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.jsonCLI 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.pyDev mode with inspector UI (recommended):
fastmcp dev server.py
# Opens inspector at http://localhost:5173
# Hot reload, detailed logging, tool/resource inspectionHTTP mode for remote clients:
python server.py --transport http --port 8000Automated 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):
1. Server file exists 2. Python syntax valid: python3 -m py_compile server.py 3. Module-level server object (not inside a function):
grep -q "^mcp = FastMCP\|^server = FastMCP\|^app = FastMCP" server.py4. requirements.txt exists with PyPI packages only (no git+, -e, .whl, .tar.gz) 5. No hardcoded secrets (check for api_key = "..." patterns excluding os.getenv/os.environ)
Advisory (warnings):
6. fastmcp listed in requirements.txt 7. .gitignore includes .env 8. No circular imports 9. Git repository initialised with remote 10. 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/mcpCloud requirements:
- Module-level server object named
mcp,server, orapp - 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 \ |
| 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 _dbHealth 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 _clientRetry 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
passProgress 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 CloudEnvironment 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.AsyncClientwith 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 templateassets/self-contained-server.py-- Server with storage and middlewareassets/tools-examples.py-- Tool patterns and type annotationsassets/resources-examples.py-- Resource URI patternsassets/prompts-examples.py-- Prompt template patternsassets/client-example.py-- MCP client usageassets/SCRIPTS-TEMPLATE.md-- CLI companion docs templateassets/script-template.ts-- TypeScript CLI script template
"""
FastMCP API Client Pattern
===========================
Manual API integration with connection pooling, caching, and retry logic.
"""
from fastmcp import FastMCP
import httpx
import os
import time
import asyncio
from typing import Optional, Any, Dict
from datetime import datetime
from dotenv import load_dotenv
load_dotenv()
mcp = FastMCP("API Client Pattern")
# ============================================================================
# Configuration
# ============================================================================
class Config:
"""API configuration from environment."""
API_BASE_URL = os.getenv("API_BASE_URL", "https://api.example.com")
API_KEY = os.getenv("API_KEY", "")
API_TIMEOUT = int(os.getenv("API_TIMEOUT", "30"))
CACHE_TTL = int(os.getenv("CACHE_TTL", "300")) # 5 minutes
MAX_RETRIES = int(os.getenv("MAX_RETRIES", "3"))
# ============================================================================
# API Client with Connection Pooling
# ============================================================================
class APIClient:
"""Singleton API client with connection pooling."""
_instance: Optional[httpx.AsyncClient] = None
@classmethod
async def get_client(cls) -> httpx.AsyncClient:
"""Get or create the shared HTTP client."""
if cls._instance is None:
cls._instance = httpx.AsyncClient(
base_url=Config.API_BASE_URL,
headers={
"Authorization": f"Bearer {Config.API_KEY}",
"Content-Type": "application/json",
"User-Agent": "FastMCP-Client/1.0"
},
timeout=httpx.Timeout(Config.API_TIMEOUT),
limits=httpx.Limits(
max_keepalive_connections=5,
max_connections=10
)
)
return cls._instance
@classmethod
async def cleanup(cls):
"""Cleanup the HTTP client."""
if cls._instance:
await cls._instance.aclose()
cls._instance = None
# ============================================================================
# Cache Implementation
# ============================================================================
class SimpleCache:
"""Time-based cache for API responses."""
def __init__(self, ttl: int = 300):
self.ttl = ttl
self.cache: Dict[str, Any] = {}
self.timestamps: Dict[str, float] = {}
def get(self, key: str) -> Optional[Any]:
"""Get cached value if not expired."""
if key in self.cache:
if time.time() - self.timestamps[key] < self.ttl:
return self.cache[key]
else:
# Expired, remove it
del self.cache[key]
del self.timestamps[key]
return None
def set(self, key: str, value: Any):
"""Set cache value with timestamp."""
self.cache[key] = value
self.timestamps[key] = time.time()
def invalidate(self, pattern: Optional[str] = None):
"""Invalidate cache entries."""
if pattern:
keys_to_delete = [k for k in self.cache if pattern in k]
for key in keys_to_delete:
del self.cache[key]
del self.timestamps[key]
else:
self.cache.clear()
self.timestamps.clear()
# Global cache instance
cache = SimpleCache(ttl=Config.CACHE_TTL)
# ============================================================================
# Retry Logic with Exponential Backoff
# ============================================================================
async def retry_with_backoff(
func,
max_retries: int = 3,
initial_delay: float = 1.0,
exponential_base: float = 2.0
):
"""Retry function with exponential backoff."""
delay = initial_delay
last_exception = None
for attempt in range(max_retries):
try:
return await func()
except (httpx.TimeoutException, httpx.NetworkError) as e:
last_exception = e
if attempt < max_retries - 1:
print(f"Attempt {attempt + 1} failed, retrying in {delay}s...")
await asyncio.sleep(delay)
delay *= exponential_base
except httpx.HTTPStatusError as e:
# Don't retry client errors (4xx)
if 400 <= e.response.status_code < 500:
raise
last_exception = e
if attempt < max_retries - 1:
await asyncio.sleep(delay)
delay *= exponential_base
raise last_exception
# ============================================================================
# API Tools
# ============================================================================
@mcp.tool()
async def api_get(
endpoint: str,
use_cache: bool = True
) -> dict:
"""
Make a GET request to the API.
Args:
endpoint: API endpoint path (e.g., "/users/123")
use_cache: Whether to use cached response if available
Returns:
API response data or error
"""
cache_key = f"GET:{endpoint}"
# Check cache
if use_cache:
cached = cache.get(cache_key)
if cached:
return {
"success": True,
"data": cached,
"from_cache": True,
"timestamp": datetime.now().isoformat()
}
# Make request with retry
async def make_request():
client = await APIClient.get_client()
response = await client.get(endpoint)
response.raise_for_status()
return response.json()
try:
data = await retry_with_backoff(make_request, max_retries=Config.MAX_RETRIES)
# Cache successful response
if use_cache:
cache.set(cache_key, data)
return {
"success": True,
"data": data,
"from_cache": False,
"timestamp": datetime.now().isoformat()
}
except httpx.HTTPStatusError as e:
return {
"success": False,
"error": f"HTTP {e.response.status_code}",
"message": e.response.text,
"endpoint": endpoint
}
except Exception as e:
return {
"success": False,
"error": str(e),
"endpoint": endpoint
}
@mcp.tool()
async def api_post(
endpoint: str,
data: dict,
invalidate_cache: bool = True
) -> dict:
"""
Make a POST request to the API.
Args:
endpoint: API endpoint path
data: Request body data
invalidate_cache: Whether to invalidate related cache entries
Returns:
API response or error
"""
async def make_request():
client = await APIClient.get_client()
response = await client.post(endpoint, json=data)
response.raise_for_status()
return response.json()
try:
result = await retry_with_backoff(make_request, max_retries=Config.MAX_RETRIES)
# Invalidate cache for related endpoints
if invalidate_cache:
cache.invalidate(endpoint.split('/')[1] if '/' in endpoint else endpoint)
return {
"success": True,
"data": result,
"timestamp": datetime.now().isoformat()
}
except httpx.HTTPStatusError as e:
return {
"success": False,
"error": f"HTTP {e.response.status_code}",
"message": e.response.text
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
@mcp.tool()
async def api_put(
endpoint: str,
data: dict,
invalidate_cache: bool = True
) -> dict:
"""Make a PUT request to the API."""
async def make_request():
client = await APIClient.get_client()
response = await client.put(endpoint, json=data)
response.raise_for_status()
return response.json()
try:
result = await retry_with_backoff(make_request)
if invalidate_cache:
cache.invalidate(endpoint)
return {"success": True, "data": result}
except Exception as e:
return {"success": False, "error": str(e)}
@mcp.tool()
async def api_delete(
endpoint: str,
invalidate_cache: bool = True
) -> dict:
"""Make a DELETE request to the API."""
async def make_request():
client = await APIClient.get_client()
response = await client.delete(endpoint)
response.raise_for_status()
return response.status_code
try:
status = await retry_with_backoff(make_request)
if invalidate_cache:
cache.invalidate(endpoint)
return {
"success": True,
"status_code": status,
"deleted": True
}
except Exception as e:
return {"success": False, "error": str(e)}
@mcp.tool()
async def batch_api_requests(
endpoints: list[str],
use_cache: bool = True
) -> dict:
"""
Make multiple GET requests in parallel.
Args:
endpoints: List of endpoint paths
use_cache: Whether to use cache
Returns:
Batch results with successes and failures
"""
async def fetch_one(endpoint: str):
return await api_get(endpoint, use_cache=use_cache)
results = await asyncio.gather(*[fetch_one(ep) for ep in endpoints])
successful = [r for r in results if r.get("success")]
failed = [r for r in results if not r.get("success")]
return {
"total": len(endpoints),
"successful": len(successful),
"failed": len(failed),
"results": results
}
@mcp.tool()
def clear_cache(pattern: Optional[str] = None) -> dict:
"""
Clear API response cache.
Args:
pattern: Optional pattern to match cache keys (clears all if not provided)
Returns:
Cache clear status
"""
try:
cache.invalidate(pattern)
return {
"success": True,
"message": f"Cache cleared{f' for pattern: {pattern}' if pattern else ''}"
}
except Exception as e:
return {"success": False, "error": str(e)}
# ============================================================================
# Resources
# ============================================================================
@mcp.resource("info://api-status")
async def api_status() -> dict:
"""Check API connectivity and status."""
try:
client = await APIClient.get_client()
response = await client.get("/health", timeout=5)
return {
"api_reachable": True,
"status_code": response.status_code,
"healthy": response.status_code == 200,
"timestamp": datetime.now().isoformat()
}
except Exception as e:
return {
"api_reachable": False,
"error": str(e),
"timestamp": datetime.now().isoformat()
}
@mcp.resource("info://cache-stats")
def cache_statistics() -> dict:
"""Get cache statistics."""
return {
"total_entries": len(cache.cache),
"ttl_seconds": cache.ttl,
"entries": list(cache.cache.keys())
}
# ============================================================================
# Main
# ============================================================================
if __name__ == "__main__":
try:
mcp.run()
finally:
# Cleanup on exit
import asyncio
asyncio.run(APIClient.cleanup())
"""
Basic FastMCP Server Template
==============================
A minimal working FastMCP server with essential patterns.
"""
from fastmcp import FastMCP
import os
# Load environment variables (optional)
from dotenv import load_dotenv
load_dotenv()
# ============================================================================
# CRITICAL: Server must be at module level for FastMCP Cloud
# ============================================================================
mcp = FastMCP(
name="My Basic Server",
instructions="""
This is a basic MCP server demonstrating core patterns.
Available tools:
- greet: Say hello to someone
- calculate: Perform basic math operations
Available resources:
- info://status: Server status information
"""
)
# ============================================================================
# Tools
# ============================================================================
@mcp.tool()
def greet(name: str, greeting: str = "Hello") -> str:
"""
Greet someone by name.
Args:
name: The name of the person to greet
greeting: The greeting to use (default: "Hello")
Returns:
A greeting message
"""
return f"{greeting}, {name}!"
@mcp.tool()
async def calculate(operation: str, a: float, b: float) -> dict:
"""
Perform a mathematical operation.
Args:
operation: The operation to perform (add, subtract, multiply, divide)
a: First number
b: Second number
Returns:
Dictionary with the result or error message
"""
operations = {
"add": lambda x, y: x + y,
"subtract": lambda x, y: x - y,
"multiply": lambda x, y: x * y,
"divide": lambda x, y: x / y if y != 0 else None
}
if operation not in operations:
return {
"error": f"Unknown operation: {operation}",
"valid_operations": list(operations.keys())
}
result = operations[operation](a, b)
if result is None:
return {"error": "Division by zero"}
return {
"operation": operation,
"a": a,
"b": b,
"result": result
}
# ============================================================================
# Resources
# ============================================================================
@mcp.resource("info://status")
def server_status() -> dict:
"""Get current server status."""
from datetime import datetime
return {
"server": "My Basic Server",
"status": "operational",
"timestamp": datetime.now().isoformat(),
"version": "1.0.0"
}
# ============================================================================
# Main Execution
# ============================================================================
if __name__ == "__main__":
# Run with stdio transport (default)
mcp.run()
# Alternative: HTTP transport for testing
# mcp.run(transport="http", port=8000)
"""
FastMCP Client Example
======================
Testing MCP servers with the FastMCP Client.
"""
import asyncio
from fastmcp import Client
from typing import Optional
async def test_basic_server():
"""Test a basic MCP server."""
print("\n=== Testing Basic Server ===\n")
async with Client("basic-server.py") as client:
# List available tools
tools = await client.list_tools()
print(f"Available tools: {len(tools)}")
for tool in tools:
print(f" - {tool.name}: {tool.description}")
# Call a tool
print("\nCalling 'greet' tool...")
result = await client.call_tool("greet", {"name": "World"})
print(f"Result: {result.data}")
# Call another tool
print("\nCalling 'calculate' tool...")
result = await client.call_tool("calculate", {
"operation": "add",
"a": 10,
"b": 5
})
print(f"Result: {result.data}")
# List resources
print("\nAvailable resources:")
resources = await client.list_resources()
for resource in resources:
print(f" - {resource.uri}: {resource.description}")
# Read a resource
print("\nReading 'info://status' resource...")
status = await client.read_resource("info://status")
print(f"Status: {status}")
async def test_tools_examples():
"""Test the tools examples server."""
print("\n=== Testing Tools Examples ===\n")
async with Client("tools-examples.py") as client:
# Test sync tool
print("Testing sync tool...")
result = await client.call_tool("simple_sync_tool", {"text": "hello"})
print(f"Sync result: {result.data}")
# Test async tool
print("\nTesting async tool...")
result = await client.call_tool("simple_async_tool", {"text": "WORLD"})
print(f"Async result: {result.data}")
# Test validated search
print("\nTesting validated search...")
result = await client.call_tool("validated_search", {
"params": {
"query": "python",
"limit": 5
}
})
print(f"Search result: {result.data}")
# Test batch processing
print("\nTesting batch process...")
result = await client.call_tool("batch_process", {
"items": ["item1", "item2", "item3"]
})
print(f"Batch result: {result.data}")
async def test_resources_examples():
"""Test the resources examples server."""
print("\n=== Testing Resources Examples ===\n")
async with Client("resources-examples.py") as client:
# List all resources
resources = await client.list_resources()
print(f"Total resources: {len(resources)}")
# Test static resource
print("\nReading static resource...")
config = await client.read_resource("data://config")
print(f"Config: {config}")
# Test dynamic resource
print("\nReading dynamic resource...")
status = await client.read_resource("info://status")
print(f"Status: {status}")
# Test resource template
print("\nReading resource template...")
profile = await client.read_resource("user://123/profile")
print(f"User profile: {profile}")
async def test_with_error_handling():
"""Test server with comprehensive error handling."""
print("\n=== Testing with Error Handling ===\n")
async with Client("error-handling.py") as client:
# Test successful operation
print("Testing successful operation...")
try:
result = await client.call_tool("divide_numbers", {
"a": 10,
"b": 2
})
print(f"Success: {result.data}")
except Exception as e:
print(f"Error: {e}")
# Test error case
print("\nTesting error case (division by zero)...")
try:
result = await client.call_tool("divide_numbers", {
"a": 10,
"b": 0
})
print(f"Result: {result.data}")
except Exception as e:
print(f"Error: {e}")
# Test validation error
print("\nTesting validation error...")
try:
result = await client.call_tool("validated_operation", {
"data": ""
})
print(f"Result: {result.data}")
except Exception as e:
print(f"Error: {e}")
async def test_http_server():
"""Test server running on HTTP transport."""
print("\n=== Testing HTTP Server ===\n")
# Note: Server must be running on http://localhost:8000
# Start with: python server.py --transport http --port 8000
try:
async with Client("http://localhost:8000/mcp") as client:
print("Connected to HTTP server")
tools = await client.list_tools()
print(f"Available tools: {len(tools)}")
if tools:
result = await client.call_tool(tools[0].name, {})
print(f"Tool result: {result.data}")
except Exception as e:
print(f"Could not connect to HTTP server: {e}")
print("Make sure server is running with: python server.py --transport http --port 8000")
async def test_with_handlers():
"""Test server with client handlers (elicitation, progress, sampling)."""
print("\n=== Testing with Handlers ===\n")
# Define handlers
async def elicitation_handler(message: str, response_type: type, context: dict):
"""Handle elicitation requests."""
print(f"\n[ELICIT] {message}")
return input("Your response: ")
async def progress_handler(progress: float, total: Optional[float], message: Optional[str]):
"""Handle progress updates."""
if total:
pct = (progress / total) * 100
print(f"\r[PROGRESS] {pct:.1f}% - {message}", end="", flush=True)
else:
print(f"\n[PROGRESS] {message}")
async def sampling_handler(messages, params, context):
"""Handle sampling requests (LLM completions)."""
print(f"\n[SAMPLE] LLM request with {len(messages)} messages")
# In production, call actual LLM
return {
"content": "Mock LLM response",
"model": params.get("model", "mock"),
"usage": {"tokens": 100}
}
# Create client with handlers
async with Client(
"server.py",
elicitation_handler=elicitation_handler,
progress_handler=progress_handler,
sampling_handler=sampling_handler
) as client:
print("Client created with handlers")
# Test tools that use handlers
# Note: Requires server to have tools using context.request_elicitation, etc.
tools = await client.list_tools()
print(f"Available tools: {len(tools)}")
async def comprehensive_test():
"""Run comprehensive test suite."""
print("=" * 60)
print("FastMCP Client Test Suite")
print("=" * 60)
tests = [
("Basic Server", test_basic_server),
("Tools Examples", test_tools_examples),
("Resources Examples", test_resources_examples),
("Error Handling", test_with_error_handling),
# ("HTTP Server", test_http_server), # Uncomment if HTTP server is running
# ("With Handlers", test_with_handlers), # Uncomment if server supports handlers
]
for test_name, test_func in tests:
try:
await test_func()
except Exception as e:
print(f"\n❌ {test_name} failed: {e}")
else:
print(f"\n✅ {test_name} passed")
print("\n" + "-" * 60)
print("\nTest suite completed!")
async def interactive_client():
"""Interactive client for manual testing."""
print("\n=== Interactive FastMCP Client ===\n")
server_path = input("Enter server path or URL: ").strip()
if not server_path:
server_path = "basic-server.py"
print(f"Using default: {server_path}")
async with Client(server_path) as client:
print(f"\n✅ Connected to: {server_path}\n")
while True:
print("\nOptions:")
print("1. List tools")
print("2. List resources")
print("3. Call tool")
print("4. Read resource")
print("5. Exit")
choice = input("\nChoice: ").strip()
if choice == "1":
tools = await client.list_tools()
print(f"\n📋 Available tools ({len(tools)}):")
for i, tool in enumerate(tools, 1):
print(f" {i}. {tool.name}")
print(f" {tool.description}")
elif choice == "2":
resources = await client.list_resources()
print(f"\n📋 Available resources ({len(resources)}):")
for i, resource in enumerate(resources, 1):
print(f" {i}. {resource.uri}")
print(f" {resource.description}")
elif choice == "3":
tool_name = input("Tool name: ").strip()
print("Arguments (as JSON): ")
import json
try:
args = json.loads(input().strip())
result = await client.call_tool(tool_name, args)
print(f"\n✅ Result: {result.data}")
except Exception as e:
print(f"\n❌ Error: {e}")
elif choice == "4":
uri = input("Resource URI: ").strip()
try:
data = await client.read_resource(uri)
print(f"\n✅ Data: {data}")
except Exception as e:
print(f"\n❌ Error: {e}")
elif choice == "5":
print("\nGoodbye!")
break
else:
print("Invalid choice")
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == "--interactive":
asyncio.run(interactive_client())
else:
asyncio.run(comprehensive_test())
"""
FastMCP Error Handling Template
================================
Comprehensive error handling patterns with structured responses and retry logic.
"""
from fastmcp import FastMCP
import asyncio
import httpx
from enum import Enum
from typing import Dict, Any, Optional
from datetime import datetime
mcp = FastMCP("Error Handling Examples")
# ============================================================================
# Error Code Enum
# ============================================================================
class ErrorCode(Enum):
"""Standard error codes."""
VALIDATION_ERROR = "VALIDATION_ERROR"
NOT_FOUND = "NOT_FOUND"
UNAUTHORIZED = "UNAUTHORIZED"
RATE_LIMITED = "RATE_LIMITED"
API_ERROR = "API_ERROR"
TIMEOUT = "TIMEOUT"
NETWORK_ERROR = "NETWORK_ERROR"
UNKNOWN_ERROR = "UNKNOWN_ERROR"
# ============================================================================
# Response Formatters
# ============================================================================
def create_success(data: Any, message: str = "Success") -> Dict[str, Any]:
"""Create structured success response."""
return {
"success": True,
"message": message,
"data": data,
"timestamp": datetime.now().isoformat()
}
def create_error(
code: ErrorCode,
message: str,
details: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Create structured error response."""
return {
"success": False,
"error": {
"code": code.value,
"message": message,
"details": details or {},
"timestamp": datetime.now().isoformat()
}
}
# ============================================================================
# Retry Logic
# ============================================================================
async def retry_with_backoff(
func,
max_retries: int = 3,
initial_delay: float = 1.0,
exponential_base: float = 2.0,
catch_exceptions: tuple = (Exception,)
):
"""
Retry function with exponential backoff.
Args:
func: Async function to retry
max_retries: Maximum number of retry attempts
initial_delay: Initial delay in seconds
exponential_base: Base for exponential backoff
catch_exceptions: Tuple of exceptions to catch and retry
Returns:
Function result if successful
Raises:
Last exception if all retries fail
"""
delay = initial_delay
last_exception = None
for attempt in range(max_retries):
try:
return await func()
except catch_exceptions as e:
last_exception = e
if attempt < max_retries - 1:
await asyncio.sleep(delay)
delay *= exponential_base
raise last_exception
# ============================================================================
# Tools with Error Handling
# ============================================================================
@mcp.tool()
def divide_numbers(a: float, b: float) -> dict:
"""
Divide two numbers with error handling.
Args:
a: Numerator
b: Denominator
Returns:
Result or error
"""
try:
if b == 0:
return create_error(
ErrorCode.VALIDATION_ERROR,
"Division by zero is not allowed",
{"a": a, "b": b}
)
result = a / b
return create_success(
{"result": result, "a": a, "b": b},
"Division successful"
)
except Exception as e:
return create_error(
ErrorCode.UNKNOWN_ERROR,
f"Unexpected error: {str(e)}"
)
@mcp.tool()
async def validated_operation(data: str, min_length: int = 1) -> dict:
"""
Operation with input validation.
Args:
data: Input data to validate
min_length: Minimum required length
Returns:
Processed result or validation error
"""
# Validate input
if not data:
return create_error(
ErrorCode.VALIDATION_ERROR,
"Data is required",
{"field": "data", "constraint": "not_empty"}
)
if len(data) < min_length:
return create_error(
ErrorCode.VALIDATION_ERROR,
f"Data must be at least {min_length} characters",
{"field": "data", "min_length": min_length, "actual_length": len(data)}
)
# Process data
try:
processed = data.upper()
return create_success(
{"original": data, "processed": processed},
"Data processed successfully"
)
except Exception as e:
return create_error(
ErrorCode.UNKNOWN_ERROR,
str(e)
)
@mcp.tool()
async def resilient_api_call(url: str) -> dict:
"""
API call with retry logic and comprehensive error handling.
Args:
url: URL to fetch
Returns:
API response or detailed error
"""
async def make_request():
async with httpx.AsyncClient(timeout=10) as client:
response = await client.get(url)
response.raise_for_status()
return response.json()
try:
data = await retry_with_backoff(
make_request,
max_retries=3,
catch_exceptions=(httpx.TimeoutException, httpx.NetworkError)
)
return create_success(
data,
"API call successful"
)
except httpx.TimeoutException:
return create_error(
ErrorCode.TIMEOUT,
"Request timed out",
{"url": url, "timeout_seconds": 10}
)
except httpx.NetworkError as e:
return create_error(
ErrorCode.NETWORK_ERROR,
"Network error occurred",
{"url": url, "error": str(e)}
)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return create_error(
ErrorCode.NOT_FOUND,
"Resource not found",
{"url": url, "status_code": 404}
)
elif e.response.status_code == 401:
return create_error(
ErrorCode.UNAUTHORIZED,
"Unauthorized access",
{"url": url, "status_code": 401}
)
elif e.response.status_code == 429:
return create_error(
ErrorCode.RATE_LIMITED,
"Rate limit exceeded",
{"url": url, "status_code": 429}
)
else:
return create_error(
ErrorCode.API_ERROR,
f"HTTP {e.response.status_code}",
{"url": url, "status_code": e.response.status_code}
)
except Exception as e:
return create_error(
ErrorCode.UNKNOWN_ERROR,
f"Unexpected error: {str(e)}",
{"url": url}
)
@mcp.tool()
async def batch_with_error_recovery(items: list[str]) -> dict:
"""
Batch process items with individual error recovery.
Args:
items: List of items to process
Returns:
Results with successes and failures tracked separately
"""
results = []
errors = []
for i, item in enumerate(items):
try:
# Simulate processing
if not item:
raise ValueError("Empty item")
await asyncio.sleep(0.1)
results.append({
"index": i,
"item": item,
"processed": item.upper(),
"success": True
})
except ValueError as e:
errors.append({
"index": i,
"item": item,
"error": str(e),
"code": ErrorCode.VALIDATION_ERROR.value
})
except Exception as e:
errors.append({
"index": i,
"item": item,
"error": str(e),
"code": ErrorCode.UNKNOWN_ERROR.value
})
return {
"success": len(errors) == 0,
"total": len(items),
"successful": len(results),
"failed": len(errors),
"results": results,
"errors": errors,
"timestamp": datetime.now().isoformat()
}
@mcp.tool()
async def safe_database_operation(query: str) -> dict:
"""
Simulated database operation with error handling.
Args:
query: SQL query (simulated)
Returns:
Query result or error
"""
try:
# Validate query
if "DROP" in query.upper():
return create_error(
ErrorCode.UNAUTHORIZED,
"DROP operations not allowed",
{"query": query}
)
if not query.strip():
return create_error(
ErrorCode.VALIDATION_ERROR,
"Query cannot be empty"
)
# Simulate query execution
await asyncio.sleep(0.1)
# Simulate success
mock_data = [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
return create_success(
{"rows": mock_data, "count": len(mock_data)},
"Query executed successfully"
)
except Exception as e:
return create_error(
ErrorCode.API_ERROR,
f"Database error: {str(e)}",
{"query": query}
)
# ============================================================================
# Resources with Error Handling
# ============================================================================
@mcp.resource("health://detailed")
async def detailed_health_check() -> dict:
"""Comprehensive health check with error tracking."""
checks = {}
errors = []
# Check API connectivity
try:
async with httpx.AsyncClient(timeout=5) as client:
response = await client.get("https://api.example.com/health")
checks["api"] = {
"status": "healthy",
"status_code": response.status_code
}
except Exception as e:
checks["api"] = {
"status": "unhealthy",
"error": str(e)
}
errors.append(f"API check failed: {e}")
# Check system resources
try:
import psutil
checks["system"] = {
"status": "healthy",
"cpu_percent": psutil.cpu_percent(),
"memory_percent": psutil.virtual_memory().percent
}
except Exception as e:
checks["system"] = {
"status": "error",
"error": str(e)
}
# Overall status
all_healthy = all(
check.get("status") == "healthy"
for check in checks.values()
)
return {
"status": "healthy" if all_healthy else "degraded",
"checks": checks,
"errors": errors if errors else None,
"timestamp": datetime.now().isoformat()
}
# ============================================================================
# Main
# ============================================================================
if __name__ == "__main__":
mcp.run()
"""
FastMCP OpenAPI Integration Template
=====================================
Auto-generate MCP server from OpenAPI/Swagger specification.
"""
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
import httpx
import os
from dotenv import load_dotenv
load_dotenv()
# ============================================================================
# Configuration
# ============================================================================
API_BASE_URL = os.getenv("API_BASE_URL", "https://api.example.com")
API_KEY = os.getenv("API_KEY", "")
OPENAPI_SPEC_URL = os.getenv("OPENAPI_SPEC_URL", f"{API_BASE_URL}/openapi.json")
# ============================================================================
# Load OpenAPI Specification
# ============================================================================
def load_openapi_spec():
"""Load OpenAPI specification from URL or file."""
try:
# Try loading from URL
response = httpx.get(OPENAPI_SPEC_URL, timeout=10)
response.raise_for_status()
return response.json()
except Exception as e:
print(f"Failed to load OpenAPI spec from URL: {e}")
# Fallback: try loading from local file
try:
import json
with open("openapi.json") as f:
return json.load(f)
except FileNotFoundError:
print("Error: OpenAPI spec not found. Please provide OPENAPI_SPEC_URL or openapi.json file")
return None
spec = load_openapi_spec()
# ============================================================================
# Create Authenticated HTTP Client
# ============================================================================
client = httpx.AsyncClient(
base_url=API_BASE_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
timeout=httpx.Timeout(30.0),
limits=httpx.Limits(
max_keepalive_connections=5,
max_connections=10
)
)
# ============================================================================
# Define Route Mapping Strategy
# ============================================================================
route_maps = [
# GET endpoints with path parameters → Resource Templates
# Example: /users/{user_id} → resource template
RouteMap(
methods=["GET"],
pattern=r".*\{.*\}.*", # Has path parameters
mcp_type=MCPType.RESOURCE_TEMPLATE
),
# GET endpoints without parameters → Static Resources
# Example: /users → static resource
RouteMap(
methods=["GET"],
pattern=r"^(?!.*\{.*\}).*$", # No path parameters
mcp_type=MCPType.RESOURCE
),
# POST/PUT/PATCH → Tools (create/update operations)
RouteMap(
methods=["POST", "PUT", "PATCH"],
mcp_type=MCPType.TOOL
),
# DELETE → Tools (delete operations)
RouteMap(
methods=["DELETE"],
mcp_type=MCPType.TOOL
),
# Exclude internal endpoints
RouteMap(
pattern=r"/internal/.*",
mcp_type=MCPType.EXCLUDE
),
# Exclude health checks
RouteMap(
pattern=r"/(health|healthz|readiness|liveness)",
mcp_type=MCPType.EXCLUDE
)
]
# ============================================================================
# Generate MCP Server from OpenAPI
# ============================================================================
if spec:
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
name="API Integration Server",
route_maps=route_maps
)
print(f"✅ Generated MCP server from OpenAPI spec")
print(f" Base URL: {API_BASE_URL}")
else:
# Fallback: create empty server if spec not available
mcp = FastMCP("API Integration Server")
print("⚠️ Running without OpenAPI spec - please configure OPENAPI_SPEC_URL")
# ============================================================================
# Add Custom Tools (on top of auto-generated ones)
# ============================================================================
@mcp.tool()
async def process_api_response(data: dict, operation: str = "format") -> dict:
"""
Process API response data.
Custom tool to transform or analyze data from API endpoints.
"""
if operation == "format":
# Format the data nicely
return {
"formatted": True,
"data": data,
"count": len(data) if isinstance(data, (list, dict)) else 1
}
elif operation == "summarize":
# Summarize the data
if isinstance(data, list):
return {
"type": "list",
"count": len(data),
"sample": data[:3] if len(data) > 3 else data
}
elif isinstance(data, dict):
return {
"type": "dict",
"keys": list(data.keys()),
"size": len(data)
}
else:
return {
"type": type(data).__name__,
"value": str(data)
}
return {"error": f"Unknown operation: {operation}"}
@mcp.tool()
async def batch_api_request(endpoints: list[str]) -> dict:
"""
Make multiple API requests in parallel.
Useful for gathering data from multiple endpoints efficiently.
"""
import asyncio
async def fetch_endpoint(endpoint: str):
try:
response = await client.get(endpoint)
response.raise_for_status()
return {
"endpoint": endpoint,
"success": True,
"data": response.json()
}
except Exception as e:
return {
"endpoint": endpoint,
"success": False,
"error": str(e)
}
# Execute all requests in parallel
results = await asyncio.gather(*[fetch_endpoint(ep) for ep in endpoints])
successful = [r for r in results if r["success"]]
failed = [r for r in results if not r["success"]]
return {
"total": len(endpoints),
"successful": len(successful),
"failed": len(failed),
"results": results
}
# ============================================================================
# Add Custom Resources
# ============================================================================
@mcp.resource("info://api-config")
def api_configuration() -> dict:
"""Get API configuration details."""
return {
"base_url": API_BASE_URL,
"spec_url": OPENAPI_SPEC_URL,
"authenticated": bool(API_KEY),
"spec_loaded": spec is not None
}
@mcp.resource("info://available-endpoints")
def list_available_endpoints() -> dict:
"""List all available API endpoints."""
if not spec:
return {"error": "OpenAPI spec not loaded"}
endpoints = []
for path, path_item in spec.get("paths", {}).items():
for method in path_item.keys():
if method.upper() in ["GET", "POST", "PUT", "PATCH", "DELETE"]:
operation = path_item[method]
endpoints.append({
"path": path,
"method": method.upper(),
"summary": operation.get("summary", ""),
"description": operation.get("description", "")
})
return {
"total": len(endpoints),
"endpoints": endpoints
}
# ============================================================================
# Main Execution
# ============================================================================
if __name__ == "__main__":
mcp.run()
"""
FastMCP Prompts Examples
=========================
Examples of pre-configured prompts for LLMs.
"""
from fastmcp import FastMCP
from datetime import datetime
mcp = FastMCP("Prompts Examples")
# ============================================================================
# Basic Prompts
# ============================================================================
@mcp.prompt("help")
def help_prompt() -> str:
"""Generate help text for the server."""
return """
Welcome to the FastMCP Prompts Examples Server!
This server demonstrates various prompt patterns for LLM interactions.
Available Tools:
- search: Search for items in the database
- analyze: Analyze data and generate insights
- summarize: Create summaries of text content
Available Resources:
- info://status: Current server status
- data://config: Server configuration
- data://users: List of all users
How to Use:
1. Use the search tool to find items
2. Use the analyze tool to generate insights from data
3. Use the summarize tool to create concise summaries
For specific tasks, use the pre-configured prompts:
- /analyze: Analyze a topic in depth
- /report: Generate a comprehensive report
- /review: Review and provide feedback
"""
@mcp.prompt("analyze")
def analyze_prompt(topic: str) -> str:
"""Generate a prompt for analyzing a topic."""
return f"""
Please analyze the following topic: {topic}
Consider the following aspects:
1. Current State: What is the current situation?
2. Challenges: What are the main challenges or issues?
3. Opportunities: What opportunities exist for improvement?
4. Data Points: What data supports your analysis?
5. Recommendations: What specific actions do you recommend?
Use the available tools to:
- Search for relevant data using the search tool
- Gather statistics and metrics
- Review related information
Provide a structured analysis with:
- Executive Summary
- Detailed Findings
- Data-Driven Insights
- Actionable Recommendations
"""
# ============================================================================
# Prompts with Parameters
# ============================================================================
@mcp.prompt("report")
def report_prompt(
subject: str,
timeframe: str = "last month",
detail_level: str = "summary"
) -> str:
"""Generate a report prompt with parameters."""
return f"""
Generate a comprehensive report on: {subject}
Timeframe: {timeframe}
Detail Level: {detail_level}
Report Structure:
1. Executive Summary
- Key findings
- Critical metrics
- Main recommendations
2. Data Analysis
- Quantitative metrics
- Trend analysis
- Comparative analysis
3. Insights
- Patterns discovered
- Anomalies identified
- Correlations found
4. Recommendations
- Short-term actions
- Long-term strategies
- Resource requirements
Please use the available tools to gather:
- Statistical data
- User information
- System metrics
- Historical trends
Format: {detail_level.upper()}
- "summary": High-level overview with key points
- "detailed": In-depth analysis with supporting data
- "comprehensive": Full analysis with all available data points
"""
@mcp.prompt("review")
def review_prompt(
item_type: str,
item_id: str,
focus_areas: str = "all"
) -> str:
"""Generate a review prompt."""
return f"""
Review the {item_type} (ID: {item_id})
Focus Areas: {focus_areas}
Review Criteria:
1. Quality Assessment
- Overall quality rating
- Strengths identified
- Areas for improvement
2. Completeness
- Required elements present
- Missing components
- Suggestions for additions
3. Consistency
- Internal consistency
- Alignment with standards
- Conformance to guidelines
4. Performance
- Efficiency metrics
- Resource utilization
- Optimization opportunities
5. Recommendations
- Priority improvements
- Nice-to-have enhancements
- Long-term considerations
Please gather relevant data using available tools and provide a structured review.
"""
# ============================================================================
# Task-Specific Prompts
# ============================================================================
@mcp.prompt("summarize")
def summarize_prompt(content_type: str = "text") -> str:
"""Generate a summarization prompt."""
return f"""
Create a comprehensive summary of the {content_type}.
Summary Guidelines:
1. Key Points
- Extract the most important information
- Identify main themes or topics
- Highlight critical details
2. Structure
- Opening: Context and overview
- Body: Main points organized logically
- Closing: Conclusions and implications
3. Audience Consideration
- Write for clarity and understanding
- Define technical terms if needed
- Provide context where necessary
4. Length
- Brief: 2-3 sentences
- Standard: 1 paragraph
- Detailed: 2-3 paragraphs
Output Format:
- Start with a one-sentence overview
- Follow with detailed points
- End with key takeaways
Use available tools to gather additional context if needed.
"""
@mcp.prompt("compare")
def compare_prompt(item1: str, item2: str, criteria: str = "general") -> str:
"""Generate a comparison prompt."""
return f"""
Compare and contrast: {item1} vs {item2}
Comparison Criteria: {criteria}
Analysis Framework:
1. Similarities
- Common features
- Shared characteristics
- Aligned goals or purposes
2. Differences
- Unique features
- Distinct characteristics
- Divergent approaches
3. Strengths and Weaknesses
- {item1} strengths
- {item1} weaknesses
- {item2} strengths
- {item2} weaknesses
4. Use Cases
- When to choose {item1}
- When to choose {item2}
- Situational recommendations
5. Conclusion
- Overall assessment
- Best fit scenarios
- Decision factors
Please gather data using available tools and provide a balanced comparison.
"""
# ============================================================================
# Workflow Prompts
# ============================================================================
@mcp.prompt("troubleshoot")
def troubleshoot_prompt(problem_description: str) -> str:
"""Generate a troubleshooting prompt."""
return f"""
Troubleshoot the following issue:
Problem: {problem_description}
Troubleshooting Process:
1. Problem Definition
- Describe the issue clearly
- Identify symptoms
- Note when it started
2. Information Gathering
- Use available tools to gather:
* System status
* Error logs
* Configuration details
* Recent changes
3. Analysis
- Identify potential causes
- Determine root cause
- Assess impact
4. Solution Development
- Propose solutions (short-term and long-term)
- Evaluate each solution
- Recommend best approach
5. Implementation Plan
- Step-by-step resolution
- Required resources
- Expected timeline
- Verification steps
6. Prevention
- Preventive measures
- Monitoring recommendations
- Documentation needs
Please be systematic and thorough in your analysis.
"""
@mcp.prompt("plan")
def plan_prompt(objective: str, constraints: str = "none") -> str:
"""Generate a planning prompt."""
return f"""
Create a detailed plan for: {objective}
Constraints: {constraints}
Planning Framework:
1. Objective Analysis
- Clear definition of success
- Key success criteria
- Expected outcomes
2. Current State Assessment
- Available resources
- Existing capabilities
- Known limitations
3. Strategy Development
- Approach options
- Recommended strategy
- Rationale
4. Action Plan
- Phase 1: Foundation
- Phase 2: Implementation
- Phase 3: Optimization
5. Resource Requirements
- Personnel
- Technology
- Budget
- Time
6. Risk Management
- Identified risks
- Mitigation strategies
- Contingency plans
7. Success Metrics
- KPIs to track
- Measurement methods
- Review milestones
Use available tools to gather supporting data and insights.
"""
# ============================================================================
# Main
# ============================================================================
if __name__ == "__main__":
mcp.run()
[project]
name = "my-fastmcp-server"
version = "1.0.0"
description = "My FastMCP Server"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "Your Name", email = "your.email@example.com"}
]
dependencies = [
"fastmcp>=2.12.0",
"httpx>=0.27.0",
"python-dotenv>=1.0.0",
"pydantic>=2.0.0",
"psutil>=5.9.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"ruff>=0.3.0",
"mypy>=1.8.0",
]
[project.urls]
Homepage = "https://github.com/yourusername/my-fastmcp-server"
Repository = "https://github.com/yourusername/my-fastmcp-server"
Issues = "https://github.com/yourusername/my-fastmcp-server/issues"
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["src"]
[tool.setuptools.package-data]
src = ["py.typed"]
# Ruff Configuration
[tool.ruff]
line-length = 100
target-version = "py310"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
]
ignore = [
"E501", # line too long (handled by formatter)
"B008", # do not perform function calls in argument defaults
]
# Mypy Configuration
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false
disallow_any_generics = false
check_untyped_defs = true
# Pytest Configuration
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
asyncio_mode = "auto"
addopts = "-v --tb=short"
# Coverage Configuration
[tool.coverage.run]
source = ["src"]
omit = ["*/tests/*", "*/test_*"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
# FastMCP Core
fastmcp>=2.14.2
# Storage Backends (for production persistence)
py-key-value-aio>=0.1.0 # Memory, Disk, Redis, DynamoDB storage
# Encryption (for secure token storage)
cryptography>=42.0.0
# HTTP Client (for API integrations)
httpx>=0.27.0
# Environment Variables
python-dotenv>=1.0.0
# Validation (optional, for complex validation)
pydantic>=2.0.0
# System Monitoring (optional, for health checks)
psutil>=5.9.0
# Async Support
asyncio-extras>=1.3.2
# Optional Storage Backend Dependencies
# Uncomment as needed for specific backends:
# redis>=5.0.0 # For RedisStore
# boto3>=1.34.0 # For DynamoDB
# pymongo>=4.6.0 # For MongoDB
# elasticsearch>=8.12.0 # For Elasticsearch
# Development Dependencies (optional)
# Uncomment for development
# pytest>=8.0.0
# pytest-asyncio>=0.23.0
# ruff>=0.3.0
# mypy>=1.8.0
"""
FastMCP Resources Examples
===========================
Examples of static resources, dynamic resources, and resource templates.
"""
from fastmcp import FastMCP
from datetime import datetime
from typing import Dict, List, Any
import os
mcp = FastMCP("Resources Examples")
# ============================================================================
# Static Resources
# ============================================================================
@mcp.resource("data://config")
def get_config() -> dict:
"""Static configuration resource."""
return {
"version": "1.0.0",
"environment": os.getenv("ENVIRONMENT", "development"),
"features": ["search", "analytics", "notifications"],
"limits": {
"max_requests": 1000,
"max_results": 100
}
}
@mcp.resource("info://server")
def server_info() -> dict:
"""Server metadata."""
return {
"name": "Resources Examples Server",
"description": "Demonstrates various resource patterns",
"version": "1.0.0",
"capabilities": [
"static_resources",
"dynamic_resources",
"resource_templates"
]
}
# ============================================================================
# Dynamic Resources
# ============================================================================
@mcp.resource("info://status")
async def server_status() -> dict:
"""Dynamic status resource (updated on each read)."""
import psutil
return {
"status": "operational",
"timestamp": datetime.now().isoformat(),
"uptime_seconds": 0, # Would track actual uptime
"system": {
"cpu_percent": psutil.cpu_percent(interval=1),
"memory_percent": psutil.virtual_memory().percent,
"disk_percent": psutil.disk_usage('/').percent
}
}
@mcp.resource("data://statistics")
async def get_statistics() -> dict:
"""Dynamic statistics resource."""
return {
"timestamp": datetime.now().isoformat(),
"total_requests": 1234, # Would track actual requests
"active_connections": 5,
"cache_hit_rate": 0.87,
"average_response_time_ms": 45.3
}
# ============================================================================
# Resource Templates (with parameters)
# ============================================================================
@mcp.resource("user://{user_id}/profile")
async def get_user_profile(user_id: str) -> dict:
"""Get user profile by ID."""
# In production, fetch from database
return {
"id": user_id,
"name": f"User {user_id}",
"email": f"user{user_id}@example.com",
"created_at": "2024-01-01T00:00:00Z",
"role": "user"
}
@mcp.resource("user://{user_id}/posts")
async def get_user_posts(user_id: str) -> List[dict]:
"""Get posts for a specific user."""
# In production, fetch from database
return [
{
"id": 1,
"user_id": user_id,
"title": "First Post",
"content": "Hello, world!",
"created_at": "2024-01-01T00:00:00Z"
},
{
"id": 2,
"user_id": user_id,
"title": "Second Post",
"content": "Another post",
"created_at": "2024-01-02T00:00:00Z"
}
]
@mcp.resource("org://{org_id}/team/{team_id}/members")
async def get_team_members(org_id: str, team_id: str) -> List[dict]:
"""Get team members with org and team context."""
# In production, fetch from database with filters
return [
{
"id": 1,
"name": "Alice",
"role": "engineer",
"org_id": org_id,
"team_id": team_id
},
{
"id": 2,
"name": "Bob",
"role": "designer",
"org_id": org_id,
"team_id": team_id
}
]
@mcp.resource("api://{version}/config")
async def get_versioned_config(version: str) -> dict:
"""Get configuration for specific API version."""
configs = {
"v1": {
"api_version": "v1",
"endpoints": ["/users", "/posts"],
"deprecated": True
},
"v2": {
"api_version": "v2",
"endpoints": ["/users", "/posts", "/comments", "/likes"],
"deprecated": False
}
}
return configs.get(version, {"error": f"Unknown version: {version}"})
# ============================================================================
# File-based Resources
# ============================================================================
@mcp.resource("file://docs/{filename}")
async def get_documentation(filename: str) -> dict:
"""Get documentation file content."""
# In production, read actual files
docs = {
"getting-started.md": {
"filename": "getting-started.md",
"content": "# Getting Started\n\nWelcome to the docs!",
"last_modified": "2024-01-01T00:00:00Z"
},
"api-reference.md": {
"filename": "api-reference.md",
"content": "# API Reference\n\nAPI documentation here.",
"last_modified": "2024-01-02T00:00:00Z"
}
}
return docs.get(filename, {"error": f"File not found: {filename}"})
# ============================================================================
# List-style Resources
# ============================================================================
@mcp.resource("data://users")
async def list_users() -> List[dict]:
"""List all users."""
# In production, fetch from database
return [
{"id": "1", "name": "Alice", "email": "alice@example.com"},
{"id": "2", "name": "Bob", "email": "bob@example.com"},
{"id": "3", "name": "Charlie", "email": "charlie@example.com"}
]
@mcp.resource("data://categories")
def list_categories() -> List[str]:
"""List available categories."""
return [
"Technology",
"Science",
"Business",
"Entertainment",
"Sports"
]
# ============================================================================
# Main
# ============================================================================
if __name__ == "__main__":
mcp.run()
#!/usr/bin/env npx tsx
/**
* {Tool Name} - {Brief description}
*
* Usage:
* npx tsx scripts/{tool-name}.ts <args>
* npx tsx scripts/{tool-name}.ts --help
*
* Examples:
* npx tsx scripts/{tool-name}.ts {example}
*/
import * as fs from 'fs';
import * as path from 'path';
// ============================================================================
// Configuration
// ============================================================================
interface Config {
// Add config options
}
function loadConfig(): Config {
// Load from env, config file, etc.
return {};
}
// ============================================================================
// Argument Parsing
// ============================================================================
interface Args {
// Define expected arguments
input?: string;
output?: string;
format: 'json' | 'csv' | 'table';
verbose: boolean;
help: boolean;
}
function parseArgs(): Args {
const args = process.argv.slice(2);
const parsed: Args = {
format: 'json',
verbose: false,
help: false,
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--input':
parsed.input = args[++i];
break;
case '--output':
parsed.output = args[++i];
break;
case '--format':
parsed.format = args[++i] as Args['format'];
break;
case '--verbose':
parsed.verbose = true;
break;
case '--help':
case '-h':
parsed.help = true;
break;
}
}
return parsed;
}
function showHelp(): void {
console.log(`
{Tool Name} - {Description}
Usage:
npx tsx scripts/{tool-name}.ts [options] <args>
Options:
--input <file> Read input from file
--output <file> Write output to file
--format <type> Output format: json, csv, table (default: json)
--verbose Show debug information
--help, -h Show this help
Examples:
npx tsx scripts/{tool-name}.ts example-arg
npx tsx scripts/{tool-name}.ts --input data.csv --output results.json
`);
}
// ============================================================================
// Core Logic
// ============================================================================
interface Result {
success: boolean;
data?: unknown;
error?: string;
}
async function execute(/* params */): Promise<Result> {
// Implement core logic here
// This could also import from a shared module
return { success: true, data: {} };
}
// ============================================================================
// Output Formatting
// ============================================================================
function formatOutput(result: Result, format: Args['format']): string {
switch (format) {
case 'json':
return JSON.stringify(result, null, 2);
case 'csv':
// Implement CSV formatting
return '';
case 'table':
// Implement table formatting
return '';
}
}
// ============================================================================
// Main
// ============================================================================
async function main(): Promise<void> {
const args = parseArgs();
if (args.help) {
showHelp();
process.exit(0);
}
try {
const result = await execute(/* pass args */);
const output = formatOutput(result, args.format);
if (args.output) {
fs.writeFileSync(args.output, output);
if (args.verbose) {
console.error(`Written to ${args.output}`);
}
} else {
console.log(output);
}
process.exit(result.success ? 0 : 1);
} catch (error: any) {
console.error(JSON.stringify({ success: false, error: error.message }));
process.exit(1);
}
}
main();
CLI Scripts
These scripts provide command-line access to {server-name} functionality. For use with Claude Code in terminal environments.
Prerequisites
- Node.js 18+
- Run
npm installin this directory
Available Scripts
{tool-name}.ts
{Description of what this tool does}
Usage:
npx tsx scripts/{tool-name}.ts <required-arg>
npx tsx scripts/{tool-name}.ts --option valueArguments:
<required-arg>- Description--option <value>- Description (default: x)
Examples:
# Basic usage
npx tsx scripts/{tool-name}.ts 12345
# With options
npx tsx scripts/{tool-name}.ts --input batch.csv --output results.json
# Verbose mode
npx tsx scripts/{tool-name}.ts 12345 --verboseOutput:
{
"success": true,
"data": { ... }
}{another-tool}.ts
...
Common Options
All scripts support these common options:
| Option | Description |
|---|---|
--output <file> | Write results to file instead of stdout |
--format <type> | Output format: json, csv, table |
--verbose | Show debug information |
--help | Show usage information |
Error Handling
Scripts exit with code 0 on success, non-zero on failure. Errors are output as JSON:
{
"success": false,
"error": "Error message here"
}"""
Self-Contained FastMCP Server
==============================
Production pattern with all utilities in one file.
Avoids circular import issues common in cloud deployment.
"""
from fastmcp import FastMCP, Context
import os
import time
import asyncio
import httpx
from typing import Dict, Any, Optional, List
from datetime import datetime
from dotenv import load_dotenv
load_dotenv()
# ============================================================================
# Configuration (Self-contained)
# ============================================================================
class Config:
"""Application configuration from environment variables."""
SERVER_NAME = os.getenv("SERVER_NAME", "Self-Contained Server")
SERVER_VERSION = "1.0.0"
API_BASE_URL = os.getenv("API_BASE_URL", "")
API_KEY = os.getenv("API_KEY", "")
CACHE_TTL = int(os.getenv("CACHE_TTL", "300"))
MAX_RETRIES = int(os.getenv("MAX_RETRIES", "3"))
# ============================================================================
# Utilities (All in one place)
# ============================================================================
def format_success(data: Any, message: str = "Success") -> Dict[str, Any]:
"""Format successful response."""
return {
"success": True,
"message": message,
"data": data,
"timestamp": datetime.now().isoformat()
}
def format_error(error: str, code: str = "ERROR") -> Dict[str, Any]:
"""Format error response."""
return {
"success": False,
"error": error,
"code": code,
"timestamp": datetime.now().isoformat()
}
# ============================================================================
# API Client (Lazy Initialization)
# ============================================================================
class APIClient:
"""Singleton HTTP client with lazy initialization."""
_instance: Optional[httpx.AsyncClient] = None
@classmethod
async def get_client(cls) -> Optional[httpx.AsyncClient]:
"""Get or create HTTP client (only when needed)."""
if not Config.API_BASE_URL or not Config.API_KEY:
return None
if cls._instance is None:
cls._instance = httpx.AsyncClient(
base_url=Config.API_BASE_URL,
headers={"Authorization": f"Bearer {Config.API_KEY}"},
timeout=httpx.Timeout(30.0)
)
return cls._instance
@classmethod
async def cleanup(cls):
"""Cleanup HTTP client."""
if cls._instance:
await cls._instance.aclose()
cls._instance = None
# ============================================================================
# Cache (Simple Implementation)
# ============================================================================
class SimpleCache:
"""Time-based cache."""
_cache: Dict[str, Any] = {}
_timestamps: Dict[str, float] = {}
@classmethod
def get(cls, key: str) -> Optional[Any]:
"""Get cached value if not expired."""
if key in cls._cache:
if time.time() - cls._timestamps[key] < Config.CACHE_TTL:
return cls._cache[key]
else:
del cls._cache[key]
del cls._timestamps[key]
return None
@classmethod
def set(cls, key: str, value: Any):
"""Set cache value."""
cls._cache[key] = value
cls._timestamps[key] = time.time()
@classmethod
def clear(cls):
"""Clear all cache."""
cls._cache.clear()
cls._timestamps.clear()
# ============================================================================
# Retry Logic
# ============================================================================
async def retry_with_backoff(func, max_retries: int = 3):
"""Retry function with exponential backoff."""
delay = 1.0
last_exception = None
for attempt in range(max_retries):
try:
return await func()
except Exception as e:
last_exception = e
if attempt < max_retries - 1:
await asyncio.sleep(delay)
delay *= 2
raise last_exception
# ============================================================================
# Server Creation (Module Level - Required for Cloud!)
# ============================================================================
mcp = FastMCP(
name=Config.SERVER_NAME,
instructions=f"""
{Config.SERVER_NAME} v{Config.SERVER_VERSION}
A self-contained MCP server with production patterns.
Available tools:
- process_data: Process and transform data
- fetch_from_api: Fetch data from external API (if configured)
- calculate: Perform calculations
Available resources:
- info://status: Server status
- info://config: Configuration details
"""
)
# ============================================================================
# Tools
# ============================================================================
@mcp.tool()
async def process_data(
data: List[Dict[str, Any]],
operation: str = "summarize"
) -> dict:
"""
Process a list of data items.
Args:
data: List of data items
operation: Operation to perform (summarize, filter, transform)
Returns:
Processed result
"""
try:
if operation == "summarize":
return format_success({
"count": len(data),
"first": data[0] if data else None,
"last": data[-1] if data else None
})
elif operation == "filter":
filtered = [d for d in data if d.get("active", False)]
return format_success({
"original_count": len(data),
"filtered_count": len(filtered),
"data": filtered
})
elif operation == "transform":
transformed = [
{**d, "processed_at": datetime.now().isoformat()}
for d in data
]
return format_success({"data": transformed})
else:
return format_error(f"Unknown operation: {operation}", "INVALID_OPERATION")
except Exception as e:
return format_error(str(e), "PROCESSING_ERROR")
@mcp.tool()
async def fetch_from_api(
endpoint: str,
use_cache: bool = True
) -> dict:
"""
Fetch data from external API.
Args:
endpoint: API endpoint path
use_cache: Whether to use cached response
Returns:
API response or error
"""
if not Config.API_BASE_URL:
return format_error("API not configured", "API_NOT_CONFIGURED")
cache_key = f"api:{endpoint}"
# Check cache
if use_cache:
cached = SimpleCache.get(cache_key)
if cached:
return format_success(cached, "Retrieved from cache")
# Fetch from API
try:
client = await APIClient.get_client()
if not client:
return format_error("API client not available", "CLIENT_ERROR")
async def make_request():
response = await client.get(endpoint)
response.raise_for_status()
return response.json()
data = await retry_with_backoff(make_request, max_retries=Config.MAX_RETRIES)
# Cache the result
if use_cache:
SimpleCache.set(cache_key, data)
return format_success(data, "Fetched from API")
except httpx.HTTPStatusError as e:
return format_error(
f"HTTP {e.response.status_code}",
"HTTP_ERROR"
)
except Exception as e:
return format_error(str(e), "API_ERROR")
@mcp.tool()
def calculate(operation: str, a: float, b: float) -> dict:
"""
Perform mathematical operations.
Args:
operation: add, subtract, multiply, divide
a: First number
b: Second number
Returns:
Calculation result
"""
operations = {
"add": lambda x, y: x + y,
"subtract": lambda x, y: x - y,
"multiply": lambda x, y: x * y,
"divide": lambda x, y: x / y if y != 0 else None
}
if operation not in operations:
return format_error(
f"Unknown operation: {operation}",
"INVALID_OPERATION"
)
result = operations[operation](a, b)
if result is None:
return format_error("Division by zero", "DIVISION_BY_ZERO")
return format_success({
"operation": operation,
"a": a,
"b": b,
"result": result
})
@mcp.tool()
async def batch_process_with_progress(
items: List[str],
context: Context
) -> dict:
"""
Process items with progress tracking.
Args:
items: List of items to process
context: FastMCP context for progress reporting
Returns:
Processing results
"""
results = []
total = len(items)
for i, item in enumerate(items):
# Report progress
await context.report_progress(
progress=i + 1,
total=total,
message=f"Processing {i + 1}/{total}: {item}"
)
# Simulate processing
await asyncio.sleep(0.1)
results.append({
"item": item,
"processed": item.upper(),
"index": i
})
return format_success({
"total": total,
"results": results
}, "Batch processing complete")
@mcp.tool()
def clear_cache() -> dict:
"""Clear all cached data."""
try:
SimpleCache.clear()
return format_success({"cleared": True}, "Cache cleared")
except Exception as e:
return format_error(str(e), "CACHE_ERROR")
# ============================================================================
# Resources
# ============================================================================
@mcp.resource("info://status")
async def server_status() -> dict:
"""Get current server status."""
return {
"server": Config.SERVER_NAME,
"version": Config.SERVER_VERSION,
"status": "operational",
"timestamp": datetime.now().isoformat(),
"api_configured": bool(Config.API_BASE_URL and Config.API_KEY),
"cache_entries": len(SimpleCache._cache)
}
@mcp.resource("info://config")
def server_config() -> dict:
"""Get server configuration (non-sensitive)."""
return {
"server_name": Config.SERVER_NAME,
"version": Config.SERVER_VERSION,
"cache_ttl": Config.CACHE_TTL,
"max_retries": Config.MAX_RETRIES,
"api_configured": bool(Config.API_BASE_URL)
}
@mcp.resource("health://check")
async def health_check() -> dict:
"""Comprehensive health check."""
checks = {}
# Check API
if Config.API_BASE_URL:
try:
client = await APIClient.get_client()
if client:
response = await client.get("/health", timeout=5)
checks["api"] = response.status_code == 200
except:
checks["api"] = False
else:
checks["api"] = None # Not configured
# Check cache
checks["cache"] = True # Always available
return {
"status": "healthy" if all(v for v in checks.values() if v is not None) else "degraded",
"checks": checks,
"timestamp": datetime.now().isoformat()
}
# ============================================================================
# Main Execution
# ============================================================================
if __name__ == "__main__":
try:
print(f"Starting {Config.SERVER_NAME}...")
mcp.run()
except KeyboardInterrupt:
print("\nShutting down...")
finally:
# Cleanup
import asyncio
asyncio.run(APIClient.cleanup())
"""
FastMCP Tools Examples
======================
Comprehensive examples of tool patterns: sync, async, validation, error handling.
"""
from fastmcp import FastMCP, Context
from pydantic import BaseModel, Field, validator
from typing import Optional, List, Dict, Any
from datetime import datetime
import asyncio
mcp = FastMCP("Tools Examples")
# ============================================================================
# Basic Tools
# ============================================================================
@mcp.tool()
def simple_sync_tool(text: str) -> str:
"""Simple synchronous tool."""
return text.upper()
@mcp.tool()
async def simple_async_tool(text: str) -> str:
"""Simple asynchronous tool."""
await asyncio.sleep(0.1) # Simulate async operation
return text.lower()
# ============================================================================
# Tools with Validation
# ============================================================================
class SearchParams(BaseModel):
"""Validated search parameters."""
query: str = Field(min_length=1, max_length=100, description="Search query")
limit: int = Field(default=10, ge=1, le=100, description="Maximum results")
offset: int = Field(default=0, ge=0, description="Offset for pagination")
@validator("query")
def clean_query(cls, v):
return v.strip()
@mcp.tool()
async def validated_search(params: SearchParams) -> dict:
"""
Search with validated parameters.
Pydantic automatically validates all parameters.
"""
return {
"query": params.query,
"limit": params.limit,
"offset": params.offset,
"results": [
{"id": 1, "title": f"Result for: {params.query}"},
{"id": 2, "title": f"Another result for: {params.query}"}
]
}
# ============================================================================
# Tools with Optional Parameters
# ============================================================================
@mcp.tool()
def process_text(
text: str,
uppercase: bool = False,
prefix: Optional[str] = None,
suffix: Optional[str] = None
) -> str:
"""Process text with optional transformations."""
result = text
if uppercase:
result = result.upper()
if prefix:
result = f"{prefix}{result}"
if suffix:
result = f"{result}{suffix}"
return result
# ============================================================================
# Tools with Complex Return Types
# ============================================================================
@mcp.tool()
async def batch_process(items: List[str]) -> Dict[str, Any]:
"""Process multiple items and return detailed results."""
results = []
errors = []
for i, item in enumerate(items):
try:
# Simulate processing
await asyncio.sleep(0.1)
results.append({
"index": i,
"item": item,
"processed": item.upper(),
"success": True
})
except Exception as e:
errors.append({
"index": i,
"item": item,
"error": str(e),
"success": False
})
return {
"total": len(items),
"successful": len(results),
"failed": len(errors),
"results": results,
"errors": errors,
"timestamp": datetime.now().isoformat()
}
# ============================================================================
# Tools with Error Handling
# ============================================================================
@mcp.tool()
async def safe_operation(data: dict) -> dict:
"""Operation with comprehensive error handling."""
try:
# Validate input
if not data:
return {
"success": False,
"error": "Data is required",
"code": "VALIDATION_ERROR"
}
# Simulate operation
await asyncio.sleep(0.1)
processed_data = {k: v.upper() if isinstance(v, str) else v for k, v in data.items()}
return {
"success": True,
"data": processed_data,
"timestamp": datetime.now().isoformat()
}
except KeyError as e:
return {
"success": False,
"error": f"Missing key: {e}",
"code": "KEY_ERROR"
}
except Exception as e:
return {
"success": False,
"error": str(e),
"code": "UNKNOWN_ERROR"
}
# ============================================================================
# Tools with Context (for Elicitation, Progress, Sampling)
# ============================================================================
@mcp.tool()
async def tool_with_progress(count: int, context: Context) -> dict:
"""Tool that reports progress."""
results = []
for i in range(count):
# Report progress if available
if context and hasattr(context, 'report_progress'):
await context.report_progress(
progress=i + 1,
total=count,
message=f"Processing item {i + 1}/{count}"
)
# Simulate work
await asyncio.sleep(0.1)
results.append(f"Item {i + 1}")
return {
"count": count,
"results": results,
"status": "completed"
}
@mcp.tool()
async def tool_with_elicitation(context: Context) -> dict:
"""Tool that requests user input."""
if context and hasattr(context, 'request_elicitation'):
# Request user input
user_name = await context.request_elicitation(
prompt="What is your name?",
response_type=str
)
return {
"greeting": f"Hello, {user_name}!",
"timestamp": datetime.now().isoformat()
}
else:
return {"error": "Elicitation not available"}
# ============================================================================
# Main
# ============================================================================
if __name__ == "__main__":
mcp.run()
FastMCP Integration Patterns Reference
Quick reference for API integration patterns with FastMCP.
Pattern 1: Manual API Integration
Best for simple APIs or when you need fine control.
import httpx
from fastmcp import FastMCP
mcp = FastMCP("API Integration")
# Reusable client
client = httpx.AsyncClient(
base_url="https://api.example.com",
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=30.0
)
@mcp.tool()
async def fetch_data(endpoint: str) -> dict:
"""Fetch from API."""
response = await client.get(endpoint)
response.raise_for_status()
return response.json()Pros:
- Full control over requests
- Easy to customize
- Simple to understand
Cons:
- Manual tool creation for each endpoint
- More boilerplate code
Pattern 2: OpenAPI/Swagger Auto-Generation
Best for well-documented APIs with OpenAPI specs.
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
import httpx
# Load spec
spec = httpx.get("https://api.example.com/openapi.json").json()
# Create client
client = httpx.AsyncClient(
base_url="https://api.example.com",
headers={"Authorization": f"Bearer {API_KEY}"}
)
# Auto-generate server
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
name="API Server",
route_maps=[
# GET + params → Resource Templates
RouteMap(
methods=["GET"],
pattern=r".*\{.*\}.*",
mcp_type=MCPType.RESOURCE_TEMPLATE
),
# GET no params → Resources
RouteMap(
methods=["GET"],
mcp_type=MCPType.RESOURCE
),
# POST/PUT/DELETE → Tools
RouteMap(
methods=["POST", "PUT", "PATCH", "DELETE"],
mcp_type=MCPType.TOOL
),
]
)Pros:
- Instant integration (minutes not hours)
- Auto-updates with spec changes
- No manual endpoint mapping
Cons:
- Requires OpenAPI/Swagger spec
- Less control over individual endpoints
- May include unwanted endpoints
Pattern 3: FastAPI Conversion
Best for converting existing FastAPI applications.
from fastapi import FastAPI
from fastmcp import FastMCP
# Existing FastAPI app
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"id": user_id, "name": "User"}
# Convert to MCP
mcp = FastMCP.from_fastapi(
app=app,
httpx_client_kwargs={
"headers": {"Authorization": "Bearer token"}
}
)Pros:
- Reuse existing FastAPI code
- Minimal changes needed
- Familiar FastAPI patterns
Cons:
- FastAPI must be running separately
- Extra HTTP hop (slower)
Route Mapping Strategies
Strategy 1: By HTTP Method
route_maps = [
RouteMap(methods=["GET"], mcp_type=MCPType.RESOURCE),
RouteMap(methods=["POST"], mcp_type=MCPType.TOOL),
RouteMap(methods=["PUT", "PATCH"], mcp_type=MCPType.TOOL),
RouteMap(methods=["DELETE"], mcp_type=MCPType.TOOL),
]Strategy 2: By Path Pattern
route_maps = [
# Admin endpoints → Exclude
RouteMap(
pattern=r"/admin/.*",
mcp_type=MCPType.EXCLUDE
),
# Internal → Exclude
RouteMap(
pattern=r"/internal/.*",
mcp_type=MCPType.EXCLUDE
),
# Health → Exclude
RouteMap(
pattern=r"/(health|healthz)",
mcp_type=MCPType.EXCLUDE
),
# Everything else
RouteMap(mcp_type=MCPType.TOOL),
]Strategy 3: By Parameters
route_maps = [
# Has path parameters → Resource Template
RouteMap(
pattern=r".*\{[^}]+\}.*",
mcp_type=MCPType.RESOURCE_TEMPLATE
),
# No parameters → Static Resource or Tool
RouteMap(
methods=["GET"],
mcp_type=MCPType.RESOURCE
),
RouteMap(
methods=["POST", "PUT", "DELETE"],
mcp_type=MCPType.TOOL
),
]Authentication Patterns
API Key Authentication
client = httpx.AsyncClient(
base_url="https://api.example.com",
headers={"X-API-Key": os.getenv("API_KEY")}
)Bearer Token
client = httpx.AsyncClient(
base_url="https://api.example.com",
headers={"Authorization": f"Bearer {os.getenv('API_TOKEN')}"}
)OAuth2 with Token Refresh
class OAuth2Client:
def __init__(self):
self.access_token = None
self.expires_at = None
async def get_token(self) -> str:
if not self.expires_at or datetime.now() > self.expires_at:
await self.refresh_token()
return self.access_token
async def refresh_token(self):
async with httpx.AsyncClient() as client:
response = await client.post(
"https://auth.example.com/token",
data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
)
data = response.json()
self.access_token = data["access_token"]
self.expires_at = datetime.now() + timedelta(
seconds=data["expires_in"] - 60
)
oauth = OAuth2Client()
@mcp.tool()
async def authenticated_request(endpoint: str) -> dict:
token = await oauth.get_token()
async with httpx.AsyncClient() as client:
response = await client.get(
endpoint,
headers={"Authorization": f"Bearer {token}"}
)
return response.json()Error Handling Patterns
Basic Error Handling
@mcp.tool()
async def safe_api_call(endpoint: str) -> dict:
try:
response = await client.get(endpoint)
response.raise_for_status()
return {"success": True, "data": response.json()}
except httpx.HTTPStatusError as e:
return {
"success": False,
"error": f"HTTP {e.response.status_code}",
"message": e.response.text
}
except httpx.TimeoutException:
return {"success": False, "error": "Request timeout"}
except Exception as e:
return {"success": False, "error": str(e)}Retry with Exponential Backoff
async def retry_with_backoff(func, max_retries=3):
delay = 1.0
for attempt in range(max_retries):
try:
return await func()
except (httpx.TimeoutException, httpx.NetworkError) as e:
if attempt < max_retries - 1:
await asyncio.sleep(delay)
delay *= 2
else:
raiseCaching Patterns
Simple Time-Based Cache
import time
class SimpleCache:
def __init__(self, ttl=300):
self.cache = {}
self.timestamps = {}
self.ttl = ttl
def get(self, key: str):
if key in self.cache:
if time.time() - self.timestamps[key] < self.ttl:
return self.cache[key]
return None
def set(self, key: str, value):
self.cache[key] = value
self.timestamps[key] = time.time()
cache = SimpleCache()
@mcp.tool()
async def cached_fetch(endpoint: str) -> dict:
# Check cache
cached = cache.get(endpoint)
if cached:
return {"data": cached, "from_cache": True}
# Fetch from API
data = await fetch_from_api(endpoint)
cache.set(endpoint, data)
return {"data": data, "from_cache": False}Rate Limiting Patterns
Simple Rate Limiter
from collections import deque
from datetime import datetime, timedelta
class RateLimiter:
def __init__(self, max_requests: int, time_window: int):
self.max_requests = max_requests
self.time_window = timedelta(seconds=time_window)
self.requests = deque()
async def acquire(self):
now = datetime.now()
# Remove old requests
while self.requests and now - self.requests[0] > self.time_window:
self.requests.popleft()
# Check limit
if len(self.requests) >= self.max_requests:
sleep_time = (self.requests[0] + self.time_window - now).total_seconds()
await asyncio.sleep(sleep_time)
return await self.acquire()
self.requests.append(now)
limiter = RateLimiter(100, 60) # 100 requests per minute
@mcp.tool()
async def rate_limited_call(endpoint: str) -> dict:
await limiter.acquire()
return await api_call(endpoint)Connection Pooling
Singleton Client Pattern
class APIClient:
_instance = None
@classmethod
async def get_client(cls):
if cls._instance is None:
cls._instance = httpx.AsyncClient(
base_url=API_BASE_URL,
timeout=30.0,
limits=httpx.Limits(
max_keepalive_connections=5,
max_connections=10
)
)
return cls._instance
@classmethod
async def cleanup(cls):
if cls._instance:
await cls._instance.aclose()
cls._instance = None
# Use in tools
@mcp.tool()
async def api_request(endpoint: str) -> dict:
client = await APIClient.get_client()
response = await client.get(endpoint)
return response.json()Batch Request Patterns
Parallel Batch Requests
@mcp.tool()
async def batch_fetch(endpoints: list[str]) -> dict:
"""Fetch multiple endpoints in parallel."""
async def fetch_one(endpoint: str):
try:
response = await client.get(endpoint)
return {"endpoint": endpoint, "success": True, "data": response.json()}
except Exception as e:
return {"endpoint": endpoint, "success": False, "error": str(e)}
results = await asyncio.gather(*[fetch_one(ep) for ep in endpoints])
return {
"total": len(endpoints),
"successful": len([r for r in results if r["success"]]),
"results": results
}Webhook Patterns
Webhook Receiver
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhook")
async def handle_webhook(request: Request):
data = await request.json()
# Process webhook
return {"status": "received"}
# Add to MCP server
mcp = FastMCP.from_fastapi(app)When to Use Each Pattern
| Pattern | Use When | Avoid When |
|---|---|---|
| Manual Integration | Simple API, custom logic needed | API has 50+ endpoints |
| OpenAPI Auto-gen | Well-documented API, many endpoints | No OpenAPI spec available |
| FastAPI Conversion | Existing FastAPI app | Starting from scratch |
| Custom Route Maps | Need precise control | Simple use case |
| Connection Pooling | High-frequency requests | Single request needed |
| Caching | Expensive API calls, data rarely changes | Real-time data required |
| Rate Limiting | API has rate limits | No limits or internal API |
Resources
- FastMCP OpenAPI: FastMCP.from_openapi documentation
- FastAPI Integration: FastMCP.from_fastapi documentation
- HTTPX Docs: https://www.python-httpx.org
- OpenAPI Spec: https://spec.openapis.org
Related skills
How it compares
Use mcp-builder for production MCP API clients with resilience; use mcp2cli when you only need quick terminal exploration without building a server.
FAQ
What does mcp- do?
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 LL
When should I use mcp-?
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 LL
What are common prerequisites?
--- name: mcp- description: "Build MCP servers in Python with FastMCP.
Is Mcp Builder safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.