# QlikMCP
MCP (Model Context Protocol) server for Qlik Cloud, enabling AI assistants like Claude to interact with your Qlik analytics platform.
## Features
- **Data Extraction**: List apps, sheets, visualizations and extract data from charts
- **Expression Evaluation**: Calculate Qlik expressions on the fly
- **App Management**: Trigger reloads, check reload status
- **Space & Files**: Browse spaces and data files
- **Automation**: List and trigger Qlik automations
## Prerequisites
- Elixir 1.17+
- A Qlik Cloud tenant with API access
- A Qlik Cloud API key
## Installation
### As a standalone server
1. Clone this repository:
```bash
git clone https://github.com/dgilperez/qlik_mcp.git
cd qlik_mcp
```
2. Install dependencies:
```bash
mix deps.get
```
3. Configure your Qlik Cloud credentials:
```bash
export QLIK_API_KEY="your-api-key"
export QLIK_TENANT_URL="https://your-tenant.region.qlikcloud.com"
```
4. Start the server:
```bash
mix run --no-halt
```
The MCP server will be available at `http://localhost:4100/mcp`.
### Auto-start on macOS (launchd)
For convenience, you can configure the MCP server to start automatically at login using macOS launchd.
1. Create a startup script at `~/.local/bin/qlik-mcp-start`:
```bash
#!/bin/bash
export HOME="$HOME"
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
# If using asdf for Elixir version management:
# source /opt/homebrew/opt/asdf/libexec/asdf.sh
# Set Qlik credentials
export QLIK_API_KEY="your-api-key"
export QLIK_TENANT_URL="https://your-tenant.region.qlikcloud.com"
cd /path/to/qlik-cloud-mcp
exec mix run --no-halt
```
Make it executable:
```bash
chmod +x ~/.local/bin/qlik-mcp-start
```
2. Create a launchd plist at `~/Library/LaunchAgents/com.qlik-mcp.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.org/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.qlik-mcp</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/YOUR_USERNAME/.local/bin/qlik-mcp-start</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/YOUR_USERNAME/.local/log/qlik-mcp.log</string>
<key>StandardErrorPath</key>
<string>/Users/YOUR_USERNAME/.local/log/qlik-mcp.error.log</string>
</dict>
</plist>
```
3. Create log directory and load the service:
```bash
mkdir -p ~/.local/log
launchctl load ~/Library/LaunchAgents/com.qlik-mcp.plist
```
4. Manage the service:
```bash
# Check status
launchctl list | grep qlik
# Stop
launchctl unload ~/Library/LaunchAgents/com.qlik-mcp.plist
# Start
launchctl load ~/Library/LaunchAgents/com.qlik-mcp.plist
# View logs
tail -f ~/.local/log/qlik-mcp.log
```
### As a dependency
Add to your `mix.exs`:
```elixir
def deps do
[
{:qlik_mcp, "~> 0.1.0"}
]
end
```
## Configuration
### Timeout Settings
The server is configured with robust timeout protection to prevent hanging:
- **Request Timeout**: 120 seconds (2 minutes) - Maximum time for tool execution
- **Idle Timeout**: 1800 seconds (30 minutes) - SSE connection keepalive
- **Inactivity Timeout**: 1800 seconds (30 minutes) - Streaming response timeout
- **QIX Operation Timeout**: 15 seconds - Individual database query timeout
These are configured in `lib/qlik_mcp/application.ex` and `lib/qlik_mcp/tools/helpers.ex`.
### Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `QLIK_API_KEY` | Yes | Your Qlik Cloud API key |
| `QLIK_TENANT_URL` | Yes | Your tenant URL (e.g., `https://tenant.region.qlikcloud.com`) |
| `QLIK_MCP_PORT` | No | HTTP port (default: 4100) |
| `QLIK_MAX_ROWS` | No | Max rows per data request (default: 10000) |
### Application Config
```elixir
# config/config.exs
config :qlik_mcp,
api_key: System.get_env("QLIK_API_KEY"),
tenant_url: System.get_env("QLIK_TENANT_URL"),
port: 4100,
max_rows: 10_000
```
## Claude Integration
### Claude Code (CLI)
Add to your `~/.claude.json` in the `mcpServers` section:
```json
{
"mcpServers": {
"qlik": {
"type": "http",
"url": "http://localhost:4100/mcp"
}
}
}
```
### Claude Desktop
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"qlik": {
"url": "http://localhost:4100/mcp"
}
}
}
```
## Available Tools
### Data Extraction (Primary Value)
| Tool | Description |
|------|-------------|
| `list_apps` | List available Qlik apps with optional filtering |
| `get_app` | Get detailed information about a specific app |
| `list_sheets` | List all sheets in an app |
| `list_charts` | List visualizations on a sheet |
| `get_chart_data` | Extract data from a visualization |
| `evaluate_expression` | Calculate a Qlik expression (e.g., `Sum(Sales)`) |
### App Management
| Tool | Description |
|------|-------------|
| `reload_app` | Trigger a data reload for an app |
| `get_reload_status` | Check reload progress |
### Spaces & Files
| Tool | Description |
|------|-------------|
| `list_spaces` | List available spaces |
| `list_files` | List data files |
### Automation
| Tool | Description |
|------|-------------|
| `list_automations` | List available automations |
| `run_automation` | Trigger an automation |
## MCP Resources
| URI | Description |
|-----|-------------|
| `qlik://apps` | JSON list of all accessible apps |
| `qlik://spaces` | JSON list of all accessible spaces |
## Example Interaction
```
User: "What's our sales by country from the Q4 dashboard?"
Claude:
1. list_apps() → finds "Q4 Sales Dashboard" app
2. list_sheets(app_id) → finds "Regional Sales" sheet
3. list_charts(app_id, sheet_id) → finds "Sales by Country" table
4. get_chart_data(app_id, object_id) → extracts:
| Country | Sales | Margin |
|---------|----------|--------|
| USA | $1.2M | 23% |
| Germany | $987K | 19% |
| UK | $654K | 21% |
5. Claude analyzes and responds with insights
```
## Architecture
```
qlik-cloud-mcp/
├── lib/
│ ├── qlik_mcp.ex # Main module
│ ├── qlik_mcp/
│ │ ├── application.ex # OTP Application
│ │ ├── server.ex # MCP Server (Anubis)
│ │ ├── config.ex # Configuration
│ │ ├── router.ex # HTTP Router (Plug)
│ │ ├── tools/ # MCP Tool implementations
│ │ │ ├── list_apps.ex
│ │ │ ├── get_app.ex
│ │ │ ├── list_sheets.ex
│ │ │ ├── list_charts.ex
│ │ │ ├── get_chart_data.ex
│ │ │ ├── evaluate_expression.ex
│ │ │ ├── reload_app.ex
│ │ │ ├── get_reload_status.ex
│ │ │ ├── list_spaces.ex
│ │ │ ├── list_files.ex
│ │ │ ├── list_automations.ex
│ │ │ ├── run_automation.ex
│ │ │ └── helpers.ex
│ │ └── resources/ # MCP Resource implementations
│ │ ├── apps.ex
│ │ └── spaces.ex
└── mix.exs
```
## Troubleshooting
### Server Logs
When running via launchd, logs are written to `~/.local/log/qlik-mcp.log`.
Check for errors:
```bash
tail -f ~/.local/log/qlik-mcp.log | grep -i error
```
### Common Issues
**Tool hangs or times out:**
- Check QIX timeout (15 seconds default for individual queries)
- Check request timeout (120 seconds default for complete tool execution)
- Verify Qlik Cloud API key is valid: `echo $QLIK_API_KEY`
- Check network connectivity to Qlik Cloud
**Server crashes:**
- Check logs for stack traces in `~/.local/log/qlik-mcp.log`
- Verify all environment variables are set correctly
- All tools have comprehensive exception handling via `safe_execute`
- If crashes persist, open an issue with logs
**Connection issues:**
- Verify server is running: `curl http://localhost:4100/health`
- Check launchd status: `launchctl list | grep qlik-mcp`
- Restart server: `launchctl unload ~/Library/LaunchAgents/com.qlik-mcp.plist && launchctl load ~/Library/LaunchAgents/com.qlik-mcp.plist`
### Reliability & Robustness
The server includes comprehensive timeout and error handling:
1. **HTTP Request Timeout** - Explicit `request_timeout: 120_000` ms prevents indefinite hangs
2. **QIX Operation Timeout** - 15-second timeout wrapper for all database queries
3. **Exception Handling** - All tools wrapped with `safe_execute` to catch and gracefully handle crashes
4. **Data Structure Handling** - Proper extraction of Qlik's complex tuple/map data structures
## Dependencies
- [Anubis MCP](https://hex.pm/packages/anubis_mcp) - Elixir MCP SDK
- [QlikElixir](https://hex.pm/packages/qlik_elixir) - Qlik Cloud client library
- [Plug Cowboy](https://hex.pm/packages/plug_cowboy) - HTTP server
## Development
```bash
# Run tests
mix test
# Run linter
mix lint
# Generate docs
mix docs
```
## Related Projects
- [qlik_elixir](https://github.com/dgilperez/qlik_elixir) - The Qlik Cloud Elixir client this MCP server is built on
- [qlik-mcp](https://github.com/jwaxman19/qlik-mcp) - TypeScript reference implementation
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Write tests first (TDD encouraged)
4. Ensure all checks pass (`mix format && mix credo --strict && mix test`)
5. Commit your changes
6. Push to the branch
7. Open a Pull Request
## License
MIT License - see [LICENSE](LICENSE) file for details.
## Links
- [HexDocs](https://hexdocs.pm/qlik_mcp)
- [Hex.pm](https://hex.pm/packages/qlik_mcp)
- [GitHub](https://github.com/dgilperez/qlik_mcp)
- [Qlik Developer Portal](https://qlik.dev/)
---
## Sponsored by
This project is proudly sponsored by **[Balneario - Clínica de Longevidad de Cofrentes](https://balneario.com)**, a world-class longevity clinic and thermal spa in Valencia, Spain. Their support makes open source development like this possible.
Thank you for investing in the developer community!