diff --git a/api-reference/v2/endpoint/list-documents.mdx b/api-reference/v2/endpoint/list-documents.mdx
index 5f9ef6dc..3f66ccd1 100644
--- a/api-reference/v2/endpoint/list-documents.mdx
+++ b/api-reference/v2/endpoint/list-documents.mdx
@@ -80,7 +80,7 @@ curl -X POST 'https://api.hydradb.com/context/list' \
### Filters
-- `filters` accepts the three categories below. Values must match exactly, except `source_fields.title`, which matches a case-insensitive prefix.
+- `filters` accepts the three categories below. Values must match exactly, except `source_fields.title`, which matches a normalized title prefix.
- All filter pairs combine with AND. When `ids` is non-empty, filters apply only to those IDs.
- Range, contains, and OR operators are not supported. For OR behavior, run separate calls and combine the results in your application.
- A `null` filter value returns `400`.
@@ -99,10 +99,23 @@ curl -X POST 'https://api.hydradb.com/context/list' \
| --- | --- | --- |
| | Context item's schema-aligned `metadata` payload | Exact matches on database metadata fields. See [Metadata and Filters](/essentials/v2/metadata#7-list-sources-with-metadata-filters). |
| | Context item's `additional_metadata` payload | Exact matches on free-form metadata. No schema declaration is required. |
-| | Built-in and connector fields listed below | `title` matches a case-insensitive prefix. Pair external IDs with `app_provider`. Unsupported keys return `400`. |
+| | Built-in and connector fields listed below | `title` matches a normalized prefix. Pair external IDs with `app_provider`. Unsupported keys return `400`. |
Supported source fields are `type`, `title`, `description`, `url`, `timestamp`, `app_provider`, `app_kind`, `app_external_id`, and `app_parent_id`.
+Title normalization applies Unicode NFKC, removes invisible marks and decoration,
+lowercases the title, collapses spaces, and trims it. HydraDB also removes all
+consecutive final extensions from `md`, `markdown`, `txt`, `pdf`, `doc`, `docx`,
+`rtf`, `odt`,
+`ppt`, `pptx`, `xls`, `xlsx`, `csv`, `tsv`, `json`, `html`, or `htm`, but only
+when text remains before it. Thus `Runbook.md` has the normalized prefix
+`runbook`. `report.pdf.md` matches `report`. Unknown extensions such as `.v2`
+remain part of the title.
+
+Source IDs and titles are separate inputs. Use `ids` for source IDs and
+`filters.source_fields.title` for title prefixes. HydraDB never guesses from the
+input format.
+
### Field projection
When you don't need every field on every row, pass `include_fields` to keep response payloads small. Only the listed fields are populated; omitted fields should be treated as unavailable in that response. `id`, `database`, and `collection` are always returned.
diff --git a/api-reference/v2/endpoint/query.mdx b/api-reference/v2/endpoint/query.mdx
index acd4fcec..e7bba82f 100644
--- a/api-reference/v2/endpoint/query.mdx
+++ b/api-reference/v2/endpoint/query.mdx
@@ -315,7 +315,7 @@ result = client.query(
| | Single collection scope. Send the collection the content was saved in. |
| | Multi-collection scope. Send a list of collection IDs for equal weighting, or an object mapping collection ID to a positive relative weight (at most one decimal place, e.g. `{"finance": 1.5, "legal": 0.8}`) to bias ranking. Up to 100 collections. Do not combine with `collection`. |
| | Query terms or natural-language question. Cannot be empty. Maximum 5,000 words. |
-| | Exact source titles, matched case-insensitively. Matches any listed title, then intersects with `ids` if both are sent. |
+| | Exact normalized source titles. Normalization applies Unicode NFKC, removes invisible marks and decoration, lowercases, collapses spaces, and removes consecutive final known file extensions (`md`, `markdown`, `txt`, `pdf`, `doc`, `docx`, `rtf`, `odt`, `ppt`, `pptx`, `xls`, `xlsx`, `csv`, `tsv`, `json`, `html`, or `htm`) when text remains before them. Matches any listed title, then intersects with `ids` if both are sent. Use `ids` for source IDs and `titles` for titles; HydraDB never guesses from the input format. |
| | Caller identities used to filter document ACLs. Omitting `acl`, sending `[]`, or sending `["*"]` disables caller-supplied filtering. For enforced connectors, the authenticated caller is used instead; see [ACL enforcement](/essentials/v2/connectors#enforce-acl). |
| | Omit for the result object with `llm_prompt`. `"structured"` leaves out the prompt string. `"markdown"` returns the prompt string directly as `data`. |
| | What to query. `"all"` runs knowledge and memory in parallel over the same scope and merges by `relevancy_score`. (default=`"knowledge"`) |
diff --git a/plugins/mcp.mdx b/plugins/mcp.mdx
index e77d8ae5..6fe18c93 100644
--- a/plugins/mcp.mdx
+++ b/plugins/mcp.mdx
@@ -456,7 +456,8 @@ See the [Glossary](/essentials/v2/glossary) for database and collection terminol
| `max_results` | number | No | Chunks to return, 1 to 50 (default: `10`) |
| `mode` | string | No | `fast` for a quick single pass, `thinking` for deeper recall with query expansion and graph traversal, or `auto` to pick per query (default: `thinking`) |
| `graph_context` | boolean | No | Include knowledge graph relations in the results (default: `true`) |
-| `titles` | string[] | No | Restrict the search to documents whose **complete** title exactly matches any value, ignoring case |
+| `source_ids` | string[] | No | Restrict the search to these source IDs. Intersects with `titles` when both are present. |
+| `titles` | string[] | No | Restrict the search to documents whose complete normalized title matches any value. See [Filtering by document title](#filtering-by-document-title). |
| `metadata_filters` | object | No | Exact-match metadata filters as `{key: value}` |
| `database` | string | No | Database (tenant) scope override for this request |
| `collection` | string | No | Collection (sub-tenant) scope override for this request |
@@ -474,15 +475,27 @@ those sources.
}
```
-- **Exact, complete titles:** `last-usage` does not match `last-usage.csv`.
-- **Case-insensitive:** `q3 roadmap.md` matches `Q3 Roadmap.md`.
-- **Several titles are ORed:** Surrounding whitespace is trimmed; punctuation such
- as commas is part of the title.
+- **One title rule:** HydraDB applies Unicode NFKC, removes invisible marks and
+ decoration, lowercases the title, collapses spaces, and trims it.
+- **Known file extensions are optional:** HydraDB removes consecutive final
+ extensions from
+ `md`, `markdown`, `txt`, `pdf`, `doc`, `docx`, `rtf`, `odt`, `ppt`, `pptx`,
+ `xls`, `xlsx`, `csv`, `tsv`, `json`, `html`, or `htm`. Text must remain before
+ the extension. `last-usage` therefore matches `last-usage.csv`. `notes.v2` stays
+ unchanged, `.md` stays unchanged, and `report.pdf.md` matches `report`.
+- **Exact after normalization:** `q3 roadmap.md` matches `Q3 Roadmap`, but a
+ normalized prefix alone does not match in this exact-title field.
+- **Several titles are ORed:** Punctuation such as commas remains part of the title.
- **No match returns an empty result** rather than widening to the whole corpus.
+Source IDs and titles are separate inputs. Use `source_ids` for source IDs and
+`titles` for document names. When both are present, HydraDB intersects the two
+scopes. HydraDB never guesses from the input format.
+
-Because matching ignores case, two documents whose names differ only by case
-(`Report.md` and `report.md`) are treated as the same title and both are returned.
+Two documents with the same normalized title are treated as the same title and
+both are returned. For example, `Report.md`, `report.pdf`, and `report` share one
+normalized title.
### hydradb_ingest