docs: README 현행화 및 고아 테스트 제거

This commit is contained in:
jade
2026-08-18 09:09:09 +09:00
parent 113bf0395b
commit 00f800aff9
2 changed files with 217 additions and 212 deletions

397
README.md
View File

@@ -1,53 +1,81 @@
# DAP WAS Tool Pods
# DAP WAS MCP Tool Pods
신한라이프 업무 시스템과 MCP(Model Context Protocol) 클라이언트를 연결하는 독립형 Tool WAS 프로젝트입니다. 이 저장소에는 Gateway가 포함되어 있지 않습니다. 각 Tool Pod가 직접 MCP Streamable HTTP와 REST 실행 API를 제공하고, 업무 요청은 `UseCase → Converter → MCI/EAI 연동`으로 처리합니다.
신한라이프 업무 기능을 MCP(Model Context Protocol) Tool로 제공하는 Java 멀티 모듈 프로젝트입니다. 각 업무 모듈은 독립 실행 가능한 Spring Boot 애플리케이션이며, MCP Streamable HTTP와 REST 실행 API를 함께 제공합니다.
> 이 문서는 현재 `main`의 구현과 설정을 기준으로 합니다. 과거 `dap-gateway`, Chat API, SSE, 외부 Tool Registry/Heartbeat 관련 문서는 현재 저장소의 동작 범위가 아니므로 포함하지 않습니다.
이 저장소에는 Gateway 애플리케이션이 포함되어 있지 않습니다. Tool Pod는 설정된 외부 Gateway에 Tool 등록을 시도하지만, Tool 조회와 직접 실행은 각 Pod가 자체적으로 처리합니다.
## 구성
## 기술 기준
- Java 21
- Gradle Wrapper 8.14.3
- Spring Boot 3.5.11
- MCP Java SDK 2.0.0
- Spring AI Community MCP Annotations 0.9.0
- Jackson 2.20.1
- MapStruct, MyBatis, Redis, Kafka, Resilience4j
- JUnit 5
## 모듈 구성
| 모듈 | 역할 | 기본 포트 | Tool 수 |
|---|---|---:|---:|
| `dap-was-lib` | MCP 서버, Tool 스캔·실행, Schema, Manifest, 보안, MCI/EAI/HTTP 연동 공통 기능 | - | - |
| `dap-was-cus` | 고객·CRM·VOC·웹 콘텐츠 관리 Tool | 8084 | 51 |
| `dap-was-sal` | 영업·청구·인수·동의·현장지원 Tool | 8082 | 52 |
| `dap-was-pro` | 상품·계약·고객·GA 설계사 Tool | 8085 | 50 |
| `dap-was-sys` | IAM·시스템 상태·공지·점검·배포 Tool | 8086 | 50 |
총 203개의 `@McpTool` 선언과 203개의 V17 Tool YAML 정의가 있습니다.
현재 업무 구현은 개발·연동 검증 단계입니다. `dap-was-sal``cmm_claim_search``cmm_memo_retriever`는 각각 MCI와 HTTP Client 흐름을 사용하며, 나머지 Tool은 외부 시스템을 변경하지 않는 모의 응답을 중심으로 구현되어 있습니다.
## 처리 구조
```text
MCP Client
├─ Streamable HTTP: /mcp
└─ REST: POST /mcp/{tool-name}
MCP Client 또는 REST Client
├─ MCP Streamable HTTP: /mcp
└─ REST: POST /mcp/{toolName}
Tool Pod (dap-was-oth 또는 dap-was-sms)
├─ LocalToolScanner: @McpTool / @McpFunction 메타데이터 생성
├─ BusinessToolController: 입력 검증·DTO 변환·동적 실행
├─ ToolManifestController: /tool-manifest 제공
└─ UseCase → Converter → MCI/EAI Client → 대상 시스템
Tool Pod
├─ McpToolMethodRegistry
│ └─ Spring Bean의 @McpTool 메서드 탐색 및 실행 메서드 캐시
├─ ToolRegistryHeartbeatSender
├─ Tool 메타데이터 생성
│ ├─ tool-definitions YAML 병합
│ └─ 외부 Gateway 등록 시도
├─ McpToolExecutionService
│ ├─ 입력 Schema 검증
│ ├─ 요청 DTO 변환 및 Tool 호출
│ └─ 출력 Schema 검증
└─ UseCase → Converter → MCI/EAI/HTTP Client 또는 Mock 응답
```
## Gradle 모듈
애플리케이션 시작 시 다음 순서로 Tool이 준비됩니다.
| 모듈 | 역할 | 기본 포트 |
|---|---|---:|
| `dap-was-lib` | MCP 어노테이션, 스캐너, 실행 Controller, Manifest, Schema, MCI/EAI/로깅/보안 공통 기능 | - |
| `dap-was-oth` | 공통·기타·샘플·SOL 업무 Tool Pod | 8084 |
| `dap-was-sms` | SMS/알림 업무 Pod | 8082 |
1. `ToolDefinitionRepository``classpath*:tool-definitions/**/*.yml`을 읽고 V17 필수 항목을 검증합니다.
2. `ToolRegistryHeartbeatSender``@McpTool` 메서드를 스캔하고 YAML 정의를 병합해 `ToolMetadata`를 생성합니다.
3. `McpToolMethodRegistry`가 실제 호출 가능한 Bean과 메서드를 Tool 이름으로 캐시합니다.
4. `ToolPodMcpToolSynchronizer`가 Tool을 MCP SDK 서버에 등록합니다.
5. REST와 MCP 요청은 공통 `McpToolExecutionService`를 통해 실행됩니다.
기술 기준은 Java 21, Spring Boot 4.0.5, Gradle Wrapper 8.14.3, Spring AI MCP Server WebMVC, Redis, MapStruct, MyBatis, Resilience4j입니다.
## 제공 API
## 현재 제공 API
각 업무 Pod가 동일한 API 구조를 제공합니다.
아래 API는 각 Tool Pod가 직접 제공합니다. OTH Pod의 로컬 주소는 `http://localhost:8084`, SMS Pod는 `http://localhost:8082`입니다.
| 목적 | 메서드 | 경로 |
|---|---|---|
| MCP Streamable HTTP | MCP 프로토콜 | `/mcp` |
| MCP 메시지 전송 | MCP 프로토콜 | `/mcp/message` |
| 로컬 Tool 메타데이터 조회 | `GET` | `/mcp/api/v1/tools/local` |
| Tool 직접 실행 | `POST` | `/mcp/{toolName}` |
| Tool Manifest 조회 | `GET` | `/tool-manifest` |
| 목적 | 메서드 | 경로 | 구현 |
|---|---|---|---|
| MCP Streamable HTTP 전송 | MCP 프로토콜 | `/mcp` | `ToolMcpServerConfiguration` |
| Pod에서 스캔한 Tool 메타데이터 조회 | `GET` | `/mcp/api/v1/tools/local` | `BusinessToolController` |
| 이름으로 Tool 직접 실행 | `POST` | `/mcp/{name}` | `BusinessToolController` |
| Pod 소유 Manifest 조회 | `GET` | `/tool-manifest` | `ToolManifestController` |
`GET /tool-manifest``If-None-Match` 요청 헤더를 지원하며, 내용이 바뀌지 않으면 `304 Not Modified`를 반환합니다. 응답에는 bundle ID, revision, Tool 목록, 입력 Schema, 실행 endpoint, annotation/meta 정보가 포함됩니다. Manifest는 `LocalToolScanner`의 전체 스캔 목록을 사용하므로 `visible = false` 또는 `register = false`인 항목도 포함될 수 있습니다.
`GET /mcp/api/v1/tools/local`은 Manifest 검증이나 MCP 세션을 열지 않고, 현재 Pod에서 스캔한 `ToolMetadata` 목록을 반환합니다.
`GET /tool-manifest``If-None-Match` 요청 헤더를 지원합니다. Manifest가 변경되지 않았으면 `304 Not Modified`를 반환합니다.
### REST 실행 예시
Tool 이름은 `@McpFunction.name` 값입니다. 예를 들어 OTH Pod의 `oth.smp.weather.inquiry`는 다음처럼 호출합니다.
다음은 SYS Pod의 시스템 상태 Tool 호출 예시입니다.
```powershell
$headers = @{
@@ -57,212 +85,221 @@ $headers = @{
Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:8084/mcp/oth.smp.weather.inquiry' `
-Uri 'http://localhost:8086/mcp/iam_system_status' `
-Headers $headers `
-ContentType 'application/json' `
-Body '{"city":"Seoul"}'
-Body '{"environment":"개발"}'
```
Controller는 이름을 찾은 뒤 요청 JSON을 첫 번째 DTO 매개변수로 변환합니다. 입력 Schema 검증 실패는 `422 INVALID_PARAM`, 존재하지 않는 Tool은 `404 TOOL_NOT_FOUND`, 실행 예외는 `502 TOOL_ERROR` 응답입니다. `trace-id``request-id`는 성공 응답 헤더로 다시 전달됩니다.
주요 실행 응답은 다음과 같습니다.
## Tool 검색과 MCP 노출 규칙
| HTTP 상태 | 코드 | 의미 |
|---:|---|---|
| 200 | - | Tool 실행 성공 |
| 404 | `TOOL_NOT_FOUND` | 요청한 Tool 이름이 없음 |
| 422 | `INVALID_PARAM` | 요청이 입력 Schema와 일치하지 않음 |
| 500 | `INVALID_TOOL_RESPONSE` | 결과가 출력 Schema와 일치하지 않음 |
| 502 | `TOOL_ERROR` | Tool 실행 중 예외 발생 |
애플리케이션 기동 시 `LocalToolScanner`는 Spring Bean에서 `@McpTool``@McpFunction` 메타데이터를 읽어 로컬 Tool 목록을 만듭니다. 기동 완료 후 `ToolPodMcpToolSynchronizer`는 이 목록 중 `visible = true`인 Tool만 MCP SDK 서버에 추가합니다.
## Tool 구현 방식
| 속성 | 현재 구현에서의 의미 |
|---|---|
| `visible` | `false`이면 MCP SDK의 Tool 등록에서 제외됩니다. |
| `register` | 스캐너 메타데이터의 `isRegistered` 값과 내부 `registeredTools` 목록에만 반영됩니다. 현재 저장소에는 외부 Registry 전송 구현이 없습니다. |
| `namespace` | 비어 있지 않으면 Tool 이름 앞에 `{namespace}_`가 붙습니다. 기본 설정은 빈 문자열입니다. |
| `enabled` | Manifest의 `_meta.enabled` 값으로 노출됩니다. 현재 synchronizer는 이 값으로 별도 필터링하지 않습니다. |
| `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` | MCP Tool annotation과 Manifest annotation에 반영됩니다. |
`POST /mcp/{name}`의 동적 실행은 `visible``register` 값으로 차단하지 않습니다. 따라서 직접 호출을 막아야 하는 Tool은 네트워크 경계와 별도 인증·인가 정책으로 보호해야 합니다.
### 현재 Tool 선언 현황
`dap-was-oth`에는 실제 `@McpFunction` 선언이 17개 있습니다. 도메인은 다음과 같습니다.
| 도메인 | 예시 Tool 이름 | 내용 |
|---|---|---|
| `cmm` | `oth.cmm.claim.search`, `oth.cmm.customer.detail`, `oth.cmm.meta.table` | 공통·고객·계약·청구·메타 기능 |
| `smp` | `oth.smp.weather.inquiry`, `oth.smp.exchange-rate.inquiry` | 샘플·조회 기능 |
| `sol` | `oth.sol.request.list`, `oth.sol.request.detail` | SOL 요청 조회 |
`dap-was-oth`에는 `categoryKey = "oth"``Onnba3011UseCase`도 있으나, 현재 `@McpFunction` 선언은 없습니다. `dap-was-sms``@McpTool(routingType = "EAI", categoryKey = "notification")`은 선언되어 있지만, `SmsToolUseCase`/구현체에 `@McpFunction`이 없습니다. 따라서 현 상태에서 두 영역은 스캐너, `/tool-manifest`, MCP SDK에 노출되는 호출 가능 Tool을 만들지 않습니다. MCP Tool로 제공하려면 각 계약 메서드에 `@McpFunction`을 선언해야 합니다.
## Tool 개발 방식
Tool 그룹은 인터페이스에 `@McpTool`, Agent가 호출하는 메서드는 `@McpFunction`을 선언합니다. `@McpTool`은 Spring `@Component` 별칭이므로 Tool 인터페이스와 구현체는 Spring Bean으로 구성되어야 합니다.
호출 가능한 메서드는 Spring AI Community의 `@McpTool`로 선언합니다. 프로젝트 고유 실행·표시 정보는 `@GrowToolHint`로 보완합니다.
```java
@McpTool(routingType = "MCI", categoryKey = "claim")
public interface ClaimInquiryUseCase {
@McpFunction(
name = "oth.claim.inquiry.detail",
displayName = "청구 상세 조회",
description = "청구 번호로 청구 상세를 조회합니다.",
mappingId = "CLM00000001",
readOnlyHint = true
)
ClaimInquiryResponse inquire(ClaimInquiryRequest request);
}
@McpTool(
name = "iam_system_status",
title = "시스템 상태 조회",
description = "모의 시스템 상태 정보를 조회합니다.",
annotations = @McpTool.McpAnnotations(openWorldHint = false)
)
@GrowToolHint(
categoryKey = "iam",
mappingId = "DIRECT_IAM_STATUS",
requiresApproval = false
)
SystemStatusResponse getSystemStatus(SystemStatusRequest request);
```
구현체에는 업무 흐름만 두고, Tool DTO와 레거시 인터페이스 DTO의 변환은 Converter에 둡니다.
```java
@Service
@RequiredArgsConstructor
class ClaimInquiryUseCaseImpl implements ClaimInquiryUseCase {
private final ClaimInquiryConverter converter;
private final MciClaimClient client;
@Override
public ClaimInquiryResponse inquire(ClaimInquiryRequest request) {
ClaimMciRequest legacyRequest = converter.toMciRequest(request);
ClaimMciResponse legacyResponse = client.call(legacyRequest);
return converter.toResponse(legacyResponse);
}
}
```
`@McpFunction.name`은 소문자 점 표기 형식으로 작성합니다. 현재 선언은 `oth.cmm.claim.search`, `oth.smp.weather.inquiry`처럼 `{pod}.{domain}.{service}.{action}` 패턴을 사용합니다. `validateMcpToolNames` Gradle 작업은 모든 Tool 모듈을 대상으로 이름 형식과 중복을 검사하며, 패키징 빌드 전에 실행됩니다.
## Input/Output Schema
입력 Schema는 다음 우선순위로 결정됩니다.
1. `inputSchemaResource`에 지정한 classpath JSON Schema
2. `inputSchema`에 인라인으로 지정한 JSON Schema
3. 요청 DTO의 `@McpValidation`을 이용한 자동 생성 Schema
출력 검증은 선택 사항입니다. 다음 중 하나가 있을 때만 반환값을 검증합니다.
1. `outputSchemaResource`
2. `outputSchema`
3. 반환 DTO의 `@McpOutputSchema``@McpValidation`
복잡한 Schema 리소스는 Tool 모듈에 둡니다. 현재 OTH의 청구 검색 예제는 다음 리소스를 사용합니다.
`@McpTool.name`은 다음 형식을 사용합니다.
```text
dap-was-oth/src/main/resources/tool-schemas/cmm/
├─ claim-search-resource-input-schema.json
└─ claim-search-resource-output-schema.json
^[a-z][a-z0-9_]{2,63}$
```
입력 검증에는 JSON Schema Draft 7이 사용됩니다. 출력 Schema 검증에 실패하면 `500 INVALID_TOOL_RESPONSE`을 반환합니다.
예: `cmm_claim_search`, `crm_customer_detail`, `iam_system_status`
이름 중복과 형식은 `validateMcpToolNames`, V17 정의는 `validateToolSchemaV17` Gradle 작업으로 검사합니다. 모든 `bootJar` 작업은 두 검증 작업에 의존합니다.
## Tool YAML 정의
각 Tool은 업무 모듈의 다음 경로에 YAML 정의를 가집니다.
```text
dap-was-*/src/main/resources/tool-definitions/{category}/{tool-name}.yml
```
V17 정의의 주요 필수 항목은 다음과 같습니다.
- `name`, `display_name`, `version`, `category_key`
- `description.function`, `when_to_use`, `when_not_to_use`, `io_limits`
- 3~10개의 `example_queries`
- `read_only`, `destructive`, `idempotent`
- `parameters_schema.type: object`
- `parameters_schema.additionalProperties: false`
- 각 입력 property의 `description`
입력·출력 Schema는 `ToolSchemaResolver`가 어노테이션의 Schema 리소스와 인라인 Schema, DTO에서 생성한 Schema를 해석합니다. `ToolRegistryHeartbeatSender`는 여기에 YAML의 `parameters_schema``output_schema`를 병합하여 최종 메타데이터를 만듭니다.
## 로컬 실행
### 사전 조건
- JDK 21
- Docker (Redis 또는 MCI mock을 사용할 경우)
- Gradle Wrapper 사용 권장
- 프로젝트에 포함된 Gradle Wrapper
- Redis 또는 외부 연동이 필요한 경우 Docker
로컬 프로필은 기본값이며, 두 Pod 모두 H2 메모리 DB와 P6Spy를 설정합니다. Pod URL은 `AXHUB_TOOL_URL` 환경 변수로 설정하며, 지정하지 않으면 해당 `server.port`의 localhost 주소를 사용합니다.
기본 활성 프로필은 `local`입니다. 로컬 프로필은 H2 메모리 DB와 P6Spy를 사용합니다.
```powershell
# 필수: Redis 기동 (캐시 및 세션 처리용)
$env:ACTIVE_PROFILE = 'local'
docker compose up -d redis
# OTH Tool Pod 실행
$env:SPRING_PROFILES_ACTIVE = 'local'
$env:AXHUB_TOOL_URL = 'http://localhost:8084'
.\gradlew.bat :dap-was-oth:bootRun
# SMS Tool Pod 실행 (별도 PowerShell)
$env:SPRING_PROFILES_ACTIVE = 'local'
$env:AXHUB_TOOL_URL = 'http://localhost:8082'
.\gradlew.bat :dap-was-sms:bootRun
```
실행 후 OTH Pod에서 다음 URL로 현재 스캔된 메타데이터와 Manifest를 확인할 수 있습니다.
각 Pod는 별도 터미널에서 실행합니다.
```powershell
# 영업 Tool Pod
$env:AXHUB_TOOL_URL = 'http://localhost:8082'
.\gradlew.bat :dap-was-sal:bootRun
# 고객 Tool Pod
$env:AXHUB_TOOL_URL = 'http://localhost:8084'
.\gradlew.bat :dap-was-cus:bootRun
# 상품 Tool Pod
$env:AXHUB_TOOL_URL = 'http://localhost:8085'
.\gradlew.bat :dap-was-pro:bootRun
# 시스템 Tool Pod
$env:AXHUB_TOOL_URL = 'http://localhost:8086'
.\gradlew.bat :dap-was-sys:bootRun
```
실행 후 SYS Pod 기준 확인 URL은 다음과 같습니다.
```text
http://localhost:8084/mcp/api/v1/tools/local
http://localhost:8084/tool-manifest
http://localhost:8084/tool-test-console.html
http://localhost:8086/mcp/api/v1/tools/local
http://localhost:8086/tool-manifest
http://localhost:8086/swagger-ui/index.html
```
`tool-test-console.html`은 공통 라이브러리의 정적 리소스입니다. `/tool-manifest`에서 Tool과 입력 Schema를 읽어 요청 JSON을 만들고, 현재 Pod의 `/mcp/{toolName}`으로 호출합니다. 저장한 테스트 케이스는 브라우저 `localStorage`에 보관됩니다.
### 주요 환경 변수
## 테스트와 빌드
| 환경 변수 | 설명 | 기본값 |
|---|---|---|
| `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` |
| `SPRING_DATA_REDIS_HOST` | Redis 호스트 | `localhost` |
| `SPRING_DATA_REDIS_PORT` | Redis 포트 | `6379` |
| `GLOW_COMMUNICATION_MCI_HOST` | MCI 대상 호스트 | 프로필별 설정 |
| `GLOW_COMMUNICATION_MCI_PORT` | MCI 대상 포트 | 프로필별 설정 |
## 테스트와 검증
```powershell
# 운영 소스 전체 컴파일
.\gradlew.bat classes
# 전체 테스트
.\gradlew.bat test
# 공통 라이브러리 테스트
# 모듈별 테스트
.\gradlew.bat :dap-was-lib:test
.\gradlew.bat :dap-was-cus:test
.\gradlew.bat :dap-was-sal:test
.\gradlew.bat :dap-was-pro:test
.\gradlew.bat :dap-was-sys:test
# OTH Tool 테스트
.\gradlew.bat :dap-was-oth:test
# Tool 이름 규칙 및 중복 검증
# Tool 이름·중복 검사
.\gradlew.bat validateMcpToolNames
# 패키징 전 전체 빌드
# 203개 Tool V17 정의 검사
.\gradlew.bat validateToolSchemaV17
# 검증, 테스트, 패키징
.\gradlew.bat clean build
```
테스트는 공통 MCP Schema/Manifest/Header 처리, Glow MCI 파서, Tool 이름 검증과 OTH의 청구·SOL·MCI 변환을 다룹니다. SMS 모듈에는 현재 별도 테스트 소스가 없습니다.
### 현재 검증 상태
2026-08-18 기준 확인 결과입니다.
- 전체 운영 소스 `classes`: 성공
- `validateMcpToolNames`: 성공
- `validateToolSchemaV17`: 203개 Tool 성공
- SAL·PRO·SYS 테스트: 20개 성공
- 전체 `test`: 테스트 소스 컴파일 오류로 실패
- LIB의 `ToolScaffolderTest`가 변경 전 `FieldDefinition` 생성자를 사용
- LIB의 `ToolManifestServiceTest` 패키지와 테스트용 생성자 접근 범위가 불일치
전체 빌드의 기준을 회복하려면 위 테스트 소스 회귀를 먼저 정리해야 합니다.
## Docker Compose
현재 Compose 서비스와 호스트 포트는 다음과 같습니다.
Compose는 네 업무 Pod를 정의합니다.
| 서비스 | 컨테이너 포트 | 호스트 포트 |
|---|---:|---:|
| `redis` | 6379 | 6379 |
| `was-sms` | 8082 | 8282 |
| `was-oth` | 8084 | 8284 |
| `was-sal` | 8082 | 8282 |
| `was-cus` | 8084 | 8284 |
| `was-pro` | 8085 | 8285 |
| `was-sys` | 8086 | 8286 |
Compose의 Pod URL은 컨테이너 DNS 이름을 사용합니다.
모듈 Dockerfile은 사전에 생성된 Boot JAR를 이미지에 복사합니다. 먼저 JAR를 빌드한 뒤 Compose를 실행합니다.
```text
was-sms: http://was-sms:8082
was-oth: http://was-oth:8084
```powershell
.\gradlew.bat :dap-was-sal:bootJar :dap-was-cus:bootJar :dap-was-pro:bootJar :dap-was-sys:bootJar
docker compose up --build
```
### Docker 컨테이너 기동
주의: `docker-compose.yml``http://gateway:8081`을 Gateway 주소로 사용하지만 이 저장소의 Compose에는 `gateway` 서비스가 없습니다. 동일 Docker 네트워크에 Gateway를 제공하거나 `AXHUB_GATEWAY_URL`을 실제 접근 가능한 주소로 변경해야 합니다.
별도의 CI/CD 러너나 외부 의존성(MCI Mock 등) 없이 독립적으로 실행 가능하도록 구성되어 있습니다. `docker-compose.yml`을 통해 Redis 및 각 Pod 컨테이너를 구동할 수 있습니다.
## 보안 및 운영 주의사항
현재 구현을 운영 환경에 노출하기 전에 아래 항목을 반드시 점검해야 합니다.
## 설정
- API Key 인터셉터는 `/rpc/**`, `/mcp/api/v1/**`에만 적용됩니다. 실제 Tool 실행 경로인 `/mcp/{toolName}`과 MCP 전송 경로 `/mcp`는 현재 검사 대상이 아닙니다.
- `mcp.security.api-keys`가 비어 있으면 인터셉터가 익명 요청을 허용합니다. 현재 기본 설정에는 API Key가 정의되어 있지 않습니다.
- `mcp.security.tenant-domains`는 설정 객체에 바인딩되지만 Tool별 인가에 사용되지 않습니다.
- `requiresApproval`은 메타데이터에만 기록되며 실행 차단이나 승인 확인 로직은 없습니다.
- 입력·출력 Schema 처리 자체에서 예외가 발생하면 현재 실행 서비스는 로그를 남기고 검증을 건너뜁니다.
- CORS는 모든 Origin을 허용하면서 credential도 허용하도록 설정되어 있습니다. 운영 Origin을 명시적으로 제한해야 합니다.
- Gateway 등록은 별도 비관리 스레드에서 Tool별로 최대 12회 재시도합니다. Gateway 장애 시 장시간 실행될 수 있으므로 타임아웃과 종료 정책을 점검해야 합니다.
- Tool 요청과 연동 오류 로그에 개인정보나 인증정보가 포함되지 않도록 DTO와 로그 마스킹 정책을 검토해야 합니다.
| 설정 | 위치/환경 변수 | 설명 |
|---|---|---|
| Pod 포트 | `server.port` 또는 `PORT` | SMS 8082, OTH 8084 |
| Pod 외부 URL | `AXHUB_TOOL_URL` | 스캐너가 Tool endpoint를 만들 때 사용 |
| MCP namespace | `mcp.namespace` | Tool 이름 앞에 `{namespace}_`를 붙임 |
| Manifest bundle | `mcp.manifest.bundle-id` | SMS는 `tool-sms`, OTH는 `tool-oth` |
| Manifest 이름 접두사 | `mcp.manifest.name-prefix` | 지정 시 모든 Manifest Tool 이름이 이 접두사로 시작해야 함 |
| 활성 프로필 | `SPRING_PROFILES_ACTIVE` | 기본값 `local`, 선택값 `dev` |
`mcp.security.tenant-domains` 설정은 각 Pod의 YAML에 존재하지만, 현재 `McpProperties``BusinessToolController`에는 이를 이용해 호출을 차단하는 로직이 없습니다. 문서상 권한 기능으로 간주하지 말고, 운영 노출 시 별도 인증·인가 계층을 적용해야 합니다.
## 보안과 운영 주의사항
- `BusinessToolController`는 요청 파라미터와 결과를 로그로 남깁니다. Tool 입력·응답에는 주민번호, 계좌번호, 전화번호, 인증값 등 민감정보를 포함하지 않도록 설계하고 공통 마스킹 적용 여부를 검토해야 합니다.
- `employee-id` 헤더는 Controller가 수신하지만 현재 실행 로직에서 사용하지 않습니다. 이 헤더만으로 인증·인가가 수행된다고 가정하면 안 됩니다.
- `mcp.security.tenant-domains`, `requiresApproval`, `register`는 현재 독립 WAS에서 실행 차단 정책을 구현하지 않습니다.
- 외부 MCI/EAI 대상은 local/dev 설정과 실제 네트워크 정책을 별도로 점검해야 합니다.
## 참고 소스
## 주요 소스 위치
| 주제 | 위치 |
|---|---|
| REST 실행 및 로컬 목록 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/presentation/BusinessToolController.java` |
| Manifest API | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/presentation/ToolManifestController.java` |
| MCP Streamable HTTP | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java` |
| 공통 빌드 및 검증 작업 | `build.gradle` |
| REST Tool 실행 API | `dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java` |
| Tool 실행 서비스 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.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 스캔 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/usecase/LocalToolScanner.java` |
| Tool 어노테이션 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpTool.java`, `McpFunction.java` |
| OTH 업무 Tool | `dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/` |
| SMS Tool | `dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/` |
| 컨테이너 구성 | `docker-compose.yml`, `dap-was-oth/Dockerfile`, `dap-was-sms/Dockerfile` |
| Tool 스캔 및 Gateway 등록 | `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` |
| API Key 인터셉터 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/ApiKeyInterceptor.java` |
| Tool·Pod 스캐폴딩 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java`, `PodScaffolder.java` |
## 개발 시 권장 확인 순서
1. Tool 메서드와 요청·응답 DTO를 구현합니다.
2. 동일 이름의 V17 YAML 정의를 `tool-definitions` 아래에 추가합니다.
3. `validateMcpToolNames``validateToolSchemaV17`을 실행합니다.
4. 모듈 테스트와 전체 테스트를 실행합니다.
5. 로컬 Pod에서 `/mcp/api/v1/tools/local``/tool-manifest`를 확인합니다.
6. REST와 MCP 양쪽에서 동일한 Tool 결과와 오류 계약을 확인합니다.