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.
Files changed (30) hide show
  1. ctfd_mcp_server-1.0.0/LICENSE +21 -0
  2. ctfd_mcp_server-1.0.0/PKG-INFO +395 -0
  3. ctfd_mcp_server-1.0.0/README.md +358 -0
  4. ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/PKG-INFO +395 -0
  5. ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/SOURCES.txt +28 -0
  6. ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/dependency_links.txt +1 -0
  7. ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/entry_points.txt +3 -0
  8. ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/requires.txt +14 -0
  9. ctfd_mcp_server-1.0.0/ctfd_mcp_server.egg-info/top_level.txt +2 -0
  10. ctfd_mcp_server-1.0.0/ctfd_mcp_server.py +246 -0
  11. ctfd_mcp_server-1.0.0/pyproject.toml +57 -0
  12. ctfd_mcp_server-1.0.0/server/__init__.py +0 -0
  13. ctfd_mcp_server-1.0.0/server/config.py +92 -0
  14. ctfd_mcp_server-1.0.0/server/ctfd_client.py +592 -0
  15. ctfd_mcp_server-1.0.0/server/errors.py +63 -0
  16. ctfd_mcp_server-1.0.0/server/file_cache.py +31 -0
  17. ctfd_mcp_server-1.0.0/server/gateway.py +201 -0
  18. ctfd_mcp_server-1.0.0/server/health.py +15 -0
  19. ctfd_mcp_server-1.0.0/server/main.py +193 -0
  20. ctfd_mcp_server-1.0.0/server/models.py +29 -0
  21. ctfd_mcp_server-1.0.0/server/session_manager.py +79 -0
  22. ctfd_mcp_server-1.0.0/server/setup.py +29 -0
  23. ctfd_mcp_server-1.0.0/server/state_manager.py +171 -0
  24. ctfd_mcp_server-1.0.0/server/utils.py +116 -0
  25. ctfd_mcp_server-1.0.0/setup.cfg +4 -0
  26. ctfd_mcp_server-1.0.0/tests/test_api.py +160 -0
  27. ctfd_mcp_server-1.0.0/tests/test_client.py +474 -0
  28. ctfd_mcp_server-1.0.0/tests/test_config.py +132 -0
  29. ctfd_mcp_server-1.0.0/tests/test_gateway.py +79 -0
  30. 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
+ [![PyPI - Version](https://img.shields.io/pypi/v/ctfd-mcp-server.svg)](https://pypi.org/project/ctfd-mcp-server/)
41
+ [![PyPI - Python Versions](https://img.shields.io/pypi/pyversions/ctfd-mcp-server.svg)](https://pypi.org/project/ctfd-mcp-server/)
42
+ [![Docker Pulls](https://img.shields.io/docker/pulls/jamescot/ctfd-mcp-server.svg)](https://hub.docker.com/r/jamescot/ctfd-mcp-server)
43
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
44
+ [![GitHub Stars](https://img.shields.io/github/stars/MrJamescot/ctfd-mcp-server?style=flat)](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>