1. Conduid
  2. Developer Tools
  3. Qlik Sense MCP
MCP server · Developer Tools

Qlik Sense MCP

MCP Server for Qlik Sense Enterprise APIs

Unclaimed MIT devtools
57Fair

Scored 4 days ago · breakdown

About Qlik Sense MCP

Qlik Sense MCP is an MCP server published by bintocher in the Developer Tools category: mCP Server for Qlik Sense Enterprise APIs.

The repository has 44 stars and 16 forks, with the last commit 10 days ago. Recent commit activity is the strongest signal that reported issues will get attention.

Install

From source
git clone https://github.com/bintocher/qlik-sense-mcp

This server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.

Ask AI

✦

Ask AI about Qlik Sense MCP

Powered by Claude · Grounded in docs

I know everything about Qlik Sense MCP. Ask me about installation, configuration, usage, or troubleshooting.

Security checks

Static checks derived from the repository 5 days ago. Heuristics on the source, not an audit.

  • ✓README presentThe repository ships documentation.
  • ✓License declaredUsable under a stated license.
  • ✓Tests presentA test suite exists in the repository.
  • !Dependencies pinnedNo lockfile. Installs can pull different code over time.
  • ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
  • !Scoped permissionsDoesn't declare a permission scope. Assume it can do anything its process can.

Releases

v2.4.0Release v2.4.0 · 13 Oct 2026Release v2.4.0 Install via uvx: uvx qlik-sense-mcp-server
v2.3.1Release v2.3.1 · 25 Sep 2026Release v2.3.1 Install via uvx: uvx qlik-sense-mcp-server
v2.3.0Release v2.3.0 · 30 Aug 2026Release v2.3.0 Install via uvx: uvx qlik-sense-mcp-server
v2.2.0Release v2.2.0 · 29 Aug 2026Release v2.2.0 Install via uvx: uvx qlik-sense-mcp-server
v2.1.0Release v2.1.0 · 29 Aug 2026Release v2.1.0 Install via uvx: uvx qlik-sense-mcp-server

README

Qlik Sense MCP Server

PyPI version PyPI downloads Python versions

Model Context Protocol server for Qlik Sense Enterprise. Exposes Qlik's Repository (HTTP) and Engine (WebSocket) APIs as 28 MCP tools so an LLM client can discover apps, inspect data models, query data, and manage reload tasks through a single uniform interface. In JWT and form (login/password) mode the 14 reload-task tools are hidden by default: QRS task administration needs an admin role in the QMC, which a JWT analyst or a form login usually does not hold. That is a property of the identity, not of how it authenticated, so QLIK_TASK_TOOLS=true turns them on for an identity that does hold it.

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) engine_query, engine_create_hypercube, get_app_script, get_app_variables, get_app_sheets, get_app_sheet_objects, get_app_object, search_app, get_app_field, engine_get_field_range, get_app_field_statistics Query data, read load script, list visualizations, inspect field values
Reload tasks (certificate mode by default; opt-in elsewhere, see below) 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, update_task_schedule, delete_task_schedule Inspect, trigger and manage reload tasks

Full list with descriptions: docs/tools.md.

The main analysis call takes the question, not the Qlik syntax for it:

// engine_query — "revenue by region for 2024, biggest first"
{
  "app_id": "<app guid>",
  "group_by": ["region_name"],
  "metrics":  [{"field": "amount", "agg": "sum", "label": "Revenue"}],
  "filters":  [{"field": "order_date", "period": "2024"}],
  "sort_by": "Revenue",
  "limit": 10
}

The server writes the set analysis, checks that the filter selects something, and answers with period_check — the earliest and latest date actually in the result — so a filter that failed to apply is visible instead of hiding behind a plausible number. Independent questions go in one call as queries and share three round-trips.

Harder questions stay in the same form. A share of the whole, with the numerator narrowed and the denominator not:

