Ctfd Mcp Server
Configuração do MCP para conectar agentes de IA a uma instância do CTFd.
Documentação
Servidor MCP CTFd
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 viastdioousse. - 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 filtrossolved/unsolved, além da recuperação de detalhes por desafio. - Envio seguro de flags — exige um
confirm=Trueexplí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 paraPOSTs (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ável | Padrão | Significado |
|---|---|---|
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_TIMEOUT | 15 | Timeout HTTP por requisição (segundos) |
CTFD_HTTP_MAX_REDIRECTS | 5 | Máximo de redirecionamentos seguidos por requisição |
CTFD_STATE_FILE | ~/.local/state/ctfd-mcp/server_state.json | Arquivo usado para armazenar em cache o estado de autenticação |
CTFD_MCP_TRANSPORT | stdio | Transporte MCP: stdio ou sse |
MCP_HOST / MCP_PORT | 127.0.0.1 / 8000 | Configuraçõ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_IPS | false | Permitir conexões a endereços privados/loopback/metadata (ex.: instâncias CTFd de teste locais) |
CTFD_DOWNLOAD_DIR | ./downloads | Diretório onde os anexos dos desafios são salvos por download_file |
CTFD_PERSIST_SECRETS | false | ⚠ Fortemente desencorajado: gravar segredos em disco |
CTFD_BASE_URLpode incluir um prefixo de caminho (ex.:https://host/ctfd); o cliente acrescenta/api/v1automaticamente.
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
| Ferramenta | Parâmetros | Descrição |
|---|---|---|
set_base_url | url | Aponta o servidor para uma instância CTFd |
set_token | token | Adota um token de API (apenas em memória) |
set_cookie | cookie | Adota um cookie de sessão (apenas em memória) |
login | username, password | Login por formulário; mantém o cookie de sessão |
challenges | category, search, solved, page, per_page | Lista paginada de desafios com filtros |
challenge | identifier (id ou nome) | Detalhe completo de um desafio |
submit_flag | flag, challenge_name/challenge_id, confirm | Envia uma flag (exige confirm=True) |
download_file | file_url, dest_dir | Baixa um anexo de desafio pela rota /files/… do CTFd |
unlock_hint | hint_id | Desbloqueia 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ó honraAuthorization: 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çalhoCSRF-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étodo | Caminho | Descrição |
|---|---|---|
| POST | /set_base_url | Valida e define a URL base do CTFd |
| POST | /set_token | Define o token de API |
| POST | /set_cookie | Define o cookie de sessão |
| POST | /set_creds | Armazena nome de usuário/senha para login posterior |
| POST | /login | Login por formulário (cookie de sessão) |
| GET | /challenges | Lista paginada + filtrada de desafios |
| GET | /challenges/{id-or-name} | Detalhe do desafio |
| POST | /submit | Envia uma flag (confirm: true obrigatório) |
| POST | /download | Baixa um anexo de desafio (corpo: file_url) |
| POST | /unlock_hint | Desbloqueia e lê uma dica (corpo: hint_id) |
| GET | /scoreboard | Classificação pública |
| GET | /progress | Sua pontuação e resoluções |
| GET | /instance_info | Metadados públicos da instância |
| GET | /auth_status | Modo de autenticação + validade |
| GET | /health | Verificaçã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ãoAuthorization: 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. HabilitarCTFD_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_urlexige uma URLhttp(s)absoluta,submit_flagexigeconfirm=True, etc.). - Novas tentativas controladas. Apenas requisições
GETidempotentes 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=1opta 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/v1usadas 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.
difficultynão é um campo padrão do CTFd; ovaluedo desafio (pontos) é retornado em seu lugar.- A filtragem por
solvedusa a flagsolved_by_medo 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/v1para 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_PASSWORDestã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/challengesem 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:
- Abra uma issue descrevendo a mudança.
- Adicione testes em
tests/(API do CTFd mockada é preferida). - Execute
python -m pytest -qeruff check server ctfd_mcp_server.py tests. - 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