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_modules or 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

--full-dep NAME (repeatable, globs OK) or embedding.full_index_dependencies

Regular dependencies

documentation only: one docstring page per module (module + public signatures + docstrings) plus .md/README chunks

default (embedding.dependency_policy: doc_pages)

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.