FastMCP: La guía completa para crear servidores y clientes de MCP

FastMCP: La guía completa para crear servidores y clientes de MCP

Un completo tutorial sobre FastMCP 2.0, la forma rápida y "pythónica" de construir servidores y clientes basados en el Protocolo de Contexto del Modelo (MCP). Aprende a crear herramientas, recursos e instrucciones para tus aplicaciones LLM.

Lección 5 2025-07-03 07:00

Patrones Avanzados y Despliegue en Producción

Patrones Avanzados y Despliegue en Producción

A medida que sus aplicaciones FastMCP evolucionan de ser simples herramientas de desarrollo a servicios listos para producción, necesitará considerar patrones avanzados, escalabilidad, seguridad y estrategias de despliegue. Esta sección cubre el desarrollo y despliegue de FastMCP a nivel empresarial.

Patrones Avanzados de Servidor

Composición y Modularidad del Servidor

FastMCP 2.0 es compatible con sofisticados patrones de composición de servidores que permiten aplicaciones modulares y mantenibles:

# auth_server.py
from fastmcp import FastMCP
import jwt
import bcrypt

auth_server = FastMCP("Servicio de Autenticación")

@auth_server.tool
def authenticate_user(username: str, password: str) -> dict:
    """Autentica a un usuario y devuelve un token JWT."""
    # Implementación para la autenticación de usuarios
    if verify_credentials(username, password):
        token = jwt.encode({"user": username}, "secret", algorithm="HS256")
        return {"token": token, "user": username, "authenticated": True}
    return {"authenticated": False, "error": "Credenciales inválidas"}

# data_server.py
from fastmcp import FastMCP
import pandas as pd

data_server = FastMCP("Servicio de Procesamiento de Datos")

@data_server.tool
def process_dataset(data_path: str, operation: str) -> dict:
    """Procesa un conjunto de datos con operaciones especificadas."""
    df = pd.read_csv(data_path)

    operations = {
        "summary": lambda df: df.describe().to_dict(),
        "count": lambda df: {"rows": len(df), "columns": len(df.columns)},
        "columns": lambda df: df.columns.tolist()
    }

    if operation not in operations:
        raise ValueError(f"Operación desconocida: {operation}")

    return operations[operation](df)

# main_server.py
from fastmcp import FastMCP
from auth_server import auth_server
from data_server import data_server

# Crear servidor principal y componer sub-servidores
main_server = FastMCP(
    name="Aplicación Empresarial",
    instructions="Aplicación empresarial completa con autenticación y procesamiento de datos"
)

# Montar sub-servidores con prefijos
main_server.mount(auth_server, prefix="auth")
main_server.mount(data_server, prefix="data")

# Añadir herramientas al servidor principal
@main_server.tool
def health_check() -> dict:
    """Verifica el estado de todos los servicios."""
    return {
        "status": "saludable",
        "services": ["auth", "data", "main"],
        "timestamp": datetime.now().isoformat()
    }

if __name__ == "__main__":
    main_server.run(transport="http", host="0.0.0.0", port=8000)

Servidores Proxy para Integración de API

Cree servidores proxy para conectar APIs existentes al ecosistema MCP:

# api_proxy.py
from fastmcp import FastMCP, Client
import asyncio

# Crear un proxy para un servidor MCP existente
async def create_api_proxy():
    # Conectarse al servidor MCP remoto
    remote_client = Client("https://api.example.com/mcp/sse")

    # Crear servidor proxy
    proxy_server = FastMCP.as_proxy(
        remote_client,
        name="Servidor Proxy de API",
        instructions="Proxy para servicios API remotos"
    )

    # Añadir middleware o herramientas personalizadas al proxy
    @proxy_server.tool
    def local_cache_status() -> dict:
        """Obtiene el estado de la caché local."""
        return {"cache_enabled": True, "cache_size": "50MB"}

    return proxy_server

# Uso
if __name__ == "__main__":
    proxy = asyncio.run(create_api_proxy())
    proxy.run(transport="http", port=9000)

Generación Dinámica de Herramientas

Genere herramientas dinámicamente a partir de configuraciones o especificaciones OpenAPI:

# dynamic_tools.py
from fastmcp import FastMCP
import yaml
import requests

mcp = FastMCP("Servidor API Dinámico")

def create_api_tool(endpoint_config):
    """Crea dinámicamente una herramienta a partir de la configuración de un endpoint de API."""
    def api_tool(**kwargs):
        response = requests.request(
            method=endpoint_config["method"],
            url=endpoint_config["url"],
            **kwargs
        )
        return response.json()

    # Establecer metadatos de la función
    api_tool.__name__ = endpoint_config["name"]
    api_tool.__doc__ = endpoint_config["description"]

    return api_tool

