redash-mcp 2.2.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ko.md +223 -0
- package/README.md +167 -47
- package/dist/bird/complexity.js +87 -0
- package/dist/bird/config.js +73 -0
- package/dist/bird/evaluation.js +184 -0
- package/dist/bird/feedback.js +154 -0
- package/dist/bird/few-shot.js +95 -0
- package/dist/bird/keyword-map.js +56 -0
- package/dist/bird/llm-table-selector.js +59 -0
- package/dist/bird/schema-pruning.js +107 -0
- package/dist/bird/smart-query.js +117 -0
- package/dist/bird/tools.js +288 -0
- package/dist/bird/types.js +1 -0
- package/dist/index.js +101 -163
- package/dist/query-cache.js +0 -2
- package/dist/redash-client.js +64 -0
- package/dist/setup.js +17 -80
- package/dist/sql-guard.js +16 -24
- package/manifest.json +46 -0
- package/package.json +18 -4
- package/.gitattributes +0 -1
- package/docs/sql-safety-guard.md +0 -197
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Jiro Developers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# redash-mcp
|
|
2
|
+
|
|
3
|
+
[Redash](https://redash.io)를 Claude AI에 연결하는 MCP 서버 — 자연어로 데이터를 조회하고 대시보드를 관리하세요.
|
|
4
|
+
|
|
5
|
+
**[English Documentation](README.md)**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 기능
|
|
10
|
+
|
|
11
|
+
### 툴 목록
|
|
12
|
+
|
|
13
|
+
| 카테고리 | 툴 | 설명 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| 데이터소스 | `list_data_sources` | 연결된 데이터소스 목록 조회 |
|
|
16
|
+
| 스키마 | `list_tables` | 테이블 목록 조회 (키워드 검색 가능) |
|
|
17
|
+
| 스키마 | `get_table_columns` | 테이블 컬럼명 및 타입 조회 |
|
|
18
|
+
| 쿼리 실행 | `run_query` | SQL 직접 실행 후 결과 반환 |
|
|
19
|
+
| 저장 쿼리 | `list_queries` | 저장된 쿼리 목록 조회 |
|
|
20
|
+
| 저장 쿼리 | `get_query` | 쿼리 상세 정보 (SQL, 시각화 등) 조회 |
|
|
21
|
+
| 저장 쿼리 | `get_query_result` | 저장된 쿼리 실행 결과 조회 |
|
|
22
|
+
| 저장 쿼리 | `create_query` | 새 쿼리 저장 |
|
|
23
|
+
| 저장 쿼리 | `update_query` | 쿼리 수정 |
|
|
24
|
+
| 저장 쿼리 | `fork_query` | 쿼리 복제 |
|
|
25
|
+
| 저장 쿼리 | `archive_query` | 쿼리 삭제 (아카이브) |
|
|
26
|
+
| 대시보드 | `list_dashboards` | 대시보드 목록 조회 |
|
|
27
|
+
| 대시보드 | `get_dashboard` | 대시보드 상세 및 위젯 목록 조회 |
|
|
28
|
+
| 대시보드 | `create_dashboard` | 새 대시보드 생성 |
|
|
29
|
+
| 대시보드 | `add_widget` | 대시보드에 시각화 위젯 추가 |
|
|
30
|
+
| 알림 | `list_alerts` | 알림 목록 조회 |
|
|
31
|
+
| 알림 | `get_alert` | 알림 상세 정보 조회 |
|
|
32
|
+
| 알림 | `create_alert` | 새 알림 생성 |
|
|
33
|
+
|
|
34
|
+
### SQL 안전 가드
|
|
35
|
+
|
|
36
|
+
위험한 쿼리로부터 데이터베이스를 보호합니다:
|
|
37
|
+
|
|
38
|
+
- **항상 차단**: `DROP`, `TRUNCATE`, `ALTER TABLE`, `GRANT/REVOKE`, `WHERE` 없는 `DELETE/UPDATE`
|
|
39
|
+
- **경고 (warn 모드)** / **차단 (strict 모드)**: `SELECT *`, `WHERE`·`LIMIT` 없는 쿼리, PII 컬럼 접근
|
|
40
|
+
- **자동 LIMIT**: `REDASH_AUTO_LIMIT` 설정 시 LIMIT 없는 쿼리에 자동으로 `LIMIT N` 추가
|
|
41
|
+
|
|
42
|
+
### 쿼리 캐시
|
|
43
|
+
|
|
44
|
+
중복 API 호출을 줄이기 위해 결과를 메모리에 캐싱합니다:
|
|
45
|
+
|
|
46
|
+
- TTL: `REDASH_MCP_CACHE_TTL` 환경변수로 설정 (기본값: 300초)
|
|
47
|
+
- 최대 메모리: `REDASH_MCP_CACHE_MAX_MB` 환경변수로 설정 (기본값: 50MB)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 설치
|
|
52
|
+
|
|
53
|
+
### 자동 설치 (권장)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx redash-mcp setup
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
설치 마법사가 실행되며 Claude Desktop, Claude Code(CLI), 또는 둘 다 선택하여 설정할 수 있습니다.
|
|
60
|
+
|
|
61
|
+
### 셸 스크립트로 설치
|
|
62
|
+
|
|
63
|
+
Node.js, Claude Desktop, MCP 설정을 한번에 처리합니다:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
curl -fsSL https://raw.githubusercontent.com/seob717/redash-mcp/main/install.sh | bash
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 수동 설치
|
|
70
|
+
|
|
71
|
+
#### 1. Redash API 키 발급
|
|
72
|
+
|
|
73
|
+
Redash → 우측 상단 프로필 → **Edit Profile** → **API Key** 복사
|
|
74
|
+
|
|
75
|
+
#### 2-A. Claude Desktop 설정
|
|
76
|
+
|
|
77
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` 파일을 열고 아래 내용을 추가합니다:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"redash-mcp": {
|
|
83
|
+
"command": "npx",
|
|
84
|
+
"args": ["-y", "redash-mcp"],
|
|
85
|
+
"env": {
|
|
86
|
+
"REDASH_URL": "https://your-redash-instance.com",
|
|
87
|
+
"REDASH_API_KEY": "your_api_key_here"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
저장 후 Claude Desktop을 완전히 종료했다가 다시 시작합니다.
|
|
95
|
+
|
|
96
|
+
#### 2-B. Claude Code (CLI) 설정
|
|
97
|
+
|
|
98
|
+
`~/.claude/settings.json` 파일을 열고 아래 내용을 추가합니다:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"redash-mcp": {
|
|
104
|
+
"command": "npx",
|
|
105
|
+
"args": ["-y", "redash-mcp"],
|
|
106
|
+
"env": {
|
|
107
|
+
"REDASH_URL": "https://your-redash-instance.com",
|
|
108
|
+
"REDASH_API_KEY": "your_api_key_here"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> **macOS**: `npx`를 못 찾는 경우 `which npx` 명령어로 전체 경로를 확인 후 대체하세요.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 환경 변수
|
|
120
|
+
|
|
121
|
+
### 필수
|
|
122
|
+
|
|
123
|
+
| 변수 | 설명 |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `REDASH_URL` | Redash 인스턴스 URL (예: `https://redash.example.com`) |
|
|
126
|
+
| `REDASH_API_KEY` | Redash 사용자 API 키 |
|
|
127
|
+
|
|
128
|
+
### 선택
|
|
129
|
+
|
|
130
|
+
| 변수 | 기본값 | 설명 |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `REDASH_SAFETY_MODE` | `warn` | SQL 안전 수준: `off` / `warn` / `strict` |
|
|
133
|
+
| `REDASH_SAFETY_DISABLE_PII` | `false` | PII 감지 비활성화 |
|
|
134
|
+
| `REDASH_SAFETY_DISABLE_COST` | `false` | 비용 경고 비활성화 |
|
|
135
|
+
| `REDASH_AUTO_LIMIT` | `0` | LIMIT 없는 쿼리에 자동으로 `LIMIT N` 추가 (0 = 비활성화) |
|
|
136
|
+
| `REDASH_DEFAULT_MAX_AGE` | `0` | Redash 캐시 TTL (초) |
|
|
137
|
+
| `REDASH_MCP_CACHE_TTL` | `300` | MCP 쿼리 캐시 TTL (초, 0 = 비활성화) |
|
|
138
|
+
| `REDASH_MCP_CACHE_MAX_MB` | `50` | MCP 쿼리 캐시 최대 메모리 (MB) |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 사용 예시
|
|
143
|
+
|
|
144
|
+
Claude에게 자연어로 요청하면 됩니다:
|
|
145
|
+
|
|
146
|
+
- "users 테이블 컬럼 보여줘"
|
|
147
|
+
- "최근 7일 주문 수를 SQL로 조회해줘"
|
|
148
|
+
- "저장된 쿼리 목록 보여줘"
|
|
149
|
+
- "매출 대시보드 위젯 목록 알려줘"
|
|
150
|
+
- "일별 가입자 수가 100명 이하로 떨어지면 알림 만들어줘"
|
|
151
|
+
|
|
152
|
+
### 예제 1: 자연어로 데이터 조회
|
|
153
|
+
|
|
154
|
+
> **프롬프트**: "이번 달 신규 가입자 수 알려줘"
|
|
155
|
+
|
|
156
|
+
**실행 흐름:**
|
|
157
|
+
1. `list_data_sources` → 데이터소스 목록 확인
|
|
158
|
+
2. `smart_query` → 질문 분석, 관련 테이블(`User`) 자동 선택, SQL 생성 가이드 제공
|
|
159
|
+
3. `run_query` → 생성된 SQL 실행
|
|
160
|
+
|
|
161
|
+
**결과:**
|
|
162
|
+
```
|
|
163
|
+
이번 달 신규 가입자는 18,197명입니다.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 예제 2: 복잡한 비즈니스 질문
|
|
167
|
+
|
|
168
|
+
> **프롬프트**: "지난주 신규 가입자 중 결제한 사용자 비율은?"
|
|
169
|
+
|
|
170
|
+
**실행 흐름:**
|
|
171
|
+
1. `smart_query` → 질문 분석, `User`·`Payment` 테이블 자동 선택, JOIN 쿼리 가이드 제공
|
|
172
|
+
2. `run_query` → SQL 실행
|
|
173
|
+
|
|
174
|
+
**결과:**
|
|
175
|
+
```
|
|
176
|
+
지난주 신규 가입자 1,204명 중 결제한 사용자는 312명 (25.9%)입니다.
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 예제 3: 쿼리 저장 + 대시보드 생성
|
|
180
|
+
|
|
181
|
+
> **프롬프트**: "월별 매출 추이 쿼리를 만들고 대시보드에 추가해줘"
|
|
182
|
+
|
|
183
|
+
**실행 흐름:**
|
|
184
|
+
1. `smart_query` → 매출 관련 테이블 분석
|
|
185
|
+
2. `create_query` → "월별 매출 추이" 쿼리 저장
|
|
186
|
+
3. `create_dashboard` → "매출 대시보드" 생성
|
|
187
|
+
4. `get_query` → 저장된 쿼리의 시각화 ID 확인
|
|
188
|
+
5. `add_widget` → 대시보드에 차트 위젯 추가
|
|
189
|
+
|
|
190
|
+
**결과:**
|
|
191
|
+
```
|
|
192
|
+
"매출 대시보드"가 생성되었고, 월별 매출 추이 차트가 추가되었습니다.
|
|
193
|
+
Redash에서 확인: https://your-redash.com/dashboard/monthly-revenue
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Privacy Policy
|
|
199
|
+
|
|
200
|
+
### 데이터 수집 및 처리
|
|
201
|
+
|
|
202
|
+
redash-mcp는 **로컬 MCP 서버**로, 사용자의 Redash 인스턴스와 직접 통신합니다. 중간 서버를 거치지 않습니다.
|
|
203
|
+
|
|
204
|
+
| 항목 | 설명 |
|
|
205
|
+
|------|------|
|
|
206
|
+
| **Redash API Key** | 로컬 환경변수(`REDASH_API_KEY`)로만 저장. 외부 전송 없음 |
|
|
207
|
+
| **쿼리 내용 및 결과** | MCP 프로토콜을 통해 로컬 클라이언트(Claude Desktop/Code)에만 전달 |
|
|
208
|
+
| **BIRD SQL 설정** | 로컬 파일(`~/.redash-mcp/`)에만 저장. Few-shot 예제, 키워드 맵, 피드백 등 |
|
|
209
|
+
| **LLM Fallback** | `ANTHROPIC_API_KEY` 설정 시, 테이블명 목록만 Anthropic API로 전송. 쿼리 데이터·결과는 전송하지 않음 |
|
|
210
|
+
|
|
211
|
+
### 제3자 공유
|
|
212
|
+
|
|
213
|
+
사용자 데이터를 제3자에게 판매하거나 공유하지 않습니다. LLM Fallback 기능 사용 시 Anthropic API로 테이블명 목록만 전송되며, 이는 사용자가 `ANTHROPIC_API_KEY`를 설정한 경우에만 활성화됩니다.
|
|
214
|
+
|
|
215
|
+
### 데이터 보존
|
|
216
|
+
|
|
217
|
+
- 설정 파일: `~/.redash-mcp/` 디렉토리에 로컬 저장 (사용자가 직접 삭제 가능)
|
|
218
|
+
- 쿼리 캐시: 메모리에만 저장, 서버 종료 시 소멸
|
|
219
|
+
- 스키마 캐시: 메모리에만 저장, 10분 TTL 후 자동 소멸
|
|
220
|
+
|
|
221
|
+
### 연락처
|
|
222
|
+
|
|
223
|
+
문의 및 보안 이슈 보고: [GitHub Issues](https://github.com/seob717/redash-mcp/issues)
|
package/README.md
CHANGED
|
@@ -1,49 +1,80 @@
|
|
|
1
1
|
# redash-mcp
|
|
2
2
|
|
|
3
|
-
[Redash](https://redash.io)
|
|
3
|
+
MCP server that connects [Redash](https://redash.io) to Claude AI — query data, manage dashboards, and run SQL with natural language.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**[한국어 문서](README.ko.md)**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
### Tools
|
|
12
|
+
|
|
13
|
+
| Category | Tool | Description |
|
|
8
14
|
|---|---|---|
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
| Data Sources | `list_data_sources` | List connected data sources |
|
|
16
|
+
| Schema | `list_tables` | List tables (supports keyword search) |
|
|
17
|
+
| Schema | `get_table_columns` | Get column names and types |
|
|
18
|
+
| Query | `run_query` | Execute SQL and return results |
|
|
19
|
+
| Saved Queries | `list_queries` | List saved queries |
|
|
20
|
+
| Saved Queries | `get_query` | Get query details (SQL, visualizations) |
|
|
21
|
+
| Saved Queries | `get_query_result` | Run a saved query and get results |
|
|
22
|
+
| Saved Queries | `create_query` | Save a new query |
|
|
23
|
+
| Saved Queries | `update_query` | Update a saved query |
|
|
24
|
+
| Saved Queries | `fork_query` | Fork a saved query |
|
|
25
|
+
| Saved Queries | `archive_query` | Archive (delete) a query |
|
|
26
|
+
| Dashboards | `list_dashboards` | List dashboards |
|
|
27
|
+
| Dashboards | `get_dashboard` | Get dashboard details and widgets |
|
|
28
|
+
| Dashboards | `create_dashboard` | Create a new dashboard |
|
|
29
|
+
| Dashboards | `add_widget` | Add a visualization widget to a dashboard |
|
|
30
|
+
| Alerts | `list_alerts` | List alerts |
|
|
31
|
+
| Alerts | `get_alert` | Get alert details |
|
|
32
|
+
| Alerts | `create_alert` | Create a new alert |
|
|
33
|
+
|
|
34
|
+
### SQL Safety Guard
|
|
35
|
+
|
|
36
|
+
Protects your database from dangerous queries:
|
|
37
|
+
|
|
38
|
+
- **Blocked always**: `DROP`, `TRUNCATE`, `ALTER TABLE`, `GRANT/REVOKE`, `DELETE/UPDATE` without `WHERE`
|
|
39
|
+
- **Warned (warn mode)** / **Blocked (strict mode)**: `SELECT *`, queries without `WHERE` or `LIMIT`, PII column access
|
|
40
|
+
- **Auto-LIMIT**: Automatically appends `LIMIT N` when `REDASH_AUTO_LIMIT` is set
|
|
41
|
+
|
|
42
|
+
### Query Cache
|
|
43
|
+
|
|
44
|
+
Results are cached in-memory to reduce redundant API calls:
|
|
45
|
+
|
|
46
|
+
- TTL: configurable via `REDASH_MCP_CACHE_TTL` (default: 300s)
|
|
47
|
+
- Max memory: configurable via `REDASH_MCP_CACHE_MAX_MB` (default: 50MB)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
### Auto Setup (Recommended)
|
|
31
54
|
|
|
32
55
|
```bash
|
|
33
56
|
npx redash-mcp setup
|
|
34
57
|
```
|
|
35
58
|
|
|
36
|
-
|
|
59
|
+
The setup wizard will guide you through configuring Claude Desktop, Claude Code (CLI), or both.
|
|
37
60
|
|
|
38
|
-
###
|
|
61
|
+
### Shell Script Install
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
Installs Node.js, Claude Desktop, and MCP config all at once:
|
|
41
64
|
|
|
42
|
-
|
|
65
|
+
```bash
|
|
66
|
+
curl -fsSL https://raw.githubusercontent.com/seob717/redash-mcp/main/install.sh | bash
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Manual Setup
|
|
70
|
+
|
|
71
|
+
#### 1. Get your Redash API Key
|
|
43
72
|
|
|
44
|
-
|
|
73
|
+
Go to Redash → Profile (top right) → **Edit Profile** → Copy **API Key**
|
|
45
74
|
|
|
46
|
-
|
|
75
|
+
#### 2-A. Claude Desktop
|
|
76
|
+
|
|
77
|
+
Open `~/Library/Application Support/Claude/claude_desktop_config.json` and add:
|
|
47
78
|
|
|
48
79
|
```json
|
|
49
80
|
{
|
|
@@ -60,11 +91,11 @@ Redash → 우측 상단 프로필 → **Edit Profile** → **API Key** 복사
|
|
|
60
91
|
}
|
|
61
92
|
```
|
|
62
93
|
|
|
63
|
-
|
|
94
|
+
Fully quit and restart Claude Desktop after saving.
|
|
64
95
|
|
|
65
|
-
#### 2-B. Claude Code (CLI)
|
|
96
|
+
#### 2-B. Claude Code (CLI)
|
|
66
97
|
|
|
67
|
-
`~/.claude/settings.json`
|
|
98
|
+
Open `~/.claude/settings.json` and add:
|
|
68
99
|
|
|
69
100
|
```json
|
|
70
101
|
{
|
|
@@ -81,23 +112,112 @@ Redash → 우측 상단 프로필 → **Edit Profile** → **API Key** 복사
|
|
|
81
112
|
}
|
|
82
113
|
```
|
|
83
114
|
|
|
84
|
-
|
|
115
|
+
> **macOS**: If `npx` is not found, run `which npx` to get the full path and use that instead.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Environment Variables
|
|
85
120
|
|
|
86
|
-
|
|
87
|
-
> **macOS**: `npx`를 못 찾는 경우 `which npx` 명령어로 전체 경로를 확인 후 대체하세요.
|
|
121
|
+
### Required
|
|
88
122
|
|
|
89
|
-
|
|
123
|
+
| Variable | Description |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `REDASH_URL` | Redash instance URL (e.g. `https://redash.example.com`) |
|
|
126
|
+
| `REDASH_API_KEY` | Redash user API key |
|
|
90
127
|
|
|
91
|
-
|
|
128
|
+
### Optional
|
|
129
|
+
|
|
130
|
+
| Variable | Default | Description |
|
|
92
131
|
|---|---|---|
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
132
|
+
| `REDASH_SAFETY_MODE` | `warn` | SQL safety level: `off` / `warn` / `strict` |
|
|
133
|
+
| `REDASH_SAFETY_DISABLE_PII` | `false` | Disable PII detection |
|
|
134
|
+
| `REDASH_SAFETY_DISABLE_COST` | `false` | Disable cost warnings |
|
|
135
|
+
| `REDASH_AUTO_LIMIT` | `0` | Auto-append `LIMIT N` to queries without one (0 = disabled) |
|
|
136
|
+
| `REDASH_DEFAULT_MAX_AGE` | `0` | Redash cache TTL in seconds |
|
|
137
|
+
| `REDASH_MCP_CACHE_TTL` | `300` | MCP query cache TTL in seconds (0 = disabled) |
|
|
138
|
+
| `REDASH_MCP_CACHE_MAX_MB` | `50` | Max memory for MCP query cache in MB |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Usage Examples
|
|
143
|
+
|
|
144
|
+
Just ask Claude in natural language:
|
|
145
|
+
|
|
146
|
+
- "Show me the columns in the users table"
|
|
147
|
+
- "Run a query to get order counts for the last 7 days"
|
|
148
|
+
- "List all saved queries"
|
|
149
|
+
- "Show widgets in the revenue dashboard"
|
|
150
|
+
- "Create an alert when daily signups drop below 100"
|
|
151
|
+
|
|
152
|
+
### Example 1: Query data with natural language
|
|
153
|
+
|
|
154
|
+
> **Prompt**: "How many new users signed up this month?"
|
|
155
|
+
|
|
156
|
+
**Tool flow:**
|
|
157
|
+
1. `list_data_sources` → Identify the target data source
|
|
158
|
+
2. `smart_query` → Analyze the question, auto-select the `User` table, provide SQL generation guidance
|
|
159
|
+
3. `run_query` → Execute the generated SQL
|
|
160
|
+
|
|
161
|
+
**Result:**
|
|
162
|
+
```
|
|
163
|
+
There were 18,197 new signups this month.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Example 2: Complex business questions
|
|
167
|
+
|
|
168
|
+
> **Prompt**: "What percentage of last week's new users made a purchase?"
|
|
169
|
+
|
|
170
|
+
**Tool flow:**
|
|
171
|
+
1. `smart_query` → Analyze the question, auto-select `User` and `Payment` tables, provide JOIN query guidance
|
|
172
|
+
2. `run_query` → Execute the SQL
|
|
173
|
+
|
|
174
|
+
**Result:**
|
|
175
|
+
```
|
|
176
|
+
Out of 1,204 new users last week, 312 made a purchase (25.9%).
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Example 3: Create a query and dashboard
|
|
180
|
+
|
|
181
|
+
> **Prompt**: "Create a monthly revenue trend query and add it to a dashboard"
|
|
182
|
+
|
|
183
|
+
**Tool flow:**
|
|
184
|
+
1. `smart_query` → Analyze revenue-related tables
|
|
185
|
+
2. `create_query` → Save the "Monthly Revenue Trend" query
|
|
186
|
+
3. `create_dashboard` → Create a "Revenue Dashboard"
|
|
187
|
+
4. `get_query` → Get the visualization ID from the saved query
|
|
188
|
+
5. `add_widget` → Add the chart widget to the dashboard
|
|
189
|
+
|
|
190
|
+
**Result:**
|
|
191
|
+
```
|
|
192
|
+
Created "Revenue Dashboard" with the monthly revenue trend chart.
|
|
193
|
+
View in Redash: https://your-redash.com/dashboard/monthly-revenue
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Privacy Policy
|
|
199
|
+
|
|
200
|
+
### Data Collection and Processing
|
|
201
|
+
|
|
202
|
+
redash-mcp is a **local MCP server** that communicates directly with your Redash instance. No intermediate servers are involved.
|
|
203
|
+
|
|
204
|
+
| Item | Description |
|
|
205
|
+
|------|-------------|
|
|
206
|
+
| **Redash API Key** | Stored only as a local environment variable (`REDASH_API_KEY`). Never transmitted externally. |
|
|
207
|
+
| **Query content & results** | Delivered only to the local MCP client (Claude Desktop/Code) via the MCP protocol. |
|
|
208
|
+
| **BIRD SQL settings** | Stored only in local files (`~/.redash-mcp/`). Includes few-shot examples, keyword maps, and feedback. |
|
|
209
|
+
| **LLM Fallback** | When `ANTHROPIC_API_KEY` is set, only table name lists are sent to the Anthropic API. Query data and results are never transmitted. |
|
|
210
|
+
|
|
211
|
+
### Third-Party Sharing
|
|
212
|
+
|
|
213
|
+
We do not sell or share user data with third parties. When the LLM Fallback feature is active, only table name lists are sent to the Anthropic API, and only when the user has explicitly configured an `ANTHROPIC_API_KEY`.
|
|
214
|
+
|
|
215
|
+
### Data Retention
|
|
95
216
|
|
|
96
|
-
|
|
217
|
+
- **Config files**: Stored locally in `~/.redash-mcp/` (user can delete at any time)
|
|
218
|
+
- **Query cache**: In-memory only, cleared on server shutdown
|
|
219
|
+
- **Schema cache**: In-memory only, auto-expires after 10-minute TTL
|
|
97
220
|
|
|
98
|
-
|
|
221
|
+
### Contact
|
|
99
222
|
|
|
100
|
-
|
|
101
|
-
- "최근 7일 주문 수를 SQL로 조회해줘"
|
|
102
|
-
- "저장된 쿼리 목록 보여줘"
|
|
103
|
-
- "매출 대시보드 위젯 목록 알려줘"
|
|
223
|
+
For inquiries and security reports: [GitHub Issues](https://github.com/seob717/redash-mcp/issues)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export function assessComplexity(question, prunedTables) {
|
|
2
|
+
let score = 0;
|
|
3
|
+
const hints = [];
|
|
4
|
+
const reasons = [];
|
|
5
|
+
const q = question.toLowerCase();
|
|
6
|
+
const relevantTables = prunedTables.filter((t) => t.score > 0).length;
|
|
7
|
+
if (relevantTables > 1) {
|
|
8
|
+
score += (relevantTables - 1) * 2;
|
|
9
|
+
reasons.push(`${relevantTables} tables involved`);
|
|
10
|
+
}
|
|
11
|
+
const joinKeywords = ["join", "combine", "relate", "between tables", "연결", "결합", "조인"];
|
|
12
|
+
if (joinKeywords.some((kw) => q.includes(kw))) {
|
|
13
|
+
score += 2;
|
|
14
|
+
reasons.push("explicit join requested");
|
|
15
|
+
hints.push("Verify JOIN conditions match the correct foreign keys.");
|
|
16
|
+
}
|
|
17
|
+
const subqueryKeywords = [
|
|
18
|
+
"for each", "among those", "within", "nested", "sub",
|
|
19
|
+
"중에서", "각각", "그중", "해당하는",
|
|
20
|
+
];
|
|
21
|
+
if (subqueryKeywords.some((kw) => q.includes(kw))) {
|
|
22
|
+
score += 2;
|
|
23
|
+
reasons.push("subquery likely needed");
|
|
24
|
+
hints.push("Consider using CTEs for readability.");
|
|
25
|
+
}
|
|
26
|
+
const windowKeywords = [
|
|
27
|
+
"ranking", "rank", "running total", "cumulative", "previous",
|
|
28
|
+
"percentile", "top n", "nth",
|
|
29
|
+
"순위", "누적", "이전", "상위",
|
|
30
|
+
];
|
|
31
|
+
if (windowKeywords.some((kw) => q.includes(kw))) {
|
|
32
|
+
score += 3;
|
|
33
|
+
reasons.push("window functions may be needed");
|
|
34
|
+
hints.push("Use window functions (ROW_NUMBER, RANK, LAG/LEAD) as appropriate.");
|
|
35
|
+
}
|
|
36
|
+
const pivotKeywords = [
|
|
37
|
+
"compare across", "breakdown by", "pivot", "crosstab",
|
|
38
|
+
"비교", "교차", "피벗", "대비",
|
|
39
|
+
];
|
|
40
|
+
if (pivotKeywords.some((kw) => q.includes(kw))) {
|
|
41
|
+
score += 2;
|
|
42
|
+
reasons.push("cross-comparison or pivoting needed");
|
|
43
|
+
hints.push("Consider CASE WHEN for pivot-style queries.");
|
|
44
|
+
}
|
|
45
|
+
const timeCompareKeywords = [
|
|
46
|
+
"year over year", "month over month", "yoy", "mom",
|
|
47
|
+
"growth rate", "change rate", "trend",
|
|
48
|
+
"전년", "전월", "증감", "추이", "변화율", "성장률",
|
|
49
|
+
];
|
|
50
|
+
if (timeCompareKeywords.some((kw) => q.includes(kw))) {
|
|
51
|
+
score += 2;
|
|
52
|
+
reasons.push("time-based comparison needed");
|
|
53
|
+
hints.push("Use LAG() or self-join for period-over-period comparisons.");
|
|
54
|
+
}
|
|
55
|
+
const ratioKeywords = [
|
|
56
|
+
"ratio", "rate", "conversion", "percentage", "proportion",
|
|
57
|
+
"비율", "전환율", "퍼센트", "점유율",
|
|
58
|
+
];
|
|
59
|
+
if (ratioKeywords.some((kw) => q.includes(kw))) {
|
|
60
|
+
score += 1;
|
|
61
|
+
reasons.push("ratio calculation involved");
|
|
62
|
+
}
|
|
63
|
+
let level;
|
|
64
|
+
if (score <= 2) {
|
|
65
|
+
level = "simple";
|
|
66
|
+
}
|
|
67
|
+
else if (score <= 5) {
|
|
68
|
+
level = "medium";
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
level = "complex";
|
|
72
|
+
}
|
|
73
|
+
if (level === "simple" && hints.length === 0) {
|
|
74
|
+
hints.push("Straightforward query. Focus on correct column names and filter values.");
|
|
75
|
+
}
|
|
76
|
+
if (level === "medium" && hints.length === 0) {
|
|
77
|
+
hints.push("Moderate complexity. Double-check JOIN conditions and GROUP BY columns.");
|
|
78
|
+
}
|
|
79
|
+
if (level === "complex" && hints.length === 0) {
|
|
80
|
+
hints.push("Complex query. Consider breaking into CTEs for clarity and correctness.");
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
level,
|
|
84
|
+
reasoning: reasons.length > 0 ? reasons.join("; ") : "single table with basic filtering",
|
|
85
|
+
hints,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readFile, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
const DEFAULT_CONFIG = {
|
|
6
|
+
bird: {
|
|
7
|
+
enabled: true,
|
|
8
|
+
schemaPruning: {
|
|
9
|
+
enabled: true,
|
|
10
|
+
topK: 7,
|
|
11
|
+
includeSampleValues: true,
|
|
12
|
+
maxSampleValues: 5,
|
|
13
|
+
},
|
|
14
|
+
fewShot: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
maxExamplesPerQuery: 3,
|
|
17
|
+
},
|
|
18
|
+
feedback: {
|
|
19
|
+
enabled: true,
|
|
20
|
+
autoPromoteThreshold: 3,
|
|
21
|
+
},
|
|
22
|
+
complexity: {
|
|
23
|
+
enabled: true,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export function getConfigDir() {
|
|
28
|
+
return process.env.REDASH_MCP_CONFIG_DIR || path.join(os.homedir(), ".redash-mcp");
|
|
29
|
+
}
|
|
30
|
+
export async function ensureConfigDir() {
|
|
31
|
+
const dir = getConfigDir();
|
|
32
|
+
for (const sub of ["", "few-shot", "feedback", "eval", "keyword-map"]) {
|
|
33
|
+
const p = path.join(dir, sub);
|
|
34
|
+
if (!existsSync(p)) {
|
|
35
|
+
await mkdir(p, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function getDataSourcePath(subdir, dataSourceId) {
|
|
40
|
+
return path.join(getConfigDir(), subdir, `${dataSourceId}.json`);
|
|
41
|
+
}
|
|
42
|
+
export async function loadConfig() {
|
|
43
|
+
const configPath = path.join(getConfigDir(), "config.json");
|
|
44
|
+
try {
|
|
45
|
+
const raw = await readFile(configPath, "utf-8");
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
return deepMerge(DEFAULT_CONFIG, parsed);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return DEFAULT_CONFIG;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function saveConfig(config) {
|
|
54
|
+
await ensureConfigDir();
|
|
55
|
+
const configPath = path.join(getConfigDir(), "config.json");
|
|
56
|
+
await writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
57
|
+
}
|
|
58
|
+
function deepMerge(target, source) {
|
|
59
|
+
const result = { ...target };
|
|
60
|
+
for (const key of Object.keys(source)) {
|
|
61
|
+
if (source[key] &&
|
|
62
|
+
typeof source[key] === "object" &&
|
|
63
|
+
!Array.isArray(source[key]) &&
|
|
64
|
+
target[key] &&
|
|
65
|
+
typeof target[key] === "object") {
|
|
66
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
result[key] = source[key];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|