- TypeScript 97.5%
- JavaScript 2.5%
| .forgejo/workflows | ||
| docs | ||
| src | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.test.json | ||
| vitest.config.ts | ||
project-docs.mcp
An MCP stdio server that makes a single project's documentation searchable through RAG.
It indexes the project's README, standard root files, and everything under docs/, and exposes
one tool: search_project_docs. The index lives inside the project at
.cache/project-docs/index.sqlite (gitignored, derived data) and re-syncs itself on every call —
editing a doc file simply makes the next search see it.
- Embeddings:
qwen/qwen3-embedding-4bvia OpenRouter (no huge local model downloads). - Retrieval: hybrid vector search (
sqlite-vec) + FTS5 keyword search, fused with Reciprocal Rank Fusion. - Reranking:
qwen/qwen3-reranker-8bvia OpenRouter, on by default, with graceful fallback to RRF ordering when the API fails.
Requirements
- Node >= 24
OPENROUTER_API_KEYenvironment variable with real credits: embeddings cost ~$0.02/M input tokens and reranking is billed per request. Only changed files are ever re-embedded.
Running
Configure the server in your MCP client (e.g. for Claude Desktop or Cursor). The exact configuration depends on how the server is installed:
Installed as a package
With the package installed (npm i -g project-docs.mcp or listed in your client's
package_manager), the project-docs-mcp binary is on PATH. If you build from this repo, a
never-versioned tarball of the latest main is published to a
moving latest release —
With the package installed (npm i -g project-docs.mcp or listed in your client's
package_manager), the project-docs-mcp binary is on PATH. If you build from this repo, a
never-versioned tarball of the latest main is published to a
moving latest release —
download the artifact and install it directly with
curl -fL --retry 5 -o /tmp/project-docs-mcp.tgz https://git.sorst.net/philipsorst/project-docs.mcp/releases/download/latest/project-docs-mcp.tgz && npm install -g /tmp/project-docs-mcp.tgz.
{
"mcpServers": {
"project-docs": {
"command": "project-docs-mcp",
"args": ["--project", "/absolute/path/to/your-repo"],
"env": {"OPENROUTER_API_KEY": "sk-or-..."}
}
}
}
IntelliJ IDEA
IntelliJ launches MCP servers with the opened project's directory as the working directory, so
--project can be omitted entirely — just the workspace being the project dir is sufficient.
Keep .mcp.json in the project IntelliJ has open (it is auto-detected; you can also add it via
Tools → MCP Servers → Add Server).
Installed as a package
With the package installed (npm i -g project-docs.mcp), the project-docs-mcp binary is on
PATH and no arguments are needed:
{
"mcpServers": {
"project-docs": {
"command": "project-docs-mcp",
"env": {"OPENROUTER_API_KEY": "sk-or-..."}
}
}
}
From a repo checkout
If you use this repository directly as a checkout, build it once (pnpm install && pnpm build)
and point the command at the built dist/index.js:
{
"mcpServers": {
"project-docs": {
"command": "node",
"args": [
"/path/to/project-docs.mcp/dist/index.js"
],
"env": {"OPENROUTER_API_KEY": "sk-or-..."}
}
}
}
Passing --project /absolute/path/to/your-repo explicitly works too and wins over the working
directory.
Note (checkout only): the checkout must be built (pnpm build) — dist/index.js is
gitignored — and node must be >= 24 and on PATH for IntelliJ's process launcher.
Options
| Option | Description |
|---|---|
--project <path> |
Root of the project to index. Defaults to the current working directory — with an MCP client that launches the server with the project dir as cwd (e.g. IntelliJ), it can be omitted. |
--db <path> |
Index database path. Defaults to <project>/.cache/project-docs/index.sqlite. Delete it to force a full reindex. |
--no-rerank |
Disable the rerank stage (pure RRF ordering). |
--help, -h |
Usage. |
What gets indexed
- Top-level standard files, matched case-insensitively:
README.md,CONTRIBUTING.md,CHANGELOG.md,CODE_OF_CONDUCT.md,SECURITY.md,SUPPORT.md,CODEOWNERS.LICENSEis intentionally skipped. - The top-level
docs/directory (case-insensitive), recursively, for: markdown/text-like extensions.md,.markdown,.mdx,.rst,.txt,.adoc.
Hidden files/directories, node_modules, symlinks, binary content, and files over 1 MiB are
skipped silently.
Tool: search_project_docs
{
"query": "how do I configure the retry budget?",
"limit": 5,
"max_chars": 6000
}
Each call:
- Syncs the index from disk: new/changed files are chunked and re-embedded (batches of ≤16, 2 retries with backoff), removed files are dropped. Unchanged files cost nothing.
- Runs hybrid vector + FTS5 search fused with RRF, reranks the top candidates, and groups matching sections by file.
- Returns results bounded by the
max_charsbudget, withmore_sectionsflags so an agent knows when a full file is worth opening itself.
Example response:
{
"status": {
"project": "/path/to/project",
"indexed_files": 14,
"files_added": 1,
"files_updated": 0,
"files_removed": 0,
"chunks": 47,
"model": "qwen/qwen3-embedding-4b",
"reranker": "qwen/qwen3-reranker-8b",
"db": "/path/to/project/.cache/project-docs/index.sqlite"
},
"results": [
{
"doc_id": "docs/configuration.md",
"title": "docs/configuration.md",
"score": 0.0328,
"sections": [
{
"heading": "Configuration > Retries",
"content": "The retry budget is configured with RETRY_LIMIT...\n\n[... truncated ...]"
}
],
"more_sections": true,
"total_sections": 4,
"shown_sections": 1
}
]
}
Validation: an empty/whitespace-only query is rejected (isError: true), and a missing
OPENROUTER_API_KEY fails loudly on first use rather than silently degrading.
Development
See AGENTS.md for the engineering contract. Quick start:
pnpm install
pnpm typecheck && pnpm typecheck:test && pnpm build && pnpm test
# live smoke tests against the real API (manual only):
OPENROUTER_API_KEY=... pnpm test -- tests/live.test.ts
Limitations
- One project per server process (pass the path at startup); run multiple server instances for multiple projects.
- The cache is a derived artifact kept in
.cache/project-docs/(gitignored): if embedding model constants change, the server refuses to open an old index (embedding model mismatch) until you deleteindex.sqlite. - No document-level ACLs or multi-user support — it mirrors your repo's file permissions.