DAP WAS MCP Tool Pods
신한라이프 업무 기능을 MCP(Model Context Protocol) Tool로 제공하는 Java 멀티 모듈 프로젝트입니다. 각 업무 모듈은 독립 실행 가능한 Spring Boot 애플리케이션이며, MCP Streamable HTTP와 REST 실행 API를 함께 제공합니다.
이 저장소에는 DAPMS(Gateway) 애플리케이션이 포함되어 있지 않습니다. DATMT는 Gateway로 Tool을 push 등록하지 않으며, 각 Pod가 GET /tool-manifest를 제공하면 DAPMS가 이 Manifest를 pull하여 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 수 |
|---|---|---|---|
dat-was-lib |
MCP 서버, Tool 스캔·실행, Schema, Manifest, 보안, MCI/EAI/HTTP 연동 공통 기능 | - | - |
dat-was-cus |
고객·CRM·VOC·웹 콘텐츠 관리 Tool | 8084 | 51 |
dat-was-sal |
영업·청구·인수·동의·현장지원 Tool | 8082 | 52 |
dat-was-pro |
상품·계약·고객·GA 설계사 Tool | 8085 | 50 |
dat-was-sys |
IAM·시스템 상태·공지·점검·배포 Tool | 8086 | 50 |
총 203개의 @McpTool 선언과 203개의 V17 Tool YAML 정의가 있습니다.
현재 업무 구현은 개발·연동 검증 단계입니다. dat-was-sal의 cmm_claim_search와 cmm_memo_retriever는 각각 MCI와 HTTP Client 흐름을 사용하며, 나머지 Tool은 외부 시스템을 변경하지 않는 모의 응답을 중심으로 구현되어 있습니다.
처리 구조
MCP Client 또는 REST Client
├─ MCP Streamable HTTP: /mcp
└─ REST: POST /mcp/{toolName}
│
▼
Tool Pod
├─ McpToolMethodRegistry
│ └─ Spring Bean의 @McpTool 메서드 탐색 및 실행 메서드 캐시
├─ ToolRegistryHeartbeatSender
│ ├─ Tool 메타데이터 생성
│ └─ tool-definitions YAML 병합
├─ ToolManifestService
│ └─ DAPMS가 pull할 bundle 단위 Manifest 생성
├─ McpToolExecutionService
│ ├─ 입력 Schema 검증
│ ├─ 요청 DTO 변환 및 Tool 호출
│ └─ 출력 Schema 검증
└─ UseCase → Converter → MCI/EAI/HTTP Client 또는 Mock 응답
애플리케이션 시작 시 다음 순서로 Tool이 준비됩니다.
ToolDefinitionRepository가classpath*:tool-definitions/**/*.yml을 읽고 V17 필수 항목을 검증합니다.ToolRegistryHeartbeatSender가@McpTool메서드를 스캔하고 YAML 정의를 병합해ToolMetadata를 생성합니다.McpToolMethodRegistry가 실제 호출 가능한 Bean과 메서드를 Tool 이름으로 캐시합니다.ToolPodMcpToolSynchronizer가 Tool을 MCP SDK 서버에 등록합니다.- REST와 MCP 요청은 공통
McpToolExecutionService를 통해 실행됩니다.
DATMT 내부에는 /registry/register, /registry/deregister 호출이나 주기적인 Gateway heartbeat 전송이 없습니다. ToolRegistryHeartbeatSender라는 클래스명은 호환성을 위해 남아 있지만 현재 역할은 로컬 Tool 스캔과 메타데이터 생성뿐입니다.
DAPMS 연동 방식
DAPMS
└─ GET {DATMT 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와 DATMT가 반환하는
bundleId가 일치해야 같은 Tool bundle로 관리됩니다. mcp.manifest.name-prefix가 비어 있지 않으면 모든 Tool 이름이 해당 prefix로 시작해야 합니다.- Manifest 내용이 바뀌면
revision이 증가하며,If-None-Match가 일치하면304 Not Modified를 반환합니다.
제공 API
각 업무 Pod가 동일한 API 구조를 제공합니다.
| 목적 | 메서드 | 경로 |
|---|---|---|
| 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 |
GET /tool-manifest는 If-None-Match 요청 헤더를 지원합니다. Manifest가 변경되지 않았으면 304 Not Modified를 반환합니다.
REST 실행 예시
다음은 SYS Pod의 시스템 상태 Tool 호출 예시입니다.
$headers = @{
'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 `
-Method Post `
-Uri 'http://localhost:8086/mcp/iam_system_status' `
-Headers $headers `
-ContentType 'application/json' `
-Body '{"environment":"개발"}'
요청 헤더 계약
DAPMS가 DATMT Tool Service를 호출할 때 사용하는 헤더는 다음과 같습니다. HTTP 헤더 이름은 대소문자를 구분하지 않지만, 문서와 구현에서는 아래 표기를 기준으로 사용합니다.
| 헤더 | 필수 여부 | 용도 | 전달 동작 |
|---|---|---|---|
X-Tool-Server-API-Key |
인증 설정 시 필수 | DAPMS와 DATMT 사이의 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 상태 | 코드 | 의미 |
|---|---|---|
| 200 | - | Tool 실행 성공 |
| 404 | TOOL_NOT_FOUND |
요청한 Tool 이름이 없음 |
| 422 | INVALID_PARAM |
요청이 입력 Schema와 일치하지 않음 |
| 500 | INVALID_TOOL_RESPONSE |
결과가 출력 Schema와 일치하지 않음 |
| 502 | TOOL_ERROR |
Tool 실행 중 예외 발생 |
Tool 구현 방식
호출 가능한 메서드는 Spring AI Community의 @McpTool로 선언합니다. 프로젝트 고유 실행·표시 정보는 @GrowToolHint로 보완합니다.
@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);
@McpTool.name은 다음 형식을 사용합니다.
^[a-z][a-z0-9_]{2,63}$
예: cmm_claim_search, crm_customer_detail, iam_system_status
이름 중복과 형식은 validateMcpToolNames, V17 정의는 validateToolSchemaV17 Gradle 작업으로 검사합니다. 모든 bootJar 작업은 두 검증 작업에 의존합니다.
Tool YAML 정의
각 Tool은 업무 모듈의 다음 경로에 YAML 정의를 가집니다.
dat-was-*/src/main/resources/tool-definitions/{category}/{tool-name}.yml
V17 정의의 주요 필수 항목은 다음과 같습니다.
name,display_name,version,category_keydescription.function,when_to_use,when_not_to_use,io_limits- 3~10개의
example_queries read_only,destructive,idempotentparameters_schema.type: objectparameters_schema.additionalProperties: false- 각 입력 property의
description
입력·출력 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 해석 또는 검증기 자체에서 예외가 발생하면 현재 구현은 오류를 로그에 기록하고 해당 검증을 건너뜁니다.
로컬 실행
사전 조건
- JDK 21
- 프로젝트에 포함된 Gradle Wrapper
- Redis 또는 외부 연동이 필요한 경우 Docker
기본 활성 프로필은 local입니다. 로컬 프로필은 H2 메모리 DB와 P6Spy를 사용합니다.
$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는 별도 터미널에서 실행합니다.
# 영업 Tool Pod
$env:AXHUB_TOOL_URL = 'http://localhost:8082'
.\gradlew.bat :dat-was-sal:bootRun
# 고객 Tool Pod
$env:AXHUB_TOOL_URL = 'http://localhost:8084'
.\gradlew.bat :dat-was-cus:bootRun
# 상품 Tool Pod
$env:AXHUB_TOOL_URL = 'http://localhost:8085'
.\gradlew.bat :dat-was-pro:bootRun
# 시스템 Tool Pod
$env:AXHUB_TOOL_URL = 'http://localhost:8086'
.\gradlew.bat :dat-was-sys:bootRun
실행 후 SYS Pod 기준 확인 URL은 다음과 같습니다.
http://localhost:8086/mcp/api/v1/tools/local
http://localhost:8086/tool-manifest
http://localhost:8086/swagger-ui/index.html
주요 환경 변수
| 환경 변수 | 설명 | 기본값 |
|---|---|---|
SPRING_PROFILES_ACTIVE |
Spring 활성 프로필 | local |
PORT |
Pod 수신 포트 | 모듈별 기본 포트 |
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. 현재 DATMT의 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의 인자 순서는 다음과 같습니다.
PodScaffolder <module-name> <port> [author] [yyyy.MM.dd]
예를 들어 payment 8099를 입력하면 dat-was-payment 모듈을 생성합니다. 생성되는 application.yml에는 다음 계약이 포함됩니다.
mcp:
manifest:
bundle-id: dat-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}가 추가됩니다. 생성 후에는 다음 항목을 반드시 확인해야 합니다.
bundle-id를 DAPMS에 등록할 bundle ID와 일치시킵니다.- 운영 환경의
TOOL_SERVER_API_KEY를 DAPMS가 전달하는 Key와 동일한 Secret으로 설정합니다. - 실제 배포 주소에 맞게
AXHUB_TOOL_URL을 설정합니다. - MCI·EAI·HTTP 연동 대상과 timeout을 환경별 설정으로 교체합니다.
validateMcpToolNames,validateToolSchemaV17, 전체 테스트를 실행합니다.
테스트와 검증
# 운영 소스 전체 컴파일
.\gradlew.bat classes
# 전체 테스트
.\gradlew.bat test
# 모듈별 테스트
.\gradlew.bat :dat-was-lib:test
.\gradlew.bat :dat-was-cus:test
.\gradlew.bat :dat-was-sal:test
.\gradlew.bat :dat-was-pro:test
.\gradlew.bat :dat-was-sys:test
# Tool 이름·중복 검사
.\gradlew.bat validateMcpToolNames
# 203개 Tool V17 정의 검사
.\gradlew.bat validateToolSchemaV17
# 검증, 테스트, 패키징
.\gradlew.bat clean build
현재 검증 상태
2026-08-18 기준 확인 결과입니다.
- 전체
test: 성공 - 총 94개 테스트 성공, 실패·오류·건너뜀 0개
validateMcpToolNames: 성공validateToolSchemaV17: 203개 Tool 성공- 실행 명령:
.\gradlew.bat test validateMcpToolNames validateToolSchemaV17
Docker Compose
docker-compose.yml은 DATMT의 네 업무 Pod만 정의합니다.
| 서비스 | 컨테이너 포트 | 호스트 포트 |
|---|---|---|
was-sal |
8082 | 8282 |
was-cus |
8084 | 8284 |
was-pro |
8085 | 8285 |
was-sys |
8086 | 8286 |
모듈 Dockerfile은 사전에 생성된 Boot JAR를 이미지에 복사합니다. 먼저 JAR를 빌드한 뒤 Compose를 실행합니다.
.\gradlew.bat :dat-was-sal:bootJar :dat-was-cus:bootJar :dat-was-pro:bootJar :dat-was-sys:bootJar
docker compose up --build
Compose 파일의 용도와 현재 주의점은 다음과 같습니다.
| 파일 | 용도 | 현재 소스 기준 주의점 |
|---|---|---|
docker-compose.yml |
DATMT 네 Pod 단독 실행 | gateway 서비스가 없지만 push 등록이 제거되어 DATMT 시작에는 필요하지 않음 |
docker-compose.local.yml |
DAPMS와 DATMT의 로컬 통합 구성 | 두 저장소가 같은 상위 디렉터리에 있는 구조를 가정 |
docker-compose.prod.yml |
DAPMS와 DATMT의 개발 프로필 기반 OCI 구성 | 저장소의 runner 등록 토큰을 운영 Secret으로 분리해야 함 |
docker-compose.local.yml과 docker-compose.prod.yml의 build context는 각각 ./dat-was-dapms, ./dat-was-datmt입니다. 현재 파일 위치에서 사용할 때는 context 기준을 두 저장소의 상위 디렉터리로 맞춰야 합니다.
# DATMT 저장소 디렉터리에서 실행
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/**에 적용됩니다. 따라서/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을 명시적으로 제한해야 합니다.
docker-compose.prod.yml에 runner 등록 토큰이 평문으로 포함되어 있습니다. 사용 중인 토큰은 폐기·재발급하고 배포 Secret으로 이전해야 합니다.- Tool 요청과 연동 오류 로그에 개인정보나 인증정보가 포함되지 않도록 DTO와 로그 마스킹 정책을 검토해야 합니다.
주요 소스 위치
| 주제 | 위치 |
|---|---|
| 공통 빌드 및 검증 작업 | build.gradle |
| REST Tool 실행 API | dat-was-lib/src/main/java/io/shinhanlife/dat/mcc/presentation/BusinessToolController.java |
| 요청 헤더 캡처·전달 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/mcp/McpRequestHeaderFilter.java, McpRequestHeaderContext.java |
| Tool 실행 서비스 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/mcp/McpToolExecutionService.java |
| JSON Schema 2020-12 검증 설정 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/config/ToolSchemaConfiguration.java |
| 실행 메서드 Registry | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/mcp/McpToolMethodRegistry.java |
| MCP SDK 서버 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/mcp/ToolMcpServerConfiguration.java |
| MCP Tool 동기화 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/mcp/ToolPodMcpToolSynchronizer.java |
| Tool 스캔 및 메타데이터 생성 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/mcp/ToolRegistryHeartbeatSender.java |
| YAML Tool 정의 로딩 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/metadata/ToolDefinitionRepository.java |
| V17 정의 검증 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/metadata/ToolDefinitionValidator.java |
| Manifest 생성 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolManifestService.java |
| API Key 인터셉터 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/mcp/security/ApiKeyInterceptor.java |
| Tool·Pod 스캐폴딩 | dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/ToolScaffolder.java, PodScaffolder.java |
개발 시 권장 확인 순서
- Tool 메서드와 요청·응답 DTO를 구현합니다.
- 동일 이름의 V17 YAML 정의를
tool-definitions아래에 추가합니다. validateMcpToolNames와validateToolSchemaV17을 실행합니다.- 모듈 테스트와 전체 테스트를 실행합니다.
- 로컬 Pod에서
/mcp/api/v1/tools/local과/tool-manifest를 확인합니다. - DAPMS의 bundle ID, Pod Manifest URL, Tool Server API Key가 DATMT 설정과 일치하는지 확인합니다.
- REST와 MCP 양쪽에서 동일한 Tool 결과·요청 헤더 전달·오류 계약을 확인합니다.