clickhouse-mcp-server
# clickhouse-mcp-server
ClickHouse에 읽기 전용으로 접근하는 MCP(Model Context Protocol) 서버입니다. Python
MCP SDK(`mcp[cli]`)와 `clickhouse-connect`로 구현했습니다.
## 프로젝트 구조
```
.
├── pyproject.toml
├── README.md
└── src/clickhouse_mcp_server/
├── __init__.py
└── server.py # FastMCP 서버 본체, 도구 4개 정의
```
## 제공 도구
| 도구 | 설명 |
|---|---|
| `list_databases()` | 서버의 모든 데이터베이스 목록 조회 |
| `list_tables(database?)` | 데이터베이스 내 테이블 목록, 엔진, row 수 조회 (미지정 시 기본 DB) |
| `describe_table(table, database?)` | 테이블 컬럼 스키마(이름/타입/기본값/코멘트 등) 조회 |
| `run_select_query(query, max_rows?)` | SELECT/SHOW/DESCRIBE 등 읽기 전용 쿼리 실행 |
`run_select_query`는 `{"columns": [...], "rows": [...], "row_count": N, "truncated": bool}`
형태로 반환합니다. `max_rows`(기본 1000, 최대 10000)를 넘는 결과는 잘리고
`truncated: true`로 표시됩니다.
## 읽기 전용 보장 (2단계)
1. **정규식 사전 차단**: 쿼리 문자열 시작 부분이
`INSERT/UPDATE/DELETE/ALTER/CREATE/DROP/TRUNCATE/RENAME/GRANT/REVOKE/OPTIMIZE/ATTACH/DETACH/KILL/SYSTEM`
등으로 시작하면 ClickHouse에 보내기 전에 `ValueError`로 즉시 거부합니다.
2. **서버 측 readonly 모드**: ClickHouse 커넥션 자체를 `readonly=2` 설정으로 엽니다.
`readonly=1`이 아니라 `2`를 쓰는 이유는, `1`은 세션 설정 변경 자체를 막아버려서
`max_result_rows`(행 수 캡)도 함께 무시되는 문제가 있었기 때문입니다(아래 "테스트 중
발견한 버그" 참고). `readonly=2`는 쓰기는 그대로 막으면서 설정 변경만 허용합니다.
행 수 캡은 서버 설정(`max_result_rows`/`result_overflow_mode=break`)만으로는 정확하지
않을 수 있어(ClickHouse가 블록 단위로만 자르기 때문에, 결과가 작아서 한 블록에 다 들어가면
캡이 적용되지 않음), 반환 직전에 **클라이언트 코드에서도 `max_rows`만큼 다시 슬라이싱**해서
정확한 상한을 보장합니다.
## 설치
```bash
uv venv .venv
uv pip install -e .
```
## 환경 변수
| 변수 | 기본값 | 설명 |
|---|---|---|
| `CLICKHOUSE_HOST` | `localhost` | ClickHouse 호스트 |
| `CLICKHOUSE_PORT` | `8123` | HTTP 포트 |
| `CLICKHOUSE_USER` | `default` | 사용자명 |
| `CLICKHOUSE_PASSWORD` | (빈 값) | 비밀번호 |
| `CLICKHOUSE_DATABASE` | `default` | 기본 데이터베이스 |
| `CLICKHOUSE_SECURE` | `false` | `true`면 HTTPS 사용 (보통 포트 8443) |
## 사용 방법
### 1. Claude Code에 등록
```bash
claude mcp add clickhouse \
--env CLICKHOUSE_HOST=your-host \
--env CLICKHOUSE_PORT=8123 \
--env CLICKHOUSE_USER=default \
--env CLICKHOUSE_PASSWORD=your-password \
--env CLICKHOUSE_DATABASE=default \
-- /home/trsprs/workspace/claude/test1/.venv/bin/python -m clickhouse_mcp_server.server
```
또는 `~/.claude.json` / 프로젝트 `.mcp.json`에 직접 추가:
```json
{
"mcpServers": {
"clickhouse": {
"command": "/home/trsprs/workspace/claude/test1/.venv/bin/python",
"args": ["-m", "clickhouse_mcp_server.server"],
"env": {
"CLICKHOUSE_HOST": "your-host",
"CLICKHOUSE_PORT": "8123",
"CLICKHOUSE_USER": "default",
"CLICKHOUSE_PASSWORD": "your-password",
"CLICKHOUSE_DATABASE": "default"
}
}
}
}
```
등록 후 Claude Code에서 "clickhouse의 테이블 목록 보여줘" 같은 요청을 하면 위 도구들이
자동으로 호출됩니다.
### 2. MCP Inspector로 직접 확인
Node.js가 있으면 브라우저 UI로 도구 목록/호출을 직접 테스트할 수 있습니다.
```bash
CLICKHOUSE_HOST=your-host CLICKHOUSE_USER=default CLICKHOUSE_PASSWORD=your-password \
.venv/bin/mcp dev src/clickhouse_mcp_server/server.py
```
### 3. 로컬 Docker ClickHouse로 임시 테스트
실제 ClickHouse가 없어도 아래처럼 임시 컨테이너를 띄워 전체 흐름을 검증할 수 있습니다
(이 저장소를 개발할 때 실제로 이렇게 검증했습니다).
```bash
# 1) ClickHouse 컨테이너 실행
docker run -d --name clickhouse-mcp-test \
-p 8123:8123 -p 9000:9000 \
-e CLICKHOUSE_USER=default \
-e CLICKHOUSE_PASSWORD=testpass \
-e CLICKHOUSE_DB=default \
clickhouse/clickhouse-server:latest
# 2) 준비될 때까지 대기
until curl -s http://localhost:8123/ping | grep -q Ok; do sleep 1; done
# 3) 샘플 테이블/데이터 생성
curl -s -u default:testpass "http://localhost:8123/" --data-binary "
CREATE TABLE default.events (
id UInt64, event_name String, user_id UInt32, created_at DateTime
) ENGINE = MergeTree ORDER BY id;
INSERT INTO default.events VALUES
(1,'signup',101,'2026-07-01 10:00:00'),
(2,'login',101,'2026-07-01 10:05:00'),
(3,'purchase',102,'2026-07-02 14:30:00');
"
# 4) MCP 서버를 실제 stdio 프로토콜로 띄워 도구 호출 (파이썬 클라이언트 예시는 아래)
CLICKHOUSE_HOST=localhost CLICKHOUSE_PORT=8123 CLICKHOUSE_USER=default \
CLICKHOUSE_PASSWORD=testpass CLICKHOUSE_DATABASE=default \
.venv/bin/python -m clickhouse_mcp_server.server
# 5) 테스트 끝나면 컨테이너 정리
docker rm -f clickhouse-mcp-test
```
4)번 대신 아래처럼 파이썬에서 MCP 클라이언트로 직접 붙여 전 도구를 검증할 수도 있습니다:
```python
import asyncio, os
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(
command=".venv/bin/python",
args=["-m", "clickhouse_mcp_server.server"],
env={**os.environ, "CLICKHOUSE_HOST": "localhost",
"CLICKHOUSE_USER": "default", "CLICKHOUSE_PASSWORD": "testpass"},
)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
print(await session.list_tools())
print(await session.call_tool("list_tables", {}))
asyncio.run(main())
```
## 검증 이력
- venv 설치 후 모듈 import, 도구 4개 등록 확인
- 로컬 Docker ClickHouse(샘플 테이블 `events`, 5행)에 대해 4개 도구 전부를 실제 MCP
stdio 프로토콜(클라이언트 세션)로 호출해 정상 응답 확인
- `DROP TABLE` 등 쓰기 쿼리가 정규식 단계에서 즉시 차단됨을 확인
- **버그 수정**: `run_select_query`의 `max_rows` 캡이 작은 결과셋에서 적용되지 않던 문제를
발견 → `readonly=2` + 클라이언트 측 슬라이싱으로 수정, 10만 행 쿼리에서도 정확히
캡되는 것을 재확인
TDQS
Scored across 4 tools
Each tool has a unique and clearly defined purpose. There is no overlap; an agent can easily distinguish between describing a table, listing databases, listing tables, and running arbitrary queries.
All tool names follow a consistent verb_noun pattern (describe, list, list, run). The naming is predictable and intuitive.
With four tools, the server is well-scoped for its read-only metadata and querying purpose. Each tool is essential and provides a distinct function without unnecessary bloat.
The tool set covers the core operations for inspecting a ClickHouse server's schema and executing queries. While some advanced metadata operations (e.g., listing table engines) are missing, the generic run_select_query tool can compensate for many gaps, making the surface reasonably complete.