From b0bf4a9c68c44cd280d8ee2dc2a9d829d028754f Mon Sep 17 00:00:00 2001 From: jade Date: Wed, 29 Jul 2026 13:25:10 +0900 Subject: [PATCH 1/8] fix: use rsync to clean stale files during host volume sync --- .gitea/workflows/deploy.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 6a01b920..42a363a0 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -16,9 +16,9 @@ jobs: run: | if command -v apt-get &> /dev/null; then apt-get update - apt-get install -y nodejs git openjdk-21-jdk + apt-get install -y nodejs git openjdk-21-jdk rsync elif command -v apk &> /dev/null; then - apk add --no-cache nodejs git openjdk21 + apk add --no-cache nodejs git openjdk21 rsync fi - name: Checkout Code @@ -26,8 +26,13 @@ jobs: - name: Sync Code to Host Volume run: | - echo "Copying latest code to /app (Host Volume)..." - cp -a . /app/ + echo "Copying latest code to /app (Host Volume) and removing stale files..." + if command -v rsync &> /dev/null; then + rsync -a --delete --exclude='.git' . /app/ + else + rm -rf /app/dap-* /app/src /app/build.gradle /app/settings.gradle + cp -a . /app/ + fi - name: Deploy Task on Host run: | From ea1aea81bb3254cabe0a45f8e25e4e950b423c4d Mon Sep 17 00:00:00 2001 From: MJH098 Date: Wed, 29 Jul 2026 14:25:47 +0900 Subject: [PATCH 2/8] feat: add SOL request detail tool --- .../sol/converter/SolReqDetailConverter.java | 31 ++++++ .../mcc/biz/sol/dto/SolReqDetailRequest.java | 27 +++++ .../mcc/biz/sol/dto/SolReqDetailResponse.java | 33 ++++++ .../biz/sol/usecase/SolReqDetailUseCase.java | 39 +++++++ .../usecase/impl/SolReqDetailUseCaseImpl.java | 101 ++++++++++++++++++ .../itrf/mci/ncl/g/io/SOLG00000002_I.java | 23 ++++ .../itrf/mci/ncl/g/io/SOLG00000002_O.java | 31 ++++++ .../src/main/resources/application-local.yml | 6 +- .../impl/SolReqDetailUseCaseImplTest.java | 45 ++++++++ 9 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java create mode 100644 dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java new file mode 100644 index 00000000..6b3a7019 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.sol.converter; + +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.converter + * @className SolReqDetailConverter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Mapper(componentModel = "spring") +public interface SolReqDetailConverter { + + @Mapping(source = "srId", target = "srId") + SOLG00000002_I toLegacyRequest(SolReqDetailRequest req); + + SolReqDetailResponse toResponse(SOLG00000002_O mciRes); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java new file mode 100644 index 00000000..13045419 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.mcc.biz.sol.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.shinhanlife.dap.lib.annotation.McpParameter; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.dto + * @className SolReqDetailRequest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SolReqDetailRequest { + + @McpParameter(description = "상세 조회할 SOL 의뢰서 ID", required = true) + private String srId; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java new file mode 100644 index 00000000..b1c1c230 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java @@ -0,0 +1,33 @@ +package io.shinhanlife.dap.mcc.biz.sol.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.dto + * @className SolReqDetailResponse + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SolReqDetailResponse { + + private String srId; + private String srName; + private String process; + private String devStage; + private String appName; + private String requester; + private String requestDate; + private String dueDate; + private String description; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java new file mode 100644 index 00000000..043bb0a2 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java @@ -0,0 +1,39 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase; + +import io.shinhanlife.dap.lib.annotation.McpFunction; +import io.shinhanlife.dap.lib.annotation.McpTool; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase + * @className SolReqDetailUseCase + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@McpTool( + routingType = "MCI", + categoryKey = "sol" +) +public interface SolReqDetailUseCase { + + @McpFunction( + displayName = "SolReqDetail 툴", + name = "solReqDetail", + description = "SOL 의뢰서 상세 조회", + prompt = "SOL 의뢰서 상세 조회해줘", + mappingId = "SOLG00000002", + register = false, + requiresApproval = false, + readOnlyHint = true, + openWorldHint = true + ) + Object execute(SolReqDetailRequest req); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java new file mode 100644 index 00000000..be197500 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java @@ -0,0 +1,101 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqDetailConverter; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse; +import io.shinhanlife.dap.mcc.biz.sol.usecase.SolReqDetailUseCase; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_O; +import io.shinhanlife.glow.communication.dto.Transfer; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl + * @className SolReqDetailUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SolReqDetailUseCaseImpl implements SolReqDetailUseCase { + + private final MciNclgClient mci; + private final SolReqDetailConverter converter; + + @Value("${sol.req-detail.mock-enabled:false}") + private boolean mockEnabled; + + @Override + public Object execute(SolReqDetailRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "solReqDetail", req); + if (req == null || req.getSrId() == null || req.getSrId().isBlank()) { + return Map.of("status", "ERROR", "message", "srId는 필수입니다."); + } + + if (mockEnabled) { + return createLocalSampleResponse(req.getSrId()); + } + + try { + SOLG00000002_I mciRequest = converter.toLegacyRequest(req); + Transfer mciResponse = mci.callTo( + "SOLG00000002", "SOLG00000002", mciRequest, SOLG00000002_O.class); + + if (mciResponse == null || mciResponse.getBody() == null) { + return Map.of("status", "NOT_FOUND", "message", "의뢰서 상세 정보를 찾을 수 없습니다."); + } + return converter.toResponse(mciResponse.getBody()); + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of( + "status", "ERROR", + "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } + + private Object createLocalSampleResponse(String srId) { + SolReqDetailResponse response = new SolReqDetailResponse(); + if ("SR-2026-001".equalsIgnoreCase(srId)) { + response.setSrId("SR-2026-001"); + response.setSrName("AX HUB 메인 화면 UI 개편"); + response.setProcess("진행중"); + response.setDevStage("개발(단위테스트)"); + response.setAppName("AX HUB"); + response.setRequester("신한준"); + response.setRequestDate("2026-07-01"); + response.setDueDate("2026-08-31"); + response.setDescription("AX HUB 메인 화면의 사용성과 접근성을 개선하는 UI 개편 의뢰입니다."); + return response; + } + if ("SR-2026-002".equalsIgnoreCase(srId)) { + response.setSrId("SR-2026-002"); + response.setSrName("SOL 연동 모듈 추가 개발"); + response.setProcess("진행중"); + response.setDevStage("분석/설계"); + response.setAppName("MCP Gateway"); + response.setRequester("고석민"); + response.setRequestDate("2026-07-15"); + response.setDueDate("2026-09-30"); + response.setDescription("SOL 의뢰서 조회 기능을 MCP 도구로 제공하기 위한 연동 모듈 개발 의뢰입니다."); + return response; + } + return Map.of( + "status", "NOT_FOUND", + "message", "의뢰서를 찾을 수 없습니다.", + "srId", srId); + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java new file mode 100644 index 00000000..3b8b2cb1 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java @@ -0,0 +1,23 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io; + +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io + * @className SOLG00000002_I + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Data +public class SOLG00000002_I { + + private String srId; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java new file mode 100644 index 00000000..1aa61472 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io; + +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io + * @className SOLG00000002_O + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +@Data +public class SOLG00000002_O { + + private String srId; + private String srName; + private String process; + private String devStage; + private String appName; + private String requester; + private String requestDate; + private String dueDate; + private String description; +} diff --git a/dap-tool-oth/src/main/resources/application-local.yml b/dap-tool-oth/src/main/resources/application-local.yml index 13593bf4..a4e5f24c 100644 --- a/dap-tool-oth/src/main/resources/application-local.yml +++ b/dap-tool-oth/src/main/resources/application-local.yml @@ -36,4 +36,8 @@ axhub: gateway: url: http://localhost:8081 tool: - url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} \ No newline at end of file + url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} + +sol: + req-detail: + mock-enabled: true diff --git a/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java b/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java new file mode 100644 index 00000000..e2b245f0 --- /dev/null +++ b/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java @@ -0,0 +1,45 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqDetailConverter; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl + * @className SolReqDetailUseCaseImplTest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ *
+ * 
+ */ +class SolReqDetailUseCaseImplTest { + + @Test + void returnsLocalSampleDetailBySrId() { + MciNclgClient mci = Mockito.mock(MciNclgClient.class); + SolReqDetailConverter converter = Mockito.mock(SolReqDetailConverter.class); + SolReqDetailUseCaseImpl useCase = new SolReqDetailUseCaseImpl(mci, converter); + ReflectionTestUtils.setField(useCase, "mockEnabled", true); + + SolReqDetailRequest request = new SolReqDetailRequest(); + request.setSrId("SR-2026-001"); + + SolReqDetailResponse response = (SolReqDetailResponse) useCase.execute(request); + + assertThat(response.getSrId()).isEqualTo("SR-2026-001"); + assertThat(response.getSrName()).isEqualTo("AX HUB 메인 화면 UI 개편"); + Mockito.verifyNoInteractions(mci); + } +} From 2f5dad05dfb0cc71539d7d8586f43eac0e89ddce Mon Sep 17 00:00:00 2001 From: juheelee Date: Wed, 29 Jul 2026 15:34:01 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EB=A9=94=ED=83=80=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=EC=BD=94=EB=93=9C=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/MetaCommonCodeConverter.java | 31 +++++++ .../biz/cmm/dto/MetaCommonCodeRequest.java | 32 +++++++ .../biz/cmm/dto/MetaCommonCodeResponse.java | 37 ++++++++ .../cmm/usecase/MetaCommonCodeUseCase.java | 37 ++++++++ .../impl/MetaCommonCodeUseCaseImpl.java | 89 +++++++++++++++++++ 5 files changed, 226 insertions(+) create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java new file mode 100644 index 00000000..47b2bebf --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.converter + * @className MetaCommonCodeConverter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  0986406    최초생성
+ * 
+ * 
+ */ +@Mapper(componentModel = "spring") +public interface MetaCommonCodeConverter { + @Mapping(target = "csNo", source = "groupCode", defaultValue = "GRP_COMM_CD") + CLCNNB00001_I toLegacyRequest(MetaCommonCodeRequest req); + + @Mapping(target = "codeList", ignore = true) + MetaCommonCodeResponse toResponse(CLCNNB00001_O mciRes); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java new file mode 100644 index 00000000..1cb36e79 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.shinhanlife.dap.lib.annotation.McpParameter; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaCommonCodeRequest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaCommonCodeRequest { + @McpParameter(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false) + private String groupCode; + + @McpParameter(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false) + private String codeName; + + @McpParameter(description = "사용여부 (예: Y, N)", required = false) + private String useYn; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java new file mode 100644 index 00000000..751f2404 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.List; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaCommonCodeResponse + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaCommonCodeResponse { + private List codeList; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class MetaCommonCodeItem { + private String groupCode; + private String code; + private String codeName; + private String codeDesc; + private Integer sortSeq; + private String useYn; + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java new file mode 100644 index 00000000..2e2ac524 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import io.shinhanlife.dap.lib.annotation.McpFunction; +import io.shinhanlife.dap.lib.annotation.McpTool; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className MetaCommonCodeUseCase + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  0986406    최초생성
+ * 
+ * 
+ */ +@McpTool( + routingType = "MCI", + categoryKey = "cmm" +) +public interface MetaCommonCodeUseCase { + @McpFunction( + displayName = "메타 통합코드 조회 툴", + name = "metaCommonCode", + description = "메타 통합코드 목록을 조회해줘", + prompt = "메타 통합코드 목록을 조회해줘", + mappingId = "CLCNNB00001", + register = false, + requiresApproval = false, + openWorldHint = true + ) + Object execute(MetaCommonCodeRequest req); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java new file mode 100644 index 00000000..4c51e6f6 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java @@ -0,0 +1,89 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaCommonCodeConverter; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse.MetaCommonCodeItem; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaCommonCodeUseCase; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl + * @className MetaCommonCodeUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  0986406    최초생성
+ * 
+ * 
+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MetaCommonCodeUseCaseImpl implements MetaCommonCodeUseCase { + + private final MciCfpaClient mci; + private final MetaCommonCodeConverter converter; + + @Override + public Object execute(MetaCommonCodeRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaCommonCode", req); + try { + CLCNNB00001_I mciRequest = converter.toLegacyRequest(req); + + Object mciResponse = mci.callCfpa0001(mciRequest); + log.info("[MCI Tool] CLCNNB00001 MCI call completed. Returning response status: {}", + mciResponse != null); + + // 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 메타 통합코드 샘플 결과를 반환합니다. + MetaCommonCodeResponse res = new MetaCommonCodeResponse(); + List list = new ArrayList<>(); + + MetaCommonCodeItem item1 = new MetaCommonCodeItem(); + item1.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD"); + item1.setCode("CD001"); + item1.setCodeName("진행중"); + item1.setCodeDesc("SR 요청 처리 진행 중 상태"); + item1.setSortSeq(1); + item1.setUseYn("Y"); + list.add(item1); + + MetaCommonCodeItem item2 = new MetaCommonCodeItem(); + item2.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD"); + item2.setCode("CD002"); + item2.setCodeName("완료"); + item2.setCodeDesc("SR 요청 처리 완료 상태"); + item2.setSortSeq(2); + item2.setUseYn("Y"); + list.add(item2); + + MetaCommonCodeItem item3 = new MetaCommonCodeItem(); + item3.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD"); + item3.setCode("CD003"); + item3.setCodeName("보류"); + item3.setCodeDesc("SR 요청 처리 일시 보류 상태"); + item3.setSortSeq(3); + item3.setUseYn("N"); + list.add(item3); + + res.setCodeList(list); + + return res; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } +} From c6a2f8245547439dd40888bc2c919d8b52b5eeab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=EA=B2=B8?= Date: Wed, 29 Jul 2026 16:08:45 +0900 Subject: [PATCH 4/8] push TEST --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d64e909..4028631a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DAP Backend -Spring Boot 기반 DAP 관리자 백엔드 API 서버 및 MCP(Model Context Protocol) Gateway / Tool 분산 서버 프로젝트입니다. +Spring Boot 기반 DAP 관리자 백엔드 API 서버 및 MCP(Model Context Protocol) Gateway / Tool 분산 서버 프로젝트 입니다. --- From b68fbb9726de1119e64a5a9c9c60702d3af34c82 Mon Sep 17 00:00:00 2001 From: juheelee Date: Wed, 29 Jul 2026 16:48:28 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=EB=A9=94=ED=83=80=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=EC=BD=94=EB=93=9C=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java | 4 ++-- .../dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java | 4 ++-- .../dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java | 4 ++-- .../dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java | 4 ++-- .../mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java index 47b2bebf..87f56ee9 100644 --- a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java @@ -11,13 +11,13 @@ import org.mapstruct.Mapping; * @package io.shinhanlife.dap.mcc.biz.cmm.converter * @className MetaCommonCodeConverter * @description AX HUB 시스템 처리 클래스 - * @author 0986406 + * @author 09863409 * @create 2026.07.29 *
  * ---------- 개정이력 ----------
  * 수정일      수정자    수정내용
  * ---------- -------- ---------------------------
- * 2026.07.29  0986406    최초생성
+ * 2026.07.29  09863409    최초생성
  * 
  * 
*/ diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java index 1cb36e79..a28a3afe 100644 --- a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java @@ -8,13 +8,13 @@ import lombok.Data; * @package io.shinhanlife.dap.mcc.biz.cmm.dto * @className MetaCommonCodeRequest * @description AX HUB 시스템 처리 클래스 - * @author 0986406 + * @author 09863409 * @create 2026.07.29 *
  * ---------- 개정이력 ----------
  * 수정일      수정자    수정내용
  * ---------- -------- ---------------------------
- * 2026.07.29  0986406    최초생성
+ * 2026.07.29  09863409    최초생성
  * 
  * 
*/ diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java index 751f2404..653a32a2 100644 --- a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java @@ -9,13 +9,13 @@ import java.util.List; * @package io.shinhanlife.dap.mcc.biz.cmm.dto * @className MetaCommonCodeResponse * @description AX HUB 시스템 처리 클래스 - * @author 0986406 + * @author 09863409 * @create 2026.07.29 *
  * ---------- 개정이력 ----------
  * 수정일      수정자    수정내용
  * ---------- -------- ---------------------------
- * 2026.07.29  0986406    최초생성
+ * 2026.07.29  09863409    최초생성
  * 
  * 
*/ diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java index 2e2ac524..f8c2a52a 100644 --- a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java @@ -8,13 +8,13 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; * @package io.shinhanlife.dap.mcc.biz.cmm.usecase * @className MetaCommonCodeUseCase * @description AX HUB 시스템 처리 클래스 - * @author 0986406 + * @author 09863409 * @create 2026.07.29 *
  * ---------- 개정이력 ----------
  * 수정일      수정자    수정내용
  * ---------- -------- ---------------------------
- * 2026.07.29  0986406    최초생성
+ * 2026.07.29  09863409    최초생성
  * 
  * 
*/ diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java index 4c51e6f6..92ddf781 100644 --- a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java @@ -19,13 +19,13 @@ import java.util.Map; * @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl * @className MetaCommonCodeUseCaseImpl * @description AX HUB 시스템 처리 클래스 - * @author 0986406 + * @author 09863409 * @create 2026.07.29 *
  * ---------- 개정이력 ----------
  * 수정일      수정자    수정내용
  * ---------- -------- ---------------------------
- * 2026.07.29  0986406    최초생성
+ * 2026.07.29  09863409    최초생성
  * 
  * 
*/ From c551b153787fbff4540e7c40f8a5bebb2d434837 Mon Sep 17 00:00:00 2001 From: jade Date: Wed, 29 Jul 2026 17:01:05 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=91=B8?= =?UTF-8?q?=EC=89=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/shinhanlife/dap/mcc/biz/cmm/dto/BalanceRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BalanceRequest.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BalanceRequest.java index a16c0c7f..1e8df091 100644 --- a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BalanceRequest.java +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BalanceRequest.java @@ -21,7 +21,7 @@ import lombok.Data; @Data public class BalanceRequest { - @McpParameter(description = "고객의 계좌번호 (- 제외)", required = true) + @McpParameter(description = "고객의 계좌번호 (- 제외) ", required = true) @McpValidation(pattern = "\\S") private String accountNumber; } From a667431de154789b88864e5a0ac8b40ca351b335 Mon Sep 17 00:00:00 2001 From: juheelee Date: Wed, 29 Jul 2026 17:27:27 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=EB=A9=94=ED=83=80=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../biz/cmm/converter/MetaTableConverter.java | 31 +++++++ .../dap/mcc/biz/cmm/dto/MetaTableRequest.java | 32 +++++++ .../mcc/biz/cmm/dto/MetaTableResponse.java | 37 ++++++++ .../mcc/biz/cmm/usecase/MetaTableUseCase.java | 37 ++++++++ .../usecase/impl/MetaTableUseCaseImpl.java | 89 +++++++++++++++++++ 5 files changed, 226 insertions(+) create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java create mode 100644 dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java new file mode 100644 index 00000000..c59e3524 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.converter + * @className MetaTableConverter + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Mapper(componentModel = "spring") +public interface MetaTableConverter { + @Mapping(target = "csNo", source = "tableName", defaultValue = "TB_META_BAS") + CLCNNB00001_I toLegacyRequest(MetaTableRequest req); + + @Mapping(target = "tableList", ignore = true) + MetaTableResponse toResponse(CLCNNB00001_O mciRes); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java new file mode 100644 index 00000000..1712fdec --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.shinhanlife.dap.lib.annotation.McpParameter; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaTableRequest + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaTableRequest { + @McpParameter(description = "테이블 물리명 키워드 (예: TB_CUST_BAS, TB_CONT)", required = false) + private String tableName; + + @McpParameter(description = "테이블 논리명(한글) 키워드 (예: 고객기본, 계약)", required = false) + private String tableLogicalName; + + @McpParameter(description = "스키마/소유자명 (예: DAPADM, SHLOWN)", required = false) + private String owner; +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java new file mode 100644 index 00000000..1b9eaf6b --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.List; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaTableResponse + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaTableResponse { + private List tableList; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class MetaTableItem { + private String owner; + private String tableName; + private String tableLogicalName; + private String tableDesc; + private Integer columnCount; + private Long rowCount; + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java new file mode 100644 index 00000000..e2ec7c64 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import io.shinhanlife.dap.lib.annotation.McpFunction; +import io.shinhanlife.dap.lib.annotation.McpTool; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className MetaTableUseCase + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@McpTool( + routingType = "MCI", + categoryKey = "cmm" +) +public interface MetaTableUseCase { + @McpFunction( + displayName = "메타 테이블 조회 툴", + name = "metaTable", + description = "메타 테이블 정보 목록을 조회해줘", + prompt = "메타 테이블 정보 목록을 조회해줘", + mappingId = "CLCNNB00001", + register = false, + requiresApproval = false, + openWorldHint = true + ) + Object execute(MetaTableRequest req); +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java new file mode 100644 index 00000000..ef981898 --- /dev/null +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java @@ -0,0 +1,89 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaTableConverter; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse.MetaTableItem; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaTableUseCase; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl + * @className MetaTableUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.07.29  09863409    최초생성
+ * 
+ * 
+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MetaTableUseCaseImpl implements MetaTableUseCase { + + private final MciCfpaClient mci; + private final MetaTableConverter converter; + + @Override + public Object execute(MetaTableRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaTable", req); + try { + CLCNNB00001_I mciRequest = converter.toLegacyRequest(req); + + Object mciResponse = mci.callCfpa0001(mciRequest); + log.info("[MCI Tool] CLCNNB00001 MCI call completed. Returning response status: {}", + mciResponse != null); + + // 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 메타 테이블 샘플 결과를 반환합니다. + MetaTableResponse res = new MetaTableResponse(); + List list = new ArrayList<>(); + + MetaTableItem item1 = new MetaTableItem(); + item1.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM"); + item1.setTableName(req.getTableName() != null ? req.getTableName() : "TB_CUST_BAS"); + item1.setTableLogicalName("고객기본정보"); + item1.setTableDesc("고객 기본 프로필 및 인적사항 관리 테이블"); + item1.setColumnCount(35); + item1.setRowCount(1250000L); + list.add(item1); + + MetaTableItem item2 = new MetaTableItem(); + item2.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM"); + item2.setTableName("TB_CONT_MCD"); + item2.setTableLogicalName("계약주계약정보"); + item2.setTableDesc("보험 계약 주계약 상세 원장 테이블"); + item2.setColumnCount(58); + item2.setRowCount(3400000L); + list.add(item2); + + MetaTableItem item3 = new MetaTableItem(); + item3.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM"); + item3.setTableName("TB_CLAIM_DTL"); + item3.setTableLogicalName("청구접수상세"); + item3.setTableDesc("보험금 청구 접수 건별 내역 테이블"); + item3.setColumnCount(42); + item3.setRowCount(890000L); + list.add(item3); + + res.setTableList(list); + + return res; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } +} From 5fe8590695c53ff81cf8888716f37eb1c78ef68b Mon Sep 17 00:00:00 2001 From: jade Date: Wed, 29 Jul 2026 23:03:11 +0900 Subject: [PATCH 8/8] feat: use MCP SDK for tool pod calls --- .../dap/mcg/service/ExecuteService.java | 11 +- .../dap/mcg/transport/HttpToolInvoker.java | 102 ++++++++++-------- .../dap/mcg/transport/ToolInvoker.java | 2 +- dap-tool-core/build.gradle | 3 +- .../mcc/mcp/ToolMcpServerConfiguration.java | 26 +++++ .../mcc/mcp/ToolPodMcpToolSynchronizer.java | 74 +++++++++++++ .../presentation/BusinessToolController.java | 19 ++-- .../ToolArgumentSchemaValidator.java | 28 +++++ .../dap/lib/util/JsonSchemaGeneratorTest.java | 16 +-- .../mcp/ToolMcpServerConfigurationTest.java | 22 ++++ .../ToolArgumentSchemaValidatorTest.java | 25 +++++ 11 files changed, 254 insertions(+), 74 deletions(-) create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java create mode 100644 dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java create mode 100644 dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java index dfb7384f..e4c51559 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java @@ -261,7 +261,6 @@ public class ExecuteService { if (metadata.getPodUrl() != null && !metadata.getPodUrl().isEmpty()) { targetUrl = metadata.getPodUrl(); } - String executeApiUrl = targetUrl + "/mcp/" + metadata.getName(); Map headers = new java.util.HashMap<>(); headers.put("trace-id", context.requestId()); @@ -279,16 +278,16 @@ public class ExecuteService { JsonNode data = null; try { - data = toolInvoker.invoke(pagePayload, executeApiUrl, headers); + data = toolInvoker.invoke(metadata.getName(), pagePayload, targetUrl, headers); } catch (org.springframework.web.client.RestClientResponseException e) { // HTTP 4xx, 5xx 에러는 연결 오류가 아니라 비즈니스 로직 오류이거나 검증 실패이므로 원본 에러를 그대로 반환 throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod HTTP 에러 (" + e.getStatusCode() + "): " + e.getResponseBodyAsString()); } catch (Exception e) { - if (executeApiUrl.contains("http://tool-")) { - String fallbackUrl = executeApiUrl.replaceAll("http://tool-[a-zA-Z0-9-]+", "http://localhost"); + if (targetUrl.contains("http://tool-")) { + String fallbackUrl = targetUrl.replaceAll("http://tool-[a-zA-Z0-9-]+", "http://localhost"); log.warn(" [ExecuteService] 호스트를 찾을 수 없어 localhost로 재시도합니다: {}", fallbackUrl); try { - data = toolInvoker.invoke(pagePayload, fallbackUrl, headers); + data = toolInvoker.invoke(metadata.getName(), pagePayload, fallbackUrl, headers); } catch (Exception ex) { throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod 호출 실패 (localhost 재시도 포함): " + ex.getMessage()); } @@ -385,4 +384,4 @@ public class ExecuteService { public void shutdown() { executor.shutdownNow(); } -} \ No newline at end of file +} diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/HttpToolInvoker.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/HttpToolInvoker.java index 54445066..b6827dd5 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/HttpToolInvoker.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/HttpToolInvoker.java @@ -1,69 +1,81 @@ package io.shinhanlife.dap.mcg.transport; - -/** - * @package io.shinhanlife.dap.mcg.transport - * @className HttpToolInvoker - * @description AX HUB 시스템 처리 클래스 - * @author 0986406 - * @create 2026.09.01 - *
- * ---------- 개정이력 ----------
- * 수정일      수정자    수정내용
- * ---------- -------- ---------------------------
- * 2026.09.01  0986406    최초생성
- * 
- * 
- */ -import io.shinhanlife.dap.mcg.resilience.FailureType; -import io.shinhanlife.dap.mcg.resilience.ToolExecutionException; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestClient; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; - +import com.fasterxml.jackson.databind.node.TextNode; +import io.modelcontextprotocol.client.McpClient; +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; +import io.modelcontextprotocol.spec.McpSchema; +import io.shinhanlife.dap.mcg.resilience.FailureType; +import io.shinhanlife.dap.mcg.resilience.ToolExecutionException; +import java.net.http.HttpRequest; +import java.time.Duration; import java.util.Map; -import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +/** MCP SDK client used for Gateway-to-Tool-Pod calls over Streamable HTTP. */ @Slf4j @Component public class HttpToolInvoker implements ToolInvoker { - private final RestClient restClient; private final ObjectMapper objectMapper; public HttpToolInvoker(ObjectMapper objectMapper) { this.objectMapper = objectMapper; - this.restClient = RestClient.create(); } @Override - public JsonNode invoke(Map payload, String targetUrl, Map headers) { - try { - RestClient.RequestBodySpec requestSpec = restClient.post() - .uri(targetUrl) - .contentType(MediaType.APPLICATION_JSON); - - if (headers != null) { - headers.forEach(requestSpec::header); + public JsonNode invoke(String toolName, Map arguments, String podUrl, Map headers) { + String endpoint = podUrl.replaceAll("/+$", "") + "/mcp"; + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(); + if (headers != null) { + headers.forEach(requestBuilder::header); + } + + HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(endpoint) + .requestBuilder(requestBuilder) + .connectTimeout(Duration.ofSeconds(5)) + .build(); + try (McpSyncClient client = McpClient.sync(transport) + .clientInfo(new McpSchema.Implementation("dap-gateway", "1.0.0")) + .requestTimeout(Duration.ofSeconds(30)) + .build()) { + client.initialize(); + McpSchema.CallToolResult result = client.callTool(McpSchema.CallToolRequest.builder() + .name(toolName) + .arguments(arguments) + .build()); + if (Boolean.TRUE.equals(result.isError())) { + throw new ToolExecutionException(FailureType.BUSINESS_ERROR, "Tool Pod MCP error: " + textContent(result)); } - - Object httpResult = requestSpec.body(payload) - .retrieve() - .body(Object.class); - - return extractData(objectMapper.valueToTree(httpResult)); - } catch (Exception e) { - throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod HTTP 호출 실패: " + e.getMessage(), e); + return result.structuredContent() != null + ? objectMapper.valueToTree(result.structuredContent()) + : textContentAsJson(result); + } catch (ToolExecutionException error) { + throw error; + } catch (Exception error) { + throw new ToolExecutionException(FailureType.SERVER_ERROR, + "Tool Pod MCP call failed: " + error.getMessage(), error); } } - private JsonNode extractData(JsonNode root) { - if (!root.path("success").asBoolean(true)) { - throw new ToolExecutionException(FailureType.BUSINESS_ERROR, "Tool 서버 업무 오류: " + root.path("error").asText()); + private JsonNode textContentAsJson(McpSchema.CallToolResult result) { + String text = textContent(result); + try { + return objectMapper.readTree(text); + } catch (Exception ignored) { + return TextNode.valueOf(text); } - return root.has("data") ? root.get("data") : root; + } + + private String textContent(McpSchema.CallToolResult result) { + return result.content().stream() + .filter(McpSchema.TextContent.class::isInstance) + .map(McpSchema.TextContent.class::cast) + .map(McpSchema.TextContent::text) + .findFirst() + .orElse(""); } } diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/ToolInvoker.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/ToolInvoker.java index 74e2db22..649638d1 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/ToolInvoker.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/ToolInvoker.java @@ -22,5 +22,5 @@ import java.util.Map; * Tool 서버 호출 transport의 최소 공통 인터페이스입니다. */ public interface ToolInvoker { - JsonNode invoke(Map payload, String targetUrl, Map headers); + JsonNode invoke(String toolName, Map arguments, String podUrl, Map headers); } diff --git a/dap-tool-core/build.gradle b/dap-tool-core/build.gradle index 66471abf..97785ca4 100644 --- a/dap-tool-core/build.gradle +++ b/dap-tool-core/build.gradle @@ -4,6 +4,7 @@ plugins { dependencies { api 'org.springframework.boot:spring-boot-starter-web' + api 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc' api 'org.springframework.boot:spring-boot-starter-validation' api 'org.springframework.boot:spring-boot-starter-data-redis' @@ -24,7 +25,7 @@ dependencies { api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1' api 'com.fasterxml.jackson.core:jackson-databind:2.17.1' - api 'com.networknt:json-schema-validator:1.4.0' + api 'com.networknt:json-schema-validator:3.0.0' api 'org.springframework.kafka:spring-kafka:3.2.0' api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' } diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java new file mode 100644 index 00000000..b07916ab --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java @@ -0,0 +1,26 @@ +package io.shinhanlife.dap.mcc.mcp; + +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** Exposes every Tool Pod through the MCP Streamable HTTP transport. */ +@Configuration +public class ToolMcpServerConfiguration { + + @Bean + @Primary + public HttpServletStreamableServerTransportProvider toolMcpTransportProvider() { + return HttpServletStreamableServerTransportProvider.builder() + .mcpEndpoint("/mcp") + .build(); + } + + @Bean + public ServletRegistrationBean toolMcpServlet( + HttpServletStreamableServerTransportProvider transportProvider) { + return new ServletRegistrationBean<>(transportProvider, "/mcp"); + } +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java new file mode 100644 index 00000000..4fe9ca75 --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java @@ -0,0 +1,74 @@ +package io.shinhanlife.dap.mcc.mcp; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.server.McpServerFeatures; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.spec.McpSchema; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import io.shinhanlife.dap.mcc.presentation.BusinessToolController; +import io.shinhanlife.dap.mcc.usecase.ToolRegistryHeartbeatSender; +import java.util.LinkedHashMap; +import java.util.Map; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +/** Registers the Tool Pod's existing annotated tools with its MCP SDK server. */ +@Component +public class ToolPodMcpToolSynchronizer { + private final McpSyncServer mcpServer; + private final ToolRegistryHeartbeatSender heartbeatSender; + private final BusinessToolController businessToolController; + private final ObjectMapper objectMapper; + + public ToolPodMcpToolSynchronizer(McpSyncServer mcpServer, ToolRegistryHeartbeatSender heartbeatSender, + BusinessToolController businessToolController, ObjectMapper objectMapper) { + this.mcpServer = mcpServer; + this.heartbeatSender = heartbeatSender; + this.businessToolController = businessToolController; + this.objectMapper = objectMapper; + } + + @EventListener(ApplicationReadyEvent.class) + public void registerLocalTools() { + heartbeatSender.getAllScannedTools().stream() + .filter(tool -> Boolean.TRUE.equals(tool.getVisible())) + .forEach(tool -> mcpServer.addTool(specification(tool))); + } + + private McpServerFeatures.SyncToolSpecification specification(ToolMetadata tool) { + McpSchema.Tool mcpTool = McpSchema.Tool.builder() + .name(tool.getName()) + .description(tool.getDescription() == null || tool.getDescription().isBlank() ? tool.getName() + " Tool" : tool.getDescription()) + .inputSchema(tool.getParametersSchema() == null ? emptySchema() : tool.getParametersSchema()) + .annotations(McpSchema.ToolAnnotations.builder() + .readOnlyHint(Boolean.TRUE.equals(tool.getReadOnlyHint())) + .destructiveHint(Boolean.TRUE.equals(tool.getDestructiveHint())) + .idempotentHint(Boolean.TRUE.equals(tool.getIdempotentHint())) + .openWorldHint(Boolean.TRUE.equals(tool.getOpenWorldHint())).build()) + .build(); + return McpServerFeatures.SyncToolSpecification.builder().tool(mcpTool) + .callHandler((context, request) -> invoke(tool.getName(), request.arguments())).build(); + } + + private McpSchema.CallToolResult invoke(String toolName, Map arguments) { + ResponseEntity response = businessToolController.executeDynamicTool(toolName, null, null, null, arguments); + boolean failed = !response.getStatusCode().is2xxSuccessful(); + Object body = response.getBody(); + try { + return McpSchema.CallToolResult.builder().addTextContent(objectMapper.writeValueAsString(body)) + .structuredContent(body).isError(failed).build(); + } catch (Exception error) { + return McpSchema.CallToolResult.builder().addTextContent(String.valueOf(body)).isError(failed).build(); + } + } + + private Map emptySchema() { + Map schema = new LinkedHashMap<>(); + schema.put("type", "object"); + schema.put("properties", Map.of()); + schema.put("additionalProperties", false); + return schema; + } +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java index 3c722d3d..64bde2c2 100644 --- a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java @@ -16,10 +16,7 @@ package io.shinhanlife.dap.mcc.presentation; * */ import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; +import com.networknt.schema.Error; import io.shinhanlife.dap.lib.annotation.McpFunction; import io.shinhanlife.dap.lib.annotation.McpTool; import io.shinhanlife.dap.lib.config.McpProperties; @@ -58,6 +55,7 @@ public class BusinessToolController { private final ObjectMapper objectMapper; private final McpProperties mcpProperties; private final ToolRegistryHeartbeatSender toolRegistryHeartbeatSender; + private final ToolArgumentSchemaValidator toolArgumentSchemaValidator; // 내부 조회용 로컬 Tool 목록 엔드포인트 @GetMapping("/mcp/api/v1/tools/local") @@ -149,17 +147,12 @@ public class BusinessToolController { if (!Map.class.isAssignableFrom(paramType)) { try { Map autoSchema = JsonSchemaGenerator.generateSchema(paramType); - String fullSchemaJson = objectMapper.writeValueAsString(autoSchema); - - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - JsonSchema schema = factory.getSchema(fullSchemaJson); - - Set errors = schema.validate(objectMapper.valueToTree(arguments)); + List errors = toolArgumentSchemaValidator.validate(autoSchema, arguments); if (!errors.isEmpty()) { log.error("[Tool] 파라미터 유효성 검증 실패: {}", errors); List errorMessages = new ArrayList<>(); - for (ValidationMessage vm : errors) { - errorMessages.add(vm.getMessage()); + for (Error validationError : errors) { + errorMessages.add(validationError.getMessage()); } Map errorDetails = new HashMap<>(); errorDetails.put("status", "422"); @@ -229,4 +222,4 @@ public class BusinessToolController { return ResponseEntity.status(502).body(errorBody); } } -} \ No newline at end of file +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java new file mode 100644 index 00000000..876144ca --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java @@ -0,0 +1,28 @@ +package io.shinhanlife.dap.mcc.presentation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import java.util.List; +import java.util.Map; +import org.springframework.stereotype.Component; + +/** Validates tool arguments with the NetworkNT version selected by the MCP SDK. */ +@Component +public class ToolArgumentSchemaValidator { + + private final ObjectMapper objectMapper; + + public ToolArgumentSchemaValidator(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public List validate(Map schemaDefinition, Map arguments) throws Exception { + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = schemaRegistry.getSchema(objectMapper.writeValueAsString(schemaDefinition)); + return schema.validate(objectMapper.writeValueAsString(arguments), InputFormat.JSON); + } +} diff --git a/dap-tool-core/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java b/dap-tool-core/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java index c7e648a8..55eaf59e 100644 --- a/dap-tool-core/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java +++ b/dap-tool-core/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java @@ -5,15 +5,15 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; import io.shinhanlife.dap.lib.annotation.McpParameter; import io.shinhanlife.dap.lib.annotation.McpValidation; import java.util.List; import java.util.Map; -import java.util.Set; import org.junit.jupiter.api.Test; /** @@ -65,10 +65,10 @@ class JsonSchemaGeneratorTest { @Test void validatorRejectsInvalidNestedValue() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); - JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7) + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7) .getSchema(objectMapper.writeValueAsString(JsonSchemaGenerator.generateSchema(NestedRequest.class))); - Set errors = schema.validate(objectMapper.valueToTree(Map.of( - "child", Map.of("businessDate", "2026-07-28")))); + List errors = schema.validate(objectMapper.writeValueAsString(Map.of( + "child", Map.of("businessDate", "2026-07-28"))), InputFormat.JSON); assertFalse(errors.isEmpty()); } diff --git a/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java new file mode 100644 index 00000000..c0fb8101 --- /dev/null +++ b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java @@ -0,0 +1,22 @@ +package io.shinhanlife.dap.mcc.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.servlet.ServletRegistrationBean; + +class ToolMcpServerConfigurationTest { + + private final ToolMcpServerConfiguration configuration = new ToolMcpServerConfiguration(); + + @Test + void exposesOnlyExactMcpEndpointSoLegacyMcpApiPathsRemainAvailable() { + HttpServletStreamableServerTransportProvider transport = configuration.toolMcpTransportProvider(); + ServletRegistrationBean registration = configuration.toolMcpServlet(transport); + + assertEquals("/mcp", registration.getUrlMappings().iterator().next()); + assertFalse(registration.getUrlMappings().contains("/mcp/*")); + } +} diff --git a/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java new file mode 100644 index 00000000..e10dcc7d --- /dev/null +++ b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java @@ -0,0 +1,25 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ToolArgumentSchemaValidatorTest { + + private final ToolArgumentSchemaValidator validator = new ToolArgumentSchemaValidator(new ObjectMapper()); + + @Test + void validatesDraft7SchemaWithTheRuntimeNetworkntVersion() throws Exception { + Map schema = Map.of( + "type", "object", + "properties", Map.of("name", Map.of("type", "string")), + "required", List.of("name")); + + assertTrue(validator.validate(schema, Map.of("name", "Hong")).isEmpty()); + assertFalse(validator.validate(schema, Map.of()).isEmpty()); + } +}