query_database
Execute custom queries on Fibery databases using the low-level API to retrieve, filter, and sort entity data with flexible field selection and parameterized conditions.
Instructions
Run any Fibery API command. This gives tremendous flexibility, but requires a bit of experience with the low-level Fibery API. In case query succeeded, return value contains a list of records with fields you specified in select. If request failed, will return detailed error message. Examples (note, that these databases are non-existent, use databases only from user's schema!): Query: What newly created Features do we have for the past 2 months? Tool use: { "q_from": "Dev/Feature", "q_select": { "Name": ["Dev/Name"], "Public Id": ["fibery/public-id"], "Creation Date": ["fibery/creation-date"] }, "q_where": [">", ["fibery/creation-date"], "$twoMonthsAgo"], "q_order_by": {"fibery/creation-date": "q/desc"}, "q_limit": 100, "q_offset": 0, "q_params": { $twoMonthsAgo: "2025-01-16T00:00:00.000Z" } }
Query: What Admin Tasks for the past week are Approval or Done? Tool use: { "q_from": "Administrative/Admin Task", "q_select": { "Name": ["Administrative/Name"], "Public Id": ["fibery/public-id"], "Creation Date": ["fibery/creation-date"], "State": ["workflow/state", "enum/name"] }, "q_where": [ "q/and", # satisfy time AND states condition [">", ["fibery/creation-date"], "$oneWeekAgo"], [ "q/or", # nested or, since entity can be in either of these states ["=", ["workflow/state", "enum/name"], "$state1"], ["=", ["workflow/state", "enum/name"], "$state2"] ] ], "q_order_by": {"fibery/creation-date": "q/desc"}, "q_limit": 100, "q_offset": 0, "q_params": { # notice that parameters used in "where" are always passed in params! $oneWeekAgo: "2025-03-07T00:00:00.000Z", $state1: "Approval", $state2: "Done" } }
Query: What Admin Tasks for the past week are Approval or Done? Tool use: { "q_from": "Administrative/Admin Task", "q_select": { "State": ["workflow/state", "enum/name"], "Public Id": ["fibery/public-id"], "Creation Date": ["fibery/creation-date"], "Modification Date": ["fibery/modification-date"], "Deadline": ["Administrative/Deadline"], "Group": ["Administrative/Group", "Administrative/name"], "Name": ["Administrative/Name"], "Priority": ["Administrative/Priority_Administrative/Admin Task", "enum/name"] }, "q_where": ["!=", ["workflow/state", "workflow/Final"], "$stateType"], # Administrative/Admin Task is not "Finished" yet "q_order_by": {"fibery/creation-date": "q/desc"}, "q_limit": 100, "q_offset": 0, "q_params: { "$stateType": true } }
Query: Summarize acc contacts with public id 1. Tool use: { "q_from": "Accounting/Acc Contacts", "q_select": { "Name": ["Accounting/Name"], "Public Id": ["fibery/public-id"], "Creation Date": ["fibery/creation-date"], "Description": ["Accounting/Description"] }, "q_where": ["=", ["fibery/public-id"], "$publicId"], "q_limit": 1, "q_params": { $publicId: "1", } }
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| q_from | Yes | Specifies the entity type in "Space/Type" format (e.g., "Product Management/feature", "Product Management/Insight") | |
| q_select | Yes | Defines what fields to retrieve. Can include: - Primitive fields using format {"AliasName": "FieldName"} (i.e. {"Name": "Product Management/Name"}) - Related entity fields using format {"AliasName": ["Related entity", "related entity field"]} (i.e. {"Secret": ["Product Management/Description", "Collaboration~Documents/secret"]}). Careful, does not work with 1-* connection! To work with 1-* relationships, you can use sub-querying: {"AliasName": {"q/from": "Related type", "q/select": {"AliasName 2": "fibery/id"}, "q/limit": 50}} AliasName can be of any arbitrary value. | |
| q_where | No | Filter conditions in format [operator, [field_path], value] or ["q/and"|"q/or", ...conditions]. Common usages: - Simple comparison: ["=", ["field", "path"], "$param"]. You cannot pass value of $param directly in where clause. Use params object instead. Pay really close attention to it as it is not common practice, but that's how it works in our case! - Logical combinations: ["q/and", ["<", ["field1"], "$param1"], ["=", ["field2"], "$param2"]] - Available operators: =, !=, <, <=, >, >=, q/contains, q/not-contains, q/in, q/not-in | |
| q_order_by | No | List of sorting criteria in format {"field1": "q/asc", "field2": "q/desc"} | |
| q_limit | No | Number of results per page (defaults to 50). Maximum allowed value is 1000 | |
| q_offset | No | Number of results to skip. Mainly used in combination with limit and orderBy for pagination. | |
| q_params | No | Dictionary of parameter values referenced in where using "$param" syntax. For example, {$fromDate: "2025-01-01"} |
Implementation Reference
- The main handler function for the 'query_database' tool. It constructs a query for the Fibery database, executes it via fibery_client.query, handles rich text fields by fetching document content, and returns the result as text.async def handle_query(fibery_client: FiberyClient, arguments: Dict[str, Any]) -> List[mcp.types.TextContent]: q_from, q_select = arguments["q_from"], arguments["q_select"] schema: Schema = await fibery_client.get_schema() database = schema.databases_by_name()[arguments["q_from"]] rich_text_fields, safe_q_select = get_rich_text_fields(q_select, database) base = { "q/from": q_from, "q/select": safe_q_select, "q/limit": arguments.get("q_limit", 50), } optional = { k: v for k, v in { "q/where": arguments.get("q_where", None), "q/order-by": parse_q_order_by(arguments.get("q_order_by", None)), "q/offset": arguments.get("q_offset", None), }.items() if v is not None } query = base | optional commandResult = await fibery_client.query(query, arguments.get("q_params", None)) if not commandResult.success: return [mcp.types.TextContent(type="text", text=str(commandResult))] for i, entity in enumerate(commandResult.result): for field in rich_text_fields: secret = entity.get(field["alias"], None) if not secret: return [ mcp.types.TextContent( type="text", text=f"Unable to get document content for entity {entity}. Field: {field}" ) ] entity[field["alias"]] = await fibery_client.get_document_content(secret) return [mcp.types.TextContent(type="text", text=str(commandResult))]
- Defines the MCP Tool object for 'query_database', including the input schema with properties for query parameters like q_from, q_select, q_where, etc., and loads description from file.def query_tool() -> mcp.types.Tool: with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "descriptions", "query"), "r") as file: description = file.read() return mcp.types.Tool( name=query_tool_name, description=description, inputSchema={ "type": "object", "properties": { "q_from": { "type": "string", "description": 'Specifies the entity type in "Space/Type" format (e.g., "Product Management/feature", "Product Management/Insight")', }, "q_select": { "type": "object", "description": "\n".join( [ "Defines what fields to retrieve. Can include:", ' - Primitive fields using format {"AliasName": "FieldName"} (i.e. {"Name": "Product Management/Name"})', ' - Related entity fields using format {"AliasName": ["Related entity", "related entity field"]} (i.e. {"Secret": ["Product Management/Description", "Collaboration~Documents/secret"]}). Careful, does not work with 1-* connection!', 'To work with 1-* relationships, you can use sub-querying: {"AliasName": {"q/from": "Related type", "q/select": {"AliasName 2": "fibery/id"}, "q/limit": 50}}', "AliasName can be of any arbitrary value.", ] ), }, "q_where": { "type": "object", "description": "\n".join( [ 'Filter conditions in format [operator, [field_path], value] or ["q/and"|"q/or", ...conditions]. Common usages:', '- Simple comparison: ["=", ["field", "path"], "$param"]. You cannot pass value of $param directly in where clause. Use params object instead. Pay really close attention to it as it is not common practice, but that\'s how it works in our case!', '- Logical combinations: ["q/and", ["<", ["field1"], "$param1"], ["=", ["field2"], "$param2"]]', "- Available operators: =, !=, <, <=, >, >=, q/contains, q/not-contains, q/in, q/not-in", ] ), }, "q_order_by": { "type": "object", "description": 'List of sorting criteria in format {"field1": "q/asc", "field2": "q/desc"}', }, "q_limit": { "type": "integer", "description": "Number of results per page (defaults to 50). Maximum allowed value is 1000", }, "q_offset": { "type": "integer", "description": "Number of results to skip. Mainly used in combination with limit and orderBy for pagination.", }, "q_params": { "type": "object", "description": 'Dictionary of parameter values referenced in where using "$param" syntax. For example, {$fromDate: "2025-01-01"}', }, }, "required": ["q_from", "q_select"], }, )
- src/fibery_mcp_server/tools/__init__.py:15-17 (registration)Registers the query_tool() in the list of available tools returned by handle_list_tools().def handle_list_tools(): return [current_date_tool(), schema_tool(), database_tool(), query_tool(), create_entity_tool(), create_entities_batch_tool(), update_entity_tool()]
- src/fibery_mcp_server/tools/__init__.py:24-25 (registration)Dispatches tool calls to handle_query when name is 'query_database' in handle_tool_call.elif name == query_tool_name: return await handle_query(fibery_client, arguments)
- Helper function to identify rich text fields in the select clause and modify the query to fetch secrets, used in the handler.def get_rich_text_fields(q_select: Dict[str, Any], database: Database) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]: rich_text_fields = [] safe_q_select = deepcopy(q_select) for field_alias, field_name in safe_q_select.items(): if not isinstance(field_name, str): if isinstance(field_name, list): field_name = field_name[0] if database.fields_by_name().get(field_name, None).is_rich_text(): rich_text_fields.append({"alias": field_alias, "name": field_name}) safe_q_select[field_alias] = [field_name, "Collaboration~Documents/secret"] return rich_text_fields, safe_q_select