Quick start¶
pip install pydocs-mcp # from PyPI — the usual path
Prebuilt wheels bundle the Rust acceleration core for Linux (x86_64 /
aarch64), macOS (Apple Silicon), and Windows (x86_64) — no toolchain
needed. On any other platform pip builds from the sdist. To build from source
instead — for development, or to compile the Rust core on an unlisted platform:
git clone https://github.com/msobroza/pydocs-mcp && cd pydocs-mcp
pip install maturin && maturin develop --release
Linux needs OpenBLAS for the vector store (macOS and Windows already ship it):
sudo apt-get install -y libopenblas-pthread-dev
Then index your project and start the server:
pydocs-mcp serve . # index project + deps, serve over MCP (stdio)
pydocs-mcp serve . --gpu # …same, with CUDA-accelerated embeddings
pydocs-mcp search "batch inference" # the same search, from the CLI
pydocs-mcp refs requests.auth.HTTPBasicAuth --direction inherits
Embeddings run on CPU by default. Add --gpu to serve / index / watch
(or the benchmark runner) to move all embedder inference — FastEmbed, the
sentence_transformers provider, and PyLate — onto CUDA. It’s a latency knob only: no YAML change, no
re-index, identical results. Needs the matching GPU runtime — see
INSTALL.md.
Live re-indexing¶
The file watcher is part of the default install — no extra step. If you edit code while you want the index to stay fresh, pick one of two modes — both debounce edits to every indexed file type (Python, docs, and the text/config formats indexed by default — see Beyond Python) into a single reindex.
pydocs-mcp serve . --watch # MCP server + watcher (for AI clients)
pydocs-mcp watch . # watcher only (no MCP server; index stays fresh for CLI `search` / `symbol` / `refs`)
Both modes share the same YAML tunables: debounce, file extensions, and
ignored paths live under serve.watch.* in your pydocs-mcp.yaml (see
DOCUMENTATION.md).
Exclude directories from indexing¶
Keep generated docs, test fixtures, or vendored trees out of search results.
Declare additional exclusions in your project’s own pyproject.toml — they
travel with the repo:
[tool.pydocs-mcp]
exclude_dirs = ["docs/generated", "fixtures"]
Bare names ("fixtures") match at any depth; paths ("docs/generated")
match only that directory. Entries are additive over the built-in floor
(.git, .venv, …) — you can exclude more, never less. A server-side YAML
equivalent (extraction.discovery.project.exclude_dirs, plus a dependency
sibling) covers both project and dependency walks; see
DOCUMENTATION.md.
Beyond Python — multilanguage indexing¶
Most Python projects carry more than .py: docs, config, and sometimes a
second-language source tree. pydocs-mcp indexes the ones that carry real
search value.
Indexed by default: Python (
.py), notebooks (.ipynb), and the text/config formats — Markdown, reStructuredText, plain text, TOML, YAML, INI/CFG, and JSON. Docs and config are the bulk of what real pull requests touch beyond code, so they are on out of the box. Files are split into searchable sections (headings for prose, top-level keys/tables for config) with real line numbers.Code languages, opt-in: JavaScript, TypeScript/TSX, C headers/sources, and Rust. These stay off by default so a vendored
node_modulesor C extension tree doesn’t flood your results. Turn them on per project by naming the extensions you want:# pydocs-mcp.yaml extraction: discovery: project: include_extensions: [".py", ".md", ".ipynb", ".rs", ".ts"]
For structural symbols (functions, classes, structs) from these languages, install the grammar extra:
pip install 'pydocs-mcp[multilang]'
Without it, code files still index as searchable text — you lose the symbol outline, not the file. A one-line log tells you when that fallback kicks in and how to enable full parsing.
What works per language (today): full-text search, symbol outlines, and
surrounding-context expansion work for every indexed language. The call/
import/reference graph and per-symbol member listings remain Python-only —
for a non-Python target, get_references returns nothing and reports its
reference resolution as unavailable rather than pretending. Vendored trees
(node_modules, extern, third_party, and the like) and binary assets are
never indexed.
Multi-repo search (optional)¶
One MCP server can host several already-indexed repos. Index each once (every
project writes a portable {name}_{hash}.db + .tq bundle under --cache-dir),
then serve them all — each query searches across every loaded repo, or one via
the project scope:
# index a few repos into a shared directory of db bundles
pydocs-mcp index ~/code/frontend --cache-dir ~/pydocs-index
pydocs-mcp index ~/code/backend --cache-dir ~/pydocs-index
# serve them all from ONE MCP server (read-only — no reindex/watch)
pydocs-mcp serve --workspace ~/pydocs-index
pydocs-mcp serve --db ~/pydocs-index/backend_1a2b3c4d5e.db # or specific bundles
# query across all loaded repos, or scope to one by name
pydocs-mcp search "db pool" --workspace ~/pydocs-index
pydocs-mcp search "db pool" --workspace ~/pydocs-index --project backend
On the MCP surface the selector is the project filter, a sibling of
package/scope: search_codebase(query="db pool", project="backend") /
get_symbol(target="app.db.Pool", project="backend"); omit it to search every loaded
repo. When the same package appears in several repos, a root-project copy wins
over a dependency copy, and among duplicate dependencies the most-recently-indexed
one is kept. Every loaded db must share the configured embedder — a mismatch
fails fast (a read-only load can’t re-embed an absent project).
Calling get_overview with no selector on a multi-repo server returns a
workspace orientation card — one line per loaded repo with its package
count — so an agent that has just connected can see everything on offer before
it narrows to a project.
Cross-repo references (multi-repo workspaces)¶
When several bundles are served together, a link pass resolves each
bundle’s unresolved references against its siblings’ symbols, so
get_references answers cross repository boundaries: callers of a mylib
symbol include its backend call sites (project-qualified rows), impact
walks across repos, and previously-unresolved callees become navigable
targets. References the local index already resolved always take precedence —
cross-links only add what a single bundle could not see.
Links live in a disposable sidecar next to the bundles
(pydocs-links.sqlite3); the bundles themselves are never modified. Serve
refreshes stale links automatically at startup; pydocs-mcp link --workspace ~/pydocs-index pre-bakes them (CI images, read-only deployments) and
--check gates on freshness. Tuning lives under reference_graph.cross_repo
in YAML (enabled: true by default — inert for single-repo serving): linked
edge kinds, alias resolution for re-exports, workspace-level ranking scores
(PageRank needs the [graph] extra; degree ranking works everywhere), and an
opt-in embedding-similarity kind. The workspace overview card reports link
freshness (cross-repo links: fresh | stale(...)).
Ask your docs — chat agent (optional)¶
The first in-tree harness on the retrieval backbone: a LangGraph ReAct agent
plus a Streamlit chat UI over the MCP server, for asking questions across your
indexed repos in natural language. Install the harness-ask-your-docs extra and run
its command:
pip install 'pydocs-mcp[harness-ask-your-docs]'
harness-ask-your-docs --workspace ~/pydocs-index
Sidebar pickers pin a project / package / own-code-vs-dependency slice (enforced
on every tool call, not left to the model), and answers cite project +
package.module with a runnable usage snippet. Configuration and the
GPU-index / CPU-serve recipe live in
examples/harness/ask_your_docs_agent.
Fast dependency indexing (selective embedding)¶
Everything is BM25/FTS-indexed, but dense embedding is selective by package tier — embedding is the dominant indexing cost, and big dependencies (torch, sklearn) carry tens of thousands of code chunks:
Tier |
What gets dense vectors |
Selected by |
|---|---|---|
Project / subprojects |
every chunk (dense + graph, unchanged) |
automatic |
Promoted dependencies |
every chunk — project-grade |
|
Regular dependencies |
documentation only: one docstring page per module (module + public signatures + docstrings) plus |
default ( |
So torch indexes in seconds (≈one embedding per module) instead of an hour,
while its docs stay semantically searchable and all of its code stays
keyword-searchable + navigable (get_symbol, kind="api"). scope=deps queries
automatically route to a BM25 ∥ dense fusion pipeline that covers both. Set
dependency_policy: full to restore embed-everything, or none for BM25-only
dependencies:
pydocs-mcp index . --full-dep my-internal-lib --full-dep "acme-*"
Point any MCP-capable AI coding client or editor at it over stdio — copy-paste
client configs are in DOCUMENTATION.md,
and install troubleshooting (including the libopenblas fallback) is in
INSTALL.md.