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 the watched file types into a single reindex. By default the watcher follows every file type the project scope indexes (extraction.discovery.project.include_extensions, which covers the config and code files described in Beyond Python); list extensions under serve.watch.extensions to override it.

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. Your project’s own JavaScript, TypeScript/TSX, C headers/sources, Rust, and Java files are indexed by default too, as structural symbols (functions, classes, structs, interfaces) parsed with tree-sitter grammars that ship in the default install.

  • Dependencies stay text/config by default: second-language code inside installed packages is often vendored or generated, so dependency walks skip it unless you opt in. Name the extensions you want for dependencies, or narrow the project list to opt your own code out:

    # pydocs-mcp.yaml
    extraction:
      discovery:
        dependency:   # the list REPLACES the default: Python, Markdown, notebooks,
                      # Rust and TypeScript (add ".toml", ".yaml", … to keep config)
          include_extensions: [".py", ".md", ".ipynb", ".rs", ".ts"]
        project:      # your project: Python, Markdown, and notebooks only
          include_extensions: [".py", ".md", ".ipynb"]
    

    Without usable grammar wheels (for example, an install built from the source distribution), code files still index as searchable text — you lose the symbol outline, not the file. A one-line log tells you why symbols are missing.

What works per language (today): full-text search, symbol outlines, and surrounding-context expansion work for every indexed language. The call/ import/reference graph covers Python and the code languages; get_references reports its resolution as syntactic (precision-biased and matched by name and import alias, not scope-resolved). When the index was built without a language’s grammar, it reports resolution as unavailable rather than pretending. Per-symbol member listings remain Python-only. 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

PYDOCS_CACHE_DIR sets the same bundle root for a whole shell session (child processes inherit it); --cache-dir overrides it per command, and both override the ~/.pydocs-mcp default.

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. An activity panel above each answer shows what the agent searched, opened and (when the endpoint returns it) reasoned; the model’s reasoning can be incomplete or unfaithful and can quote the files it read, so treat it as working notes. The sidebar’s Connection dialog also carries the model settings — a Thinking switch plus Temperature, Max output tokens, Top p and Seed, prefilled from ask_your_docs.llm.params — and offers only the ones this model and endpoint can honour: what it hides, it does not send, and Test connection reports exactly what went out. 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.