Skip to main content
Glama
README.md
# MCP API 서버

FastAPI 및 FastMCP 기반의 Model Context Protocol (MCP) 서버로, YOLOv8 이미지 분석과 PostgreSQL 데이터베이스 통합을 지원합니다.

## 프로젝트 개요

이 프로젝트는 Claude와 같은 AI 에이전트가 이미지를 분석하고 결과를 데이터베이스에 저장할 수 있도록 MCP 프로토콜을 통해 제공합니다.

**주요 기능:**

- **YOLOv8 이미지 분석** - nano/small/medium 모델 지원 (빠른 추론)
- **감지된 객체 자동 처리** - 크롭 및 base64 인코딩
- **PostgreSQL 데이터 저장** - asyncpg 기반 비동기 DB 연결
- **4-Layer 아키텍처** - Presenter → Service → Core → Utility 레이어 구분
- **MCP 3가지 전송 방식** - StdIO (Claude Desktop), HTTP/JSON-RPC, WebSocket

---

## 시스템 요구사항

- **Python:** 3.13.3 이상
- **패키지 매니저:** `uv` ([설치 가이드](https://docs.astral.sh/uv/getting-started/installation/))
- **메모리:** 2GB 이상 (YOLOv8 모델 로드용)
- **데이터베이스:** PostgreSQL 12+ (pgvector 확장 필수)

---

## 빠른 시작

### 1단계: 의존성 설치

```bash
# 프로젝트 루트에서 실행
uv sync
```

### 2단계: 데이터베이스 설정

PostgreSQL 데이터베이스를 생성하고 `.env` 파일에 접속 정보를 설정합니다:

```bash
# .env 파일 생성
cat > .env << EOF
# PostgreSQL 데이터베이스 설정
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mcp_db
DB_USER=postgres
DB_PASSWORD=your_password

# YOLOv8 모델 설정
YOLO_MODEL=yolov8n.pt
YOLO_HOME=./src/mcp_server/models
YOLO_CONF_THRESHOLD=0.5

# MCP 서버 설정
MCP_SERVER_NAME=mcp-api-server
LOG_LEVEL=INFO
EOF
```

**설정 항목 설명:**

| 항목                  | 기본값                          | 설명                                           |
| --------------------- | ------------------------------- | ---------------------------------------------- |
| `DB_HOST`             | `localhost`                     | PostgreSQL 호스트                              |
| `DB_PORT`             | `5432`                          | PostgreSQL 포트                                |
| `DB_NAME`             | `mcp_db`                        | 데이터베이스명                                 |
| `DB_USER`             | `postgres`                      | DB 사용자명                                    |
| `DB_PASSWORD`         | -                               | DB 암호                                        |
| `YOLO_MODEL`          | `yolov8n.pt`                    | YOLOv8 모델 (nano/s/m/l/x)                     |
| `YOLO_HOME`           | `./src/mcp_server/models`       | 모델 다운로드 디렉토리                         |
| `YOLO_CONF_THRESHOLD` | `0.5`                           | 감지 신뢰도 임계값 (0~1)                       |
| `MCP_SERVER_NAME`     | `mcp-api-server`                | MCP 서버 이름                                  |
| `LOG_LEVEL`           | `INFO`                          | 로그 레벨 (DEBUG/INFO/WARNING)                 |

**YOLOv8 모델 선택 가이드:**

| 모델       | 추론시간 | 메모리  | 정확도 | 추천 상황      |
| ---------- | -------- | ------- | ------ | -------------- |
| yolov8n.pt | ~3ms     | <100MB  | 낮음   | 빠른 응답 필요 |
| yolov8s.pt | ~10ms    | 200MB   | 중간   | ✅ 권장        |
| yolov8m.pt | ~20ms    | 400MB   | 높음   | 정확도 중시    |

### 3단계: 서버 실행

**MCP StdIO 서버 (Claude Desktop 연결용):**

```bash
# FastMCP 방식 (권장, 간단함)
uv run python -m mcp_server

# 또는 MCP SDK 방식 (더 많은 기능)
MCP_SERVER_TYPE=sdk uv run python -m mcp_server
```

**FastAPI HTTP/WebSocket 서버 (원격 클라이언트용):**

```bash
# 개발 모드 (자동 리로드 활성화)
uv run uvicorn src.mcp_server.main:app --reload

# 프로덕션 모드 (4개 워커)
uv run uvicorn src.mcp_server.main:app --workers 4
```

**서버 시작 확인:**

- FastMCP StdIO: 터미널에서 "Starting FastMCP server" 로그 메시지 확인
- FastAPI HTTP: 브라우저에서 `http://localhost:8000/health` 접속 → `{"status": "ok"}`

---

## 프로젝트 구조

```
mcp-api-server/
│
├── .git/                                    # Git 저장소
├── .python-version                          # Python 3.13.3
├── .env.example                             # 환경변수 템플릿
├── .gitignore
│
├── pyproject.toml                           # uv 프로젝트 설정
├── uv.lock                                  # 의존성 잠금 파일
│
├── README.md                                # 이 파일
├── CLAUDE.md                                # 개발 규약
├── NOTION_USAGE_GUIDE.md                    # Notion 작업 문서 규칙
├── ARCHITECTURE.md                          # 아키텍처 설명
│
├── src/
│   └── mcp_server/                          # 메인 패키지
│       ├── __main__.py                      # MCP StdIO 진입점
│       ├── main.py                          # FastAPI HTTP/WS 진입점
│       ├── fastmcp_server.py                # FastMCP 서버 구현
│       ├── server.py                        # MCP 서버 인스턴스 (SDK용)
│       │
│       ├── commons/                         # 공유 유틸리티 (infrastructure)
│       │   ├── config.py                    # Pydantic Settings (환경변수)
│       │   ├── database.py                  # SQLAlchemy AsyncEngine + asyncpg
│       │   ├── logging.py                   # 로깅 설정
│       │   └── exceptions.py                # 커스텀 예외
│       │
│       ├── core/                            # 도메인 모델 (domain layer)
│       │   ├── yolo_model.py                # YOLOv8 lazy loading (스레드 안전)
│       │   └── embedding_client.py          # 외부 API 클라이언트
│       │
│       ├── schemas/                         # 데이터 모델 (Pydantic)
│       │   ├── response.py                  # 응답 모델 (Detection, AnalysisResult, ImageRecordResponse)
│       │   └── img_model.py                 # DB ORM 모델 (ImageRecord)
│       │
│       ├── services/                        # 비즈니스 로직 (service layer)
│       │   ├── image_analyzer_service.py    # YOLOv8 분석 로직
│       │   ├── image_query_service.py       # DB 조회 로직
│       │   └── repository.py                # DB 저장 로직
│       │
│       ├── tools/                           # MCP Tool 구현 (presenter layer)
│       │   ├── __init__.py
│       │   └── image_analysis.py            # analyze_image tool
│       │
│       ├── resources/                       # MCP Resource (선택)
│       │   └── __init__.py
│       │
│       ├── prompts/                         # MCP Prompt (선택)
│       │   └── __init__.py
│       │
│       └── utils/                           # 공유 유틸리티
│           ├── suppress_output.py           # 출력 억제 데코레이터
│           └── image_utils.py               # 이미지 처리 헬퍼
│
├── tests/                                   # 테스트 스위트
│   ├── conftest.py                          # pytest fixtures
│   ├── test_models.py                       # Pydantic 모델 테스트
│   └── test_*.py                            # 기능별 테스트
│
└── examples/ (선택)                         # 클라이언트 예제
    ├── http_client.py                       # HTTP JSON-RPC 클라이언트
    └── websocket_client.py                  # WebSocket 클라이언트
```

---

## 아키텍처 (4 Layers)

```
┌──────────────────────────────────────────────────────────┐
│ Presenter Layer (MCP Tools)                              │
│  - tools/image_analysis.py                               │
│  - MCP Protocol 변환 (JSON-RPC 2.0)                      │
└──────────────────┬───────────────────────────────────────┘
                   │
┌──────────────────▼───────────────────────────────────────┐
│ Service Layer (비즈니스 로직)                             │
│  - services/image_analyzer_service.py                    │
│  - services/image_query_service.py                       │
│  - @atransactional 데코레이터로 DB 트랜잭션 관리        │
└──────────────────┬───────────────────────────────────────┘
                   │
┌──────────────────▼───────────────────────────────────────┐
│ Core Layer (도메인 모델)                                 │
│  - core/yolo_model.py (double-checked locking)          │
│  - core/embedding_client.py                             │
│  - schemas/img_model.py (SQLAlchemy ORM)                │
└──────────────────┬───────────────────────────────────────┘
                   │
┌──────────────────▼───────────────────────────────────────┐
│ Utility Layer (공유 인프라)                              │
│  - commons/database.py (AsyncEngine + asyncpg)          │
│  - commons/config.py (환경변수)                         │
│  - utils/suppress_output.py (출력 억제)                 │
└──────────────────────────────────────────────────────────┘
```

**의존성 흐름:** Presenter → Service → Core → Utility (역방향 금지)

---

## 주요 기능 설명

### `analyze_image` MCP Tool

이미지 파일 경로를 입력받아 YOLOv8로 객체를 감지하고 결과를 저장합니다.

**입력 파라미터:**

| 파라미터         | 타입   | 필수 | 설명                          |
| ---------------- | ------ | ---- | ----------------------------- |
| `image_path`     | string | ✅   | 이미지 파일 절대 경로         |
| `conf_threshold` | float  | ❌   | 감지 임계값 (0~1, 기본: 0.5)  |

**반환 결과 (AnalysisResult):**

```json
{
  "image_path": "/path/to/image.jpg",
  "detections": [
    {
      "class_name": "person",
      "confidence": 0.91,
      "bbox": [100.0, 50.0, 200.0, 300.0]
    }
  ],
  "total_objects": 1
}
```

**처리 흐름:**

1. **입력 검증** - 파일 존재 확인
2. **이미지 전처리** - PIL로 로드, RGB 변환, 1024×1024 리사이징
3. **객체 감지** - YOLOv8 추론 (asyncio.to_thread 사용)
4. **DB 저장** - ImageRecord 생성 및 PostgreSQL 저장 (@atransactional)
5. **결과 반환** - Pydantic AnalysisResult 모델

**주요 특징:**

- **비동기 처리** - asyncio.to_thread()로 블로킹 작업 분리
- **자동 리사이징** - 1024×1024으로 일관된 처리 품질 보장
- **에러 처리** - DB 연결 실패 시 예외 발생, 로그 기록
- **타입 안정성** - Pydantic 모델로 검증

---

## 데이터베이스 구성

### PostgreSQL + asyncpg 설정

```bash
# PostgreSQL 설치 (Windows)
# https://www.postgresql.org/download/windows/

# 데이터베이스 생성
psql -U postgres -c "CREATE DATABASE mcp_db;"
```

### 데이터베이스 테이블 (자동 생성)

서버 첫 실행 시 `init_async_db()` 함수가 아래 테이블을 자동으로 생성합니다:

```sql
CREATE TABLE image_records (
  id SERIAL PRIMARY KEY,
  path VARCHAR NOT NULL UNIQUE,
  category VARCHAR NOT NULL,
  confidence FLOAT NOT NULL,
  description TEXT,
  objects JSONB,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);
```

### 비동기 데이터베이스 접근

모든 DB 접근은 `@atransactional` 데코레이터를 사용합니다:

```python
from mcp_server.commons.database import atransactional, AsyncSession
from mcp_server.schemas.response import AnalysisResult

@atransactional
async def save_analysis(result: AnalysisResult, db: AsyncSession):
    """데이터베이스에 분석 결과 저장"""
    from mcp_server.schemas.img_model import ImageRecord
    
    record = ImageRecord(
        path=result.image_path,
        category="detected_object",
        confidence=result.detections[0].confidence if result.detections else 0.0,
    )
    db.add(record)
    # 트랜잭션 자동 커밋 (예외 발생 시 자동 롤백)
```

**특징:**

- **asyncpg 기반** - PostgreSQL 비동기 드라이버
- **Lazy initialization** - FastMCP 루프에서만 engine 생성
- **자동 트랜잭션** - 데코레이터로 commit/rollback 자동 관리
- **세션 주입** - 함수 파라미터에 자동으로 db 제공

---

## API 엔드포인트

### 헬스 체크 및 서버 정보

```bash
# 헬스 체크
GET /health
→ {"status": "ok"}

# 서버 정보
GET /info
```

### MCP HTTP/JSON-RPC (네트워크 기반)

**엔드포인트:** `POST /mcp/messages`

```python
import httpx
import asyncio

async def call_tool():
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "http://localhost:8000/mcp/messages",
            json={
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/call",
                "params": {
                    "name": "analyze_image",
                    "arguments": {
                        "image_path": "/absolute/path/to/image.jpg",
                        "conf_threshold": 0.5
                    }
                }
            }
        )
        print(response.json())

asyncio.run(call_tool())
```

### MCP WebSocket 양방향 통신

**엔드포인트:** `WS /mcp/ws`

```python
import asyncio
import json
import websockets

async def ws_client():
    async with websockets.connect("ws://localhost:8000/mcp/ws") as ws:
        await ws.send(json.dumps({
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {}
        }))
        response = await ws.recv()
        print(f"Response: {response}")

asyncio.run(ws_client())
```

### MCP StdIO (Claude Desktop)

**Claude Desktop 설정:**

macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
Windows: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "mcp-api-server": {
      "command": "uv",
      "args": ["run", "python", "-m", "mcp_server"],
      "cwd": "/absolute/path/to/mcp-api-server"
    }
  }
}
```

---

## 개발 명령어

### 테스트 실행

```bash
# 모든 테스트 실행
uv run pytest

