# DATMT MCP Tool Pods 신한라이프 업무 기능을 MCP(Model Context Protocol) Tool로 제공하는 Java 멀티 모듈 프로젝트입니다. 각 업무 모듈은 독립 실행 가능한 Spring Boot 애플리케이션이며, MCP Streamable HTTP와 REST 실행 API를 함께 제공합니다. 이 저장소에는 DATMS(Gateway) 애플리케이션이 포함되어 있지 않습니다. DATMT는 Gateway로 Tool을 push 등록하지 않으며, 각 Pod가 `GET /tool-manifest`를 제공하면 DATMS가 이 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.19.4 (Spring Boot BOM) - 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` | 상품 영역 Tool(개인고객 상세조회·모집수수료 공시) | 8085 | 2 | | `dat-was-sys` | IAM·시스템 상태·공지·점검·배포 Tool | 8086 | 51 | 업무 모듈에는 총 156개의 `@McpTool` 선언이 있습니다. 현재 업무 모듈의 `src/main/resources`에는 별도 `tool-definitions` YAML이 없으며, Tool 메타데이터는 어노테이션과 `@GrowToolHint`를 기준으로 생성됩니다. 현재 업무 구현은 개발·연동 검증 단계입니다. `dat-was-sal`의 `cmm_claim_search`와 `cmm_memo_retriever`는 각각 MCI와 HTTP Client 흐름을 사용하며, 나머지 Tool은 외부 시스템을 변경하지 않는 모의 응답을 중심으로 구현되어 있습니다. ## 처리 구조 ```text MCP Client 또는 REST Client ├─ MCP Streamable HTTP: /mcp └─ REST: POST /mcp/{toolName} │ ▼ Tool Pod ├─ McpToolMethodRegistry │ └─ Spring Bean의 @McpTool 메서드 탐색 및 실행 메서드 캐시 ├─ ToolRegistryHeartbeatSender │ ├─ Tool 메타데이터 생성 │ └─ 선택적 tool-definitions YAML 병합 ├─ ToolManifestService │ └─ DATMS가 pull할 bundle 단위 Manifest 생성 ├─ McpToolExecutionService │ ├─ 입력 Schema 검증 │ ├─ 요청 DTO 변환 및 Tool 호출 │ └─ 출력 Schema 검증 └─ UseCase → Converter → MCI/EAI/HTTP Client 또는 Mock 응답 ``` 애플리케이션 시작 시 다음 순서로 Tool이 준비됩니다. 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`를 통해 실행됩니다. DATMT 내부에는 `/registry/register`, `/registry/deregister` 호출이나 주기적인 Gateway heartbeat 전송이 없습니다. `ToolRegistryHeartbeatSender`라는 클래스명은 호환성을 위해 남아 있지만 현재 역할은 로컬 Tool 스캔과 메타데이터 생성뿐입니다. ### DATMS 연동 방식 ```text DATMS └─ 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`입니다. - DATMS에 설정한 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` | | 카테고리별 Tool Manifest 조회 | `GET` | `/tool-manifest/{categoryKey}` | `GET /tool-manifest`는 `If-None-Match` 요청 헤더를 지원합니다. Manifest가 변경되지 않았으면 `304 Not Modified`를 반환합니다. ### REST 실행 예시 다음은 SYS Pod의 시스템 상태 Tool 호출 예시입니다. ```powershell $headers = @{ 'X-Tool-Server-API-Key' = $env:TOOL_SERVER_API_KEY 'X-Guid' = 'guid-local-001' 'X-Praf-No' = '100001' 'X-Request-Id' = 'request-local-001' 'X-Request-Time' = '2026-08-25T12:34:56+09:00' 'X-Vrtl-Praf-No' = 'V100001' 'X-App-Code' = 'DATMT' 'X-Project-Code' = 'AXHUB' 'X-User-Ip' = '10.0.0.10' 'X-Caller-Ip' = '10.0.0.20' 'X-Caller-Host' = 'caller.example.internal' 'X-Channel' = 'MCP' 'X-Agent-Id' = 'agent-local-001' '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":"개발"}' ``` ### 요청 헤더 계약 DATMS가 DATMT Tool Service를 호출할 때 사용하는 헤더는 다음과 같습니다. HTTP 헤더 이름은 대소문자를 구분하지 않지만, 문서와 구현에서는 아래 표기를 기준으로 사용합니다. | 헤더 | 필수 여부 | 용도 | 전달 동작 | |---|---|---|---| | `X-Tool-Server-API-Key` | 인증 설정 시 필수 | DATMS와 DATMT 사이의 Tool Server 인증 | `mcp.security.api-key` 또는 `api-keys`와 비교 | | `X-Guid` | 선택 | 업무 호출 상관관계 식별자 | 실행 로그, 성공 응답, 하위 HTTP 호출로 전달 | | `X-Praf-No` | 선택 | 실제 사용자 사번 | 세션 조회와 하위 HTTP 호출로 전달 | | `X-Request-Id` | 선택 | 요청 추적 식별자 | 실행 로그, 성공 응답, 하위 HTTP 호출로 전달 | | `X-Request-Time` | 선택 | 요청 발생 시각 | 하위 HTTP 호출로 전달 | | `X-Vrtl-Praf-No` | 선택 | 가상 사용자 사번 | 하위 HTTP 호출로 전달 | | `X-App-Code` | 선택 | 호출 애플리케이션 코드 | 하위 HTTP 호출로 전달 | | `X-Project-Code` | 선택 | 호출 프로젝트 코드 | 하위 HTTP 호출로 전달 | | `X-User-Ip` | 선택 | 사용자 IP 주소 | 하위 HTTP 호출로 전달 | | `X-Caller-Ip` | 선택 | 호출 시스템 IP 주소 | 하위 HTTP 호출로 전달 | | `X-Caller-Host` | 선택 | 호출 시스템 호스트명 | 하위 HTTP 호출로 전달 | | `X-Channel` | 선택 | 호출 채널 | 하위 HTTP 호출로 전달 | | `X-Agent-Id` | 선택 | 호출 Agent 식별자 | 하위 HTTP 호출로 전달 | | `mcp-session-id` | 선택 | MCP 세션 식별자 | 성공 응답과 하위 HTTP 호출로 전달 | `McpRequestHeaderFilter`는 URI에 `/mcp`가 포함된 요청에서 위 헤더를 `McpRequestHeaderContext`에 저장하므로 REST `/mcp/{toolName}`과 MCP Streamable HTTP `/mcp`, `/mcp/message`에 모두 적용됩니다. REST 경로는 `BusinessToolController`도 동일한 헤더를 직접 읽어 실행 서비스에 전달합니다. 헤더가 없는 하위 HTTP 호출에는 `X-ANONYMOUS-REQ: AXHUB-TOOL`이 설정됩니다. 기존 `guid`, `employee-no`, `virtual-employee-no` 헤더는 지원하지 않습니다. 주요 실행 응답은 다음과 같습니다. | 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`로 보완합니다. ```java @McpTool( name = "iam_system_status", title = "시스템 상태 조회", description = "모의 시스템 상태 정보를 조회합니다.", annotations = @McpTool.McpAnnotations(openWorldHint = false) ) @GrowToolHint( categoryKey = "iam", mappingId = "DIRECT_IAM_STATUS", requiresApproval = false, timeoutMillis = 5000L, retryMaxAttempts = 3 ) SystemStatusResponse getSystemStatus(SystemStatusRequest request); ``` `@GrowToolHint`의 실행 제어 기본값은 `timeoutMillis = 5000L`, `retryMaxAttempts = 3`입니다. 어노테이션에서 값을 지정하면 해당 Tool의 메타데이터에 반영됩니다. `retryEnabled`는 지원하지 않으며, 재시도 여부는 `retryMaxAttempts` 값으로 판단합니다. `@McpTool.name`은 다음 형식을 사용합니다. ```text ^[a-z][a-z0-9_]{2,63}$ ``` 예: `cmm_claim_search`, `crm_customer_detail`, `iam_system_status` 이름 중복과 형식은 `validateMcpToolNames` Gradle 작업으로 검사합니다. 현재 루트 `build.gradle`에는 `validateToolSchemaV17` Gradle 작업이 등록되어 있지 않으며, 모든 `bootJar` 작업은 `validateMcpToolNames`에만 의존합니다. ## Tool YAML 정의 공통 라이브러리는 Tool 메타데이터를 보강하기 위한 선택적 YAML 정의를 지원합니다. YAML 정의를 추가할 경우 업무 모듈의 다음 경로를 사용합니다. ```text dat-was-*/src/main/resources/tool-definitions/{category}/{tool-name}.yml ``` 현재 업무 모듈에는 이 경로의 YAML 정의가 없습니다. 따라서 실행 시 메타데이터는 `@McpTool`, `@GrowToolHint`, DTO Schema를 기준으로 구성됩니다. YAML 정의를 도입하는 경우 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를 해석합니다. YAML 정의가 있으면 `ToolRegistryHeartbeatSender`가 `parameters_schema`와 `output_schema`를 병합하여 최종 메타데이터를 만듭니다. ### Tool 실행 제어 메타데이터 다음 메타데이터는 Tool 목록과 Manifest에 함께 제공됩니다. | 필드 | 기본값 | 설명 | |---|---:|---| | `timeoutMillis` | `5000` | Gateway가 Tool 응답을 기다리는 최대 시간(밀리초) | | `retryMaxAttempts` | `3` | 최초 호출을 포함한 최대 시도 횟수 | `retryEnabled` 필드는 외부 메타데이터와 `ToolMetadata`에서 제거되었습니다. 따라서 클라이언트와 Gateway는 `retryMaxAttempts`만 사용해야 하며, 값이 없거나 1보다 작으면 기본값 3이 적용됩니다. Scaffold로 생성되는 Tool에도 위 두 값이 자동으로 삽입됩니다. 표준 MCP `tools/list` 응답에서는 두 필드가 Tool의 `_meta`에 camelCase로 내려갑니다. ```json { "_meta": { "timeoutMillis": 5000, "retryMaxAttempts": 3 } } ``` `GET /tool-manifest` 응답의 각 Tool `_meta`에도 동일한 두 필드가 포함됩니다. `GET /mcp/api/v1/tools/local` 응답은 내부 `ToolMetadata` 표현을 사용하므로 동일한 실행 제어 값을 확인할 수 있습니다. 실행 시 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를 사용합니다. ```powershell $env:SPRING_PROFILES_ACTIVE = 'local' $env:TOOL_SERVER_API_KEY = 'tool-server-key' ``` `TOOL_SERVER_API_KEY`를 지정하지 않으면 현재 개발 기본값인 `tool-server-key`가 사용됩니다. 운영 환경에서는 기본값을 사용하지 말고 DATMS의 Tool Server API Key와 동일한 별도 Secret을 주입해야 합니다. 각 Pod는 별도 터미널에서 실행합니다. ```powershell # 영업 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은 다음과 같습니다. ```text 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` | DATMS가 `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`는 단일 DATMS 공통 Key를, `mcp.security.api-keys`는 `API Key → tenant ID` 형태의 다중 Key를 지원합니다. `ApiKeyInterceptor`는 Spring MVC가 처리하는 `/rpc/**`, `/mcp/**` 요청에 적용되므로 REST `/mcp/{toolName}`과 `/mcp/api/v1/tools/local`은 올바른 `X-Tool-Server-API-Key`가 없으면 `401 Unauthorized`가 됩니다. 두 설정이 모두 비어 있을 때는 해당 MVC 요청을 익명으로 허용합니다. 현재 네 업무 Pod의 기본 `application.yml`은 단일 Key를 설정합니다. 반면 `/mcp`와 `/mcp/message`는 별도 Servlet으로 등록되어 Spring MVC `HandlerInterceptor`를 통과하지 않습니다. 현재 구현만으로는 이 두 MCP Streamable HTTP 경로에 `ApiKeyInterceptor` 인증이 적용되지 않으므로, 운영 배포 전 Servlet Filter 또는 전용 MCP 인증 계층을 추가해야 합니다. ## 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 [author] [yyyy.MM.dd] ``` 예를 들어 `payment 8099`를 입력하면 `dat-was-payment` 모듈을 생성합니다. 생성되는 `application.yml`에는 다음 계약이 포함됩니다. ```yaml 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}`가 추가됩니다. 생성 후에는 다음 항목을 반드시 확인해야 합니다. 1. `bundle-id`를 DATMS에 등록할 bundle ID와 일치시킵니다. 2. 운영 환경의 `TOOL_SERVER_API_KEY`를 DATMS가 전달하는 Key와 동일한 Secret으로 설정합니다. 3. 실제 배포 주소에 맞게 `AXHUB_TOOL_URL`을 설정합니다. 4. MCI·EAI·HTTP 연동 대상과 timeout을 환경별 설정으로 교체합니다. 5. `validateMcpToolNames`와 모듈·전체 테스트를 실행합니다. YAML 정의를 추가했다면 `ToolSchemaV17ValidationRunner` 또는 관련 테스트로 V17 항목을 별도 검증합니다. ## 테스트와 검증 ```powershell # 운영 소스 전체 컴파일 .\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 # 검증, 테스트, 패키징 .\gradlew.bat clean build ``` ### 실행 전 확인 테스트 수와 성공 여부는 소스 변경에 따라 달라지므로 고정된 수치를 문서화하지 않습니다. 배포 전 현재 작업 트리에서 다음 명령을 실행해 확인합니다. ```powershell .\gradlew.bat test validateMcpToolNames ``` ## 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를 실행합니다. ```powershell .\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` | DATMS와 DATMT의 로컬 통합 구성 | 두 저장소가 같은 상위 디렉터리에 있는 구조를 가정 | | `docker-compose.prod.yml` | DATMS와 DATMT의 개발 프로필 기반 OCI 구성 | 저장소의 runner 등록 토큰을 운영 Secret으로 분리해야 함 | `docker-compose.local.yml`과 `docker-compose.prod.yml`의 build context는 각각 `./dat-was-DATMS`, `./dat-was-datmt`입니다. 현재 파일 위치에서 사용할 때는 context 기준을 두 저장소의 상위 디렉터리로 맞춰야 합니다. ```powershell # 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` 전달 설정을 추가하고 DATMS의 Key와 일치시켜야 합니다. DATMS는 각 Pod의 `AXHUB_TOOL_URL` 또는 배포 URL에 접근해 `/tool-manifest`를 pull할 수 있어야 합니다. ## OpenShift (Kubernetes) 배포 OpenShift 개발 환경용 Kustomize 매니페스트는 [`k8s/`](k8s/)에 있습니다. 대상은 DATMT의 네 Tool Pod뿐이며 DATMS(Gateway)의 Deployment·Service·Route는 이 저장소에서 만들지 않습니다. ```text k8s/ ├─ base/ # 네 Pod 공통 ConfigMap, Service, Deployment └─ overlays/dev/ # 개발 namespace, Registry 이미지 경로와 tag ``` ### 배포 구조 각 Tool Pod는 Deployment 1개와 외부에 노출되지 않는 `ClusterIP` Service 1개를 사용합니다. OpenShift Route와 LoadBalancer Service는 생성하지 않으며, Gateway가 클러스터 내부 DNS로 호출합니다. | Service | Pod 포트 | Gateway 호출 주소 | |---|---:|---| | `was-sal` | 8082 | `http://was-sal:8082` | | `was-cus` | 8084 | `http://was-cus:8084` | | `was-pro` | 8085 | `http://was-pro:8085` | | `was-sys` | 8086 | `http://was-sys:8086` | Gateway가 다른 namespace에 있으면 `was-sal.axhub-datmt-dev.svc`와 같은 FQDN을 사용하고, NetworkPolicy에서 Gateway namespace의 ingress를 별도로 허용해야 합니다. ### 반영 전 설정 다음 값은 실제 신한라이프 개발망 값으로 교체해야 합니다. 1. [`k8s/overlays/dev/kustomization.yaml`](k8s/overlays/dev/kustomization.yaml)의 namespace, 내부 Container Registry 경로, 배포 image tag 2. [`k8s/base/configmap.yaml`](k8s/base/configmap.yaml)의 `CHANGE_ME` MCI·EXTMCI·EAI 호스트 3. 실제 Secret 값 `datmt-runtime-secrets` Secret은 Git에 저장하지 않고 OpenShift namespace에서 별도로 생성합니다. 최소한 `TOOL_SERVER_API_KEY`는 DATMS가 전달하는 `X-Tool-Server-API-Key`와 같은 값이어야 합니다. DB 계정·비밀번호, API Key, 인증서 비밀번호 등도 이 Secret으로 관리합니다. ```powershell # runtime-secrets.env는 저장소 밖에 보관합니다. oc -n axhub-datmt-dev create secret generic datmt-runtime-secrets ` --from-env-file=runtime-secrets.env ``` Secret이 없으면 각 Deployment의 `envFrom.secretRef`를 해석할 수 없어 Pod가 시작하지 않을 수 있습니다. ### 이미지 빌드와 배포 모듈 Dockerfile은 미리 생성된 Boot JAR를 복사하므로, 이미지를 만들기 전에 네 모듈의 JAR를 빌드합니다. 개발망에서는 JDK/JRE 베이스 이미지와 Gradle/Maven 의존성을 내부 Registry·Nexus에서 사용할 수 있어야 합니다. ```powershell .\gradlew.bat :dat-was-sal:bootJar :dat-was-cus:bootJar :dat-was-pro:bootJar :dat-was-sys:bootJar # OpenShift 로그인 및 project 선택 후 oc kustomize k8s/overlays/dev oc apply -k k8s/overlays/dev oc get deployment,pod,svc -n axhub-datmt-dev ``` 적용 전에는 `oc kustomize k8s/overlays/dev | oc apply --dry-run=client -f -`로 서버 측 스키마 검증을 수행합니다. 현재 매니페스트의 readiness/liveness probe는 TCP 포트 확인 방식입니다. Actuator health endpoint를 추가한 뒤에는 HTTP readiness/liveness probe로 변경하는 것을 권장합니다. 상세한 명령과 Gateway 연결 확인 방법은 [`k8s/README.md`](k8s/README.md)를 참고합니다. ## 보안 및 운영 주의사항 현재 구현을 운영 환경에 노출하기 전에 아래 항목을 반드시 점검해야 합니다. - API Key 인터셉터는 Spring MVC의 `/rpc/**`, `/mcp/**` Handler에 적용됩니다. REST `/mcp/{toolName}`과 `/mcp/api/v1/tools/local`은 인증 대상입니다. - 별도 Servlet인 `/mcp`, `/mcp/message`는 MVC 인터셉터를 우회하므로 현재 API Key 인증 대상이 아닙니다. 운영 노출 전에 별도 인증을 추가해야 합니다. - `/tool-manifest`와 `/tool-manifest/{categoryKey}`는 위 인터셉터 경로 밖에 있어 현재 API Key 인증 대상이 아닙니다. 내부망·Ingress 정책 또는 별도 인증이 필요한지 운영 기준을 확인해야 합니다. - 네 업무 Pod의 기본 Key는 모두 `tool-server-key`입니다. 운영에서는 반드시 별도 Secret으로 교체하고 DATMS의 `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` | | MCP `tools/list` 메타데이터 변환 | `dat-was-lib/src/main/java/io/shinhanlife/dat/lib/mcp/ToolMetadataMcpMapper.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` | ## 개발 시 권장 확인 순서 1. Tool 메서드와 요청·응답 DTO를 구현합니다. 2. 동일 이름의 V17 YAML 정의를 `tool-definitions` 아래에 추가합니다. 3. `validateMcpToolNames`와 모듈·전체 테스트를 실행합니다. Tool YAML 정의를 추가한 경우에는 V17 항목을 별도 검증합니다. 4. 모듈 테스트와 전체 테스트를 실행합니다. 5. 로컬 Pod에서 `/mcp/api/v1/tools/local`과 `/tool-manifest`를 확인합니다. 6. DATMS의 bundle ID, Pod Manifest URL, Tool Server API Key가 DATMT 설정과 일치하는지 확인합니다. 7. REST와 MCP 양쪽에서 동일한 Tool 결과·요청 헤더 전달·오류 계약을 확인합니다.