Skip to main content
Glama
bintocher

Qlik Sense MCP Server

by bintocher

get_app_field

Retrieve field values from Qlik Sense applications with pagination and wildcard search capabilities for efficient data exploration.

Instructions

Return values of a single field from app with pagination and wildcard search (supports * and %).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
app_idYesApplication GUID
field_nameYesField name
limitNoMax values to return (default: 10, max: 100)
offsetNoOffset for pagination (default: 0)
search_stringNoWildcard text search mask (* and % supported), case-insensitive by default
search_numberNoWildcard numeric search mask (* and % supported)
case_sensitiveNoCase sensitive matching for search_string

Implementation Reference

  • Tool registration in list_tools() handler, defining the tool name, description, and input schema for get_app_field.
    Tool(
        name="get_app_field",
        description="Return values of a single field from app with pagination and wildcard search (supports * and %).",
        inputSchema={
            "type": "object",
            "properties": {
                "app_id": {"type": "string", "description": "Application GUID"},
                "field_name": {"type": "string", "description": "Field name"},
                "limit": {"type": "integer", "description": "Max values to return (default: 10, max: 100)", "default": 10},
                "offset": {"type": "integer", "description": "Offset for pagination (default: 0)", "default": 0},
                "search_string": {"type": "string", "description": "Wildcard text search mask (* and % supported), case-insensitive by default"},
                "search_number": {"type": "string", "description": "Wildcard numeric search mask (* and % supported)"},
                "case_sensitive": {"type": "boolean", "description": "Case sensitive matching for search_string", "default": False}
            },
            "required": ["app_id", "field_name"],
        }
    ),
  • Main handler logic in handle_call_tool() for executing get_app_field tool. Parses arguments, connects to engine, fetches field values via engine_api.get_field_values, applies filters/pagination, and returns JSON response.
    elif name == "get_app_field":
        app_id = arguments["app_id"]
        field_name = arguments["field_name"]
        limit = arguments.get("limit", 10)
        offset = arguments.get("offset", 0)
        search_string = arguments.get("search_string")
        search_number = arguments.get("search_number")
        case_sensitive = arguments.get("case_sensitive", False)
    
        if limit is None or limit < 1:
            limit = 10
        if limit > 100:
            limit = 100
        if offset is None or offset < 0:
            offset = 0
    
        def _wildcard_to_regex(pattern: str, case_sensitive_flag: bool) -> Any:
            import re
            escaped = re.escape(pattern).replace("\\*", ".*").replace("%", ".*")
            regex = f"^{escaped}$"
            return re.compile(regex, 0 if case_sensitive_flag else re.IGNORECASE)
    
        def _get_values():
            try:
                self.engine_api.connect()
                app_result = self.engine_api.open_doc_safe(app_id, no_data=False)
                app_handle = app_result.get("qReturn", {}).get("qHandle", -1)
                if app_handle == -1:
                    return {"error": "Failed to open app"}
    
                fetch_size = max(limit + offset, 500)
                if fetch_size > 5000:
                    fetch_size = 5000
                field_data = self.engine_api.get_field_values(app_handle, field_name, fetch_size, include_frequency=False)
                values = [v.get("value", "") for v in field_data.get("values", [])]
    
                if search_string:
                    rx = _wildcard_to_regex(search_string, case_sensitive)
                    values = [val for val in values if isinstance(val, str) and rx.match(val)]
    
                if search_number:
                    rxn = _wildcard_to_regex(search_number, case_sensitive)
                    filtered = []
                    for idx, vobj in enumerate(field_data.get("values", [])):
                        cell_text = vobj.get("value", "")
                        qnum = vobj.get("numeric_value", None)
                        if qnum is not None:
                            if rxn.match(str(qnum)) or rxn.match(str(cell_text)):
                                filtered.append(cell_text)
                    values = filtered
    
                sliced = values[offset:offset + limit]
                return {"field_values": sliced}
            except Exception as e:
                return {"error": str(e)}
            finally:
                self.engine_api.disconnect()
    
        result = await asyncio.to_thread(_get_values)
        return [TextContent(type="text", text=json.dumps(result, indent=2, ensure_ascii=False))]
  • Core helper method in QlikEngineAPI class that creates a ListObject session object to fetch field values with optional frequency, processes the data, and returns structured field information used by the get_app_field handler.
    def get_field_values(
        self,
        app_handle: int,
        field_name: str,
        max_values: int = 100,
        include_frequency: bool = True,
    ) -> Dict[str, Any]:
        """Get field values with frequency information using ListObject."""
        try:
            # Use correct structure
            list_def = {
                "qInfo": {"qId": f"field-values-{field_name}", "qType": "ListObject"},
                "qListObjectDef": {
                    "qStateName": "$",
                    "qLibraryId": "",
                    "qDef": {
                        "qFieldDefs": [field_name],
                        "qFieldLabels": [],
                        "qSortCriterias": [
                            {
                                "qSortByState": 0,
                                "qSortByFrequency": 1 if include_frequency else 0,
                                "qSortByNumeric": 1,
                                "qSortByAscii": 1,
                                "qSortByLoadOrder": 0,
                                "qSortByExpression": 0,
                                "qExpression": {"qv": ""},
                            }
                        ],
                    },
                    "qInitialDataFetch": [
                        {"qTop": 0, "qLeft": 0, "qHeight": max_values, "qWidth": 1}
                    ],
                },
            }
    
            # Create session object - use correct parameter format
            result = self.send_request(
                "CreateSessionObject", [list_def], handle=app_handle
            )
    
            if "qReturn" not in result or "qHandle" not in result["qReturn"]:
                return {"error": "Failed to create session object", "response": result}
    
            list_handle = result["qReturn"]["qHandle"]
    
            layout = self.send_request("GetLayout", [], handle=list_handle)
    
            # Correct path to qListObject - it's in qLayout
            if "qLayout" not in layout or "qListObject" not in layout["qLayout"]:
                # Clean up object before returning error
                try:
                    self.send_request(
                        "DestroySessionObject",
                        [f"field-values-{field_name}"],
                        handle=app_handle,
                    )
                except:
                    pass
                return {"error": "No list object in layout", "layout": layout}
    
            list_object = layout["qLayout"]["qListObject"]
            values_data = []
    
            # Process data
            for page in list_object.get("qDataPages", []):
                for row in page.get("qMatrix", []):
                    if row and len(row) > 0:
                        cell = row[0]
                        value_info = {
                            "value": cell.get("qText", ""),
                            "state": cell.get(
                                "qState", "O"
                            ),  # O=Optional, S=Selected, A=Alternative, X=Excluded
                            "numeric_value": cell.get("qNum", None),
                            "is_numeric": cell.get("qIsNumeric", False),
                        }
    
                        # Add frequency if available
                        if "qFrequency" in cell:
                            value_info["frequency"] = cell.get("qFrequency", 0)
    
                        values_data.append(value_info)
    
            # Get general field information
            field_info = {
                "field_name": field_name,
                "values": values_data,
                "total_values": list_object.get("qSize", {}).get("qcy", 0),
                "returned_count": len(values_data),
                "dimension_info": list_object.get("qDimensionInfo", {}),
                "debug_info": {
                    "list_handle": list_handle,
                    "data_pages_count": len(list_object.get("qDataPages", [])),
                    "raw_size": list_object.get("qSize", {}),
                },
            }
    
            try:
                self.send_request(
                    "DestroySessionObject",
                    [f"field-values-{field_name}"],
                    handle=app_handle,
                )
            except Exception as cleanup_error:
                field_info["cleanup_warning"] = str(cleanup_error)
    
            return field_info

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bintocher/qlik-sense-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server