read me 업데이트

This commit is contained in:
jade
2026-08-18 12:50:09 +09:00
parent 7f0dfee0f5
commit 1adbe5a2c7

137
README.md
View File

@@ -2,7 +2,7 @@
신한라이프 업무 기능을 MCP(Model Context Protocol) Tool로 제공하는 Java 멀티 모듈 프로젝트입니다. 각 업무 모듈은 독립 실행 가능한 Spring Boot 애플리케이션이며, MCP Streamable HTTP와 REST 실행 API를 함께 제공합니다.
이 저장소에는 Gateway 애플리케이션이 포함되어 있지 않습니다. Tool Pod는 설정된 외부 Gateway에 Tool 록을 시도하지만, Tool 조회와 직접 실행은 각 Pod 자체적으로 처리합니다.
이 저장소에는 DAPMS(Gateway) 애플리케이션이 포함되어 있지 않습니다. DAPMT는 Gateway로 Tool을 push 등록하지 않으며, 각 Pod가 `GET /tool-manifest`를 제공하면 DAPMS가 이 Manifest를 pull하여 Tool 록을 구성합니다. Tool 조회와 직접 실행은 각 Pod에서도 자체적으로 처리합니다.
## 기술 기준
@@ -42,8 +42,9 @@ Tool Pod
│ └─ Spring Bean의 @McpTool 메서드 탐색 및 실행 메서드 캐시
├─ ToolRegistryHeartbeatSender
│ ├─ Tool 메타데이터 생성
─ tool-definitions YAML 병합
│ └─ 외부 Gateway 등록 시도
─ tool-definitions YAML 병합
├─ ToolManifestService
│ └─ DAPMS가 pull할 bundle 단위 Manifest 생성
├─ McpToolExecutionService
│ ├─ 입력 Schema 검증
│ ├─ 요청 DTO 변환 및 Tool 호출
@@ -59,6 +60,23 @@ Tool Pod
4. `ToolPodMcpToolSynchronizer`가 Tool을 MCP SDK 서버에 등록합니다.
5. REST와 MCP 요청은 공통 `McpToolExecutionService`를 통해 실행됩니다.
DAPMT 내부에는 `/registry/register`, `/registry/deregister` 호출이나 주기적인 Gateway heartbeat 전송이 없습니다. `ToolRegistryHeartbeatSender`라는 클래스명은 호환성을 위해 남아 있지만 현재 역할은 로컬 Tool 스캔과 메타데이터 생성뿐입니다.
### DAPMS 연동 방식
```text
DAPMS
└─ GET {DAPMT Pod URL}/tool-manifest
└─ bundleId + revision + tools[]
└─ 각 Tool endpoint: {Pod URL}/mcp/{toolName}
```
- `mcp.manifest.bundle-id`는 Manifest를 제공하는 Pod의 고유 식별자이며 필수입니다.
- 현재 값은 `was-sal`, `was-cus`, `was-pro`, `was-sys`입니다.
- DAPMS에 설정한 bundle ID와 DAPMT가 반환하는 `bundleId`가 일치해야 같은 Tool bundle로 관리됩니다.
- `mcp.manifest.name-prefix`가 비어 있지 않으면 모든 Tool 이름이 해당 prefix로 시작해야 합니다.
- Manifest 내용이 바뀌면 `revision`이 증가하며, `If-None-Match`가 일치하면 `304 Not Modified`를 반환합니다.
## 제공 API
각 업무 Pod가 동일한 API 구조를 제공합니다.
@@ -79,8 +97,12 @@ Tool Pod
```powershell
$headers = @{
'trace-id' = 'trace-local-001'
'request-id' = 'request-local-001'
'X-Tool-Server-API-Key' = $env:TOOL_SERVER_API_KEY
'guid' = 'guid-local-001'
'x-request-id' = 'request-local-001'
'employee-no' = '100001'
'virtual-employee-no' = 'V100001'
'mcp-session-id' = 'session-local-001'
}
Invoke-RestMethod `
@@ -91,6 +113,21 @@ Invoke-RestMethod `
-Body '{"environment":"개발"}'
```
### 요청 헤더 계약
DAPMS가 DAPMT Tool Service를 호출할 때 사용하는 헤더는 다음과 같습니다. HTTP 헤더 이름은 대소문자를 구분하지 않지만, 문서와 구현에서는 아래 표기를 기준으로 사용합니다.
| 헤더 | 필수 여부 | 용도 | 전달 동작 |
|---|---|---|---|
| `X-Tool-Server-API-Key` | 인증 설정 시 필수 | DAPMS와 DAPMT 사이의 Tool Server 인증 | `mcp.security.api-key` 또는 `api-keys`와 비교 |
| `guid` | 선택 | 업무 호출 상관관계 식별자 | 실행 로그, 성공 응답, 하위 HTTP 호출로 전달 |
| `x-request-id` | 선택 | 요청 추적 식별자 | 실행 로그, 성공 응답, 하위 HTTP 호출로 전달 |
| `employee-no` | 선택 | 실제 사용자 사번 | 하위 HTTP 호출로 전달 |
| `virtual-employee-no` | 선택 | 가상 사용자 사번 | 하위 HTTP 호출로 전달 |
| `mcp-session-id` | 선택 | MCP 세션 식별자 | 성공 응답과 하위 HTTP 호출로 전달 |
REST 경로 `/mcp/{toolName}`은 Controller가 위 헤더를 직접 읽습니다. MCP Streamable HTTP 경로 `/mcp``McpRequestHeaderFilter`가 동일한 헤더를 `McpRequestHeaderContext`에 저장한 뒤 Tool 실행과 하위 HTTP 호출에 전달합니다. 헤더가 없는 하위 HTTP 호출에는 `X-ANONYMOUS-REQ: AXHUB-TOOL`이 설정됩니다.
주요 실행 응답은 다음과 같습니다.
| HTTP 상태 | 코드 | 의미 |
@@ -150,6 +187,8 @@ V17 정의의 주요 필수 항목은 다음과 같습니다.
입력·출력 Schema는 `ToolSchemaResolver`가 어노테이션의 Schema 리소스와 인라인 Schema, DTO에서 생성한 Schema를 해석합니다. `ToolRegistryHeartbeatSender`는 여기에 YAML의 `parameters_schema``output_schema`를 병합하여 최종 메타데이터를 만듭니다.
실행 시 Schema 검증은 MCP Java SDK의 `DefaultJsonSchemaValidator`를 사용하며 JSON Schema 2020-12 기준으로 처리합니다. 입력 불일치는 `422 INVALID_PARAM`, 출력 불일치는 `500 INVALID_TOOL_RESPONSE`로 반환됩니다. 단, Schema 해석 또는 검증기 자체에서 예외가 발생하면 현재 구현은 오류를 로그에 기록하고 해당 검증을 건너뜁니다.
## 로컬 실행
### 사전 조건
@@ -162,8 +201,11 @@ V17 정의의 주요 필수 항목은 다음과 같습니다.
```powershell
$env:SPRING_PROFILES_ACTIVE = 'local'
$env:TOOL_SERVER_API_KEY = 'tool-server-key'
```
`TOOL_SERVER_API_KEY`를 지정하지 않으면 현재 개발 기본값인 `tool-server-key`가 사용됩니다. 운영 환경에서는 기본값을 사용하지 말고 DAPMS의 Tool Server API Key와 동일한 별도 Secret을 주입해야 합니다.
각 Pod는 별도 터미널에서 실행합니다.
```powershell
@@ -198,13 +240,50 @@ http://localhost:8086/swagger-ui/index.html
|---|---|---|
| `SPRING_PROFILES_ACTIVE` | Spring 활성 프로필 | `local` |
| `PORT` | Pod 수신 포트 | 모듈별 기본 포트 |
| `AXHUB_TOOL_URL` | Manifest와 Gateway 등록에 사용할 Pod 외부 URL | `http://localhost:${server.port}` |
| `AXHUB_GATEWAY_URL` | 외부 Gateway URL | `http://localhost:8081` |
| `TOOL_SERVER_API_KEY` | DAPMS가 `X-Tool-Server-API-Key`로 전달할 공통 인증 Key | `tool-server-key` |
| `AXHUB_TOOL_URL` | Manifest의 Tool endpoint 생성에 사용할 Pod 외부 URL | `http://localhost:${server.port}` |
| `AXHUB_GATEWAY_URL` | 프로필 및 Compose 호환용 Gateway URL. 현재 DAPMT의 push 등록에는 사용하지 않음 | `http://localhost:8081` |
| `SPRING_DATA_REDIS_HOST` | Redis 호스트 | `localhost` |
| `SPRING_DATA_REDIS_PORT` | Redis 포트 | `6379` |
| `GLOW_COMMUNICATION_MCI_HOST` | MCI 대상 호스트 | 프로필별 설정 |
| `GLOW_COMMUNICATION_MCI_PORT` | MCI 대상 포트 | 프로필별 설정 |
`mcp.security.api-key`는 단일 DAPMS 공통 Key를, `mcp.security.api-keys``API Key → tenant ID` 형태의 다중 Key를 지원합니다. 둘 중 하나라도 설정되어 있으면 올바른 `X-Tool-Server-API-Key`가 없는 `/rpc/**`, `/mcp/**` 요청은 `401 Unauthorized`가 됩니다. 두 설정이 모두 비어 있을 때만 익명 요청을 허용합니다. 현재 네 업무 Pod의 기본 `application.yml`은 단일 Key를 설정하므로 API Key 없이 Tool을 호출할 수 없습니다.
## Scaffold
공통 라이브러리는 새 Tool과 새 Tool Pod를 만드는 두 개의 Java CLI를 제공합니다. 두 클래스의 `main` 메서드를 IDE에서 실행하거나 필요한 인자를 전달해 실행할 수 있습니다.
| Scaffolder | 역할 | 주요 생성·수정 대상 |
|---|---|---|
| `ToolScaffolder` | 기존 Pod에 Tool 구현 추가 | UseCase, DTO, Converter, Mock 응답, V17 Tool YAML, 연동 설정 |
| `PodScaffolder` | 새 실행 Pod 모듈 추가 | 모듈 디렉터리, `build.gradle`, Dockerfile, Application 클래스, 프로필 설정, `settings.gradle`, `docker-compose.yml` |
`PodScaffolder`의 인자 순서는 다음과 같습니다.
```text
PodScaffolder <module-name> <port> [author] [yyyy.MM.dd]
```
예를 들어 `payment 8099`를 입력하면 `dap-was-payment` 모듈을 생성합니다. 생성되는 `application.yml`에는 다음 계약이 포함됩니다.
```yaml
mcp:
manifest:
bundle-id: dap-was-payment
name-prefix: ""
security:
api-key: ${TOOL_SERVER_API_KEY:tool-server-key}
```
생성되는 Compose 서비스에도 `TOOL_SERVER_API_KEY=${TOOL_SERVER_API_KEY:-tool-server-key}`가 추가됩니다. 생성 후에는 다음 항목을 반드시 확인해야 합니다.
1. `bundle-id`를 DAPMS에 등록할 bundle ID와 일치시킵니다.
2. 운영 환경의 `TOOL_SERVER_API_KEY`를 DAPMS가 전달하는 Key와 동일한 Secret으로 설정합니다.
3. 실제 배포 주소에 맞게 `AXHUB_TOOL_URL`을 설정합니다.
4. MCI·EAI·HTTP 연동 대상과 timeout을 환경별 설정으로 교체합니다.
5. `validateMcpToolNames`, `validateToolSchemaV17`, 전체 테스트를 실행합니다.
## 테스트와 검증
```powershell
@@ -235,19 +314,15 @@ http://localhost:8086/swagger-ui/index.html
2026-08-18 기준 확인 결과입니다.
- 전체 운영 소스 `classes`: 성공
- 전체 `test`: 성공
- 총 94개 테스트 성공, 실패·오류·건너뜀 0개
- `validateMcpToolNames`: 성공
- `validateToolSchemaV17`: 203개 Tool 성공
- SAL·PRO·SYS 테스트: 20개 성공
- 전체 `test`: 테스트 소스 컴파일 오류로 실패
- LIB의 `ToolScaffolderTest`가 변경 전 `FieldDefinition` 생성자를 사용
- LIB의 `ToolManifestServiceTest` 패키지와 테스트용 생성자 접근 범위가 불일치
전체 빌드의 기준을 회복하려면 위 테스트 소스 회귀를 먼저 정리해야 합니다.
- 실행 명령: `.\gradlew.bat test validateMcpToolNames validateToolSchemaV17`
## Docker Compose
Compose는 네 업무 Pod 정의합니다.
`docker-compose.yml`은 DAPMT의 네 업무 Pod 정의합니다.
| 서비스 | 컨테이너 포트 | 호스트 포트 |
|---|---:|---:|
@@ -263,19 +338,36 @@ Compose는 네 업무 Pod를 정의합니다.
docker compose up --build
```
주의: `docker-compose.yml``http://gateway:8081`을 Gateway 주소로 사용하지만 이 저장소의 Compose에는 `gateway` 서비스가 없습니다. 동일 Docker 네트워크에 Gateway를 제공하거나 `AXHUB_GATEWAY_URL`을 실제 접근 가능한 주소로 변경해야 합니다.
Compose 파일의 용도와 현재 주의점은 다음과 같습니다.
| 파일 | 용도 | 현재 소스 기준 주의점 |
|---|---|---|
| `docker-compose.yml` | DAPMT 네 Pod 단독 실행 | `gateway` 서비스가 없지만 push 등록이 제거되어 DAPMT 시작에는 필요하지 않음 |
| `docker-compose.local.yml` | DAPMS와 DAPMT의 로컬 통합 구성 | 두 저장소가 같은 상위 디렉터리에 있는 구조를 가정 |
| `docker-compose.prod.yml` | DAPMS와 DAPMT의 개발 프로필 기반 OCI 구성 | 저장소의 runner 등록 토큰을 운영 Secret으로 분리해야 함 |
`docker-compose.local.yml``docker-compose.prod.yml`의 build context는 각각 `./dap-was-dapms`, `./dap-was-dapmt`입니다. 현재 파일 위치에서 사용할 때는 context 기준을 두 저장소의 상위 디렉터리로 맞춰야 합니다.
```powershell
# DAPMT 저장소 디렉터리에서 실행
docker compose --project-directory .. -f docker-compose.local.yml up --build
```
현재 기존 네 Pod의 Compose 정의에는 `TOOL_SERVER_API_KEY` 환경 변수 전달이 없습니다. 따라서 컨테이너는 애플리케이션 기본값 `tool-server-key`를 사용합니다. 운영 배포 전 각 서비스에 Secret 기반 `TOOL_SERVER_API_KEY` 전달 설정을 추가하고 DAPMS의 Key와 일치시켜야 합니다. DAPMS는 각 Pod의 `AXHUB_TOOL_URL` 또는 배포 URL에 접근해 `/tool-manifest`를 pull할 수 있어야 합니다.
## 보안 및 운영 주의사항
현재 구현을 운영 환경에 노출하기 전에 아래 항목을 반드시 점검해야 합니다.
- API Key 인터셉터는 `/rpc/**`, `/mcp/api/v1/**` 적용됩니다. 실제 Tool 실행 경로인 `/mcp/{toolName}`과 MCP 전송 경로 `/mcp`는 현재 검사 대상이 아닙니다.
- `mcp.security.api-keys`가 비어 있으면 인터셉터가 익명 요청을 허용합니다. 현재 기본 설정에는 API Key가 정의되어 있지 않습니다.
- API Key 인터셉터는 `/rpc/**`, `/mcp/**`에 적용됩니다. 따라서 `/mcp`, `/mcp/{toolName}`, `/mcp/api/v1/tools/local`은 인증 대상입니다.
- `/tool-manifest`는 위 인터셉터 경로 밖에 있어 현재 API Key 인증 대상이 아닙니다. 내부망·Ingress 정책 또는 별도 인증이 필요한지 운영 기준을 확인해야 합니다.
- 네 업무 Pod의 기본 Key는 모두 `tool-server-key`입니다. 운영에서는 반드시 별도 Secret으로 교체하고 DAPMS의 `X-Tool-Server-API-Key` 값과 일치시켜야 합니다.
- 설정된 단일 Key와 다중 Key가 모두 없을 때만 익명 요청이 허용됩니다.
- `mcp.security.tenant-domains`는 설정 객체에 바인딩되지만 Tool별 인가에 사용되지 않습니다.
- `requiresApproval`은 메타데이터에만 기록되며 실행 차단이나 승인 확인 로직은 없습니다.
- 입력·출력 Schema 처리 자체에서 예외가 발생하면 현재 실행 서비스는 로그를 남기고 검증을 건너뜁니다.
- CORS는 모든 Origin을 허용하면서 credential도 허용하도록 설정되어 있습니다. 운영 Origin을 명시적으로 제한해야 합니다.
- Gateway 등록은 별도 비관리 스레드에서 Tool별로 최대 12회 재시도합니다. Gateway 장애 시 장시간 실행될 수 있으므로 타임아웃과 종료 정책을 점검해야 합니다.
- `docker-compose.prod.yml`에 runner 등록 토큰이 평문으로 포함되어 있습니다. 사용 중인 토큰은 폐기·재발급하고 배포 Secret으로 이전해야 합니다.
- Tool 요청과 연동 오류 로그에 개인정보나 인증정보가 포함되지 않도록 DTO와 로그 마스킹 정책을 검토해야 합니다.
## 주요 소스 위치
@@ -284,11 +376,13 @@ docker compose up --build
|---|---|
| 공통 빌드 및 검증 작업 | `build.gradle` |
| REST Tool 실행 API | `dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java` |
| 요청 헤더 캡처·전달 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilter.java`, `McpRequestHeaderContext.java` |
| Tool 실행 서비스 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.java` |
| JSON Schema 2020-12 검증 설정 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/ToolSchemaConfiguration.java` |
| 실행 메서드 Registry | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java` |
| MCP SDK 서버 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java` |
| MCP Tool 동기화 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java` |
| Tool 스캔 및 Gateway 등록 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java` |
| Tool 스캔 및 메타데이터 생성 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java` |
| YAML Tool 정의 로딩 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionRepository.java` |
| V17 정의 검증 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionValidator.java` |
| Manifest 생성 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java` |
@@ -302,4 +396,5 @@ docker compose up --build
3. `validateMcpToolNames``validateToolSchemaV17`을 실행합니다.
4. 모듈 테스트와 전체 테스트를 실행합니다.
5. 로컬 Pod에서 `/mcp/api/v1/tools/local``/tool-manifest`를 확인합니다.
6. REST와 MCP 양쪽에서 동일한 Tool 결과와 오류 계약을 확인합니다.
6. DAPMS의 bundle ID, Pod Manifest URL, Tool Server API Key가 DAPMT 설정과 일치하는지 확인합니다.
7. REST와 MCP 양쪽에서 동일한 Tool 결과·요청 헤더 전달·오류 계약을 확인합니다.