Ctfd Mcp Server

Configuración de MCP para vincular agentes de IA con una instancia de CTFd.

Documentación

Servidor MCP de CTFd

PyPI - Version PyPI - Python Versions Docker Pulls License: MIT GitHub Stars

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 de stdio o sse.
  • 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 de solved/unsolved, además de recuperación de detalles por desafío.
  • Envío seguro de banderas — requiere un confirm=True explí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 para POSTs (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_URL se 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

VariablePredeterminadoSignificado
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_TIMEOUT15Tiempo de espera HTTP por solicitud (segundos)
CTFD_HTTP_MAX_REDIRECTS5Redirecciones máximas seguidas por solicitud
CTFD_STATE_FILE~/.local/state/ctfd-mcp/server_state.jsonArchivo utilizado para almacenar en caché el estado de autenticación
CTFD_MCP_TRANSPORTstdioTransporte MCP: stdio o sse
MCP_HOST / MCP_PORT127.0.0.1 / 8000Configuració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_IPSfalsePermitir conexiones a direcciones privadas/loopback/metadatos (p. ej., instancias de prueba CTFd locales)
CTFD_DOWNLOAD_DIR./downloadsDirectorio donde download_file guarda los archivos adjuntos de desafíos
CTFD_PERSIST_SECRETSfalse⚠ Muy desaconsejado: escribir secretos en disco

CTFD_BASE_URL puede incluir un prefijo de ruta (p. ej., https://host/ctfd); el cliente agrega /api/v1 automá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

HerramientaParámetrosDescripción
set_base_urlurlApunta el servidor a una instancia CTFd
set_tokentokenAdopta un token de API (solo memoria)
set_cookiecookieAdopta una cookie de sesión (solo memoria)
loginusername, passwordInicio de sesión con formulario; conserva la cookie de sesión
challengescategory, search, solved, page, per_pageLista de desafíos paginada con filtros
challengeidentifier (id o nombre)Detalle completo de un desafío
submit_flagflag, challenge_name/challenge_id, confirmEnvía una bandera (requiere confirm=True)
download_filefile_url, dest_dirDescarga un archivo adjunto de desafío a través de la ruta /files/… de CTFd
unlock_hinthint_idDesbloquea 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 honra Authorization: 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 encabezado CSRF-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étodoRutaDescripción
POST/set_base_urlValida y establece la URL base de CTFd
POST/set_tokenEstablece el token de API
POST/set_cookieEstablece la cookie de sesión
POST/set_credsAlmacena usuario/contraseña para inicio de sesión posterior
POST/loginInicio de sesión con formulario (cookie de sesión)
GET/challengesLista de desafíos paginada + filtrada
GET/challenges/{id-or-name}Detalle del desafío
POST/submitEnvía una bandera (confirm: true requerido)
POST/downloadDescarga un archivo adjunto de desafío (cuerpo: file_url)
POST/unlock_hintDesbloquea y lee una pista (cuerpo: hint_id)
GET/scoreboardClasificaciones públicas
GET/progressTu puntuación y resoluciones
GET/instance_infoMetadatos públicos de la instancia
GET/auth_statusModo de autenticación + validez
GET/healthVerificació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án Authorization: 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. Habilitar CTFD_PERSIST_SECRETS está 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_url requiere una URL http(s) absoluta, submit_flag requiere confirm=True, etc.).
  • Reintentos controlados. Solo se reintentan (una vez) las solicitudes GET idempotentes. 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=1 opta 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/v1 utilizadas 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.
  • difficulty no es un campo estándar de CTFd; en su lugar se devuelve el value del desafío (puntos).
  • El filtrado de solved utiliza la bandera solved_by_me de 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/v1 a 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/challenges en 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:

  1. Abra un issue describiendo el cambio.
  2. Agregue pruebas en tests/ (se prefiere la API de CTFd simulada).
  3. Ejecute python -m pytest -q y ruff check server ctfd_mcp_server.py tests.
  4. No incluya credenciales en código, pruebas, ni confirme server_state.json / .env.

Licencia

MIT — repositorio: https://github.com/MrJamescot/ctfd-mcp-server