joo_mcp
by marcjoo1209
README.md
# joo_mcp — FastAPI + Gemini + MCP 학습 예제
MCP를 처음 접하는 사람도 따라 할 수 있도록 만든 교육용 예제입니다.
**자연어로 메모(note)를 CRUD** 하는 작은 앱을 만들면서 MCP의 핵심을 익힙니다.
```
사용자: "내일 회의 준비라는 메모 만들어줘"
│
▼ (HTTP POST /chat)
FastAPI 앱 ──► Gemini API ──► MCP 서버(도구: create_note 등) ──► SQLite DB
│ │
◄────────────── "id=1 로 '내일 회의 준비' 메모를 만들었습니다" ◄──────────┘
```
---
## 1. 먼저 읽어보세요 (MCP 개념)
MCP가 처음이라면 아래 문서를 순서대로 읽으면 됩니다.
| 순서 | 문서 | 내용 |
|---|---|---|
| 1 | [docs/01-MCP란-무엇인가.md](docs/01-MCP란-무엇인가.md) | MCP가 무엇이고 왜 필요한가 (비유로 설명) |
| 2 | [docs/02-MCP-아키텍처.md](docs/02-MCP-아키텍처.md) | Host / Client / Server 구조와 도구 호출 흐름 |
| 3 | [docs/03-프로젝트-동작-방식.md](docs/03-프로젝트-동작-방식.md) | 이 코드가 실제로 어떻게 동작하는지 줄줄이 설명 |
| 4 | [docs/04-비용과-세션-최적화.md](docs/04-비용과-세션-최적화.md) | 세션 재사용·컨텍스트 캐싱으로 지연/비용 줄이기 |
| 5 | [docs/05-랭그래프-의도파악.md](docs/05-랭그래프-의도파악.md) | LangGraph 로 엔티티 기반 의도 파악 (`/intent`) |
| 6 | [docs/06-시나리오-LLM-CRUD.md](docs/06-시나리오-LLM-CRUD.md) | LLM 이 메모를 MCP 로 CRUD 하는 시나리오 (흐름→코드 라인→엣지케이스→검증 로그) |
> 💡 위 시나리오를 **단계별 애니메이션으로 보려면** [`dashboard/scenarios.html`](dashboard/scenarios.html)
> 을 브라우저로 열면 됩니다. 서버·API 키 없이 동작하는 정적 대시보드입니다.
---
## 2. 프로젝트 구조 (표준 레이어드 아키텍처)
요청은 위에서 아래로만 흐릅니다: **Router → Service → Repository → DB**
```
joo_mcp/
├── README.md
├── requirements.txt
├── .env.example
│
├── docs/ ← MCP 교육 자료
│ ├── 01-MCP란-무엇인가.md
│ ├── 02-MCP-아키텍처.md
│ └── 03-프로젝트-동작-방식.md
│
├── mcp_server/
│ └── server.py ← ★ MCP 서버: 메모 CRUD 도구 5개 (Repository 재사용)
│
└── app/
├── main.py ← ★ 앱 팩토리: 라우터/예외/lifespan 조립
├── core/
│ ├── config.py ← 설정(pydantic-settings)
│ └── exceptions.py ← 도메인 예외
├── api/
│ ├── deps.py ← 의존성 주입(DI) 제공자
│ └── routes/
│ ├── notes.py ← /notes 라우터 (직접 REST CRUD)
│ ├── chat.py ← /chat 라우터 (AI CRUD, LLM이 도구 선택)
│ └── intent.py ← /intent 라우터 (LangGraph 의도 파악)
├── services/
│ ├── note_service.py ← 메모 비즈니스 로직
│ ├── chat_service.py ← ★ Gemini ↔ MCP 오케스트레이션 (핵심)
│ ├── intent_graph.py ← ★ LangGraph 엔티티 기반 의도 파악
│ └── mcp_session.py ← 영속 MCP 세션 매니저 (요청당 subprocess 제거)
├── repositories/
│ └── note_repository.py ← ★ 데이터 접근(SQL). REST와 MCP가 공유
├── models/note.py ← 도메인 모델
├── schemas/note.py ← 요청/응답 DTO
└── db/database.py ← DB 연결/초기화
```
★ 표시가 핵심 파일입니다. 계층별 책임은
[docs/03-프로젝트-동작-방식.md](docs/03-프로젝트-동작-방식.md)에서 코드와 함께 설명합니다.
---
## 3. 설치 및 실행
### (1) 가상환경 + 패키지 설치
```powershell
# Windows PowerShell 기준
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
```
```bash
# macOS / Linux
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### (2) API 키 설정
[Google AI Studio](https://aistudio.google.com/apikey)에서 Gemini API 키를 발급받은 뒤:
```powershell
copy .env.example .env # macOS/Linux: cp .env.example .env
```
`.env` 파일을 열어 `GEMINI_API_KEY` 값을 채웁니다.
### (3) 서버 실행
```bash
uvicorn app.main:app --reload
```
> MCP 서버(`mcp_server/server.py`)는 따로 실행하지 않습니다.
> FastAPI 앱이 **시작 시 한 번** 하위 프로세스로 띄워 앱 수명 동안 재사용합니다.
> (요청마다 띄우지 않아 지연이 줄어듭니다 — [docs/04](docs/04-비용과-세션-최적화.md))
실행 후 브라우저에서 **http://127.0.0.1:8000/docs** 를 열면
Swagger UI로 모든 API를 직접 눌러볼 수 있습니다.
---
## 4. 사용해보기
### A. 직접 REST CRUD (MCP/AI 없이)
```bash
# Create
curl -X POST http://127.0.0.1:8000/notes -H "Content-Type: application/json" -d "{\"title\":\"장보기\",\"content\":\"우유, 계란\"}"
# Read (전체)
curl http://127.0.0.1:8000/notes
# Update
curl -X PUT http://127.0.0.1:8000/notes/1 -H "Content-Type: application/json" -d "{\"content\":\"우유, 계란, 빵\"}"
# Delete
curl -X DELETE http://127.0.0.1:8000/notes/1
```
### B. AI 기반 CRUD (FastAPI → Gemini → MCP)
```bash
curl -X POST http://127.0.0.1:8000/chat -H "Content-Type: application/json" -d "{\"message\":\"내일 회의 준비라는 제목으로 메모 만들어줘\"}"
curl -X POST http://127.0.0.1:8000/chat -H "Content-Type: application/json" -d "{\"message\":\"메모 목록 보여줘\"}"
curl -X POST http://127.0.0.1:8000/chat -H "Content-Type: application/json" -d "{\"message\":\"1번 메모 삭제해줘\"}"
```
`/chat`으로 만든 메모는 `GET /notes`로도 똑같이 보입니다.
**같은 DB를 사람과 AI가 공유**하기 때문입니다. — 이것이 MCP의 핵심 가치입니다.
### C. LangGraph 의도 파악 (엔티티 → 의도 → 실행)
`/chat`은 Gemini가 도구를 직접 고르지만, `/intent`는 **엔티티를 추출해 의도를 명시적으로
파악**한 뒤 실행합니다. 응답에 인식한 엔티티/의도가 그대로 담깁니다.
```bash
curl -X POST http://127.0.0.1:8000/intent -H "Content-Type: application/json" -d "{\"message\":\"회의록 이라는 제목으로 메모 만들어줘\"}"
# → {"intent":"create","entities":{"action":"create","title":"회의록",...},"reply":"메모를 생성했습니다. (id=1)"}
```
자세한 그래프 구조는 [docs/05](docs/05-랭그래프-의도파악.md) 참고.
---
## 5. 테스트 (e2e)
CRUD 전체를 두 경로로 검증하는 e2e 테스트가 `tests/` 에 있습니다.
각 테스트는 **임시 SQLite 파일**로 격리되어 실제 `notes.db` 를 건드리지 않습니다.
```powershell
pip install -r requirements.txt -r requirements-dev.txt
pytest
```
| 파일 | 검증 대상 |
|---|---|
| `tests/test_notes_api_e2e.py` | 직접 REST CRUD (생성/조회/수정/삭제 + 404·422) |
| `tests/test_mcp_server_e2e.py` | MCP 서버 도구 CRUD (실제 서버를 stdio 로 띄워 호출) |
| `tests/test_intent.py` | LangGraph 의도 분류/응답(결정론적) + 그래프 e2e(SKIP) |
| `tests/test_chat_e2e.py` | AI 경로(FastAPI→Gemini→MCP). 기본 SKIP |
`test_chat_e2e.py` 는 실제 Gemini 를 호출하므로 비용/비결정성 때문에 기본으로 건너뜁니다.
돌리려면 `GEMINI_API_KEY` 설정 후:
```powershell
$env:RUN_CHAT_E2E="1"; pytest tests/test_chat_e2e.py -v
```
---
## 6. 무엇을 배우게 되나요?
- **MCP 서버 만들기**: `@mcp.tool()` 로 함수를 AI가 쓸 수 있는 도구로 노출
- **MCP 클라이언트 연결**: stdio 로 서버 프로세스를 띄우고 세션 연결
- **Gemini 도구 연동**: MCP 도구를 Gemini FunctionDeclaration 으로 변환하고 함수 호출 루프로 실행
- **CRUD 설계**: 같은 데이터 저장소를 REST API 와 AI 도구가 함께 사용
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues