MCP server for CTFd that lets regular users browse challenges, manage dynamic instances, and submit flags.
Project description
CTFd MCP server (user scope)
MCP server that lets a regular CTFd user list challenges, read details, start/stop dynamic docker instances, and submit flags.
Requirements
- Python 3.13 (managed by
uv). - Environment variables (choose one auth method):
CTFD_URL(e.g. https://ctfd.example.com)CTFD_TOKEN(user token, not admin) orCTFD_SESSION(session cookie if tokens are disabled).CTFD_CSRF_TOKEN(optional, only if the server/plugin requires CSRF for ctfd-owl).
You can store them in a .env file in the repo root:
CTFD_URL=https://ctfd.example.com/
CTFD_USERNAME=your_username
CTFD_PASSWORD=your_password
# or, if you prefer to use a token:
# CTFD_TOKEN=your_ctfd_api_token_here
# or, if tokens are disabled:
# CTFD_SESSION=your_session_token_here
# and, if the owl plugin enforces CSRF:
# CTFD_CSRF_TOKEN=your_csrf_token_here
Install
- From PyPI (recommended):
uvx ctfd-mcp --help - From source checkout (no install):
uvx --from . ctfd-mcp --help
Run MCP server (stdio)
# installed from PyPI
uvx ctfd-mcp
# from local checkout
uvx --from . ctfd-mcp
Cursor and Claude MCP config example
{
"mcpServers": {
"ctfd-mcp": {
"command": "uvx",
"args": ["ctfd-mcp"],
"env": {
"CTFD_URL": "https://ctfd.example.com",
"CTFD_TOKEN": "your_user_token"
}
}
}
}
Codex MCP config example
[mcp_servers.ctfd-mcp]
command = "uvx"
args = ["ctfd-mcp"]
[mcp_servers.ctfd-mcp.env]
CTFD_URL = "https://ctfd.example.com"
CTFD_TOKEN = "your_user_token"
Exposed tools
list_challenges(category?, only_unsolved?)— list visible challenges, optional category/unsolved filter.challenge_details(challenge_id)— description (HTML +description_text), metadata, attachment URLs, solved status.submit_flag(challenge_id, flag)— attempt a flag; returns status/message.start_container(challenge_id)— unified start; auto-detects dynamic_docker, ctfd-owl or k8s/api/v1/k8s.stop_container(container_id?, challenge_id?)— unified stop; whale can be stopped with justcontainer_id, owl/k8s needchallenge_id.
Attachments are returned as absolute URLs in files; the client/host can fetch them directly.
MCP resources
resource://ctfd/challenges/{challenge_id}— markdown snapshot of a challenge (metadata, description, attachment URLs, connection info if present).
Error handling
- Missing env/config -> clear MCP error.
- 401/403 -> auth failed, check token or session cookie.
- 404 -> not found (or dynamic container API missing).
- 429 -> rate limited (Retry-After if present).
- Other HTTP/API errors -> surfaced as MCP errors with CTFd message/status.
Notes and troubleshooting
- Dynamic containers require the ctfd-whale (dynamic_docker) plugin on the target CTFd; otherwise
/api/v1/containersreturns 404. - Owl challenges (
dynamic_check_docker) use a different endpoint:/plugins/ctfd-owl/container?challenge_id=<id>. They usually require a session cookie, and some setups require a CSRF token; setCTFD_CSRF_TOKENif needed. - Some events expose Kubernetes-backed instances at
/api/v1/k8s/{get,create,delete}with multipart form data; the client will try these when the challenge type includesk8s(or when a dynamic_docker endpoint is missing). - If the server redirects you to
/login(302) when using a token, switch to a browser session cookie: setCTFD_SESSIONfrom thesessioncookie after logging in. - The client now supports logging in with
CTFD_USERNAMEandCTFD_PASSWORD; these fields take precedence over stale tokens/sessions. - Auth priority: username/password first, then token, then session cookie. Lower-priority credentials are ignored when a higher-priority option is present.
Support / feedback
If something breaks or you have questions, reach out:
- Telegram: @ismailgaleev
- Jabber: ismailgaleev@chat.merlok.ru
- Email: umbra2728@gmail.com
Testing
- Run
uv run pytest. - Timeouts are configurable via env:
CTFD_TIMEOUT(total),CTFD_CONNECT_TIMEOUT,CTFD_READ_TIMEOUT(seconds). Defaults are 20s total / 10s connect / 15s read.
Development
- Dev dependencies:
uv sync --group dev - Lint/format:
uv run ruff check .anduv run ruff format . - Tests:
uv run pytest - Pre-commit:
uv run pre-commit install(seeCONTRIBUTING.md)
License
Apache-2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ctfd_mcp-0.1.2.tar.gz.
File metadata
- Download URL: ctfd_mcp-0.1.2.tar.gz
- Upload date:
- Size: 56.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7e693b113625c03cb523755a24a23bfbd5e7996a129cfb4d104db38fc170c5c
|
|
| MD5 |
b0dd299338c1ef46e98da99df8dd5504
|
|
| BLAKE2b-256 |
19a43b1f6048bed86815d14df468b21573d06f9c7c07b3710e92a02c863823ce
|
Provenance
The following attestation bundles were made for ctfd_mcp-0.1.2.tar.gz:
Publisher:
publish.yml on umbra2728/ctfd-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ctfd_mcp-0.1.2.tar.gz -
Subject digest:
b7e693b113625c03cb523755a24a23bfbd5e7996a129cfb4d104db38fc170c5c - Sigstore transparency entry: 911309486
- Sigstore integration time:
-
Permalink:
umbra2728/ctfd-mcp@d2ec2059f032fd72dee94d4594188a88a7608082 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/umbra2728
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d2ec2059f032fd72dee94d4594188a88a7608082 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ctfd_mcp-0.1.2-py3-none-any.whl.
File metadata
- Download URL: ctfd_mcp-0.1.2-py3-none-any.whl
- Upload date:
- Size: 22.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a32dfbe346b18e24af2f4f5a194b3629f510b63972e60b3717c8c190d728afa
|
|
| MD5 |
9dccdb2b3cd885c8995093c77db574dc
|
|
| BLAKE2b-256 |
e0647a9f2565ff661e3a3718a1da928c0fce6c9c0e54d8e6d54df11df176ca9a
|
Provenance
The following attestation bundles were made for ctfd_mcp-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on umbra2728/ctfd-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ctfd_mcp-0.1.2-py3-none-any.whl -
Subject digest:
0a32dfbe346b18e24af2f4f5a194b3629f510b63972e60b3717c8c190d728afa - Sigstore transparency entry: 911309567
- Sigstore integration time:
-
Permalink:
umbra2728/ctfd-mcp@d2ec2059f032fd72dee94d4594188a88a7608082 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/umbra2728
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d2ec2059f032fd72dee94d4594188a88a7608082 -
Trigger Event:
push
-
Statement type: