Configuration¶
The MCP tool surface is pinned at the nine task-shaped tools
(frozen contract: docs/tool-contracts.md). Every other knob — ranking
weights, fusion algorithm, embedder identity, reference-graph capture toggles,
chunking strategies, output limits, formatter choice — lives in AppConfig
(pydantic-settings) with layered defaults:
shipped defaults/default_config.yaml (lowest priority)
→ shipped pipeline blueprints (pipelines/*.yaml)
→ your overlay — the first of: --config PATH → PYDOCS_CONFIG_PATH
→ ./pydocs-mcp.yaml → ~/.config/pydocs-mcp/config.yaml
→ env vars (PYDOCS_*) (highest priority)
The overlay is auto-discovered: with no --config flag and no
PYDOCS_CONFIG_PATH, a pydocs-mcp.yaml in the current directory wins,
falling back to ~/.config/pydocs-mcp/config.yaml, then to the shipped
baseline alone.
This keeps MCP clients (Claude Code, Cursor, IDE extensions) stable across deployments while giving you per-project experiment tracking: two YAMLs produce two comparable retrieval runs with nothing rebuilt client-side. (This is exactly what the benchmark harness exploits.)
Shipped blueprints¶
pipelines/chunk_search_graph.yaml/…_graph_ranked.yaml— default chunk search: dense retrieval +graph_expandreference-graph expansion.pipelines/chunk_search.yaml— BM25 chunk search (the former default).pipelines/chunk_search_ranked.yaml— BM25, ranked top-K (no composite collapse).pipelines/chunk_search_dense.yaml/…_dense_ranked.yaml— dense retrieval.pipelines/chunk_search_hybrid.yaml/…_hybrid_ranked.yaml— BM25 + dense fused via RRF.pipelines/decision_search.yaml— an active default route, not an opt-in preset:kind=decisionqueries route here via thekind_is_decisionpredicate (BM25 ∥ dense over mined decision records, RRF-fused).pipelines/chunk_search_deps.yaml— also an active default route:scope=depsqueries route here via thescope_is_dependencies_onlypredicate (BM25 over all dep chunks ∥ dense over their doc pages, RRF-fused).pipelines/chunk_search_late_interaction.yaml/…_late_interaction_ranked.yaml— late-interaction (ColBERT / PyLate) MaxSim re-scoring ([late-interaction]extra).pipelines/tree_only.yaml— LLM tree-reasoning only (vectorless).pipelines/chunk_search_with_tree_reasoning_parallel.yaml— hybrid + LLM tree-reasoning in a parallel branch, fused downstream.pipelines/chunk_search_with_tree_reasoning_after.yaml— hybrid first, then LLM tree-reasoning as a downstream reranker.pipelines/member_search.yaml— default member search.pipelines/ingestion.yaml— default ingestion (discovery → read → chunk → reference capture → flatten → content-hash → embed → package build).pipelines/ingestion_late_interaction.yaml— ingestion with the multi-vector embed stage ([late-interaction]extra).
Pipeline schema¶
name: chunk_search
steps:
- name: fetch # addressable + greppable
type: chunk_fetcher # a registered step type
params: { schema_name: chunk }
- name: score
type: bm25_scorer
params: {}
# …
Each step entry needs a name:, a registered type:, and params: matching the
step’s dataclass fields.
Example overlay¶
# my-pydocs.yaml
extraction:
chunking:
markdown:
max_heading_level: 4 # default: 3
search:
output:
default_limit: 20 # default: 10 — bounds the client `limit` param
# (multi-repo unions); single-project count =
# the pipeline YAML's limit step
reference_graph:
capture:
enabled: true
kinds: [calls, imports, inherits, mentions] # opt into MENTIONS
pydocs-mcp --config ./my-pydocs.yaml serve . # --config is global — it precedes the subcommand
# or: PYDOCS_CONFIG_PATH=./my-pydocs.yaml pydocs-mcp serve .
Description surface, suggestions, and session-start context¶
The LLM-facing text and the deterministic nudges around it have their own
knobs (decision records: docs/adr/0005–0008; authoring guide:
docs/description-authoring.md):
Key |
Default |
Effect |
|---|---|---|
|
|
Serve the tool descriptions / server instructions / session-start preamble from an override document instead of the packaged |
|
|
Inject the session-start context pack (marker line + preamble + overview card + installed-package version inventory) into the ask-your-docs agent prompt at agent-session start. |
|
|
Hard cap on the pack in real (tiktoken) tokens; the overview card is trimmed before the version inventory, and truncation is always noted in the pack. |
|
|
Zero-hit |
|
|
Truncated |
|
|
Zero-hit |
The suggestion lines are deterministic machinery with a fixed [suggestion:
prefix — server-initiated nudges, distinguishable in any transcript from
model-chosen routing. Each flag ablates one rule independently; with a flag
off, that rule’s responses are byte-identical to output with no suggestion
machinery at all. The startup log pins which description document a run
served: descriptions artifact <hash12> source=packaged|<path>.
Every tunable is listed in python/pydocs_mcp/defaults/default_config.yaml —
read it as the canonical reference.
Database schema¶
The SQLite file holds ten tables (schema v15): the six core tables diagrammed
below plus chunk_multi_vector_ids, node_scores, decision_records, and
index_metadata (covered in the read-path table underneath). The schema is
versioned via PRAGMA user_version; known older versions are migrated in
place with additive, data-preserving sweeps — only an unrecognized version
drops the known tables and re-indexes from scratch. Dense vectors are not
in SQLite — they live in the per-project .tq TurboQuant sidecar.
erDiagram
packages {
TEXT name PK
TEXT version
TEXT content_hash "xxh3 of (path,mtime) pairs"
TEXT origin "site-packages | __project__"
TEXT local_path
}
chunks {
INTEGER id PK
TEXT package FK
TEXT module
TEXT title
TEXT text "indexed by chunks_fts (FTS5)"
TEXT content_hash
TEXT source_path "v15: span provenance"
INTEGER start_line
INTEGER end_line
}
chunks_fts {
FTS5 virtual "porter + unicode61 tokenizer"
}
module_members {
INTEGER id PK
TEXT package FK
TEXT module
TEXT name "function/class/method"
TEXT kind
TEXT signature
TEXT docstring
}
document_trees {
TEXT package PK
TEXT module PK
TEXT tree_json "DocumentNode tree"
TEXT content_hash
}
node_references {
TEXT from_package PK
TEXT from_node_id PK
TEXT to_name PK
TEXT kind PK "CALLS | IMPORTS | INHERITS | MENTIONS | SIMILAR (opt-in) | GOVERNS"
TEXT to_node_id "null if unresolved"
}
packages ||--o{ chunks : has
packages ||--o{ module_members : has
packages ||--o{ document_trees : has
packages ||--o{ node_references : owns
chunks ||--|| chunks_fts : indexed_by
Table |
What it stores |
Where it’s read |
|---|---|---|
|
One row per indexed package + the cache-skip |
Indexing skip-check, |
|
Documentation + source chunks (markdown sections, docstrings, code blocks). |
|
|
FTS5 virtual table mirroring |
|
|
Functions, classes, methods, attributes — name + signature + docstring + kind. |
|
|
The hierarchical |
|
|
The reference graph: one row per ( |
|
|
Bridges |
|
|
Per-symbol in-degree, PageRank, and Louvain community (opt-in via |
|
|
Mined architectural-decision records. |
|
|
Per-bundle stamp: project name/root, embedder identity, |
Multi-repo loading ( |
The schema is documented in python/pydocs_mcp/db.py.