Skip to main content
Glama
gustavoali

knowledge-index

by gustavoali

knowledge-index (ki)

A private, self-hosted RAG service exposed over MCP (Model Context Protocol), built as a replacement for NotebookLM in a personal AI-tooling ecosystem: documents stay on hardware I control, retrieval is hybrid (BM25 + dense vectors fused with RRF), and every capability is reachable as a tool from a Claude Code session instead of a web UI.

Project status — read this first. The ingestion, chunking, retrieval and embedding layers are implemented and covered by 130 passing unit tests under ruff + mypy --strict. The storage layer (PostgreSQL schema/migrations and the OpenSearch index + RRF pipeline) is written and its integration tests exist, but its live acceptance criteria are still unverified: the host that runs OpenSearch and Postgres has been offline, so the end-to-end pipeline has not been exercised against real services. This is not a deployed product — it is a working codebase with a deliberate architecture, and I would rather say so than imply otherwise.


Why it exists

Hosted notebook tools are convenient but impose three costs I did not want to pay: documents leave my machine, the tool cannot be driven programmatically, and scraping around those limits violates the terms of service. ki trades that convenience for control — the corpus lives on my own host, and the interface is MCP, so an agent can ingest, query and manage notebooks as ordinary tool calls.

Related MCP server: mcp-rag-assistant

What it does

  • Ingests documents (PDF, Markdown, TXT today; URL, YouTube, Google Docs, DOCX planned) into isolated notebooks.

  • Retrieves with a hybrid strategy: BM25 lexical search and dense k-NN vector search, fused by Reciprocal Rank Fusion, then optionally re-ranked with a cross-encoder.

  • Exposes 14 MCP tools over SSE, consumed directly from agent sessions.

  • Abstracts the embedding provider behind one interface — Cohere (default), Voyage, OpenAI, Gemini — so the model is a configuration decision, not an architectural one.


Architecture

SOURCE            PDF · Markdown · TXT
   |
   v
ADAPTERS          pypdf primary, pdfplumber fallback when extraction yields <100 chars/page
                  de-hyphenation · whitespace normalization · encoding cascade (utf-8-sig → cp1252 → latin-1)
                  page_map preserved (page, char_start, char_end) for citations
   |
   v
CHUNKING          structure-aware splitting · content_hash for deduplication
   |
   v
EMBEDDINGS        provider registry behind one ABC · 1024-dim vectors
                  notebook freezes its provider+model at creation time
   |
   v
STORAGE           PostgreSQL  → notebooks, sources, chunks, jobs, costs (5 tables, 8 indices,
                                 NOTIFY + updated_at triggers, Alembic migrations)
                  OpenSearch  → ki_chunks index, HNSW (lucene, cosine, 1024) + BM25
   |
   v
RETRIEVAL         hybrid query → RRF fusion → optional cross-encoder rerank
   |
   v
INTERFACE         14 MCP tools over SSE

Stack: Python 3.11 · Pydantic · asyncio · OpenSearch · PostgreSQL + Alembic · Cohere · pypdf/pdfplumber · pytest · ruff · mypy (strict).


Design decisions and trade-offs

The interesting part of this project is not the code, it is what got ruled out and why.

Vector store: reuse OpenSearch instead of adding Qdrant or pgvector

Chosen: the OpenSearch cluster already running on my host, with a dedicated index.

  • Qdrant has better ergonomics and native payload filtering, but it means standing up another service at roughly 500 MB of RAM on hardware that is already tight.

  • pgvector adds zero infrastructure, but HNSW performance degrades past ~100k chunks, lexical search via pg_trgm is materially weaker than BM25, and there is no native RRF.

OpenSearch gives BM25 and k-NN in one engine with RRF available in the search pipeline, at no additional memory cost. The price paid: the index mapping fixes vector dimensionality at 1024, which constrains which embedding models are usable, and the cluster is shared — so isolation is by index name with number_of_shards=1.

Embeddings: multi-provider abstraction, Cohere as default

Self-hosting bge-m3 was the theoretically better answer (no runtime cost, nothing leaves the host) and was measured and rejected: the available hardware has 8 GB of RAM and no usable GPU, and the model's working memory would starve the OpenSearch instance sharing the box. Gemini's embeddings are 768-dimensional and would not fit the index without a full rebuild.