# Cargar configuración de la API
with open("api_config.yaml", "r") as f:
    api_config = yaml.safe_load(f)

# Generar herramientas dinámicamente
for endpoint in api_config["endpoints"]:
    tool_func = create_api_tool(endpoint)
    mcp.tool(tool_func)

# Alternativa: Generar a partir de especificaciones OpenAPI
def load_from_openapi(spec_url: str):
    """Carga herramientas de una especificación OpenAPI."""
    # FastMCP tiene integración OpenAPI incorporada
    api_server = FastMCP.from_openapi(spec_url)
    return api_server

# Uso
# openapi_server = load_from_openapi("https://api.example.com/openapi.json")
# mcp.mount(openapi_server, prefix="api")

Autenticación Lista para Producción

Autenticación Basada en JWT

# secure_server.py
from fastmcp import FastMCP
from fastmcp.auth import JWTAuthProvider
import os

# Configurar autenticación JWT
auth_provider = JWTAuthProvider(
    secret_key=os.getenv("JWT_SECRET_KEY"),
    algorithm="HS256",
    token_header="Authorization"
)

# Crear servidor autenticado
mcp = FastMCP(
    "Servidor de Producción Seguro",
    auth_provider=auth_provider
)

@mcp.tool(require_auth=True)
def get_sensitive_data(user_context) -> dict:
    """Obtiene datos sensibles - requiere autenticación."""
    return {
        "user_id": user_context.user_id,
        "data": "información sensible",
        "access_level": user_context.permissions
    }

@mcp.tool  # Herramienta pública, no requiere autenticación
def get_public_info() -> dict:
    """Obtiene información pública."""
    return {"status": "público", "version": "1.0.0"}

Autenticación por Clave API

# api_key_server.py
from fastmcp import FastMCP
from fastmcp.auth import APIKeyAuthProvider

# Configurar autenticación por clave API
auth_provider = APIKeyAuthProvider(
    api_keys={
        "key123": {"name": "Cliente A", "permissions": ["read", "write"]},
        "key456": {"name": "Cliente B", "permissions": ["read"]}
    },
    header_name="X-API-Key"
)

mcp = FastMCP("Servidor Protegido por Clave API", auth_provider=auth_provider)

@mcp.tool(require_permissions=["write"])
def write_data(data: dict, auth_context) -> dict:
    """Escribe datos - requiere permiso de escritura."""
    # Implementación
    return {"written": True, "client": auth_context.client_name}

Estrategias de Despliegue

Despliegue con Docker

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

# Instalar dependencias
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar aplicación
COPY . .

# Crear usuario no root
RUN useradd -m -u 1000 mcpuser && chown -R mcpuser:mcpuser /app
USER mcpuser

# Verificación de salud (Health check)
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# Ejecutar servidor
EXPOSE 8000
CMD ["python", "server.py"]
# docker-compose.yml
version: '3.8'

services:
  fastmcp-server:
    build: .
    ports:
      - "8000:8000"
    environment:
      - FASTMCP_LOG_LEVEL=INFO
      - JWT_SECRET_KEY=${JWT_SECRET_KEY}
      - DATABASE_URL=${DATABASE_URL}
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - fastmcp-server
    restart: unless-stopped

volumes:
  redis_data:

Despliegue en Kubernetes

# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fastmcp-server
  labels:
    app: fastmcp-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: fastmcp-server
  template:
    metadata:
      labels:
        app: fastmcp-server
    spec:
      containers:
      - name: fastmcp-server
        image: your-registry/fastmcp-server:latest
        ports:
        - containerPort: 8000
        env:
        - name: FASTMCP_LOG_LEVEL
          value: "INFO"
        - name: JWT_SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: fastmcp-secrets
              key: jwt-secret
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: fastmcp-service
spec:
  selector:
    app: fastmcp-server
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8000
  type: LoadBalancer

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: fastmcp-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
  - hosts:
    - api.yourdomain.com
    secretName: fastmcp-tls
  rules:
  - host: api.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: fastmcp-service
            port:
              number: 80

Monitorización y Observabilidad

Registro y Métricas

# monitored_server.py
from fastmcp import FastMCP, Context
import logging
import time
from prometheus_client import Counter, Histogram, start_http_server

