Skip to content

Latest commit

 

History

History
413 lines (312 loc) · 17.7 KB

API.md

File metadata and controls

413 lines (312 loc) · 17.7 KB

API Reference — 13 MCP Tools

Reference for the 13 MCP tools exposed by knowledge-rag. Existing parameter names and defaults are covered by tests/test_backwards_compat.py; additive optional parameters are also checked by the public API surface gate.

Compatibility: MCP spec 2026-07-28 · Anthropic Tier 1 SDK (mcp>=2.0.0,<3.0.0) · Claude Code · Claude Desktop · Cursor · Windsurf · VS Code (Copilot) · Cline · Gemini CLI · Zed.

Related docs:


Search & Query

search_knowledge

Search using semantic retrieval and BM25 with optional cross-encoder reranking, or the opt-in FTS5 lexical index.

Parameter Type Default Description
query string required Search query text; use identifiers for lexical search or a question for semantic search
max_results int 5 Maximum results to return; clamped to search.max_results (100 by default)
category string null Filter by category. Accepts the configured categories (category_mappings values and keyword_routes keys) plus general; an empty string, like omitting it, searches every category, and other values return an error that lists the valid ones. list_categories() shows which categories hold documents
hybrid_alpha float 0.3 Balance: 0.0 = keyword only, 1.0 = semantic only
min_score float 0.0 Minimum score (0.0-1.0) to include a result. Scores are min-max normalized per query over its candidates: the best scores 1.0 and the weakest 0.0, while a single candidate or candidates that all tie score 1.0. On the hybrid path (results labeled hybrid, semantic or keyword) the candidates are the returned results, so any min_score above 0 drops at least the weakest one unless all tie; fts5 results are BM25 scores normalized over up to max(3 * max_results, 20) hits, so even the weakest returned one can score well above 0.0. It is not an absolute relevance threshold
snippet_mode bool true Truncate content to ~500 chars from the start of the matching chunk, at natural break points. Adds content_length field. With snippet_mode=false, content can include the adjacent chunks and match_offset (when present) is where the matching chunk starts in it
search_method string "auto" auto, hybrid, or fts5; explicit fts5 requires the feature to be enabled and ready
include_folders list[string] null Recursively search only these directories relative to documents_dir, before top-k selection
exclude_folders list[string] null Recursively exclude these directories before top-k; exclusions override inclusions

auto uses the configured lexical router only when FTS5 is enabled. It falls back to hybrid search when the lexical index is unavailable, fails, or returns too few useful hits. hybrid always bypasses that router. An explicit fts5 request returns a structured error if the index is disabled or not ready; it does not silently switch methods. hybrid_alpha controls the hybrid path, not FTS5 ranking. Scores are ranking signals, not calibrated probabilities, and should not be compared as confidence across methods.

Returns:

{
  "status": "success",
  "query": "mimikatz credential dump",
  "hybrid_alpha": 0.5,
  "result_count": 3,
  "filtered_by_score": 2,
  "cache_hit_rate": "0.0%",
  "results": [
    {
      "content": "Mimikatz can extract credentials from memory...",
      "source": "documents/security/credential-attacks.md",
      "filename": "credential-attacks.md",
      "category": "security",
      "score": 0.9823,
      "raw_rrf_score": 0.016393,
      "reranker_score": 0.987654,
      "semantic_rank": 2,
      "bm25_rank": 1,
      "search_method": "hybrid",
      "keywords": ["mimikatz", "credential", "lsass"],
      "routed_by": "redteam"
    }
  ]
}

Search Method Values:

  • hybrid: Found by both semantic and BM25 search
  • semantic: Found only by semantic search
  • keyword: Found only by BM25 keyword search
  • fts5: Retrieved through the lexical fast path; optional reranking retains this label

The routed_by field reports category keyword routing for diagnostics. It does not impose a category filter or boost. Pass category explicitly to restrict results.

Folder filters intersect with category and apply consistently to semantic, BM25, FTS5 and cached results. They accept at most 32 relative directories each; absolute paths, .., alternate data streams and escaping links are rejected. See scope examples and evaluation limits for recursive matching, missing folders, platform path conventions and candidate selection.


get_document

Retrieve the full content of a specific document.

Parameter Type Description
filepath string Path to the document file

Returns: JSON with document content, metadata, keywords, and chunk count.


reindex_documents

Index or reindex all documents in the knowledge base. Runs in background — returns immediately. Poll progress via get_reindex_status().

Parameter Type Default Description
force bool false Reprocess and re-embed all discovered documents, including unchanged files, with a smart-reindex checkpoint. Without it, unchanged files are skipped.
full_rebuild bool false Re-embed all documents into a staging collection and replace the live collection after validation. Requires a running orchestrator and temporary storage for both collections.
resume bool false Resume a compatible smart-reindex checkpoint; missing, corrupt, or incompatible checkpoints start a fresh smart run. Cannot be combined with full_rebuild=true.

