Si los 20.000 servidores MCP del catálogo no resuelven exactamente tu caso, escribir uno propio es más fácil de lo que parece. En 30 minutos vas a tener un servidor MCP en Python ejecutándose contra Claude Desktop, con tools custom y manejo correcto de errores.
Por qué Python para tu primer servidor MCP
El SDK oficial mcp en Python tiene una API limpia, async-friendly, y aprovecha el ecosistema científico (pandas, requests, sqlalchemy) que probablemente ya conoces. Si vas a integrar APIs internas o lógica de procesamiento de datos, Python es la ruta más rápida. Si tu servidor va a ser distribuible y consumir poca memoria, considera TypeScript/Rust después.
Setup del proyecto
Usa uv de Astral, no pip directo: arranca proyectos en segundos y maneja dependencias correctamente.
uv init mi-mcp-server
cd mi-mcp-server
uv add "mcp[cli]"
Esto te genera un pyproject.toml y un entorno aislado. Crea server.py con el esqueleto mínimo:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("mi-mcp-server")
@mcp.tool()
def saludar(nombre: str) -> str:
"""Devuelve un saludo personalizado."""
return f"Hola, {nombre}, desde mi servidor MCP."
if __name__ == "__main__":
mcp.run()
Pruébalo localmente:
uv run mcp dev server.py
Abre el inspector que arranca en localhost — verás la tool saludar registrada y la podrás invocar manualmente.
Conectar a Claude Desktop
Edita claude_desktop_config.json:
{
"mcpServers": {
"mi-server": {
"command": "uv",
"args": ["--directory", "/ruta/absoluta/a/mi-mcp-server", "run", "server.py"]
}
}
}
Reinicia Claude Desktop completo. La tool aparece en el ícono de martillo.
Tools con argumentos complejos y validación
FastMCP usa los type hints de Python para autovalidar argumentos. Para casos avanzados, declara tipos Pydantic:
from pydantic import BaseModel, Field
class CrearTareaArgs(BaseModel):
titulo: str = Field(..., min_length=3, max_length=120)
prioridad: int = Field(default=2, ge=1, le=5)
tags: list[str] = []
@mcp.tool()
def crear_tarea(args: CrearTareaArgs) -> dict:
# Tu lógica aquí: insertar en DB, llamar API, etc.
return {"id": 42, "titulo": args.titulo}
El modelo recibe el schema generado y lo respeta — menos alucinaciones, mejores invocaciones.
Manejo de errores: explícito, no excepciones desnudas
No dejes que las excepciones de Python lleguen al cliente como tracebacks crípticos. Captura y devuelve mensajes accionables:
@mcp.tool()
def consultar_api(endpoint: str) -> dict:
try:
r = requests.get(f"https://api.ejemplo.com/{endpoint}", timeout=10)
r.raise_for_status()
return r.json()
except requests.Timeout:
return {"error": "La API tardó más de 10 segundos. Reintenta o reduce el alcance."}
except requests.HTTPError as e:
return {"error": f"API devolvió {e.response.status_code}: {e.response.text[:200]}"}
El modelo lee el campo error y reacciona en lenguaje natural en vez de mostrar un wall of text al usuario.
Resources y prompts (más allá de tools)
MCP soporta tres primitivas: tools (acciones), resources (datos referenciables) y prompts (templates parametrizados). Para un servidor de notas, expón cada nota como un resource en vez de una tool — el modelo puede leerlo proactivamente sin invocar nada.
Empaquetar y publicar en PyPI
Cuando esté maduro, publica para que otros lo instalen con uvx tu-paquete:
uv build
uv publish
Y luego envíalo al directorio MCP·es para que aparezca en el catálogo en español.
Buenas prácticas finales
- Logs a stderr, no stdout — stdout es el canal del protocolo, escribir ahí lo rompe.
- Idempotencia en tools que mutan estado.
- Timeouts explícitos en cualquier llamada de red.
- Documenta el docstring de cada tool — eso es lo que el modelo ve para decidir cuándo invocarla.