docsearch-mcp
# docsearch-mcp
Local document search with **cited sources**, as an [MCP](https://modelcontextprotocol.io) server
and a web app. It uses BM25 ranking implemented in pure Python: no API key, no model download,
no vector database.

## Why
Agents need grounded context they can cite. This gives any MCP client two tools, `search_docs`
and `list_sources`, over your own folder of `.md`/`.txt`/`.rst` files, with the source and a
relevance score returned for every chunk so answers can be traced back.
## Run the demo
```bash
pip install -e ".[dev]"
docsearch-web # http://127.0.0.1:8000
```
The default corpus is the docstrings of ~70 Python standard-library modules (about 1,000 chunks),
so it works with nothing to configure. Try "run subprocess and capture output" or
"compute sha256 hash".
Search your own documents instead:
```bash
DOCSEARCH_DIR=./my-docs docsearch-web
```
## Use it from an MCP client
```json
{ "mcpServers": { "docsearch": { "command": "docsearch-mcp-server",
"env": { "DOCSEARCH_DIR": "/path/to/docs" } } } }
```
| Tool | Returns |
| --- | --- |
| `search_docs(query, k=5)` | Top-k chunks: `source`, `chunk`, `score`, `text` |
| `list_sources()` | Number of chunks and the list of source documents |
## How it works
Documents are split into ~120-word chunks on paragraph boundaries. Chunks are tokenized
(lowercased, stopwords removed; the source name is indexed too) and ranked with Okapi BM25
(k1=1.5, b=0.75).
## Tests
```bash
python -m pytest # 10 tests
ruff check src tests
```
## Limitations
- Lexical matching only: a query must share words with the text. It does not understand
synonyms the way embedding search can.
- The index is rebuilt in memory at startup, which suits small and medium corpora.
- Plain-text formats only (no PDF or HTML parsing).
TDQS
Scored across 2 tools
The two tools have clearly distinct purposes: one searches content by relevance, the other enumerates indexed sources. There is no realistic way to confuse a BM25 search with listing sources.
Both names follow the same verb_noun snake_case pattern (search_docs, list_sources). The convention is predictable and readable.
Two tools is thin for a search server; useful capabilities like fetching a full document or filtering search by source are absent. Still, the count is not absurd for a narrowly scoped read-only index.
Core retrieval and source enumeration are covered, but there is no way to retrieve a whole document, filter searches by source, or manage the index. Agents hitting a chunk boundary or needing document-level context will dead-end.