2025-08-26 01:31:42 +09:00
|
|
|
# GPT-Edit MCP Server - 설계 및 개발 가이드
|
2025-08-25 00:39:39 +09:00
|
|
|
|
|
|
|
|
## 📋 프로젝트 개요
|
2025-08-26 01:31:42 +09:00
|
|
|
GPT-Edit는 OpenAI의 이미지 편집 API를 MCP(Model Context Protocol) 서버로 구현한 프로젝트입니다.
|
2025-08-25 00:39:39 +09:00
|
|
|
이 문서는 향후 유사한 MCP 서버 개발 시 참고할 수 있는 설계 원칙과 구현 가이드를 제공합니다.
|
|
|
|
|
|
|
|
|
|
## 🏗️ 핵심 설계 원칙
|
|
|
|
|
|
|
|
|
|
### 1. 파일 구조 단순화
|
|
|
|
|
**✅ 단일 디렉토리 구조 채택:**
|
|
|
|
|
```
|
|
|
|
|
generated_images/
|
|
|
|
|
├── gptimage1_123456_20250824_143022_000.png # 입력 파일
|
|
|
|
|
├── gptimage1_123456_20250824_143022_001.png # 첫 번째 출력
|
|
|
|
|
├── gptimage1_123456_20250824_143022_001.json # 첫 번째 출력 파라미터
|
|
|
|
|
├── gptimage1_123456_20250824_143022_mask.png # 마스크 파일 (필요시)
|
|
|
|
|
└── ...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. 파일 명명 규칙
|
|
|
|
|
#### Base Name 형식
|
|
|
|
|
`gptimage1_{seed}_{yyyymmdd}_{hhmmss}`
|
|
|
|
|
- `gptimage1`: 고정 prefix (환경변수로 변경 가능)
|
|
|
|
|
- `{seed}`: 6자리 랜덤 시드 (세션 단위로 유지)
|
|
|
|
|
- `{yyyymmdd}`: 날짜 (예: 20250824)
|
|
|
|
|
- `{hhmmss}`: 시간 (예: 143022)
|
|
|
|
|
|
|
|
|
|
#### 파일 타입별 명명
|
|
|
|
|
- **입력 파일**: `{base_name}_000.{ext}` (000은 항상 입력을 의미)
|
|
|
|
|
- **출력 파일**: `{base_name}_001.png`, `{base_name}_002.png`, ...
|
|
|
|
|
- **파라미터 파일**: `{base_name}_001.json`, `{base_name}_002.json`, ...
|
|
|
|
|
- **마스크 파일**: `{base_name}_mask.{ext}`
|
|
|
|
|
|
|
|
|
|
### 3. 세션 기반 시드 관리
|
|
|
|
|
```python
|
|
|
|
|
class ToolHandlers:
|
|
|
|
|
def __init__(self, config):
|
|
|
|
|
self.current_seed = None # 세션 시드
|
|
|
|
|
|
|
|
|
|
def _get_or_create_seed(self):
|
|
|
|
|
if self.current_seed is None:
|
|
|
|
|
self.current_seed = random.randint(0, 999999)
|
|
|
|
|
return self.current_seed
|
|
|
|
|
|
|
|
|
|
def _reset_seed(self):
|
|
|
|
|
self.current_seed = None # 작업 완료 후 리셋
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## ⚠️ API 수정 시 필수 체크리스트
|
|
|
|
|
|
|
|
|
|
### 함수명이나 파라미터 변경 시 반드시 확인할 파일들
|
|
|
|
|
MCP 서버는 Claude와 통신하는 양방향 시스템입니다. **한쪽만 수정하면 작동하지 않습니다!**
|
|
|
|
|
|
|
|
|
|
#### 1. 서버 측 (MCP Server)
|
|
|
|
|
- `src/server/models.py` - 도구 정의 (Tool definitions)
|
|
|
|
|
- `src/server/handlers.py` - 도구 실행 로직 (Handler implementations)
|
|
|
|
|
- `src/server/mcp_server.py` - 도구 등록 및 라우팅 (Tool registration)
|
|
|
|
|
|
|
|
|
|
#### 2. 커넥터 측 (API Connector)
|
|
|
|
|
- `src/connector/openai_client.py` - API 호출 로직
|
|
|
|
|
- `src/connector/config.py` - 설정 및 파라미터 관리
|
|
|
|
|
- `src/connector/models.py` - 데이터 모델 정의 (있는 경우)
|
|
|
|
|
|
|
|
|
|
#### 3. 유틸리티
|
|
|
|
|
- `src/utils/` - 공통 함수들 (파라미터 타입 변경 시)
|
|
|
|
|
|
|
|
|
|
#### 4. 문서
|
|
|
|
|
- `README.md` - 사용 예제
|
|
|
|
|
- `TECHNICAL_SPECS.md` - API 스펙
|
|
|
|
|
- `MCP_CONNECTOR_GUIDE.md` - 연동 가이드
|
|
|
|
|
|
|
|
|
|
### 수정 순서 (중요!)
|
|
|
|
|
|
|
|
|
|
1. **모델 정의 수정** (`models.py`)
|
|
|
|
|
```python
|
|
|
|
|
# 예: edit_simple → edit_image
|
|
|
|
|
def get_edit_image_tool() -> Tool:
|
|
|
|
|
return Tool(
|
|
|
|
|
name="edit_image", # 이름 변경
|
|
|
|
|
inputSchema={
|
|
|
|
|
"properties": {
|
|
|
|
|
"input_image_b64": {...} # 파라미터 변경
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. **핸들러 수정** (`handlers.py`)
|
|
|
|
|
```python
|
|
|
|
|
async def handle_edit_image(self, arguments):
|
|
|
|
|
# 새 파라미터 처리 로직
|
|
|
|
|
if 'input_image_b64' not in arguments: # 변경된 파라미터
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
3. **서버 라우팅 수정** (`mcp_server.py`)
|
|
|
|
|
```python
|
|
|
|
|
if name == "edit_image": # 변경된 이름
|
|
|
|
|
return await self.handlers.handle_edit_image(arguments)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
4. **테스트 실행**
|
|
|
|
|
```bash
|
|
|
|
|
# 단독 테스트
|
|
|
|
|
python tests/test_server.py
|
|
|
|
|
|
|
|
|
|
# Claude 연동 테스트
|
|
|
|
|
python main.py
|
|
|
|
|
# Claude Desktop에서 도구 호출 테스트
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 자주 발생하는 실수
|
|
|
|
|
|
|
|
|
|
❌ **하지 말아야 할 것:**
|
|
|
|
|
- 한쪽만 수정하고 테스트
|
|
|
|
|
- 프롬프트 이름만 바꾸고 핸들러는 그대로 둠
|
|
|
|
|
- 파라미터 이름 변경 시 validation 로직 미수정
|
|
|
|
|
|
|
|
|
|
✅ **반드시 해야 할 것:**
|
|
|
|
|
- 모든 관련 파일 동시 수정
|
|
|
|
|
- 변경 후 즉시 테스트
|
|
|
|
|
- 문서 업데이트
|
|
|
|
|
|
|
|
|
|
### 실제 수정 예시: image_path → input_image_b64
|
|
|
|
|
|
|
|
|
|
#### 변경 이유
|
|
|
|
|
Claude가 업로드된 이미지를 바로 처리할 수 있도록 Base64 입력으로 변경
|
|
|
|
|
|
|
|
|
|
#### 수정한 파일들
|
|
|
|
|
|
|
|
|
|
1. **`src/server/models.py`** - 도구 정의
|
|
|
|
|
```python
|
|
|
|
|
# Before
|
|
|
|
|
"image_path": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"description": "Path to the image file"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# After
|
|
|
|
|
"input_image_b64": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"description": "Base64 encoded input image data"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. **`src/server/handlers.py`** - 핸들러 로직
|
|
|
|
|
```python
|
|
|
|
|
# Before
|
|
|
|
|
image_path = arguments.get('image_path')
|
|
|
|
|
if not image_path or not Path(image_path).exists():
|
|
|
|
|
return [TextContent(text="Image not found")]
|
|
|
|
|
|
|
|
|
|
# After
|
|
|
|
|
if 'input_image_b64' not in arguments:
|
|
|
|
|
return [TextContent(text="input_image_b64 is required")]
|
|
|
|
|
|
|
|
|
|
# Save b64 to temp file
|
|
|
|
|
image_path = self._save_b64_to_temp_file(
|
|
|
|
|
arguments['input_image_b64'],
|
|
|
|
|
base_name,
|
|
|
|
|
0,
|
|
|
|
|
"input"
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
3. **`src/utils/image_utils.py`** - 새 함수 추가
|
|
|
|
|
```python
|
|
|
|
|
def decode_image_base64(base64_str: str) -> bytes:
|
|
|
|
|
"""Decode base64 string to image data"""
|
|
|
|
|
return base64.b64decode(base64_str)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 디버깅 팁
|
|
|
|
|
|
|
|
|
|
#### 에러 발생 시 확인 순서
|
|
|
|
|
|
|
|
|
|
1. **로그 파일 확인**
|
|
|
|
|
```bash
|
2025-08-26 01:31:42 +09:00
|
|
|
tail -f gpt-edit.log
|
2025-08-25 00:39:39 +09:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. **Claude Desktop 에러 메시지**
|
|
|
|
|
- "Method not found" → 함수명 불일치
|
|
|
|
|
- "Invalid arguments" → 파라미터 문제
|
|
|
|
|
- "Server disconnected" → Python 크래시
|
|
|
|
|
|
|
|
|
|
3. **단계별 테스트**
|
|
|
|
|
```python
|
|
|
|
|
# tests/test_api_change.py
|
|
|
|
|
async def test_new_parameter():
|
|
|
|
|
handlers = ToolHandlers(config)
|
|
|
|
|
result = await handlers.handle_edit_image({
|
|
|
|
|
"input_image_b64": test_b64,
|
|
|
|
|
"prompt": "test"
|
|
|
|
|
})
|
|
|
|
|
assert result[0].type == "text"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
4. **JSON-RPC 통신 확인**
|
|
|
|
|
```python
|
|
|
|
|
# 로깅 추가로 통신 내용 확인
|
|
|
|
|
logger.debug(f"Received: {json.dumps(request, indent=2)}")
|
|
|
|
|
logger.debug(f"Sending: {json.dumps(response, indent=2)}")
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### API 변경 테스트 체크리스트
|
|
|
|
|
|
|
|
|
|
☑️ **1단계: 단독 테스트**
|
|
|
|
|
```bash
|
|
|
|
|
# Python 직접 테스트
|
|
|
|
|
python -c "from src.server.models import MCPToolDefinitions; print([t.name for t in MCPToolDefinitions.get_all_tools()])"
|
|
|
|
|
# 결과: ['edit_image', 'edit_with_mask', ...] 확인
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
☑️ **2단계: 서버 실행 테스트**
|
|
|
|
|
```bash
|
|
|
|
|
python main.py
|
|
|
|
|
# 로그에 "Tool called: edit_image" 확인
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
☑️ **3단계: Claude Desktop 테스트**
|
|
|
|
|
1. Claude Desktop 재시작
|
|
|
|
|
2. "사용 가능한 도구가 무엇인가요?" 질문
|
|
|
|
|
3. 변경된 도구명 확인
|
|
|
|
|
4. 실제 호출 테스트
|
|
|
|
|
|
|
|
|
|
☑️ **4단계: 파라미터 테스트**
|
|
|
|
|
```python
|
|
|
|
|
# tests/test_parameters.py
|
|
|
|
|
import asyncio
|
|
|
|
|
from src.server.handlers import ToolHandlers
|
|
|
|
|
from src.connector import Config
|
|
|
|
|
|
|
|
|
|
async def test():
|
|
|
|
|
handlers = ToolHandlers(Config())
|
|
|
|
|
# 새 파라미터로 테스트
|
|
|
|
|
result = await handlers.handle_edit_image({
|
|
|
|
|
"input_image_b64": "base64_data_here",
|
|
|
|
|
"prompt": "test prompt"
|
|
|
|
|
})
|
|
|
|
|
print(result)
|
|
|
|
|
|
|
|
|
|
asyncio.run(test())
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 🔧 MCP 서버 구현 체크리스트
|
|
|
|
|
|
|
|
|
|
### 필수 MCP 메서드
|
|
|
|
|
MCP 서버는 다음 메서드들을 반드시 구현해야 합니다:
|
|
|
|
|
|
|
|
|
|
1. **`list_tools()`** - 사용 가능한 도구 목록 반환
|
|
|
|
|
2. **`call_tool()`** - 도구 실행
|
|
|
|
|
3. **`list_prompts()`** - 프롬프트 템플릿 목록 반환
|
|
|
|
|
4. **`get_prompt()`** - 특정 프롬프트 템플릿 반환
|
|
|
|
|
5. **`list_resources()`** - 리소스 목록 반환 (없으면 빈 리스트)
|
|
|
|
|
|
|
|
|
|
### 로깅 설정
|
|
|
|
|
```python
|
|
|
|
|
# stdout과 충돌 방지를 위해 stderr 사용
|
|
|
|
|
logging.basicConfig(
|
|
|
|
|
level=logging.INFO,
|
|
|
|
|
handlers=[
|
2025-08-26 01:31:42 +09:00
|
|
|
logging.FileHandler('gpt-edit.log', encoding='utf-8'),
|
2025-08-25 00:39:39 +09:00
|
|
|
logging.StreamHandler(sys.stderr) # stderr 사용!
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 📁 프로젝트 구조
|
|
|
|
|
|
|
|
|
|
```
|
2025-08-26 01:31:42 +09:00
|
|
|
gpt-edit/
|
2025-08-25 00:39:39 +09:00
|
|
|
├── src/
|
|
|
|
|
│ ├── connector/ # API 연결 모듈
|
|
|
|
|
│ │ ├── config.py # 설정 관리 및 파일명 생성
|
|
|
|
|
│ │ └── openai_client.py
|
|
|
|
|
│ ├── server/ # MCP 서버
|
|
|
|
|
│ │ ├── mcp_server.py # MCP 핸들러 등록
|
|
|
|
|
│ │ ├── handlers.py # 도구 구현
|
|
|
|
|
│ │ └── models.py # 도구 정의
|
|
|
|
|
│ └── utils/ # 유틸리티
|
|
|
|
|
├── generated_images/ # 모든 이미지 저장 (단일 디렉토리)
|
|
|
|
|
├── temp/ # 임시 파일
|
|
|
|
|
├── .env # 환경 변수
|
|
|
|
|
└── main.py # 진입점
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 🔑 환경 변수 설정
|
|
|
|
|
|
|
|
|
|
`.env` 파일 예시:
|
|
|
|
|
```bash
|
|
|
|
|
# API 설정
|
|
|
|
|
OPENAI_API_KEY=sk-xxxxx
|
|
|
|
|
OPENAI_ORGANIZATION=org-xxxxx # 선택사항
|
|
|
|
|
|
|
|
|
|
# 서버 설정
|
|
|
|
|
LOG_LEVEL=INFO
|
|
|
|
|
MAX_IMAGE_SIZE_MB=4
|
|
|
|
|
DEFAULT_TIMEOUT=30
|
|
|
|
|
|
|
|
|
|
# 파일 명명 설정
|
|
|
|
|
OUTPUT_FILENAME_PREFIX=gptimage1 # 파일명 prefix
|
|
|
|
|
|
|
|
|
|
# 기능 플래그
|
|
|
|
|
ENABLE_AUTO_OPTIMIZE=true
|
|
|
|
|
SAVE_ORIGINALS=true
|
|
|
|
|
SAVE_PARAMETERS=true
|
|
|
|
|
|
|
|
|
|
# 경로 설정 (기본값 사용 권장)
|
|
|
|
|
# GENERATED_IMAGES_PATH=./generated_images
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 🎯 Base64 입력 지원 (2025-01-17 업데이트)
|
|
|
|
|
|
|
|
|
|
### 변경사항
|
|
|
|
|
Claude와의 통합을 개선하기 위해 `image_path` 대신 `image_b64`를 사용하도록 변경했습니다.
|
|
|
|
|
|
|
|
|
|
#### 이전 방식 (image_path 사용)
|
|
|
|
|
```python
|
|
|
|
|
{
|
|
|
|
|
"image_path": "/path/to/image.png",
|
|
|
|
|
"prompt": "edit the image"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 현재 방식 (input_image_b64 사용)
|
|
|
|
|
```python
|
|
|
|
|
{
|
|
|
|
|
"input_image_b64": "base64_encoded_string_here", # PNG, JPEG, WebP 등 지원
|
|
|
|
|
"prompt": "edit the image",
|
|
|
|
|
"background": "transparent", # 선택사항: "transparent" 또는 "opaque"
|
|
|
|
|
"save_to_file": true # 선택사항: 파일로 저장 여부
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 장점
|
|
|
|
|
1. **즉시 사용 가능**: Claude가 업로드된 이미지를 바로 처리
|
|
|
|
|
2. **경로 문제 해결**: 파일 시스템 경로 찾기 불필요
|
|
|
|
|
3. **직접 통합**: Claude의 이미지 인식과 직접 연동
|
|
|
|
|
|
|
|
|
|
### 구현 패턴
|
|
|
|
|
```python
|
|
|
|
|
def _save_b64_to_temp_file(self, b64_data: str, base_name: str, index: int, file_type: str = "input") -> str:
|
|
|
|
|
"""Base64 데이터를 파일로 저장 (다양한 포맷 지원)"""
|
|
|
|
|
# 1. Base64 디코딩
|
|
|
|
|
image_data = decode_image_base64(b64_data)
|
|
|
|
|
|
|
|
|
|
# 2. 이미지 포맷 자동 감지
|
|
|
|
|
with Image.open(io.BytesIO(image_data)) as img:
|
|
|
|
|
format_ext = img.format.lower() # PNG, JPEG, WEBP 등 감지
|
|
|
|
|
|
|
|
|
|
# 마스크는 PNG로 변환 (OpenAI API 요구사항)
|
|
|
|
|
if file_type == "mask" and format_ext != 'png':
|
|
|
|
|
buffer = io.BytesIO()
|
|
|
|
|
img.save(buffer, format='PNG')
|
|
|
|
|
image_data = buffer.getvalue()
|
|
|
|
|
format_ext = 'png'
|
|
|
|
|
|
|
|
|
|
# 3. 파일명 생성 (원본 포맷 유지)
|
|
|
|
|
if file_type == "mask":
|
|
|
|
|
filename = f"{base_name}_mask.{format_ext}"
|
|
|
|
|
else:
|
|
|
|
|
filename = f"{base_name}_{index:03d}.{format_ext}"
|
|
|
|
|
|
|
|
|
|
# 4. generated_images에 저장
|
|
|
|
|
file_path = self.config.generated_images_path / filename
|
|
|
|
|
save_image(image_data, str(file_path))
|
|
|
|
|
|
|
|
|
|
return str(file_path)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 워크플로우
|
|
|
|
|
1. Claude가 이미지를 base64로 전송 (원본 포맷 유지: PNG, JPEG, WebP 등)
|
|
|
|
|
2. 포맷을 자동 감지하여 `generated_images`에 저장 (입력 추적용)
|
|
|
|
|
3. 저장된 파일 경로로 OpenAI API 호출
|
|
|
|
|
4. 결과를 저장하고 base64로 반환
|
|
|
|
|
|
|
|
|
|
### 지원 이미지 포맷
|
|
|
|
|
- **입력 이미지**: PNG, JPEG, WebP, GIF, BMP, TIFF
|
|
|
|
|
- **마스크 이미지**: PNG (다른 포맷은 자동 변환)
|
|
|
|
|
- **출력 이미지**: PNG (OpenAI API 기본값)
|
|
|
|
|
|
|
|
|
|
## 🛠️ 구현 패턴
|
|
|
|
|
|
|
|
|
|
### 1. Base Name 생성
|
|
|
|
|
```python
|
|
|
|
|
def generate_base_name(seed: Optional[int] = None) -> str:
|
|
|
|
|
"""gptimage1_{seed}_{yyyymmdd}_{hhmmss} 형식으로 생성"""
|
|
|
|
|
if seed is None:
|
|
|
|
|
seed = random.randint(0, 999999)
|
|
|
|
|
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
date_str = now.strftime("%Y%m%d")
|
|
|
|
|
time_str = now.strftime("%H%M%S")
|
|
|
|
|
|
|
|
|
|
return f"gptimage1_{seed}_{date_str}_{time_str}"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. 파일 저장 패턴
|
|
|
|
|
```python
|
|
|
|
|
# Base name 생성
|
|
|
|
|
seed = self._get_or_create_seed()
|
|
|
|
|
base_name = self.config.generate_base_name(seed)
|
|
|
|
|
|
|
|
|
|
# 입력 파일 저장
|
|
|
|
|
input_path = self.config.get_output_path(base_name, 0, 'png')
|
|
|
|
|
shutil.copy2(original_image, input_path)
|
|
|
|
|
|
|
|
|
|
# 출력 파일 저장
|
|
|
|
|
output_path = self.config.get_output_path(base_name, 1, 'png')
|
|
|
|
|
save_image(edited_image, output_path)
|
|
|
|
|
|
|
|
|
|
# 파라미터 저장
|
|
|
|
|
json_path = self.config.get_output_path(base_name, 1, 'json')
|
|
|
|
|
with open(json_path, 'w') as f:
|
|
|
|
|
json.dump(params, f, indent=2)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. 작업 플로우
|
|
|
|
|
```python
|
|
|
|
|
async def handle_edit_image(self, arguments):
|
|
|
|
|
try:
|
|
|
|
|
# 1. 시드 생성/가져오기
|
|
|
|
|
seed = self._get_or_create_seed()
|
|
|
|
|
base_name = self.config.generate_base_name(seed)
|
|
|
|
|
|
|
|
|
|
# 2. 입력 파일 저장 (000)
|
|
|
|
|
input_path = self.config.get_output_path(base_name, 0, ext)
|
|
|
|
|
|
|
|
|
|
# 3. 처리 실행
|
|
|
|
|
response = await self.client.edit_image(request)
|
|
|
|
|
|
|
|
|
|
# 4. 출력 파일 저장 (001)
|
|
|
|
|
output_path = self.config.get_output_path(base_name, 1, 'png')
|
|
|
|
|
|
|
|
|
|
# 5. 파라미터 저장 (001.json)
|
|
|
|
|
json_path = self.config.get_output_path(base_name, 1, 'json')
|
|
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
# 6. 세션 종료 시 시드 리셋
|
|
|
|
|
self._reset_seed()
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 📝 저장되는 파라미터 구조
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"base_name": "gptimage1_123456_20250824_143022",
|
|
|
|
|
"seed": 123456,
|
|
|
|
|
"timestamp": "2025-08-24T14:30:22.123456",
|
|
|
|
|
"prompt": "make the image more colorful",
|
|
|
|
|
"background": "transparent",
|
|
|
|
|
"input_image": "generated_images/gptimage1_123456_20250824_143022_000.png",
|
|
|
|
|
"input_size": [1024, 768],
|
|
|
|
|
"output_size": [1024, 1024],
|
|
|
|
|
"execution_time": 3.45,
|
|
|
|
|
"optimization": {
|
|
|
|
|
"optimized": true,
|
|
|
|
|
"original_size_mb": 5.2,
|
|
|
|
|
"final_size_mb": 3.8,
|
|
|
|
|
"format_used": "PNG"
|
|
|
|
|
},
|
|
|
|
|
"token_stats": {
|
|
|
|
|
"estimated_tokens": 45,
|
|
|
|
|
"token_limit": 1000,
|
|
|
|
|
"usage_percentage": 4.5
|
|
|
|
|
},
|
|
|
|
|
"config": {
|
|
|
|
|
"model": "gpt-image-1",
|
|
|
|
|
"quality": "high",
|
|
|
|
|
"api_version": "gpt-image-1"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 🚀 실행 및 테스트
|
|
|
|
|
|
|
|
|
|
### 서버 실행
|
|
|
|
|
```bash
|
|
|
|
|
python main.py
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Claude Desktop 설정
|
|
|
|
|
`claude_desktop_config.json`:
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"mcpServers": {
|
2025-08-26 01:31:42 +09:00
|
|
|
"gpt-edit": {
|
2025-08-25 00:39:39 +09:00
|
|
|
"command": "python",
|
2025-08-26 01:31:42 +09:00
|
|
|
"args": ["D:/Project/gpt-edit/main.py"]
|
2025-08-25 00:39:39 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## ⚠️ 일반적인 문제 해결
|
|
|
|
|
|
|
|
|
|
### 1. "Method not found" 에러
|
|
|
|
|
- `list_prompts()`, `list_resources()` 메서드 구현 확인
|
2025-08-26 01:31:42 +09:00
|
|
|
- MCP 서버 이름이 단순한지 확인 (예: "gpt-edit")
|
2025-08-25 00:39:39 +09:00
|
|
|
|
|
|
|
|
### 2. JSON 파싱 에러
|
|
|
|
|
- 로그가 stdout으로 출력되지 않도록 stderr 사용
|
|
|
|
|
- print() 대신 logger 사용
|
|
|
|
|
|
|
|
|
|
### 3. 파일 명명 충돌
|
|
|
|
|
- 타임스탬프와 시드 조합으로 유니크성 보장
|
|
|
|
|
- 같은 세션 내에서는 동일 시드 사용
|
|
|
|
|
|
|
|
|
|
## 📚 핵심 교훈
|
|
|
|
|
|
|
|
|
|
1. **일관된 명명**: `prefix_seed_date_time_number` 패턴 고수
|
|
|
|
|
2. **세션 관리**: 한 작업 세션 동안 동일 시드 유지
|
|
|
|
|
3. **단순한 구조**: 모든 파일을 한 디렉토리에 저장
|
|
|
|
|
4. **추적 가능성**: 파일명만으로 언제, 어떤 작업인지 파악 가능
|
|
|
|
|
5. **자동화**: Base name 생성과 파일 경로 관리 자동화
|
|
|
|
|
|
|
|
|
|
## 🔄 다중 편집 시나리오
|
|
|
|
|
|
|
|
|
|
### 같은 이미지를 여러 번 편집
|
|
|
|
|
```
|
|
|
|
|
# 첫 번째 편집 (seed: 123456)
|
|
|
|
|
gptimage1_123456_20250824_143022_000.png # 원본
|
|
|
|
|
gptimage1_123456_20250824_143022_001.png # 첫 편집 결과
|
|
|
|
|
|
|
|
|
|
# 두 번째 편집 (새 seed: 789012)
|
|
|
|
|
gptimage1_789012_20250824_143055_000.png # 원본 (복사)
|
|
|
|
|
gptimage1_789012_20250824_143055_001.png # 두 번째 편집 결과
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 배치 편집
|
|
|
|
|
```
|
|
|
|
|
# 배치 작업 (seed: 456789)
|
|
|
|
|
gptimage1_456789_20250824_144512_001.png # 첫 번째 이미지 결과
|
|
|
|
|
gptimage1_456789_20250824_144512_002.png # 두 번째 이미지 결과
|
|
|
|
|
gptimage1_456789_20250824_144512_003.png # 세 번째 이미지 결과
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## ⚠️ API 변경 시 놓치기 쉬운 부분들
|
|
|
|
|
|
|
|
|
|
### 1. 프롬프트 정의 (list_prompts)
|
|
|
|
|
함수명 변경 시 `mcp_server.py`의 프롬프트 정의도 함께 수정해야 합니다:
|
|
|
|
|
```python
|
|
|
|
|
@self.server.list_prompts()
|
|
|
|
|
async def handle_list_prompts():
|
|
|
|
|
prompts = [
|
|
|
|
|
Prompt(
|
|
|
|
|
name="edit_image", # ← 이것도 변경 필요!
|
|
|
|
|
description="Edit an image with AI",
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. 배치 처리 (batch_edit)
|
|
|
|
|
`batch_edit`는 여전히 `image_path`를 사용할 수 있습니다.
|
|
|
|
|
두 함수의 일관성을 유지하려면 함께 수정해야 합니다.
|
|
|
|
|
|
|
|
|
|
### 3. 유틸리티 함수
|
|
|
|
|
파라미터 타입 변경 시 관련 유틸리티 함수들도 확인:
|
|
|
|
|
- `validation.py`: 파라미터 검증 함수
|
|
|
|
|
- `image_utils.py`: 이미지 처리 함수
|
|
|
|
|
- `token_utils.py`: 토큰 계산 함수
|
|
|
|
|
|
|
|
|
|
### 4. 테스트 파일
|
|
|
|
|
`tests/` 폴더의 모든 테스트 파일들도 업데이트:
|
|
|
|
|
- 예시 데이터
|
|
|
|
|
- 함수 호출
|
|
|
|
|
- 기대값 검증
|
|
|
|
|
|
|
|
|
|
### 5. 에러 메시지
|
|
|
|
|
에러 메시지에서도 변경된 파라미터명 사용:
|
|
|
|
|
```python
|
|
|
|
|
# Before
|
|
|
|
|
return [TextContent(text="image_path is required")]
|
|
|
|
|
|
|
|
|
|
# After
|
|
|
|
|
return [TextContent(text="input_image_b64 is required")]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 6. 로깅 메시지
|
|
|
|
|
로깅에서도 일관성 유지:
|
|
|
|
|
```python
|
|
|
|
|
logger.info(f"Processing edit_image with prompt: {prompt}")
|
|
|
|
|
# 함수명이 로그에도 반영되어야 함
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 7. 문서의 코드 예시
|
|
|
|
|
문서에 있는 코드 예시들도 모두 업데이트:
|
|
|
|
|
- README.md의 사용 예시
|
|
|
|
|
- TECHNICAL_SPECS.md의 API 스펙
|
|
|
|
|
- CLAUDE.md의 구현 패턴
|
|
|
|
|
|
|
|
|
|
### 8. 통합 테스트 체크리스트
|
|
|
|
|
|
|
|
|
|
API 변경 후 반드시 실행할 통합 테스트:
|
|
|
|
|
|
|
|
|
|
☑️ **MCP 서버 테스트**
|
|
|
|
|
- [ ] `python main.py` 실행 확인
|
|
|
|
|
- [ ] 로그에 에러 없는지 확인
|
|
|
|
|
- [ ] Ctrl+C로 정상 종료 확인
|
|
|
|
|
|
|
|
|
|
☑️ **Claude Desktop 테스트**
|
|
|
|
|
- [ ] Claude Desktop 완전 종료 후 재시작
|
|
|
|
|
- [ ] "사용 가능한 도구가 무엇인가요?" 질문
|
|
|
|
|
- [ ] 변경된 도구명 표시 확인
|
|
|
|
|
- [ ] 실제 이미지 업로드 테스트
|
|
|
|
|
- [ ] 결과 파일 생성 확인
|
|
|
|
|
|
|
|
|
|
☑️ **파일 시스템 테스트**
|
|
|
|
|
- [ ] `generated_images/` 폴더에 파일 생성 확인
|
|
|
|
|
- [ ] 파일명 형식 확인 (base_name 패턴)
|
|
|
|
|
- [ ] JSON 파라미터 파일 저장 확인
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
이 가이드를 따르면 일관되고 추적 가능한 파일 구조를 가진 MCP 서버를 개발할 수 있습니다.
|
|
|
|
|
|
|
|
|
|
**💡 기억하세요: MCP 서버는 양방향 통신입니다. 한쪽만 수정하면 작동하지 않습니다!**
|