Initial commit
This commit is contained in:
259
templates/openapi-integration.py
Normal file
259
templates/openapi-integration.py
Normal file
@@ -0,0 +1,259 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user