Ctfd Mcp Server
Configuración de MCP para vincular agentes de IA con una instancia de CTFd.
Documentación
Servidor MCP de CTFd
Un servidor de Protocolo de Contexto de Modelo (MCP) para interactuar con cualquier instancia de CTFd v3. Permite que herramientas de IA (Claude Desktop, Cursor, agentes personalizados, ...) se autentiquen, listen e inspeccionen desafíos, envíen banderas y consulten el estado de la instancia a través de una interfaz estable y segura de tipos.
El proyecto incluye dos interfaces construidas sobre la misma biblioteca cliente:
- Herramientas MCP (principal) —
ctfd_mcp_server.py, utilizadas a través destdioosse. - API REST (opcional) —
server/main.py, un espejo FastAPI para scripting, depuración e implementaciones Docker.
┌──────────────────────────────────────────────┐
AI agent / MCP │ FastMCP (MCP tools) │
client ────────►│ set_token · login · challenges · submit_flag │
└──────────────────┬───────────────────────────┘
│ shared client
┌──────────────────▼───────────────────────────┐
curl / scripts ─►│ FastAPI REST (/api/v1/...) (optional) │
└──────────────────┬───────────────────────────┘
│
┌──────────────────▼───────────────────────────┐
│ server.ctfd_client.CTFdClient │
│ └─ gateway.py (HTTP, auth, timeouts) │
└──────────────────┬───────────────────────────┘
│ HTTPS / HTTP
┌─────▼─────┐
│ CTFd │
└───────────┘
Las credenciales (token / cookie / contraseña) viven solo en memoria y nunca se
muestran en la salida de las herramientas, se escriben en server_state.json ni se registran.
Características
- Múltiples modos de autenticación — token de API, cookie de sesión o inicio de sesión con formulario de usuario/contraseña (con manejo de CSRF).
- Consultas enriquecidas de desafíos — listado paginado con
category,search(nombre), y filtros desolved/unsolved, además de recuperación de detalles por desafío. - Envío seguro de banderas — requiere un
confirm=Trueexplícito, devuelve éxito/fracaso claro y muestra errores de límite de velocidad. Las banderas nunca se registran. - Introspección de instancia — información pública de la instancia, verificación de salud y una herramienta de estado de autenticación que no revela secretos.
- Errores estructurados consistentes —
AuthenticationError,CTFdAPIError,ChallengeNotFoundError,SubmissionError,ValidationError,ConfigurationError. - Paginación por defecto — una página de desafíos por llamada; sin descargas accidentales de volcados completos.
- HTTP endurecido — tiempos de espera configurables, un reintento seguro para
GETs idempotentes, sin reintentos paraPOSTs (sin envíos duplicados), análisis estricto de JSON/contenido. - REST + MCP desde un solo código base — comportamiento idéntico en ambas interfaces.
- Sin instancia codificada —
BASE_URLse valida y configura al inicio y en tiempo de ejecución.
Instalación
Requiere Python 3.10+.
La forma más rápida es instalar desde PyPI:
pip install ctfd-mcp-server
# MCP stdio server with env config:
CTFD_BASE_URL=https://ctf.example.com CTFD_ADMIN_TOKEN=ctfd_... ctfd-mcp
# optional REST interface:
ctfd-rest
Para clientes MCP, apunta tu configuración al punto de entrada empaquetado:
{
"mcpServers": {
"ctfd-mcp": {
"command": "ctfd-mcp",
"env": {
"CTFD_BASE_URL": "https://demo.ctfd.io",
"CTFD_ADMIN_TOKEN": "ctfd_..."
}
}
}
}
O ejecuta desde el código fuente:
git clone https://github.com/MrJamescot/ctfd-mcp-server.git
cd ctfd-mcp-server
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then edit .env
Configurar
| Variable | Predeterminado | Significado |
|---|---|---|
CTFD_BASE_URL | (vacío) | Raíz de la instancia CTFd, p. ej. https://ctf.example.com (sin /api/v1) |
CTFD_ADMIN_TOKEN | (vacío) | Token de API (autenticación preferida) |
CTFD_SESSION_COOKIE | (vacío) | Cookie de sesión, p. ej. session=abc... |
CTFD_USERNAME | (vacío) | Nombre de usuario para inicio de sesión con formulario |
CTFD_PASSWORD | (vacío) | Contraseña para inicio de sesión con formulario |
CTFD_HTTP_TIMEOUT | 15 | Tiempo de espera HTTP por solicitud (segundos) |
CTFD_HTTP_MAX_REDIRECTS | 5 | Redirecciones máximas seguidas por solicitud |
CTFD_STATE_FILE | ~/.local/state/ctfd-mcp/server_state.json | Archivo utilizado para almacenar en caché el estado de autenticación |
CTFD_MCP_TRANSPORT | stdio | Transporte MCP: stdio o sse |
MCP_HOST / MCP_PORT | 127.0.0.1 / 8000 | Configuración de enlace del servidor REST (loopback por defecto) |
CTFD_API_TOKEN | (vacío) | Token portador opcional que protege la API REST opcional (/api/v1/*) |
CTFD_ALLOW_PRIVATE_IPS | false | Permitir conexiones a direcciones privadas/loopback/metadatos (p. ej., instancias de prueba CTFd locales) |
CTFD_DOWNLOAD_DIR | ./downloads | Directorio donde download_file guarda los archivos adjuntos de desafíos |
CTFD_PERSIST_SECRETS | false | ⚠ Muy desaconsejado: escribir secretos en disco |
CTFD_BASE_URLpuede incluir un prefijo de ruta (p. ej.,https://host/ctfd); el cliente agrega/api/v1automáticamente.
Ejecutar el servidor MCP
La mayoría de los clientes MCP inician el servidor ellos mismos mediante una configuración de command/args.
Para eso, la configuración de tu cliente debe hacer referencia a ctfd_mcp_server.py:
// e.g. Claude Desktop / mcp.json
{
"mcpServers": {
"ctfd-mcp": {
"command": "python",
"args": ["/path/to/ctfd-mcp-server/ctfd_mcp_server.py"],
"env": {
"CTFD_BASE_URL": "https://demo.ctfd.io",
"CTFD_ADMIN_TOKEN": "ctfd_..."
}
}
}
}
Inicio manual:
# stdio (default) — used by MCP clients
python ctfd_mcp_server.py
# SSE — expose over HTTP for remote/Docker use
CTFD_MCP_TRANSPORT=sse python ctfd_mcp_server.py # http://127.0.0.1:8000/sse
Herramientas MCP
| Herramienta | Parámetros | Descripción |
|---|---|---|
set_base_url | url | Apunta el servidor a una instancia CTFd |
set_token | token | Adopta un token de API (solo memoria) |
set_cookie | cookie | Adopta una cookie de sesión (solo memoria) |
login | username, password | Inicio de sesión con formulario; conserva la cookie de sesión |
challenges | category, search, solved, page, per_page | Lista de desafíos paginada con filtros |
challenge | identifier (id o nombre) | Detalle completo de un desafío |
submit_flag | flag, challenge_name/challenge_id, confirm | Envía una bandera (requiere confirm=True) |
download_file | file_url, dest_dir | Descarga un archivo adjunto de desafío a través de la ruta /files/… de CTFd |
unlock_hint | hint_id | Desbloquea y lee una pista (las pistas de pago cuestan puntos) |
scoreboard | — | Clasificaciones públicas del marcador |
progress | — | Tu puntuación + desafíos resueltos |
instance_info | — | Metadatos públicos seguros de la instancia |
auth_status | — | Modo de autenticación + validez (sin secretos) |
health | — | Verificaciones de alcance, API y autenticación |
Con autenticación por token, las solicitudes se envían con
Content-Type: application/json(CTFd solo honraAuthorization: Token ...en solicitudes JSON). Con autenticación por cookie/credenciales, las solicitudes que cambian el estado repiten el nonce CSRF de la sesión como encabezadoCSRF-Token, que se vuelve a obtener del sitio después del inicio de sesión.
Las herramientas devuelven texto JSON. Los errores están estructurados, p. ej.:
{ "error": { "type": "ChallengeNotFoundError", "message": "Challenge '99' not found (or not visible)." } }
Ejecutar la API REST (opcional)
python scripts/run_local.sh # reads .env, default http://127.0.0.1:8000
# or
uvicorn server.main:app --host 0.0.0.0 --port 8000
Endpoints (todos bajo /api/v1):
| Método | Ruta | Descripción |
|---|---|---|
| POST | /set_base_url | Valida y establece la URL base de CTFd |
| POST | /set_token | Establece el token de API |
| POST | /set_cookie | Establece la cookie de sesión |
| POST | /set_creds | Almacena usuario/contraseña para inicio de sesión posterior |
| POST | /login | Inicio de sesión con formulario (cookie de sesión) |
| GET | /challenges | Lista de desafíos paginada + filtrada |
| GET | /challenges/{id-or-name} | Detalle del desafío |
| POST | /submit | Envía una bandera (confirm: true requerido) |
| POST | /download | Descarga un archivo adjunto de desafío (cuerpo: file_url) |
| POST | /unlock_hint | Desbloquea y lee una pista (cuerpo: hint_id) |
| GET | /scoreboard | Clasificaciones públicas |
| GET | /progress | Tu puntuación y resoluciones |
| GET | /instance_info | Metadatos públicos de la instancia |
| GET | /auth_status | Modo de autenticación + validez |
| GET | /health | Verificación de salud |
La API REST opcional se puede proteger con un token portador adicional: establece
CTFD_API_TOKEN, y las solicitudes a/api/v1/*requeriránAuthorization: Bearer <token>. El servidor se enlaza a loopback por defecto (MCP_HOST=127.0.0.1).
Docker
Hay una imagen lista publicada en Docker Hub:
docker run --rm -p 8000:8000 \
-e CTFD_BASE_URL=https://ctf.example.com \
-e CTFD_ADMIN_TOKEN=ctfd_... \
jamescot/ctfd-mcp-server
O compila localmente (modo REST):
docker build -t ctfd-mcp .
docker run --rm -p 8000:8000 \
-e CTFD_BASE_URL=https://ctf.example.com \
-e CTFD_ADMIN_TOKEN=ctfd_... \
ctfd-mcp
docker compose up --build también funciona (API REST en http://localhost:8000).
Para ejecutar el servidor MCP SSE en un contenedor en su lugar:
docker run --rm -it -e CTFD_BASE_URL=https://ctf.example.com ctfd-mcp python ctfd_mcp_server.py
# stdio on the attached terminal
Ejemplos de uso
MCP (agente)
1. set_base_url url="https://ctf.example.com"
2. set_token token="ctfd_..."
3. challenges category="web", solved=false, page=1, per_page=25
4. challenge identifier="3"
5. submit_flag flag="flag{...}", challenge_id=3, confirm=true
REST
curl -X POST http://localhost:8000/api/v1/set_base_url \
-H 'Content-Type: application/json' -d '{"url":"https://ctf.example.com"}'
curl -X POST http://localhost:8000/api/v1/set_token \
-H 'Content-Type: application/json' -d '{"token":"ctfd_..."}'
curl 'http://localhost:8000/api/v1/challenges?search=web&solved=false&per_page=10'
curl -X POST http://localhost:8000/api/v1/submit \
-H 'Content-Type: application/json' \
-d '{"challenge_id":3,"flag":"flag{...}","confirm":true}'
curl http://localhost:8000/api/v1/health
Consulta DEMO.md para un recorrido completo y
examples/ para fragmentos de curl y Python.
Desarrollo y pruebas
pip install -r requirements-dev.txt
python -m pytest -q # 76 unit tests, mocked CTFd API (no network)
ruff check server ctfd_mcp_server.py tests
El conjunto de pruebas simula la API de CTFd (tests/conftest.py::FakeGateway), por lo que las pruebas
unitarias se ejecutan sin conexión.
Pruebas de integración contra un CTFd real
Ejecuta un CTFd local para pruebas en vivo (recomendado sobre la instancia de demostración compartida, que sirve HTML en rutas públicas protegidas por autenticación):
git clone https://github.com/CTFd/CTFd.git /tmp/CTFd
docker compose -f /tmp/CTFd/docker-compose.yml up
# create a user/challenge, then:
CTFD_BASE_URL=http://localhost:8000 python ctfd_mcp_server.py
CTFD_BASE_URL=http://localhost:8000 uvicorn server.main:app --port 8001
curl http://localhost:8001/api/v1/health
Consideraciones de seguridad
- Las credenciales son solo de memoria. Por defecto, nada se escribe en
server_state.json. HabilitarCTFD_PERSIST_SECRETSestá desaconsejado. - Los secretos nunca se repiten. Las respuestas de herramientas y API, mensajes de error y registros
redactan tokens, cookies, contraseñas y banderas (
server/utils.py). - Cada herramienta valida su entrada antes de tocar la red (
set_base_urlrequiere una URLhttp(s)absoluta,submit_flagrequiereconfirm=True, etc.). - Reintentos controlados. Solo se reintentan (una vez) las solicitudes
GETidempotentes. Los envíos de banderas nunca se reproducen automáticamente. - Modelo de confianza. El servidor es una herramienta local/de desarrollo: quien pueda llamar a sus herramientas puede apuntarlo a cualquier instancia CTFd y (con una credencial válida) leer datos o enviar banderas. No expongas los endpoints REST/SSE en una red no confiable.
- Protección SSRF. Por defecto, se rechazan las conexiones a direcciones privadas / loopback / enlace local /
metadatos (
CTFD_ALLOW_PRIVATE_IPS=1opta por no participar). Un contenedor local o una herramienta apuntada a una instancia privada recibirá un error claro.
Limitaciones
- Requiere CTFd v3+. Las rutas
/api/v1utilizadas son rutas estándar de la API de CTFd v3. - El inicio de sesión por formulario depende del flujo de sesión web de CTFd (la extracción del nonce CSRF es un esfuerzo de mejor aproximación). Se recomiendan los tokens de API como método de autenticación.
difficultyno es un campo estándar de CTFd; en su lugar se devuelve elvaluedel desafío (puntos).- El filtrado de
solvedutiliza la banderasolved_by_mede CTFd, que solo tiene significado cuando se está autenticado. - La "versión" de la instancia solo se informa cuando aparece en la página renderizada; CTFd no tiene un endpoint público de API para la versión.
- Los tokens son por instancia. CTFd redirige las llamadas
/api/v1a su página de inicio de sesión cuando una credencial no es válida. El servidor detecta esto e informa: "la credencial no es válida para ESTA instancia" — un token de una instancia de CTFd nunca funciona en otra. - Cuando se configuran
CTFD_USERNAME/CTFD_PASSWORD, el servidor inicia sesión automáticamente bajo demanda (rotando la cookie de sesión) cada vez que una llamada devuelve un estado no autenticado, por lo que las sesiones expiradas se auto-reparan. Después de cada inicio de sesión, el nonce CSRF se vuelve a obtener del sitio (se requiere un nonce nuevo para el envío de banderas a través de una sesión web). - Una cuenta sin equipo no ve ningún
/api/v1/challengesen instancias de modo equipo hasta que se une o crea un equipo; el servidor muestra el mensaje de permiso de CTFd.
Contribuciones
Las solicitudes de extracción (pull requests) son bienvenidas. Por favor:
- Abra un issue describiendo el cambio.
- Agregue pruebas en
tests/(se prefiere la API de CTFd simulada). - Ejecute
python -m pytest -qyruff check server ctfd_mcp_server.py tests. - No incluya credenciales en código, pruebas, ni confirme
server_state.json/.env.
Licencia
MIT — repositorio: https://github.com/MrJamescot/ctfd-mcp-server