Skip to main content
Glama
polaris428

readonly-db-mcp

by polaris428
README.md
# readonly-db-mcp

SQL Server 데이터베이스를 읽기 전용으로 조회하는 MCP 서버입니다.

멀티 테넌트 프로젝트에서 매번 `.env`를 바꾸지 않도록, 자주 쓰는 DB 접속 환경은 `db_env` 프리셋으로 등록하고 실제 조회할 DB는 tool 호출 시 `database_name`으로 지정합니다.

## 제공 Tool

- `list_tables(schema_name="dbo", db_env=None, db_host=None, db_port=None, database_name=None)`
- `describe_table(schema_name, table_name, db_env=None, db_host=None, db_port=None, database_name=None)`
- `query_table(request)`

`query_table`의 `request`에는 다음 DB 선택 필드를 넣을 수 있습니다.

- `db_env`: `.env`에 등록한 접속 프리셋 이름. 예: `local`, `qa`, `qa_system`
- `database_name`: 실제 조회할 SQL Server database 이름. 예: `c_test`
- `db_host`, `db_port`: 프리셋 없이 직접 host/port를 지정해야 할 때만 사용

## 안전 정책

`query_table`은 Raw SQL을 받지 않습니다. 테이블/컬럼을 실제 스키마와 비교하고 다음을 강제합니다.

- `SELECT`만 실행
- 단일 테이블만 조회
- `WITH (NOLOCK)` 적용
- 최대 행 수 제한
- 파라미터 바인딩
- 쿼리 타임아웃
- 한글 컬럼명 지원

데이터 조회 전 내부적으로 테이블 컬럼 목록을 조회해서 요청 컬럼, 필터 컬럼, 정렬 컬럼이 실제로 존재하는지 검증합니다.

## 준비

- Python 3.11 이상
- Microsoft ODBC Driver 18 for SQL Server
- 조회 대상 DB에 읽기 권한이 있는 계정

Docker로 실행하면 ODBC 드라이버는 이미지 안에 포함됩니다.

## 설치

로컬 Python 실행:

```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -U pip
pip install -e ".[dev]"
```

Docker 실행:

```bash
docker build -t readonly-db-mcp:local .
```

## 환경 변수

개인별 최초 셋업은 스크립트로 진행합니다.

```bash
python3 scripts/setup_mcp.py
```

이 스크립트는 다음 작업을 합니다.

- 개인 설정 파일 `.env.local` 생성
- Docker 이미지 `readonly-db-mcp:local` 빌드
- Codex MCP 등록 갱신
- Claude MCP 등록 갱신

`.env.local`은 개인 DB 계정/비밀번호를 담는 파일이며 git에 올리지 않습니다. 공용 예시는 `.env.example`에만 둡니다.

기본 구조:

```dotenv
DB_USER=readonly_user
DB_PASSWORD=change-me

DB_DEFAULT_ENV=local

DB_LOCAL_HOST=db-tenant
DB_LOCAL_PORT=14332

DB_QA_HOST=10.1.1.49
DB_QA_PORT=14332
DB_QA_USER=readonly_user
DB_QA_PASSWORD=change-me

DB_QA_SYSTEM_HOST=host.docker.internal
DB_QA_SYSTEM_PORT=14331
DB_QA_SYSTEM_USER=readonly_user
DB_QA_SYSTEM_PASSWORD=change-me

DB_DRIVER=ODBC Driver 18 for SQL Server
DB_ENCRYPT=yes
DB_TRUST_SERVER_CERTIFICATE=yes
DB_QUERY_TIMEOUT_SECONDS=3
DB_MAX_ROWS=100
```

현재 프리셋 의미:

- `local`: 로컬 tenant DB. 기본값은 `db-tenant:14332`
- `qa`: QA tenant DB. 기본값은 `10.1.1.49:14332`
- `qa_system`: QA system DB. Docker 안에서는 `host.docker.internal:14331`로 접근

프리셋 규칙:

- `db_env=qa`는 `DB_QA_HOST`, `DB_QA_PORT`, `DB_QA_USER`, `DB_QA_PASSWORD`를 사용합니다.
- `db_env=qa_system`은 `DB_QA_SYSTEM_HOST`, `DB_QA_SYSTEM_PORT`, `DB_QA_SYSTEM_USER`, `DB_QA_SYSTEM_PASSWORD`를 사용합니다.
- `db_host`/`db_port`를 tool 호출에 직접 넘기면 프리셋보다 우선합니다.
- `DB_CONNECTION_STRING`을 설정하면 모든 분리 변수와 tool 호출 인자보다 우선합니다.

비밀번호에 `;` 또는 `}`가 있어도 자동으로 ODBC 형식에 맞게 감쌉니다.

## 직접 실행

```bash
readonly-db-mcp
```

stdio MCP 서버이므로 실행 후 화면이 멈춘 것처럼 보이는 것이 정상입니다.

Docker로 직접 실행:

```bash
docker run --rm -i \
  --network sellmate-dockerize_default \
  --add-host host.docker.internal:host-gateway \
  --env-file /home/polaris/readonly-db-mcp/.env.local \
  readonly-db-mcp:local
```

## Codex MCP 등록 예시

`/home/polaris/.codex/config.toml`:

```toml
[mcp_servers.readonly_db]
command = "docker"
args = ["run", "--rm", "-i", "--network", "sellmate-dockerize_default", "--add-host", "host.docker.internal:host-gateway", "--env-file", "/home/polaris/readonly-db-mcp/.env.local", "readonly-db-mcp:local"]
```

## Claude MCP 등록 예시

```bash
claude mcp add -s user readonly_db -- \
  docker run --rm -i \
  --network sellmate-dockerize_default \
  --add-host host.docker.internal:host-gateway \
  --env-file /home/polaris/readonly-db-mcp/.env.local \
  readonly-db-mcp:local
```

등록 확인:

```bash
claude mcp get readonly_db
```

stdio 서버 특성상 헬스체크가 `Failed to connect`로 보일 수 있습니다. 실제 Claude 세션에서 tool이 보이고 호출되면 정상입니다.

## 사용 예시

Claude/Codex에 자연어로 요청:

```text
readonly_db MCP로 qa 환경의 c_yongma DB에서 발주정보 테이블 구조 조회해줘.
```

명시적 tool 인자 기준:

```json
{
  "schema_name": "dbo",
  "table_name": "발주정보",
  "db_env": "qa",
  "database_name": "c_yongma"
}
```

`query_table` 요청 예시:

```json
{
  "request": {
    "db_env": "qa",
    "database_name": "c_yongma",
    "schema_name": "dbo",
    "table_name": "발주정보",
    "columns": ["일련번호", "SEQ코드"],
    "filters": [
      {
        "column": "판매처주문번호",
        "operator": "eq",
        "value": "2026071610"
      }
    ],
    "order_by": [
      {
        "column": "일련번호",
        "direction": "desc"
      }
    ],
    "limit": 50
  }
}
```

지원 연산자:

- `eq`, `neq`
- `gt`, `gte`, `lt`, `lte`
- `in`
- `like`
- `is_null`, `not_null`

## 테스트

```bash
pytest
```

## 현재 범위

포함:

- SQL Server 읽기 전용 조회
- 환경 프리셋 기반 DB 접속 선택
- 요청별 database 선택
- 테이블 목록 및 컬럼 구조 조회
- 단일 테이블 데이터 조회
- 한글 테이블명/컬럼명 조회

제외:

- system DB에서 tenant 자동 탐색
- 사용자 인증 및 권한 승인
- JOIN
- Raw SQL
- INSERT/UPDATE/DELETE
- 웹 API