notes
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., "@notessearch my notes for korean recipes tagged cooking"
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.
personal-vector-mcp
Semantic search over your own markdown notes, run entirely on your machine and exposed to Claude Code as an MCP tool. A local Qwen3-Embedding-0.6B service turns notes into vectors, Qdrant stores them, and an MCP server lets Claude search them and answer with sources. No API key is needed, and no embedding or chat model is paid for: Claude Code is the step that writes the answer.
What's here
embedder/is a FastAPI service that serves Qwen3-Embedding-0.6B (1024 dimensions) over an OpenAI-style/v1/embeddingsendpoint.mcp/is an MCP server over streamable HTTP with two tools,search_notesandlist_tags.docker-compose.ymlruns Qdrant, the embedder, and the MCP server together.ingest.pysplits notes into chunks, embeds them, and stores them in Qdrant.query.pysearches the stored chunks from the command line.embed_client.pyis the shared embedding client the host scripts use.smoke.py,ingest_check.py, andmcp/check.pyare the runnable checks.
Related MCP server: knowledgebased
Requirements
Docker with at least 6 GB of memory available to containers. The embedder alone uses about 3 GB once the model is loaded and more while it embeds a batch.
uv to run the host scripts.
Claude Code to ask questions through the MCP tool.
Everything runs on CPU. The embedder image is about 4 GB because the model is baked in at build time.
Add your notes
Put your notes in a data/ folder at the repo root.
The ingest script picks up every *.md and *.txt file in it, including subfolders, and splits each into chunks of at most 1500 characters at paragraph, line, or word breaks.
A frontmatter block at the top of a note is read for tags and a topic, as described under "Tag your notes", and is not chunked.
Each search result names the file it came from, so descriptive filenames make better citations.
uv run ingest.py embeds each note under data/ that is not in data/ingested/, then moves it into data/ingested/, keeping its subfolder.
So each run embeds only the notes added since the last one.
To update a note, drop the new version into data/ again, and the next run replaces its old chunks and its old file.
Edits made in place under data/ingested/, and files deleted from it, are only picked up by uv run ingest.py --rebuild.
--rebuild drops the collection and re-ingests every note, data/ingested/ included.
make ingest and make ingest-rebuild run the same two commands.
If a run fails partway, the notes it finished are already in data/ingested/ and the next run picks up the rest.
Tag your notes
A note can start with a YAML frontmatter block, and the ingest script reads three optional fields from it:
---
tags: [cooking, food, korean cuisine]
topic: Author James Jones shares fusion Asian American recipes he formed after his trip through Southeast Asia
document_date: 2019-05-01
---tags is a list of keywords that search results can be narrowed to.
They are normalised on the way in: lowercased, trimmed, inner whitespace collapsed to one space, and duplicates dropped, so Korean Cuisine and korean cuisine are the same tag.
topic is a one-sentence description of the whole note.
It is embedded together with every chunk of the note, so a chunk is also found by what its document is about, while the stored text stays the chunk alone.
document_date is when the note or its source was written, in any form YAML accepts.
Other fields are ignored, and a note without frontmatter ingests as before.
A frontmatter block that is not valid YAML stops the run with the file named, and the note stays in data/ for a fix.
Each stored chunk carries tags, topic, document_date, and date_ingested, the UTC time the ingest run stored it.
Chunks stored before this existed carry none of these until uv run ingest.py --rebuild; searches still return them, with empty tags and null fields.
Run
Start Qdrant, the embedder, and the MCP server with docker compose up --build -d.
The first build downloads the 1.2 GB embedding model once and bakes it into the image.
Check the embedder with uv run smoke.py, which prints smoke ok and client ok.
Ingest your notes with uv run ingest.py.
Embedding takes minutes on CPU, about ten for a thousand chunks, so the first run is the slow one.
Check the ingest itself with uv run ingest_check.py, which prints ingest ok and uses its own collection and temporary notes.
Query the collection with uv run query.py "your question", which prints the five closest chunks with their scores and files.
Check the MCP server with uv run mcp/check.py, which prints mcp ok.
It searches your notes without assuming what they contain, so run it after ingest.py.
The Qdrant dashboard is at http://localhost:6333/dashboard.
Each service restarts by itself when it crashes or Docker restarts, unless you stopped it with docker compose stop.
After a reboot the stack comes back once Docker Desktop is running, so turn on its "Start Docker Desktop when you sign in" setting to make that automatic.
While the embedder loads its model, for about a minute after Docker starts, search_notes reports that the embedder failed.
Ask Claude Code
The notes MCP server is registered in .mcp.json.
The first time you start claude in this directory, it asks whether to use that server; approve it.
If you declined by mistake, run claude mcp reset-project-choices and start claude again.
claude mcp list shows notes as connected once the stack is up and the server is approved.
Then ask Claude Code anything your notes cover, and it calls search_notes and cites the files it found.
search_notes takes a query, an optional k, the number of chunks to return, and an optional tags list.
k defaults to 5 and is clamped to 1 to 20.
With tags, only chunks carrying at least one of those tags are searched, with the same normalisation as ingest, so case and spacing do not matter.
Each result carries the chunk's text, source, score, tags, topic, document date, and ingest time.
list_tags returns every tag in the collection with the number of chunks carrying it, so Claude only filters on tags that exist.
Its description tells Claude to search unfiltered first and narrow by tag only when you name a domain or the first results are off-topic.
Both tools search the notes collection that ingest.py builds.
Configuration
Every service URL comes from the environment, and only the defaults mention localhost.
Variable | Default | Read by |
|
| host scripts, MCP server |
|
| host scripts, MCP server |
|
|
|
|
|
|
The ingest and query scripts also load these from a .env file at the repo root.
Inside compose, the MCP server gets http://embedder:8080 and http://qdrant:6333 from docker-compose.yml.
Embedding API
POST /v1/embeddings takes {"input": "text" or ["text", ...], "input_type": "document" or "query"} and returns OpenAI-shaped embeddings.
input_type defaults to "document"; "query" applies the model's query prompt, which improves retrieval for questions.
Empty input returns 422, and inputs longer than 2048 tokens are truncated.
Clients should send at most 16 texts of up to 1500 characters per request; larger requests can exhaust the container's memory.
GET /health reports the model and its dimension once it is loaded.
For example: curl -s localhost:8080/v1/embeddings -H 'Content-Type: application/json' -d '{"input": "hello", "input_type": "query"}'.
Offline and security
No script needs an API key or a network connection once the images are built and the Python dependencies are installed. Only Claude Code itself talks to Anthropic.
Qdrant, the embedder, and the MCP server have no authentication.
Their ports (6333, 6334, 8080, 8000) are bound to 127.0.0.1, so only this machine can reach them.
The MCP server also rejects requests whose Host header is not localhost, which blocks DNS-rebinding attacks from web pages.
Add authentication before exposing any of them on a network.
This server cannot be deployed
Maintenance
Related MCP Connectors
Search and reason over your Obsidian-style Markdown vault, right from ChatGPT.
Agentic search over your Dewey document collections from any MCP-compatible client.
Ingest, manage, and retrieve documents for RAG-powered AI applications
Securely search and manage workspace context files for AI agents and teams.
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-
- AlicenseAqualityDmaintenanceProvides semantic search and a tag-based knowledge graph for any project, auto-discovering local markdown knowledge bases with YAML frontmatter.103 npmMIT
- AlicenseAqualityAmaintenanceEnables AI agents to search local Markdown documents using natural language, with automatic indexing and section-level retrieval.106 npm1MIT
- AlicenseAqualityCmaintenanceEnables semantic search over local markdown documentation by indexing files and ranking results using vector similarity and BM25 fusion.12 npmApache 2.0