mcp-lab
by NicolasVegaQ
README.md
# mcp-lab
Servidor **MCP** mínimo (transporte `streamable-http`) para probar MCP desde ChatGPT
desplegado en **Dokploy/Traefik**. Construido con el SDK oficial de MCP en Python.
> **Nota sobre la versión del SDK:** se usa la rama **v1.x** del SDK (`mcp[cli]>=1.0,<2`),
> que es la que mantiene la API `FastMCP` (`from mcp.server.fastmcp import FastMCP`).
> En **mcp 2.x** `FastMCP` fue renombrado a `MCPServer` y su API cambió; por eso se fija
> `mcp<2` para usar exactamente la API que describe este laboratorio.
## Arquitectura
```
Internet
│ HTTPS :443
▼
Dokploy / Traefik
│ Docker network
▼
mcp-lab:8000 (escucha en 0.0.0.0 dentro del contenedor)
│
├── /mcp → protocolo MCP streamable-http (endpoint para ChatGPT)
└── /health → healthcheck seguro ({"status":"ok"})
```
```
mcp-lab/
├── pyproject.toml # metadata + dependencias (mcp[cli]>=1.0,<2) + script mcp-lab
├── server.py # FastMCP: tools, /mcp, /health, env vars, seguridad
├── data.py # dataset falso de personas + notas en memoria (thread-safe)
├── Dockerfile # imagen Python 3.12-slim, usuario no-root
├── .dockerignore
├── .gitignore
├── .env.example # plantilla de variables de entorno
└── README.md
```
## Tools expuestas
| Tool | Args | Descripción |
|---|---|---|
| `ping` | – | Health: `{"status":"ok","message":"pong"}` |
| `calculator` | `a: float`, `b: float`, `operation: str` | `add/subtract/multiply/divide` (valida división por cero) |
| `search_people` | `name: str` | Resumen pequeño `{id, name}` por coincidencia parcial |
| `get_person` | `person_id: int` | Ficha completa `{id, name, city, role}` |
| `create_note` | `title: str`, `content: str` | Crea nota en memoria |
| `list_notes` | – | Lista notas en memoria |
| `delete_note` | `note_id: int` | Elimina una nota |
Las notas viven **solo en memoria** (se pierden al reiniciar). No hay base de datos.
## Ejecución local
Requiere Python 3.12 (se usa `uv` para gestionar el entorno):
```bash
uv venv --python 3.12 .venv
source .venv/bin/activate
uv pip install -e . # instala deps incluyendo mcp[cli]
python server.py # o: uv run python server.py
```
El servidor arranca en `0.0.0.0:8000`. Endpoint MCP: `http://localhost:8000/mcp`.
Healthcheck: `http://localhost:8000/health`.
Prueba rápida con `curl` (health y rechazo de Host para /mcp):
```bash
curl -s http://localhost:8000/health # {"status":"ok"}
```
### Variables de entorno
| Variable | Default | Descripción |
|---|---|---|
| `MCP_HOST` | `0.0.0.0` | Interface de escucha |
| `MCP_PORT` | `8000` | Puerto interno |
| `MCP_ALLOWED_HOSTS` | *(vacío → loopback)* | Hostname(s) permitidos por la protección anti DNS-rebinding, separados por coma. Ej: `mcp.example.com` |
## Docker
```bash
docker build -t mcp-lab .
docker run --rm -p 8000:8000 -e MCP_ALLOWED_HOSTS=mcp.example.com mcp-lab
```
Para Docker/Dokploy usa **solo `expose`, nunca `ports`** (Traefik enruta al puerto interno):
```yaml
# docker-compose.yml (opcional)
services:
mcp-lab:
build: .
restart: unless-stopped
environment:
MCP_HOST: "0.0.0.0"
MCP_PORT: "8000"
MCP_ALLOWED_HOSTS: "mcp.example.com"
expose:
- "8000"
```
El proceso corre como usuario no-root, sin `--privileged`, sin socket Docker, y el
contenedor termina limpiamente con SIGTERM.
## Despliegue en Dokploy
1. Crea una aplicación (build) con el repositorio; Dokploy construirá la imagen con el `Dockerfile`.
2. Define las variables de entorno del servicio (`MCP_ALLOWED_HOSTS` con tu dominio).
3. Traefik enruta `https://mcp.example.com` → `mcp-lab:8000` (sin Nginx; el contenedor escucha en `0.0.0.0`).
4. En ChatGPT usa la URL del MCP: `https://mcp.example.com/mcp`.
## Seguridad
- Ninguna tool permite ejecución arbitraria (sin shell, sin Python arbitrario, sin SQL, sin FS, sin Docker).
- Validación de argumentos y límite de tamaño de body (1 MiB).
- Protección anti **DNS-rebinding** vía allowlist de `Host` (`MCP_ALLOWED_HOSTS`).
- `/health` solo devuelve `{"status":"ok"}`; no expone env, secretos ni filesystem.
- Errores controlados (sin stack traces completos hacia el cliente).
- Sin OAuth por ahora (datos falsos); la arquitectura (`custom_route`, separación de módulos) deja preparado el camino para añadirlo sin reescribir las tools.
## Verificación posterior desde ChatGPT
Con la app desplegada, abre ChatGPT (plataforma) y registra el MCP remoto con URL
`https://mcp.example.com/mcp`. El modelo debería descubrir las 7 tools y poder:
`ping` → `calculator` → `search_people("Carlos")` → `get_person(<id>)` →
`create_note` → `list_notes` → `delete_note`.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues