Skip to main content
Glama
bintocher

Qlik Sense MCP Server

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
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions pagination and wildcard search support, which adds some context beyond basic retrieval. However, it lacks details on permissions, rate limits, error handling, or response format (e.g., structure of returned values), leaving significant gaps for a tool with 7 parameters and no output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose ('Return values of a single field from app') and appends key features ('with pagination and wildcard search'). Every word earns its place, with no redundancy or unnecessary elaboration.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (7 parameters, no output schema, and no annotations), the description is insufficient. It doesn't explain the return format, error conditions, or behavioral nuances like how wildcards interact with pagination. For a data retrieval tool with multiple search options, more context is needed to ensure proper agent usage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all 7 parameters with details like defaults and constraints. The description adds minimal value by mentioning 'pagination and wildcard search', which loosely relates to 'limit', 'offset', 'search_string', and 'search_number', but doesn't provide additional semantic context beyond what's in the schema. Baseline 3 is appropriate given high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Return values') and resource ('a single field from app'), specifying the scope of retrieval. It distinguishes itself from siblings like 'get_app_details' or 'get_apps' by focusing on field values rather than app metadata or lists. However, it doesn't explicitly contrast with 'get_app_field_statistics', which might handle aggregated data instead of raw values.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance is provided on when to use this tool versus alternatives like 'get_app_field_statistics' or 'get_app_object'. The description mentions pagination and wildcard search features, but doesn't clarify scenarios where this tool is preferred over other field-related or app-related tools in the sibling list.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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