> ## Documentation Index
> Fetch the complete documentation index at: https://retrievalcontextprotocol.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Metadata Filtering

> Restrict retrieval with a small, safe boolean tree over document metadata — validated against the server's advertised fields.

When a server advertises the **`filter`** capability, `retrieve` (and
`index/delete`) accept a small boolean tree over document metadata. Fields and
operators are validated against what the server advertised, so a filter is both
expressive and safe.

```json theme={null}
{ "and": [
    { "field": "lang", "op": "eq", "value": "en" },
    { "or": [
        { "field": "year", "op": "gte", "value": 2023 },
        { "field": "tag",  "op": "in",  "value": ["news", "blog"] } ] } ] }
```

## Structure

A filter node is either a **combinator** (`and` / `or` / `not`, nesting
arbitrarily) or a **leaf** (`{ field, op, value }`).

<ParamField path="and / or" type="node[]">
  Boolean combinators over child nodes. An **empty** `and`/`or` array is
  `-32602`.
</ParamField>

<ParamField path="not" type="node">
  Negates a single child node.
</ParamField>

<ParamField path="field" type="string">
  A metadata field name — **MUST** be one the server advertised in
  `filter.fields`, else `-32602`.
</ParamField>

<ParamField path="op" type="string">
  A comparison operator (see below). Must be in the advertised `filter.operators`,
  else `-32005`.
</ParamField>

## Operators

| Operator                 | `value` shape                   | Applies to                                                    | Meaning                                    |
| ------------------------ | ------------------------------- | ------------------------------------------------------------- | ------------------------------------------ |
| `eq`, `ne`               | scalar (string / number / bool) | any                                                           | (in)equality                               |
| `gt`, `gte`, `lt`, `lte` | number or `date`                | `int` / `float` / `date`                                      | ordered comparison                         |
| `in`, `nin`              | array of scalars                | any                                                           | set membership / exclusion                 |
| `contains`               | scalar                          | `text` (substring) or array-valued field (element membership) | containment                                |
| `exists`                 | boolean                         | any                                                           | field present (`true`) or absent (`false`) |

## Field types

A server **SHOULD** advertise `filter.fields` as a map of field name → type:
`keyword` (exact string), `text` (tokenised), `int`, `float`, `bool`, `date`
(an RFC 3339 string or epoch-ms), or `geo`. The allowed operators and the JSON
type of `value` depend on the field type.

<Warning>
  A server **MUST** return `-32602` (with `error.data.field`) when a `value`'s JSON
  type is incompatible with the field type, or the field is not advertised. This is
  also a **security boundary**: field names are validated precisely because a
  silently-ignored filter would return wrong — possibly unauthorised — results.
  Servers **MUST** parameterise filters into the backing query engine, never
  string-concatenate. See [Security](/operating/security).
</Warning>

<Info>
  Unless a server documents otherwise, string comparison is case-sensitive and
  byte-ordered.
</Info>

## The canonical builder & validator

Every RCP SDK ships a first-class `filter` module so you **never hand-write the
JSON**. It has two halves:

* A **builder** — typed leaf constructors (`eq` `ne` `gt` `gte` `lt` `lte` `in_`
  `nin` `contains` `exists`) and combinators (`all`/`any`/`not_`, with `&` `|` `~`
  operator sugar) that make a malformed tree impossible to *construct*.
* A **validator** — `validate(node, fields, operators)` that a **server** runs on
  the untrusted incoming tree. It returns a normalized tree or a precise
  `-32602` carrying `data.field`. It enforces field/operator authorization,
  value-shape (array-vs-scalar), ordered-op-vs-type, combinator/leaf mixing, and
  a nesting-depth DoS guard.

<CodeGroup>
  ```python Python theme={null}
  from rcp import filter as f

  # client: build a tree
  tree = f.all_(f.eq("lang", "en"), f.gte("year", 2023)) | f.contains("tag", "news")
  params = {"query": "...", "filter": tree.to_json()}

  # server: validate the untrusted tree against what you advertised
  safe = f.validate(
      incoming_filter,
      fields={"lang": "keyword", "year": "int", "tag": "text"},
      operators=["eq", "gte", "contains"],
  )  # -> normalized tree, or raises RcpError(-32602, data={"field": ...})
  ```

  ```javascript Node.js theme={null}
  import { filter as f } from "rcp-protocol";

  // client
  const tree = f.any(
    f.all(f.eq("lang", "en"), f.gte("year", 2023)),
    f.contains("tag", "news"),
  );
  const params = { query: "...", filter: tree.toJSON() };

  // server
  const safe = f.validate(incoming, {
    fields: { lang: "keyword", year: "int", tag: "text" },
    operators: ["eq", "gte", "contains"],
  }); // -> normalized tree, or throws RcpError(-32602)
  ```

  ```rust Rust theme={null}
  use rcp::filter;

  // client (& | ! operator sugar via std::ops)
  let tree = (filter::eq("lang", "en".into()) & filter::gte("year", 2023.into()))
      | filter::contains("tag", "news".into());
  let params = obj(&[("query", "...".into()), ("filter", tree.to_json())]);

  // server
  let safe = filter::validate(
      Some(&incoming),
      Some(&[("lang", "keyword"), ("year", "int"), ("tag", "text")]),
      Some(&["eq", "gte", "contains"]),
      32, // max nesting depth (DoS guard)
  )?; // -> Ok(Some(normalized)) | Ok(None) if empty | Err(RcpError -32602)
  ```

  ```cpp C++ theme={null}
  #include <rcp/filter.hpp>
  using namespace rcp::filter;

  // client (&& || ! operator sugar)
  auto tree = (eq("lang", "en") && gte("year", 2023)) || contains("tag", "news");
  Json params = {{"query", "..."}, {"filter", tree.to_json()}};

  // server
  auto safe = validate(incoming,
                       /*fields*/ {{"lang", "keyword"}, {"year", "int"}, {"tag", "text"}},
                       /*operators*/ {"eq", "gte", "contains"});
  // -> Result<Json>: value is the normalized tree, error is Error{-32602, ...}
  ```
</CodeGroup>

<Tip>
  Don't roll your own filter parser. A hand-written server parser turns a malformed
  tree into a generic `-32603` crash; the canonical `validate()` turns it into a
  clean, actionable `-32602` with `data.field` — and closes the security boundary
  above for free. The reference Whoosh adapter
  ([`examples/whoosh_adapter.py`](https://github.com/1ay1/rcp/blob/main/examples/whoosh_adapter.py))
  calls `validate()` then compiles the *trusted* tree into engine queries.
</Tip>
