DAP WAS Tool Pods
신한라이프 업무 시스템과 MCP(Model Context Protocol) 클라이언트를 연결하는 독립형 Tool WAS 프로젝트입니다. 이 저장소에는 Gateway가 포함되어 있지 않습니다. 각 Tool Pod가 직접 MCP Streamable HTTP와 REST 실행 API를 제공하고, 업무 요청은 UseCase → Converter → MCI/EAI 연동으로 처리합니다.
이 문서는 현재
main의 구현과 설정을 기준으로 합니다. 과거dap-gateway, Chat API, SSE, 외부 Tool Registry/Heartbeat 관련 문서는 현재 저장소의 동작 범위가 아니므로 포함하지 않습니다.
구성
MCP Client
├─ Streamable HTTP: /mcp
└─ REST: POST /mcp/{tool-name}
│
▼
Tool Pod (dap-was-oth 또는 dap-was-sms)
│
├─ LocalToolScanner: @McpTool / @McpFunction 메타데이터 생성
├─ BusinessToolController: 입력 검증·DTO 변환·동적 실행
├─ ToolManifestController: /tool-manifest 제공
└─ UseCase → Converter → MCI/EAI Client → 대상 시스템
Gradle 모듈
| 모듈 | 역할 | 기본 포트 |
|---|---|---|
dap-was-lib |
MCP 어노테이션, 스캐너, 실행 Controller, Manifest, Schema, MCI/EAI/로깅/보안 공통 기능 | - |
dap-was-oth |
공통·기타·샘플·SOL 업무 Tool Pod | 8084 |
dap-was-sms |
SMS/알림 업무 Pod | 8082 |
기술 기준은 Java 21, Spring Boot 4.0.5, Gradle Wrapper 8.14.3, Spring AI MCP Server WebMVC, Redis, MapStruct, MyBatis, Resilience4j입니다.
현재 제공 API
아래 API는 각 Tool Pod가 직접 제공합니다. OTH Pod의 로컬 주소는 http://localhost:8084, SMS Pod는 http://localhost:8082입니다.
| 목적 | 메서드 | 경로 | 구현 |
|---|---|---|---|
| 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 목록을 반환합니다.
REST 실행 예시
Tool 이름은 @McpFunction.name 값입니다. 예를 들어 OTH Pod의 oth.smp.weather.inquiry는 다음처럼 호출합니다.
$headers = @{
'trace-id' = 'trace-local-001'
'request-id' = 'request-local-001'
}
Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:8084/mcp/oth.smp.weather.inquiry' `
-Headers $headers `
-ContentType 'application/json' `
-Body '{"city":"Seoul"}'
Controller는 이름을 찾은 뒤 요청 JSON을 첫 번째 DTO 매개변수로 변환합니다. 입력 Schema 검증 실패는 422 INVALID_PARAM, 존재하지 않는 Tool은 404 TOOL_NOT_FOUND, 실행 예외는 502 TOOL_ERROR 응답입니다. trace-id와 request-id는 성공 응답 헤더로 다시 전달됩니다.
Tool 검색과 MCP 노출 규칙
애플리케이션 기동 시 LocalToolScanner는 Spring Bean에서 @McpTool과 @McpFunction 메타데이터를 읽어 로컬 Tool 목록을 만듭니다. 기동 완료 후 ToolPodMcpToolSynchronizer는 이 목록 중 visible = true인 Tool만 MCP SDK 서버에 추가합니다.
| 속성 | 현재 구현에서의 의미 |
|---|---|
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으로 구성되어야 합니다.
@McpTool(routingType = "MCI", categoryKey = "claim")
public interface ClaimInquiryUseCase {
@McpFunction(
name = "oth.claim.inquiry.detail",
displayName = "청구 상세 조회",
description = "청구 번호로 청구 상세를 조회합니다.",
mappingId = "CLM00000001",
readOnlyHint = true
)
ClaimInquiryResponse inquire(ClaimInquiryRequest request);
}
구현체에는 업무 흐름만 두고, Tool DTO와 레거시 인터페이스 DTO의 변환은 Converter에 둡니다.
@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는 다음 우선순위로 결정됩니다.
inputSchemaResource에 지정한 classpath JSON SchemainputSchema에 인라인으로 지정한 JSON Schema- 요청 DTO의
@McpValidation을 이용한 자동 생성 Schema
출력 검증은 선택 사항입니다. 다음 중 하나가 있을 때만 반환값을 검증합니다.
outputSchemaResourceoutputSchema- 반환 DTO의
@McpOutputSchema와@McpValidation
복잡한 Schema 리소스는 Tool 모듈에 둡니다. 현재 OTH의 청구 검색 예제는 다음 리소스를 사용합니다.
dap-was-oth/src/main/resources/tool-schemas/cmm/
├─ claim-search-resource-input-schema.json
└─ claim-search-resource-output-schema.json
입력 검증에는 JSON Schema Draft 7이 사용됩니다. 출력 Schema 검증에 실패하면 500 INVALID_TOOL_RESPONSE을 반환합니다.
로컬 실행
사전 조건
- JDK 21
- Docker (Redis 또는 MCI mock을 사용할 경우)
- Gradle Wrapper 사용 권장
로컬 프로필은 기본값이며, 두 Pod 모두 H2 메모리 DB와 P6Spy를 설정합니다. Pod URL은 AXHUB_TOOL_URL 환경 변수로 설정하며, 지정하지 않으면 해당 server.port의 localhost 주소를 사용합니다.
# 필수: 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를 확인할 수 있습니다.
http://localhost:8084/mcp/api/v1/tools/local
http://localhost:8084/tool-manifest
http://localhost:8084/tool-test-console.html
tool-test-console.html은 공통 라이브러리의 정적 리소스입니다. /tool-manifest에서 Tool과 입력 Schema를 읽어 요청 JSON을 만들고, 현재 Pod의 /mcp/{toolName}으로 호출합니다. 저장한 테스트 케이스는 브라우저 localStorage에 보관됩니다.
테스트와 빌드
# 전체 테스트
.\gradlew.bat test
# 공통 라이브러리 테스트
.\gradlew.bat :dap-was-lib:test
# OTH Tool 테스트
.\gradlew.bat :dap-was-oth:test
# Tool 이름 규칙 및 중복 검증
.\gradlew.bat validateMcpToolNames
# 패키징 전 전체 빌드
.\gradlew.bat clean build
테스트는 공통 MCP Schema/Manifest/Header 처리, Glow MCI 파서, Tool 이름 검증과 OTH의 청구·SOL·MCI 변환을 다룹니다. SMS 모듈에는 현재 별도 테스트 소스가 없습니다.
Docker Compose
현재 Compose 서비스와 호스트 포트는 다음과 같습니다.
| 서비스 | 컨테이너 포트 | 호스트 포트 |
|---|---|---|
redis |
6379 | 6379 |
was-sms |
8082 | 8282 |
was-oth |
8084 | 8284 |
Compose의 Pod URL은 컨테이너 DNS 이름을 사용합니다.
was-sms: http://was-sms:8082
was-oth: http://was-oth:8084
Docker 컨테이너 기동
별도의 CI/CD 러너나 외부 의존성(MCI Mock 등) 없이 독립적으로 실행 가능하도록 구성되어 있습니다. docker-compose.yml을 통해 Redis 및 각 Pod 컨테이너를 구동할 수 있습니다.
설정
| 설정 | 위치/환경 변수 | 설명 |
|---|---|---|
| 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 |
| 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 |