Returns: {"status": "started", "operation": "..."} immediately. If a reindex, a file-watcher pass or a document write is already running, returns {"status": "already_running", "progress": "1200/3734"}; retry after it finishes. full_rebuild=true waits for a watcher pass or document write instead.

Poll until reindex.active is false, then inspect last_error and last_result.errors. An idle worker is not proof of a successful rebuild. Smart reindexing writes a resumable checkpoint; see the operations guide.


get_reindex_status

Get the current status of a background reindex operation. Lightweight — does not compute full index statistics.

Returns (active):

{
  "status": "success",
  "reindex": {
    "active": true,
    "operation": "nuclear_rebuild",
    "progress": "1200/3734",
    "percent": 32,
    "indexed": 1200,
    "skipped": 0,
    "errors": 0,
    "started_at": "2026-06-17T18:29:49"
  }
}

Returns (idle): {"status": "success", "reindex": {"active": false, ...}}, with the last operation's result or error when available. When the last run had errors, last_result includes failed_files ([{"path", "reason"}, ...], deduped by path, at most 100 entries) and failed_files_truncated (true when more than 100 distinct files failed); below the cap, errors matches the list length. A failed full_rebuild appears in last_error, whose message lists the first failing paths, and the partial last_result still carries failed_files:

{
  "status": "success",
  "reindex": {
    "active": false,
    "last_result": {
      "errors": 2,
      "failed_files": [
        {"path": "C:/docs/corrupt.pdf", "reason": "PdfReadError: EOF marker not found"},
        {"path": "C:/docs/locked.docx", "reason": "PermissionError: access denied"}
      ],
      "failed_files_truncated": false
    }
  }
}

list_categories

List all document categories with their document counts.

Returns:

{
  "status": "success",
  "categories": {
    "security": 52,
    "development": 8,
    "ctf": 12,
    "general": 3
  },
  "total_documents": 75
}

list_documents

List indexed documents one page at a time, optionally filtered by category.

Parameter Type Default Description
category string null Optional filter, matched exactly against each document's category; an empty string, like omitting it, lists every document, and a category without documents returns total: 0. list_categories() lists the categories in the index
limit int 100 Maximum documents to return, clamped to 1–200
offset int 0 Matching documents to skip; negative values count as 0

Returns: JSON with total (documents matching the filter), offset, limit, count (documents in this page) and documents, ordered by source path, each with id, source, category, format, chunks, and keywords. For the next page, call again with offset + count until offset reaches total.


get_index_stats

Get statistics about the knowledge base index.

Returns:

