RAG Search MCP Server
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., "@RAG Search MCP Serversearch my docs for why retrieval quality degrades over time"
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.
RAG Search MCP Server
A small MCP server with one tool, search_documents(query, top_k),
that lets an agent search a local set of text files by meaning.
There are no external services, API keys, or databases. Clone it, install the dependencies, point an MCP client at it, and it works against the bundled sample corpus.
Why I built it
An agent can only use the tools it's given. I'd already built retrieval pipelines, so this project covers the other half: making retrieval something an agent can call, through the protocol agent clients (Claude Desktop, Claude Code, and others) already speak.
The search code comes from my pdf-rag-from-scratch
project: tokenizer-aware chunking, local embeddings, cosine-similarity ranking. I re-pointed it at a
folder of .txt files instead of a PDF, so nobody has to supply their own corpus to try it.
Related MCP server: rag_mcp
What it does
query
↓
embed the query (same model that embedded the corpus)
↓
cosine similarity against every chunk
↓
top_k chunks, best first, each with its source file and scoreIt only retrieves. It doesn't write an answer; that's the agent's job (see Design decisions).
Install and run
Needs Python 3.10 or newer (the MCP SDK's minimum). I've only run it on 3.14.
git clone https://github.com/regankight/rag-search-mcp-server.git
cd rag-search-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txtRun it directly (MCP clients talk to it over stdio):
python mcp_server.pyTo use it from an MCP client such as Claude Desktop or Claude Code, point the client's config at that command:
{
"mcpServers": {
"rag-search": {
"command": "/absolute/path/to/rag-search-mcp-server/.venv/bin/python",
"args": ["/absolute/path/to/rag-search-mcp-server/mcp_server.py"]
}
}
}The first run downloads the embedding model (all-MiniLM-L6-v2, about 90MB) from Hugging Face and
caches it. The server loads the model and builds the index in a background thread as soon as it
starts, so it answers the client's handshake in about 0.3 seconds instead of waiting for the model.
A search that arrives before the index is ready waits for it.
Example
Searching for "catching quality getting worse over time before users complain" returns (text shortened):
[
{
"chunk_id": "chunk_0002",
"source": "observability.txt",
"text": "Observability for a language model application means being able to see...",
"score": 0.3443
},
{
"chunk_id": "chunk_0000",
"source": "evaluation_frameworks.txt",
"text": "Evaluating a retrieval system means measuring whether the documents it...",
"score": 0.192
}
]The observability passage wins by a wide margin. It talks about noticing "quality drift before a user
complains", and shares only two generic words with the query (quality, before), so the match comes
from meaning, not wording. These numbers come from a real run on the bundled corpus; scores shift
slightly with the embedding model.
Called from Claude Code

Claude Code calls search_documents through the MCP server. The raw result includes the source
file and score for each passage, and the answer is built from that passage.
Design decisions
Bundled sample corpus. A server only proves something if a reviewer can run it. Asking them to
bring their own documents first is friction most people won't push through. sample_docs/ has five
short passages on RAG-related topics (retrieval, hybrid search, observability, evaluation,
embeddings) so a fresh clone gives a correct result straight away. To search your own files, point
DEFAULT_DOCS_DIR in search.py at a directory of .txt files.
Self-written samples, not a real dataset. The benchmark PDF used in pdf-rag-from-scratch isn't
committed there (data/*.pdf is gitignored), and its redistribution license was never checked. I
wrote the samples myself instead of carrying content with unclear reuse rights into a second public
repo. Their only job is to show that search works.
Retrieval only, no generation. Adding an LLM call would make this a smaller copy of
pdf-rag-from-scratch. The point here is the MCP layer: giving an agent a search tool it can call.
In-memory index, no vector database. Five short files make a handful of chunks, so comparing the query against every chunk is simpler and fast enough. A vector database would solve a scaling problem this project doesn't have. The index is built once per server process and kept in memory.
Copied search code, not imported. Depending on pdf-rag-from-scratch (submodule, or pip install
from a git URL) would mean cloning two repos to run one. Copying the small amount of code needed keeps
this repo standalone. The cost is that the copies can drift apart, which is fine for a demo and not
something I'd do for shared production code.
mcp>=2,<3 is pinned. The MCP Python SDK made a breaking change in v2.0.0 (2026-07-28): the server
class moved from mcp.server.fastmcp.FastMCP to mcp.server.MCPServer, and the old import path was
removed, not deprecated. Tutorials written before then fail on a fresh install with
ModuleNotFoundError. This repo uses the v2 API (from mcp.server import MCPServer, with an explicit
mcp.run(transport="stdio")) and pins the major version so it doesn't break the same way when the SDK
changes again.
Index built in a background thread, behind a lock. Loading the model takes seconds, and the first
run also downloads it. Doing that inside the first tool call risks a client timeout on the call a
reviewer is most likely to make first. Doing it before the server starts would delay the handshake. So
python mcp_server.py starts the build in the background and serves immediately, and an early search
waits on the same build. Even importing the embedding library is slow, because sentence-transformers
pulls in torch (about 2.5s), so that import is deferred into the build too. A test checks that
importing the server never loads torch. The lock is there because the SDK runs sync tool functions on
worker threads: without it, two searches arriving together would each load the model. A test starts
eight threads at once and asserts the index is built exactly once.
Blank queries are an error. Embedding an empty string still produces a vector, so every passage
gets ranked and returned with a near-zero score that looks like a result but isn't one. The tool
rejects blank queries instead. The error is raised as the SDK's ToolError, because an ordinary
exception is hidden from the client (it would see only "Error executing tool search_documents"), and
the agent wouldn't learn what to fix. A test checks the message from the client's side over real
stdio. The tool description also states two behaviors the schema can't show: top_k is clamped to
between 1 and the corpus size, and queries over the embedding model's 256-token limit are truncated.
Results include the source file. A passage with only a positional chunk_id can't be cited or
checked, and a retrieval tool exists so an agent can ground an answer in something. Files are chunked
separately, so each chunk comes from exactly one file, and that filename is returned with the text.
search() is separate from the decorated tool. search_documents is a thin wrapper around
search(). Tests call the plain function, so they don't depend on the SDK's decorator internals. The
wrapper's only other job is converting a plain error into a ToolError.
Three kinds of tests. Fast tests use a stub model (fixed vectors keyed by exact text), so ranking, corpus loading, and the search logic run in milliseconds without loading a model. Chunking tests use the real embedding model's tokenizer, because the chunker has to agree with that tokenizer's limits and a mock could pass while the real one truncates. Slower end-to-end tests build the index over the real corpus and start the server as a subprocess, talking to it with an MCP client over real stdio. That covers tool registration and the protocol layer, which the unit tests skip and which the SDK's July 2026 rename actually broke.
Project structure
rag-search-mcp-server/
├── mcp_server.py # the MCP server: index cache and the search_documents tool
├── search.py # chunking, embedding, cosine-similarity ranking (adapted from pdf-rag-from-scratch)
├── sample_docs/ # five short passages the demo searches by default
├── tests/
│ └── test_search.py
├── requirements.txt
├── pytest.ini
├── LICENSE
└── README.mdRunning the tests
pytest tests/ -vScope
This is a small, single-tool server meant to show how retrieval gets exposed to an agent. It isn't a general document-search service: no authentication, one fixed corpus directory, and the index lives only as long as the server process.
This server cannot be deployed
Maintenance
Related MCP Connectors
Securely search and manage workspace context files for AI agents and teams.
Search and reason over your Obsidian-style Markdown vault, right from ChatGPT.
Memory system for AI agents with semantic search. Store and recall memories with ease.
Persistent memory for AI agents. Search, store, and recall across sessions.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables semantic search over local notes and documents using natural language queries. Supports multiple file types (Markdown, Python, HTML, JSON, CSV, text) with fast local embeddings and persistent ChromaDB vector storage.1-
- FlicenseNot gradedqualityDmaintenanceEnables retrieval-augmented generation by allowing LLMs to search documents in a local folder using vector similarity, supporting multiple file formats.-
- AlicenseAqualityDmaintenanceEnables searching and retrieving documents from a local folder to ground LLM answers in your files.2MIT
- AlicenseNot gradedqualityDmaintenanceEnables semantic search over local AI-agent session logs (e.g., Claude Code, Hermes) using local embeddings, returning relevant chunks without exposing full transcripts.MIT