qlik-mcp-server

qlik-mcp-server

MCP server that enables AI assistants to inspect Qlik Sense data models, explore data sources, and build dashboard sheets and charts via local LLMs like Ollama.

Category
访问服务器

README

AI Dashboard Builder for Qlik Sense

Connect a local LLM to a Qlik Sense app and have it inspect the data model and build sheets and charts — what Qlik Cloud gives you with a hosted assistant, running entirely on your own machine.

Today it targets Qlik Sense Desktop + Ollama. Enterprise on-premise is implemented behind the same interface (see Enterprise).

One command

python web_app.py
  Qlik AI      -> http://127.0.0.1:8000
  MCP endpoint -> http://127.0.0.1:8000/mcp
  app 'data' (desktop), model qwen2.5-coder:7b

That is the whole product. The page has both halves of Qlik behind a view switcher, the way Qlik itself does it:

  • Data load editor — sections, script, data connections, Load data
  • Sheets — existing sheets, a field-by-field view of what's loaded, and a box to describe a dashboard you want

…with the assistant beside both, and app and model dropdowns in the header.

One Qlik connection is shared by everything (session.py) — the browser, the assistant and the MCP endpoint all work on the same open app. That isn't an optimisation: Qlik Sense Desktop allows only one session per app, so separate connections would lock each other out.

Point Claude Code or Claude Desktop at http://127.0.0.1:8000/mcp and it sees the same open app you do, live, without starting anything else.

Secondary entry points, all optional:

python chat.py The same assistant in a terminal. It can also reload (asks first).
python mcp_server.py MCP over stdio, if you'd rather not run the web server
python main.py One-shot: design a dashboard with the local model and build it
python check_connection.py Tests Qlik and Ollama separately when something breaks

How it works

config.py           settings from .env, validated at import
qlik_engine.py      websocket JSON-RPC client for the Qlik Engine API
chart_specs.py      property trees for each native chart type
prompts.py          system prompt + field-list prompt
ollama_client.py    local LLM calls, constrained to JSON
dashboard_builder.py  design -> validate against real fields -> build
mcp_server.py       all of the above as MCP tools and resources
  1. qlik_engine.py opens a websocket to the Engine API and can read field metadata, create sheets, and create charts (bar, line, pie, table, KPI), positioning each on the sheet grid with the property schema Qlik's native renderers actually require.
  2. dashboard_builder.py sends the field list to the LLM with a strict JSON schema and validates the result against the real field list before building anything. This matters: Qlik does not reject a chart that references a field which doesn't exist — it creates the object and renders an empty box. Validation is the only thing standing between a hallucinated field name and a silently blank chart.
  3. mcp_server.py wraps both as MCP tools.

Setup

pip install -r requirements.txt
cp .env.example .env      # then edit it
python check_connection.py

check_connection.py tests each dependency separately, so you find out which one is broken rather than watching the whole pipeline fail at once. Run it first whenever something stops working.

