# Dewey MCP 서버 API 명세서 ## 개요 Dewey MCP (Model Context Protocol) 서버는 JSON-RPC 2.0 프로토콜을 기반으로 AI 장기기억 시스템의 기능을 제공합니다. - **프로토콜 버전**: 2025-06-18 (클라이언트가 보낸 버전 사용, 기본값: 2024-11-05) - **서버 이름**: Dewey Memory Server - **서버 버전**: 0.0.1-SNAPSHOT - **Base URL**: `http://localhost:8080/mcp` - **아키텍처**: MVC (Model-View-Controller) 패턴 --- ## 아키텍처 ### MVC 구조 ``` Controller (McpController) ↓ API Service (McpService) ↓ Domain Service (MemoryService) ↓ Repository (MemoryRepository) ↓ Database (PostgreSQL / Redis) ``` ### 계층별 역할 - **Controller**: HTTP 요청 처리 및 라우팅 - **API Service**: Controller와 Domain Service 사이의 어댑터 - **Domain Service**: 비즈니스 로직 처리 - **Repository**: 데이터 접근 계층 - **Model**: 도메인 엔티티 및 DTO --- ## 엔드포인트 ### GET /mcp 서버 상태를 확인하는 헬스 체크 엔드포인트입니다. **요청:** ``` GET http://localhost:8080/mcp ``` **응답:** ```json { "status": "ok", "service": "Dewey MCP Server", "version": "0.0.1-SNAPSHOT", "protocol": "JSON-RPC 2.0", "endpoint": "/mcp" } ``` --- ### OPTIONS /mcp CORS preflight 요청을 처리하는 엔드포인트입니다. **요청:** ``` OPTIONS http://localhost:8080/mcp ``` **응답:** ``` HTTP 200 OK ``` --- ### POST /mcp 모든 MCP 요청은 이 단일 엔드포인트로 처리됩니다. 요청 본문은 JSON-RPC 2.0 형식을 따릅니다. **요청 헤더:** ``` Content-Type: application/json ``` --- ## MCP 메서드 ### 1. initialize MCP 서버를 초기화하고 서버 정보 및 지원 기능을 반환합니다. #### 요청 ```json { "jsonrpc": "2.0", "id": "1", "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": { "tools": true, "prompts": true, "resources": true, "logging": false }, "clientInfo": { "name": "cursor-vscode", "version": "1.0.0" } } } ``` #### 응답 ```json { "jsonrpc": "2.0", "id": "1", "result": { "protocolVersion": "2025-06-18", "capabilities": { "tools": { "listChanged": true, "supported": true }, "resources": { "listChanged": false, "supported": true }, "prompts": { "listChanged": false, "supported": false } }, "serverInfo": { "name": "Dewey Memory Server", "version": "0.0.1-SNAPSHOT" } } } ``` **참고:** - `protocolVersion`: 클라이언트가 보낸 버전을 그대로 반환합니다 (호환성) - `capabilities.tools.listChanged`: `true`로 설정하여 Cursor가 `tools/list`를 호출하도록 유도합니다 - `capabilities.tools.supported`: `true`로 설정 (도구 기능 지원) - `capabilities.resources.listChanged`: `false`로 설정 (리소스 목록 변경 없음) - `capabilities.resources.supported`: `true`로 설정 (리소스 기능 지원) - `capabilities.prompts.listChanged`: `false`로 설정 (프롬프트 목록 변경 없음) - `capabilities.prompts.supported`: `false`로 설정 (프롬프트 기능 미지원) **응답 DTO**: `McpInitializeResponse` **참고:** `initialize` 요청 후 클라이언트는 `initialized` 알림을 보낼 수 있습니다. 이 알림은 응답이 필요 없습니다 (notification). --- ### 2. initialized (알림) `initialize` 요청 후 클라이언트가 보내는 알림입니다. 응답이 필요 없습니다. #### 요청 ```json { "jsonrpc": "2.0", "method": "initialized" } ``` **참고:** `id` 필드가 없으면 알림(notification)으로 처리됩니다. #### 응답 응답이 필요 없습니다. HTTP 200 OK만 반환됩니다. --- ### 3. tools/list 사용 가능한 모든 도구 목록을 반환합니다. #### 요청 ```json { "jsonrpc": "2.0", "id": "2", "method": "tools/list" } ``` #### 응답 ```json { "jsonrpc": "2.0", "id": "2", "result": { "tools": [ { "name": "store_memory", "description": "메모리를 Redis에 임시 저장합니다", "inputSchema": { "type": "object", "properties": { "memory_text": { "type": "string", "description": "저장할 메모리 텍스트" }, "user_id": { "type": "string", "description": "사용자 ID" }, "importance": { "type": "integer", "description": "중요도 (1-5)", "minimum": 1, "maximum": 5 } }, "required": ["memory_text", "user_id"] } }, { "name": "retrieve_memory", "description": "사용자의 메모리를 조회합니다", "inputSchema": { "type": "object", "properties": { "user_id": { "type": "string", "description": "사용자 ID" }, "limit": { "type": "integer", "description": "조회할 메모리 개수", "default": 10 } }, "required": ["user_id"] } }, { "name": "search_memory", "description": "벡터 유사도 기반으로 메모리를 검색합니다", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "검색 쿼리" }, "user_id": { "type": "string", "description": "사용자 ID (선택)" }, "limit": { "type": "integer", "description": "검색 결과 개수", "default": 5 } }, "required": ["query"] } } ] } } ``` **응답 DTO**: `McpToolResponse.ToolListResponse` **참고:** `initialize` 응답에서 `capabilities.tools.listChanged: true`로 설정하면 클라이언트가 이 메서드를 호출합니다. --- ### 4. tools/call 특정 도구를 실행합니다. 도구 실행 결과에 따라 응답 내용이 달라집니다. #### 요청 형식 ```json { "jsonrpc": "2.0", "id": "3", "method": "tools/call", "params": { "arguments": { "name": "도구명", "arguments": { "도구별_파라미터": "값" } } } } ``` #### 응답 형식 ```json { "jsonrpc": "2.0", "id": "3", "result": { "isError": false, "content": "실행 결과 메시지", "parts": [], "metadata": { "tool": "도구명", "timestamp": 1701234567890 } } } ``` **응답 DTO**: `McpToolCallResponse` #### 도구별 사용 예시 ##### store_memory 메모리를 Redis에 임시 저장합니다. **요청:** ```json { "jsonrpc": "2.0", "id": "4", "method": "tools/call", "params": { "arguments": { "name": "store_memory", "arguments": { "memory_text": "오늘 사용자가 피자를 주문했습니다", "user_id": "user123", "importance": 4 } } } } ``` **성공 응답:** ```json { "jsonrpc": "2.0", "id": "4", "result": { "isError": false, "content": "Memory stored successfully", "parts": [], "metadata": { "tool": "store_memory", "timestamp": 1701234567890 } } } ``` **비즈니스 로직**: `MemoryService.storeTemporaryMemory()` 호출 --- ##### retrieve_memory 사용자의 영구 메모리를 PostgreSQL에서 조회합니다. **요청:** ```json { "jsonrpc": "2.0", "id": "5", "method": "tools/call", "params": { "arguments": { "name": "retrieve_memory", "arguments": { "user_id": "user123", "limit": 10 } } } } ``` **성공 응답:** ```json { "jsonrpc": "2.0", "id": "5", "result": { "isError": false, "content": "Retrieved 10 memories", "parts": [], "metadata": { "tool": "retrieve_memory", "timestamp": 1701234567890 } } } ``` **비즈니스 로직**: `MemoryService.getPermanentMemories()` 호출 --- ##### search_memory 벡터 유사도 기반으로 메모리를 검색합니다. **요청:** ```json { "jsonrpc": "2.0", "id": "6", "method": "tools/call", "params": { "arguments": { "name": "search_memory", "arguments": { "query": "커피", "user_id": "user123", "limit": 5 } } } } ``` **성공 응답:** ```json { "jsonrpc": "2.0", "id": "6", "result": { "isError": false, "content": "Found 3 matching memories", "parts": [], "metadata": { "tool": "search_memory", "timestamp": 1701234567890 } } } ``` **비즈니스 로직**: `MemoryService.searchMemoriesByVector()` 호출 **에러 응답 예시:** ```json { "jsonrpc": "2.0", "id": "6", "result": { "isError": true, "content": "Error: Unknown tool: invalid_tool", "parts": [], "metadata": { "tool": "invalid_tool", "timestamp": 1701234567890 } } } ``` --- ### 5. resources/list 사용 가능한 모든 리소스 목록을 반환합니다. #### 요청 ```json { "jsonrpc": "2.0", "id": "7", "method": "resources/list" } ``` #### 응답 ```json { "jsonrpc": "2.0", "id": "7", "result": { "resources": [ { "uri": "memory://recent", "name": "Recent Memories", "description": "최근 저장된 메모리 리소스", "mimeType": "application/json" }, { "uri": "memory://long-term", "name": "Long-term Memories", "description": "장기 저장된 메모리 리소스", "mimeType": "application/json" } ] } } ``` **응답 DTO**: `McpResourceResponse.ResourceListResponse` --- ### 6. resources/read 특정 리소스의 내용을 읽습니다. #### 요청 ```json { "jsonrpc": "2.0", "id": "8", "method": "resources/read", "params": { "uri": "memory://recent" } } ``` 또는 ```json { "jsonrpc": "2.0", "id": "8", "method": "resources/read", "params": { "arguments": { "uri": "memory://recent" } } } ``` #### 응답 ```json { "jsonrpc": "2.0", "id": "8", "result": { "contents": [ { "uri": "memory://recent", "mimeType": "application/json", "text": "{\"message\": \"Resource content for memory://recent\"}" } ] } } ``` **응답 DTO**: `McpResourceResponse.ResourceReadResponse` **참고:** MCP 프로토콜에 따라 `contents` 필드는 배열이며 필수입니다. 각 요소는 `uri`, `mimeType`, `text` (또는 바이너리 리소스의 경우 `blob`)를 포함합니다. --- ### 7. prompts/list 사용 가능한 모든 프롬프트 목록을 반환합니다. #### 요청 ```json { "jsonrpc": "2.0", "id": "9", "method": "prompts/list" } ``` #### 응답 ```json { "jsonrpc": "2.0", "id": "9", "result": { "prompts": [ { "name": "memory_summary", "description": "메모리 목록을 요약하는 프롬프트", "arguments": [ { "name": "memories", "description": "요약할 메모리 텍스트 목록", "required": true } ] }, { "name": "memory_search", "description": "메모리 검색을 위한 프롬프트", "arguments": [ { "name": "query", "description": "검색할 키워드", "required": true } ] } ] } } ``` **응답 DTO**: `McpPromptResponse.PromptListResponse` --- ### 8. prompts/get 특정 프롬프트를 가져옵니다. #### 요청 ```json { "jsonrpc": "2.0", "id": "10", "method": "prompts/get", "params": { "arguments": { "name": "memory_summary", "arguments": { "memories": ["메모리1", "메모리2", "메모리3"] } } } } ``` #### 응답 ```json { "jsonrpc": "2.0", "id": "10", "result": { "name": "memory_summary", "messages": [ { "role": "user", "content": "Prompt: memory_summary" } ], "arguments": { "memories": ["메모리1", "메모리2", "메모리3"] } } } ``` **응답 DTO**: `McpPromptResponse.PromptGetResponse` --- ## 에러 응답 에러가 발생한 경우 다음과 같은 형식으로 응답됩니다: ```json { "jsonrpc": "2.0", "id": "1", "error": { "code": -32603, "message": "Internal error", "data": null } } ``` **참고:** - 정상 응답일 때는 `error` 필드가 포함되지 않습니다 (JSON-RPC 2.0 표준) - 에러 응답일 때는 `result` 필드가 포함되지 않습니다 - `@JsonInclude(JsonInclude.Include.NON_NULL)` 어노테이션으로 null 값 필드는 자동 제외됩니다 ### 에러 코드 | 코드 | 의미 | 설명 | |------|------|------| | `-32600` | Invalid Request | 요청 형식이 잘못됨 | | `-32601` | Method not found | 존재하지 않는 메서드 호출 | | `-32602` | Invalid params | 파라미터가 잘못됨 | | `-32603` | Internal error | 서버 내부 오류 | | `-32700` | Parse error | JSON 파싱 오류 | **에러 처리**: `GlobalExceptionHandler`에서 전역 예외 처리 **추가 예외 처리:** - `HttpRequestMethodNotSupportedException`: 지원되지 않는 HTTP 메서드 (405 Method Not Allowed) - `HttpMessageNotReadableException`: JSON 파싱 오류 (400 Bad Request, JSON-RPC 2.0 에러 형식으로 반환) --- ## 사용 예시 ### cURL 예시 #### 헬스 체크 ```bash curl -X GET http://localhost:8080/mcp ``` #### initialize ```bash curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "1", "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": { "tools": true, "prompts": true, "resources": true }, "clientInfo": { "name": "test-client", "version": "1.0.0" } } }' ``` #### tools/list ```bash curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "2", "method": "tools/list" }' ``` #### tools/call - store_memory ```bash curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "3", "method": "tools/call", "params": { "arguments": { "name": "store_memory", "arguments": { "memory_text": "사용자는 파이썬을 좋아합니다", "user_id": "user123", "importance": 4 } } } }' ``` #### tools/call - retrieve_memory ```bash curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "4", "method": "tools/call", "params": { "arguments": { "name": "retrieve_memory", "arguments": { "user_id": "user123", "limit": 10 } } } }' ``` #### tools/call - search_memory ```bash curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "5", "method": "tools/call", "params": { "arguments": { "name": "search_memory", "arguments": { "query": "커피", "user_id": "user123", "limit": 5 } } } }' ``` --- ## 데이터 모델 ### Memory (도메인 엔티티) PostgreSQL에 저장되는 영구 메모리 엔티티입니다. ```java @Entity @Table(name = "memories") public class Memory { private Long id; private String userId; private String memoryText; private Integer importance; // 1~5 private String tags; // JSON 형식 private LocalDateTime createdAt; private LocalDateTime updatedAt; } ``` ### TemporaryMemory (도메인 모델) Redis에 저장되는 임시 메모리 모델입니다. ```java public class TemporaryMemory { private String id; // Redis key private String userId; private String memoryText; private Integer importance; private LocalDateTime createdAt; private LocalDateTime expiresAt; // TTL 기반 } ``` ### MemoryResponse (응답 DTO) ```java public class MemoryResponse { private Long id; private String userId; private String memoryText; private Integer importance; private String tags; private LocalDateTime createdAt; private LocalDateTime updatedAt; } ``` --- ## 참고사항 ### 1. 요청/응답 형식 - 모든 요청은 `Content-Type: application/json` 헤더가 필요합니다. - `id` 필드는 요청과 응답을 매칭하기 위해 사용됩니다. - 모든 응답은 `jsonrpc: "2.0"` 필드를 포함합니다. ### 2. 아키텍처 - **MVC 패턴**을 따르며, 각 계층이 명확히 분리되어 있습니다. - **Controller**는 요청 처리만 담당합니다. - **API Service**는 Controller와 Domain Service 사이의 어댑터 역할을 합니다. - **Domain Service**는 비즈니스 로직을 처리합니다. - **Repository**는 데이터 접근만 담당합니다. ### 3. 데이터 저장 - **임시 메모리**: Redis에 저장 (TTL 3일) - **영구 메모리**: PostgreSQL에 저장 - **벡터 검색**: PostgreSQL의 pgvector 확장 사용 (향후 구현) ### 4. CORS 설정 - CORS는 모든 origin에서 허용되도록 설정되어 있습니다 (`/mcp/**`). - 허용된 HTTP 메서드: `GET`, `POST`, `OPTIONS` - 허용된 헤더: 모든 헤더 (`*`) - 프로덕션 환경에서는 특정 origin만 허용하도록 변경하는 것을 권장합니다. ### 5. 예외 처리 - 전역 예외 처리는 `GlobalExceptionHandler`에서 수행됩니다. - 유효성 검증 오류는 `MethodArgumentNotValidException`으로 처리됩니다. - 모든 예외는 JSON-RPC 2.0 에러 형식으로 변환됩니다. - `HttpRequestMethodNotSupportedException`: 405 Method Not Allowed 응답 반환 - `HttpMessageNotReadableException`: JSON 파싱 오류를 JSON-RPC 2.0 에러 형식으로 변환하여 반환 ### 6. 로깅 - 모든 요청과 응답이 상세하게 로깅됩니다. - 요청 본문은 JSON 포맷으로 로깅됩니다 (`=== MCP 요청 수신 ===`). - 응답 본문은 JSON 포맷으로 로깅됩니다 (`=== MCP 응답 전송 ===`). - 에러 응답도 별도로 로깅됩니다 (`=== MCP 에러 응답 전송 ===`). - 헬스 체크와 OPTIONS 요청도 로깅됩니다. ### 6. 구현 상태 - ✅ 기본 MCP 프로토콜 구현 완료 - ✅ 도메인 모델 및 Repository 생성 완료 - ✅ MVC 구조 재설계 완료 - ✅ 헬스 체크 엔드포인트 추가 (GET /mcp) - ✅ CORS preflight 처리 (OPTIONS /mcp) - ✅ initialized 알림 처리 - ✅ 예외 처리 개선 (HTTP 메서드, JSON 파싱) - ✅ `capabilities`에 `supported` 필드 추가 - ✅ JSON-RPC 2.0 표준 준수 (정상 응답에 error 필드 제외) - ✅ 요청/응답 전체 로깅 기능 추가 - ⏳ Redis 연동 (TODO) - ⏳ 벡터 검색 구현 (TODO) - ⏳ 배치 처리 구현 (TODO) ### 7. Cursor IDE 연동 - ✅ Cursor IDE에서 HTTP 기반 MCP 서버로 연결 성공 - ✅ `mcp.json` 설정 파일에서 `url` 필드로 서버 주소 지정 - ✅ `tools/list` 요청 정상 처리 확인 - ✅ `capabilities.supported` 필드로 기능 지원 여부 명시 --- ## 버전 정보 - **문서 버전**: 2.2.0 - **최종 업데이트**: 2025-12-10 - **서버 버전**: 0.0.1-SNAPSHOT - **아키텍처**: MVC (Model-View-Controller) - **프로토콜 버전**: 2025-06-18 (클라이언트 호환) - **상태**: ✅ Cursor IDE 연동 성공 --- ## 관련 파일 구조 ``` src/main/java/com/pandol365/dewey/ ├── api/ │ ├── controller/ │ │ └── McpController.java # Controller 계층 │ ├── dto/ │ │ ├── request/ # 요청 DTO │ │ │ ├── McpJsonRpcRequest.java │ │ │ ├── MemoryStoreRequest.java │ │ │ └── MemorySearchRequest.java │ │ └── response/ # 응답 DTO (View) │ │ ├── McpJsonRpcResponse.java │ │ ├── McpInitializeResponse.java │ │ ├── McpToolResponse.java │ │ ├── McpResourceResponse.java │ │ ├── McpPromptResponse.java │ │ ├── McpToolCallResponse.java │ │ └── MemoryResponse.java │ └── service/ # API Service 계층 │ ├── McpService.java │ └── impl/ │ └── McpServiceImpl.java ├── domain/ │ └── memory/ │ ├── model/ # 도메인 모델 (Model) │ │ ├── Memory.java │ │ └── TemporaryMemory.java │ ├── repository/ # Repository 계층 │ │ └── MemoryRepository.java │ └── service/ # Domain Service 계층 │ ├── MemoryService.java │ └── impl/ │ └── MemoryServiceImpl.java └── exception/ └── GlobalExceptionHandler.java # 예외 처리 ```