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)
-
If
q_where
is an object, leave it. -
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.) -
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.