Initial commit
This commit is contained in:
456
references/integration-patterns.md
Normal file
456
references/integration-patterns.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# 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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
client = httpx.AsyncClient(
|
||||
base_url="https://api.example.com",
|
||||
headers={"X-API-Key": os.getenv("API_KEY")}
|
||||
)
|
||||
```
|
||||
|
||||
### Bearer Token
|
||||
|
||||
```python
|
||||
client = httpx.AsyncClient(
|
||||
base_url="https://api.example.com",
|
||||
headers={"Authorization": f"Bearer {os.getenv('API_TOKEN')}"}
|
||||
)
|
||||
```
|
||||
|
||||
### OAuth2 with Token Refresh
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
@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
|
||||
|
||||
```python
|
||||
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:
|
||||
raise
|
||||
```
|
||||
|
||||
## Caching Patterns
|
||||
|
||||
### Simple Time-Based Cache
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
@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
|
||||
|
||||
```python
|
||||
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
|
||||
Reference in New Issue
Block a user