Skip to main content
Glama
NicolasVegaQ

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`.