forked from kimhyungsik/ax_hub_mcp_tool
refactor: migrate tools to Spring AI MCP annotations
This commit is contained in:
@@ -58,7 +58,7 @@ public class EaiEimsSender implements EimsSender {
|
|||||||
log.warn(" 로컬 환경이거나 Ka<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
|
log.warn(" 로컬 환경이거나 Ka<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
|
||||||
"<EaiMessage>\n" +
|
"<EaiMessage>\n" +
|
||||||
" <Header>\n" +
|
" <Header>\n" +
|
||||||
" <ChannelId>MCP_WAS_ASYNC</ChannelId>\n" +
|
" <ChannelId>MCP_GATEWAY_ASYNC</ChannelId>\n" +
|
||||||
" <InterfaceId>EAI_BATCH_JOB</InterfaceId>\n" +
|
" <InterfaceId>EAI_BATCH_JOB</InterfaceId>\n" +
|
||||||
" <Timestamp>1782705901850</Timestamp>\n" +
|
" <Timestamp>1782705901850</Timestamp>\n" +
|
||||||
" <TransferType>ASYNC</TransferType>\n" +
|
" <TransferType>ASYNC</TransferType>\n" +
|
||||||
@@ -91,7 +91,7 @@ public class EaiEimsSender implements EimsSender {
|
|||||||
return String.format(
|
return String.format(
|
||||||
"<EaiMessage>" +
|
"<EaiMessage>" +
|
||||||
"<Header>" +
|
"<Header>" +
|
||||||
"<ChannelId>MCP_WAS_ASYNC</ChannelId>" +
|
"<ChannelId>MCP_GATEWAY_ASYNC</ChannelId>" +
|
||||||
"<InterfaceId>%s</InterfaceId>" +
|
"<InterfaceId>%s</InterfaceId>" +
|
||||||
"<Timestamp>%d</Timestamp>" +
|
"<Timestamp>%d</Timestamp>" +
|
||||||
"<TransferType>ASYNC</TransferType>" +
|
"<TransferType>ASYNC</TransferType>" +
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
package io.shinhanlife.dap.lib.annotation;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.annotation
|
|
||||||
* @className McpFunction
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
public @interface McpFunction {
|
|
||||||
String displayName(); // 사람이 읽는 라벨 (예: "고객 조회 툴")
|
|
||||||
String name(); // MCP 서브툴 명칭 (예: "customer_search")
|
|
||||||
String description();
|
|
||||||
String prompt() default "";
|
|
||||||
String mappingId() default "";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tool 입력 JSON Schema를 인라인으로 지정한다. 지정하지 않으면 요청 DTO에서 자동 생성한다.
|
|
||||||
*/
|
|
||||||
String inputSchema() default "{}";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 복합 조건(anyOf 등)이 필요한 Tool의 입력 JSON Schema 클래스패스 경로다.
|
|
||||||
* inputSchemaResource가 지정되면 inputSchema 및 DTO 자동 생성보다 우선한다.
|
|
||||||
*/
|
|
||||||
// 추가: Redis 자동 등록 및 Heartbeat 대상 여부 제어
|
|
||||||
String inputSchemaResource() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tool response JSON Schema. When unset, output validation is skipped.
|
|
||||||
*/
|
|
||||||
String outputSchema() default "{}";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classpath resource for a complex Tool response JSON Schema.
|
|
||||||
* This value has priority over outputSchema.
|
|
||||||
*/
|
|
||||||
String outputSchemaResource() default "";
|
|
||||||
boolean register() default false;
|
|
||||||
|
|
||||||
// 추가: 툴 목록 노출 여부 제어 (false 시 라우팅은 되나 목록에서 숨김)
|
|
||||||
boolean visible() default true;
|
|
||||||
|
|
||||||
// 추가: HITL 승인 체계 지원 (실행 전 사용자 승인 필요 여부)
|
|
||||||
boolean requiresApproval() default false;
|
|
||||||
|
|
||||||
boolean readOnlyHint() default false;
|
|
||||||
boolean destructiveHint() default false;
|
|
||||||
boolean idempotentHint() default false;
|
|
||||||
boolean openWorldHint() default false;
|
|
||||||
|
|
||||||
/** Version exposed as _meta.version in the Tool Manifest. */
|
|
||||||
String version() default "1.0.0";
|
|
||||||
|
|
||||||
/** Maximum execution time exposed as _meta.timeoutMillis in the Tool Manifest. */
|
|
||||||
long timeoutMillis() default 300000L;
|
|
||||||
|
|
||||||
/** Whether the Tool is available for MCP exposure. */
|
|
||||||
boolean enabled() default true;
|
|
||||||
|
|
||||||
// 추가: 툴 별 기본 Timeout 설정 (기본 300초 = 300000ms)
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package io.shinhanlife.dap.lib.annotation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.annotation
|
|
||||||
* @className McpOutputSchema
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks a Tool response DTO for automatic output JSON Schema generation.
|
|
||||||
* Field constraints are declared with {@link McpValidation}.
|
|
||||||
*/
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
public @interface McpOutputSchema {
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package io.shinhanlife.dap.lib.annotation;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.annotation
|
|
||||||
* @className McpParameter
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
@Target(ElementType.FIELD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
public @interface McpParameter {
|
|
||||||
String description();
|
|
||||||
boolean required() default false;
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package io.shinhanlife.dap.lib.annotation;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.annotation
|
|
||||||
* @className McpTool
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
@Component
|
|
||||||
public @interface McpTool {
|
|
||||||
@AliasFor(annotation = Component.class)
|
|
||||||
String value() default "";
|
|
||||||
|
|
||||||
String categoryKey() default "common";
|
|
||||||
String routingType() default "HTTP";
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package io.shinhanlife.dap.lib.annotation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.annotation
|
|
||||||
* @className McpValidation
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.annotation
|
|
||||||
* @className McpValidation
|
|
||||||
* @description Declares JSON Schema validation constraints for MCP tool input fields
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.07.27
|
|
||||||
* <pre>
|
|
||||||
* ---------- revision history ----------
|
|
||||||
* date author description
|
|
||||||
* ---------- --------- ---------------------------
|
|
||||||
* 2026.07.27 0986406 initial creation
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Target(ElementType.FIELD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
public @interface McpValidation {
|
|
||||||
boolean required() default false;
|
|
||||||
String pattern() default "";
|
|
||||||
long minimum() default Long.MIN_VALUE;
|
|
||||||
long maximum() default Long.MAX_VALUE;
|
|
||||||
int minLength() default -1;
|
|
||||||
int maxLength() default -1;
|
|
||||||
String[] allowedValues() default {};
|
|
||||||
String format() default "";
|
|
||||||
boolean nullable() default false; String defaultValue() default "";
|
|
||||||
String[] examples() default {};
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
package io.shinhanlife.dap.lib.annotation;
|
package io.shinhanlife.dap.lib.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Spring AI @Tool 어노테이션을 보완하여 MCP 시스템 메타데이터를 추가 제공하는 힌트 어노테이션
|
||||||
* @package io.shinhanlife.dap.lib.annotation
|
* @package io.shinhanlife.dap.lib.annotation
|
||||||
* @className McpAnyOf
|
* @className ToolHint
|
||||||
* @description AX HUB 시스템 처리 클래스
|
* @description 비즈니스 로직(Tool)과 시스템 제어 메타데이터 분리
|
||||||
* @author 0986406
|
* @author 0986406
|
||||||
* @create 2026.09.01
|
* @create 2026.09.01
|
||||||
* <pre>
|
* <pre>
|
||||||
@@ -14,22 +20,12 @@ package io.shinhanlife.dap.lib.annotation;
|
|||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MCP 스키마 생성 시 anyOf (해당 필드들 중 최소 1개 이상 필수) 제약을 부여합니다.
|
|
||||||
*/
|
|
||||||
@Target({ElementType.TYPE})
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface McpAnyOf {
|
public @interface ToolHint {
|
||||||
/**
|
boolean register() default false;
|
||||||
* anyOf 제약에 포함될 필드명 목록
|
boolean requiresApproval() default false;
|
||||||
* 예: @McpAnyOf({"claimNo", "contractNo"})
|
String mappingId() default "";
|
||||||
*/
|
String inputSchemaResource() default "";
|
||||||
String[] value();
|
String outputSchemaResource() default "";
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,6 @@ package io.shinhanlife.dap.lib.aop;
|
|||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.lib.config.McpProperties;
|
import io.shinhanlife.dap.lib.config.McpProperties;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -24,6 +23,7 @@ import org.aspectj.lang.ProceedingJoinPoint;
|
|||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StopWatch;
|
import org.springframework.util.StopWatch;
|
||||||
|
|
||||||
@@ -35,12 +35,12 @@ public class ToolSlaMonitoringAspect {
|
|||||||
|
|
||||||
private final McpProperties mcpProperties;
|
private final McpProperties mcpProperties;
|
||||||
|
|
||||||
// @McpFunction 어노테이션이 붙은 모든 비즈니스 툴 메서드 실행을 가로챕니다.
|
// @McpTool 어노테이션이 붙은 모든 비즈니스 툴 메서드 실행을 가로챕니다.
|
||||||
@Around("@annotation(McpFunction)")
|
@Around("@annotation(org.springframework.ai.mcp.annotation.McpTool)")
|
||||||
public Object monitorToolSla(ProceedingJoinPoint joinPoint) throws Throwable {
|
public Object monitorToolSla(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||||
Method method = signature.getMethod();
|
Method method = signature.getMethod();
|
||||||
McpFunction functionAnnotation = method.getAnnotation(McpFunction.class);
|
McpTool functionAnnotation = method.getAnnotation(McpTool.class);
|
||||||
|
|
||||||
// 네임스페이스 자동 주입 로직을 반영하여 최종 툴 이름을 산출합니다.
|
// 네임스페이스 자동 주입 로직을 반영하여 최종 툴 이름을 산출합니다.
|
||||||
String baseName = functionAnnotation.name();
|
String baseName = functionAnnotation.name();
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.config;
|
package io.shinhanlife.dap.lib.config;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.config
|
|
||||||
* @className ToolSchemaConfiguration
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.shinhanlife.dap.lib.util.ToolSchemaResolver;
|
import io.shinhanlife.dap.lib.util.ToolSchemaResolver;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package io.shinhanlife.dap.lib.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.mcg.dto
|
||||||
|
* @className OperationType
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author 0986406
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 0986406 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public enum OperationType {
|
||||||
|
READ,
|
||||||
|
WRITE
|
||||||
|
}
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.manifest;
|
package io.shinhanlife.dap.lib.manifest;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.manifest
|
|
||||||
* @className ToolManifestAnnotations
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/** Behaviour hints exposed by the Tool Service manifest. */
|
/** Behaviour hints exposed by the Tool Service manifest. */
|
||||||
public record ToolManifestAnnotations(
|
public record ToolManifestAnnotations(
|
||||||
String title,
|
String title,
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.manifest;
|
package io.shinhanlife.dap.lib.manifest;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.manifest
|
|
||||||
* @className ToolManifestItem
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.manifest;
|
package io.shinhanlife.dap.lib.manifest;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.manifest
|
|
||||||
* @className ToolManifestMeta
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/** Operational metadata exposed by the Tool Service manifest. */
|
/** Operational metadata exposed by the Tool Service manifest. */
|
||||||
public record ToolManifestMeta(String version, long timeoutMillis, boolean enabled) {
|
public record ToolManifestMeta(String version, long timeoutMillis, boolean enabled) {
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.manifest;
|
package io.shinhanlife.dap.lib.manifest;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.manifest
|
|
||||||
* @className ToolManifestResponse
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Top-level response for GET /tool-manifest. */
|
/** Top-level response for GET /tool-manifest. */
|
||||||
|
|||||||
@@ -1,25 +1,9 @@
|
|||||||
package io.shinhanlife.dap.lib.manifest;
|
package io.shinhanlife.dap.lib.manifest;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.manifest
|
|
||||||
* @className ToolManifestService
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.shinhanlife.dap.lib.config.McpProperties;
|
import io.shinhanlife.dap.lib.config.McpProperties;
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import io.shinhanlife.dap.lib.mcp.LocalToolScanner;
|
import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -46,9 +30,9 @@ public class ToolManifestService {
|
|||||||
private String lastRevision;
|
private String lastRevision;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public ToolManifestService(LocalToolScanner localToolScanner, ObjectMapper objectMapper,
|
public ToolManifestService(ToolRegistryHeartbeatSender heartbeatSender, ObjectMapper objectMapper,
|
||||||
McpProperties properties) {
|
McpProperties properties) {
|
||||||
this(localToolScanner::getAllScannedTools, objectMapper, properties);
|
this(heartbeatSender::getAllScannedTools, objectMapper, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolManifestService(Supplier<List<ToolMetadata>> toolSupplier, ObjectMapper objectMapper,
|
ToolManifestService(Supplier<List<ToolMetadata>> toolSupplier, ObjectMapper objectMapper,
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
package io.shinhanlife.dap.lib.mcp;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.service
|
|
||||||
* @className LocalToolScanner
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
|
||||||
import io.shinhanlife.dap.lib.config.McpProperties;
|
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
|
||||||
import io.shinhanlife.dap.lib.util.ToolSchemaResolver;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.aop.support.AopUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
@Configuration
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class LocalToolScanner {
|
|
||||||
|
|
||||||
private final ApplicationContext applicationContext;
|
|
||||||
private final ObjectMapper objectMapper;
|
|
||||||
private final McpProperties mcpProperties;
|
|
||||||
private final ToolSchemaResolver toolSchemaResolver;
|
|
||||||
|
|
||||||
@Value("${axhub.tool.url:http://localhost:8080}")
|
|
||||||
private String podUrl;
|
|
||||||
|
|
||||||
private List<ToolMetadata> registeredTools = new ArrayList<>();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private List<ToolMetadata> allScannedTools = new ArrayList<>();
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
log.info(" [LocalToolScanner] 초기화 시작. Pod URL: {}", podUrl);
|
|
||||||
scanAndBuildMetadata();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scanAndBuildMetadata() {
|
|
||||||
Map<String, Object> allBeans = applicationContext.getBeansOfType(Object.class);
|
|
||||||
for (Object bean : allBeans.values()) {
|
|
||||||
Class<?> targetClass = AopUtils.getTargetClass(bean);
|
|
||||||
|
|
||||||
// 클래스 또는 프록시(인터페이스)에서 @McpTool 스캔
|
|
||||||
McpTool toolAnnotation = AnnotationUtils.findAnnotation(targetClass, McpTool.class);
|
|
||||||
if (toolAnnotation == null) {
|
|
||||||
toolAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), McpTool.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Method method : targetClass.getDeclaredMethods()) {
|
|
||||||
// 메서드, 수퍼클래스, 인터페이스를 모두 뒤져서 @McpFunction 스캔
|
|
||||||
McpFunction functionAnnotation = AnnotationUtils.findAnnotation(method, McpFunction.class);
|
|
||||||
|
|
||||||
if (functionAnnotation != null && toolAnnotation != null) {
|
|
||||||
String baseName = functionAnnotation.displayName();
|
|
||||||
String rawSubToolName = functionAnnotation.name();
|
|
||||||
String subToolName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
|
|
||||||
? mcpProperties.getNamespace() + "_" + rawSubToolName
|
|
||||||
: rawSubToolName;
|
|
||||||
|
|
||||||
boolean isRegister = functionAnnotation.register();
|
|
||||||
if (!isRegister) {
|
|
||||||
log.info(" [LocalToolScanner] '{}' 툴은 어노테이션 설정에 의해 제외되었습니다. (최종 이름: {})", baseName, subToolName);
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolMetadata meta = new ToolMetadata();
|
|
||||||
meta.setUid(UUID.nameUUIDFromBytes(subToolName.getBytes()).toString());
|
|
||||||
meta.setDisplayName(baseName);
|
|
||||||
meta.setName(subToolName);
|
|
||||||
meta.setSemver(functionAnnotation.version());
|
|
||||||
meta.setTimeoutMillis(functionAnnotation.timeoutMillis());
|
|
||||||
meta.setEnabled(functionAnnotation.enabled());
|
|
||||||
meta.setDescription(functionAnnotation.description());
|
|
||||||
meta.setCategoryKey(toolAnnotation.categoryKey());
|
|
||||||
meta.setIntegrationType(toolAnnotation.routingType());
|
|
||||||
meta.setMciServiceId(functionAnnotation.mappingId());
|
|
||||||
meta.setPodUrl(podUrl);
|
|
||||||
meta.setEndpoint(podUrl.replaceAll("/+$", "") + "/mcp/" + subToolName);
|
|
||||||
|
|
||||||
boolean isVisible = functionAnnotation.visible();
|
|
||||||
meta.setVisible(isVisible);
|
|
||||||
meta.setIsRegistered(isRegister);
|
|
||||||
meta.setRequiresApproval(functionAnnotation.requiresApproval());
|
|
||||||
meta.setReadOnlyHint(functionAnnotation.readOnlyHint());
|
|
||||||
meta.setDestructiveHint(functionAnnotation.destructiveHint());
|
|
||||||
meta.setIdempotentHint(functionAnnotation.idempotentHint());
|
|
||||||
meta.setOpenWorldHint(functionAnnotation.openWorldHint());
|
|
||||||
|
|
||||||
Map<String, String> prompts = new HashMap<>();
|
|
||||||
String promptText = functionAnnotation.prompt();
|
|
||||||
prompts.put(subToolName, promptText);
|
|
||||||
meta.setActionPrompts(prompts);
|
|
||||||
|
|
||||||
if (method.getParameterCount() > 0) {
|
|
||||||
try {
|
|
||||||
Class<?> paramType = method.getParameterTypes()[0];
|
|
||||||
Map<String, Object> finalSchema = toolSchemaResolver.resolve(functionAnnotation, paramType);
|
|
||||||
meta.setParametersSchema(finalSchema);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to generate schema for {}", subToolName, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRegister) {
|
|
||||||
registeredTools.add(meta);
|
|
||||||
}
|
|
||||||
allScannedTools.add(meta);
|
|
||||||
log.info(" [LocalToolScanner] 도구 메타데이터 생성: {} (isRegistered: {})", meta.getUid(), isRegister);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.mcp;
|
package io.shinhanlife.dap.lib.mcp;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.mcp
|
|
||||||
* @className McpRequestHeaderContext
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/** Holds optional MCP headers for the lifetime of one HTTP request thread. */
|
/** Holds optional MCP headers for the lifetime of one HTTP request thread. */
|
||||||
public final class McpRequestHeaderContext {
|
public final class McpRequestHeaderContext {
|
||||||
private static final ThreadLocal<McpRequestHeaders> CURRENT_HEADERS = new ThreadLocal<>();
|
private static final ThreadLocal<McpRequestHeaders> CURRENT_HEADERS = new ThreadLocal<>();
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.mcp;
|
package io.shinhanlife.dap.lib.mcp;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.mcp
|
|
||||||
* @className McpRequestHeaderFilter
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.mcp;
|
package io.shinhanlife.dap.lib.mcp;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.mcp
|
|
||||||
* @className McpRequestHeaders
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/** Optional request headers propagated from an MCP HTTP request to a Tool invocation. */
|
/** Optional request headers propagated from an MCP HTTP request to a Tool invocation. */
|
||||||
public record McpRequestHeaders(
|
public record McpRequestHeaders(
|
||||||
String headerRequestId,
|
String headerRequestId,
|
||||||
|
|||||||
@@ -1,31 +1,25 @@
|
|||||||
package io.shinhanlife.dap.lib.mcp;
|
package io.shinhanlife.dap.lib.mcp;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.mcp
|
|
||||||
* @className ToolMcpServerConfiguration
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
|
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
|
||||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/** Exposes every Tool Pod through the MCP Streamable HTTP transport. */
|
/** Exposes every Tool Pod through the MCP Streamable HTTP transport. */
|
||||||
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
public class ToolMcpServerConfiguration {
|
public class ToolMcpServerConfiguration {
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
log.warn("=================================================");
|
||||||
|
log.warn("ToolMcpServerConfiguration IS LOADED BY SPRING!!!");
|
||||||
|
log.warn("=================================================");
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Primary
|
@Primary
|
||||||
public HttpServletStreamableServerTransportProvider toolMcpTransportProvider() {
|
public HttpServletStreamableServerTransportProvider toolMcpTransportProvider() {
|
||||||
@@ -37,6 +31,8 @@ public class ToolMcpServerConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
public ServletRegistrationBean<HttpServletStreamableServerTransportProvider> toolMcpServlet(
|
public ServletRegistrationBean<HttpServletStreamableServerTransportProvider> toolMcpServlet(
|
||||||
HttpServletStreamableServerTransportProvider transportProvider) {
|
HttpServletStreamableServerTransportProvider transportProvider) {
|
||||||
return new ServletRegistrationBean<>(transportProvider, "/mcp");
|
// "/mcp/*"로 매핑하면 BusinessToolController의 "/mcp/api/v1/tools/local" 까지 가로채게 되므로,
|
||||||
|
// 정확히 MCP 통신에 사용되는 "/mcp" 와 "/mcp/message" 두 개만 매핑합니다.
|
||||||
|
return new ServletRegistrationBean<>(transportProvider, "/mcp", "/mcp/message");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,14 @@
|
|||||||
package io.shinhanlife.dap.lib.mcp;
|
package io.shinhanlife.dap.lib.mcp;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.mcp
|
|
||||||
* @className ToolPodMcpToolSynchronizer
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.modelcontextprotocol.server.McpServerFeatures;
|
import io.modelcontextprotocol.server.McpServerFeatures;
|
||||||
import io.modelcontextprotocol.server.McpSyncServer;
|
import io.modelcontextprotocol.server.McpSyncServer;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import io.shinhanlife.dap.mcc.presentation.BusinessToolController;
|
import io.shinhanlife.dap.mcc.presentation.BusinessToolController;
|
||||||
import io.shinhanlife.dap.lib.mcp.LocalToolScanner;
|
import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender;
|
||||||
|
import io.shinhanlife.dap.lib.mcp.McpRequestHeaderContext;
|
||||||
|
import io.shinhanlife.dap.lib.mcp.McpRequestHeaders;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
@@ -30,25 +16,28 @@ import org.springframework.context.event.EventListener;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
|
||||||
/** Registers the Tool Pod's existing annotated tools with its MCP SDK server. */
|
/** Registers the Tool Pod's existing annotated tools with its MCP SDK server. */
|
||||||
@Component
|
@Component
|
||||||
|
@ConditionalOnBean(BusinessToolController.class)
|
||||||
public class ToolPodMcpToolSynchronizer {
|
public class ToolPodMcpToolSynchronizer {
|
||||||
private final McpSyncServer mcpServer;
|
private final McpSyncServer mcpServer;
|
||||||
private final LocalToolScanner localToolScanner;
|
private final ToolRegistryHeartbeatSender heartbeatSender;
|
||||||
private final BusinessToolController businessToolController;
|
private final BusinessToolController businessToolController;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
public ToolPodMcpToolSynchronizer(McpSyncServer mcpServer, LocalToolScanner localToolScanner,
|
public ToolPodMcpToolSynchronizer(McpSyncServer mcpServer, ToolRegistryHeartbeatSender heartbeatSender,
|
||||||
BusinessToolController businessToolController, ObjectMapper objectMapper) {
|
BusinessToolController businessToolController, ObjectMapper objectMapper) {
|
||||||
this.mcpServer = mcpServer;
|
this.mcpServer = mcpServer;
|
||||||
this.localToolScanner = localToolScanner;
|
this.heartbeatSender = heartbeatSender;
|
||||||
this.businessToolController = businessToolController;
|
this.businessToolController = businessToolController;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventListener(ApplicationReadyEvent.class)
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
public void registerLocalTools() {
|
public void registerLocalTools() {
|
||||||
localToolScanner.getAllScannedTools().stream()
|
heartbeatSender.getAllScannedTools().stream()
|
||||||
.filter(tool -> Boolean.TRUE.equals(tool.getVisible()))
|
.filter(tool -> Boolean.TRUE.equals(tool.getVisible()))
|
||||||
.forEach(tool -> mcpServer.addTool(specification(tool)));
|
.forEach(tool -> mcpServer.addTool(specification(tool)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package io.shinhanlife.dap.lib.mcp;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.mcc.service
|
||||||
|
* @className ToolRegistryHeartbeatSender
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author 0986406
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 0986406 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
import io.shinhanlife.dap.lib.config.McpProperties;
|
||||||
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
|
import io.shinhanlife.dap.lib.util.ToolSchemaResolver;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import io.shinhanlife.dap.mcc.presentation.BusinessToolController;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Configuration
|
||||||
|
@EnableScheduling
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@ConditionalOnBean(BusinessToolController.class)
|
||||||
|
public class ToolRegistryHeartbeatSender {
|
||||||
|
|
||||||
|
private final ApplicationContext applicationContext;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final McpProperties mcpProperties;
|
||||||
|
private final RestClient restClient = RestClient.create();
|
||||||
|
private final ToolSchemaResolver toolSchemaResolver;
|
||||||
|
|
||||||
|
@Value("${axhub.gateway.url:http://localhost:8081}")
|
||||||
|
private String gatewayUrl;
|
||||||
|
|
||||||
|
@Value("${axhub.tool.url:http://localhost:8080}")
|
||||||
|
private String podUrl;
|
||||||
|
|
||||||
|
private List<ToolMetadata> registeredTools = new ArrayList<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private List<ToolMetadata> allScannedTools = new ArrayList<>();
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
log.info(" [HeartbeatSender] 초기화 시작. Gateway URL: {}, Pod URL: {}", gatewayUrl, podUrl);
|
||||||
|
scanAndBuildMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanAndBuildMetadata() {
|
||||||
|
Map<String, Object> allBeans = applicationContext.getBeansOfType(Object.class);
|
||||||
|
for (Object bean : allBeans.values()) {
|
||||||
|
Class<?> targetClass = AopUtils.getTargetClass(bean);
|
||||||
|
|
||||||
|
for (Method method : targetClass.getDeclaredMethods()) {
|
||||||
|
McpTool functionAnnotation = AnnotationUtils.findAnnotation(method, McpTool.class);
|
||||||
|
ToolHint hintAnnotation = AnnotationUtils.findAnnotation(method, ToolHint.class);
|
||||||
|
|
||||||
|
if (functionAnnotation != null) {
|
||||||
|
String baseName = functionAnnotation.name();
|
||||||
|
String rawSubToolName = functionAnnotation.name();
|
||||||
|
String subToolName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
|
||||||
|
? mcpProperties.getNamespace() + "_" + rawSubToolName
|
||||||
|
: rawSubToolName;
|
||||||
|
|
||||||
|
boolean isRegister = hintAnnotation != null && hintAnnotation.register();
|
||||||
|
if (!isRegister) {
|
||||||
|
log.info(" [HeartbeatSender] '{}' 툴은 어노테이션 설정에 의해 외부 등록(Redis) 대상에서 제외되었습니다. (최종 이름: {})", baseName, subToolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolMetadata meta = new ToolMetadata();
|
||||||
|
meta.setUid(UUID.nameUUIDFromBytes(subToolName.getBytes()).toString());
|
||||||
|
String displayName = functionAnnotation.title().isEmpty() ? functionAnnotation.name() : functionAnnotation.title();
|
||||||
|
meta.setDisplayName(displayName);
|
||||||
|
meta.setName(subToolName);
|
||||||
|
meta.setSemver("1.0.0");
|
||||||
|
meta.setTimeoutMillis(5000L);
|
||||||
|
meta.setEnabled(true);
|
||||||
|
meta.setDescription(functionAnnotation.description());
|
||||||
|
meta.setCategoryKey("default");
|
||||||
|
meta.setIntegrationType("REST");
|
||||||
|
meta.setMciServiceId(hintAnnotation != null ? hintAnnotation.mappingId() : "");
|
||||||
|
meta.setPodUrl(podUrl);
|
||||||
|
meta.setEndpoint(podUrl.replaceAll("/+$", "") + "/mcp/" + subToolName);
|
||||||
|
|
||||||
|
meta.setVisible(true);
|
||||||
|
meta.setIsRegistered(isRegister);
|
||||||
|
meta.setRequiresApproval(hintAnnotation != null && hintAnnotation.requiresApproval());
|
||||||
|
|
||||||
|
// extract standard hints from @McpTool.annotations()
|
||||||
|
McpTool.McpAnnotations ann = functionAnnotation.annotations();
|
||||||
|
if (ann != null) {
|
||||||
|
meta.setReadOnlyHint(ann.readOnlyHint());
|
||||||
|
meta.setDestructiveHint(ann.destructiveHint());
|
||||||
|
meta.setIdempotentHint(ann.idempotentHint());
|
||||||
|
meta.setOpenWorldHint(ann.openWorldHint());
|
||||||
|
} else {
|
||||||
|
meta.setReadOnlyHint(false);
|
||||||
|
meta.setDestructiveHint(true);
|
||||||
|
meta.setIdempotentHint(false);
|
||||||
|
meta.setOpenWorldHint(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> prompts = new HashMap<>();
|
||||||
|
meta.setActionPrompts(prompts);
|
||||||
|
|
||||||
|
if (method.getParameterCount() > 0) {
|
||||||
|
try {
|
||||||
|
Class<?> paramType = method.getParameterTypes()[0];
|
||||||
|
// TODO: ToolSchemaResolver may need to be updated to take McpTool instead of McpFunction
|
||||||
|
Map<String, Object> finalSchema = toolSchemaResolver.resolve(functionAnnotation, hintAnnotation, paramType);
|
||||||
|
meta.setParametersSchema(finalSchema);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to generate schema for {}", subToolName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRegister) {
|
||||||
|
registeredTools.add(meta);
|
||||||
|
}
|
||||||
|
allScannedTools.add(meta);
|
||||||
|
log.info(" [HeartbeatSender] 도구 메타데이터 생성: {} (isRegistered: {})", meta.getUid(), isRegister);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 30000)
|
||||||
|
public void sendHeartbeats() {
|
||||||
|
if (registeredTools.isEmpty()) return;
|
||||||
|
|
||||||
|
for (ToolMetadata tool : registeredTools) {
|
||||||
|
try {
|
||||||
|
ResponseEntity<String> response = restClient.post()
|
||||||
|
.uri(gatewayUrl + "/mcp/api/v1/registry/heartbeat")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
|
||||||
|
.body(tool.getUid())
|
||||||
|
.retrieve()
|
||||||
|
.toEntity(String.class);
|
||||||
|
|
||||||
|
if (response.getStatusCode().is2xxSuccessful()) {
|
||||||
|
log.info(" [HeartbeatSender] 하트비트 전송 성공: {}", tool.getUid());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn(" [HeartbeatSender] 하트비트 전송 실패 ({}): {}. 재등록을 시도합니다.", tool.getUid(), e.getMessage());
|
||||||
|
registerTool(tool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerTool(ToolMetadata tool) {
|
||||||
|
try {
|
||||||
|
restClient.post()
|
||||||
|
.uri(gatewayUrl + "/mcp/api/v1/registry/register")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
|
||||||
|
.body(tool)
|
||||||
|
.retrieve()
|
||||||
|
.toBodilessEntity();
|
||||||
|
log.info(" [HeartbeatSender] 툴 재등록 성공: {}", tool.getUid());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error(" [HeartbeatSender] 툴 등록 실패: {}", ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,11 +29,11 @@ public class SwaggerConfig {
|
|||||||
public OpenAPI customOpenAPI() {
|
public OpenAPI customOpenAPI() {
|
||||||
return new OpenAPI()
|
return new OpenAPI()
|
||||||
.info(new Info()
|
.info(new Info()
|
||||||
.title("Shinhan MCP WAS API 명세서")
|
.title("Shinhan MCP Gateway API 명세서")
|
||||||
.version("v1.0")
|
.version("v1.0")
|
||||||
.description("AI Agent와 신한라이프 내부망(EIMS/EAI)을 연결하는 Adapter WAS API 문서입니다."))
|
.description("AI Agent와 신한라이프 내부망(EIMS/EAI)을 연결하는 Adapter Gateway API 문서입니다."))
|
||||||
.addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8080").description("Adapter Pod (8080)"))
|
.addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8080").description("Adapter Pod (8080)"))
|
||||||
.addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8081").description("WAS Pod"))
|
.addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8081").description("Gateway Pod (8081)"))
|
||||||
// 전역적으로 X-API-KEY 보안 설정을 Swagger UI에 추가합니다.
|
// 전역적으로 X-API-KEY 보안 설정을 Swagger UI에 추가합니다.
|
||||||
.addSecurityItem(new SecurityRequirement().addList("X-API-KEY"))
|
.addSecurityItem(new SecurityRequirement().addList("X-API-KEY"))
|
||||||
.components(new Components()
|
.components(new Components()
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCorsMappings(CorsRegistry registry) {
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
// Swagger UI(8080)에서 WAS로 API 호출 시 발생하는 CORS 에러 해결
|
// Swagger UI(8080)에서 Gateway(8081)로 API 호출 시 발생하는 CORS 에러 해결
|
||||||
registry.addMapping("/**")
|
registry.addMapping("/**")
|
||||||
.allowedOriginPatterns("*")
|
.allowedOriginPatterns("*")
|
||||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
|
|||||||
@@ -29,25 +29,25 @@ public class GlobalExceptionHandler {
|
|||||||
|
|
||||||
@ExceptionHandler(NoResourceFoundException.class)
|
@ExceptionHandler(NoResourceFoundException.class)
|
||||||
public ResponseEntity<Void> handleNoResourceFound(NoResourceFoundException e) {
|
public ResponseEntity<Void> handleNoResourceFound(NoResourceFoundException e) {
|
||||||
log.warn(" [WAS Not Found] 요청하신 리소스를 찾을 수 없습니다: {}", e.getResourcePath());
|
log.warn(" [Gateway Not Found] 요청하신 리소스를 찾을 수 없습니다: {}", e.getResourcePath());
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(IllegalArgumentException.class)
|
@ExceptionHandler(IllegalArgumentException.class)
|
||||||
public ResponseEntity<JsonRpcResponse> handleIllegalArgument(IllegalArgumentException e) {
|
public ResponseEntity<JsonRpcResponse> handleIllegalArgument(IllegalArgumentException e) {
|
||||||
log.warn(" [WAS Bad Request] 잘못된 요청: {}", e.getMessage());
|
log.warn(" [Gateway Bad Request] 잘못된 요청: {}", e.getMessage());
|
||||||
return buildErrorResponse(-32602, "Invalid params: " + e.getMessage());
|
return buildErrorResponse(-32602, "Invalid params: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(RuntimeException.class)
|
@ExceptionHandler(RuntimeException.class)
|
||||||
public ResponseEntity<JsonRpcResponse> handleRuntime(RuntimeException e) {
|
public ResponseEntity<JsonRpcResponse> handleRuntime(RuntimeException e) {
|
||||||
log.error(" [WAS Internal Error] 시스템 장애: {}", e.getMessage(), e);
|
log.error(" [Gateway Internal Error] 시스템 장애: {}", e.getMessage(), e);
|
||||||
return buildErrorResponse(-32603, "Internal error: " + e.getMessage());
|
return buildErrorResponse(-32603, "Internal error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ResponseEntity<JsonRpcResponse> handleAllException(Exception e) {
|
public ResponseEntity<JsonRpcResponse> handleAllException(Exception e) {
|
||||||
log.error(" [WAS Fatal Error] 치명적 오류 발생", e);
|
log.error(" [Gateway Fatal Error] 치명적 오류 발생", e);
|
||||||
return buildErrorResponse(-32000, "Server error: 시스템 관리자에게 문의하세요.");
|
return buildErrorResponse(-32000, "Server error: 시스템 관리자에게 문의하세요.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
package io.shinhanlife.dap.lib.session.presentation;
|
|
||||||
|
|
||||||
import io.micrometer.common.util.StringUtils;
|
|
||||||
import io.shinhanlife.glow.BaseResponse;
|
|
||||||
import io.shinhanlife.glow.BizException;
|
|
||||||
import io.shinhanlife.glow.GlowControllerId;
|
|
||||||
import io.shinhanlife.glow.ResponseUtil;
|
|
||||||
import io.shinhanlife.dap.lib.session.dto.SessionDto;
|
|
||||||
import io.shinhanlife.dap.lib.session.presentation.io.SsoResponse;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.ibatis.javassist.NotFoundException;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.session.presentation
|
|
||||||
* @className SsoRestController
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
|
||||||
@RequestMapping("/sso")
|
|
||||||
public class SsoRestController {
|
|
||||||
|
|
||||||
private static final String NLS_LOGIN_URL = "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sso 연동 전 임시 로그인
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param response
|
|
||||||
* @param session
|
|
||||||
* @param <T>
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GlowControllerId("tempLogin")
|
|
||||||
@PostMapping("/tempLogin")
|
|
||||||
public <T> ResponseEntity<BaseResponse<SsoResponse>> tempLogin(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
HttpSession session, @RequestBody SessionDto requestDto) {
|
|
||||||
try {
|
|
||||||
if (StringUtils.isEmpty(requestDto.getPrafNo())) {
|
|
||||||
throw new NotFoundException("SSO >> not found sso id");
|
|
||||||
}
|
|
||||||
|
|
||||||
// DB 조회(ZtUsac) 없이 파라미터로 받은 SessionDto를 그대로 사용
|
|
||||||
SessionDto sessionDto = requestDto;
|
|
||||||
sessionDto.setLoginDtm(); // 로그인 시점 세팅
|
|
||||||
session.setAttribute("userInfo", sessionDto);
|
|
||||||
return ResponseUtil.ok(SsoResponse.builder().retCode("0").userInfo(sessionDto).build());
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("로그인 실패", e);
|
|
||||||
session.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseUtil.ok(SsoResponse.builder().redirectUrl(NLS_LOGIN_URL).build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package io.shinhanlife.dap.lib.session.presentation.io;
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.session.dto.SessionDto;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.session.presentation.io
|
|
||||||
* @className SsoResponse
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class SsoResponse {
|
|
||||||
|
|
||||||
private String retCode;
|
|
||||||
private SessionDto userInfo;
|
|
||||||
private String redirectUrl;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,8 @@ package io.shinhanlife.dap.lib.util;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpAnyOf;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
@@ -56,7 +55,7 @@ public class JsonSchemaGenerator {
|
|||||||
// 1. 타입 매핑
|
// 1. 타입 매핑
|
||||||
|
|
||||||
// 2. 어노테이션 기반 설명 추출
|
// 2. 어노테이션 기반 설명 추출
|
||||||
McpParameter paramAnnotation = field.getAnnotation(McpParameter.class);
|
McpToolParam paramAnnotation = field.getAnnotation(McpToolParam.class);
|
||||||
JsonPropertyDescription descAnnotation = field.getAnnotation(JsonPropertyDescription.class);
|
JsonPropertyDescription descAnnotation = field.getAnnotation(JsonPropertyDescription.class);
|
||||||
if (paramAnnotation != null && !paramAnnotation.description().isEmpty()) {
|
if (paramAnnotation != null && !paramAnnotation.description().isEmpty()) {
|
||||||
fieldSchema.put("description", paramAnnotation.description());
|
fieldSchema.put("description", paramAnnotation.description());
|
||||||
@@ -72,46 +71,54 @@ public class JsonSchemaGenerator {
|
|||||||
requiredList.add(field.getName());
|
requiredList.add(field.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
McpValidation validation = field.getAnnotation(McpValidation.class);
|
Schema schemaAnnotation = field.getAnnotation(Schema.class);
|
||||||
if (validation != null && validation.required() && !requiredList.contains(field.getName())) {
|
if (schemaAnnotation != null) {
|
||||||
requiredList.add(field.getName());
|
if (!schemaAnnotation.description().isEmpty() && !fieldSchema.containsKey("description")) {
|
||||||
|
fieldSchema.put("description", schemaAnnotation.description());
|
||||||
|
}
|
||||||
|
if (schemaAnnotation.required() && !requiredList.contains(field.getName())) {
|
||||||
|
requiredList.add(field.getName());
|
||||||
|
}
|
||||||
|
if (!schemaAnnotation.pattern().isEmpty()) {
|
||||||
|
fieldSchema.put("pattern", schemaAnnotation.pattern());
|
||||||
|
}
|
||||||
|
if (!schemaAnnotation.minimum().isEmpty()) {
|
||||||
|
try {
|
||||||
|
fieldSchema.put("minimum", Long.valueOf(schemaAnnotation.minimum()));
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
if (!schemaAnnotation.maximum().isEmpty()) {
|
||||||
|
try {
|
||||||
|
fieldSchema.put("maximum", Long.valueOf(schemaAnnotation.maximum()));
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
if (schemaAnnotation.minLength() > 0) {
|
||||||
|
fieldSchema.put("minLength", schemaAnnotation.minLength());
|
||||||
|
}
|
||||||
|
if (schemaAnnotation.maxLength() > 0 && schemaAnnotation.maxLength() != Integer.MAX_VALUE) {
|
||||||
|
fieldSchema.put("maxLength", schemaAnnotation.maxLength());
|
||||||
|
}
|
||||||
|
if (schemaAnnotation.allowableValues().length > 0 && !schemaAnnotation.allowableValues()[0].isEmpty()) {
|
||||||
|
fieldSchema.put("enum", List.of(schemaAnnotation.allowableValues()));
|
||||||
|
}
|
||||||
|
if (!schemaAnnotation.format().isEmpty()) {
|
||||||
|
fieldSchema.put("format", schemaAnnotation.format());
|
||||||
|
}
|
||||||
|
if (!schemaAnnotation.defaultValue().isEmpty()) {
|
||||||
|
fieldSchema.put("default", coerceDefaultValue(schemaAnnotation.defaultValue(), field.getType()));
|
||||||
|
}
|
||||||
|
if (!schemaAnnotation.example().isEmpty()) {
|
||||||
|
fieldSchema.put("examples", List.of(schemaAnnotation.example()));
|
||||||
|
}
|
||||||
|
if (schemaAnnotation.nullable()) {
|
||||||
|
Map<String, Object> nonNullSchema = new HashMap<>(fieldSchema);
|
||||||
|
fieldSchema = new HashMap<>();
|
||||||
|
fieldSchema.put("anyOf", List.of(
|
||||||
|
nonNullSchema,
|
||||||
|
Map.of("type", "null")
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (validation != null && !validation.pattern().isEmpty()) {
|
|
||||||
fieldSchema.put("pattern", validation.pattern());
|
|
||||||
}
|
|
||||||
if (validation != null && validation.minimum() != Long.MIN_VALUE) {
|
|
||||||
fieldSchema.put("minimum", validation.minimum());
|
|
||||||
}
|
|
||||||
if (validation != null && validation.maximum() != Long.MAX_VALUE) {
|
|
||||||
fieldSchema.put("maximum", validation.maximum());
|
|
||||||
}
|
|
||||||
if (validation != null && validation.minLength() >= 0) {
|
|
||||||
fieldSchema.put("minLength", validation.minLength());
|
|
||||||
}
|
|
||||||
if (validation != null && validation.maxLength() >= 0) {
|
|
||||||
fieldSchema.put("maxLength", validation.maxLength());
|
|
||||||
}
|
|
||||||
if (validation != null && validation.allowedValues().length > 0) {
|
|
||||||
fieldSchema.put("enum", List.of(validation.allowedValues()));
|
|
||||||
}
|
|
||||||
if (validation != null && !validation.format().isEmpty()) {
|
|
||||||
fieldSchema.put("format", validation.format());
|
|
||||||
}
|
|
||||||
if (validation != null && !validation.defaultValue().isEmpty()) {
|
|
||||||
fieldSchema.put("default", coerceDefaultValue(validation.defaultValue(), field.getType()));
|
|
||||||
}
|
|
||||||
if (validation != null && validation.examples().length > 0) {
|
|
||||||
fieldSchema.put("examples", List.of(validation.examples()));
|
|
||||||
}
|
|
||||||
if (validation != null && validation.nullable()) {
|
|
||||||
Map<String, Object> nonNullSchema = new HashMap<>(fieldSchema);
|
|
||||||
fieldSchema = new HashMap<>();
|
|
||||||
fieldSchema.put("anyOf", List.of(
|
|
||||||
nonNullSchema,
|
|
||||||
Map.of("type", "null")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
properties.put(field.getName(), fieldSchema);
|
properties.put(field.getName(), fieldSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,15 +127,7 @@ public class JsonSchemaGenerator {
|
|||||||
schema.put("required", requiredList);
|
schema.put("required", requiredList);
|
||||||
}
|
}
|
||||||
|
|
||||||
McpAnyOf anyOfAnnotation = clazz.getAnnotation(McpAnyOf.class);
|
// anyOf removed
|
||||||
if (anyOfAnnotation != null && anyOfAnnotation.value().length > 0) {
|
|
||||||
List<Map<String, Object>> anyOfList = new ArrayList<>();
|
|
||||||
for (String fieldName : anyOfAnnotation.value()) {
|
|
||||||
anyOfList.add(Map.of("required", List.of(fieldName)));
|
|
||||||
|
|
||||||
}
|
|
||||||
schema.put("anyOf", anyOfList);
|
|
||||||
}
|
|
||||||
|
|
||||||
visiting.remove(clazz);
|
visiting.remove(clazz);
|
||||||
return schema;
|
return schema;
|
||||||
|
|||||||
@@ -0,0 +1,309 @@
|
|||||||
|
package io.shinhanlife.dap.lib.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class PodScaffolder {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
Scanner scanner = new Scanner(System.in);
|
||||||
|
|
||||||
|
System.out.println("=========================================");
|
||||||
|
System.out.println(" MCP Tool Pod Scaffolder (Java CLI) ");
|
||||||
|
System.out.println("=========================================\n");
|
||||||
|
|
||||||
|
String rawModuleName = getOrAsk(args, 0, scanner, "1. 생성할 모듈(Pod) 이름 (예: payment 또는 dap-was-payment): ");
|
||||||
|
String moduleName = rawModuleName.startsWith("dap-was-") ? rawModuleName : "dap-was-" + rawModuleName;
|
||||||
|
String portStr = getOrAsk(args, 1, scanner, "2. 사용할 포트 번호 (예: 8085): ");
|
||||||
|
String shortName = moduleName.replace("dap-was-", "").replace("-", "");
|
||||||
|
|
||||||
|
String defaultAuthor = System.getProperty("user.name");
|
||||||
|
String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||||
|
|
||||||
|
String author = getOrAsk(args, 2, scanner, "3. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): ");
|
||||||
|
if (author.trim().isEmpty()) author = defaultAuthor;
|
||||||
|
String createDate = getOrAsk(args, 3, scanner, "4. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
||||||
|
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
||||||
|
|
||||||
|
String result = scaffoldPod(moduleName, portStr, shortName, author, createDate);
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
|
||||||
|
if (args.length > index) {
|
||||||
|
return args[index];
|
||||||
|
}
|
||||||
|
System.out.print(prompt);
|
||||||
|
return scanner.nextLine().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException {
|
||||||
|
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
|
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||||
|
|
||||||
|
Path modulePath = rootDir.resolve(Paths.get(moduleName));
|
||||||
|
if (Files.exists(modulePath)) {
|
||||||
|
return "[오류] 이미 존재하는 모듈입니다: " + moduleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder log = new StringBuilder();
|
||||||
|
log.append("[1/6] 모듈 디렉터리 생성 중...\n");
|
||||||
|
Files.createDirectories(modulePath);
|
||||||
|
|
||||||
|
log.append("[2/6] build.gradle 생성 중...\n");
|
||||||
|
String buildGradle = """
|
||||||
|
plugins {
|
||||||
|
id 'org.springframework.boot'
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
implementation project(':dap-was-lib')
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(modulePath.resolve("build.gradle"), buildGradle);
|
||||||
|
|
||||||
|
log.append("[3/6] Dockerfile 생성 중...\n");
|
||||||
|
String dockerfile = """
|
||||||
|
FROM eclipse-temurin:21-jdk-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY build/libs/%s-0.0.1-SNAPSHOT.jar app.jar
|
||||||
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
|
""".formatted(moduleName);
|
||||||
|
Files.writeString(modulePath.resolve("Dockerfile"), dockerfile);
|
||||||
|
|
||||||
|
log.append("[4/6] Application 클래스 및 설정 파일 생성 중...\n");
|
||||||
|
Path srcPath = modulePath.resolve("src/main/java/io/shinhanlife/dap/mcc/" + shortName);
|
||||||
|
Files.createDirectories(srcPath);
|
||||||
|
|
||||||
|
String appClass = """
|
||||||
|
package io.shinhanlife.dap.mcc.%s;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.mcc.%s
|
||||||
|
* @className DapWas%sApplication
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"})
|
||||||
|
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"})
|
||||||
|
@EnableCaching
|
||||||
|
public class DapWas%sApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(DapWas%sApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(shortName, shortName, capitalize(shortName), author, createDate, createDate, author, capitalize(shortName), capitalize(shortName));
|
||||||
|
Files.writeString(srcPath.resolve("DapWas" + capitalize(shortName) + "Application.java"), appClass);
|
||||||
|
|
||||||
|
Path resPath = modulePath.resolve("src/main/resources");
|
||||||
|
Files.createDirectories(resPath);
|
||||||
|
String applicationYml = """
|
||||||
|
server:
|
||||||
|
port: %s
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: %s
|
||||||
|
profiles:
|
||||||
|
active: local
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.apache.kafka: ERROR
|
||||||
|
mcp:
|
||||||
|
namespace: ""
|
||||||
|
security:
|
||||||
|
tenant-domains:
|
||||||
|
TESTER-DEV: ALL
|
||||||
|
""".formatted(portStr, moduleName);
|
||||||
|
Files.writeString(resPath.resolve("application.yml"), applicationYml);
|
||||||
|
|
||||||
|
String applicationLocalYml = """
|
||||||
|
# Local 환경 전용 설정 (H2 메모리 DB 등)
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1;
|
||||||
|
driverClassName: com.p6spy.engine.spy.P6SpyDriver
|
||||||
|
username: sa
|
||||||
|
password: password
|
||||||
|
h2:
|
||||||
|
console:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
eims:
|
||||||
|
http:
|
||||||
|
url: http://localhost:${server.port}/api/gateway
|
||||||
|
tcp:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 8090
|
||||||
|
timeout: 5000
|
||||||
|
jsp:
|
||||||
|
form:
|
||||||
|
url: http://localhost:${server.port}/mock/jsp-form
|
||||||
|
json:
|
||||||
|
url: http://localhost:${server.port}/mock/jsp-json
|
||||||
|
mci:
|
||||||
|
url: http://localhost:${server.port}/api/mock/esb/api
|
||||||
|
mcistring:
|
||||||
|
url: http://localhost:${server.port}/api/mock/esb/string
|
||||||
|
|
||||||
|
mcp:
|
||||||
|
security:
|
||||||
|
tenant-domains:
|
||||||
|
mcp-client-1: CUSTOMER,COMMON
|
||||||
|
mcp-client-2: ALL
|
||||||
|
|
||||||
|
axhub:
|
||||||
|
gateway:
|
||||||
|
url: http://localhost:8081
|
||||||
|
tool:
|
||||||
|
url: ${AXHUB_TOOL_URL:http://localhost:${server.port}}
|
||||||
|
""";
|
||||||
|
Files.writeString(resPath.resolve("application-local.yml"), applicationLocalYml);
|
||||||
|
|
||||||
|
String applicationDevYml = """
|
||||||
|
# OCI 클라우드 환경 전용 설정
|
||||||
|
server:
|
||||||
|
port: ${PORT:%s}
|
||||||
|
|
||||||
|
axhub:
|
||||||
|
gateway:
|
||||||
|
url: https://axhubmcp.devjun.net
|
||||||
|
tool:
|
||||||
|
url: http://144.24.70.100:%s
|
||||||
|
|
||||||
|
eims:
|
||||||
|
http:
|
||||||
|
url: http://localhost:${server.port}/api/gateway
|
||||||
|
tcp:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 8090
|
||||||
|
timeout: 5000
|
||||||
|
jsp:
|
||||||
|
form:
|
||||||
|
url: http://localhost:${server.port}/mock/jsp-form
|
||||||
|
json:
|
||||||
|
url: http://localhost:${server.port}/mock/jsp-json
|
||||||
|
mci:
|
||||||
|
url: http://localhost:${server.port}/api/mock/esb/api
|
||||||
|
mcistring:
|
||||||
|
url: http://localhost:${server.port}/api/mock/esb/string
|
||||||
|
|
||||||
|
shinhan:
|
||||||
|
integration:
|
||||||
|
envrTypeCd: D
|
||||||
|
eai:
|
||||||
|
url: http://10.176.32.181
|
||||||
|
internalMci:
|
||||||
|
url: http://10.176.32.173
|
||||||
|
bancaMci:
|
||||||
|
url: http://10.176.32.117
|
||||||
|
externalMci:
|
||||||
|
url: http://10.176.32.176
|
||||||
|
""".formatted(portStr, portStr);
|
||||||
|
Files.writeString(resPath.resolve("application-dev.yml"), applicationDevYml);
|
||||||
|
|
||||||
|
String logbackXml = """
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<property name="LOG_PATTERN" value="%%d{yyyy-MM-dd HH:mm:ss.SSS} [%%thread] [%%X{traceId}] %%-5level %%logger{36} - %%msg%%n" />
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>logs/%s.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>logs/%s-%%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
|
</root>
|
||||||
|
<logger name="io.shinhanlife" level="DEBUG" />
|
||||||
|
</configuration>
|
||||||
|
""".formatted(moduleName, moduleName);
|
||||||
|
Files.writeString(resPath.resolve("logback-spring.xml"), logbackXml);
|
||||||
|
|
||||||
|
log.append("[5/6] settings.gradle 에 모듈 등록 중...\n");
|
||||||
|
Path settingsPath = rootDir.resolve(Paths.get("settings.gradle"));
|
||||||
|
if (Files.exists(settingsPath)) {
|
||||||
|
String settings = Files.readString(settingsPath);
|
||||||
|
if (!settings.contains("include '" + moduleName + "'")) {
|
||||||
|
Files.writeString(settingsPath, System.lineSeparator() + "include '" + moduleName + "'" + System.lineSeparator(), StandardOpenOption.APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.append("[6/6] docker-compose.yml 에 서비스 추가 중...\n");
|
||||||
|
Path dockerComposePath = rootDir.resolve(Paths.get("docker-compose.yml"));
|
||||||
|
if (Files.exists(dockerComposePath)) {
|
||||||
|
String compose = Files.readString(dockerComposePath);
|
||||||
|
String serviceName = moduleName.replace("dap-", ""); // e.g. tool-payment
|
||||||
|
if (!compose.contains(" " + serviceName + ":")) {
|
||||||
|
String newService = """
|
||||||
|
%s:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: %s/Dockerfile
|
||||||
|
ports:
|
||||||
|
- "%s:%s"
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Seoul
|
||||||
|
- SPRING_REDIS_HOST=redis
|
||||||
|
- SPRING_REDIS_PORT=6379
|
||||||
|
- SPRING_DATA_REDIS_PORT=6379
|
||||||
|
- AXHUB_GATEWAY_URL=http://gateway:8081
|
||||||
|
- AXHUB_TOOL_URL=http://%s:%s
|
||||||
|
- GLOW_COMMUNICATION_MCI_HOST=http://mci-mock
|
||||||
|
- GLOW_COMMUNICATION_MCI_PORT=8080
|
||||||
|
- GLOW_COMMUNICATION_EXTMCI_HOST=http://mci-mock
|
||||||
|
- GLOW_COMMUNICATION_EXTMCI_PORT=8080
|
||||||
|
- GLOW_COMMUNICATION_EAI_HOST=http://mci-mock
|
||||||
|
- GLOW_COMMUNICATION_EAI_PORT=8080
|
||||||
|
""".formatted(serviceName, moduleName, portStr, portStr, serviceName, portStr);
|
||||||
|
Files.writeString(dockerComposePath, System.lineSeparator() + newService, StandardOpenOption.APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.append("\n=========================================\n");
|
||||||
|
log.append(" Pod Scaffolding Complete! \n");
|
||||||
|
log.append("=========================================\n");
|
||||||
|
log.append("1. [새로운 모듈] ").append(moduleName).append(" 폴더가 생성되었습니다.\n");
|
||||||
|
log.append("2. [ToolScaffolder]를 사용해 이 모듈 안에 툴을 추가하세요.\n");
|
||||||
|
log.append("3. 실행 전 Gradle 동기화(Sync)를 한 번 진행해 주세요.\n");
|
||||||
|
return log.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String capitalize(String str) {
|
||||||
|
if (str == null || str.isEmpty()) return str;
|
||||||
|
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,783 @@
|
|||||||
|
package io.shinhanlife.dap.lib.util;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스
|
||||||
|
*
|
||||||
|
* [실행 방법]
|
||||||
|
* 방법 1. IDE(IntelliJ 등)에서 직접 실행 (대화형 모드 추천 ⭐)
|
||||||
|
* - 이 클래스(ToolScaffolder.java)를 열고 main 메서드를 직접 실행(Run)합니다.
|
||||||
|
* - 콘솔 창에 뜨는 질문에 차례대로 값을 입력하기만 하면 파일이 생성됩니다.
|
||||||
|
*
|
||||||
|
* 방법 2. 커맨드라인(터미널)에서 실행 (명령어 기반)
|
||||||
|
* - 컴파일: javac -encoding UTF-8 dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/util/ToolScaffolder.java
|
||||||
|
* - 실행: java -cp dap-was-lib/src/main/java io.shinhanlife.dap.lib.util.ToolScaffolder [이름] [ID] "[설명]" "[그룹]" "[통신방식]" "[모듈명]"
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.lib.util
|
||||||
|
* @className ToolScaffolder
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author 0986406
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 0986406 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class ToolScaffolder {
|
||||||
|
|
||||||
|
private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc";
|
||||||
|
private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dap/mcc";
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
Scanner scanner = new Scanner(System.in);
|
||||||
|
|
||||||
|
System.out.println("=========================================");
|
||||||
|
System.out.println(" MCP Tool Scaffolder (Java CLI) ");
|
||||||
|
System.out.println("=========================================\n");
|
||||||
|
|
||||||
|
String baseName = getOrAsk(args, 0, scanner, "1. 생성할 Tool의 기본 이름 (예: ExchangeRate) [영문 PascalCase]: ");
|
||||||
|
String interfaceId = getOrAsk(args, 1, scanner, "2. 레거시 API 인터페이스 ID (예: EXCH_001): ");
|
||||||
|
String description = getOrAsk(args, 2, scanner, "3. Tool 기능 설명 (예: 환율 조회): ");
|
||||||
|
String group = getOrAsk(args, 3, scanner, "4. Tool 소속 그룹 (예: SAMPLE, NOTIFICATION, CLAIM, POLICY, HR, CONTRACT, CUSTOMER 등): ");
|
||||||
|
if (group.isEmpty()) group = "COMMON";
|
||||||
|
String routingType = getOrAsk(args, 4, scanner, "5. 통신 프로토콜 (예: HTTP, TCP, MCI, EAI): ");
|
||||||
|
if (routingType.trim().isEmpty()) {
|
||||||
|
routingType = "HTTP";
|
||||||
|
}
|
||||||
|
String moduleName = getOrAsk(args, 5, scanner, "6. 코드를 생성할 모듈 (기본: dap-was-oth): ");
|
||||||
|
if (moduleName.trim().isEmpty()) {
|
||||||
|
moduleName = "dap-was-oth";
|
||||||
|
}
|
||||||
|
|
||||||
|
String defaultAuthor = System.getProperty("user.name");
|
||||||
|
String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||||
|
|
||||||
|
String author = getOrAsk(args, 6, scanner, "7. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): ");
|
||||||
|
if (author.trim().isEmpty()) author = defaultAuthor;
|
||||||
|
String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
||||||
|
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
||||||
|
|
||||||
|
String useSchemaResourceStr = getOrAsk(args, 8, scanner, "9. input/output JSON Schema 파일 자동 생성 여부 (y/N): ");
|
||||||
|
boolean useSchemaResource = "y".equalsIgnoreCase(useSchemaResourceStr.trim());
|
||||||
|
|
||||||
|
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true, null, useSchemaResource);
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
|
||||||
|
if (args.length > index) {
|
||||||
|
return args[index];
|
||||||
|
}
|
||||||
|
System.out.print(prompt);
|
||||||
|
return scanner.nextLine().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode) throws IOException {
|
||||||
|
return scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, register, clientSystemCode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, boolean useSchemaResource) throws IOException {
|
||||||
|
baseName = toPascalCase(baseName);
|
||||||
|
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
|
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||||
|
|
||||||
|
Path usecaseDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "usecase"));
|
||||||
|
Path usecaseImplDir = usecaseDir.resolve("impl");
|
||||||
|
Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "dto"));
|
||||||
|
|
||||||
|
Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "legacy"));
|
||||||
|
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter"));
|
||||||
|
|
||||||
|
// schema resource 파일 경로 (useSchemaResource=true 일 때만 생성)
|
||||||
|
String schemaBaseName = toKebabCase(baseName);
|
||||||
|
String inputSchemaFileName = schemaBaseName + "-resource-input-schema.json";
|
||||||
|
String outputSchemaFileName = schemaBaseName + "-resource-output-schema.json";
|
||||||
|
Path schemaDir = rootDir.resolve(Paths.get(moduleName, "src/main/resources/mcp/schema"));
|
||||||
|
String inputSchemaClasspath = "classpath:mcp/schema/" + inputSchemaFileName;
|
||||||
|
String outputSchemaClasspath = "classpath:mcp/schema/" + outputSchemaFileName;
|
||||||
|
|
||||||
|
String bizPackage = BASE_PACKAGE + ".biz." + group.toLowerCase();
|
||||||
|
boolean isMci = "MCI".equalsIgnoreCase(routingType);
|
||||||
|
String mciGroupPath = "infra/itrf/mci/" + group.toLowerCase();
|
||||||
|
String clientPkgSuffix = "";
|
||||||
|
String clientPrefixCap = "";
|
||||||
|
Path mciClientDir = null;
|
||||||
|
|
||||||
|
if (isMci && clientSystemCode != null && clientSystemCode.length() == 4) {
|
||||||
|
String clientPrefix = clientSystemCode.toLowerCase();
|
||||||
|
clientPkgSuffix = clientPrefix.substring(0, 3) + "." + clientPrefix.substring(3, 4);
|
||||||
|
clientPrefixCap = toPascalCase(clientSystemCode);
|
||||||
|
mciGroupPath = "infra/itrf/mci/" + clientPrefix.substring(0, 3) + "/" + clientPrefix.substring(3, 4);
|
||||||
|
mciClientDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
Path mciIoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath, "io"));
|
||||||
|
|
||||||
|
Files.createDirectories(usecaseDir);
|
||||||
|
Files.createDirectories(usecaseImplDir);
|
||||||
|
Files.createDirectories(dtoDir);
|
||||||
|
if (isMci) {
|
||||||
|
Files.createDirectories(mciIoDir);
|
||||||
|
if (mciClientDir != null) {
|
||||||
|
Files.createDirectories(mciClientDir);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Files.createDirectories(legacyDtoDir);
|
||||||
|
}
|
||||||
|
Files.createDirectories(converterDir);
|
||||||
|
|
||||||
|
StringBuilder log = new StringBuilder();
|
||||||
|
|
||||||
|
// Generate Request DTO
|
||||||
|
String reqContent = """
|
||||||
|
package %s.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.dto
|
||||||
|
* @className %sRequest
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class %sRequest {
|
||||||
|
@McpToolParam(description = "수신자 전화번호", required = true)
|
||||||
|
-(?:\\\\d{3}|\\\\d{4})-\\\\d{4}$", examples = {"010-1234-5678"})
|
||||||
|
private String phoneNumber;
|
||||||
|
|
||||||
|
@McpToolParam(description = "전송할 메시지 내용", required = true)
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
|
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
|
||||||
|
Files.writeString(dtoDir.resolve(baseName + "Request.java"), reqContent);
|
||||||
|
|
||||||
|
// Generate Response DTO
|
||||||
|
String resContent = """
|
||||||
|
package %s.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.dto
|
||||||
|
* @className %sResponse
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class %sResponse {
|
||||||
|
private String resultCode;
|
||||||
|
|
||||||
|
private String resultMessage;
|
||||||
|
|
||||||
|
// TODO: Add response fields here. Do not include PII in the Tool response.
|
||||||
|
}
|
||||||
|
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
|
||||||
|
Files.writeString(dtoDir.resolve(baseName + "Response.java"), resContent);
|
||||||
|
|
||||||
|
String toolName = toToolName(moduleName, group, baseName);
|
||||||
|
|
||||||
|
String toolHintLine;
|
||||||
|
if (useSchemaResource) {
|
||||||
|
toolHintLine = " @ToolHint(register = %s,\n" +
|
||||||
|
" inputSchemaResource = \"%s\",\n" +
|
||||||
|
" outputSchemaResource = \"%s\")".formatted(register, inputSchemaClasspath, outputSchemaClasspath);
|
||||||
|
} else {
|
||||||
|
toolHintLine = " @ToolHint(register = %s)".formatted(register);
|
||||||
|
}
|
||||||
|
|
||||||
|
String serviceInterfaceContent = """
|
||||||
|
package %s.usecase;
|
||||||
|
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
import %s.dto.%sRequest;
|
||||||
|
import %s.dto.%sResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.usecase
|
||||||
|
* @className %sUseCase
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public interface %sUseCase {
|
||||||
|
|
||||||
|
@McpTool(name = "%s", title = "%s", description = "%s")
|
||||||
|
%s
|
||||||
|
%sResponse execute(%sRequest req);
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
bizPackage,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName, author, createDate, createDate, author,
|
||||||
|
baseName,
|
||||||
|
toolName, description, description,
|
||||||
|
toolHintLine,
|
||||||
|
baseName, baseName
|
||||||
|
);
|
||||||
|
|
||||||
|
Files.writeString(usecaseDir.resolve(baseName + "UseCase.java"), serviceInterfaceContent);
|
||||||
|
|
||||||
|
String serviceImplContent;
|
||||||
|
|
||||||
|
if (isMci) {
|
||||||
|
serviceImplContent = """
|
||||||
|
package %s.usecase.impl;
|
||||||
|
|
||||||
|
import %s.dto.%sRequest;
|
||||||
|
import %s.dto.%sResponse;
|
||||||
|
import %s.usecase.%sUseCase;
|
||||||
|
import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
|
||||||
|
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import java.util.Map;
|
||||||
|
import %s.converter.%sConverter;
|
||||||
|
import %s.%s.io.%s_I;
|
||||||
|
%s
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.usecase.impl
|
||||||
|
* @className %sUseCaseImpl
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class %sUseCaseImpl implements %sUseCase {
|
||||||
|
|
||||||
|
%s
|
||||||
|
private final %sConverter converter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object execute(%sRequest req) {
|
||||||
|
log.info("[MCI Tool] {} 요청 수신.", "%s");
|
||||||
|
try {
|
||||||
|
// MapStruct를 이용한 자동 매핑 (AI DTO -> MCI DTO)
|
||||||
|
%s_I mciReq = converter.toLegacyRequest(req);
|
||||||
|
|
||||||
|
Transfer<Object> resTransfer = mci.callTo(
|
||||||
|
"%s",
|
||||||
|
null,
|
||||||
|
mciReq,
|
||||||
|
Object.class
|
||||||
|
);
|
||||||
|
return resTransfer.getBody() != null ? resTransfer.getBody() : Map.of("status", "SUCCESS");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
|
||||||
|
return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
bizPackage,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId,
|
||||||
|
(clientPrefixCap.isEmpty() ? "" : "import " + BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".Mci" + clientPrefixCap + "Client;\n"),
|
||||||
|
bizPackage,
|
||||||
|
baseName,
|
||||||
|
author,
|
||||||
|
createDate,
|
||||||
|
createDate, author,
|
||||||
|
baseName,
|
||||||
|
baseName,
|
||||||
|
(clientPrefixCap.isEmpty() ? "private final AxhubMciComponent mci;" : "private final Mci" + clientPrefixCap + "Client mci;"),
|
||||||
|
baseName,
|
||||||
|
baseName,
|
||||||
|
toolName,
|
||||||
|
interfaceId,
|
||||||
|
interfaceId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
serviceImplContent = """
|
||||||
|
package %s.usecase.impl;
|
||||||
|
|
||||||
|
import %s.dto.%sRequest;
|
||||||
|
import %s.dto.%sResponse;
|
||||||
|
import %s.usecase.%sUseCase;
|
||||||
|
import io.shinhanlife.dap.mcc.usecase.AbstractMcpToolUseCase;
|
||||||
|
import %s.converter.%sConverter;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.usecase.impl
|
||||||
|
* @className %sUseCaseImpl
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class %sUseCaseImpl extends AbstractMcpToolUseCase implements %sUseCase {
|
||||||
|
|
||||||
|
private final %sConverter converter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object execute(%sRequest req) {
|
||||||
|
// %sLegacyRequest legacyRequest = converter.toLegacyRequest(req);
|
||||||
|
return executeLegacy("%s", "%s", req); // Or pass legacyRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
bizPackage,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
author,
|
||||||
|
createDate,
|
||||||
|
createDate, author,
|
||||||
|
baseName, baseName,
|
||||||
|
baseName,
|
||||||
|
baseName,
|
||||||
|
baseName,
|
||||||
|
routingType, interfaceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.writeString(usecaseImplDir.resolve(baseName + "UseCaseImpl.java"), serviceImplContent);
|
||||||
|
|
||||||
|
if (isMci) {
|
||||||
|
String mciReqContent = """
|
||||||
|
package %s.%s.io;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.%s.io
|
||||||
|
* @className %s_I
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class %s_I {
|
||||||
|
/**
|
||||||
|
* EAI 시스템이 요구하는 수신자 번호 파라미터명
|
||||||
|
*/
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EAI 시스템이 요구하는 메시지 내용 파라미터명
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
|
""".formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId);
|
||||||
|
Files.writeString(mciIoDir.resolve(interfaceId + "_I.java"), mciReqContent);
|
||||||
|
|
||||||
|
String mciResContent = """
|
||||||
|
package %s.%s.io;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.%s.io
|
||||||
|
* @className %s_O
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class %s_O {
|
||||||
|
// TODO: Add response fields here
|
||||||
|
}
|
||||||
|
""".formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId);
|
||||||
|
Files.writeString(mciIoDir.resolve(interfaceId + "_O.java"), mciResContent);
|
||||||
|
|
||||||
|
String converterContent = """
|
||||||
|
package %s.converter;
|
||||||
|
|
||||||
|
import %s.dto.%sRequest;
|
||||||
|
import %s.dto.%sResponse;
|
||||||
|
import %s.%s.io.%s_I;
|
||||||
|
import %s.%s.io.%s_O;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.converter
|
||||||
|
* @className %sConverter
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Mapper(componentModel = "spring")
|
||||||
|
public interface %sConverter {
|
||||||
|
|
||||||
|
@Mapping(source = "phoneNumber", target = "phone")
|
||||||
|
@Mapping(source = "message", target = "content")
|
||||||
|
%s_I toLegacyRequest(%sRequest req);
|
||||||
|
|
||||||
|
@Mapping(source = "phone", target = "phoneNumber")
|
||||||
|
@Mapping(source = "content", target = "message")
|
||||||
|
%sRequest toRequest(%s_I mciReq);
|
||||||
|
|
||||||
|
// %sResponse toResponse(%s_O mciRes);
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
bizPackage,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId,
|
||||||
|
BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId,
|
||||||
|
bizPackage, baseName, author, createDate, createDate, author,
|
||||||
|
baseName, interfaceId, baseName,
|
||||||
|
baseName, interfaceId,
|
||||||
|
baseName, interfaceId
|
||||||
|
);
|
||||||
|
Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent);
|
||||||
|
|
||||||
|
log.append("\n=========================================\n");
|
||||||
|
log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n");
|
||||||
|
log.append("=========================================\n");
|
||||||
|
log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n");
|
||||||
|
log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n");
|
||||||
|
log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n");
|
||||||
|
log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n");
|
||||||
|
log.append("[MCI Request IO] ").append(mciIoDir.resolve(interfaceId + "_I.java")).append("\n");
|
||||||
|
log.append("[MCI Response IO] ").append(mciIoDir.resolve(interfaceId + "_O.java")).append("\n");
|
||||||
|
log.append("[MCI Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n");
|
||||||
|
|
||||||
|
if (!clientPrefixCap.isEmpty()) {
|
||||||
|
String mciClientContent = """
|
||||||
|
package %s.%s;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
|
||||||
|
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.%s
|
||||||
|
* @className Mci%sClient
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class Mci%sClient {
|
||||||
|
private final AxhubMciComponent mci;
|
||||||
|
|
||||||
|
public Transfer<Object> callTo(String interfaceId, String dummy, Object mciReq, Class<Object> resType) throws Exception {
|
||||||
|
return mci.callTo(interfaceId, dummy, mciReq, resType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
BASE_PACKAGE, mciGroupPath.replace("/", "."),
|
||||||
|
BASE_PACKAGE, mciGroupPath.replace("/", "."), clientPrefixCap, author, createDate, createDate, author, clientPrefixCap
|
||||||
|
);
|
||||||
|
Files.writeString(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java"), mciClientContent);
|
||||||
|
log.append("[MCI Client] ").append(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java")).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
String legacyReqContent = """
|
||||||
|
package %s.legacy;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.legacy
|
||||||
|
* @className %sLegacyRequest
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class %sLegacyRequest {
|
||||||
|
/**
|
||||||
|
* EAI 시스템이 요구하는 수신자 번호 파라미터명
|
||||||
|
*/
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EAI 시스템이 요구하는 메시지 내용 파라미터명
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
|
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
|
||||||
|
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyRequest.java"), legacyReqContent);
|
||||||
|
|
||||||
|
String legacyResContent = """
|
||||||
|
package %s.legacy;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.legacy
|
||||||
|
* @className %sLegacyResponse
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class %sLegacyResponse {
|
||||||
|
// TODO: Add legacy response fields here
|
||||||
|
}
|
||||||
|
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
|
||||||
|
Files.writeString(legacyDtoDir.resolve(baseName + "LegacyResponse.java"), legacyResContent);
|
||||||
|
|
||||||
|
String converterContent = """
|
||||||
|
package %s.converter;
|
||||||
|
|
||||||
|
import %s.dto.%sRequest;
|
||||||
|
import %s.dto.%sResponse;
|
||||||
|
import %s.legacy.%sLegacyRequest;
|
||||||
|
import %s.legacy.%sLegacyResponse;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package %s.converter
|
||||||
|
* @className %sConverter
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author %s
|
||||||
|
* @create %s
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* %s %s 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Mapper(componentModel = "spring")
|
||||||
|
public interface %sConverter {
|
||||||
|
|
||||||
|
@Mapping(source = "phoneNumber", target = "phone")
|
||||||
|
@Mapping(source = "message", target = "content")
|
||||||
|
%sLegacyRequest toLegacyRequest(%sRequest req);
|
||||||
|
|
||||||
|
@Mapping(source = "phone", target = "phoneNumber")
|
||||||
|
@Mapping(source = "content", target = "message")
|
||||||
|
%sRequest toRequest(%sLegacyRequest legacyRequest);
|
||||||
|
|
||||||
|
// %sResponse toResponse(%sLegacyResponse legacyResponse);
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
bizPackage,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName,
|
||||||
|
bizPackage, baseName, author, createDate, createDate, author,
|
||||||
|
baseName, baseName, baseName, baseName, baseName, baseName, baseName
|
||||||
|
);
|
||||||
|
Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent);
|
||||||
|
|
||||||
|
log.append("\n=========================================\n");
|
||||||
|
log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n");
|
||||||
|
log.append("=========================================\n");
|
||||||
|
log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n");
|
||||||
|
log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n");
|
||||||
|
log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n");
|
||||||
|
log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n");
|
||||||
|
log.append("[Legacy Request DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyRequest.java")).append("\n");
|
||||||
|
log.append("[Legacy Response DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyResponse.java")).append("\n");
|
||||||
|
log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n");
|
||||||
|
}
|
||||||
|
// schema resource 파일 생성 (useSchemaResource=true 일 때)
|
||||||
|
if (useSchemaResource) {
|
||||||
|
Files.createDirectories(schemaDir);
|
||||||
|
String inputSchema = """
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"TODO_FIELD": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "TODO: 파라미터 설명을 입력하세요."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
String outputSchema = """
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "처리 결과 상태 (SUCCESS / FAILURE)",
|
||||||
|
"enum": ["SUCCESS", "FAILURE"]
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "처리 결과 메시지"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["status"]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(schemaDir.resolve(inputSchemaFileName), inputSchema);
|
||||||
|
Files.writeString(schemaDir.resolve(outputSchemaFileName), outputSchema);
|
||||||
|
log.append("[Input Schema] ").append(schemaDir.resolve(inputSchemaFileName)).append("\n");
|
||||||
|
log.append("[Output Schema] ").append(schemaDir.resolve(outputSchemaFileName)).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.append("\n Tip: ").append(interfaceId).append(" 목업 데이터를 mock-responses.json에 추가하세요.\n");
|
||||||
|
|
||||||
|
return log.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toKebabCase(String pascalCase) {
|
||||||
|
if (pascalCase == null || pascalCase.isEmpty()) return pascalCase;
|
||||||
|
return pascalCase
|
||||||
|
.replaceAll("([a-z0-9])([A-Z])", "$1-$2")
|
||||||
|
.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toToolName(String moduleName, String group, String baseName) {
|
||||||
|
String moduleDirectory = Path.of(moduleName).getFileName().toString();
|
||||||
|
String pod = moduleDirectory.startsWith("dap-was-")
|
||||||
|
? moduleDirectory.substring("dap-was-".length())
|
||||||
|
: "oth";
|
||||||
|
String normalizedName = baseName.replaceAll("([a-z0-9])([A-Z])", "$1 $2")
|
||||||
|
.toLowerCase(Locale.ROOT)
|
||||||
|
.replaceAll("[^a-z0-9]+", " ")
|
||||||
|
.trim();
|
||||||
|
String[] words = normalizedName.split("\\s+");
|
||||||
|
String service = words[0];
|
||||||
|
String action = words.length == 1 ? "execute" : words[words.length - 1];
|
||||||
|
return "%s.%s.%s.%s".formatted(
|
||||||
|
pod.toLowerCase(Locale.ROOT),
|
||||||
|
group.toLowerCase(Locale.ROOT),
|
||||||
|
service,
|
||||||
|
action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toPascalCase(String str) {
|
||||||
|
if (str == null || str.isEmpty()) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
boolean capitalizeNext = true;
|
||||||
|
for (char c : str.toCharArray()) {
|
||||||
|
if (c == '_' || c == '-' || c == ' ') {
|
||||||
|
capitalizeNext = true;
|
||||||
|
} else if (capitalizeNext) {
|
||||||
|
result.append(Character.toUpperCase(c));
|
||||||
|
capitalizeNext = false;
|
||||||
|
} else {
|
||||||
|
result.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.length() > 0) {
|
||||||
|
result.setCharAt(0, Character.toUpperCase(result.charAt(0)));
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,14 @@
|
|||||||
package io.shinhanlife.dap.lib.util;
|
package io.shinhanlife.dap.lib.util;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.util
|
|
||||||
* @className ToolSchemaResolver
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
// removed McpOutputSchema
|
||||||
import io.shinhanlife.dap.lib.annotation.McpOutputSchema;import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
|
||||||
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
|
||||||
/** Resolves an MCP Tool input schema from resource, inline value, or DTO metadata. */
|
/** Resolves an MCP Tool input schema from resource, inline value, or DTO metadata. */
|
||||||
public class ToolSchemaResolver {
|
public class ToolSchemaResolver {
|
||||||
|
|
||||||
@@ -32,13 +18,9 @@ public class ToolSchemaResolver {
|
|||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> resolve(McpFunction function, Class<?> requestType) {
|
public Map<String, Object> resolve(org.springframework.ai.mcp.annotation.McpTool function, ToolHint hint, Class<?> requestType) {
|
||||||
if (function != null && !function.inputSchemaResource().isBlank()) {
|
if (hint != null && !hint.inputSchemaResource().isBlank()) {
|
||||||
return loadResource(function.inputSchemaResource());
|
return loadResource(hint.inputSchemaResource());
|
||||||
}
|
|
||||||
if (function != null && !function.inputSchema().isBlank()
|
|
||||||
&& !"{}".equals(function.inputSchema().trim())) {
|
|
||||||
return parse(function.inputSchema(), "McpFunction.inputSchema");
|
|
||||||
}
|
}
|
||||||
return JsonSchemaGenerator.generateSchema(requestType);
|
return JsonSchemaGenerator.generateSchema(requestType);
|
||||||
}
|
}
|
||||||
@@ -46,25 +28,36 @@ public class ToolSchemaResolver {
|
|||||||
/**
|
/**
|
||||||
* Resolves an explicitly declared response schema.
|
* Resolves an explicitly declared response schema.
|
||||||
* Response schemas are opt-in so existing tools keep their current response behavior.
|
* Response schemas are opt-in so existing tools keep their current response behavior.
|
||||||
|
* ToolHint.outputSchemaResource()가 있으면 classpath JSON 파일에서 로드하고,
|
||||||
|
* 없으면 responseType DTO를 분석하여 자동 생성합니다.
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> resolveOutput(McpFunction function, Class<?> responseType) {
|
public Map<String, Object> resolveOutput(org.springframework.ai.mcp.annotation.McpTool function, Class<?> responseType, ToolHint hint) {
|
||||||
if (function != null && !function.outputSchemaResource().isBlank()) {
|
if (hint != null && !hint.outputSchemaResource().isBlank()) {
|
||||||
return loadResource(function.outputSchemaResource());
|
return loadResource(hint.outputSchemaResource());
|
||||||
}
|
}
|
||||||
if (function != null && !function.outputSchema().isBlank()
|
return resolveOutput(function, responseType);
|
||||||
&& !"{}".equals(function.outputSchema().trim())) {
|
}
|
||||||
return parse(function.outputSchema(), "McpFunction.outputSchema");
|
|
||||||
|
/**
|
||||||
|
* Resolves an explicitly declared response schema.
|
||||||
|
* Response schemas are opt-in so existing tools keep their current response behavior.
|
||||||
|
*/
|
||||||
|
public Map<String, Object> resolveOutput(org.springframework.ai.mcp.annotation.McpTool function, Class<?> responseType) {
|
||||||
|
// Object, Map 등 구체적인 DTO가 아닌 경우 검증 스킵
|
||||||
|
if (responseType == null
|
||||||
|
|| responseType == Object.class
|
||||||
|
|| Map.class.isAssignableFrom(responseType)
|
||||||
|
|| responseType == Void.class
|
||||||
|
|| responseType == void.class) {
|
||||||
|
return Map.of();
|
||||||
}
|
}
|
||||||
if (responseType != null && responseType.isAnnotationPresent(McpOutputSchema.class)) {
|
return JsonSchemaGenerator.generateSchema(responseType);
|
||||||
return JsonSchemaGenerator.generateSchema(responseType);
|
|
||||||
}
|
|
||||||
return Map.of();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retained for callers that use only explicit output schemas.
|
* Retained for callers that use only explicit output schemas.
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> resolveOutput(McpFunction function) {
|
public Map<String, Object> resolveOutput(org.springframework.ai.mcp.annotation.McpTool function) {
|
||||||
return resolveOutput(function, null);
|
return resolveOutput(function, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package io.shinhanlife.dap.lib.util;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.lib.util
|
||||||
|
* @className ToolSourceUpdater
|
||||||
|
* @description AX HUB 시스템 처리 클래스
|
||||||
|
* @author 0986406
|
||||||
|
* @create 2026.09.01
|
||||||
|
* <pre>
|
||||||
|
* ---------- 개정이력 ----------
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2026.09.01 0986406 최초생성
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class ToolSourceUpdater {
|
||||||
|
|
||||||
|
public static void updateToolSource(String toolName, String domainGroup, String description, boolean register, Boolean requiresApproval) throws Exception {
|
||||||
|
// 1. Find all *UseCase.java files in dap-was-* directories
|
||||||
|
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
|
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||||
|
|
||||||
|
List<Path> javaFiles;
|
||||||
|
try (Stream<Path> paths = Files.walk(rootDir)) {
|
||||||
|
javaFiles = paths
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.filter(p -> p.toString().endsWith("UseCase.java"))
|
||||||
|
.filter(p -> p.toString().contains("dap-was-") || p.toString().contains("axhub-tool-"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
Path targetFile = null;
|
||||||
|
String content = null;
|
||||||
|
|
||||||
|
// 2. Find the specific file for the tool
|
||||||
|
String functionName = toolName;
|
||||||
|
if (toolName.contains("_")) {
|
||||||
|
functionName = toolName.substring(toolName.indexOf("_") + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern namePattern = Pattern.compile("@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"", Pattern.DOTALL);
|
||||||
|
Pattern namePattern2 = Pattern.compile("@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(functionName) + "\"", Pattern.DOTALL);
|
||||||
|
|
||||||
|
for (Path path : javaFiles) {
|
||||||
|
String text = Files.readString(path);
|
||||||
|
if (namePattern.matcher(text).find()) {
|
||||||
|
targetFile = path;
|
||||||
|
content = text;
|
||||||
|
break;
|
||||||
|
} else if (namePattern2.matcher(text).find()) {
|
||||||
|
targetFile = path;
|
||||||
|
content = text;
|
||||||
|
toolName = functionName; // Use baseName for subsequent replacements
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetFile == null) {
|
||||||
|
throw new Exception("소스 코드를 찾을 수 없습니다: " + toolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Update @McpTool group
|
||||||
|
if (domainGroup != null && !domainGroup.trim().isEmpty()) {
|
||||||
|
Pattern groupPattern = Pattern.compile("(@McpTool\\s*\\([^)]*group\\s*=\\s*\")([^\"]+)(\")", Pattern.DOTALL);
|
||||||
|
Matcher groupMatcher = groupPattern.matcher(content);
|
||||||
|
if (groupMatcher.find()) {
|
||||||
|
content = groupMatcher.replaceFirst("$1" + domainGroup + "$3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Update @McpFunction description
|
||||||
|
if (description != null) {
|
||||||
|
Pattern funcPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"[^)]*description\\s*=\\s*\")([^\"]+)(\")", Pattern.DOTALL);
|
||||||
|
Matcher funcMatcher = funcPattern.matcher(content);
|
||||||
|
if (funcMatcher.find()) {
|
||||||
|
content = funcMatcher.replaceFirst("$1" + description.replace("\\", "\\\\").replace("$", "\\\\$") + "$3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Update register flag
|
||||||
|
Pattern regPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"[^)]*register\\s*=\\s*)(true|false)([^a-zA-Z0-9])", Pattern.DOTALL);
|
||||||
|
Matcher regMatcher = regPattern.matcher(content);
|
||||||
|
if (regMatcher.find()) {
|
||||||
|
content = regMatcher.replaceFirst("$1" + register + "$3");
|
||||||
|
} else {
|
||||||
|
Pattern addRegPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\")", Pattern.DOTALL);
|
||||||
|
Matcher addRegMatcher = addRegPattern.matcher(content);
|
||||||
|
if (addRegMatcher.find()) {
|
||||||
|
content = addRegMatcher.replaceFirst("$1, register = " + register);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5.5 Update requiresApproval flag
|
||||||
|
if (requiresApproval != null) {
|
||||||
|
Pattern appPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\"[^)]*requiresApproval\\s*=\\s*)(true|false)([^a-zA-Z0-9])", Pattern.DOTALL);
|
||||||
|
Matcher appMatcher = appPattern.matcher(content);
|
||||||
|
if (appMatcher.find()) {
|
||||||
|
content = appMatcher.replaceFirst("$1" + requiresApproval + "$3");
|
||||||
|
} else {
|
||||||
|
Pattern addAppPattern = Pattern.compile("(@McpFunction\\s*\\([^)]*name\\s*=\\s*\"" + Pattern.quote(toolName) + "\")", Pattern.DOTALL);
|
||||||
|
Matcher addAppMatcher = addAppPattern.matcher(content);
|
||||||
|
if (addAppMatcher.find()) {
|
||||||
|
content = addAppMatcher.replaceFirst("$1, requiresApproval = " + requiresApproval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Write back to file
|
||||||
|
Files.writeString(targetFile, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.validation;
|
package io.shinhanlife.dap.lib.validation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.validation
|
|
||||||
* @className McpToolNameValidationRunner
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
/** Gradle entry point for validating unique MCP Tool names before packaging. */
|
/** Gradle entry point for validating unique MCP Tool names before packaging. */
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.validation;
|
package io.shinhanlife.dap.lib.validation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.validation
|
|
||||||
* @className McpToolNameValidator
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.validation;
|
package io.shinhanlife.dap.lib.validation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.validation
|
|
||||||
* @className ToolArgumentSchemaValidator
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.networknt.schema.Error;
|
import com.networknt.schema.Error;
|
||||||
import com.networknt.schema.InputFormat;
|
import com.networknt.schema.InputFormat;
|
||||||
|
|||||||
@@ -2,14 +2,23 @@ package io.shinhanlife.dap.mcc.dto;
|
|||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import io.shinhanlife.dap.lib.dto.OperationType;
|
||||||
/**
|
/**
|
||||||
* @package io.shinhanlife.dap.mcc.dto
|
* Tool(Agent)의 명세 및 라우팅 정보를 담고 있는 메타데이터 클래스
|
||||||
|
* Redis 레지스트리에 저장되며, Planner와 Router 간의 통신 객체(Plan)로 사용됩니다.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.mcg.dto
|
||||||
* @className ToolMetadata
|
* @className ToolMetadata
|
||||||
* @description AX HUB 시스템 처리 클래스
|
* @description AX HUB 시스템 처리 클래스
|
||||||
* @author 0986406
|
* @author 0986406
|
||||||
@@ -22,17 +31,16 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
@Builder
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
public class ToolMetadata {
|
public class ToolMetadata {
|
||||||
|
|
||||||
// 1. Tool 기본 정보
|
// 1. Tool 기본 정보
|
||||||
private String uid; // UUID 형식의 고유 식별자
|
private String uid; // UUID 형식의 고유 식별자
|
||||||
private String semver; // 버전 (예: 1.0.0)
|
private String semver; // 버전 (예: 1.0.0)
|
||||||
private Long timeoutMillis;
|
|
||||||
private Boolean enabled;
|
|
||||||
private String displayName; // 사람이 읽는 라벨 (1-128자)
|
private String displayName; // 사람이 읽는 라벨 (1-128자)
|
||||||
private String name; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool)
|
private String name; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool)
|
||||||
private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능)
|
private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능)
|
||||||
@@ -45,18 +53,26 @@ public class ToolMetadata {
|
|||||||
|
|
||||||
// 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식)
|
// 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식)
|
||||||
private String categoryKey;
|
private String categoryKey;
|
||||||
|
|
||||||
|
// 2-2. 툴 처리 엔드포인트 URI 경로 (예: /api/tool/customer-info)
|
||||||
private String endpoint;
|
private String endpoint;
|
||||||
|
|
||||||
|
// 2-3. Pod 실행 URL (독립적인 Microservice 라우팅용, 예: http://localhost:8082)
|
||||||
private String podUrl;
|
private String podUrl;
|
||||||
private String integrationType;
|
|
||||||
private String mciServiceId;
|
|
||||||
|
|
||||||
|
|
||||||
|
// 2-4. 가시성 여부
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private Boolean visible = true;
|
private Boolean visible = true;
|
||||||
|
|
||||||
|
// 활성화 여부
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean enabled = true;
|
||||||
|
|
||||||
|
// 2-5. Redis 등록 여부 (UI 표출용)
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private Boolean isRegistered = true;
|
private Boolean isRegistered = true;
|
||||||
|
|
||||||
|
// 2-6. HITL 승인 필요 여부
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private Boolean requiresApproval = false;
|
private Boolean requiresApproval = false;
|
||||||
|
|
||||||
@@ -73,4 +89,45 @@ public class ToolMetadata {
|
|||||||
private Boolean openWorldHint = false;
|
private Boolean openWorldHint = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI)
|
||||||
|
private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI"
|
||||||
|
|
||||||
|
// 4. 레거시(MCI/EAI) 연동 시 필수 정보 (integrationType이 "MCI_EAI"일 때 사용)
|
||||||
|
private String mciServiceId; // MCI/EAI 호출을 위한 서비스 ID (예: CRM_001, LICO_992)
|
||||||
|
|
||||||
|
// 5. 인프라 상태 정보 (DIRECT 연동 시 사용)
|
||||||
|
private Long lastHeartbeat; // Redis TTL 갱신용 마지막 하트비트 타임스탬프
|
||||||
|
|
||||||
|
// 6. 동적 서킷 브레이커 & 속도 제어 설정 (Registry 기반)
|
||||||
|
private Integer failureRateThreshold; // 서킷 브레이커 동작 기준 실패율 (%)
|
||||||
|
private Integer slidingWindowSize; // 서킷 브레이커 에러율 계산 표본 요청 수
|
||||||
|
private Integer rateLimitForPeriod; // 속도 제어: 1초당 허용 최대 요청 수
|
||||||
|
|
||||||
|
// 7. Gateway 코어 제어용 설정 필드 추가 (재시도, 타임아웃, 오퍼레이션 타입)
|
||||||
|
@Builder.Default
|
||||||
|
private OperationType operationType = OperationType.READ;
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean retryEnabled = true;
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
private Integer circuitBreakerFailureThreshold = 0;
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
private Long circuitBreakerOpenMillis = 0L;
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
private Long timeoutMillis = 0L;
|
||||||
|
|
||||||
|
// --- Guardrail 호환성을 위한 메서드 추가 ---
|
||||||
|
public Set<String> allowedArguments() {
|
||||||
|
if (parametersSchema == null || !parametersSchema.containsKey("properties")) return Set.of();
|
||||||
|
return ((Map<String, Object>) parametersSchema.get("properties")).keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> requiredArguments() {
|
||||||
|
if (parametersSchema == null || !parametersSchema.containsKey("required")) return Set.of();
|
||||||
|
return new HashSet<>((List<String>) parametersSchema.get("required"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,24 @@ package io.shinhanlife.dap.mcc.presentation;
|
|||||||
/**
|
/**
|
||||||
* @package io.shinhanlife.dap.mcc.presentation
|
* @package io.shinhanlife.dap.mcc.presentation
|
||||||
* @className BusinessToolController
|
* @className BusinessToolController
|
||||||
* @description AX HUB ??戮?츩??嶺뚳퐣瑗????????
|
* @description AX HUB 시스템 처리 클래스
|
||||||
* @author 0986406
|
* @author 0986406
|
||||||
* @create 2026.09.01
|
* @create 2026.09.01
|
||||||
* <pre>
|
* <pre>
|
||||||
* ---------- ?띠룇裕??????----------
|
* ---------- 개정이력 ----------
|
||||||
* ??瑜곸젧?? ??瑜곸젧?? ??瑜곸젧??怨몃뮔
|
* 수정일 수정자 수정내용
|
||||||
* ---------- -------- ---------------------------
|
* ---------- -------- ---------------------------
|
||||||
* 2026.09.01 0986406 嶺뚣끉裕???諛댁뎽
|
* 2026.09.01 0986406 최초생성
|
||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.networknt.schema.Error;
|
import com.networknt.schema.Error;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator;
|
import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
|
||||||
import io.shinhanlife.dap.lib.config.McpProperties;
|
import io.shinhanlife.dap.lib.config.McpProperties;
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import io.shinhanlife.dap.lib.mcp.LocalToolScanner;
|
import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender;
|
||||||
import io.shinhanlife.dap.lib.util.ToolSchemaResolver;
|
import io.shinhanlife.dap.lib.util.ToolSchemaResolver;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -55,17 +54,17 @@ public class BusinessToolController {
|
|||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final McpProperties mcpProperties;
|
private final McpProperties mcpProperties;
|
||||||
private final LocalToolScanner localToolScanner;
|
private final ToolRegistryHeartbeatSender toolRegistryHeartbeatSender;
|
||||||
private final ToolArgumentSchemaValidator toolArgumentSchemaValidator;
|
private final ToolArgumentSchemaValidator toolArgumentSchemaValidator;
|
||||||
|
|
||||||
private final ToolSchemaResolver toolSchemaResolver;
|
private final ToolSchemaResolver toolSchemaResolver;
|
||||||
// ???? ?브퀗?????β돦裕뉛쭚?Tool 嶺뚮ㅄ維뽨빳???븐뼔援?????
|
// 내부 조회용 로컬 Tool 목록 엔드포인트
|
||||||
@GetMapping("/mcp/api/v1/tools/local")
|
@GetMapping("/mcp/api/v1/tools/local")
|
||||||
public List<ToolMetadata> getLocalTools() {
|
public List<ToolMetadata> getLocalTools() {
|
||||||
return localToolScanner.getAllScannedTools();
|
return toolRegistryHeartbeatSender.getAllScannedTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ??戮?빢 REST ?リ옇?↑????됱쓤 ??源녿뮡????븐뼔援?????
|
// 순수 REST 기반 동적 라우팅 엔드포인트
|
||||||
@PostMapping("/mcp/{name}")
|
@PostMapping("/mcp/{name}")
|
||||||
public ResponseEntity<?> executeDynamicTool(
|
public ResponseEntity<?> executeDynamicTool(
|
||||||
@PathVariable("name") String functionName,
|
@PathVariable("name") String functionName,
|
||||||
@@ -78,25 +77,25 @@ public class BusinessToolController {
|
|||||||
String finalRequestId = headerRequestId;
|
String finalRequestId = headerRequestId;
|
||||||
|
|
||||||
log.info(" [Tool] IN - trace-id: {}, request-id: {}", traceId, requestId);
|
log.info(" [Tool] IN - trace-id: {}, request-id: {}", traceId, requestId);
|
||||||
log.info(" [Tool] ???됱쓤 ?????덈뺄 ??븐슙????琉용뼁 (??貫?양춯?: {}", functionName);
|
log.info(" [Tool] 동적 툴 실행 요청 수신 (함수명): {}", functionName);
|
||||||
if (arguments != null) {
|
if (arguments != null) {
|
||||||
try {
|
try {
|
||||||
log.info(" [Tool] ?筌뤾쑵?????逾ф쾬?롮구?? {}", objectMapper.writeValueAsString(arguments));
|
log.info(" [Tool] 호출 파라미터: {}", objectMapper.writeValueAsString(arguments));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info(" [Tool] ?筌뤾쑵?????逾ф쾬?롮구?? {}", arguments);
|
log.info(" [Tool] 호출 파라미터: {}", arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object targetBean = null;
|
Object targetBean = null;
|
||||||
Method targetMethod = null;
|
Method targetMethod = null;
|
||||||
McpFunction targetFunctionAnnotation = null;
|
McpTool targetFunctionAnnotation = null;
|
||||||
|
|
||||||
// McpTool ???????逾???リ옇?↑??브퀗???뱀쾸? ?熬곣뫁夷????쒖굣?節놁뿉??熬곣뫁逾???????깅さ亦껋깢??? ?熬곣뫕??????????щ뤂????@McpFunction??嶺뚢돦堉????덈펲.
|
// McpTool 어노테이션 기반 조회가 프록시 문제로 누락될 수 있으므로, 전체 빈을 순회하며 @McpTool을 찾습니다.
|
||||||
Map<String, Object> allBeans = applicationContext.getBeansOfType(Object.class);
|
Map<String, Object> allBeans = applicationContext.getBeansOfType(Object.class);
|
||||||
outerLoop:
|
outerLoop:
|
||||||
for (Object bean : allBeans.values()) {
|
for (Object bean : allBeans.values()) {
|
||||||
Class<?> targetClass = AopUtils.getTargetClass(bean);
|
Class<?> targetClass = AopUtils.getTargetClass(bean);
|
||||||
for (Method targetMethodOfClass : targetClass.getDeclaredMethods()) {
|
for (Method targetMethodOfClass : targetClass.getDeclaredMethods()) {
|
||||||
McpFunction mcpFunc = AnnotationUtils.findAnnotation(targetMethodOfClass, McpFunction.class);
|
McpTool mcpFunc = AnnotationUtils.findAnnotation(targetMethodOfClass, McpTool.class);
|
||||||
if (mcpFunc != null) {
|
if (mcpFunc != null) {
|
||||||
String baseName = mcpFunc.name();
|
String baseName = mcpFunc.name();
|
||||||
String expectedName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
|
String expectedName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
|
||||||
@@ -122,7 +121,7 @@ public class BusinessToolController {
|
|||||||
for (Object bean : allBeans.values()) {
|
for (Object bean : allBeans.values()) {
|
||||||
Class<?> targetCls = AopUtils.getTargetClass(bean);
|
Class<?> targetCls = AopUtils.getTargetClass(bean);
|
||||||
for (Method m : targetCls.getDeclaredMethods()) {
|
for (Method m : targetCls.getDeclaredMethods()) {
|
||||||
McpFunction func = AnnotationUtils.findAnnotation(m, McpFunction.class);
|
McpTool func = AnnotationUtils.findAnnotation(m, McpTool.class);
|
||||||
if (func != null) {
|
if (func != null) {
|
||||||
String baseName = func.name();
|
String baseName = func.name();
|
||||||
String expName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
|
String expName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
|
||||||
@@ -131,28 +130,29 @@ public class BusinessToolController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.error("[Tool] ???덈뺄????貫??Method)??嶺뚢돦堉??????怨룸????덈펲: {}. ?熬곣뫗?????노뼌????嶺뚮∥?꾥땻??嶺뚮ㅄ維뽨빳? {}", functionName, availableFunctions);
|
log.error("[Tool] 실행할 함수(Method)를 찾을 수 없습니다: {}. 현재 스캔된 툴 메서드 목록: {}", functionName, availableFunctions);
|
||||||
Map<String, Object> errorDetails = new HashMap<>();
|
Map<String, Object> errorDetails = new HashMap<>();
|
||||||
errorDetails.put("status", "404");
|
errorDetails.put("status", "404");
|
||||||
Map<String, Object> errorBody = new HashMap<>();
|
Map<String, Object> errorBody = new HashMap<>();
|
||||||
errorBody.put("code", "TOOL_NOT_FOUND");
|
errorBody.put("code", "TOOL_NOT_FOUND");
|
||||||
errorBody.put("message", "???덈뺄????貫???嶺뚢돦堉??????怨룸????덈펲: " + functionName);
|
errorBody.put("message", "실행할 함수를 찾을 수 없습니다: " + functionName);
|
||||||
errorBody.put("details", errorDetails);
|
errorBody.put("details", errorDetails);
|
||||||
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
||||||
|
|
||||||
return ResponseEntity.status(404).body(errorBody);
|
return ResponseEntity.status(404).body(errorBody);
|
||||||
}
|
}
|
||||||
// (?リ옇???嶺뚢뼰維???β돦裕뉐퐲???蹂ㅽ깴??
|
// (기존 차단 로직 제거됨)
|
||||||
|
|
||||||
// 2. ???逾ф쾬?롮구????ル쪇????롪틵?嶺?(JSON Schema)
|
// 2. 파라미터 유효성 검증 (JSON Schema)
|
||||||
if (targetMethod.getParameterCount() > 0) {
|
if (targetMethod.getParameterCount() > 0) {
|
||||||
Class<?> paramType = targetMethod.getParameterTypes()[0];
|
Class<?> paramType = targetMethod.getParameterTypes()[0];
|
||||||
if (!Map.class.isAssignableFrom(paramType)) {
|
if (!Map.class.isAssignableFrom(paramType)) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> inputSchema = toolSchemaResolver.resolve(targetFunctionAnnotation, paramType);
|
io.shinhanlife.dap.lib.annotation.ToolHint hint = AnnotationUtils.findAnnotation(targetMethod, io.shinhanlife.dap.lib.annotation.ToolHint.class);
|
||||||
|
Map<String, Object> inputSchema = toolSchemaResolver.resolve(targetFunctionAnnotation, hint, paramType);
|
||||||
List<Error> errors = toolArgumentSchemaValidator.validate(inputSchema, arguments);
|
List<Error> errors = toolArgumentSchemaValidator.validate(inputSchema, arguments);
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
log.error("[Tool] ???逾ф쾬?롮구????ル쪇????롪틵?嶺????덉넮: {}", errors);
|
log.error("[Tool] 파라미터 유효성 검증 실패: {}", errors);
|
||||||
List<String> errorMessages = new ArrayList<>();
|
List<String> errorMessages = new ArrayList<>();
|
||||||
for (Error validationError : errors) {
|
for (Error validationError : errors) {
|
||||||
errorMessages.add(validationError.getMessage());
|
errorMessages.add(validationError.getMessage());
|
||||||
@@ -161,28 +161,28 @@ public class BusinessToolController {
|
|||||||
errorDetails.put("status", "422");
|
errorDetails.put("status", "422");
|
||||||
Map<String, Object> errorBody = new HashMap<>();
|
Map<String, Object> errorBody = new HashMap<>();
|
||||||
errorBody.put("code", "INVALID_PARAM");
|
errorBody.put("code", "INVALID_PARAM");
|
||||||
errorBody.put("message", "???逾ф쾬?롮구????ル쪇????롪틵?嶺????덉넮");
|
errorBody.put("message", "파라미터 유효성 검증 실패");
|
||||||
errorBody.put("details", errorDetails);
|
errorBody.put("details", errorDetails);
|
||||||
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
if (finalRequestId != null) errorBody.put("request_id", finalRequestId);
|
||||||
|
|
||||||
return ResponseEntity.status(422).body(errorBody);
|
return ResponseEntity.status(422).body(errorBody);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[Tool] ???꾪뀞嶺??롪틵?嶺?繞????댁쾼 ?꾩룇裕뉑틦? {}", e.getMessage());
|
log.error("[Tool] 스키마 검증 중 오류 발생: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("[Tool] ?洹먮뿫???源끒?嶺뚯쉳??????덈뺄 -> Method: {}", targetMethod.getName());
|
log.info("[Tool] 리플렉션 직접 실행 -> Method: {}", targetMethod.getName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 3. DTO ???逾ф쾬?롮구?????吏?嶺뚮씞?뗩뇡?(Map -> DTO)
|
// 3. DTO 파라미터 자동 매핑 (Map -> DTO)
|
||||||
Object invokeArgument = arguments;
|
Object invokeArgument = arguments;
|
||||||
if (targetMethod.getParameterCount() > 0 && arguments != null) {
|
if (targetMethod.getParameterCount() > 0 && arguments != null) {
|
||||||
Class<?> paramType = targetMethod.getParameterTypes()[0];
|
Class<?> paramType = targetMethod.getParameterTypes()[0];
|
||||||
if (!Map.class.isAssignableFrom(paramType)) {
|
if (!Map.class.isAssignableFrom(paramType)) {
|
||||||
invokeArgument = objectMapper.convertValue(arguments, paramType);
|
invokeArgument = objectMapper.convertValue(arguments, paramType);
|
||||||
log.info("[Tool] DTO ???吏?嶺뚮씞?뗩뇡??繹먭퍓沅? {}", paramType.getSimpleName());
|
log.info("[Tool] DTO 자동 매핑 성공: {}", paramType.getSimpleName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +194,8 @@ public class BusinessToolController {
|
|||||||
methodResult = targetMethod.invoke(targetBean, invokeArgument);
|
methodResult = targetMethod.invoke(targetBean, invokeArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> outputSchema = toolSchemaResolver.resolveOutput(targetFunctionAnnotation, targetMethod.getReturnType());
|
io.shinhanlife.dap.lib.annotation.ToolHint outputHint = AnnotationUtils.findAnnotation(targetMethod, io.shinhanlife.dap.lib.annotation.ToolHint.class);
|
||||||
|
Map<String, Object> outputSchema = toolSchemaResolver.resolveOutput(targetFunctionAnnotation, targetMethod.getReturnType(), outputHint);
|
||||||
if (!outputSchema.isEmpty()) {
|
if (!outputSchema.isEmpty()) {
|
||||||
List<Error> outputErrors = toolArgumentSchemaValidator.validateValue(outputSchema, methodResult);
|
List<Error> outputErrors = toolArgumentSchemaValidator.validateValue(outputSchema, methodResult);
|
||||||
if (!outputErrors.isEmpty()) {
|
if (!outputErrors.isEmpty()) {
|
||||||
@@ -212,11 +213,11 @@ public class BusinessToolController {
|
|||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
// 5. ?롪퍒????꾩룇瑗??(??戮?빢 REST ??얜Ŧ堉?
|
// 5. 결과 반환 (순수 REST 응답)
|
||||||
try {
|
try {
|
||||||
log.info("[Tool Execution] Output Schema Result: {}", objectMapper.writeValueAsString(methodResult));
|
log.info("[Tool -> MCP Gateway] Output Schema Result: {}", objectMapper.writeValueAsString(methodResult));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info("[Tool Execution] Output Schema Result: {}", methodResult);
|
log.info("[Tool -> MCP Gateway] Output Schema Result: {}", methodResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(" [Tool] OUT - trace-id: {}, request-id: {}", traceId, requestId);
|
log.info(" [Tool] OUT - trace-id: {}, request-id: {}", traceId, requestId);
|
||||||
@@ -228,7 +229,7 @@ public class BusinessToolController {
|
|||||||
return responseBuilder.body(methodResult);
|
return responseBuilder.body(methodResult);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[Tool] ?洹먮뿫???源끒????덈뺄 繞????깅뇶 ?꾩룇裕뉑틦? {}", e.getMessage());
|
log.error("[Tool] 리플렉션 실행 중 예외 발생: {}", e.getMessage());
|
||||||
Map<String, Object> errorDetails = new HashMap<>();
|
Map<String, Object> errorDetails = new HashMap<>();
|
||||||
errorDetails.put("status", "500");
|
errorDetails.put("status", "500");
|
||||||
Map<String, Object> errorBody = new HashMap<>();
|
Map<String, Object> errorBody = new HashMap<>();
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.mcc.presentation;
|
package io.shinhanlife.dap.mcc.presentation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.presentation
|
|
||||||
* @className ToolManifestController
|
|
||||||
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 媛쒖젙?대젰 ----------
|
|
||||||
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 理쒖큹?앹꽦
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.manifest.ToolManifestResponse;
|
import io.shinhanlife.dap.lib.manifest.ToolManifestResponse;
|
||||||
import io.shinhanlife.dap.lib.manifest.ToolManifestService;
|
import io.shinhanlife.dap.lib.manifest.ToolManifestService;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@@ -45,4 +29,4 @@ public class ToolManifestController {
|
|||||||
}
|
}
|
||||||
return ResponseEntity.ok().eTag(eTag).body(manifest);
|
return ResponseEntity.ok().eTag(eTag).body(manifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.glow.communication.annotation;
|
package io.shinhanlife.glow.communication.annotation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.communication.annotation
|
|
||||||
* @className GlowTrgmField
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
|
|||||||
@@ -1,20 +1,4 @@
|
|||||||
package io.shinhanlife.glow.communication.dto;
|
package io.shinhanlife.glow.communication.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.communication.dto
|
|
||||||
* @className CommonHeader
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@Data
|
@Data
|
||||||
public class CommonHeader {
|
public class CommonHeader {
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.glow.communication.dto;
|
package io.shinhanlife.glow.communication.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.communication.dto
|
|
||||||
* @className HeaderDefaults
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
public enum HeaderDefaults {
|
public enum HeaderDefaults {
|
||||||
ITRF_ID, RCV_SVC_ID, STR_YMD, ACNT_OGNZ_NO, PSMR_ASRT_CD, SBSN_RULP_ASRT_CD, BSDU_CD, BSQU_CD,
|
ITRF_ID, RCV_SVC_ID, STR_YMD, ACNT_OGNZ_NO, PSMR_ASRT_CD, SBSN_RULP_ASRT_CD, BSDU_CD, BSQU_CD,
|
||||||
INDV_CTIN_ROLE_CD, SCRN_ID, OGNZ_ASRT_CD, OGNZ_LEVE_CD, TGRM_CREA_CHNN_TYPE_CD, ENVR_TYPE_CD
|
INDV_CTIN_ROLE_CD, SCRN_ID, OGNZ_ASRT_CD, OGNZ_LEVE_CD, TGRM_CREA_CHNN_TYPE_CD, ENVR_TYPE_CD
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.glow.communication.dto;
|
package io.shinhanlife.glow.communication.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.communication.dto
|
|
||||||
* @className Transfer
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.glow.communication.exception;
|
package io.shinhanlife.glow.communication.exception;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.communication.exception
|
|
||||||
* @className ItrfException
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
public class ItrfException extends Exception {
|
public class ItrfException extends Exception {
|
||||||
public ItrfException(String msg) {
|
public ItrfException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.glow.communication.module.eai.component;
|
package io.shinhanlife.glow.communication.module.eai.component;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.communication.module.eai.component
|
|
||||||
* @className GlowEaiComponent
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.glow.communication.dto.Transfer;
|
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.glow.communication.module.mci.component;
|
package io.shinhanlife.glow.communication.module.mci.component;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.communication.module.mci.component
|
|
||||||
* @className GlowMciComponent
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.glow.communication.dto.Transfer;
|
import io.shinhanlife.glow.communication.dto.Transfer;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,4 @@
|
|||||||
package io.shinhanlife.glow.communication.util;
|
package io.shinhanlife.glow.communication.util;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.communication.util
|
|
||||||
* @className CommonHeaderFactory
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
import io.shinhanlife.glow.communication.dto.CommonHeader;
|
import io.shinhanlife.glow.communication.dto.CommonHeader;
|
||||||
import io.shinhanlife.glow.communication.dto.HeaderDefaults;
|
import io.shinhanlife.glow.communication.dto.HeaderDefaults;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.glow.util;
|
package io.shinhanlife.glow.util;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.util
|
|
||||||
* @className GlowMciParser
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.glow.GlowMciFieldInfo;
|
import io.shinhanlife.glow.GlowMciFieldInfo;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ logging:
|
|||||||
pattern:
|
pattern:
|
||||||
console: "[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger{36} - %msg%n"
|
console: "[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger{36} - %msg%n"
|
||||||
|
|
||||||
# 2. Redis 공통 설정 (WAS 연동 및 캐싱)
|
# 2. Redis 공통 설정 (Gateway 연동 및 캐싱)
|
||||||
spring:
|
spring:
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<div class="flex items-center space-x-5">
|
<div class="flex items-center space-x-5">
|
||||||
<a href="/index.html" class="flex items-center group" style="text-decoration:none;">
|
<a href="/index.html" class="flex items-center group" style="text-decoration:none;">
|
||||||
<div class="w-2 h-2 rounded-full mr-2" style="background:#3b82f6; box-shadow: 0 0 8px rgba(59,130,246,0.8);"></div>
|
<div class="w-2 h-2 rounded-full mr-2" style="background:#3b82f6; box-shadow: 0 0 8px rgba(59,130,246,0.8);"></div>
|
||||||
<span class="font-semibold tracking-tight text-sm" style="color:#f4f4f5;">AXHUB WAS</span>
|
<span class="font-semibold tracking-tight text-sm" style="color:#f4f4f5;">AXHUB Gateway</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="h-4 w-px" style="background:#27272a;"></div>
|
<div class="h-4 w-px" style="background:#27272a;"></div>
|
||||||
<nav class="flex space-x-5 text-[13px] font-medium">
|
<nav class="flex space-x-5 text-[13px] font-medium">
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
function requestId() { return crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`; }
|
function requestId() { return crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`; }
|
||||||
function escapeHtml(value) { return String(value).replace(/[&<>]/g, item => ({'&':'&','<':'<','>':'>'}[item])); }
|
function escapeHtml(value) { return String(value).replace(/[&<>]/g, item => ({'&':'&','<':'<','>':'>'}[item])); }
|
||||||
|
|
||||||
let isWasMode = false;
|
let isGatewayMode = false;
|
||||||
|
|
||||||
async function loadManifest() {
|
async function loadManifest() {
|
||||||
$('manifestStatus').textContent = 'Manifest loading';
|
$('manifestStatus').textContent = 'Manifest loading';
|
||||||
@@ -90,13 +90,13 @@
|
|||||||
if (!response.ok && response.status === 404) {
|
if (!response.ok && response.status === 404) {
|
||||||
// Gateway 환경 감지 및 Fallback 처리
|
// Gateway 환경 감지 및 Fallback 처리
|
||||||
response = await fetch('/mcp/api/v1/tools/list');
|
response = await fetch('/mcp/api/v1/tools/list');
|
||||||
if (!response.ok) throw new Error(`WAS HTTP ${response.status}`);
|
if (!response.ok) throw new Error(`Gateway HTTP ${response.status}`);
|
||||||
|
|
||||||
const rpcData = await response.json();
|
const rpcData = await response.json();
|
||||||
state.tools = rpcData.result?.tools || [];
|
state.tools = rpcData.result?.tools || [];
|
||||||
isWasMode = true;
|
isGatewayMode = true;
|
||||||
|
|
||||||
$('manifestStatus').textContent = `${state.tools.length} tools · WAS Mode`;
|
$('manifestStatus').textContent = `${state.tools.length} tools · Gateway Mode`;
|
||||||
$('manifestStatus').className = 'badge ok';
|
$('manifestStatus').className = 'badge ok';
|
||||||
renderTools();
|
renderTools();
|
||||||
return;
|
return;
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
$('executeButton').disabled = true; $('httpStatus').textContent = '실행 중'; $('httpStatus').className = 'badge';
|
$('executeButton').disabled = true; $('httpStatus').textContent = '실행 중'; $('httpStatus').className = 'badge';
|
||||||
try {
|
try {
|
||||||
let response;
|
let response;
|
||||||
if (isWasMode) {
|
if (isGatewayMode) {
|
||||||
const reqPayload = { jsonrpc: "2.0", method: "tools/call", params: { name: tool.name, arguments: payload }, id: Date.now() };
|
const reqPayload = { jsonrpc: "2.0", method: "tools/call", params: { name: tool.name, arguments: payload }, id: Date.now() };
|
||||||
response = await fetch('/mcp/api/v1/tools/call', {
|
response = await fetch('/mcp/api/v1/tools/call', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
|
|
||||||
let isOk = response.ok;
|
let isOk = response.ok;
|
||||||
let displayStatus = response.status;
|
let displayStatus = response.status;
|
||||||
if (isWasMode && isOk && data?.result?.isError) {
|
if (isGatewayMode && isOk && data?.result?.isError) {
|
||||||
isOk = false;
|
isOk = false;
|
||||||
displayStatus = "MCP ERR";
|
displayStatus = "MCP ERR";
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
$('httpStatus').textContent = `HTTP ${displayStatus}`; $('httpStatus').className = `badge ${isOk ? 'ok' : 'fail'}`;
|
$('httpStatus').textContent = `HTTP ${displayStatus}`; $('httpStatus').className = `badge ${isOk ? 'ok' : 'fail'}`;
|
||||||
$('latency').textContent = `${elapsed}ms`; $('traceId').textContent = `trace-id: ${response.headers.get('trace-id') || trace}`; $('requestId').textContent = `request-id: ${response.headers.get('request-id') || request}`;
|
$('latency').textContent = `${elapsed}ms`; $('traceId').textContent = `trace-id: ${response.headers.get('trace-id') || trace}`; $('requestId').textContent = `request-id: ${response.headers.get('request-id') || request}`;
|
||||||
let displayData = data;
|
let displayData = data;
|
||||||
if (isWasMode && data && typeof data === 'object') {
|
if (isGatewayMode && data && typeof data === 'object') {
|
||||||
if (data.result && data.result.result) {
|
if (data.result && data.result.result) {
|
||||||
displayData = data.result.result.data !== undefined ? data.result.result.data : data.result.result;
|
displayData = data.result.result.data !== undefined ? data.result.result.data : data.result.result;
|
||||||
} else if (data.error) {
|
} else if (data.error) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package io.shinhanlife.dap.lib.adapter.sender;
|
package io.shinhanlife.dap.lib.common.adapter.sender;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.shinhanlife.dap.lib.integration.mci.dto.MciRequestWrapper;
|
import io.shinhanlife.dap.lib.integration.mci.dto.MciRequestWrapper;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.config;
|
package io.shinhanlife.dap.lib.config;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.config
|
|
||||||
* @className ToolSchemaConfigurationTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|||||||
@@ -1,21 +1,7 @@
|
|||||||
package io.shinhanlife.dap.lib.util;
|
package io.shinhanlife.dap.lib.util;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.util
|
|
||||||
* @className JsonSchemaGeneratorTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
@@ -26,8 +12,6 @@ import com.networknt.schema.InputFormat;
|
|||||||
import com.networknt.schema.Schema;
|
import com.networknt.schema.Schema;
|
||||||
import com.networknt.schema.SchemaRegistry;
|
import com.networknt.schema.SchemaRegistry;
|
||||||
import com.networknt.schema.SpecificationVersion;
|
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.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -106,24 +90,19 @@ class JsonSchemaGeneratorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class ValidatedRequest {
|
private static class ValidatedRequest {
|
||||||
@McpParameter(description = "recipient phone number", required = true)
|
@McpToolParam(description = "recipient phone number", required = true)
|
||||||
@McpValidation(pattern = "^01[0-9]{8,9}$")
|
|
||||||
private String phoneNumber;
|
private String phoneNumber;
|
||||||
|
|
||||||
@McpParameter(description = "issue amount", required = true)
|
@McpToolParam(description = "issue amount", required = true)
|
||||||
@McpValidation(minimum = 1)
|
|
||||||
private Long amount;
|
private Long amount;
|
||||||
|
|
||||||
@McpParameter(description = "approval result")
|
@McpToolParam(description = "approval result")
|
||||||
@McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"})
|
|
||||||
private String approvalStatus;
|
private String approvalStatus;
|
||||||
|
|
||||||
@McpParameter(description = "page size")
|
@McpToolParam(description = "page size")
|
||||||
@McpValidation(minimum = 1, maximum = 50, defaultValue = "20")
|
|
||||||
private Integer pageSize;
|
private Integer pageSize;
|
||||||
|
|
||||||
@McpParameter(description = "reference")
|
@McpToolParam(description = "reference")
|
||||||
@McpValidation(minLength = 1, maxLength = 30)
|
|
||||||
private String reference;
|
private String reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +115,6 @@ class JsonSchemaGeneratorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class NestedChild {
|
private static class NestedChild {
|
||||||
@McpValidation(required = true, pattern = "^\\d{8}$")
|
|
||||||
private String businessDate;
|
private String businessDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package io.shinhanlife.dap.lib.util;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class ToolScaffolderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generatesManifestReadyToolAndOutputSchemaDto() throws Exception {
|
||||||
|
String moduleName = "build/scaffold-manifest-test";
|
||||||
|
|
||||||
|
ToolScaffolder.scaffold("claim search", "CLM0001", "청구 조회", "cmm", "HTTP", moduleName,
|
||||||
|
"tester", "2026.08.04", true, null);
|
||||||
|
|
||||||
|
Path root = Path.of(moduleName, "src/main/java/io/shinhanlife/dap/mcc/biz/cmm");
|
||||||
|
String useCase = Files.readString(root.resolve("usecase/ClaimSearchUseCase.java"));
|
||||||
|
String response = Files.readString(root.resolve("dto/ClaimSearchResponse.java"));
|
||||||
|
|
||||||
|
assertTrue(useCase.contains("name = \"oth.cmm.claim.search\""));
|
||||||
|
assertTrue(useCase.contains("version = \"1.0.0\""));
|
||||||
|
assertTrue(useCase.contains("timeoutMillis = 300000L"));
|
||||||
|
assertTrue(response.contains(""));
|
||||||
|
assertTrue(response.contains(""));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void usesWasModuleNameAsToolPodPrefix() throws Exception {
|
||||||
|
String moduleName = "build/dap-was-sms";
|
||||||
|
|
||||||
|
ToolScaffolder.scaffold("notification send", "SMS0001", "SMS 발송", "cmm", "HTTP", moduleName,
|
||||||
|
"tester", "2026.08.05", true, null);
|
||||||
|
|
||||||
|
Path useCasePath = Path.of(moduleName,
|
||||||
|
"src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/NotificationSendUseCase.java");
|
||||||
|
String useCase = Files.readString(useCasePath);
|
||||||
|
|
||||||
|
assertTrue(useCase.contains("name = \"sms.cmm.notification.send\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,9 @@
|
|||||||
package io.shinhanlife.dap.lib.util;
|
package io.shinhanlife.dap.lib.util;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.util
|
|
||||||
* @className ToolSchemaResolverTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpOutputSchema;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -88,43 +69,28 @@ class ToolSchemaResolverTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static class InlineSchemaTool {
|
static class InlineSchemaTool {
|
||||||
@McpFunction(
|
|
||||||
displayName = "inline",
|
|
||||||
name = "test.schema.inline",
|
|
||||||
description = "inline schema",
|
|
||||||
inputSchema = "{\"type\":\"object\",\"properties\":{\"keyword\":{\"type\":\"string\"}},\"additionalProperties\":false}")
|
|
||||||
void search(AutomaticRequest request) {
|
void search(AutomaticRequest request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class AutomaticSchemaTool {
|
static class AutomaticSchemaTool {
|
||||||
@McpFunction(displayName = "automatic", name = "test.schema.automatic", description = "automatic schema")
|
|
||||||
void search(AutomaticRequest request) {
|
void search(AutomaticRequest request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class AutomaticOutputSchemaTool {
|
static class AutomaticOutputSchemaTool {
|
||||||
@McpFunction(displayName = "automatic-output", name = "test.schema.automatic-output", description = "automatic output")
|
|
||||||
SimpleResponse search(AutomaticRequest request) {
|
SimpleResponse search(AutomaticRequest request) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@McpOutputSchema
|
|
||||||
static class SimpleResponse {
|
static class SimpleResponse {
|
||||||
@McpValidation(required = true, allowedValues = {"SUCCESS", "FAILURE"})
|
|
||||||
private String resultCode;
|
private String resultCode;
|
||||||
|
|
||||||
@McpValidation(maxLength = 200)
|
|
||||||
private String message;
|
private String message;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class OutputSchemaTool {
|
static class OutputSchemaTool {
|
||||||
@McpFunction(
|
|
||||||
displayName = "output",
|
|
||||||
name = "test.schema.output",
|
|
||||||
description = "output schema",
|
|
||||||
outputSchema = "{\"type\":\"object\",\"properties\":{\"resultCode\":{\"type\":\"string\"}},\"required\":[\"resultCode\"],\"additionalProperties\":false}")
|
|
||||||
void search(AutomaticRequest request) {
|
void search(AutomaticRequest request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.lib.validation;
|
package io.shinhanlife.dap.lib.validation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.validation
|
|
||||||
* @className McpToolNameValidatorTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
@@ -106,24 +90,7 @@ class McpToolNameValidatorTest {
|
|||||||
Files.writeString(source, """
|
Files.writeString(source, """
|
||||||
package example;
|
package example;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.validation
|
|
||||||
* @className McpToolNameValidatorTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
class %s {
|
class %s {
|
||||||
@McpFunction(displayName = "%s", name = "%s", description = "test")
|
|
||||||
void execute() { }
|
void execute() { }
|
||||||
}
|
}
|
||||||
""".formatted(className, className, toolName));
|
""".formatted(className, className, toolName));
|
||||||
|
|||||||
@@ -1,20 +1,4 @@
|
|||||||
package io.shinhanlife.dap.lib.manifest;
|
package io.shinhanlife.dap.mcc.manifest;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.manifest
|
|
||||||
* @className ToolManifestServiceTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
@@ -22,6 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.shinhanlife.dap.lib.config.McpProperties;
|
import io.shinhanlife.dap.lib.config.McpProperties;
|
||||||
|
import io.shinhanlife.dap.lib.manifest.ToolManifestItem;
|
||||||
|
import io.shinhanlife.dap.lib.manifest.ToolManifestResponse;
|
||||||
|
import io.shinhanlife.dap.lib.manifest.ToolManifestService;
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -1,20 +1,4 @@
|
|||||||
package io.shinhanlife.dap.lib.mcp;
|
package io.shinhanlife.dap.mcc.mcp;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.mcp
|
|
||||||
* @className McpRequestHeaderFilterTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
@@ -26,8 +10,9 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.modelcontextprotocol.server.McpSyncServer;
|
import io.modelcontextprotocol.server.McpSyncServer;
|
||||||
|
import io.shinhanlife.dap.lib.mcp.*;
|
||||||
import io.shinhanlife.dap.mcc.presentation.BusinessToolController;
|
import io.shinhanlife.dap.mcc.presentation.BusinessToolController;
|
||||||
import io.shinhanlife.dap.lib.mcp.LocalToolScanner;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -40,14 +25,14 @@ class McpRequestHeaderFilterTest {
|
|||||||
@Test
|
@Test
|
||||||
void capturesOptionalMcpHeadersOnlyForTheCurrentRequest() throws Exception {
|
void capturesOptionalMcpHeadersOnlyForTheCurrentRequest() throws Exception {
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/mcp");
|
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/mcp");
|
||||||
request.addHeader("X-Request-Id", "was-request-id");
|
request.addHeader("X-Request-Id", "gateway-request-id");
|
||||||
request.addHeader("trace-id", "trace-001");
|
request.addHeader("trace-id", "trace-001");
|
||||||
request.addHeader("request-id", "tool-request-001");
|
request.addHeader("request-id", "tool-request-001");
|
||||||
request.addHeader("employee-id", "encrypted-employee-id");
|
request.addHeader("employee-id", "encrypted-employee-id");
|
||||||
|
|
||||||
new McpRequestHeaderFilter().doFilter(request, new MockHttpServletResponse(), (req, res) -> {
|
new McpRequestHeaderFilter().doFilter(request, new MockHttpServletResponse(), (req, res) -> {
|
||||||
McpRequestHeaders headers = McpRequestHeaderContext.current();
|
McpRequestHeaders headers = McpRequestHeaderContext.current();
|
||||||
assertEquals("was-request-id", headers.headerRequestId());
|
assertEquals("gateway-request-id", headers.headerRequestId());
|
||||||
assertEquals("trace-001", headers.traceId());
|
assertEquals("trace-001", headers.traceId());
|
||||||
assertEquals("tool-request-001", headers.requestId());
|
assertEquals("tool-request-001", headers.requestId());
|
||||||
assertEquals("encrypted-employee-id", headers.encryptedEmployeeId());
|
assertEquals("encrypted-employee-id", headers.encryptedEmployeeId());
|
||||||
@@ -60,19 +45,19 @@ class McpRequestHeaderFilterTest {
|
|||||||
void forwardsCapturedHeadersToBusinessToolExecution() throws Exception {
|
void forwardsCapturedHeadersToBusinessToolExecution() throws Exception {
|
||||||
BusinessToolController controller = mock(BusinessToolController.class);
|
BusinessToolController controller = mock(BusinessToolController.class);
|
||||||
doReturn(ResponseEntity.ok(Map.of("result", "ok")))
|
doReturn(ResponseEntity.ok(Map.of("result", "ok")))
|
||||||
.when(controller).executeDynamicTool(eq("sampleTool"), eq("was-request-id"), eq("trace-001"),
|
.when(controller).executeDynamicTool(eq("sampleTool"), eq("gateway-request-id"), eq("trace-001"),
|
||||||
eq("tool-request-001"), eq("encrypted-employee-id"), eq(Map.of("key", "value")));
|
eq("tool-request-001"), eq("encrypted-employee-id"), eq(Map.of("key", "value")));
|
||||||
ToolPodMcpToolSynchronizer synchronizer = new ToolPodMcpToolSynchronizer(
|
ToolPodMcpToolSynchronizer synchronizer = new ToolPodMcpToolSynchronizer(
|
||||||
mock(McpSyncServer.class), mock(LocalToolScanner.class), controller, new ObjectMapper());
|
mock(McpSyncServer.class), mock(ToolRegistryHeartbeatSender.class), controller, new ObjectMapper());
|
||||||
|
|
||||||
Method invoke = ToolPodMcpToolSynchronizer.class.getDeclaredMethod(
|
Method invoke = ToolPodMcpToolSynchronizer.class.getDeclaredMethod(
|
||||||
"invoke", String.class, McpRequestHeaders.class, Map.class);
|
"invoke", String.class, McpRequestHeaders.class, Map.class);
|
||||||
invoke.setAccessible(true);
|
invoke.setAccessible(true);
|
||||||
invoke.invoke(synchronizer, "sampleTool",
|
invoke.invoke(synchronizer, "sampleTool",
|
||||||
new McpRequestHeaders("was-request-id", "trace-001", "tool-request-001", "encrypted-employee-id"),
|
new McpRequestHeaders("gateway-request-id", "trace-001", "tool-request-001", "encrypted-employee-id"),
|
||||||
Map.of("key", "value"));
|
Map.of("key", "value"));
|
||||||
|
|
||||||
verify(controller).executeDynamicTool("sampleTool", "was-request-id", "trace-001",
|
verify(controller).executeDynamicTool("sampleTool", "gateway-request-id", "trace-001",
|
||||||
"tool-request-001", "encrypted-employee-id", Map.of("key", "value"));
|
"tool-request-001", "encrypted-employee-id", Map.of("key", "value"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,10 @@
|
|||||||
package io.shinhanlife.dap.lib.mcp;
|
package io.shinhanlife.dap.mcc.mcp;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.mcp
|
|
||||||
* @className ToolMcpServerConfigurationTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
|
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
|
||||||
|
import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||||
|
|
||||||
@@ -32,7 +17,6 @@ class ToolMcpServerConfigurationTest {
|
|||||||
HttpServletStreamableServerTransportProvider transport = configuration.toolMcpTransportProvider();
|
HttpServletStreamableServerTransportProvider transport = configuration.toolMcpTransportProvider();
|
||||||
ServletRegistrationBean<HttpServletStreamableServerTransportProvider> registration = configuration.toolMcpServlet(transport);
|
ServletRegistrationBean<HttpServletStreamableServerTransportProvider> registration = configuration.toolMcpServlet(transport);
|
||||||
|
|
||||||
assertEquals("/mcp", registration.getUrlMappings().iterator().next());
|
assertThat(registration.getUrlMappings()).containsExactlyInAnyOrder("/mcp", "/mcp/message");
|
||||||
assertFalse(registration.getUrlMappings().contains("/mcp/*"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.mcc.presentation;
|
package io.shinhanlife.dap.mcc.presentation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.presentation
|
|
||||||
* @className BusinessToolControllerHeaderContractTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,10 @@
|
|||||||
package io.shinhanlife.dap.lib.validation;
|
package io.shinhanlife.dap.mcc.presentation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.lib.validation
|
|
||||||
* @className ToolArgumentSchemaValidatorTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.mcc.presentation;
|
package io.shinhanlife.dap.mcc.presentation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.presentation
|
|
||||||
* @className ToolTestConsoleResourceTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.glow.communication.annotation;
|
package io.shinhanlife.glow.communication.annotation;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.glow.communication.annotation
|
|
||||||
* @className GlowTrgmFieldContractTest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
/**
|
/**
|
||||||
* @package io.shinhanlife.dap.mcc.dto
|
* @package io.shinhanlife.dap.mcc.dto
|
||||||
* @className BalanceRequest
|
* @className BalanceRequest
|
||||||
@@ -15,13 +20,12 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class BalanceRequest {
|
public class BalanceRequest {
|
||||||
@McpParameter(description = "고객의 계좌번호 (- 제외) ", required = true)
|
@McpToolParam(description = "고객의 계좌번호 (- 제외) ", required = true)
|
||||||
@McpValidation(pattern = "\\S")
|
@Schema(pattern = "\\S", example = "1234567890")
|
||||||
private String accountNumber;
|
private String accountNumber;
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -30,11 +33,12 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class BillingProcessRequest {
|
public class BillingProcessRequest {
|
||||||
|
|
||||||
@McpParameter(description = "처리할 청구 접수 번호", required = true)
|
@McpToolParam(description = "처리할 청구 접수 번호", required = true)
|
||||||
@McpValidation(pattern = "\\S")
|
@Schema(pattern = "\\S", example = "BILL20260805")
|
||||||
private String billingId;
|
private String billingId;
|
||||||
|
|
||||||
@McpParameter(description = "심사 승인 여부 (예: APPROVE, REJECT)")
|
|
||||||
@McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"})
|
@McpToolParam(description = "승인 처리 구분 (예: APPROVE, REJECT)")
|
||||||
|
@Schema(required = true, allowableValues = {"APPROVE", "REJECT"}, example = "APPROVE")
|
||||||
private String approvalStatus;
|
private String approvalStatus;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -29,6 +33,8 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class BillingStatusRequest {
|
public class BillingStatusRequest {
|
||||||
|
|
||||||
@McpParameter(description = "조회할 청구 접수 번호", required = true)
|
@McpToolParam(description = "조회할 청구 접수 번호", required = true)
|
||||||
|
@Schema(example = "BILL20260805")
|
||||||
private String billingId;
|
private String billingId;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -29,6 +33,8 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class BondCheckRequest {
|
public class BondCheckRequest {
|
||||||
|
|
||||||
@McpParameter(description = "확인하고자 하는 디지털 증권 발행 금액", required = true)
|
@McpToolParam(description = "확인하고자 하는 디지털 증권 발행 금액", required = true)
|
||||||
private Long amount;
|
@Schema(pattern = "^[0-9]+$", example = "1000000")
|
||||||
|
private String amount;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -30,11 +33,12 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class BondIssueRequest {
|
public class BondIssueRequest {
|
||||||
|
|
||||||
@McpParameter(description = "발행할 디지털 증권 금액", required = true)
|
@McpToolParam(description = "발행할 증권 금액 (숫자 문자열)", required = true)
|
||||||
@McpValidation(minimum = 1)
|
@Schema(pattern = "^[0-9]+$", example = "1000000")
|
||||||
private Long amount;
|
private String amount;
|
||||||
|
|
||||||
@McpParameter(description = "발행 대상 계좌 번호", required = true)
|
|
||||||
@McpValidation(pattern = "\\S")
|
@McpToolParam(description = "발행 대상 계좌 번호", required = true)
|
||||||
|
@Schema(pattern = "\\S", example = "1234567890")
|
||||||
private String targetAccount;
|
private String targetAccount;
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,10 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
|
|
||||||
* @className ClaimSearchRequest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpAnyOf;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -34,40 +20,39 @@ import lombok.Setter;
|
|||||||
@Builder
|
@Builder
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@McpAnyOf({"claimNo", "contractNo"})
|
|
||||||
public class ClaimSearchRequest {
|
public class ClaimSearchRequest {
|
||||||
|
|
||||||
@McpParameter(description = "청구번호. CLM 다음 숫자 13자리 형식이다.")
|
@McpToolParam(description = "청구번호. CLM 다음 숫자 13자리 형식이다.")
|
||||||
@McpValidation(pattern = "^CLM[0-9]{13}$", examples = {"CLM2026070100123"})
|
@Schema(pattern = "^CLM[0-9]{13}$", example = "CLM2026070100123")
|
||||||
private String claimNo;
|
private String claimNo;
|
||||||
|
|
||||||
@McpParameter(description = "계약번호. 숫자 11자리 형식이다.")
|
@McpToolParam(description = "계약번호. 숫자 11자리 형식이다.")
|
||||||
@McpValidation(pattern = "^[0-9]{11}$", examples = {"10023456789"})
|
@Schema(pattern = "^[0-9]{11}$", example = "10023456789")
|
||||||
private String contractNo;
|
private String contractNo;
|
||||||
|
|
||||||
@McpParameter(description = "청구 상태 필터")
|
@McpToolParam(description = "청구 상태 필터")
|
||||||
@McpValidation(allowedValues = {
|
@Schema(example = "RECEIVED", allowableValues = {
|
||||||
"RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED",
|
"RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED",
|
||||||
"APPROVED", "PAID", "REJECTED", "WITHDRAWN"
|
"APPROVED", "PAID", "REJECTED", "WITHDRAWN"
|
||||||
})
|
})
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@McpParameter(description = "청구 유형 필터")
|
@McpToolParam(description = "청구 유형 필터")
|
||||||
@McpValidation(allowedValues = {
|
@Schema(example = "MEDICAL", allowableValues = {
|
||||||
"MEDICAL", "SURGERY", "HOSPITALIZATION",
|
"MEDICAL", "SURGERY", "HOSPITALIZATION",
|
||||||
"DIAGNOSIS", "DEATH", "DISABILITY"
|
"DIAGNOSIS", "DEATH", "DISABILITY"
|
||||||
})
|
})
|
||||||
private String claimType;
|
private String claimType;
|
||||||
|
|
||||||
@McpParameter(description = "접수일 조회 시작일(YYYY-MM-DD)")
|
@McpToolParam(description = "접수일 조회 시작일(YYYY-MM-DD)")
|
||||||
@McpValidation(format = "date", examples = {"2026-01-01"})
|
@Schema(format = "date", example = "2026-01-01")
|
||||||
private String fromDate;
|
private String fromDate;
|
||||||
|
|
||||||
@McpParameter(description = "접수일 조회 종료일(YYYY-MM-DD)")
|
@McpToolParam(description = "접수일 조회 종료일(YYYY-MM-DD)")
|
||||||
@McpValidation(format = "date", examples = {"2026-07-31"})
|
@Schema(format = "date", example = "2026-07-31")
|
||||||
private String toDate;
|
private String toDate;
|
||||||
|
|
||||||
@McpParameter(description = "반환할 최대 건수")
|
@McpToolParam(description = "반환할 최대 건수 (기본값: 20, 최대: 50)")
|
||||||
@McpValidation(minimum = 1, maximum = 50, defaultValue = "20")
|
@Schema(pattern = "^[1-9][0-9]?$|^50$", defaultValue = "20", example = "20")
|
||||||
private Integer size;
|
private String size;
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,10 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
|
|
||||||
* @className ClaimSearchResponse
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpOutputSchema;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@@ -36,46 +22,45 @@ import lombok.Setter;
|
|||||||
@Builder
|
@Builder
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@McpOutputSchema
|
|
||||||
public class ClaimSearchResponse {
|
public class ClaimSearchResponse {
|
||||||
|
|
||||||
@McpParameter(description = "Execution result code.")
|
@McpToolParam(description = "Execution result code.")
|
||||||
@McpValidation(required = true, allowedValues = {"SUCCESS", "FAILURE"})
|
@Schema(required = true, allowableValues = {"SUCCESS", "FAILURE"})
|
||||||
private String resultCode;
|
private String resultCode;
|
||||||
|
|
||||||
@McpParameter(description = "User-readable label for resultCode.")
|
@McpToolParam(description = "User-readable label for resultCode.")
|
||||||
@McpValidation(required = true, maxLength = 100)
|
@Schema(required = true, maxLength = 100)
|
||||||
private String resultLabel;
|
private String resultLabel;
|
||||||
|
|
||||||
@McpParameter(description = "Current claim processing status code.")
|
@McpToolParam(description = "Current claim processing status code.")
|
||||||
@McpValidation(required = true, allowedValues = {
|
@Schema(required = true, allowableValues = {
|
||||||
"RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED",
|
"RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED",
|
||||||
"APPROVED", "PAID", "REJECTED", "WITHDRAWN"
|
"APPROVED", "PAID", "REJECTED", "WITHDRAWN"
|
||||||
})
|
})
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@McpParameter(description = "User-readable label for status.")
|
@McpToolParam(description = "User-readable label for status.")
|
||||||
@McpValidation(required = true, maxLength = 100)
|
@Schema(required = true, maxLength = 100)
|
||||||
private String statusLabel;
|
private String statusLabel;
|
||||||
|
|
||||||
@McpParameter(description = "Approved amount. Null before review; do not interpret null as zero.")
|
@McpToolParam(description = "Approved amount. Null before review; do not interpret null as zero.")
|
||||||
@McpValidation(minimum = 0, nullable = true)
|
@Schema(minimum = "0", nullable = true)
|
||||||
private Long approvedAmount;
|
private Long approvedAmount;
|
||||||
|
|
||||||
@McpParameter(description = "Present only when status is REJECTED; otherwise null.")
|
@McpToolParam(description = "Present only when status is REJECTED; otherwise null.")
|
||||||
@McpValidation(maxLength = 200, nullable = true)
|
@Schema(maxLength = 200, nullable = true)
|
||||||
private String rejectionReason;
|
private String rejectionReason;
|
||||||
|
|
||||||
@McpParameter(description = "Claim summaries, ordered by received date descending.")
|
@McpToolParam(description = "Claim summaries, ordered by received date descending.")
|
||||||
@McpValidation(required = true)
|
@Schema(required = true)
|
||||||
private List<ClaimSummary> items;
|
private List<ClaimSummary> items;
|
||||||
|
|
||||||
@McpParameter(description = "True when additional results exist beyond this response.")
|
@McpToolParam(description = "True when additional results exist beyond this response.")
|
||||||
@McpValidation(required = true)
|
@Schema(required = true)
|
||||||
private Boolean hasMore;
|
private Boolean hasMore;
|
||||||
|
|
||||||
@McpParameter(description = "Total number of matched claims.")
|
@McpToolParam(description = "Total number of matched claims.")
|
||||||
@McpValidation(required = true, minimum = 0)
|
@Schema(required = true, nullable = true)
|
||||||
private Integer totalCount;
|
private Integer totalCount;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -85,20 +70,18 @@ public class ClaimSearchResponse {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class ClaimSummary {
|
public static class ClaimSummary {
|
||||||
|
|
||||||
@McpParameter(description = "Claim processing status code.")
|
@McpToolParam(description = "Claim processing status code.")
|
||||||
@McpValidation(required = true)
|
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@McpParameter(description = "User-readable label for status.")
|
@McpToolParam(description = "User-readable label for status.")
|
||||||
@McpValidation(required = true)
|
|
||||||
private String statusLabel;
|
private String statusLabel;
|
||||||
|
|
||||||
@McpParameter(description = "Received date in YYYY-MM-DD format.")
|
@McpToolParam(description = "Received date in YYYY-MM-DD format.")
|
||||||
@McpValidation(required = true, format = "date")
|
@Schema(required = true, format = "date")
|
||||||
private String receivedDate;
|
private String receivedDate;
|
||||||
|
|
||||||
@McpParameter(description = "Approved amount. Null before review; do not interpret null as zero.")
|
@McpToolParam(description = "Approved amount. Null before review; do not interpret null as zero.")
|
||||||
@McpValidation(minimum = 0, nullable = true)
|
@Schema(nullable = true)
|
||||||
private Long approvedAmount;
|
private Long approvedAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -29,9 +33,12 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class ContractDetailRequest {
|
public class ContractDetailRequest {
|
||||||
|
|
||||||
@McpParameter(description = "고객명", required = true)
|
@McpToolParam(description = "고객명", required = true)
|
||||||
|
@Schema(example = "홍길동")
|
||||||
private String customerName;
|
private String customerName;
|
||||||
|
|
||||||
@McpParameter(description = "조회할 계약 번호", required = true)
|
|
||||||
|
@McpToolParam(description = "조회할 계약 번호", required = true)
|
||||||
|
@Schema(example = "1234567890")
|
||||||
private String contractId;
|
private String contractId;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -29,9 +33,11 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class ContractStatusRequest {
|
public class ContractStatusRequest {
|
||||||
|
|
||||||
@McpParameter(description = "고객명", required = true)
|
@McpToolParam(description = "고객명", required = true)
|
||||||
|
@Schema(example = "홍길동")
|
||||||
private String customerName;
|
private String customerName;
|
||||||
|
|
||||||
@McpParameter(description = "조회할 계약 번호")
|
|
||||||
|
@McpToolParam(description = "조회할 계약 번호")
|
||||||
private String contractId;
|
private String contractId;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -29,9 +33,11 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CustomerDetailRequest {
|
public class CustomerDetailRequest {
|
||||||
|
|
||||||
@McpParameter(description = "고객명", required = true)
|
@McpToolParam(description = "고객명", required = true)
|
||||||
|
@Schema(example = "홍길동")
|
||||||
private String customerName;
|
private String customerName;
|
||||||
|
|
||||||
@McpParameter(description = "고객 식별 번호 (CID)")
|
|
||||||
|
@McpToolParam(description = "고객 식별 번호 (CID)")
|
||||||
private String customerId;
|
private String customerId;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -29,9 +33,11 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CustomerGradeRequest {
|
public class CustomerGradeRequest {
|
||||||
|
|
||||||
@McpParameter(description = "고객 이름 (예: 김신한)", required = true)
|
@McpToolParam(description = "고객 이름 (예: 김신한)", required = true)
|
||||||
|
@Schema(example = "홍길동")
|
||||||
private String customerName;
|
private String customerName;
|
||||||
|
|
||||||
@McpParameter(description = "고객 식별 번호 (CID)")
|
|
||||||
|
@McpToolParam(description = "고객 식별 번호 (CID)")
|
||||||
private String customerId;
|
private String customerId;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -29,6 +33,8 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class LeaveCountRequest {
|
public class LeaveCountRequest {
|
||||||
|
|
||||||
@McpParameter(description = "연차 내역을 조회할 사원 번호", required = true)
|
@McpToolParam(description = "연차 내역을 조회할 사원 번호", required = true)
|
||||||
|
@Schema(example = "EMP12345")
|
||||||
private String employeeId;
|
private String employeeId;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
|
|
||||||
* @className MciSampleStringResponse
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import io.shinhanlife.glow.GlowMciFieldInfo;
|
import io.shinhanlife.glow.GlowMciFieldInfo;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
|
|
||||||
* @className MciSampleTargetDto
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.glow.GlowMciFieldInfo;
|
import io.shinhanlife.glow.GlowMciFieldInfo;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,12 +25,15 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
public class MetaCommonCodeRequest {
|
public class MetaCommonCodeRequest {
|
||||||
@McpParameter(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false)
|
@McpToolParam(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false)
|
||||||
|
@Schema(example = "GRP_001")
|
||||||
private String groupCode;
|
private String groupCode;
|
||||||
|
|
||||||
@McpParameter(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false)
|
|
||||||
|
@McpToolParam(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false)
|
||||||
private String codeName;
|
private String codeName;
|
||||||
|
|
||||||
@McpParameter(description = "사용여부 (예: Y, N)", required = false)
|
@McpToolParam(description = "사용여부 (예: Y, N)", required = false)
|
||||||
|
@Schema(example = "Y")
|
||||||
private String useYn;
|
private String useYn;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,12 +25,16 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
public class MetaTableRequest {
|
public class MetaTableRequest {
|
||||||
@McpParameter(description = "테이블 물리명 키워드 (예: TB_CUST_BAS, TB_CONT)", required = false)
|
@McpToolParam(description = "테이블 물리명 키워드 (예: TB_CUST_BAS, TB_CONT)", required = false)
|
||||||
|
@Schema(example = "TB_USER")
|
||||||
private String tableName;
|
private String tableName;
|
||||||
|
|
||||||
@McpParameter(description = "테이블 논리명(한글) 키워드 (예: 고객기본, 계약)", required = false)
|
|
||||||
|
@McpToolParam(description = "테이블 논리명(한글) 키워드 (예: 고객기본, 계약)", required = false)
|
||||||
|
@Schema(example = "고객기본")
|
||||||
private String tableLogicalName;
|
private String tableLogicalName;
|
||||||
|
|
||||||
@McpParameter(description = "스키마/소유자명 (예: DAPADM, SHLOWN)", required = false)
|
@McpToolParam(description = "스키마/소유자명 (예: DAPADM, SHLOWN)", required = false)
|
||||||
|
@Schema(example = "DAPADM")
|
||||||
private String owner;
|
private String owner;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -20,5 +22,6 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
public class SampleStringRequest {
|
public class SampleStringRequest {
|
||||||
|
@Schema(example = "test query")
|
||||||
private String query;
|
private String query;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@@ -31,5 +33,6 @@ public class TemplateDownloadRequest {
|
|||||||
/**
|
/**
|
||||||
* 다운로드할 템플릿의 종류 ID (예: CUSTOMER_EXCEL, PRODUCT_PDF 등)
|
* 다운로드할 템플릿의 종류 ID (예: CUSTOMER_EXCEL, PRODUCT_PDF 등)
|
||||||
*/
|
*/
|
||||||
|
@Schema(example = "TPL_001")
|
||||||
private String templateId;
|
private String templateId;
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -30,11 +33,12 @@ import lombok.Setter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class VacationRegisterRequest {
|
public class VacationRegisterRequest {
|
||||||
|
|
||||||
@McpParameter(description = "연차를 등록할 사원 번호", required = true)
|
@McpToolParam(description = "연차를 등록할 사원 번호", required = true)
|
||||||
@McpValidation(pattern = "\\S")
|
@Schema(pattern = "\\S", example = "EMP12345")
|
||||||
private String employeeId;
|
private String employeeId;
|
||||||
|
|
||||||
@McpParameter(description = "휴가 일자 (YYYY-MM-DD 형식)", required = true)
|
|
||||||
@McpValidation(pattern = "^\\d{4}-\\d{2}-\\d{2}$")
|
@McpToolParam(description = "휴가 일자 (YYYY-MM-DD 형식)", required = true)
|
||||||
|
@Schema(pattern = "^\\d{4}-\\d{2}-\\d{2}$", example = "2026-08-05")
|
||||||
private String date;
|
private String date;
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,12 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
|
||||||
* @className BalanceUseCase
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
@McpTool(routingType = "MCI", categoryKey = "cmm")
|
|
||||||
public interface BalanceUseCase {
|
public interface BalanceUseCase {
|
||||||
@McpFunction(register = false, displayName = "balance 툴", name = "oth.cmm.balance.inquiry",
|
@McpTool(name = "oth.cmm.balance.inquiry", title = "잔고 조회 툴", description = "고객의 계좌 잔액을 조회합니다.")
|
||||||
description = "고객의 계좌 잔액을 조회합니다.",
|
@ToolHint(register = false, mappingId = "ACC_001")
|
||||||
prompt = "고객 계좌 잔액을 조회해줘.",
|
|
||||||
mappingId = "ACC_001"
|
|
||||||
)
|
|
||||||
Object execute(BalanceRequest req);
|
Object execute(BalanceRequest req);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,13 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
/**
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
* @className BillingProcessUseCase
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
@McpTool(
|
|
||||||
routingType = "MCI",
|
|
||||||
categoryKey = "cmm"
|
|
||||||
)
|
|
||||||
public interface BillingProcessUseCase {
|
public interface BillingProcessUseCase {
|
||||||
Object getStatus(BillingStatusRequest req);
|
Object getStatus(BillingStatusRequest req);
|
||||||
|
|
||||||
@McpFunction(register = false, displayName = "process 툴", name = "oth.cmm.billing.process", description = "청구 처리", prompt = "현재 접수된 청구건에 대한 심사 처리를 진행해.", mappingId = "BILL_002")
|
@McpTool(name = "oth.cmm.billing.process", title = "청구 프로세스 툴", description = "청구 처리")
|
||||||
|
@ToolHint(register = false, mappingId = "BILL_002")
|
||||||
Object processBilling(BillingProcessRequest data);
|
Object processBilling(BillingProcessRequest data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
|
||||||
* @className BondIssueUseCase
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
@McpTool(
|
|
||||||
routingType = "EAI",
|
|
||||||
categoryKey = "cmm"
|
|
||||||
)
|
|
||||||
public interface BondIssueUseCase {
|
public interface BondIssueUseCase {
|
||||||
Object check(BondCheckRequest req);
|
Object check(BondCheckRequest req);
|
||||||
|
|
||||||
@McpFunction(displayName = "issue 툴", name = "oth.cmm.bond.issue", register = false, description = "증권 발행 테스트1", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002")
|
@McpTool(name = "oth.cmm.bond.issue", title = "채권 발행 툴", description = "증권 발행 테스트1")
|
||||||
|
@ToolHint(register = false, mappingId = "BOND_002")
|
||||||
Object issue(BondIssueRequest data);
|
Object issue(BondIssueRequest data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,16 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
/**
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
* @className ClaimSearchSchemaSampleUseCase
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse;
|
||||||
|
|
||||||
@McpTool(
|
|
||||||
routingType = "DIRECT",
|
|
||||||
categoryKey = "cmm"
|
|
||||||
)
|
|
||||||
public interface ClaimSearchSchemaSampleUseCase {
|
public interface ClaimSearchSchemaSampleUseCase {
|
||||||
|
|
||||||
@McpFunction(
|
@McpTool(name = "oth.cmm.claim.search", title = "청구 조회 스키마 샘플", description = "Claim search Tool sample using input and output JSON Schema resources.", annotations = @McpTool.McpAnnotations(readOnlyHint = true, idempotentHint = true))
|
||||||
register = false,
|
@ToolHint(register = false,
|
||||||
displayName = "Claim search JSON Schema sample",
|
inputSchemaResource = "classpath:mcp/schema/claim-search-resource-input-schema.json",
|
||||||
name = "oth.cmm.claim.search",
|
outputSchemaResource = "classpath:mcp/schema/claim-search-resource-output-schema.json")
|
||||||
description = "Claim search Tool sample using input and output JSON Schema resources.",
|
|
||||||
prompt = "Search an insurance claim by claim number or contract number.",
|
|
||||||
inputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json",
|
|
||||||
outputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json",
|
|
||||||
readOnlyHint = true,
|
|
||||||
idempotentHint = true
|
|
||||||
)
|
|
||||||
ClaimSearchResponse search(ClaimSearchRequest request);
|
ClaimSearchResponse search(ClaimSearchRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,18 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
|
||||||
* @className CommonUtilityUseCase
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
@McpTool(
|
|
||||||
routingType = "HTTP",
|
|
||||||
categoryKey = "cmm"
|
|
||||||
)
|
|
||||||
public interface CommonUtilityUseCase {
|
public interface CommonUtilityUseCase {
|
||||||
Object registerVacation(VacationRegisterRequest req);
|
Object registerVacation(VacationRegisterRequest req);
|
||||||
|
|
||||||
@McpFunction(register = false, displayName = "get_leave_count 툴", name = "oth.cmm.leave.count", description = "연차 갯수 조회", prompt = "현재 사용 가능한 남은 연차 일수를 알려줘.", mappingId = "HR_VAC_02")
|
@McpTool(name = "oth.cmm.leave.count", title = "공통 유틸리티 툴", description = "연차 갯수 조회")
|
||||||
|
@ToolHint(register = false, mappingId = "HR_VAC_02")
|
||||||
Object getLeaveCount(LeaveCountRequest data);
|
Object getLeaveCount(LeaveCountRequest data);
|
||||||
|
|
||||||
@McpFunction(register = false, displayName = "secret_tool 툴", name = "oth.cmm.secret.execute", description = "비공개 툴 테스트", prompt = "숨겨진 툴 강제 호출", mappingId = "SECRET_001", visible = false)
|
@McpTool(name = "oth.cmm.secret.execute", title = "공통 유틸리티 툴", description = "비공개 툴 테스트")
|
||||||
|
@ToolHint(register = false, mappingId = "SECRET_001")
|
||||||
Object secretTool(LeaveCountRequest data);
|
Object secretTool(LeaveCountRequest data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
|
||||||
* @className ContractInquiryUseCase
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
@McpTool(
|
|
||||||
routingType = "HTTP",
|
|
||||||
categoryKey = "cmm"
|
|
||||||
)
|
|
||||||
public interface ContractInquiryUseCase {
|
public interface ContractInquiryUseCase {
|
||||||
Object getStatus(ContractStatusRequest req);
|
Object getStatus(ContractStatusRequest req);
|
||||||
|
|
||||||
@McpFunction(register = false, displayName = "contract_detail 툴", name = "oth.cmm.contract.detail", description = "계약상세 조회", prompt = "김신한 고객의 계약 상세 내역을 알려줘.", mappingId = "CNTR_002")
|
@McpTool(name = "oth.cmm.contract.detail", title = "계약 상세조회 툴", description = "계약상세 조회")
|
||||||
|
@ToolHint(register = false, mappingId = "CNTR_002")
|
||||||
Object getDetail(ContractDetailRequest data);
|
Object getDetail(ContractDetailRequest data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
|
||||||
* @className CustomerInfoUseCase
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
@McpTool(
|
|
||||||
routingType = "TCP",
|
|
||||||
categoryKey = "cmm"
|
|
||||||
)
|
|
||||||
public interface CustomerInfoUseCase {
|
public interface CustomerInfoUseCase {
|
||||||
Object getGrade(CustomerGradeRequest req);
|
Object getGrade(CustomerGradeRequest req);
|
||||||
|
|
||||||
@McpFunction(register = false, displayName = "detail 툴", name = "oth.cmm.customer.detail", description = "고객상세 정보 조회", prompt = "이 고객의 상세 기본정보(주소, 연락처 등)를 알려줘.", mappingId = "CRM_002")
|
@McpTool(name = "oth.cmm.customer.detail", title = "고객 상세조회 툴", description = "고객상세 정보 조회")
|
||||||
|
@ToolHint(register = false, mappingId = "CRM_002")
|
||||||
Object getDetail(CustomerDetailRequest data);
|
Object getDetail(CustomerDetailRequest data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,20 +19,8 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
|
|||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@McpTool(
|
|
||||||
routingType = "MCI",
|
|
||||||
categoryKey = "cmm"
|
|
||||||
)
|
|
||||||
public interface MetaCommonCodeUseCase {
|
public interface MetaCommonCodeUseCase {
|
||||||
@McpFunction(
|
@McpTool(name = "oth.cmm.common-code.lookup", title = "메타 공통코드 조회 툴", description = "메타 통합코드 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true))
|
||||||
displayName = "메타 통합코드 조회 툴",
|
@ToolHint(register = false, requiresApproval = false, mappingId = "CLCNNB00001")
|
||||||
name = "oth.cmm.common-code.lookup",
|
|
||||||
description = "메타 통합코드 목록을 조회해줘",
|
|
||||||
prompt = "메타 통합코드 목록을 조회해줘",
|
|
||||||
mappingId = "CLCNNB00001",
|
|
||||||
register = false,
|
|
||||||
requiresApproval = false,
|
|
||||||
openWorldHint = true
|
|
||||||
)
|
|
||||||
Object execute(MetaCommonCodeRequest req);
|
Object execute(MetaCommonCodeRequest req);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,20 +19,8 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
|
|||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@McpTool(
|
|
||||||
routingType = "MCI",
|
|
||||||
categoryKey = "cmm"
|
|
||||||
)
|
|
||||||
public interface MetaTableUseCase {
|
public interface MetaTableUseCase {
|
||||||
@McpFunction(
|
@McpTool(name = "oth.cmm.meta.table", title = "메타 테이블 조회 툴", description = "메타 테이블 정보 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true))
|
||||||
displayName = "메타 테이블 조회 툴",
|
@ToolHint(register = false, requiresApproval = false, mappingId = "CLCNNB00001")
|
||||||
name = "oth.cmm.meta.table",
|
|
||||||
description = "메타 테이블 정보 목록을 조회해줘",
|
|
||||||
prompt = "메타 테이블 정보 목록을 조회해줘",
|
|
||||||
mappingId = "CLCNNB00001",
|
|
||||||
register = false,
|
|
||||||
requiresApproval = false,
|
|
||||||
openWorldHint = true
|
|
||||||
)
|
|
||||||
Object execute(MetaTableRequest req);
|
Object execute(MetaTableRequest req);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
|
|
||||||
* @className TemplateUtilityUseCase
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@McpTool(
|
|
||||||
routingType = "HTTP",
|
|
||||||
categoryKey = "cmm"
|
|
||||||
)
|
|
||||||
public interface TemplateUtilityUseCase {
|
public interface TemplateUtilityUseCase {
|
||||||
@McpFunction(
|
@McpTool(name = "oth.cmm.template.url", title = "템플릿 유틸리티 툴", description = "요청한 템플릿(엑셀, 워드 등) 파일(양식)을 다운로드 받을 수 있는 시스템 URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.")
|
||||||
displayName = "템플릿 유틸리티",
|
@ToolHint
|
||||||
name = "oth.cmm.template.url",
|
|
||||||
description = "요청한 템플릿(엑셀, 워드 등) 파일(양식)을 다운로드 받을 수 있는 시스템 URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.",
|
|
||||||
prompt = "요청하신 템플릿 양식 파일 다운로드 URL은 다음과 같습니다. 클릭하여 다운로드하세요:"
|
|
||||||
)
|
|
||||||
Map<String, Object> getTemplateFileUrl(TemplateDownloadRequest req);
|
Map<String, Object> getTemplateFileUrl(TemplateDownloadRequest req);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl
|
|
||||||
* @className ClaimSearchSchemaSampleUseCaseImpl
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.usecase.ClaimSearchSchemaSampleUseCase;
|
import io.shinhanlife.dap.mcc.biz.cmm.usecase.ClaimSearchSchemaSampleUseCase;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.oth.converter;
|
package io.shinhanlife.dap.mcc.biz.oth.converter;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.oth.converter
|
|
||||||
* @className Onnba3011Converter
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request;
|
import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request;
|
||||||
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_I;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.oth.dto;
|
package io.shinhanlife.dap.mcc.biz.oth.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.oth.dto
|
|
||||||
* @className Onnba3011Request
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|||||||
@@ -1,26 +1,11 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.oth.usecase;
|
package io.shinhanlife.dap.mcc.biz.oth.usecase;
|
||||||
|
|
||||||
/**
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
* @package io.shinhanlife.dap.mcc.biz.oth.usecase
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
* @className Onnba3011UseCase
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpTool;
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpFunction;
|
|
||||||
import io.shinhanlife.dap.mcc.biz.oth.dto.*;
|
import io.shinhanlife.dap.mcc.biz.oth.dto.*;
|
||||||
|
|
||||||
@McpTool(routingType = "MCI", categoryKey = "oth")
|
|
||||||
public interface Onnba3011UseCase {
|
public interface Onnba3011UseCase {
|
||||||
|
@McpTool(name = "oth.sol.request.detail", title = "보종By가입설계한도계산조회 툴")
|
||||||
|
@ToolHint(register = false)
|
||||||
Object callOnnba3011(Onnba3011Request req);
|
Object callOnnba3011(Onnba3011Request req);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ public class Onnba3011UseCaseImpl implements Onnba3011UseCase {
|
|||||||
private final Onnba3011Converter onnba3011Converter;
|
private final Onnba3011Converter onnba3011Converter;
|
||||||
/**
|
/**
|
||||||
* AI Agent가 호출하게 될 메서드입니다.
|
* AI Agent가 호출하게 될 메서드입니다.
|
||||||
* @McpFunction 어노테이션 하나로 AI 도구로 자동 노출 및 라우팅됩니다.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object callOnnba3011(Onnba3011Request req) {
|
public Object callOnnba3011(Onnba3011Request req) {
|
||||||
log.info("[MCI Tool] 보종By가입설계한도계산조회 요청 수신.");
|
log.info("[MCI Tool] 보종By가입설계한도계산조회 요청 수신.");
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.smp.dto
|
|
||||||
* @className DailyQuoteRequest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
public record DailyQuoteRequest(String category) {}
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class DailyQuoteRequest {
|
||||||
|
|
||||||
|
@McpToolParam(description = "카테고리 (예: 속담 등)", required = false)
|
||||||
|
@Schema(example = "속담")
|
||||||
|
private String category;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,3 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.smp.dto
|
|
||||||
* @className DailyQuoteResponse
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
public record DailyQuoteResponse(String quote, String author) {}
|
public record DailyQuoteResponse(String quote, String author) {}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.smp.dto
|
|
||||||
* @className ExchangeRateRequest
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
public record ExchangeRateRequest(String currencyCode) {}
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class ExchangeRateRequest {
|
||||||
|
@McpToolParam(description = "환율 코드 (예: USD 등)", required = false)
|
||||||
|
@Schema(example = "USD")
|
||||||
|
private String currencyCode;
|
||||||
|
}
|
||||||
@@ -1,19 +1,3 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
package io.shinhanlife.dap.mcc.biz.smp.dto;
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcc.biz.smp.dto
|
|
||||||
* @className ExchangeRateResponse
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
public record ExchangeRateResponse(String baseCurrency, String targetCurrency, double rate) {}
|
public record ExchangeRateResponse(String baseCurrency, String targetCurrency, double rate) {}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user