{
  "group_by": ["region_name"],
  "metrics": [{"label": "Share", "op": "divide", "of": [
    {"field": "amount", "agg": "sum",
     "filters": [{"field": "category", "values": ["Alpha"]}]},
    {"field": "amount", "agg": "sum", "total": true}]}]
}

The same form states an aggregation over an aggregation ("inner_agg": "sum", "per": "order_id", "agg": "median"), the clients who bought in one year and not the next (matching / not_matching), counting over a bookmark or ignoring selections (scope), and values kept, dropped, added or intersected on any field.

The app's own master measures are named rather than rebuilt - "measures": [{"master": "Revenue"}] - and take the query's filters as an outer set in front of them.

engine_create_hypercube takes the same shape with the expressions written by hand, for calculations the typed form cannot state.

Quick start

uvx qlik-sense-mcp-server

The server starts in Streamable HTTP mode on http://127.0.0.1:8000/mcp. Configure it via environment variables — see docs/configuration.md.

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

Three authentication modes are supported: client certificate (legacy, full QRS access), JWT via virtual proxy (per-analyst, no on-disk secrets), and login/password against a "Form based" virtual proxy (e.g. the in-box Windows credentials login page). See docs/AUTH_JWT.md and docs/AUTH_FORM.md for setup.

Documentation

Document What's inside
docs/installation.md Requirements, install via uvx / pip / source, certificate setup
docs/configuration.md All QLIK_* environment variables, sample .env, MCP client config snippet
docs/AUTH_JWT.md JWT authentication via virtual proxy: key generation, virtual proxy setup, QLIK_JWT_TOKEN usage
docs/AUTH_FORM.md Login/password authentication via a "Form based" virtual proxy: how the login flow works, QLIK_PASSWORD usage, overrides for non-default login pages
docs/usage.md Transports, server start commands, recommended call order, hard limits enforced by this server
docs/tools.md Inventory of all 28 tools, response/error envelope, error categories
docs/architecture.md Project layout, components, connection caching, strict id-matching, two-tier timeout
docs/development.md make targets, tests, versioning, how to add a new tool
docs/troubleshooting.md Common errors, hypercube planning failures, verbose logging, configuration self-test
docs/llm-behaviour.md What models actually do with this server, measured: calls per question, where they go wrong, session limits, how to benchmark honestly
CHANGELOG.md Release notes

