Ctfd Mcp Server

Configuração do MCP para conectar agentes de IA a uma instância do CTFd.

Documentação

Servidor MCP CTFd

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

Um servidor Model Context Protocol (MCP) para interagir com qualquer instância CTFd v3. Ele permite que ferramentas de IA (Claude Desktop, Cursor, agentes personalizados, ...) autentiquem, listem e inspecionem desafios, enviem flags e consultem o estado da instância por meio de uma interface estável e type-safe.

O projeto oferece duas interfaces construídas sobre a mesma biblioteca cliente:

  • Ferramentas MCP (principal) — ctfd_mcp_server.py, usadas via stdio ou sse.
  • API REST (opcional) — server/main.py, um espelho FastAPI para scripts, depuração e implantações 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    │
                              └───────────┘

As credenciais (token / cookie / senha) ficam apenas em memória e nunca são exibidas na saída das ferramentas, gravadas em server_state.json ou registradas em logs.


Recursos

  • Múltiplos modos de autenticação — token de API, cookie de sessão ou login por formulário com nome de usuário/senha (com tratamento de CSRF).
  • Consultas ricas de desafios — listagem paginada com category, search (nome), e filtros solved/unsolved, além da recuperação de detalhes por desafio.
  • Envio seguro de flags — exige um confirm=True explícito, retorna sucesso/fracasso claros e expõe erros de limite de taxa. Flags nunca são registradas em logs.
  • Introspecção da instância — informações públicas da instância, verificação de saúde e uma ferramenta de status de autenticação que não revelam segredos.
  • Erros estruturados consistentes — AuthenticationError, CTFdAPIError, ChallengeNotFoundError, SubmissionError, ValidationError, ConfigurationError.
  • Paginação por padrão — uma página de desafios por chamada; sem downloads acidentais de dados completos.
  • HTTP reforçado — timeouts configuráveis, uma nova tentativa segura para GETs idempotentes, sem novas tentativas para POSTs (sem envios duplicados), análise estrita de JSON/conteúdo.
  • REST + MCP a partir de um único código-fonte — comportamento idêntico em ambas as interfaces.
  • Sem instância fixa — BASE_URL é validado e configurável na inicialização e em tempo de execução.

Instalação

Requer Python 3.10+.

A maneira mais rápida é instalar a partir do 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, aponte sua configuração para o entry point empacotado:

{
  "mcpServers": {
    "ctfd-mcp": {
      "command": "ctfd-mcp",
      "env": {
        "CTFD_BASE_URL": "https://demo.ctfd.io",
        "CTFD_ADMIN_TOKEN": "ctfd_..."
      }
    }
  }
}

Ou execute a partir do código-fonte:

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

Configuração

