Metadata-Version: 2.4
Name: qlik-sense-mcp-server
Version: 1.7.1
Summary: MCP Server for Qlik Sense Enterprise APIs
Project-URL: Homepage, https://github.com/bintocher/qlik-sense-mcp
Project-URL: Repository, https://github.com/bintocher/qlik-sense-mcp
Project-URL: Issues, https://github.com/bintocher/qlik-sense-mcp/issues
Author-email: Stanislav Chernov <bintocher@yandex.com>
License: MIT
License-File: LICENSE
Keywords: analytics,mcp,model-context-protocol,qlik,qlik-sense
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Requires-Dist: cryptography>=41.0.0
Requires-Dist: httpx>=0.25.0
Requires-Dist: mcp<3.0.0,>=1.8.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pyjwt[crypto]>=2.8.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: websocket-client>=1.6.0
Provides-Extra: dev
Requires-Dist: build>=0.10.0; extra == 'dev'
Requires-Dist: bump2version>=1.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: twine>=4.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# Qlik Sense MCP Server

[![PyPI version](https://badge.fury.io/py/qlik-sense-mcp-server.svg)](https://pypi.org/project/qlik-sense-mcp-server/)
[![PyPI downloads](https://img.shields.io/pypi/dm/qlik-sense-mcp-server)](https://pypi.org/project/qlik-sense-mcp-server/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python versions](https://img.shields.io/pypi/pyversions/qlik-sense-mcp-server)](https://pypi.org/project/qlik-sense-mcp-server/)

[Model Context Protocol](https://modelcontextprotocol.io/) server for
Qlik Sense Enterprise. Exposes Qlik's Repository (HTTP) and Engine
(WebSocket) APIs as **24 MCP tools** so an LLM client can discover apps,
inspect data models, build hypercubes, and manage reload tasks through
a single uniform interface. In JWT mode the 12 reload-task tools are
hidden, since QRS task administration needs certificate auth.

## What's in the box

| Area | Tools | Used for |
|------|-------|----------|
| Repository (apps & metadata) | `get_about`, `get_apps`, `get_app_details` | Discover apps, list tables and fields with cardinalities |
| Engine (data & script)       | `get_app_script`, `get_app_variables`, `get_app_sheets`, `get_app_sheet_objects`, `get_app_object`, `get_app_field`, `engine_get_field_range`, `get_app_field_statistics`, `engine_create_hypercube` | Read load script, list visualizations, query field values, build hypercubes |
| Reload tasks *(certificate mode only)* | `get_tasks`, `get_task_details`, `get_task_dependencies`, `get_task_schedule`, `get_task_executions`, `get_task_script_log`, `get_failed_tasks_with_logs`, `start_task`, `create_task`, `update_task`, `delete_task`, `create_task_schedule` | Inspect, trigger and manage reload tasks |

Full list with descriptions: [`docs/tools.md`](docs/tools.md).

The main analysis call maps onto SQL one-to-one — `dimensions` is the
`GROUP BY`, `measures` are the aggregates, `sort_by` / `sort_order` are
the `ORDER BY`, `limit` is the `LIMIT`:

```jsonc
// engine_create_hypercube — "the 10 clients with the highest GGR"
{
  "app_id": "<app guid>",
  "dimensions": [{"field": "clientid"}],
  "measures":   [{"expression": "Sum(ggr)", "label": "GGR"}],
  "sort_by": "GGR",
  "sort_order": "desc",
  "limit": 10
}
```

## Quick start

```bash
uvx qlik-sense-mcp-server
```

The server starts in [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports)
mode on `http://127.0.0.1:8000/mcp`. Configure it via environment
variables — see [`docs/configuration.md`](docs/configuration.md).

For stdio mode (legacy MCP transport), pass `--stdio`.

Two authentication modes are supported: client certificate (legacy,
full QRS access) and JWT via virtual proxy (per-analyst, no on-disk
secrets). See [`docs/AUTH_JWT.md`](docs/AUTH_JWT.md) for the JWT setup.

## Documentation

| Document | What's inside |
|----------|---------------|
| [`docs/installation.md`](docs/installation.md) | Requirements, install via `uvx` / `pip` / source, certificate setup |
| [`docs/configuration.md`](docs/configuration.md) | All `QLIK_*` environment variables, sample `.env`, MCP client config snippet |
| [`docs/AUTH_JWT.md`](docs/AUTH_JWT.md) | JWT authentication via virtual proxy: key generation, virtual proxy setup, `QLIK_JWT_TOKEN` usage |
| [`docs/usage.md`](docs/usage.md) | Transports, server start commands, recommended call order, hard limits enforced by this server |
| [`docs/tools.md`](docs/tools.md) | Inventory of all 24 tools, response/error envelope, error categories |
| [`docs/architecture.md`](docs/architecture.md) | Project layout, components, connection caching, strict id-matching, two-tier timeout |
| [`docs/development.md`](docs/development.md) | `make` targets, tests, versioning, how to add a new tool |
| [`docs/troubleshooting.md`](docs/troubleshooting.md) | Common errors, hypercube planning failures, verbose logging, configuration self-test |
| [`CHANGELOG.md`](CHANGELOG.md) | Release notes |

## Key facts about the v1.7.0 line

- **Runs on both MCP SDK lines.** SDK 2.0 dropped `FastMCP`; the server
  now picks `MCPServer` (2.x) or `FastMCP` (1.x) at import time, so
  `mcp>=1.1.0,<3.0.0` all work. Both lines are covered by the test
  suite and were verified end to end against a live Qlik app.

- **Ranked queries (top-N) in one call.** `engine_create_hypercube`
  takes `sort_by` (a measure label, a measure expression or a dimension
  field), `sort_order` (`desc` / `asc`) and `limit`, so "the 10 clients
  with the highest GGR" is a single request. Before v1.6.0 sorting by a
  measure silently did nothing — `qInterColumnSortOrder` was hard-coded
  to the dimensions, so the server returned the alphabetically first
  rows instead of the largest ones.
- **NULL groups stay out of rankings.** Facts with no value for the
  grouping field collapse into Qlik's `"-"` row, which often holds a
  large total and would otherwise take first place in a top-N. It is
  dropped by default; pass `exclude_null_dimensions=false` to measure
  how much data is unattributed.
- **Compact, LLM-friendly results.** The hypercube response is
  `columns` + `rows` with real numbers, plus `grand_total` and
  per-step `timings`. Pass `include_raw_layout=true` for the full Qlik
  layout.
- **Failures name the query that failed.** Every error reply, timeouts
  included, echoes `tool` and `request` with the exact arguments sent.
- **Fewer useless tools in JWT mode.** Reload-task administration needs
  QRS admin rights, so those 12 tools are registered only in
  certificate mode: 24 tools with a certificate, 12 with a JWT.
- **One Qlik session per server.** Qlik allows max 5 concurrent
  sessions per user and can lock the account beyond that, so all tool
  calls share a single cached Engine session — never fan them out in
  parallel.
- **JWT authentication via virtual proxy.** Set `QLIK_JWT_TOKEN`
  instead of certificate paths and the server will authenticate every
  Repository and Engine call as the analyst encoded in the token. No
  certificates or private keys live on the host. The legacy
  certificate mode is unchanged and still required for full QRS access.
  Setup guide: [`docs/AUTH_JWT.md`](docs/AUTH_JWT.md).
- **Cached Engine WebSocket connections.** Once an app is opened, every
  subsequent tool call against the same `app_id` reuses the same
  WebSocket and the same open document. Switching `app_id` closes the
  old document and opens the new one on the same socket. Dropped
  connections are reopened transparently. Implementation:
  [`engine_api.py`](qlik_sense_mcp_server/engine_api.py) and
  [`docs/architecture.md`](docs/architecture.md).
- **Streamable HTTP transport by default.** The server is a long-lived
  process; multiple MCP clients can talk to it in parallel. The legacy
  stdio mode still works behind `--stdio`.
- **`tool_call_seconds`** is injected as the first key of every tool
  response — wall-clock time of the call in milliseconds. Use it to
  spot slow tools.
- **Hard hypercube limits.** `engine_create_hypercube` rejects requests
  with `max_rows > 5000` or `columns * max_rows > 9900` immediately,
  with a structured error and a hint pointing at set-analysis or
  top-N patterns. Qlik Engine itself returns
  [error 7009 `calc-pages-too-large`](https://help.qlik.com/en-US/sense-developer/November2025/Subsystems/EngineJSONAPI/Content/service-genericobject-gethypercubedata.htm)
  for any single page over 10000 cells.
- **Single timeout knob.** `QLIK_WS_TIMEOUT` (default `180.0` seconds)
  controls both the WebSocket handshake and every Engine API call.

## Requirements

- Python 3.12 (the package is built and tested against this version; see [`pyproject.toml`](pyproject.toml))
- Qlik Sense Enterprise (Repository on port 4242, Engine on port 4747 — the
  [standard ports](https://help.qlik.com/en-US/sense-admin/Subsystems/DeployAdministerQSE/Content/Sense_DeployAdminister/QSEoW/Deploy_QSEoW/Ports.htm))
- Client certificate, private key and root CA from the Qlik Sense node
- Network access from the host running this server to Qlik

## Disclaimer

This project is an independent, community-built integration. It is
**NOT affiliated with, endorsed by, sponsored by, or supported by Qlik
Technologies Inc., QlikTech International AB, or any other Qlik
entity**. "Qlik", "Qlik Sense", "QlikView" and all related product
names are trademarks of their respective owners.

All information about Qlik Sense APIs, port allocations, error codes,
protocol behavior and usage patterns used in this project was obtained
exclusively from publicly available sources — the Qlik Developer Portal
([help.qlik.com](https://help.qlik.com), [qlik.dev](https://qlik.dev)),
the [Qlik Community](https://community.qlik.com) forums, and other
public documentation. No proprietary, confidential or reverse-engineered
material is used.

## License

[MIT](LICENSE) © 2025-2026 Stanislav Chernov
