# Contextator

> Contextator is a self-hosted, multi-tenant documentation server that takes a project's documentation from wherever it already lives — a mounted folder, a git repository, an uploaded archive, an Obsidian vault or a Notion workspace — and makes it searchable behind a single MCP endpoint. Every project gets its own URL and its own document collection; embeddings are generated on the machine's CPU by default, and the documents never reach a third party.

- Role: Founder & Developer
- Class: Flagships
- Status: Ongoing
- Started: 2026-09-17
- Technologies: TypeScript, Node.js, Fastify, PostgreSQL, pgvector, Drizzle ORM, MCP, transformers.js, Docker, Notion API, Vitest, Biome
- Skills: MCP Server Development, Retrieval System Design, Multi-Tenant Architecture, Vector Search, Hybrid Search, Embedding Pipelines, Document Parsing, Incremental Indexing, Self-Hosted Deployment, Access Control, Open Source Maintenance, Evaluation Harness Design
- Website: https://contextator.com
- Source code: https://github.com/Contextator/Contextator
- Source: https://www.muhammetsafak.com.tr/en/portfolio/contextator/
- Language: en-US
- Author: Muhammet Şafak

---
Contextator is an open-source MCP server I built so that a coding agent can reach a project's documentation without the documentation having to move anywhere. The promise in one sentence: you give a project its sources — a folder mounted on the server, a git repository or a single subdirectory of it, an uploaded `.zip`/`.tar.gz`/`.rar` archive, an Obsidian vault or a Notion workspace — and that project becomes its own Model Context Protocol endpoint at `/mcp/<project-name>`. Claude Code, Cursor, Claude Desktop or any client that speaks MCP connects there and works with three tools: `search_docs` searches semantically, `list_topics` lists the indexed documents grouped by directory, and `read_document` returns a file or a single section of it. Every source is mounted under its own name, so a document reads as `handbook/install.md` — and a client connected to `/mcp/billing` never sees `/mcp/mobile`.

Three decisions carry the architecture. **Isolation is the address:** each project holds its own URL and its own document collection, so there is no cross-project search by construction — the alternative was one endpoint with a `project` argument, and in that design isolation would have depended on the agent passing the right one. **The default path never leaves the host:** embeddings are generated on the machine's CPU with `Xenova/multilingual-e5-small` (384 dimensions, a multilingual retrieval model that covers Turkish), and a few environment variables switch it to OpenAI embeddings — but nothing requires an API key to work. **A search asks two indexes:** because a sentence model cannot represent a constant name or an error code, every chunk sits in a full-text index as well as an HNSW vector index, and a search fuses the rankings of both lists — ranks rather than scores, since any weighting of scores would have to be re-learnt each time the embedding model changed. Files are tracked by sha256: only a changed file is re-embedded, a deleted file's documents are dropped, and a full rebuild is written *beside* the live index and published in one step, so the project keeps answering for the whole run.

The product ships as a single Docker container: PostgreSQL 16 + pgvector and the Node.js application live in the same image, started and stopped together by a small entrypoint script — the cost of the "installation is one command" decision is a deliberate departure from one-process orthodoxy. `.html`, `.docx`, `.csv` and `.pdf` files are converted to Markdown as they are indexed; a scanned PDF with no text layer is refused rather than indexed, because the failure nobody notices is the one that produces a document that exists, is listed, and matches nothing. Accounts and roles are `root`/`admin`/`member` plus a per-project `viewer`/`editor` membership, every MCP endpoint carries a door with three settings (open · bearer token · account required, through OAuth 2.1), and retrieval quality is measured against a golden set, with the build failing in CI when that quality drops below its floor. The licence is AGPL-3.0-or-later: anyone serving a modified version over a network publishes those modifications under the same licence.