# Configurar métricas
TOOL_CALLS = Counter('mcp_tool_calls_total', 'Llamadas totales a herramientas', ['tool_name', 'status'])
TOOL_DURATION = Histogram('mcp_tool_duration_seconds', 'Tiempo de ejecución de la herramienta', ['tool_name'])

# Configurar registro
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('fastmcp.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

mcp = FastMCP("Servidor Monitorizado")

def monitor_tool(func):
    """Decorador para añadir monitorización a las herramientas."""
    def wrapper(*args, **kwargs):
        start_time = time.time()
        tool_name = func.__name__

        try:
            logger.info(f"Iniciando herramienta: {tool_name}")
            result = func(*args, **kwargs)
            TOOL_CALLS.labels(tool_name=tool_name, status='success').inc()
            logger.info(f"Herramienta {tool_name} completada con éxito")
            return result
        except Exception as e:
            TOOL_CALLS.labels(tool_name=tool_name, status='error').inc()
            logger.error(f"Herramienta {tool_name} falló: {str(e)}")
            raise
        finally:
            duration = time.time() - start_time
            TOOL_DURATION.labels(tool_name=tool_name).observe(duration)
            logger.info(f"Herramienta {tool_name} tomó {duration:.2f}s")

    return wrapper

@mcp.tool
@monitor_tool
def process_data(data: list) -> dict:
    """Procesa datos con monitorización."""
    # Lógica de procesamiento
    return {"processed": len(data), "status": "success"}

# Iniciar servidor de métricas Prometheus
start_http_server(9090)

if __name__ == "__main__":
    mcp.run(transport="http", port=8000)

Verificaciones de Salud y Cortacircuitos

# resilient_server.py
from fastmcp import FastMCP, Context
import asyncio
from circuit_breaker import CircuitBreaker
import aioredis

mcp = FastMCP("Servidor Resiliente")

# Cortacircuitos para llamadas a la API externa
api_circuit_breaker = CircuitBreaker(
    failure_threshold=5,
    recovery_timeout=30,
    expected_exception=Exception
)

@mcp.tool
async def call_external_api(endpoint: str, ctx: Context) -> dict:
    """Llama a la API externa con protección de cortacircuitos."""
    @api_circuit_breaker
    async def make_api_call():
        # Implementación de la llamada real a la API
        async with aiohttp.ClientSession() as session:
            async with session.get(endpoint) as response:
                return await response.json()

    try:
        return await make_api_call()
    except Exception as e:
        await ctx.error(f"La llamada a la API falló: {str(e)}")
        return {"error": "Servicio temporalmente no disponible"}

@mcp.resource("health://status")
async def health_check() -> dict:
    """Verificación de salud exhaustiva."""
    health_status = {
        "status": "saludable",
        "timestamp": datetime.now().isoformat(),
        "checks": {}
    }

    # Verificar conexión a la base de datos
    try:
        # Lógica de verificación de salud de la base de datos
        health_status["checks"]["database"] = "saludable"
    except Exception:
        health_status["checks"]["database"] = "insalubre"
        health_status["status"] = "insalubre"

    # Verificar conexión a Redis
    try:
        redis = aioredis.from_url("redis://localhost")
        await redis.ping()
        health_status["checks"]["redis"] = "saludable"
    except Exception:
        health_status["checks"]["redis"] = "insalubre"
        health_status["status"] = "degradado"

    return health_status

Mejores Prácticas de Seguridad

Validación y Saneamiento de Entradas

# secure_tools.py
from fastmcp import FastMCP
from pydantic import BaseModel, validator
import re
import html

mcp = FastMCP("Servidor Seguro")

class UserInput(BaseModel):
    username: str
    email: str
    age: int

    @validator('username')
    def validate_username(cls, v):
        if not re.match(r'^[a-zA-Z0-9_]+$', v):
            raise ValueError('El nombre de usuario solo debe contener letras, números y guiones bajos')
        if len(v) < 3 or len(v) > 20:
            raise ValueError('El nombre de usuario debe tener entre 3 y 20 caracteres')
        return v

    @validator('email')
    def validate_email(cls, v):
        email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(email_pattern, v):
            raise ValueError('Formato de correo electrónico inválido')
        return v

    @validator('age')
    def validate_age(cls, v):
        if v < 0 or v > 150:
            raise ValueError('La edad debe estar entre 0 y 150')
        return v

@mcp.tool
def create_user(user_data: UserInput) -> dict:
    """Crea un usuario con entrada validada."""
    # La entrada es validada automáticamente por Pydantic
    return {
        "user_id": generate_user_id(),
        "username": user_data.username,
        "email": user_data.email,
        "status": "creado"
    }

@mcp.tool
def sanitize_html_content(content: str) -> str:
    """Sanea contenido HTML para prevenir XSS."""
    # Saneamiento básico de HTML
    sanitized = html.escape(content)
    return sanitized

Limitación de Tasa y Throttling

# rate_limited_server.py
from fastmcp import FastMCP
from fastmcp.middleware import RateLimitMiddleware
import asyncio

# Configurar limitación de tasa
rate_limiter = RateLimitMiddleware(
    requests_per_minute=60,
    burst_size=10,
    storage_backend="redis",  # o "memory"
    redis_url="redis://localhost:6379"
)

mcp = FastMCP("Servidor con Tasa Limitada", middleware=[rate_limiter])

@mcp.tool
def expensive_operation(data: str) -> dict:
    """Una operación costosa que debería tener una tasa limitada."""
    # Simular procesamiento costoso
    time.sleep(2)
    return {"processed": True, "data_length": len(data)}

Optimización del Rendimiento

Operaciones Asíncronas y Pool de Conexiones

# optimized_server.py
from fastmcp import FastMCP, Context
import asyncio
import aiohttp
import asyncpg
from contextlib import asynccontextmanager

class DatabasePool:
    def __init__(self):
        self.pool = None

    async def create_pool(self):
        self.pool = await asyncpg.create_pool(
            "postgresql://user:pass@localhost/db",
            min_size=10,
            max_size=20
        )

    async def close_pool(self):
        if self.pool:
            await self.pool.close()

db_pool = DatabasePool()

@asynccontextmanager
async def lifespan():
    # Inicio
    await db_pool.create_pool()
    yield
    # Apagado
    await db_pool.close_pool()

mcp = FastMCP("Servidor Optimizado", lifespan=lifespan)

@mcp.tool
async def query_database(query: str) -> dict:
    """Ejecuta una consulta a la base de datos utilizando el pool de conexiones."""
    async with db_pool.pool.acquire() as connection:
        result = await connection.fetch(query)
        return {"rows": [dict(row) for row in result]}

@mcp.tool
async def batch_http_requests(urls: list[str], ctx: Context) -> list[dict]:
    """Realiza múltiples peticiones HTTP concurrentemente."""
    await ctx.info(f"Realizando {len(urls)} peticiones concurrentes...")

    async with aiohttp.ClientSession() as session:
        tasks = []
        for url in urls:
            task = session.get(url)
            tasks.append(task)

        responses = await asyncio.gather(*tasks, return_exceptions=True)

        results = []
        for i, response in enumerate(responses):
            if isinstance(response, Exception):
                results.append({"url": urls[i], "error": str(response)})
            else:
                results.append({
                    "url": urls[i],
                    "status": response.status,
                    "data": await response.text()
                })

        return results

Conclusión

FastMCP 2.0 proporciona una plataforma completa para construir servidores MCP listos para producción que pueden escalar desde simples herramientas de desarrollo hasta aplicaciones de nivel empresarial. Puntos clave:

Mejores Prácticas de Desarrollo

  • Utilice la composición modular de servidores para un código mantenible
  • Implemente un manejo de errores y validación exhaustivos
  • Añada un registro y una monitorización adecuados desde el principio
  • Pruebe a fondo sus herramientas y recursos

Preparación para la Producción

  • Implemente una autenticación y autorización adecuadas
  • Utilice configuraciones basadas en el entorno
  • Añada verificaciones de salud y cortacircuitos
  • Monitorice el rendimiento y el uso de recursos

Estrategias de Despliegue

  • Contenerice sus aplicaciones para mayor consistencia
  • Utilice plataformas de orquestación como Kubernetes para escalar
  • Implemente pipelines CI/CD adecuados
  • Planifique la alta disponibilidad y la recuperación ante desastres

Consideraciones de Seguridad

  • Valide y sanee todas las entradas
  • Implemente la limitación de tasa y el throttling
  • Utilice HTTPS y una autenticación adecuada
  • Realice auditorías y actualizaciones de seguridad regulares

FastMCP permite construir sofisticadas aplicaciones impulsadas por IA que se integran a la perfección con los flujos de trabajo de desarrollo modernos y la infraestructura de producción. Ya sea que esté construyendo herramientas de desarrollo, servicios de procesamiento de datos o complejas aplicaciones empresariales, FastMCP le proporciona la base que necesita para tener éxito.

¡Feliz construcción con FastMCP! 🚀