ctf-rag-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ctf-rag-mcpfind CTF writeups about prototype pollution"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ctf-rag-mcp
A minimal MCP (stdio) server that performs lexical search (BM25) over a user-provided corpus of text documents — originally built to let an autonomous AI agent query a corpus of CTF writeups (both solved and failed attempts), but the format is generic.
This repository ships no dataset: you bring your own (see corpus_examples/ for the expected
format and a small working example).
Why BM25 instead of semantic embeddings
On a corpus of dense technical jargon (tool names, function names, CVEs, rare terms like "needle", "mergedeep", "prototype pollution"), a small general-purpose embedding model tends to produce generically high similarity scores for any technical text, without real topical discrimination — tested in practice: a completely unrelated writeup scored higher on semantic similarity than the actually relevant one, because shared technical vocabulary ("exploit", "payload", "flag"...) dominated the signal.
Exact lexical search (BM25) proved noticeably more reliable for this use case: a precise, rare term matches directly, without dilution.
Related MCP server: agent-bm25-knowledge-mcp
Corpus format
A JSON file, a list of objects:
[
{
"id": "optional",
"title": "required",
"category": "optional, used by the --category filter",
"source": "optional, shown in results",
"outcome": "optional, e.g. solved / failed",
"difficulty": "optional",
"text": "required — the indexed content, also returned as an excerpt"
}
]See corpus_examples/sample_corpus.json for a minimal example (3 fictional documents).
Two deployment modes
rag_server.py— MCP stdio server. Simplest for a single machine: the MCP client spawns it directly (no network service to run).http_server.py+mcp_http_bridge.py— centralized mode, for multiple clients sharing one corpus/index (mirrors how a SearXNG instance is typically deployed: one long-running Docker service, reached by a thin stdlib-only client-side bridge on each machine — no per-client dependencies, no per-client copy of the corpus).http_server.pyruns as a persistent service (e.g. indocker-compose.yml,restart: unless-stopped), exposingPOST /searchandGET /health. It reloads the corpus file automatically when its mtime changes — no restart needed to pick up updates.mcp_http_bridge.pyis the thing each MCP client actually spawns: pure stdlib, no dependencies, just relays MCP stdio calls to the HTTP server's/searchendpoint viaCTF_RAG_URL.
Usage
Locally (without Docker)
pip install -r requirements.txt
CTF_RAG_CORPUS_PATH=./corpus_examples/sample_corpus.json python3 rag_server.pyThe server speaks MCP over stdio (line-delimited JSON-RPC) — it isn't meant to be typed at directly, but launched by an MCP client (see below).
With Docker
docker pull openprunus/ctf-rag-mcp:latest
docker run -i --rm -v "$(pwd)/corpus_examples/sample_corpus.json:/data/corpus.json:ro" openprunus/ctf-rag-mcp(-i is required: the MCP stdio protocol needs a continuously connected stdin/stdout — don't use
-d.)
MCP client configuration (example: opencode)
Single machine, stdio mode:
{
"mcp": {
"ctf-rag": {
"type": "local",
"command": [
"docker", "run", "-i", "--rm",
"-v", "/path/to/my_corpus.json:/data/corpus.json:ro",
"openprunus/ctf-rag-mcp"
],
"enabled": true
}
}
}Multiple machines sharing one centralized server (recommended once you have more than one client): point every client at the same bridge script and URL — no path or dependency differs between machines, so this config is safe to commit and share as-is.
{
"mcp": {
"ctf-rag": {
"type": "local",
"command": ["python3", "/path/to/mcp_http_bridge.py"],
"environment": { "CTF_RAG_URL": "http://my-server:8090" },
"enabled": true
}
}
}Or without Docker at all, by pointing directly at rag_server.py with the
CTF_RAG_CORPUS_PATH environment variable (stdio mode, single machine).
Exposed tool
search_writeups(query, top_k=5, category=None, only_solved=False) — BM25 search over the
corpus, returns the top_k most relevant documents (score, title, category, source, outcome,
excerpt).
Environment variables
Variable | Default | Role |
|
| Path to the corpus file |
|
| Max excerpt length returned per result |
|
| Characters of |
License
MIT.
This server cannot be deployed
Maintenance
Related MCP Connectors
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Agentic search over your Dewey document collections from any MCP-compatible client.
MCP server for querying Forkast documentation
Related MCP Servers
- AlicenseAqualityDmaintenanceA lightweight knowledge base MCP server that enables full-text search and retrieval of markdown documents from indexed sites using Orama BM25.7Apache 2.0
- FlicenseNot gradedqualityCmaintenanceA local BM25 knowledge retrieval MCP server that indexes configured files and directories for agents like Claude Code, Kiro CLI, and Codex, providing interpretable search and chunk retrieval tools.-
- AlicenseBqualityBmaintenanceProvides local hybrid search over files by combining semantic vector and full-text BM25 retrieval, with MCP stdio/HTTP support, OAuth 2.1 authentication, and tools for reading and converting documents to Markdown.23MIT
- AlicenseAqualityCmaintenanceAn MCP server that enables local BM25 search over 1C:Enterprise platform documentation, exposing identical tools via both stdio and streamable HTTP endpoints.5MIT