Creando Herramientas y Recursos Potentes
Creación de Herramientas y Recursos Potentes
Comprensión de los Componentes MCP
Los servidores FastMCP exponen tres tipos principales de componentes a los clientes LLM:
- Herramientas (Tools): Funciones que realizan acciones o cálculos.
- Recursos (Resources): Fuentes de datos que los clientes pueden leer.
- Prompts (Indicaciones): Plantillas de mensajes reutilizables para interacciones con LLM.
Exploremos cómo construir cada uno de ellos de forma eficaz.
Creación de Herramientas
Las herramientas son el motor de los servidores MCP. Permiten a los LLM realizar acciones, hacer cálculos, llamar a APIs e interactuar con sistemas externos.
Herramientas Básicas
Las herramientas más sencillas son funciones decoradas:
from fastmcp import FastMCP
mcp = FastMCP("Utility Server")
@mcp.tool
def calculate_area(length: float, width: float) -> float:
"""Calcula el área de un rectángulo."""
return length * width
@mcp.tool
def generate_password(length: int = 12, include_symbols: bool = True) -> str:
"""Genera una contraseña aleatoria."""
import random
import string
chars = string.ascii_letters + string.digits
if include_symbols:
chars += "!@#$%^&*"
return ''.join(random.choice(chars) for _ in range(length))
Herramientas Avanzadas con Contexto
Las herramientas pueden acceder al contexto MCP para realizar operaciones avanzadas:
from fastmcp import FastMCP, Context
import aiohttp
import json
mcp = FastMCP("API Tools")
@mcp.tool
async def fetch_weather(city: str, ctx: Context) -> dict:
"""Obtiene el clima actual para una ciudad."""
await ctx.info(f"Obteniendo clima para {city}...")
# Realiza una solicitud HTTP usando el contexto
response = await ctx.http_request(
"GET",
f"https://api.openweathermap.org/data/2.5/weather",
params={"q": city, "appid": "your_api_key"}
)
weather_data = response.json()
return {
"city": city,
"temperature": weather_data["main"]["temp"],
"description": weather_data["weather"][0]["description"],
"humidity": weather_data["main"]["humidity"]
}
@mcp.tool
async def analyze_text(text: str, ctx: Context) -> str:
"""Analiza texto usando el LLM del cliente."""
prompt = f"""
Por favor, analiza el siguiente texto y proporciona información sobre:
1. Sentimiento
2. Temas clave
3. Estilo de escritura
Texto: {text}
"""
# Usa el LLM del cliente para el análisis
response = await ctx.sample(prompt)
return response.text
Herramientas con Tipos de Retorno Complejos
Las herramientas pueden devolver varios tipos de datos, incluyendo medios:
from fastmcp import FastMCP
from fastmcp.types import Image, BlobResourceContents
import matplotlib.pyplot as plt
import io
import base64
mcp = FastMCP("Data Visualization")
@mcp.tool
def create_chart(data: list[float], chart_type: str = "line") -> Image:
"""Crea un gráfico a partir de datos."""
plt.figure(figsize=(10, 6))
if chart_type == "line":
plt.plot(data)
elif chart_type == "bar":
plt.bar(range(len(data)), data)
plt.title(f"Gráfico de {chart_type.title()}")
plt.xlabel("Índice")
plt.ylabel("Valor")
# Guarda en bytes
buffer = io.BytesIO()
plt.savefig(buffer, format='png')
buffer.seek(0)
# Convierte a base64
image_base64 = base64.b64encode(buffer.read()).decode()
return Image(
data=image_base64,
mimeType="image/png"
)
@mcp.tool
def process_data(numbers: list[float]) -> dict:
"""Procesa una lista de números y devuelve estadísticas."""
import statistics
if not numbers:
return {"error": "No se proporcionaron datos"}
return {
"count": len(numbers),
"sum": sum(numbers),
"mean": statistics.mean(numbers),
"median": statistics.median(numbers),
"std_dev": statistics.stdev(numbers) if len(numbers) > 1 else 0,
"min": min(numbers),
"max": max(numbers)
}
Creación de Recursos
Los recursos exponen datos que los LLM pueden leer e incorporar a su contexto. Son perfectos para datos de configuración, documentación o cualquier información de solo lectura.
Recursos Estáticos
Fuentes de datos simples con URIs fijas:
@mcp.resource("config://database")
def get_database_config() -> dict:
"""Configuración de la base de datos."""
return {
"host": "localhost",
"port": 5432,
"database": "myapp",
"ssl_enabled": True
}
@mcp.resource("docs://api-guide")
def get_api_documentation() -> str:
"""Documentación de uso de la API."""
return """
# Guía de Uso de la API
## Autenticación
Todas las solicitudes requieren una clave de API en el encabezado de Autorización.
## Límites de Tasa
- 1000 solicitudes por hora para el nivel básico
- 10000 solicitudes por hora para el nivel premium
## Puntos Finales Disponibles
- GET /users - Lista todos los usuarios
- POST /users - Crea un nuevo usuario
- GET /users/{id} - Obtiene detalles del usuario
"""
Plantillas de Recursos Dinámicos
Las plantillas permiten a los clientes solicitar datos específicos utilizando parámetros:
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: int) -> dict:
"""Obtiene la información del perfil de un usuario."""
# En una aplicación real, esto consultaría una base de datos
users_db = {
1: {"name": "Alice Johnson", "role": "Admin", "email": "[email protected]"},
2: {"name": "Bob Smith", "role": "User", "email": "[email protected]"},
3: {"name": "Carol Davis", "role": "Manager", "email": "[email protected]"}
}
user = users_db.get(user_id)
if not user:
raise ValueError(f"Usuario {user_id} no encontrado")
return {
"id": user_id,
**user,
"last_login": "2024-01-15T10:30:00Z",
"status": "active"
}
@mcp.resource("data://{dataset}/stats")
def get_dataset_stats(dataset: str) -> dict:
"""Obtiene estadísticas para un conjunto de datos específico."""
# Simula diferentes conjuntos de datos
datasets = {
"sales": {"records": 10000, "last_updated": "2024-01-15", "size_mb": 45.2},
"users": {"records": 5000, "last_updated": "2024-01-14", "size_mb": 12.8},
"products": {"records": 2500, "last_updated": "2024-01-13", "size_mb": 8.1}
}
if dataset not in datasets:
raise ValueError(f"Conjunto de datos '{dataset}' no encontrado")
return {
"dataset": dataset,
**datasets[dataset],
"access_level": "public"
}
Recursos Basados en Archivos
Los recursos también pueden servir contenido de archivos:
import os
from pathlib import Path
@mcp.resource("files://{file_path}")
def read_file_content(file_path: str) -> str:
"""Lee el contenido de un archivo en el directorio del proyecto."""
# Seguridad: restringe al directorio del proyecto
project_root = Path(__file__).parent
full_path = project_root / file_path
# Asegúrate de que la ruta esté dentro del directorio del proyecto
if not str(full_path.resolve()).startswith(str(project_root.resolve())):
raise ValueError("Acceso denegado: ruta fuera del directorio del proyecto")
if not full_path.exists():
raise FileNotFoundError(f"Archivo no encontrado: {file_path}")
return full_path.read_text(encoding='utf-8')
@mcp.resource("logs://{date}")
def get_daily_logs(date: str) -> str:
"""Obtiene los registros de la aplicación para una fecha específica."""
import datetime
try:
# Valida el formato de la fecha
datetime.datetime.strptime(date, "%Y-%m-%d")
except ValueError:
raise ValueError("La fecha debe estar en formato YYYY-MM-DD")
log_file = f"logs/{date}.log"
if not os.path.exists(log_file):
return f"No se encontraron registros para {date}"
with open(log_file, 'r') as f:
return f.read()
Creación de Prompts (Indicaciones)
Los prompts proporcionan plantillas reutilizables para las interacciones con LLM:
@mcp.prompt
def code_review_prompt(code: str, language: str = "python") -> str:
"""Genera una indicación para la revisión de código."""
return f"""
Por favor, revisa el siguiente código de {language} y proporciona comentarios sobre:
1. Calidad y legibilidad del código
2. Consideraciones de rendimiento
3. Problemas de seguridad
4. Adherencia a las mejores prácticas
5. Sugerencias de mejora
Código:
```{language}
{code}
```
Por favor, proporciona comentarios constructivos con ejemplos específicos.
"""
@mcp.prompt
def data_analysis_prompt(data_description: str, questions: list[str]) -> str:
"""Genera una indicación para el análisis de datos."""
questions_text = "\\n".join(f"- {q}" for q in questions)
return f"""
Necesito ayuda para analizar datos con las siguientes características:
{data_description}
Preguntas específicas que quiero explorar:
{questions_text}
Por favor, proporciona un enfoque de análisis estructurado y sugiere
métodos estadísticos o visualizaciones apropiadas.
"""
Manejo de Errores y Validación
Las herramientas robustas incluyen un manejo de errores adecuado:
@mcp.tool
def divide_numbers(a: float, b: float) -> float:
"""Divide dos números con manejo de errores."""
if b == 0:
raise ValueError("No se puede dividir por cero")
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("Ambos argumentos deben ser números")
return a / b
@mcp.tool
def process_email(email: str) -> dict:
"""Valida y procesa una dirección de correo electrónico."""
import re
# Validación básica de correo electrónico
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, email):
raise ValueError("Formato de correo electrónico no válido")
username, domain = email.split('@')
return {
"email": email,
"username": username,
"domain": domain,
"is_valid": True
}
Prueba de tus Componentes
Siempre prueba tus herramientas y recursos:
import asyncio
from fastmcp import Client
async def test_server():
async with Client(mcp) as client:
# Prueba de herramientas
result = await client.call_tool("calculate_area", {"length": 5, "width": 3})
print(f"Área: {result.text}")
# Prueba de recursos
config = await client.read_resource("config://database")
print(f"Configuración: {config.content}")
# Prueba de plantillas de recursos
user = await client.read_resource("users://1/profile")
print(f"Usuario: {user.content}")
if __name__ == "__main__":
asyncio.run(test_server())
Mejores Prácticas
- Documentación Clara: Siempre incluye docstrings descriptivos.
- Sugerencias de Tipos: Utiliza anotaciones de tipos adecuadas para la generación automática de esquemas.
- Manejo de Errores: Proporciona mensajes de error significativos.
- Seguridad: Valida las entradas y restringe el acceso a archivos de manera apropiada.
- Rendimiento: Considera operaciones asíncronas para tareas ligadas a E/S.
- Pruebas: Prueba exhaustivamente todos los componentes.
En la siguiente sección, exploraremos cómo integrar tu servidor FastMCP con Claude Code para flujos de trabajo de desarrollo sin interrupciones.