Whoosh as an MCP server: local full-text search for your AI agent

A pure-Python search + fetch tool backend — no server, no native deps

The Model Context Protocol (MCP) is how modern LLM agents — Claude Desktop, IDE assistants, and custom agent loops — discover and call tools. A very common pair of tools an agent wants is search (find relevant documents for a query) and fetch (read one document in full). That is exactly what a full-text search engine is for.

Whoosh is a great fit for this job because it is pure Python and embedded: the index is just a directory of files, there is no server to run and no native wheel to compile. You can ship it inside the same process that hosts your MCP server, rebuild it in CI, and run it anywhere CPython runs — a laptop, a container, or serverless. No Elasticsearch cluster, no vector database, no GPU.

Install

pip install "whoosh3[mcp]"

whoosh3 is the actively-maintained continuation of Whoosh (Python 3.9–3.14); the [mcp] extra pulls in the official Model Context Protocol SDK.

Run it in one line

The package ships a whoosh-mcp console script. Point it at a folder of .md/.txt/.rst files and it serves them to an agent over stdio — one document per file:

whoosh-mcp ~/notes        # or omit the path to serve built-in samples

A reusable search core (no MCP dependency)

Keep the search logic independent of the protocol so you can unit-test it or reuse it behind any agent framework. This builds a small BM25F index and exposes search() and fetch():

import os.path, tempfile
from dataclasses import dataclass
from whoosh import highlight
from whoosh.fields import ID, TEXT, Schema
from whoosh.index import create_in, open_dir
from whoosh.qparser import MultifieldParser

@dataclass
class SearchCore:
    index_dir: str

    @classmethod
    def build(cls, docs, index_dir=None):
        index_dir = index_dir or tempfile.mkdtemp(prefix="whoosh_mcp_")
        os.makedirs(index_dir, exist_ok=True)
        schema = Schema(id=ID(stored=True, unique=True),
                        title=TEXT(stored=True), body=TEXT(stored=True))
        ix = create_in(index_dir, schema)
        w = ix.writer()
        for d in docs:
            w.update_document(id=d["id"], title=d["title"], body=d["body"])
        w.commit()
        return cls(index_dir)

    def search(self, query, limit=5):
        ix = open_dir(self.index_dir)
        q = MultifieldParser(["title", "body"], schema=ix.schema).parse(query)
        out = []
        with ix.searcher() as s:
            results = s.search(q, limit=limit)
            results.fragmenter = highlight.ContextFragmenter(maxchars=160, surround=40)
            for hit in results:
                out.append(dict(id=hit["id"], title=hit["title"],
                                score=round(hit.score, 4),
                                snippet=hit.highlights("body") or hit["body"][:160]))
        return out

    def fetch(self, doc_id):
        ix = open_dir(self.index_dir)
        with ix.searcher() as s:
            hit = s.document(id=doc_id)
            return {"id": doc_id, "error": "not found"} if hit is None else \
                   {"id": hit["id"], "title": hit["title"], "text": hit["body"]}

Wrap it as an MCP server

The official SDK's FastMCP turns plain functions into MCP tools. Docstrings and type hints become the tool schema the agent sees, so write them for the model:

from mcp.server.fastmcp import FastMCP

core = SearchCore.build(MY_DOCS)
mcp = FastMCP("whoosh-search")

@mcp.tool()
def search(query: str, limit: int = 5) -> list[dict]:
    """Full-text search the local corpus. Returns ranked {id, title, score, snippet}."""
    return core.search(query, limit)

@mcp.tool()
def fetch(id: str) -> dict:
    """Fetch the full text of a document by its id (as returned by search)."""
    return core.fetch(id)

if __name__ == "__main__":
    mcp.run()   # stdio transport, ready to be spawned by an MCP client

Connect an agent to it

Point any MCP client at the script. For Claude Desktop, add it to claude_desktop_config.json:

{
  "mcpServers": {
    "whoosh": { "command": "whoosh-mcp", "args": ["/path/to/your/docs"] }
  }
}

Prefer a container? The repository ships a Dockerfile, so you can run the server without a local Python environment — mount your docs read-only and keep stdin open for the stdio transport:

docker build -t whoosh-mcp .
docker run --rm -i -v "$HOME/notes:/corpus:ro" whoosh-mcp /corpus

The agent now sees a search tool and a fetch tool. Ask it a question and it will search your corpus, read the most relevant document, and answer with grounded, cited context — all backed by a local BM25F index. A query like "agent tools protocol" against the sample corpus returns the "Model Context Protocol" document with a highlighted snippet; fetch("mcp") then returns its full text.

Why keyword search still matters for agents

Why Whoosh specifically

Install: pip install "whoosh3[mcp]" · Full runnable example: examples/mcp_server.py · Related: hybrid RAG retrieval, LangChain retriever, index a knowledge base.

Indexed on TensorBlock MCP Index


The Whoosh search core above was run against whoosh3 3.31.0 before publishing; the MCP wiring uses the official mcp SDK's documented FastMCP API. "Whoosh" is the pure-Python search library originally by Matt Chaput; this is an actively maintained continuation. MCP is an open protocol — the same server works with any compliant client.