Skip to content

Asking in plain English

People ask an AI assistant for documents the way they would ask a colleague: "show me all our real estate notes from 2025", "every CORD note by MH in the last six months". The assistant (Claude, Microsoft Copilot, …) usually passes that whole sentence to K-Lake as the search query. A keyword search then looks for the words "last", "six" and "months" in the documents, rather than restricting the search to the last six months.

With plain-English reading on, the MCP search_content tool reads the request first, turns the parts it understands into search filters, and searches for the rest. It works by fixed rules, not a language model, so the same request always reads the same way and takes microseconds. Every result says exactly what was read, so the assistant, and the person asking, can check it and override it.

What it understands

The request says It becomes
a relative period: "last 6 months", "past year", "last quarter", "this year", "YTD", "since March" a document-date window, counted back from today
a named period: "2025", "Q3 2024", "H1 2026", "April 2026", "between March and May 2025", "before 2025" the same window, when the request is about documents (it mentions notes, research, reports, published, written, or says "all"/"every")
"all", "every", "everything", a leading "list", "what have we published on…" find-all: one result per document, paged to the end
"PDFs", "pdf reports", "xlsx files" a file-type filter
a company name or ticker ("Cordwell Digital Infrastructure", "CORD") a ticker filter, using the tenant's ticker aliases
a sector, analyst or note type that your documents carry ("real estate", "MH", "initiations", "NAV updates") a sector, analyst or note_type filter

Dates use each document's own date (from its folder or first page), not the date the file was copied. The fields come from the source's metadata rules. The words that name a company, sector, analyst or subject stay in the query, so ranking still uses them. Only the words of a date phrase, "all"/"every" and a leading "show me" or "can you find" are removed.

Company, sector, analyst and note-type filters are only applied to a request for a list of documents ("all real estate initiations", "CORD notes from Q3"). A question about a document ("what did the CORD note say about its results?") gets no field filter.

It needs your metadata rules and ticker aliases

Dates, find-all and file types work on any estate. The ticker, sector, analyst and note-type readings only work once the sources have metadata rules that give documents those fields, and the tenant has ticker aliases that link company names to tickers. A value is only read if it exists on a document the person asking is allowed to see.

Example prompts for broker research

These work well in Claude or Copilot:

  • "Show me all our real estate notes from 2025.": sector REAL ESTATE, 2025, every document.
  • "Every CORD note by MH in the last six months.": ticker CORD, analyst MH, the last six months, every document.
  • "List the investment companies initiations published since March.": sector and note type, from 1 March.
  • "What have we published on Cordwell Digital Infrastructure this year?": the company's ticker, this year, every document.
  • "All NAV updates on Kingsmead Property Income in Q1 2026.": ticker and note type, January to March 2026.
  • "All financials research from H1 2025 as PDFs.": sector, the first half of 2025, PDF only.

Name sectors, note types and analysts the way your documents do (as they appear in the fields of a search result). The parser does not know that "property" means REAL ESTATE or that "banks and brokers" means FINANCIALS. Those words still search as keywords, but they do not become a filter.

Reading interpreted_as

Every result of an interpreted search carries interpreted_as:

{
  "original_query": "show me all our real estate sector notes from 2025",
  "query": "our real estate sector notes",
  "today": "2026-09-30",
  "applied": {
    "published_after": "2025-01-01",
    "published_before": "2025-12-31",
    "fields": { "sector": ["REAL ESTATE"] },
    "group_by_file": true
  },
  "readings": [
    { "text": "from 2025", "as": "date", "applied": true,
      "sets": { "published_after": "2025-01-01", "published_before": "2025-12-31" } },
    { "text": "real estate", "as": "field", "applied": true,
      "sets": { "fields": { "sector": ["REAL ESTATE"] } } },
    { "text": "sector notes", "as": "field", "applied": false,
      "sets": { "fields": { "note_type": ["sector note"] } },
      "reason": "not applied (ambiguous): after a sector, \"sector notes\" may mean every note on that sector, not only the \"sector note\" type, so only the sector was applied. For the type alone, pass `fields: {\"note_type\": [\"sector note\"]}` or ask for the \"sector-note type\"" },
    { "text": "all", "as": "find_all", "applied": true, "sets": { "group_by_file": true } },
    { "text": "show me", "as": "framing", "applied": true }
  ]
}
  • query is what was actually searched; original_query is what the assistant sent.
  • applied is every filter that was used.
  • readings lists each phrase that was read, what it would set, and whether it was applied. When a reading was not applied, reason says why, and how to ask for it explicitly.
  • today is the date that relative phrases ("last 6 months") counted back from.

A good assistant tells the user what it searched for. If a result looks thin, check interpreted_as first: a filter that was not what the user meant is the usual cause.

Overriding it

  • Explicit filters always win. If the assistant passes published_after, published_before, content_type, group_by_file or fields itself, that value is used, and the reading it replaced is listed as not applied.
  • interpret: false searches the query exactly as written, for one call.
  • interpret: true turns reading on for one call on an estate where it is off by default.
  • interpret_today: "YYYY-MM-DD" sets the date relative phrases count back from, when the user's "today" is not the server's.

When reading is on for the estate, the search_content tool description tells the assistant so, and tells it how to use interpreted_as and how to opt out.

Known limits

  • Ambiguous phrases are reported, not applied. "Real estate sector notes" could mean every real-estate note or only notes of the sector note type, so only the sector is applied. To get only the type, say "real estate sector-notes", "sector note type", or pass fields: {"note_type": ["sector note"]}. Likewise "spreadsheets" (xlsx, xls or csv?), two date phrases that do not make one window, and a name that may continue ("Dunmore Logistics" in "Dunmore Logistics Income") are reported, not applied.
  • No filters on financial figures. "NAV discount above 10%" or "yield over 5%" is searched as words. Numbers in the text are not filters.
  • Dates count from today. "Last six months" means the six months before the day the search runs (UTC). Pass interpret_today for a different reference date.
  • A year in a question about content stays in the query. In "AMD operating income in 2022", 2022 is part of the subject, not a publication date.
  • Quoted phrases, -exclusions and file names are never read. A query that names a file searches for that file as written.

Turning it on for an estate

Reading is off by default. An administrator turns it on for every MCP call on the deployment. A call can still turn it off with interpret: false.

Proof of concept (the kit's poc-up.sh), at install or upgrade:

./scripts/poc-up.sh --version <release> --interpret-queries

Later upgrades keep it without the flag being repeated. The script prints keeping plain-English search reading from the running install. To turn it off: ./scripts/poc-up.sh --no-interpret-queries.

Helm:

helm upgrade kdbl oci://ghcr.io/kdbl-consulting/kdbl --version <release> \
  --namespace kdbl --reuse-values --set search.interpretQueries=true

Or set it in your values file:

search:
  interpretQueries: true

The API rolls to pick it up. Search is unaffected during the roll. Nothing is re-indexed.