# 상세 출력
uv run pytest -v

# 특정 테스트 실행
uv run pytest tests/test_models.py -v

# 커버리지 리포트
uv run pytest --cov=src
```

### 코드 린트 및 포맷팅

```bash
# 린트 검사
uv run ruff check .

# 자동 포맷팅
uv run ruff format .

# 린트 + 포맷 함께
uv run ruff check . && uv run ruff format .
```

### 타입 검사

```bash
# VS Code Pylance strict mode 사용 (IDE에서 설정)
# .vscode/settings.json:
{
  "python.analysis.typeCheckingMode": "strict"
}
```

---

## 트러블슈팅

### YOLOv8 모델 자동 다운로드 실패

```bash
# 수동 다운로드
uv run python -c "from ultralytics import YOLO; YOLO('yolov8n.pt')"

# 또는 서버 실행 (자동 다운로드)
uv run uvicorn src.mcp_server.main:app
```

**모델 저장 위치:** `./src/mcp_server/models/` (`.gitignore` 제외)

### PostgreSQL 연결 오류

```bash
# DB 접속 가능 확인
psql -h localhost -U postgres -d mcp_db -c "SELECT 1;"

# 방화벽 설정 확인 (Windows)
# → Defender 방화벽에서 PostgreSQL 포트 5432 허용
```

### asyncpg 루프 바인딩 에러

에러: `'NoneType' object has no attribute 'send'`

**원인:** asyncpg 커넥션이 다른 event loop에 바인딩됨

**해결:**
- `init_async_db()`는 별도 loop에서 실행 (스키마만 초기화)
- 실제 engine은 FastMCP/FastAPI loop에서 lazy 생성
- 자세한 설명은 [ARCHITECTURE.md](ARCHITECTURE.md) 참고

### 메모리 부족

```bash
# 더 작은 모델 사용
echo "YOLO_MODEL=yolov8n.pt" >> .env

