Fibery MCP Server: q_where Clause Format Mismatch Causes API Error

While working with the Fibery MCP server, I encountered a recurring issue where queries fail with a validation error if the q_where parameter is provided in array form instead of the expected object form.

Currently, this seems to require manual correction in client-side code or tooling prompts. To make integrations more resilient and reduce friction for developers, it would be helpful if the MCP server automatically normalized array-style q_where clauses to the object format before validation.

Below are the technical details and a reproducible example of the issue, along with a suggested fix.

Summary
Some clients generate q_where as an array (e.g., ["=", ["fibery/public-id"], "$id"]). The server validator requires an object (e.g., { "=": [["fibery/public-id"], "$id"] }) and returns:
Input validation error: [...] is not of type 'object'.

Expected behavior
Accept array-form q_where or normalize it to the object form server-side before validation/execution.

Actual behavior
Requests fail when q_where is an array at the top level, despite being semantically correct.

Minimal reproduction (generic)

{
  "q_from": "Space/Entity",
  "q_select": { "Name": ["Space/Name"] },
  "q_where": ["=", ["fibery/public-id"], "$publicId"],
  "q_limit": 1,
  "q_params": { "$publicId": "1" }
}

Proposed fix (server-side normalization)
Normalize q_where if it arrives in array form; keep object form untouched; reject other types clearly.

TypeScript/Node (if the server or proxy is JS-based)

type JSONVal = any;

function normalizeWhere(node: JSONVal): JSONVal {
  if (node && typeof node === "object" && !Array.isArray(node)) return node; // already object-form
  if (Array.isArray(node)) {
    if (node.length === 0) throw new Error("Invalid q_where: empty array");
    const op = node[0];
    if (op === "q/and" || op === "q/or") {
      return { [op]: node.slice(1).map(normalizeWhere) }; // recursively normalize children
    }
    return { [op]: node.slice(1) }; // e.g., "=","!=","<",">=", "q/in", etc.
  }
  throw new Error("Invalid q_where: must be array or object");
}

export function preprocessFiberyQueryDatabase(params: Record<string, JSONVal>) {
  if (params && "q_where" in params) params.q_where = normalizeWhere(params.q_where);
  return params;
}

Python (if the server is Python-based)

from typing import Any, Dict, List, Union

JSON = Union[Dict[str, Any], List[Any], str, int, float, bool, None]

def normalize_where(node: JSON) -> JSON:
    if isinstance(node, dict):
        return node  # already object-form
    if isinstance(node, list):
        if not node:
            raise ValueError("Invalid q_where: empty array")
        op, *rest = node
        if op in ("q/and", "q/or"):
            return {op: [normalize_where(x) for x in rest]}
        return {op: rest}
    raise ValueError("Invalid q_where: must be array or object")

def preprocess_params(params: Dict[str, Any]) -> Dict[str, Any]:
    if "q_where" in params:
        params["q_where"] = normalize_where(params["q_where"])
    return params

Normalization rules (concise)

  1. If q_where is an object, leave it.

  2. If q_where is an array:
    ["q/and", cond1, cond2, ...]{ "q/and": [normalize(cond1), normalize(cond2), ...] }
    ["=", fieldPath, value]{ "=": [fieldPath, value] } (works for !=, <, <=, >, >=, q/in, q/not-in, etc.)

  3. If q_where is empty array, or neither array nor object, reject with a clear message.

Test cases

  • Single condition: ["=", ["fibery/public-id"], "$id"]{ "=": [["fibery/public-id"], "$id"] }

  • Nested AND: ["q/and", ["=", ["A/Name"], "$n"], [">", ["fibery/creation-date"], "$after"]]{ "q/and": [{ "=": [["A/Name"], "$n"] }, { ">": [["fibery/creation-date"], "$after"] }] }

  • Already object: unchanged.

  • Invalid types: explicit error.

Impact
This server-side normalization makes all clients resilient (AIs, scripts, integrations) without requiring per-client prompt discipline or brittle runtime workarounds.