You'll need:

  • An app already loaded with data, named to match APP_NAME (python list_sheets.py --apps shows what's available).
  • Ollama running with the model in .env pulled, e.g. ollama pull phi4:14b — only for the AI-design features, not the low-level MCP tools.

Qlik Sense Desktop (default)

No auth. Desktop only listens on 4848 while the Hub is open.

QLIK_MODE=desktop
QLIK_HOST=localhost
QLIK_PORT=4848
APP_NAME=data

Enterprise on-premise

Certificate auth, connecting directly to the Engine API and bypassing the proxy.

QLIK_MODE=enterprise
QLIK_HOST=your-engine-host
QLIK_PORT=4747
QLIK_CERT_DIR=C:/path/to/certs
QLIK_USER_DIRECTORY=YOURDOMAIN
QLIK_USER_ID=your_user

QLIK_CERT_DIR must contain client.pem, client_key.pem and root.pem, exported from the QMC (Certificates > Export certificates, platform independent .pem format). These identify which user the engine session runs as; that user needs access to the app in the QMC.

Everything above the socket is mode-agnostic, so the same tools and prompts work in both. Two things to know about the move:

  • Desktop opens an app by name; Enterprise requires the app GUID. open_app() resolves a friendly name to an id via GetDocList first, so APP_NAME keeps working either way.
  • TLS is verified against root.pem by default. The engine certificate is issued to the server's hostname, so connect by that name — not by IP. If you need to diagnose a handshake failure, QLIK_SSL_VERIFY=false disables verification, but it also stops authenticating the server, so turn it back on once it works.

Enterprise has not been tested against a live server from this repo; it follows Qlik's documented direct-Engine-API pattern. If your environment uses a non-default proxy setup you may need to adjust the connection block in qlik_engine.py.

Usage — AI Data Load Editor

python web_app.py --model gemma4:26b

Open http://127.0.0.1:8000. The page mirrors Qlik's Data load editor — Sections on the left, the script in the middle, Data connections on the right, Save and Load data at the top — with a chat panel underneath:

> load every csv in my downloads folder and merge them
> drop the columns that are always the same
> what columns does this file have?

The assistant writes into the editor; you press Load data. That split is deliberate and mirrors Qlik itself: the model is not given the reload action at all here, so it can prepare a script but can never rebuild your data on its own.

Why a page of our own

Qlik has no extension point for the Data load editor — Qlik Sense extensions render inside sheets only, and Qlik Sense Desktop runs in its own embedded browser window where browser extensions aren't practical either. So this is our page, editing the real load script through the Engine API. What the assistant writes is what Qlik runs.

It behaves identically against Desktop and Enterprise on-premise, because the only thing that differs between them is how the websocket is opened.

Nothing leaves the machine: browser → this server → Qlik and Ollama.

Usage — chatbot

python chat.py
python chat.py --app data --model gemma4:26b
> load every csv in my downloads folder and merge them
> what is total sales by market?
> clean the data - drop the columns that are always the same
> build me a sales dashboard

Everything runs on your machine: a local Ollama model drives the Qlik Engine API directly. It shows each action as it takes it, and asks before anything that would replace your data.

The model must support tool calling, and several good ones do not — phi4 and deepseek-coder return a 400 for tools. If the configured model can't, chat.py and web_app.py pick an installed one that can and say so, rather than refusing to start. Set your own with CHAT_MODEL in .env, separate from OLLAMA_MODEL so the dashboard designer (which only needs JSON, not tools) can keep using a different one.

Check any model with ollama show <model> and look for tools under capabilities — that's read from metadata, so it doesn't load the model.

Two things keep a local model from doing damage:

  • It is not allowed to write Qlik script by hand. Small models produce QVS that looks right and isn't — DROP FIELDS inside a LOAD, say. Script is generated by build_load_script, and anything applied is syntax-checked and rolled back if it doesn't parse.
  • Reloading asks first. It replaces every row in the app, and a model will occasionally reach for it unprompted.

Usage — CLI

python check_connection.py                 # smoke test, no LLM calls
python main.py                             # build an AI-designed dashboard
python main.py --app Sales --instruction "3 charts, focus on region"
python list_sheets.py                      # sheets as the Hub sees them
python list_sheets.py --apps               # apps and their ids
python dump_object_properties.py           # every object's full properties
python dump_object_properties.py --type piechart

Usage — MCP server

python mcp_server.py     # stdio, in this environment
mcp dev mcp_server.py    # with the MCP Inspector web UI

mcp dev does not run the server in your current Python environment. It launches it via uv run in a throwaway environment containing only mcp plus the packages the server declares in SERVER_DEPENDENCIES (mcp_server.py). Without that list the server dies on import websocket before registering a single tool and the Inspector shows an empty server, so keep it in sync with requirements.txt whenever you add a dependency.

For Claude Code, add to your MCP config:

{
  "mcpServers": {
    "qlik-dashboard-builder": {
      "command": "python",
      "args": ["C:/project/claudemcp/mcp_server.py"]
    }
  }
}

Tools

Four tools, deliberately.

Tool Purpose
qlik_open(app) Open an app — no argument lists them. Call this first.
qlik_data_sources(connection, path) Connections → folder contents → a file's real columns and sample rows, without loading it
qlik_build_sheet(instruction | charts, title) Build a sheet — describe it in plain language, or specify the charts
qlik_save() Persist changes

qlik_build_sheet takes it either way. Pass instruction="sales by region and customer segment, 4 charts" and the local Ollama model designs it by reading the data model itself — no JSON to write, nothing else required. Pass charts=[...] when the caller wants to choose precisely; a capable assistant will get a better result that way than the local model does. Either way, every chart is validated against the real data first.

Loading data, editing the load script, cleaning, and exploring values are not here. They live in the chatbot, where you reach them by asking rather than by filling in tool arguments by hand.

Resources: qlik://fields and qlik://sheets.

Resources: qlik://fields and qlik://sheets.

The load editor is meant to be edited by the AI

qlik_script / qlik_edit_script / qlik_reload are the centrepiece, not a side feature — the assistant reads the script, rewrites it, runs it, reads the errors, and fixes them. Three things make that safe to do repeatedly:

  • Edits are per-tab. replace_tab rewrites one section and leaves every hand-written tab exactly as it was. Regenerating replaces that tab instead of appending, so iterating doesn't pile up dead code.
  • A broken script never lands. The result is syntax-checked before it's applied; if it doesn't parse, the previous script is restored and the errors come back instead.
  • Every edit is undoable with mode='undo', and a failed reload returns the engine's own error messages so the script can actually be repaired.

So "load every CSV in my Downloads folder, merge them and clean them" runs as:

qlik_add_data_source("downloads", "C:/Users/me/Downloads")
qlik_data_sources("downloads")                    → which files are there
qlik_data_sources("downloads", "orders.csv")      → real columns + sample rows
qlik_generate_load_script(sources=[...], mode="concatenate")
qlik_edit_script(content=..., tab="Loader")       → syntax-checked
qlik_reload()                                     → errors back if it fails
qlik_data_model()                                 → what's constant / mostly null
qlik_generate_load_script(..., drop_fields=[...]) → second pass, now informed

Cleaning knows types. Trim() returns text, so trimming a numeric column silently turns it into strings — the field loses its $numeric tag and every Sum() over it stops working. Sample rows are used to detect numeric columns and leave them alone. On the bundled dataset that protects 26 of 53 columns.

Qlik Sense Desktop caches open apps — read this

Qlik Sense Desktop keeps its own in-memory copy of any app it has open. Everything this tool creates is written to the .qvf immediately, but a Qlik window that already had the app open will not show it. That looks exactly like nothing happened.

Two consequences:

  • To see new sheets, close the app in Qlik and open it again. If that isn't enough, restart Qlik Sense Desktop — its copy is held per process.
  • Never press Save in Qlik after the assistant has worked on the app. Qlik would write its older copy over what was just saved, and the new sheets are gone.

The safest habit is to keep the app closed in Qlik while working here, and open it when you want to look.

To make this diagnosable rather than mysterious, saves report the .qvf's size and modification time, and the assistant says so whenever it creates a sheet — so you can tell "it didn't save" from "Qlik is showing you a cached copy".

This is a Desktop limitation. Enterprise on-premise runs a shared engine where sessions see each other's changes, so it largely goes away on the migration.

Why cardinality matters

A model given only field names cannot tell a category from an identifier, and will cheerfully build a bar chart grouped by an order id with 65,000 distinct values — it renders, and it is useless. So the field list now carries cardinality (which the engine returns for free), low-cardinality fields are profiled for real sample values, and any dimension above MAX_DIMENSION_CARDINALITY is rejected before it reaches Qlik.

Safety

  • Connection strings are redacted. Qlik stores REST/database connection strings verbatim, API tokens included. qlik_list_connections strips credentials before returning them, because these values otherwise reach the model and the conversation transcript. Loading data only ever needs a connection's name for a lib:// path.
  • qlik_set_script writes to its own tab by default, leaving hand-written tabs alone, and syntax-checks before applying — a script that doesn't parse is rolled back rather than left in the app. qlik_restore_script undoes the last write.
  • qlik_reload is the destructive one. It rebuilds the data model from source; charts referencing fields that no longer exist go blank.

Tests

pip install -r requirements-dev.txt
pytest

The suite runs against a fake websocket — no Qlik or Ollama needed.

Notes / limitations

  • One shared Qlik session per server process. Suits one person building one app at a time. Engine calls are serialised internally, so concurrent MCP tool calls are safe, but they share the same open app.
  • Nothing persists until qlik_save().
  • measure_expression must be a simple aggregationSum([Field]), Count([Field]), Count(DISTINCT [Field]), Avg(...), Min(...), Max(...). Anything with SortBy, Limit or Aggr fails to calculate and renders blank rather than erroring. Rejected at validation.
  • Field names must match exactly, character for character. A wrong name creates a chart that silently shows no data, so always check qlik_list_fields() first.
  • Chart property trees are load-bearing. The values in chart_specs.py came from real objects read back with GetProperties, not from the docs — CreateChild fills in no defaults, and the nebula.js renderers either crash or draw an empty box when a key is missing. To add a chart type, build one by hand in the Qlik client, dump it with dump_object_properties.py, and copy the real shape.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选