notes-rag-pg
# Notes RAG on PostgreSQL + pgvector + MCP
Migrates the retrieval layer of [AskMyNotes](https://github.com/00aimlds00/class-notes-rag) from flat files (NumPy + pickle) to a real PostgreSQL database with the [pgvector](https://github.com/pgvector/pgvector) extension, running in Docker. The database is exposed to Claude through a custom [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server, so questions can be answered via genuine SQL-backed semantic search instead of an in-memory NumPy comparison.
## Architecture
Vorhandener Notizen-Index (embeddings.npy, chunks.pkl, metadata.json) │ ▼ migrate.py ──────────────► PostgreSQL + pgvector (Docker) │ ▼ mcp_server.py (MCP tools) │ ▼ mcp-client ──────────► Claude
## Prerequisites
- **Docker Desktop** — running and verified working (`docker run hello-world`)
- **[uv](https://docs.astral.sh/uv/)** — Python package/environment manager
- **An Anthropic API key** — [console.anthropic.com](https://console.anthropic.com)
- **A pre-built notes index from [class-notes-rag](https://github.com/00aimlds00/class-notes-rag)** — this project migrates that data, it doesn't generate it. Clone that repo, add your own notes to `notes/`, and run its `ingest.py` first to produce `index/embeddings.npy`, `index/chunks.pkl`, and `index/metadata.json`.
- **[mcp-client](https://github.com/00aimlds00/mcp-client)** — used to actually connect to and query the MCP server built here.
This repo, `class-notes-rag`, and `mcp-client` are designed to sit as **sibling folders** on disk, e.g.:C:\Users\you\class-notes-rag
C:\Users\you\notes-rag-pg\ ← dieses Repo
C:\Users\you\mcp-client\
## Setup
1. Clone this repo and start the database:git clone https://github.com/00aimlds00/notes-rag-pg.git cd notes-rag-pg docker compose up -d
This runs PostgreSQL 16 with pgvector pre-installed, on host port **5433** (not 5432 — chosen to avoid clashing with any existing native PostgreSQL install on your machine; change it in `docker-compose.yml` if 5433 is also taken).
2. Install Python dependencies:uv sync
3. Create a `.env` file in this folder:DATABASE_URL=postgresql://notesuser:notespass@localhost:5433/notesdb ANTHROPIC_API_KEY=your-api-key-here
4. Migrate your notes index (requires `class-notes-rag/index/` to already exist, as a sibling directory — see Prerequisites):uv run migrate.py
This creates a `notes_chunks` table and loads every chunk and embedding. Safe to re-run — it drops and recreates the table each time.
## Usage
**Test retrieval directly**, without MCP or Claude, to confirm the database is working:uv run test_search.py
**Connect via MCP**, from your `mcp-client` project:cd ../mcp-client uv add psycopg2-binary sentence-transformers uv run client.py ../notes-rag-pg/mcp_server.py
(The `uv add` step is needed because `mcp-client` launches this server as a subprocess using its own Python environment — see Troubleshooting below.)
Then ask a question, e.g. `What is a patent?` — Claude will call the `search_notes` tool, which runs a live cosine-distance SQL query against Postgres.
## Tools exposed by the MCP server
| Tool | Description |
|---|---|
| `search_notes(query, top_k=4, class_filter="")` | Semantic search over indexed notes. `class_filter` can restrict to `"class-xi"` or `"class-xii"`. |
| `list_note_sources(class_filter="")` | Lists all distinct indexed source files. |
## Design notes
- **No approximate-nearest-neighbor index (ivfflat/hnsw) is used.** At this data scale (a few thousand chunks), exact brute-force cosine search is both fast enough and fully accurate. An `ivfflat` index was tested and found to *reduce* retrieval accuracy at this size — see full write-up in the project documentation.
- **Uses the public `pgvector/pgvector:pg16` image unmodified** — no custom Docker image to build or pull separately.
- **`mcp_server.py` disables Hugging Face/Transformers stdout output** (`HF_HUB_DISABLE_PROGRESS_BARS`, etc.) and lazy-loads `sentence-transformers`, since MCP's stdio transport uses stdout exclusively for JSON-RPC — any stray print statement would corrupt the protocol stream.
## Troubleshooting
- **`password authentication failed`** — likely a port conflict with an existing native PostgreSQL install. Change the host-side port in `docker-compose.yml` (`"5433:5432"` → e.g. `"5434:5432"`) and update `DATABASE_URL` to match.
- **MCP client shows `Connection closed` / handshake timeout** — the server process almost certainly crashed on import. Make sure `mcp-client`'s own environment has `psycopg2-binary` and `sentence-transformers` installed (see Usage above) — it launches the server using its own Python interpreter, not this project's.
- **Search returns irrelevant results** — if you've added a vector index, try dropping it (`DROP INDEX ...;`) and re-testing with `test_search.py`. Approximate indexes need tuning (`probes`) that isn't worth it below tens of thousands of rows.
## License
MITAvailable Tools
2 toolslist_note_sourcesA
List all distinct source files currently indexed in the notes database.
Args: class_filter: Optional — restrict to "class-xi" or "class-xii". Leave empty for both.
| Name | Required | Description | Default |
|---|---|---|---|
| class_filter | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. The verb 'list' clearly implies a read-only operation, and the description specifies that it enumerates distinct sources. For a simple query tool, this adequately conveys the behavioral profile, though it does not go into detail about limitations (e.g., sorting, limits) — which are not critical here.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences: purpose first, then parameter details. It is front-loaded, has zero redundancy, and every word earns its place. Structure is well-organized with a clear separation of purpose and argument explanation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple, has an output schema (so return format is covered), and the only parameter is fully explained. The description provides all necessary information for correct invocation. While it doesn't contrast with the sibling, that is a usage-guidelines gap rather than a completeness gap for this simple tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description compensates fully. It explains that class_filter is optional, restricts to 'class-xi' or 'class-xii', and empty means both. This adds meaning beyond the schema's bare type and default, and even gives allowed values.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('list') and resource ('all distinct source files currently indexed in the notes database'), which is precise and clearly differentiates from the sibling search_notes, which would search within notes rather than enumerate sources. This is a clear, non-tautological purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description does not explicitly contrast this tool with search_notes or state when to use it versus the alternative. The purpose implies a listing use case, but there is no explicit when-to-use or when-not-to-use guidance, leaving it to the agent to infer from context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_notesA
Search the indexed class notes (Class XI & XII) for content relevant to a question.
Args: query: The natural-language question or topic to search for. top_k: Number of top matching chunks to return (default 4). class_filter: Optional — restrict results to "class-xi" or "class-xii". Leave empty to search both.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| top_k | No | ||
| class_filter | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden. It discloses the read-only nature implicitly via 'Search' and states it returns 'top matching chunks', which covers the primary behavior. However, it does not explicitly state side effects, auth requirements, or any constraints, leaving the agent to infer the operational profile.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and well-structured: a one-line purpose statement followed by a clear 'Args:' section with bullet points for each parameter. Every sentence adds value, and the main purpose is front-loaded before parameter details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is relatively simple with an output schema present, so return format details are covered elsewhere. The description sufficiently explains parameters and the search scope. The only gap is explicit routing to the sibling 'list_note_sources', but given the distinct verb and resource, the context is nearly complete for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, yet the description thoroughly explains all three parameters: query as a natural-language question, top_k as the number of chunks with a default, and class_filter with allowed values and semantics. It fully compensates for the schema's lack of descriptions, adding meaning beyond raw types and defaults.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states a specific verb ('Search'), a specific resource ('indexed class notes (Class XI & XII)'), and the scope ('Class XI & XII'). It distinguishes itself from the sibling 'list_note_sources' by focusing on content search rather than listing sources.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies use for searching content by question/topic but provides no explicit guidance on when to choose this tool over the sibling 'list_note_sources'. It does not mention when not to use it or give alternative routing, so usage is inferred rather than explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
Each tool has a completely distinct purpose: one searches content, the other lists source files. There is no overlap in functionality or arguments, so an agent would never confuse them.
Both tools follow a clear verb_noun pattern: 'search_notes' and 'list_note_sources'. They use snake_case consistently and start with an imperative verb, making the naming predictable and intuitive.
With only two tools, the server feels minimal. While the scope (searching and listing notes) is narrow and could justify a lean surface, it is at the borderline where agents might benefit from additional operations like fetching a specific chunk or retrieving full notes.
For a read-only RAG server over class notes, the two tools cover the essential workflows: searching relevant content and listing available sources. There are no obvious missing operations that would cause an agent to get stuck in a typical use case.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Create, search, and update notes in an xNotepad AI notebook, with semantic search and AI Q&A.
Search and manage your Dexi notes: full-text and semantic search, tags, folders, spaced repetition.
Persistent memory and knowledge management for AI agents with semantic search and 50+ tools.
Persistent memory for AI agents. Search and store durable facts, preferences and decisions.
Related MCP Servers
- FlicenseBqualityDmaintenanceEnables indexing and retrieving notes with full-text search using SQLite, plus building knowledge graphs to find relationships between concepts. Supports natural language note management, tagging, and semantic connections.16
- AlicenseNot gradedqualityDmaintenanceProvides semantic search and keyword search over Obsidian notes, along with direct note retrieval, allowing external AI agents to query and access the vault.18BSD Zero Clause
- FlicenseNot gradedqualityCmaintenanceEnables semantic search over personal study notes by exposing a vector search tool that Claude Desktop can call to retrieve relevant note content and synthesize grounded answers.
- FlicenseNot gradedqualityAmaintenanceEnables semantic search over personal markdown notes by indexing them into a vector database and exposing search, reindex, and status tools via MCP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/00aimlds00/notes-rag-pg'
If you have feedback or need assistance with the MCP directory API, please join our Discord server