# 또는 명령어로 실행
YOLO_MODEL=yolov8s.pt uv run uvicorn src.mcp_server.main:app
```

---

## 개발 규약

모든 개발 규약은 [CLAUDE.md](CLAUDE.md)에 자세히 기술되어 있습니다.

**주요 규약:**

- **언어:** Python 3.13.3 이상
- **타입 힌팅:** Pylance strict mode 필수 (모든 함수/파라미터)
- **코드 스타일:** Ruff (line-length: 100)
- **데이터 모델:** Pydantic BaseModel + SQLAlchemy ORM
- **커밋 메시지:** Conventional Commits
- **테스트:** pytest + pytest-asyncio
- **아키텍처:** 4-Layer (Presenter → Service → Core → Utility)

---

## Git 워크플로우

### 새 기능 추가

```bash
git checkout -b feature/기능-이름
# ... 작업 ...
git add .
git commit -m "feat(scope): 기능 설명"
git push -u origin feature/기능-이름
```

### 버그 수정

```bash
git checkout -b fix/버그-이름
# ... 수정 ...
git commit -m "fix(scope): 버그 설명"
git push -u origin fix/버그-이름
```

---

## 성능 최적화

### YOLOv8 모델 선택

| 모델       | 추론시간 | 메모리 | 정확도 | 추천        |
| ---------- | -------- | ------ | ------ | ----------- |
| yolov8n.pt | ~3ms     | <100MB | 낮음   | 빠른 응답   |
| yolov8s.pt | ~10ms    | 200MB  | 중간   | ✅ 권장     |
| yolov8m.pt | ~20ms    | 400MB  | 높음   | 정확도 중시 |
| yolov8l.pt | ~40ms    | 800MB  | 매우높음 | 매우 정확   |

### 비동기 배치 처리

```python
from mcp_server.services.image_analyzer_service import ImageAnalyzerService

service = ImageAnalyzerService()

# 여러 이미지를 동시에 분석
results = await service.analyze_images_abatch([
    "/path/to/image1.jpg",
    "/path/to/image2.jpg",
    "/path/to/image3.jpg",
])
```

---

## 라이선스

MIT License

---

## 참고 자료

- [MCP 공식 문서](https://modelcontextprotocol.io/)
- [FastAPI 문서](https://fastapi.tiangolo.com/)
- [FastMCP 문서](https://github.com/jqlang/fastmcp)
- [YOLOv8 문서](https://docs.ultralytics.com/)
- [SQLAlchemy 비동기](https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html)
- [asyncpg 문서](https://magicstack.github.io/asyncpg/)
- [Pydantic 문서](https://docs.pydantic.dev/)
- [uv 패키지 매니저](https://docs.astral.sh/uv/)

더 자세한 개발 정보는 [CLAUDE.md](CLAUDE.md)를 참고하세요.