Files
dap-was-dapmt/README.md
2026-08-14 18:16:14 +09:00

13 KiB

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-manifestIf-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-idrequest-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}의 동적 실행은 visibleregister 값으로 차단하지 않습니다. 따라서 직접 호출을 막아야 하는 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는 다음 우선순위로 결정됩니다.

  1. inputSchemaResource에 지정한 classpath JSON Schema
  2. inputSchema에 인라인으로 지정한 JSON Schema
  3. 요청 DTO의 @McpValidation을 이용한 자동 생성 Schema

출력 검증은 선택 사항입니다. 다음 중 하나가 있을 때만 반환값을 검증합니다.

  1. outputSchemaResource
  2. outputSchema
  3. 반환 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에 존재하지만, 현재 McpPropertiesBusinessToolController에는 이를 이용해 호출을 차단하는 로직이 없습니다. 문서상 권한 기능으로 간주하지 말고, 운영 노출 시 별도 인증·인가 계층을 적용해야 합니다.

보안과 운영 주의사항

  • 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