Key facts

  • A wrong query is refused, not answered. Qlik evaluates an unknown field name as an expression worth 0, so a hypercube grouped by a typo came back as a single row holding the grand total — a plausible number with nothing to mark it as wrong. Every query is checked by Engine before it runs: ExpandExpression resolves variables, CheckExpression reports syntax and unknown names, GetFieldsFromExpression reports the fields a set modifier actually filters on. The four checks cost about 4ms in one batch, against 75ms for the smallest hypercube.

  • A period filter is measured, not assumed. Comparison inside a set modifier runs against the text Qlik displays for a value, so a serial number range returns 0 on a field displayed as 01.01.2024 and works on one displayed as 45292 — with no error either way. State the period as a filter and the server writes it as a range in the field's own display format, checks it against a reference count, falls back to the numeric range and then to the form that always works, and reports the period the result actually covers - and when a shifted calendar made the period reach facts outside it.

  • Filters reach the author's measures. {"master": "Revenue"} with a period is computed as {&<period>} [Revenue], an outer set in front of the measure, and the reply names a measure that sets a filtered field itself or counts over a set of its own, where the filter does not reach.

  • One value, one writing. A date in a query result reads as the text Qlik displays for it, the same as the sample values in get_app_details and the bounds from engine_get_field_range.

  • A field name is always written in brackets. A bare Тип ставки is read by Qlik's parser as two tokens — "Garbage after expression: 'ставки'" — which used to refuse queries Qlik itself runs happily.

  • Aggregating over groups, not rows. per and inner_agg state "sum per issue, then the 85th percentile across issues", which is a different question from a percentile over rows and gives a different number.

  • A measure can narrow itself. Its own filters override the query's, so a KPI carries its numerator and its denominator in one answer.

  • Objects say what they show. get_app_sheet_objects gives each object one line - its dimensions, its measures with their expressions, the conditions and variables it depends on, what a filter or button does - and fields_used, including fields reached through master items and the ones inside a filter pane's listboxes — so "what does this sheet work with" is one call, with nothing calculated.

  • Paging is done by Qlik, not after it. App and task listings read /{entity}/table with skip/take and take the total from /{entity}/count, so nothing past the QRS record limit goes missing and total_found is the real total. Field search and field paging likewise happen in Engine — verified on a field with 200,000 distinct values, where the old local scan simply could not see a match.

  • A failure is never an empty answer. A QRS 500, a refused connection or an Engine error used to arrive as [], "" or "no schedule", which reads as a tidy, empty Qlik. Every such path now returns an error_category and the original cause.

  • Column meanings, not just column names. Fields and tables commented in the load script (COMMENT FIELD / COMMENT TABLE) carry that text into get_app_details as comment, and into get_app_field as field_comment, so the model reads what a column means instead of guessing from its name. Added in 1.7.2.

  • 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.8.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 are visible, not hidden. Facts with no value for the grouping field collapse into Qlik's "-" row, which often holds a large total and can take first place in a top-N. It is kept by default — a fact with no grouping value is still a fact — and exclude_null_dimensions=true leaves it out.

  • 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/form mode, by default. Reload-task administration needs QRS admin rights — a QMC role, not a property of the authentication method — so those 14 tools default to on in certificate mode and off in JWT/form mode: 28 tools with a certificate, 14 with a JWT or a login/password. QLIK_TASK_TOOLS=true turns them on in JWT/form mode too, for an identity verified to hold those rights.

  • One Qlik session per server. Qlik's per-user limit (5 by default) counts proxy sessions, and in JWT/form mode one is created by the session bootstrap itself — before any WebSocket. The server therefore bootstraps once and reuses that session for every call; restarting it in a loop is what exhausts the quota, not the number of queries.

  • 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.

  • Login/password authentication via a "Form based" virtual proxy. Set QLIK_PASSWORD (plus QLIK_USER_ID and optionally QLIK_USER_DIRECTORY) instead of a certificate or a JWT and the server logs into the proxy's login page the way a browser would, then reuses the resulting session cookie exactly like JWT mode does after its own bootstrap. Setup guide: docs/AUTH_FORM.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/connection.py and 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 for any single page over 10000 cells.

  • Two timeout knobs. QLIK_WS_TIMEOUT (default 180.0 seconds) controls both the WebSocket handshake and every Engine API call; QLIK_TOOL_TIMEOUT (default 240.0) bounds a whole tool call, so a slow one ends with a clear error before the client gives up on it.

Requirements

  • Python 3.12 (the package is built and tested against this version; see pyproject.toml)
  • Qlik Sense Enterprise (Repository on port 4242, Engine on port 4747 — the standard ports)
  • Client certificate, private key and root CA from the Qlik Sense node (certificate mode only - JWT and form mode need nothing on disk beyond the token or the password)
  • 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, qlik.dev), the Qlik Community forums, and other public documentation. No proprietary, confidential or reverse-engineered material is used.

License

MIT © 2025-2026 Stanislav Chernov

README fetched from the repository 5 days ago. The original is authoritative.

Questions

About Qlik Sense MCP

How do I install Qlik Sense MCP?

No package has been published for this server, so install it from source: clone the repository and follow its README.

Is Qlik Sense MCP safe to use with an AI agent?

Its trust score is 57 out of 100 (fair). It passes 4 of the 6 static security checks run so far; the failures are listed above. It has no ConduID identity yet, so agent calls to it are not receipted.

Is Qlik Sense MCP still maintained?

Yes — the latest release is v2.4.0 (13 Oct 2026), and the last commit was 10 days ago. The repository has 44 stars and 1 open issues.