VariávelPadrãoSignificado
CTFD_BASE_URL(vazio)Raiz da instância CTFd, ex.: https://ctf.example.com (sem /api/v1)
CTFD_ADMIN_TOKEN(vazio)Token de API (autenticação preferida)
CTFD_SESSION_COOKIE(vazio)Cookie de sessão, ex.: session=abc...
CTFD_USERNAME(vazio)Nome de usuário para login por formulário
CTFD_PASSWORD(vazio)Senha para login por formulário
CTFD_HTTP_TIMEOUT15Timeout HTTP por requisição (segundos)
CTFD_HTTP_MAX_REDIRECTS5Máximo de redirecionamentos seguidos por requisição
CTFD_STATE_FILE~/.local/state/ctfd-mcp/server_state.jsonArquivo usado para armazenar em cache o estado de autenticação
CTFD_MCP_TRANSPORTstdioTransporte MCP: stdio ou sse
MCP_HOST / MCP_PORT127.0.0.1 / 8000Configurações de bind do servidor REST (loopback por padrão)
CTFD_API_TOKEN(vazio)Token bearer opcional que protege a API REST opcional (/api/v1/*)
CTFD_ALLOW_PRIVATE_IPSfalsePermitir conexões a endereços privados/loopback/metadata (ex.: instâncias CTFd de teste locais)
CTFD_DOWNLOAD_DIR./downloadsDiretório onde os anexos dos desafios são salvos por download_file
CTFD_PERSIST_SECRETSfalse⚠ Fortemente desencorajado: gravar segredos em disco

CTFD_BASE_URL pode incluir um prefixo de caminho (ex.: https://host/ctfd); o cliente acrescenta /api/v1 automaticamente.


Executando o Servidor MCP

A maioria dos clientes MCP inicia o servidor por conta própria via uma configuração command/args. Para isso, a configuração do seu cliente deve referenciar 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_..."
      }
    }
  }
}

Inicialização 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

Ferramentas MCP

FerramentaParâmetrosDescrição
set_base_urlurlAponta o servidor para uma instância CTFd
set_tokentokenAdota um token de API (apenas em memória)
set_cookiecookieAdota um cookie de sessão (apenas em memória)
loginusername, passwordLogin por formulário; mantém o cookie de sessão
challengescategory, search, solved, page, per_pageLista paginada de desafios com filtros
challengeidentifier (id ou nome)Detalhe completo de um desafio
submit_flagflag, challenge_name/challenge_id, confirmEnvia uma flag (exige confirm=True)
download_filefile_url, dest_dirBaixa um anexo de desafio pela rota /files/… do CTFd
unlock_hinthint_idDesbloqueia e lê uma dica (dicas pagas custam pontos)
scoreboard—Classificação pública do placar
progress—Sua pontuação + desafios resolvidos
instance_info—Metadados públicos seguros da instância
auth_status—Modo de autenticação + validade (sem segredos)
health—Verificações de alcance, API e autenticação

Com autenticação por token, as requisições são enviadas com Content-Type: application/json (o CTFd só honra Authorization: Token ... em requisições JSON). Com autenticação por cookie/credenciais, requisições que alteram estado ecoam o nonce CSRF da sessão como o cabeçalho CSRF-Token, que é reobtido do site após o login.

As ferramentas retornam texto JSON. Os erros são estruturados, ex.:

{ "error": { "type": "ChallengeNotFoundError", "message": "Challenge '99' not found (or not visible)." } }

Executando a 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 sob /api/v1):

MétodoCaminhoDescrição
POST/set_base_urlValida e define a URL base do CTFd
POST/set_tokenDefine o token de API
POST/set_cookieDefine o cookie de sessão
POST/set_credsArmazena nome de usuário/senha para login posterior
POST/loginLogin por formulário (cookie de sessão)
GET/challengesLista paginada + filtrada de desafios
GET/challenges/{id-or-name}Detalhe do desafio
POST/submitEnvia uma flag (confirm: true obrigatório)
POST/downloadBaixa um anexo de desafio (corpo: file_url)
POST/unlock_hintDesbloqueia e lê uma dica (corpo: hint_id)
GET/scoreboardClassificação pública
GET/progressSua pontuação e resoluções
GET/instance_infoMetadados públicos da instância
GET/auth_statusModo de autenticação + validade
GET/healthVerificação de saúde

A API REST opcional pode ser protegida com um token bearer extra: defina CTFD_API_TOKEN, e requisições a /api/v1/* exigirão Authorization: Bearer <token>. O servidor faz bind no loopback por padrão (MCP_HOST=127.0.0.1).


Docker

Uma imagem pronta é publicada no 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

Ou construa 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 também funciona (API REST em http://localhost:8000).

Para executar o servidor MCP SSE em um contêiner:

docker run --rm -it -e CTFD_BASE_URL=https://ctf.example.com ctfd-mcp python ctfd_mcp_server.py
# stdio on the attached terminal

Exemplos 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

Consulte DEMO.md para um passo a passo completo e examples/ para trechos de código curl e Python.


Desenvolvimento e testes

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

A suíte de testes simula a API do CTFd (tests/conftest.py::FakeGateway), portanto os testes unitários são executados offline.

Testes de integração contra um CTFd real

Execute um CTFd local para testes ao vivo (recomendado em vez da instância de demonstração compartilhada, que serve HTML em rotas públicas protegidas por autenticação):

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

Considerações de segurança

  • Credenciais apenas em memória. Por padrão, nada é gravado em server_state.json. Habilitar CTFD_PERSIST_SECRETS é desencorajado.
  • Segredos nunca são ecoados. Respostas de ferramentas e API, mensagens de erro e logs ocultam tokens, cookies, senhas e flags (server/utils.py).
  • Cada ferramenta valida sua entrada antes de tocar a rede (set_base_url exige uma URL http(s) absoluta, submit_flag exige confirm=True, etc.).
  • Novas tentativas controladas. Apenas requisições GET idempotentes são repetidas (uma vez). Envios de flags nunca são reproduzidos automaticamente.
  • Modelo de confiança. O servidor é uma ferramenta local/de desenvolvimento: quem puder chamar suas ferramentas pode apontá-lo para qualquer instância CTFd e (com uma credencial válida) ler dados ou enviar flags. Não exponha os endpoints REST/SSE em uma rede não confiável.
  • Proteção SSRF. Por padrão, conexões a endereços privados / loopback / link-local / metadata são recusadas (CTFD_ALLOW_PRIVATE_IPS=1 opta por sair). Um contêiner local ou uma ferramenta apontada para uma instância privada receberá um erro claro.

Limitações

  • Requer CTFd v3+. As rotas /api/v1 usadas são rotas padrão da API do CTFd v3.
  • O login por formulário depende do fluxo de sessão web do CTFd (a extração do nonce CSRF é feita da melhor forma possível). Tokens de API são o método de autenticação recomendado.
  • difficulty não é um campo padrão do CTFd; o value do desafio (pontos) é retornado em seu lugar.
  • A filtragem por solved usa a flag solved_by_me do CTFd, que só é significativa quando autenticado.
  • A "versão" da instância é informada apenas quando aparece na página renderizada; o CTFd não possui um endpoint público de API para versão.
  • Tokens são por instância. O CTFd redireciona chamadas /api/v1 para sua página de login quando uma credencial é inválida. O servidor detecta isso e reporta: "a credencial não é válida para ESTA instância" — um token de uma instância CTFd nunca funciona em outra.
  • Quando CTFD_USERNAME/CTFD_PASSWORD estão configurados, o servidor faz login automático sob demanda (rotacionando o cookie de sessão) sempre que uma chamada retorna não autenticada, então sessões expiradas se auto-reparam. Após cada login, o nonce CSRF é reobtido do site (um nonce novo é necessário para envios de flag via sessão web).
  • Uma conta sem equipe não vê /api/v1/challenges em instâncias no modo equipe até que entre ou crie uma equipe; o servidor exibe a mensagem de permissão do CTFd.

Contribuindo

Pull requests são bem-vindos. Por favor:

  1. Abra uma issue descrevendo a mudança.
  2. Adicione testes em tests/ (API do CTFd mockada é preferida).
  3. Execute python -m pytest -q e ruff check server ctfd_mcp_server.py tests.
  4. Não inclua credenciais em código, testes ou commits server_state.json / .env.

Licença

MIT — repositório: https://github.com/MrJamescot/ctfd-mcp-server