Dewey/API명세서.md

21 KiB

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

응답:

{
  "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 서버를 초기화하고 서버 정보 및 지원 기능을 반환합니다.

요청

{
  "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"
    }
  }
}

응답

{
  "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 요청 후 클라이언트가 보내는 알림입니다. 응답이 필요 없습니다.

요청

{
  "jsonrpc": "2.0",
  "method": "initialized"
}

참고: id 필드가 없으면 알림(notification)으로 처리됩니다.

응답

응답이 필요 없습니다. HTTP 200 OK만 반환됩니다.


3. tools/list

사용 가능한 모든 도구 목록을 반환합니다.

요청

{
  "jsonrpc": "2.0",
  "id": "2",
  "method": "tools/list"
}

응답

{
  "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

특정 도구를 실행합니다. 도구 실행 결과에 따라 응답 내용이 달라집니다.

요청 형식

{
  "jsonrpc": "2.0",
  "id": "3",
  "method": "tools/call",
  "params": {
    "arguments": {
      "name": "도구명",
      "arguments": {
        "도구별_파라미터": "값"
      }
    }
  }
}

응답 형식

{
  "jsonrpc": "2.0",
  "id": "3",
  "result": {
    "isError": false,
    "content": "실행 결과 메시지",
    "parts": [],
    "metadata": {
      "tool": "도구명",
      "timestamp": 1701234567890
    }
  }
}

응답 DTO: McpToolCallResponse

도구별 사용 예시

store_memory

메모리를 Redis에 임시 저장합니다.

요청:

{
  "jsonrpc": "2.0",
  "id": "4",
  "method": "tools/call",
  "params": {
    "arguments": {
      "name": "store_memory",
      "arguments": {
        "memory_text": "오늘 사용자가 피자를 주문했습니다",
        "user_id": "user123",
        "importance": 4
      }
    }
  }
}

성공 응답:

{
  "jsonrpc": "2.0",
  "id": "4",
  "result": {
    "isError": false,
    "content": "Memory stored successfully",
    "parts": [],
    "metadata": {
      "tool": "store_memory",
      "timestamp": 1701234567890
    }
  }
}

비즈니스 로직: MemoryService.storeTemporaryMemory() 호출


retrieve_memory

사용자의 영구 메모리를 PostgreSQL에서 조회합니다.

요청:

{
  "jsonrpc": "2.0",
  "id": "5",
  "method": "tools/call",
  "params": {
    "arguments": {
      "name": "retrieve_memory",
      "arguments": {
        "user_id": "user123",
        "limit": 10
      }
    }
  }
}

성공 응답:

{
  "jsonrpc": "2.0",
  "id": "5",
  "result": {
    "isError": false,
    "content": "Retrieved 10 memories",
    "parts": [],
    "metadata": {
      "tool": "retrieve_memory",
      "timestamp": 1701234567890
    }
  }
}

비즈니스 로직: MemoryService.getPermanentMemories() 호출


search_memory

벡터 유사도 기반으로 메모리를 검색합니다.

요청:

{
  "jsonrpc": "2.0",
  "id": "6",
  "method": "tools/call",
  "params": {
    "arguments": {
      "name": "search_memory",
      "arguments": {
        "query": "커피",
        "user_id": "user123",
        "limit": 5
      }
    }
  }
}

성공 응답:

{
  "jsonrpc": "2.0",
  "id": "6",
  "result": {
    "isError": false,
    "content": "Found 3 matching memories",
    "parts": [],
    "metadata": {
      "tool": "search_memory",
      "timestamp": 1701234567890
    }
  }
}

비즈니스 로직: MemoryService.searchMemoriesByVector() 호출

에러 응답 예시:

{
  "jsonrpc": "2.0",
  "id": "6",
  "result": {
    "isError": true,
    "content": "Error: Unknown tool: invalid_tool",
    "parts": [],
    "metadata": {
      "tool": "invalid_tool",
      "timestamp": 1701234567890
    }
  }
}

5. resources/list

사용 가능한 모든 리소스 목록을 반환합니다.

요청

{
  "jsonrpc": "2.0",
  "id": "7",
  "method": "resources/list"
}

응답

{
  "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

특정 리소스의 내용을 읽습니다.

요청

{
  "jsonrpc": "2.0",
  "id": "8",
  "method": "resources/read",
  "params": {
    "uri": "memory://recent"
  }
}

또는

{
  "jsonrpc": "2.0",
  "id": "8",
  "method": "resources/read",
  "params": {
    "arguments": {
      "uri": "memory://recent"
    }
  }
}

응답

{
  "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

사용 가능한 모든 프롬프트 목록을 반환합니다.

요청

{
  "jsonrpc": "2.0",
  "id": "9",
  "method": "prompts/list"
}

응답

{
  "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

특정 프롬프트를 가져옵니다.

요청

{
  "jsonrpc": "2.0",
  "id": "10",
  "method": "prompts/get",
  "params": {
    "arguments": {
      "name": "memory_summary",
      "arguments": {
        "memories": ["메모리1", "메모리2", "메모리3"]
      }
    }
  }
}

응답

{
  "jsonrpc": "2.0",
  "id": "10",
  "result": {
    "name": "memory_summary",
    "messages": [
      {
        "role": "user",
        "content": "Prompt: memory_summary"
      }
    ],
    "arguments": {
      "memories": ["메모리1", "메모리2", "메모리3"]
    }
  }
}

응답 DTO: McpPromptResponse.PromptGetResponse


에러 응답

에러가 발생한 경우 다음과 같은 형식으로 응답됩니다:

{
  "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 예시

헬스 체크

curl -X GET http://localhost:8080/mcp

initialize

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

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

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

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

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에 저장되는 영구 메모리 엔티티입니다.

@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에 저장되는 임시 메모리 모델입니다.

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)

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 파싱)
  • capabilitiessupported 필드 추가
  • JSON-RPC 2.0 표준 준수 (정상 응답에 error 필드 제외)
  • 요청/응답 전체 로깅 기능 추가
  • Redis 연동 (임시 메모리 저장/조회 완료, TTL 3일)
  • 벡터 검색 구현 (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     # 예외 처리