Local MCP server for searching Ethereum Improvement Proposals with Qdrant, FastEmbed dense embeddings, and FastEmbed BM25 sparse vectors.
The MCP client talks to this server. The server talks to Qdrant. Qdrant stores the embedded EIP corpus.
src/eip_mcp/server.py: FastMCP stdio server.src/eip_mcp/search.py: hybrid dense/sparse search service.scripts/index_corpus.py: corpus indexing command.scripts/smoke_search.py: local search smoke-test command.docker-compose.yml: local Qdrant service.
The generated corpus is intentionally not committed:
data/search-corpus.json
Generate the EIP search corpus from the EIPs workspace/build pipeline, then put
the artifact at that path before indexing. You can also point somewhere else
with EIP_CORPUS_PATH.
Requirements:
- Python 3.11+
- uv (installation guide)
- Docker with Compose
uv sync --locked
docker compose up -d qdrantThe committed uv.lock pins the Python dependency tree used by
uv sync --locked.
Use --locked for the tested dependency set; drop it only when intentionally
refreshing dependencies.
Place the corpus:
mkdir -p data
cp /path/to/search-corpus.json data/search-corpus.jsonThe first embedding/indexing run downloads the FastEmbed models into
var/fastembed, so a fresh machine may pause while the model cache warms.
Index the full corpus into Qdrant:
uv run --locked python -m scripts.index_corpus --batch-size 64The command recreates the eip_chunks collection by default. Use
--no-recreate only when you intentionally want to append/upsert into the
existing collection.
Watch progress from another terminal:
total=$(uv run --locked python -c 'import json; print(len(json.load(open("data/search-corpus.json"))["chunks"]))')
while sleep 10; do
curl -fsS http://localhost:6333/collections/eip_chunks \
| TOTAL="$total" uv run --locked python -c 'import json,os,sys; r=json.load(sys.stdin)["result"]; p=r.get("points_count",0); print("{}/{} indexed status={}".format(p, os.environ["TOTAL"], r.get("status")))'
doneFor a tiny local smoke index only:
uv run --locked python -m scripts.index_corpus --limit 200 --batch-size 32Run direct search checks:
uv run --locked python -m scripts.smoke_search "Where is BASEFEE specified?"
uv run --locked python -m scripts.smoke_search "Explain EIP-7702 authorization tuples"
uv run --locked python -m scripts.smoke_search "Show ERC-20 transfer requirements"Check collection metadata:
uv run --locked python - <<'PY'
from eip_mcp.search import SearchService
print(SearchService().index_info())
PYFor the current corpus, a complete index should report 17037 indexed points.
Run the MCP server over stdio:
uv run --locked python -m eip_mcp.serverTools exposed:
search_eips: hybrid dense/sparse search with optional metadata filters (proposal,status,eip_type,proposal_category).get_eip: ordered chunks for one EIP/ERC proposal.get_chunk: exact chunk lookup by returnedchunk_id.index_info: corpus, model, and Qdrant collection metadata.
Use absolute paths in MCP client config. After uv sync --locked, the virtualenv
interpreter is under .venv/bin/python on macOS/Linux and
.venv\Scripts\python.exe on Windows.
Codex ~/.codex/config.toml example:
[mcp_servers.eip-search]
command = "/absolute/path/to/eip-vector/.venv/bin/python"
args = ["-m", "eip_mcp.server"]
cwd = "/absolute/path/to/eip-vector"
[mcp_servers.eip-search.env]
QDRANT_URL = "http://localhost:6333"
EIP_CORPUS_PATH = "data/search-corpus.json"
EIP_QDRANT_COLLECTION = "eip_chunks"Claude Desktop style JSON example:
{
"mcpServers": {
"eip-search": {
"command": "/absolute/path/to/eip-vector/.venv/bin/python",
"args": ["-m", "eip_mcp.server"],
"cwd": "/absolute/path/to/eip-vector",
"env": {
"QDRANT_URL": "http://localhost:6333",
"EIP_CORPUS_PATH": "data/search-corpus.json",
"EIP_QDRANT_COLLECTION": "eip_chunks"
}
}
}
}Environment variables:
QDRANT_URL: Qdrant URL. Default:http://localhost:6333.EIP_QDRANT_COLLECTION: Qdrant collection. Default:eip_chunks.EIP_CORPUS_PATH: corpus path. Default:data/search-corpus.json.EIP_DENSE_MODEL: dense model. Default:BAAI/bge-small-en-v1.5.EIP_SPARSE_MODEL: sparse model. Default:Qdrant/bm25.FASTEMBED_CACHE_PATH: model cache path. Default:var/fastembed.EIP_INDEX_BATCH_SIZE: indexing batch size. Default:64.
Do not commit generated runtime data:
.venv/data/search-corpus.jsonvar/qdrant/var/fastembed/
The repo is meant to contain the server, indexer, docs, and Docker config. The corpus and Qdrant database are local/generated artifacts.