ctfd-mcp-server 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ctfd_mcp_server-1.0.0/LICENSE +21 -0
- ctfd_mcp_server-1.0.0/PKG-INFO +395 -0
- ctfd_mcp_server-1.0.0/README.md +358 -0
- ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/PKG-INFO +395 -0
- ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/SOURCES.txt +28 -0
- ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/dependency_links.txt +1 -0
- ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/entry_points.txt +3 -0
- ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/requires.txt +14 -0
- ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/top_level.txt +2 -0
- ctfd_mcp_server-1.0.0/ctfd_mcp_server.py +246 -0
- ctfd_mcp_server-1.0.0/pyproject.toml +57 -0
- ctfd_mcp_server-1.0.0/server/__init__.py +0 -0
- ctfd_mcp_server-1.0.0/server/config.py +92 -0
- ctfd_mcp_server-1.0.0/server/ctfd_client.py +592 -0
- ctfd_mcp_server-1.0.0/server/errors.py +63 -0
- ctfd_mcp_server-1.0.0/server/file_cache.py +31 -0
- ctfd_mcp_server-1.0.0/server/gateway.py +201 -0
- ctfd_mcp_server-1.0.0/server/health.py +15 -0
- ctfd_mcp_server-1.0.0/server/main.py +193 -0
- ctfd_mcp_server-1.0.0/server/models.py +29 -0
- ctfd_mcp_server-1.0.0/server/session_manager.py +79 -0
- ctfd_mcp_server-1.0.0/server/setup.py +29 -0
- ctfd_mcp_server-1.0.0/server/state_manager.py +171 -0
- ctfd_mcp_server-1.0.0/server/utils.py +116 -0
- ctfd_mcp_server-1.0.0/setup.cfg +4 -0
- ctfd_mcp_server-1.0.0/tests/test_api.py +160 -0
- ctfd_mcp_server-1.0.0/tests/test_client.py +474 -0
- ctfd_mcp_server-1.0.0/tests/test_config.py +132 -0
- ctfd_mcp_server-1.0.0/tests/test_gateway.py +79 -0
- ctfd_mcp_server-1.0.0/tests/test_mcp_tools.py +135 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jamescot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctfd-mcp-server
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Model Context Protocol (MCP) server for CTFd v3 instances
|
|
5
|
+
Author: MrJamescot
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MrJamescot/ctfd-mcp-server
|
|
8
|
+
Project-URL: Repository, https://github.com/MrJamescot/ctfd-mcp-server
|
|
9
|
+
Project-URL: Issues, https://github.com/MrJamescot/ctfd-mcp-server/issues
|
|
10
|
+
Keywords: mcp,ctfd,ctf,ai,agent,llm
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: aiohttp<4,>=3.9
|
|
25
|
+
Requires-Dist: pydantic>=2.7
|
|
26
|
+
Requires-Dist: pydantic-settings>=2.2
|
|
27
|
+
Requires-Dist: python-dotenv>=1.0
|
|
28
|
+
Requires-Dist: mcp>=1.0
|
|
29
|
+
Requires-Dist: fastmcp>=2.0
|
|
30
|
+
Provides-Extra: rest
|
|
31
|
+
Requires-Dist: fastapi>=0.110; extra == "rest"
|
|
32
|
+
Requires-Dist: uvicorn>=0.29; extra == "rest"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# CTFd MCP Server
|
|
39
|
+
|
|
40
|
+
[](https://pypi.org/project/ctfd-mcp-server/)
|
|
41
|
+
[](https://pypi.org/project/ctfd-mcp-server/)
|
|
42
|
+
[](https://hub.docker.com/r/jamescot/ctfd-mcp-server)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
[](https://github.com/MrJamescot/ctfd-mcp-server)
|
|
45
|
+
|
|
46
|
+
A Model Context Protocol (MCP) server for interacting with any **CTFd v3** instance.
|
|
47
|
+
It lets AI tools (Claude Desktop, Cursor, custom agents, ...) authenticate, list
|
|
48
|
+
and inspect challenges, submit flags, and query instance state through a stable,
|
|
49
|
+
type-safe interface.
|
|
50
|
+
|
|
51
|
+
The project ships two interfaces built on the **same** client library:
|
|
52
|
+
|
|
53
|
+
- **MCP tools** (primary) — `ctfd_mcp_server.py`, used over `stdio` or `sse`.
|
|
54
|
+
- **REST API** (optional) — `server/main.py`, a FastAPI mirror for scripting,
|
|
55
|
+
debugging, and Docker deployments.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
┌──────────────────────────────────────────────┐
|
|
59
|
+
AI agent / MCP │ FastMCP (MCP tools) │
|
|
60
|
+
client ────────►│ set_token · login · challenges · submit_flag │
|
|
61
|
+
└──────────────────┬───────────────────────────┘
|
|
62
|
+
│ shared client
|
|
63
|
+
┌──────────────────▼───────────────────────────┐
|
|
64
|
+
curl / scripts ─►│ FastAPI REST (/api/v1/...) (optional) │
|
|
65
|
+
└──────────────────┬───────────────────────────┘
|
|
66
|
+
│
|
|
67
|
+
┌──────────────────▼───────────────────────────┐
|
|
68
|
+
│ server.ctfd_client.CTFdClient │
|
|
69
|
+
│ └─ gateway.py (HTTP, auth, timeouts) │
|
|
70
|
+
└──────────────────┬───────────────────────────┘
|
|
71
|
+
│ HTTPS / HTTP
|
|
72
|
+
┌─────▼─────┐
|
|
73
|
+
│ CTFd │
|
|
74
|
+
└───────────┘
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Credentials (token / cookie / password) live **in memory only** and are never
|
|
78
|
+
echoed in tool output, written to `server_state.json`, or logged.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Features
|
|
83
|
+
|
|
84
|
+
- **Multiple authentication modes** — API token, session cookie, or username/password
|
|
85
|
+
form login (with CSRF handling).
|
|
86
|
+
- **Rich challenge queries** — paginated listing with `category`, `search` (name),
|
|
87
|
+
and `solved`/`unsolved` filters, plus per-challenge detail retrieval.
|
|
88
|
+
- **Safe flag submission** — requires an explicit `confirm=True`, returns clear
|
|
89
|
+
success/failure, and surfaces rate-limit errors. Flags are never logged.
|
|
90
|
+
- **Instance introspection** — public instance info, health check, and an
|
|
91
|
+
authentication-status tool that reveal no secrets.
|
|
92
|
+
- **Consistent structured errors** — `AuthenticationError`, `CTFdAPIError`,
|
|
93
|
+
`ChallengeNotFoundError`, `SubmissionError`, `ValidationError`,
|
|
94
|
+
`ConfigurationError`.
|
|
95
|
+
- **Pagination by default** — one page of challenges per call; no accidental
|
|
96
|
+
full-dump downloads.
|
|
97
|
+
- **Hardened HTTP** — configurable timeouts, one safe retry for idempotent `GET`s,
|
|
98
|
+
no retries for `POST`s (no duplicate submissions), strict JSON/content parsing.
|
|
99
|
+
- **REST + MCP from one codebase** — identical behaviour on both interfaces.
|
|
100
|
+
- **No hardcoded instance** — `BASE_URL` is validated and configurable at startup
|
|
101
|
+
and at runtime.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Installation
|
|
106
|
+
|
|
107
|
+
Requires Python 3.10+.
|
|
108
|
+
|
|
109
|
+
The fastest way is to install from **[PyPI](https://pypi.org/project/ctfd-mcp-server/)**:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install ctfd-mcp-server
|
|
113
|
+
|
|
114
|
+
# MCP stdio server with env config:
|
|
115
|
+
CTFD_BASE_URL=https://ctf.example.com CTFD_ADMIN_TOKEN=ctfd_... ctfd-mcp
|
|
116
|
+
|
|
117
|
+
# optional REST interface:
|
|
118
|
+
ctfd-rest
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For MCP clients, point your config at the packaged entry point:
|
|
122
|
+
|
|
123
|
+
```jsonc
|
|
124
|
+
{
|
|
125
|
+
"mcpServers": {
|
|
126
|
+
"ctfd-mcp": {
|
|
127
|
+
"command": "ctfd-mcp",
|
|
128
|
+
"env": {
|
|
129
|
+
"CTFD_BASE_URL": "https://demo.ctfd.io",
|
|
130
|
+
"CTFD_ADMIN_TOKEN": "ctfd_..."
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Or run from source:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
git clone https://github.com/MrJamescot/ctfd-mcp-server.git
|
|
141
|
+
cd ctfd-mcp-server
|
|
142
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
143
|
+
pip install -r requirements.txt
|
|
144
|
+
|
|
145
|
+
cp .env.example .env # then edit .env
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Configure
|
|
149
|
+
|
|
150
|
+
| Variable | Default | Meaning |
|
|
151
|
+
| ---------------------- | --------------------- | ---------------------------------------------- |
|
|
152
|
+
| `CTFD_BASE_URL` | *(empty)* | CTFd instance root, e.g. `https://ctf.example.com` (without `/api/v1`) |
|
|
153
|
+
| `CTFD_ADMIN_TOKEN` | *(empty)* | API token (preferred auth) |
|
|
154
|
+
| `CTFD_SESSION_COOKIE` | *(empty)* | Session cookie, e.g. `session=abc...` |
|
|
155
|
+
| `CTFD_USERNAME` | *(empty)* | Username for form login |
|
|
156
|
+
| `CTFD_PASSWORD` | *(empty)* | Password for form login |
|
|
157
|
+
| `CTFD_HTTP_TIMEOUT` | `15` | Per-request HTTP timeout (seconds) |
|
|
158
|
+
| `CTFD_MCP_TRANSPORT` | `stdio` | MCP transport: `stdio` or `sse` |
|
|
159
|
+
| `MCP_HOST` / `MCP_PORT`| `0.0.0.0` / `8000` | REST server bind settings |
|
|
160
|
+
| `FILE_CACHE_DIR` | `./file_cache` | Where downloaded challenge files are stored |
|
|
161
|
+
| `CTFD_PERSIST_SECRETS` | `false` | ⚠ Strongly discouraged: write secrets to disk |
|
|
162
|
+
|
|
163
|
+
> `CTFD_BASE_URL` may include a path prefix (e.g. `https://host/ctfd`); the client
|
|
164
|
+
> appends `/api/v1` automatically.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Running the MCP Server
|
|
169
|
+
|
|
170
|
+
Most MCP clients launch the server themselves via a `command`/`args` config.
|
|
171
|
+
For that, your client config should reference **`ctfd_mcp_server.py`**:
|
|
172
|
+
|
|
173
|
+
```jsonc
|
|
174
|
+
// e.g. Claude Desktop / mcp.json
|
|
175
|
+
{
|
|
176
|
+
"mcpServers": {
|
|
177
|
+
"ctfd-mcp": {
|
|
178
|
+
"command": "python",
|
|
179
|
+
"args": ["/path/to/ctfd-mcp-server/ctfd_mcp_server.py"],
|
|
180
|
+
"env": {
|
|
181
|
+
"CTFD_BASE_URL": "https://demo.ctfd.io",
|
|
182
|
+
"CTFD_ADMIN_TOKEN": "ctfd_..."
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Manual launch:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# stdio (default) — used by MCP clients
|
|
193
|
+
python ctfd_mcp_server.py
|
|
194
|
+
|
|
195
|
+
# SSE — expose over HTTP for remote/Docker use
|
|
196
|
+
CTFD_MCP_TRANSPORT=sse python ctfd_mcp_server.py # http://127.0.0.1:8000/sse
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### MCP tools
|
|
200
|
+
|
|
201
|
+
| Tool | Parameters | Description |
|
|
202
|
+
| ----------------- | ----------------------------------------------------------------------- | ----------- |
|
|
203
|
+
| `set_base_url` | `url` | Point the server at a CTFd instance |
|
|
204
|
+
| `set_token` | `token` | Adopt an API token (memory only) |
|
|
205
|
+
| `set_cookie` | `cookie` | Adopt a session cookie (memory only) |
|
|
206
|
+
| `login` | `username`, `password` | Form login; keeps the session cookie |
|
|
207
|
+
| `challenges` | `category`, `search`, `solved`, `page`, `per_page` | Paginated challenge list with filters |
|
|
208
|
+
| `challenge` | `identifier` (id **or** name) | Full detail of one challenge |
|
|
209
|
+
| `submit_flag` | `flag`, `challenge_name`/`challenge_id`, `confirm` | Submit a flag (requires `confirm=True`) |
|
|
210
|
+
| `scoreboard` | — | Public scoreboard standings |
|
|
211
|
+
| `progress` | — | Your score + solved challenges |
|
|
212
|
+
| `instance_info` | — | Safe public instance metadata |
|
|
213
|
+
| `auth_status` | — | Auth mode + validity (no secrets) |
|
|
214
|
+
| `health` | — | Reachability, API and auth checks |
|
|
215
|
+
| `download_file` | `file_id` | Save a challenge file to the cache |
|
|
216
|
+
|
|
217
|
+
Tools return JSON text. Errors are structured, e.g.:
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{ "error": { "type": "ChallengeNotFoundError", "message": "Challenge '99' not found (or not visible)." } }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Running the REST API (optional)
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
python scripts/run_local.sh # reads .env, default http://127.0.0.1:8000
|
|
229
|
+
# or
|
|
230
|
+
uvicorn server.main:app --host 0.0.0.0 --port 8000
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Endpoints (all under `/api/v1`):
|
|
234
|
+
|
|
235
|
+
| Method | Path | Description |
|
|
236
|
+
| ------ | ----------------------------- | ---------------------------------------- |
|
|
237
|
+
| POST | `/set_base_url` | Validate & set the CTFd base URL |
|
|
238
|
+
| POST | `/set_token` | Set API token |
|
|
239
|
+
| POST | `/set_cookie` | Set session cookie |
|
|
240
|
+
| POST | `/set_creds` | Store username/password for later login |
|
|
241
|
+
| POST | `/login` | Form login (session cookie) |
|
|
242
|
+
| GET | `/challenges` | Paginated + filtered challenge list |
|
|
243
|
+
| GET | `/challenges/{id-or-name}` | Challenge detail |
|
|
244
|
+
| POST | `/submit` | Submit a flag (`confirm: true` required) |
|
|
245
|
+
| GET | `/scoreboard` | Public standings |
|
|
246
|
+
| GET | `/progress` | Your score and solves |
|
|
247
|
+
| GET | `/instance_info` | Public instance metadata |
|
|
248
|
+
| GET | `/auth_status` | Auth mode + validity |
|
|
249
|
+
| GET | `/health` | Health check |
|
|
250
|
+
| GET | `/files/{fid}/download` | Save a challenge file |
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Docker
|
|
255
|
+
|
|
256
|
+
A ready-made image is published on **[Docker Hub](https://hub.docker.com/r/jamescot/ctfd-mcp-server)**:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
docker run --rm -p 8000:8000 \
|
|
260
|
+
-e CTFD_BASE_URL=https://ctf.example.com \
|
|
261
|
+
-e CTFD_ADMIN_TOKEN=ctfd_... \
|
|
262
|
+
jamescot/ctfd-mcp-server
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Or build locally (REST mode):
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
docker build -t ctfd-mcp .
|
|
269
|
+
docker run --rm -p 8000:8000 \
|
|
270
|
+
-e CTFD_BASE_URL=https://ctf.example.com \
|
|
271
|
+
-e CTFD_ADMIN_TOKEN=ctfd_... \
|
|
272
|
+
ctfd-mcp
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`docker compose up --build` also works (REST API on `http://localhost:8000`).
|
|
276
|
+
|
|
277
|
+
To run the **MCP SSE** server in a container instead:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
docker run --rm -it -e CTFD_BASE_URL=https://ctf.example.com ctfd-mcp python ctfd_mcp_server.py
|
|
281
|
+
# stdio on the attached terminal
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Usage examples
|
|
287
|
+
|
|
288
|
+
### MCP (agent)
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
1. set_base_url url="https://ctf.example.com"
|
|
292
|
+
2. set_token token="ctfd_..."
|
|
293
|
+
3. challenges category="web", solved=false, page=1, per_page=25
|
|
294
|
+
4. challenge identifier="3"
|
|
295
|
+
5. submit_flag flag="flag{...}", challenge_id=3, confirm=true
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### REST
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
curl -X POST http://localhost:8000/api/v1/set_base_url \
|
|
302
|
+
-H 'Content-Type: application/json' -d '{"url":"https://ctf.example.com"}'
|
|
303
|
+
|
|
304
|
+
curl -X POST http://localhost:8000/api/v1/set_token \
|
|
305
|
+
-H 'Content-Type: application/json' -d '{"token":"ctfd_..."}'
|
|
306
|
+
|
|
307
|
+
curl 'http://localhost:8000/api/v1/challenges?search=web&solved=false&per_page=10'
|
|
308
|
+
|
|
309
|
+
curl -X POST http://localhost:8000/api/v1/submit \
|
|
310
|
+
-H 'Content-Type: application/json' \
|
|
311
|
+
-d '{"challenge_id":3,"flag":"flag{...}","confirm":true}'
|
|
312
|
+
|
|
313
|
+
curl http://localhost:8000/api/v1/health
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
See [`DEMO.md`](DEMO.md) for a complete walkthrough and
|
|
317
|
+
[`examples/`](examples) for curl and Python snippets.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Development & testing
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
pip install -r requirements-dev.txt
|
|
325
|
+
|
|
326
|
+
python -m pytest -q # 76 unit tests, mocked CTFd API (no network)
|
|
327
|
+
ruff check server ctfd_mcp_server.py tests
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The test suite mocks the CTFd API (`tests/conftest.py::FakeGateway`), so unit
|
|
331
|
+
tests run offline.
|
|
332
|
+
|
|
333
|
+
### Integration testing against a real CTFd
|
|
334
|
+
|
|
335
|
+
Run a local CTFd for live tests (recommended over the shared demo instance,
|
|
336
|
+
which serves HTML on public auth-gated routes):
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
git clone https://github.com/CTFd/CTFd.git /tmp/CTFd
|
|
340
|
+
docker compose -f /tmp/CTFd/docker-compose.yml up
|
|
341
|
+
# create a user/challenge, then:
|
|
342
|
+
CTFD_BASE_URL=http://localhost:8000 python ctfd_mcp_server.py
|
|
343
|
+
CTFD_BASE_URL=http://localhost:8000 uvicorn server.main:app --port 8001
|
|
344
|
+
curl http://localhost:8001/api/v1/health
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Security considerations
|
|
350
|
+
|
|
351
|
+
- **Credentials are memory-only.** By default nothing is written to
|
|
352
|
+
`server_state.json`. Enabling `CTFD_PERSIST_SECRETS` is discouraged.
|
|
353
|
+
- **Secrets are never echoed.** Tool and API responses, error messages and logs
|
|
354
|
+
redact tokens, cookies, passwords and flags (`server/utils.py`).
|
|
355
|
+
- **Every tool validates its input** before touching the network (`set_base_url`
|
|
356
|
+
requires an absolute `http(s)` URL, `submit_flag` requires `confirm=True`,
|
|
357
|
+
etc.).
|
|
358
|
+
- **Controlled retries.** Only idempotent `GET` requests are retried (once).
|
|
359
|
+
Flag submissions are never automatically replayed.
|
|
360
|
+
- **Trust model.** The server is a local/dev tool: whoever can call its tools can
|
|
361
|
+
point it at any CTFd instance and (with a valid credential) read data or submit
|
|
362
|
+
flags. Do **not** expose the REST/SSE endpoints on an untrusted network.
|
|
363
|
+
- **File downloads** are written under `FILE_CACHE_DIR` with sanitized filenames
|
|
364
|
+
(path-traversal protected).
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Limitations
|
|
369
|
+
|
|
370
|
+
- Requires CTFd **v3+**. The `/api/v1` routes used are standard CTFd v3 API
|
|
371
|
+
routes.
|
|
372
|
+
- Form login depends on CTFd's web session flow (CSRF nonce extraction is
|
|
373
|
+
best-effort). **API tokens are the recommended authentication method.**
|
|
374
|
+
- `difficulty` is not a standard CTFd field; challenge `value` (points) is
|
|
375
|
+
returned instead.
|
|
376
|
+
- `solved` filtering uses CTFd's `solved_by_me` flag, which is only meaningful
|
|
377
|
+
when authenticated.
|
|
378
|
+
- Instance "version" is reported only when it appears in the rendered page; CTFd
|
|
379
|
+
has no public version API endpoint.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Contributing
|
|
384
|
+
|
|
385
|
+
Pull requests are welcome. Please:
|
|
386
|
+
|
|
387
|
+
1. Open an issue describing the change.
|
|
388
|
+
2. Add tests in `tests/` (mocked CTFd API preferred).
|
|
389
|
+
3. Run `python -m pytest -q` and `ruff check server ctfd_mcp_server.py tests`.
|
|
390
|
+
4. Do not ship credentials in code, tests, or commit `server_state.json` /
|
|
391
|
+
`.env`.
|
|
392
|
+
|
|
393
|
+
## License
|
|
394
|
+
|
|
395
|
+
[MIT](LICENSE) — repository: <https://github.com/MrJamescot/ctfd-mcp-server>
|