{
  "status": "success",
  "stats": {
    "total_documents": 75,
    "total_chunks": 9256,
    "categories": {"security": 52, "development": 8},
    "supported_formats": [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv", ".ipynb"],
    "embedding_model": "BAAI/bge-small-en-v1.5",
    "embedding_dim": 384,
    "reranker_model": "Xenova/ms-marco-MiniLM-L-6-v2",
    "chunk_size": 1000,
    "chunk_overlap": 200,
    "query_cache": {
      "size": 12,
      "max_size": 100,
      "ttl_seconds": 300,
      "hits": 45,
      "misses": 23,
      "hit_rate": "66.2%"
    },
    "self_recall": {
      "sampled": 300,
      "top1": 0.9867
    },
    "hnsw_ef_search": 100
  }
}

self_recall probes the HNSW index (GH #287): up to 300 chunks are sampled with a fixed seed over sorted ids (deterministic across rebuilds), each is queried by its own stored vector, and top1 reports the fraction that comes back as the top-1 result. Exact-distance ties count as hits, so identical chunks under different ids do not read as misses. With 300 samples the metric has a sampling error of about ±0.7 percentage points (95% CI) — compare before/after values, not against a hard 1.0. A value well below 1.0 means some chunks cannot find themselves: raise search.ef_search in config.yaml and run reindex_documents(full_rebuild=True) (see docs/reindex-operations.md). sampled: 0 with top1: null has three causes: an empty index, a reindex in progress (the reindex.active field in the same payload disambiguates; the probe is skipped while a reindex runs), or a probe failure, which adds an error field. The probe is cached for 300 seconds and recomputed after every index mutation.

hnsw_ef_search is the value ChromaDB actually applies to the live collection. It only changes when a collection is created, so it can lag behind search.ef_search in config.yaml until the next full_rebuild — when the two differ, the configured value is not in effect yet.


Document Management

add_document

Add a new document to the knowledge base from raw content. Saves the file to the documents directory and indexes it immediately.

Parameter Type Default Description
content string required Full text content of the document
filepath string required Relative path within documents dir (e.g., security/new-technique.md)
category string "general" Category stored with the document. Any name is accepted, but search_knowledge filters only by configured categories, and update_document or re-indexing the file detects it again from the path through category_mappings

Example (cookbook):

add_document(
    content="# Kerberoasting\n\nSteal Kerberos service tickets and crack them offline...",
    filepath="security/redteam/kerberoasting.md",
    category="redteam",
)

update_document

Update an existing document. Removes old chunks from the index and re-indexes with new content. The category is detected again from the path through category_mappings (general when none matches), so a category stored by add_document or add_from_url is not kept.

Parameter Type Description
filepath string Full path to the document file
content string New content for the document

Example:

update_document(
    filepath="security/redteam/kerberoasting.md",
    content="# Kerberoasting (updated 2026-08)\n\nAdded Rubeus /aes flag detail...",
)

remove_document

Remove a document from the knowledge base index. Optionally deletes the file from disk.

Parameter Type Default Description
filepath string required Path to the document file
delete_file bool false If true, also delete the file from disk

Example:

# Drop from the index only — keep the file
remove_document(filepath="security/legacy/old-runbook.md")

# Drop from the index AND delete the file from disk
remove_document(filepath="security/legacy/old-runbook.md", delete_file=True)

add_from_url

Fetch content from a URL, strip HTML (scripts, styles, nav, footer, header), convert to markdown, and add to the knowledge base. The URL body is wrapped in a provenance fence and injection sentinels are neutralized (OWASP LLM01:2025).

Parameter Type Default Description
url string required URL to fetch content from
category string "general" Category stored with the document and the folder it is saved in. Being part of the path, it must stay inside documents_dir: a value such as ../outside returns a Filepath rejected error. Otherwise the same rules as add_document apply
title string null Custom title (auto-detected from <title> tag if not provided)

The page is saved as <category>/<title slug>.md, or as <title slug>-<first 8 hex characters of the URL's SHA-256>.md (then 16, then the full digest) when a file not saved from this URL already has the shorter name. Use the filepath in the response instead of building the path.

Example:

add_from_url(
    url="https://attack.mitre.org/techniques/T1558/003/",
    category="security",
    title="MITRE ATT&CK T1558.003 — Kerberoasting",
)

search_similar

Find documents similar to a given document using embedding similarity.

Parameter Type Default Description
filepath string required Path to the reference document
max_results int 5 Number of similar document chunks to return, bounded by search.max_results

Example:

# "Show me 5 docs semantically closest to my Kerberoasting note"
search_similar(
    filepath="security/redteam/kerberoasting.md",
    max_results=5,
)

reindex_documents — call examples

Four common patterns:

# 1. Smart incremental — detects changes via mtime/size, only reindexes what moved.
#    Runs in the background; call get_reindex_status() to poll progress.
reindex_documents()

# 2. Forced smart reindex — re-embed even unchanged files; supports checkpoints.
reindex_documents(force=True)

# 3. Full rebuild — populate and validate a staging collection before swap.
#    Requires temporary space for both the old and replacement collections.
reindex_documents(full_rebuild=True)

# 4. Resume a compatible interrupted smart reindex.
reindex_documents(resume=True)

Changing the embedding model/profile can prevent the existing collection from opening, so a subsequent MCP rebuild call is not always available. For model migrations, use a separate data directory and metadata, index the same source corpus, validate retrieval, then switch clients. See the model migration procedure.


evaluate_retrieval — full example

Measure retrieval quality against a ground-truth test set. Useful for tuning hybrid_alpha, testing query expansion effectiveness, or validating after reindexing.

Parameter Type Description
test_cases string (JSON) Array of test cases: [{"query": "...", "expected_filepath": "..."}, ...]

Complete example:

import json

test_cases = json.dumps([
    {"query": "kerberoasting", "expected_filepath": "security/redteam/kerberoasting.md"},
    {"query": "sql injection payloads", "expected_filepath": "security/webapp/sqli-cheatsheet.md"},
    {"query": "prometheus histogram buckets", "expected_filepath": "docs/observability/metrics.md"},
    {"query": "how to rotate refresh tokens", "expected_filepath": "docs/adr/0018-auth.md"},
    {"query": "chromadb sqlite variable limit", "expected_filepath": "CHANGELOG.md"},
])

evaluate_retrieval(test_cases=test_cases)

Returns mrr_at_5, recall_at_5, and a per-query breakdown showing which expected document was found and at what rank. Each case requires non-empty query and expected_filepath strings. Invalid cases return an error before executing searches. The tool does not return Precision@5.

Metrics:

  • MRR@5 (Mean Reciprocal Rank): Average of 1/rank for expected documents. 1.0 = always first result.
  • Recall@5: Fraction of expected documents found in top 5 results. 1.0 = all found.

Use representative, independently selected questions and keep the corpus, model, prefixes, search settings, and test cases fixed when comparing runs. A single expected document per query measures hit rate and rank; it does not measure all relevant documents or establish a universal quality threshold. English BGE models need a separate evaluation for non-English corpora.