So the provider sits behind an ABC with a registry, and each notebook freezes its provider and model at creation time — mixing embedding spaces inside one index silently destroys retrieval quality, and freezing makes that failure impossible rather than merely discouraged. Migration is handled explicitly by a re-index operation.

Query returns chunks, not a synthesized answer

ki_query returns ranked chunks; synthesis is opt-in through a separate tool. The consumer is already an LLM session, so synthesizing server-side would mean paying for a second model call to produce something the caller can do for free — and it would force notebooks marked sensitive through a cloud provider they are specifically configured to avoid.

Privacy as an enforced constraint, not a convention

Notebooks can be flagged sensitive. The provider layer rejects any provider whose is_cloud flag is true for those notebooks — enforced in the abstraction, with parametric tests covering it, rather than left to the caller to remember.


Measured numbers

Metric

Value

Unit tests

130, passing

Type checking

mypy --strict, clean

Lint

ruff, clean

Embedding latency (Cohere embed-multilingual-v3.0)

~1150 ms p95 (target was <2 s)

Embedding dimensionality

1024, confirmed against the live API

Embedding providers implemented

4 (1 fully live, 3 behind the same interface)

PostgreSQL schema

5 tables · 8 indices · 2 triggers

The rerank path is deliberately disabled by default in development: the Cohere trial allows 10 rerank calls per month against 1000 embedding calls, which is trivially exhausted during active retrieval work. Finding that in the response headers before it caused a mid-development outage is exactly the kind of cost detail that separates a demo from something operable.


Running it

pip install -e ".[dev]"
cp .env.example .env.local        # fill in COHERE_API_KEY and KI_PG_DSN

# Unit tests — no network, no external services required
pytest tests/unit -v

# Live integration tests — require reachable OpenSearch + PostgreSQL and real credentials
KI_TEST_LIVE=1 pytest tests/integration -v

Bootstrapping the storage layer (both idempotent, safe to re-run):

alembic upgrade head              # PostgreSQL: schema, tables, indices, triggers
python scripts/init_opensearch.py # OpenSearch: ki_chunks index + RRF search pipeline

Configuration

Variable

Purpose

COHERE_API_KEY

Embedding provider credential

KI_PG_DSN

libpq DSN; rewritten internally to +asyncpg / +psycopg

KI_PG_SCHEMA

Defaults to ki

KI_OPENSEARCH_URL

e.g. http://localhost:9200

KI_EMBEDDING_PROVIDER

cohere (default), voyage, openai, gemini

KI_TEST_LIVE

Set to 1 to run integration tests against real services

Secrets are typed as Pydantic SecretStr so they are never emitted through logs or reprs.


What I would do next

  • Verify the live acceptance criteria for the storage layer once the host is back, and run the end-to-end smoke over a real corpus.

  • Add retrieval quality evaluation — a ground-truth query set with recall@k, so changes to chunking or fusion can be judged by measurement instead of impression.

  • Add tracing over the ingestion and query paths; per-notebook cost accounting is already modelled in the schema but not yet surfaced.

License

Not currently licensed for reuse. Published as a portfolio artifact.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    A
    quality
    B
    maintenance
    A self-hosted, notebook-scoped RAG pipeline with contextual retrieval, delivered as an MCP server. Enables ingestion of documents into named notebooks and semantic search returning raw ranked chunks.
    11
  • F
    license
    -
    quality
    C
    maintenance
    Provides RAG-based knowledge retrieval and document management as MCP tools, supporting hybrid search, reranking, and retrieval process visualization.
  • A
    license
    -
    quality
    B
    maintenance
    MCP server for local RAG over personal notes, PDFs, and documents, enabling plain-English querying and hybrid search with multi-hop context expansion.
    MIT

View all related MCP servers

Related MCP Connectors

  • Multi-engine search for AI agents. Trust scoring, local corpus, MCP-native. Self-hostable, BYOK.

  • User-owned memory for AI agents, Copilot, Claude, IDEs, CLIs, and chat apps over remote MCP.

  • Self-hosted MCP gateway: turn any API, database or MCP server into AI connectors — no code.

View all MCP Connectors

Latest Blog Posts

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/gustavoali/hybrid-rag-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server