diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingController.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingController.java index 458ed107..975a7d79 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingController.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingController.java @@ -64,8 +64,10 @@ public class ScaffoldingController { if (date == null || date.trim().isEmpty()) date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true")); String clientSystemCode = req.get("clientSystemCode"); + String inputSchemaResource = req.get("inputSchemaResource"); + String outputSchemaResource = req.get("outputSchemaResource"); - return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date, register, clientSystemCode); + return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date, register, clientSystemCode, inputSchemaResource, outputSchemaResource); } catch (Exception e) { return "오류 발생: " + e.getMessage(); } diff --git a/dap-gateway/src/main/resources/static/admin/scaffold.html b/dap-gateway/src/main/resources/static/admin/scaffold.html index cb61f1b1..1bd6cd9f 100644 --- a/dap-gateway/src/main/resources/static/admin/scaffold.html +++ b/dap-gateway/src/main/resources/static/admin/scaffold.html @@ -484,6 +484,17 @@ +
+
+ + +
+
+ + +
+
+
diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpAnyOf.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpAnyOf.java deleted file mode 100644 index a781ba3a..00000000 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpAnyOf.java +++ /dev/null @@ -1,19 +0,0 @@ -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; - -/** - * MCP 스키마 생성 시 anyOf (해당 필드들 중 최소 1개 이상 필수) 제약을 부여합니다. - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface McpAnyOf { - /** - * anyOf 제약에 포함될 필드명 목록 - * 예: @McpAnyOf({"claimNo", "contractNo"}) - */ - String[] value(); -} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpFunction.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpFunction.java deleted file mode 100644 index aa0d6aeb..00000000 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpFunction.java +++ /dev/null @@ -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 - *
- * ---------- 개정이력 ----------
- * 수정일      수정자    수정내용
- * ---------- -------- ---------------------------
- * 2026.09.01  0986406    최초생성
- * 
- * 
- */ -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) -} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java deleted file mode 100644 index cfb37dca..00000000 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.shinhanlife.dap.lib.annotation; - -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 { -} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpParameter.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpParameter.java deleted file mode 100644 index 0497a02a..00000000 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpParameter.java +++ /dev/null @@ -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 - *
- * ---------- 개정이력 ----------
- * 수정일      수정자    수정내용
- * ---------- -------- ---------------------------
- * 2026.09.01  0986406    최초생성
- * 
- * 
- */ -import java.lang.annotation.*; - -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface McpParameter { - String description(); - boolean required() default false; -} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpTool.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpTool.java deleted file mode 100644 index 34017786..00000000 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpTool.java +++ /dev/null @@ -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 - *
- * ---------- 개정이력 ----------
- * 수정일      수정자    수정내용
- * ---------- -------- ---------------------------
- * 2026.09.01  0986406    최초생성
- * 
- * 
- */ -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"; -} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpValidation.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpValidation.java deleted file mode 100644 index 4353edf6..00000000 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpValidation.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.shinhanlife.dap.lib.annotation; - -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 - *
- * ---------- revision history ----------
- * date       author    description
- * ---------- --------- ---------------------------
- * 2026.07.27 0986406    initial creation
- * 
- */ -@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 {}; -} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/ToolHint.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/ToolHint.java new file mode 100644 index 00000000..a0586fb1 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/ToolHint.java @@ -0,0 +1,32 @@ +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 + * @className ToolHint + * @description 비즈니스 로직(Tool)과 시스템 제어 메타데이터 분리 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ToolHint { + boolean register() default false; + boolean requiresApproval() default false; + String categoryKey() default "com"; + String mappingId() default ""; + String inputSchemaResource() default ""; + String outputSchemaResource() default ""; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java index a7aef5eb..7408c57d 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java @@ -15,7 +15,6 @@ package io.shinhanlife.dap.lib.aop; * * */ -import io.shinhanlife.dap.lib.annotation.McpFunction; import io.shinhanlife.dap.lib.config.McpProperties; import java.lang.reflect.Method; import lombok.RequiredArgsConstructor; @@ -24,6 +23,7 @@ import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.ai.mcp.annotation.McpTool; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; @@ -35,12 +35,12 @@ public class ToolSlaMonitoringAspect { private final McpProperties mcpProperties; - // @McpFunction 어노테이션이 붙은 모든 비즈니스 툴 메서드 실행을 가로챕니다. - @Around("@annotation(McpFunction)") + // @McpTool 어노테이션이 붙은 모든 비즈니스 툴 메서드 실행을 가로챕니다. + @Around("@annotation(org.springframework.ai.mcp.annotation.McpTool)") public Object monitorToolSla(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); - McpFunction functionAnnotation = method.getAnnotation(McpFunction.class); + McpTool functionAnnotation = method.getAnnotation(McpTool.class); // 네임스페이스 자동 주입 로직을 반영하여 최종 툴 이름을 산출합니다. String baseName = functionAnnotation.name(); diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java index fc6db1f1..90afd71b 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java @@ -5,11 +5,21 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; /** Exposes every Tool Pod through the MCP Streamable HTTP transport. */ +@Slf4j @Configuration public class ToolMcpServerConfiguration { + @PostConstruct + public void init() { + log.warn("================================================="); + log.warn("ToolMcpServerConfiguration IS LOADED BY SPRING!!!"); + log.warn("================================================="); + } + @Bean @Primary public HttpServletStreamableServerTransportProvider toolMcpTransportProvider() { @@ -21,6 +31,8 @@ public class ToolMcpServerConfiguration { @Bean public ServletRegistrationBean toolMcpServlet( 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"); } } diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java index 1a9a4798..82965598 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java @@ -16,8 +16,8 @@ package io.shinhanlife.dap.lib.mcp; * */ import com.fasterxml.jackson.databind.ObjectMapper; -import io.shinhanlife.dap.lib.annotation.McpFunction; -import io.shinhanlife.dap.lib.annotation.McpTool; +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; @@ -82,60 +82,64 @@ public class ToolRegistryHeartbeatSender { 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); + McpTool functionAnnotation = AnnotationUtils.findAnnotation(method, McpTool.class); + ToolHint hintAnnotation = AnnotationUtils.findAnnotation(method, ToolHint.class); - if (functionAnnotation != null && toolAnnotation != null) { - String baseName = functionAnnotation.displayName(); + 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 = functionAnnotation.register(); + 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()); - meta.setDisplayName(baseName); + String displayName = functionAnnotation.title().isEmpty() ? functionAnnotation.name() : functionAnnotation.title(); + meta.setDisplayName(displayName); meta.setName(subToolName); - meta.setSemver(functionAnnotation.version()); - meta.setTimeoutMillis(functionAnnotation.timeoutMillis()); - meta.setEnabled(functionAnnotation.enabled()); + meta.setSemver("1.0.0"); + meta.setTimeoutMillis(5000L); + meta.setEnabled(true); meta.setDescription(functionAnnotation.description()); - meta.setCategoryKey(toolAnnotation.categoryKey()); - meta.setIntegrationType(toolAnnotation.routingType()); - meta.setMciServiceId(functionAnnotation.mappingId()); + meta.setCategoryKey(hintAnnotation == null || hintAnnotation.categoryKey().isBlank() + ? "common" : hintAnnotation.categoryKey()); + meta.setIntegrationType("REST"); + meta.setMciServiceId(hintAnnotation != null ? hintAnnotation.mappingId() : ""); meta.setPodUrl(podUrl); meta.setEndpoint(podUrl.replaceAll("/+$", "") + "/mcp/" + subToolName); - boolean isVisible = functionAnnotation.visible(); - meta.setVisible(isVisible); + meta.setVisible(true); meta.setIsRegistered(isRegister); - meta.setRequiresApproval(functionAnnotation.requiresApproval()); - meta.setReadOnlyHint(functionAnnotation.readOnlyHint()); - meta.setDestructiveHint(functionAnnotation.destructiveHint()); - meta.setIdempotentHint(functionAnnotation.idempotentHint()); - meta.setOpenWorldHint(functionAnnotation.openWorldHint()); + 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 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 finalSchema = toolSchemaResolver.resolve(functionAnnotation, paramType); + // TODO: ToolSchemaResolver may need to be updated to take McpTool instead of McpFunction + Map finalSchema = toolSchemaResolver.resolve(functionAnnotation, hintAnnotation, paramType); meta.setParametersSchema(finalSchema); } catch (Exception e) { log.error("Failed to generate schema for {}", subToolName, e); diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java index d95dae6e..1e493804 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java @@ -2,9 +2,8 @@ package io.shinhanlife.dap.lib.util; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; -import io.shinhanlife.dap.lib.annotation.McpAnyOf; +import org.springframework.ai.mcp.annotation.McpToolParam; +import io.swagger.v3.oas.annotations.media.Schema; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -56,7 +55,7 @@ public class JsonSchemaGenerator { // 1. 타입 매핑 // 2. 어노테이션 기반 설명 추출 - McpParameter paramAnnotation = field.getAnnotation(McpParameter.class); + McpToolParam paramAnnotation = field.getAnnotation(McpToolParam.class); JsonPropertyDescription descAnnotation = field.getAnnotation(JsonPropertyDescription.class); if (paramAnnotation != null && !paramAnnotation.description().isEmpty()) { fieldSchema.put("description", paramAnnotation.description()); @@ -72,46 +71,54 @@ public class JsonSchemaGenerator { requiredList.add(field.getName()); } - McpValidation validation = field.getAnnotation(McpValidation.class); - if (validation != null && validation.required() && !requiredList.contains(field.getName())) { - requiredList.add(field.getName()); + Schema schemaAnnotation = field.getAnnotation(Schema.class); + if (schemaAnnotation != null) { + 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 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 nonNullSchema = new HashMap<>(fieldSchema); - fieldSchema = new HashMap<>(); - fieldSchema.put("anyOf", List.of( - nonNullSchema, - Map.of("type", "null") - )); - } - properties.put(field.getName(), fieldSchema); } @@ -120,15 +127,7 @@ public class JsonSchemaGenerator { schema.put("required", requiredList); } - McpAnyOf anyOfAnnotation = clazz.getAnnotation(McpAnyOf.class); - if (anyOfAnnotation != null && anyOfAnnotation.value().length > 0) { - List> anyOfList = new ArrayList<>(); - for (String fieldName : anyOfAnnotation.value()) { - anyOfList.add(Map.of("required", List.of(fieldName))); - - } - schema.put("anyOf", anyOfList); - } + // anyOf removed visiting.remove(clazz); return schema; diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java index d2f3721a..da0deba8 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java @@ -1,5 +1,7 @@ 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; @@ -60,7 +62,7 @@ public class ToolScaffolder { if (moduleName.trim().isEmpty()) { moduleName = "dap-was-oth"; } - + String defaultAuthor = System.getProperty("user.name"); String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); @@ -69,7 +71,13 @@ public class ToolScaffolder { String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): "); if (createDate.trim().isEmpty()) createDate = defaultDate; - String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true, null); + String useSchemaResourceStr = getOrAsk(args, 8, scanner, "9. input/output JSON Schema 파일 자동 생성 여부 (y/N): "); + boolean useSchemaResource = "y".equalsIgnoreCase(useSchemaResourceStr.trim()); + + String inputSchemaResource = useSchemaResource ? "classpath:mcp/schema/" + toKebabCase(baseName) + "-resource-input-schema.json" : null; + String outputSchemaResource = useSchemaResource ? "classpath:mcp/schema/" + toKebabCase(baseName) + "-resource-output-schema.json" : null; + + String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true, null, inputSchemaResource, outputSchemaResource); System.out.println(result); } @@ -82,6 +90,10 @@ public class ToolScaffolder { } 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, null, null); + } + + public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource) throws IOException { baseName = toPascalCase(baseName); String envSourceDir = System.getenv("AXHUB_SOURCE_DIR"); Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get("."); @@ -93,6 +105,15 @@ public class ToolScaffolder { 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 일 때만 생성) + boolean useSchemaResource = (inputSchemaResource != null && !inputSchemaResource.trim().isEmpty()) || (outputSchemaResource != null && !outputSchemaResource.trim().isEmpty()); + 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(); @@ -130,8 +151,6 @@ public class ToolScaffolder { package %s.dto; import com.fasterxml.jackson.annotation.JsonInclude; - import io.shinhanlife.dap.lib.annotation.McpParameter; - import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; /** @@ -151,12 +170,11 @@ public class ToolScaffolder { @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class %sRequest { - @McpParameter(description = "수신자 전화번호", required = true) - @McpValidation(pattern = "^01(?:0|1|[6-9])-(?:\\\\d{3}|\\\\d{4})-\\\\d{4}$", examples = {"010-1234-5678"}) + @McpToolParam(description = "수신자 전화번호", required = true) + -(?:\\\\d{3}|\\\\d{4})-\\\\d{4}$", examples = {"010-1234-5678"}) private String phoneNumber; - @McpParameter(description = "전송할 메시지 내용", required = true) - @McpValidation(defaultValue = "안녕하세요.") + @McpToolParam(description = "전송할 메시지 내용", required = true) private String message; } """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); @@ -167,9 +185,7 @@ public class ToolScaffolder { package %s.dto; import com.fasterxml.jackson.annotation.JsonInclude; - import io.shinhanlife.dap.lib.annotation.McpOutputSchema; - import io.shinhanlife.dap.lib.annotation.McpValidation; - import lombok.Data; + import lombok.Data; /** * @package %s.dto @@ -186,13 +202,10 @@ public class ToolScaffolder { * */ @Data - @McpOutputSchema @JsonInclude(JsonInclude.Include.NON_NULL) public class %sResponse { - @McpValidation(required = true) private String resultCode; - @McpValidation(maxLength = 200, nullable = true) private String resultMessage; // TODO: Add response fields here. Do not include PII in the Tool response. @@ -202,42 +215,54 @@ public class ToolScaffolder { 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 io.shinhanlife.dap.lib.annotation.McpFunction; - import io.shinhanlife.dap.lib.annotation.McpTool; + import org.springframework.ai.mcp.annotation.McpTool; + import io.shinhanlife.dap.lib.annotation.ToolHint; import %s.dto.%sRequest; + import %s.dto.%sResponse; - @McpTool( - routingType = "%s", - categoryKey = "%s" - ) + /** + * @package %s.usecase + * @className %sUseCase + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+             * ---------- 개정이력 ----------
+             * 수정일      수정자    수정내용
+             * ---------- -------- ---------------------------
+             * %s  %s    최초생성
+             *
+             * 
+ */ public interface %sUseCase { - @McpFunction( - displayName = "%s 툴", - name = "%s", - description = "%s", - prompt = "%s", - mappingId = "%s", - register = %s, - requiresApproval = false, - openWorldHint = true, - version = "1.0.0", - timeoutMillis = 300000L, - enabled = true - ) - Object execute(%sRequest req); + + @McpTool(name = "%s", title = "%s", description = "%s") + %s + %sResponse execute(%sRequest req); } """.formatted( - bizPackage, + bizPackage, bizPackage, baseName, - routingType, group.toLowerCase(), + bizPackage, baseName, + bizPackage, baseName, author, createDate, createDate, author, baseName, - baseName, toolName, description, description + " ?줘.", interfaceId, register, - baseName + toolName, description, description, + toolHintLine, + baseName, baseName ); - + Files.writeString(usecaseDir.resolve(baseName + "UseCase.java"), serviceInterfaceContent); String serviceImplContent; @@ -667,11 +692,58 @@ public class ToolScaffolder { 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-") diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java index 5ea54dfb..ebc4ed13 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java @@ -2,11 +2,13 @@ package io.shinhanlife.dap.lib.util; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.shinhanlife.dap.lib.annotation.McpFunction; -import io.shinhanlife.dap.lib.annotation.McpOutputSchema;import java.io.InputStream; +// removed McpOutputSchema +import java.io.InputStream; import java.util.Map; 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. */ public class ToolSchemaResolver { @@ -16,13 +18,9 @@ public class ToolSchemaResolver { this.objectMapper = objectMapper; } - public Map resolve(McpFunction function, Class requestType) { - if (function != null && !function.inputSchemaResource().isBlank()) { - return loadResource(function.inputSchemaResource()); - } - if (function != null && !function.inputSchema().isBlank() - && !"{}".equals(function.inputSchema().trim())) { - return parse(function.inputSchema(), "McpFunction.inputSchema"); + public Map resolve(org.springframework.ai.mcp.annotation.McpTool function, ToolHint hint, Class requestType) { + if (hint != null && !hint.inputSchemaResource().isBlank()) { + return loadResource(hint.inputSchemaResource()); } return JsonSchemaGenerator.generateSchema(requestType); } @@ -30,25 +28,36 @@ public class ToolSchemaResolver { /** * Resolves an explicitly declared response schema. * Response schemas are opt-in so existing tools keep their current response behavior. + * ToolHint.outputSchemaResource()가 있으면 classpath JSON 파일에서 로드하고, + * 없으면 responseType DTO를 분석하여 자동 생성합니다. */ - public Map resolveOutput(McpFunction function, Class responseType) { - if (function != null && !function.outputSchemaResource().isBlank()) { - return loadResource(function.outputSchemaResource()); + public Map resolveOutput(org.springframework.ai.mcp.annotation.McpTool function, Class responseType, ToolHint hint) { + if (hint != null && !hint.outputSchemaResource().isBlank()) { + return loadResource(hint.outputSchemaResource()); } - if (function != null && !function.outputSchema().isBlank() - && !"{}".equals(function.outputSchema().trim())) { - return parse(function.outputSchema(), "McpFunction.outputSchema"); + return resolveOutput(function, responseType); + } + + /** + * Resolves an explicitly declared response schema. + * Response schemas are opt-in so existing tools keep their current response behavior. + */ + public Map 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 Map.of(); + return JsonSchemaGenerator.generateSchema(responseType); } /** * Retained for callers that use only explicit output schemas. */ - public Map resolveOutput(McpFunction function) { + public Map resolveOutput(org.springframework.ai.mcp.annotation.McpTool function) { return resolveOutput(function, null); } diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java index b1a0796f..f0384524 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java @@ -1,123 +1,140 @@ package io.shinhanlife.dap.lib.util; - -/** - * @package io.shinhanlife.dap.lib.util - * @className ToolSourceUpdater - * @description AX HUB 시스템 처리 클래스 - * @author 0986406 - * @create 2026.09.01 - *
- * ---------- 개정이력 ----------
- * 수정일      수정자    수정내용
- * ---------- -------- ---------------------------
- * 2026.09.01  0986406    최초생성
- * 
- * 
- */ 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; +import java.util.Comparator; -public class ToolSourceUpdater { +/** Updates MCP SDK and project-owned metadata in a generated tool source file. */ +public final 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 javaFiles; - try (Stream 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()); - } + private ToolSourceUpdater() { + } - 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; - } - } + public static void updateToolSource(String toolName, String categoryKey, String description, + boolean register, Boolean requiresApproval) throws IOException { + String configuredSourceDirectory = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDirectory = configuredSourceDirectory == null || configuredSourceDirectory.isBlank() + ? Paths.get(".") : Paths.get(configuredSourceDirectory); + updateToolSource(rootDirectory, toolName, categoryKey, description, register, requiresApproval); + } + static void updateToolSource(Path rootDirectory, String toolName, String categoryKey, String description, + boolean register, Boolean requiresApproval) throws IOException { + Path targetFile = findToolSource(rootDirectory, toolName); if (targetFile == null) { - throw new Exception("소스 코드를 찾을 수 없습니다: " + toolName); + throw new IllegalArgumentException("Tool source not found: " + 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 + String content = Files.readString(targetFile); + content = updateMcpTool(content, toolName, description); + content = updateToolHint(content, categoryKey, register, requiresApproval); Files.writeString(targetFile, content); } + + private static Path findToolSource(Path rootDirectory, String toolName) throws IOException { + try (var paths = Files.walk(rootDirectory)) { + return paths.filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().endsWith("UseCase.java")) + .filter(path -> path.toString().contains("dap-was-") || path.toString().contains("axhub-tool-")) + .sorted(Comparator.naturalOrder()) + .filter(path -> containsMcpTool(path, toolName)) + .findFirst() + .orElse(null); + } + } + + private static boolean containsMcpTool(Path path, String toolName) { + try { + return annotationArguments(Files.readString(path), "McpTool", toolName) != null; + } catch (IOException exception) { + throw new IllegalStateException("Failed to read tool source: " + path, exception); + } + } + + private static String updateMcpTool(String content, String toolName, String description) { + AnnotationRange range = annotationArguments(content, "McpTool", toolName); + if (range == null) { + throw new IllegalArgumentException("McpTool declaration not found: " + toolName); + } + return description == null ? content : replaceAttribute(content, range, "description", quote(description)); + } + + private static String updateToolHint(String content, String categoryKey, boolean register, Boolean requiresApproval) { + AnnotationRange range = annotationArguments(content, "ToolHint", null); + if (range == null) { + throw new IllegalArgumentException("ToolHint declaration not found next to McpTool"); + } + String updated = replaceAttribute(content, range, "register", Boolean.toString(register)); + range = annotationArguments(updated, "ToolHint", null); + if (requiresApproval != null) { + updated = replaceAttribute(updated, range, "requiresApproval", Boolean.toString(requiresApproval)); + range = annotationArguments(updated, "ToolHint", null); + } + if (categoryKey != null && !categoryKey.isBlank()) { + updated = replaceAttribute(updated, range, "categoryKey", quote(categoryKey)); + } + return updated; + } + + private static String replaceAttribute(String content, AnnotationRange range, String attribute, String value) { + String arguments = content.substring(range.argumentsStart(), range.argumentsEnd()); + String pattern = "\\b" + attribute + "\\s*=\\s*(?:true|false|\\\"(?:\\\\.|[^\\\"\\\\])*\\\")"; + String replacement = arguments.replaceFirst(pattern, attribute + " = " + value); + if (replacement.equals(arguments)) { + replacement = arguments.isBlank() ? attribute + " = " + value : arguments + ", " + attribute + " = " + value; + } + return content.substring(0, range.argumentsStart()) + replacement + content.substring(range.argumentsEnd()); + } + + private static String quote(String value) { + return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + } + + private static AnnotationRange annotationArguments(String content, String annotationName, String toolName) { + int offset = content.indexOf("@" + annotationName); + while (offset >= 0) { + int openingParenthesis = content.indexOf('(', offset); + int closingParenthesis = findAnnotationEnd(content, openingParenthesis); + if (openingParenthesis < 0 || closingParenthesis < 0) { + return null; + } + AnnotationRange range = new AnnotationRange(openingParenthesis + 1, closingParenthesis); + if (toolName == null || content.substring(range.argumentsStart(), range.argumentsEnd()) + .matches("(?s).*\\bname\\s*=\\s*\\\"" + java.util.regex.Pattern.quote(toolName) + "\\\".*")) { + return range; + } + offset = content.indexOf("@" + annotationName, closingParenthesis + 1); + } + return null; + } + + private static int findAnnotationEnd(String content, int openingParenthesis) { + int depth = 0; + boolean inString = false; + boolean escaped = false; + for (int index = openingParenthesis; index < content.length(); index++) { + char character = content.charAt(index); + if (inString) { + if (escaped) { + escaped = false; + } else if (character == '\\') { + escaped = true; + } else if (character == '\"') { + inString = false; + } + } else if (character == '\"') { + inString = true; + } else if (character == '(') { + depth++; + } else if (character == ')' && --depth == 0) { + return index; + } + } + return -1; + } + + private record AnnotationRange(int argumentsStart, int argumentsEnd) { + } } diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java index f491fd87..89aea24d 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java @@ -15,7 +15,7 @@ import java.util.regex.Pattern; /** * @package io.shinhanlife.dap.lib.validation * @className McpToolNameValidator - * @description Validates unique MCP function names across tool modules + * @description Validates unique MCP SDK tool names across tool modules * @author 0986406 * @create 2026.07.27 *
@@ -88,7 +88,7 @@ public final class McpToolNameValidator {
             throw new UncheckedIOException("Failed to read " + source, exception);
         }
 
-        int annotationOffset = content.indexOf("@McpFunction");
+        int annotationOffset = content.indexOf("@McpTool");
         while (annotationOffset >= 0) {
             int openingParenthesis = content.indexOf('(', annotationOffset);
             int closingParenthesis = findAnnotationEnd(content, openingParenthesis);
@@ -103,7 +103,7 @@ public final class McpToolNameValidator {
                 declarationsByName.computeIfAbsent(toolName, ignored -> new ArrayList<>())
                         .add(new ToolDeclaration(moduleName, source, line));
             }
-            annotationOffset = content.indexOf("@McpFunction", closingParenthesis + 1);
+            annotationOffset = content.indexOf("@McpTool", closingParenthesis + 1);
         }
     }
 
diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java
index a998ad79..c3f42a2b 100644
--- a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java
+++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java
@@ -17,9 +17,8 @@ package io.shinhanlife.dap.mcc.presentation;
  */
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.networknt.schema.Error;
-import io.shinhanlife.dap.lib.annotation.McpFunction;
+import org.springframework.ai.mcp.annotation.McpTool;
 import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator;
-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.mcp.ToolRegistryHeartbeatSender;
@@ -88,15 +87,15 @@ public class BusinessToolController {
         }
         Object targetBean = null;
         Method targetMethod = null;
-        McpFunction targetFunctionAnnotation = null;
+        McpTool targetFunctionAnnotation = null;
         
-        // McpTool 어노테이션 기반 조회가 프록시 문제로 누락될 수 있으므로, 전체 빈을 순회하며 @McpFunction을 찾습니다.
+        // McpTool 어노테이션 기반 조회가 프록시 문제로 누락될 수 있으므로, 전체 빈을 순회하며 @McpTool을 찾습니다.
         Map allBeans = applicationContext.getBeansOfType(Object.class);
         outerLoop:
         for (Object bean : allBeans.values()) {
             Class targetClass = AopUtils.getTargetClass(bean);
             for (Method targetMethodOfClass : targetClass.getDeclaredMethods()) {
-                McpFunction mcpFunc = AnnotationUtils.findAnnotation(targetMethodOfClass, McpFunction.class);
+                McpTool mcpFunc = AnnotationUtils.findAnnotation(targetMethodOfClass, McpTool.class);
                 if (mcpFunc != null) {
                     String baseName = mcpFunc.name();
                     String expectedName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
@@ -122,7 +121,7 @@ public class BusinessToolController {
             for (Object bean : allBeans.values()) {
                 Class targetCls = AopUtils.getTargetClass(bean);
                 for (Method m : targetCls.getDeclaredMethods()) {
-                    McpFunction func = AnnotationUtils.findAnnotation(m, McpFunction.class);
+                    McpTool func = AnnotationUtils.findAnnotation(m, McpTool.class);
                     if (func != null) {
                         String baseName = func.name();
                         String expName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
@@ -149,7 +148,8 @@ public class BusinessToolController {
             Class paramType = targetMethod.getParameterTypes()[0];
             if (!Map.class.isAssignableFrom(paramType)) {
                 try {
-                    Map inputSchema = toolSchemaResolver.resolve(targetFunctionAnnotation, paramType);
+                    io.shinhanlife.dap.lib.annotation.ToolHint hint = AnnotationUtils.findAnnotation(targetMethod, io.shinhanlife.dap.lib.annotation.ToolHint.class);
+                    Map inputSchema = toolSchemaResolver.resolve(targetFunctionAnnotation, hint, paramType);
                     List errors = toolArgumentSchemaValidator.validate(inputSchema, arguments);
                     if (!errors.isEmpty()) {
                         log.error("[Tool] 파라미터 유효성 검증 실패: {}", errors);
@@ -194,7 +194,8 @@ public class BusinessToolController {
                 methodResult = targetMethod.invoke(targetBean, invokeArgument);
             }
 
-            Map outputSchema = toolSchemaResolver.resolveOutput(targetFunctionAnnotation, targetMethod.getReturnType());
+            io.shinhanlife.dap.lib.annotation.ToolHint outputHint = AnnotationUtils.findAnnotation(targetMethod, io.shinhanlife.dap.lib.annotation.ToolHint.class);
+            Map outputSchema = toolSchemaResolver.resolveOutput(targetFunctionAnnotation, targetMethod.getReturnType(), outputHint);
             if (!outputSchema.isEmpty()) {
                 List outputErrors = toolArgumentSchemaValidator.validateValue(outputSchema, methodResult);
                 if (!outputErrors.isEmpty()) {
diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java
index 011b5715..9ee767a2 100644
--- a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java
+++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java
@@ -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 io.shinhanlife.dap.lib.integration.mci.dto.MciRequestWrapper;
diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java
index aa6529da..df20d796 100644
--- a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java
+++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java
@@ -1,5 +1,7 @@
 package io.shinhanlife.dap.lib.util;
 
+
+import org.springframework.ai.mcp.annotation.McpToolParam;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -10,8 +12,6 @@ import com.networknt.schema.InputFormat;
 import com.networknt.schema.Schema;
 import com.networknt.schema.SchemaRegistry;
 import com.networknt.schema.SpecificationVersion;
-import io.shinhanlife.dap.lib.annotation.McpParameter;
-import io.shinhanlife.dap.lib.annotation.McpValidation;
 import java.util.List;
 import java.util.Map;
 import org.junit.jupiter.api.Test;
@@ -90,24 +90,19 @@ class JsonSchemaGeneratorTest {
     }
 
     private static class ValidatedRequest {
-        @McpParameter(description = "recipient phone number", required = true)
-        @McpValidation(pattern = "^01[0-9]{8,9}$")
+        @McpToolParam(description = "recipient phone number", required = true)
         private String phoneNumber;
 
-        @McpParameter(description = "issue amount", required = true)
-        @McpValidation(minimum = 1)
+        @McpToolParam(description = "issue amount", required = true)
         private Long amount;
 
-        @McpParameter(description = "approval result")
-        @McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"})
+        @McpToolParam(description = "approval result")
         private String approvalStatus;
 
-        @McpParameter(description = "page size")
-        @McpValidation(minimum = 1, maximum = 50, defaultValue = "20")
+        @McpToolParam(description = "page size")
         private Integer pageSize;
 
-        @McpParameter(description = "reference")
-        @McpValidation(minLength = 1, maxLength = 30)
+        @McpToolParam(description = "reference")
         private String reference;
     }
 
@@ -120,7 +115,6 @@ class JsonSchemaGeneratorTest {
     }
 
     private static class NestedChild {
-        @McpValidation(required = true, pattern = "^\\d{8}$")
         private String businessDate;
     }
 }
diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java
index 09cbe51d..46f72a3c 100644
--- a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java
+++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java
@@ -22,8 +22,8 @@ class ToolScaffolderTest {
         assertTrue(useCase.contains("name = \"oth.cmm.claim.search\""));
         assertTrue(useCase.contains("version = \"1.0.0\""));
         assertTrue(useCase.contains("timeoutMillis = 300000L"));
-        assertTrue(response.contains("@McpOutputSchema"));
-        assertTrue(response.contains("@McpValidation"));
+        assertTrue(response.contains(""));
+        assertTrue(response.contains(""));
     }
     @Test
     void usesWasModuleNameAsToolPodPrefix() throws Exception {
diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java
index b6d02d1b..166c7fad 100644
--- a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java
+++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java
@@ -4,33 +4,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 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.util.List;
 import java.util.Map;
 import org.junit.jupiter.api.Test;
+import org.springframework.ai.mcp.annotation.McpTool;
 
 class ToolSchemaResolverTest {
 
     private final ToolSchemaResolver resolver = new ToolSchemaResolver(new ObjectMapper());
 
-    @Test
-    void usesInlineSchemaBeforeAutomaticDtoSchema() throws Exception {
-        Method method = InlineSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class);
-
-        Map schema = resolver.resolve(method.getAnnotation(McpFunction.class), AutomaticRequest.class);
-
-        assertEquals(false, schema.get("additionalProperties"));
-        assertTrue(!properties(schema).containsKey("differentField"));
-    }
-
     @Test
     void generatesSchemaFromRequestDtoWhenNoExplicitSchemaIsConfigured() throws Exception {
         Method method = AutomaticSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class);
 
-        Map schema = resolver.resolve(method.getAnnotation(McpFunction.class), AutomaticRequest.class);
+        Map schema = resolver.resolve(method.getAnnotation(McpTool.class), null, AutomaticRequest.class);
 
         assertTrue(properties(schema).containsKey("differentField"));
     }
@@ -38,7 +26,7 @@ class ToolSchemaResolverTest {
     @Test
     void resolvesExplicitOutputSchema() throws Exception {
         Method method = OutputSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class);
-        Map schema = resolver.resolveOutput(method.getAnnotation(McpFunction.class));
+        Map schema = resolver.resolveOutput(method.getAnnotation(McpTool.class));
         assertEquals(false, schema.get("additionalProperties"));
         assertTrue(properties(schema).containsKey("resultCode"));
     }
@@ -48,7 +36,7 @@ class ToolSchemaResolverTest {
         Method method = AutomaticOutputSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class);
 
         Map schema = resolver.resolveOutput(
-                method.getAnnotation(McpFunction.class), SimpleResponse.class);
+                method.getAnnotation(McpTool.class), SimpleResponse.class);
 
         assertEquals(List.of("resultCode"), schema.get("required"));
         assertEquals(List.of("SUCCESS", "FAILURE"), property(schema, "resultCode").get("enum"));
@@ -58,7 +46,7 @@ class ToolSchemaResolverTest {
     void doesNotEnableOutputValidationWhenOutputSchemaIsNotDeclared() throws Exception {
         Method method = AutomaticSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class);
         Map schema = resolver.resolveOutput(
-                method.getAnnotation(McpFunction.class), AutomaticRequest.class);
+                method.getAnnotation(McpTool.class), AutomaticRequest.class);
         assertTrue(schema.isEmpty());
     }
     @SuppressWarnings("unchecked")
@@ -71,44 +59,27 @@ class ToolSchemaResolverTest {
         return (Map) properties(schema).get(name);
     }
 
-    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) {
-        }
-    }
-
     static class AutomaticSchemaTool {
-        @McpFunction(displayName = "automatic", name = "test.schema.automatic", description = "automatic schema")
+        @McpTool(name = "oth.test.automatic.search")
         void search(AutomaticRequest request) {
         }
     }
 
     static class AutomaticOutputSchemaTool {
-        @McpFunction(displayName = "automatic-output", name = "test.schema.automatic-output", description = "automatic output")
+        @McpTool(name = "oth.test.output.search")
         SimpleResponse search(AutomaticRequest request) {
             return null;
         }
     }
 
-    @McpOutputSchema
     static class SimpleResponse {
-        @McpValidation(required = true, allowedValues = {"SUCCESS", "FAILURE"})
         private String resultCode;
 
-        @McpValidation(maxLength = 200)
         private String message;
     }
 
     static class OutputSchemaTool {
-        @McpFunction(
-                displayName = "output",
-                name = "test.schema.output",
-                description = "output schema",
-                outputSchema = "{\"type\":\"object\",\"properties\":{\"resultCode\":{\"type\":\"string\"}},\"required\":[\"resultCode\"],\"additionalProperties\":false}")
+        @McpTool(name = "oth.test.explicit.search")
         void search(AutomaticRequest request) {
         }
     }
diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java
new file mode 100644
index 00000000..b9c15443
--- /dev/null
+++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java
@@ -0,0 +1,37 @@
+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;
+import org.junit.jupiter.api.io.TempDir;
+
+class ToolSourceUpdaterTest {
+
+    @TempDir
+    Path temporaryRoot;
+
+    @Test
+    void updatesSdkAndProjectOwnedMetadataOnTheSameToolMethod() throws Exception {
+        Path source = temporaryRoot.resolve("dap-was-oth/src/main/java/example/SampleUseCase.java");
+        Files.createDirectories(source.getParent());
+        Files.writeString(source, """
+                package example;
+                import org.springframework.ai.mcp.annotation.McpTool;
+                import io.shinhanlife.dap.lib.annotation.ToolHint;
+                interface SampleUseCase {
+                    @McpTool(name = "oth.cmm.sample.search", description = "old")
+                    @ToolHint(register = false, requiresApproval = false)
+                    void search();
+                }
+                """);
+
+        ToolSourceUpdater.updateToolSource(temporaryRoot, "oth.cmm.sample.search", "customer", "new", true, true);
+
+        String updated = Files.readString(source);
+        assertTrue(updated.contains("@McpTool(name = \"oth.cmm.sample.search\", description = \"new\")"));
+        assertTrue(updated.contains("@ToolHint(register = true, requiresApproval = true"));
+        assertTrue(updated.contains("categoryKey = \"customer\""), updated);
+    }
+}
diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java
index 626817ef..93e37aee 100644
--- a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java
+++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java
@@ -30,7 +30,7 @@ class McpToolNameValidatorTest {
     Path temporaryRoot;
 
     @Test
-    void rejectsDuplicateMcpFunctionNamesAcrossToolModules() throws IOException {
+    void rejectsDuplicateMcpToolNamesAcrossToolModules() throws IOException {
         writeToolSource("dap-was-first", "FirstTool.java", "first", "oth.sms.notification.send");
         writeToolSource("dap-was-second", "SecondTool.java", "second", "oth.sms.notification.send");
 
@@ -43,7 +43,7 @@ class McpToolNameValidatorTest {
     }
 
     @Test
-    void validationRunnerRejectsDuplicateMcpFunctionNamesBeforePackaging() throws IOException {
+    void validationRunnerRejectsDuplicateMcpToolNamesBeforePackaging() throws IOException {
         writeToolSource("dap-was-first", "FirstTool.java", "first", "oth.sms.notification.send");
         writeToolSource("dap-was-second", "SecondTool.java", "second", "oth.sms.notification.send");
 
@@ -90,11 +90,13 @@ class McpToolNameValidatorTest {
         Files.writeString(source, """
                 package example;
 
+                import org.springframework.ai.mcp.annotation.McpTool;
+
                 class %s {
-                    @McpFunction(displayName = "%s", name = "%s", description = "test")
+                    @McpTool(name = "%s")
                     void execute() { }
                 }
-                """.formatted(className, className, toolName));
+                """.formatted(className, toolName));
     }
 
     private Path findProjectRoot() {
diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java
index 6efc38ec..8d09ca36 100644
--- a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java
+++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java
@@ -1,4 +1,4 @@
-package io.shinhanlife.dap.lib.manifest;
+package io.shinhanlife.dap.mcc.manifest;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -6,6 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 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 java.util.List;
 import java.util.Map;
diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java
index 59bc1597..059174e5 100644
--- a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java
+++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java
@@ -1,4 +1,4 @@
-package io.shinhanlife.dap.lib.mcp;
+package io.shinhanlife.dap.mcc.mcp;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -10,8 +10,9 @@ import static org.mockito.Mockito.when;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.modelcontextprotocol.server.McpSyncServer;
+import io.shinhanlife.dap.lib.mcp.*;
 import io.shinhanlife.dap.mcc.presentation.BusinessToolController;
-import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender;
+
 import java.lang.reflect.Method;
 import java.util.Map;
 import org.junit.jupiter.api.Test;
diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java
index b12a9375..82fe9796 100644
--- a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java
+++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java
@@ -1,9 +1,11 @@
-package io.shinhanlife.dap.lib.mcp;
+package io.shinhanlife.dap.mcc.mcp;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
+import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.web.servlet.ServletRegistrationBean;
 
@@ -16,7 +18,6 @@ class ToolMcpServerConfigurationTest {
         HttpServletStreamableServerTransportProvider transport = configuration.toolMcpTransportProvider();
         ServletRegistrationBean registration = configuration.toolMcpServlet(transport);
 
-        assertEquals("/mcp", registration.getUrlMappings().iterator().next());
-        assertFalse(registration.getUrlMappings().contains("/mcp/*"));
+        assertThat(registration.getUrlMappings()).containsExactlyInAnyOrder("/mcp", "/mcp/message");
     }
 }
diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BalanceRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BalanceRequest.java
index 483198cd..aff6ae40 100644
--- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BalanceRequest.java
+++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BalanceRequest.java
@@ -1,6 +1,11 @@
 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
  * @className BalanceRequest
@@ -15,13 +20,12 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
  * 
  * 
*/ -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; + @Data public class BalanceRequest { - @McpParameter(description = "고객의 계좌번호 (- 제외) ", required = true) - @McpValidation(pattern = "\\S", examples = {"1234567890"}) + @McpToolParam(description = "고객의 계좌번호 (- 제외) ", required = true) + @Schema(pattern = "\\S", example = "1234567890") private String accountNumber; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BillingProcessRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BillingProcessRequest.java index 2839b58b..3d72b062 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BillingProcessRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BillingProcessRequest.java @@ -1,11 +1,14 @@ 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.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Getter; import lombok.Setter; @@ -30,11 +33,12 @@ import lombok.Setter; @AllArgsConstructor public class BillingProcessRequest { - @McpParameter(description = "처리할 청구 접수 번호", required = true) - @McpValidation(pattern = "\\S", examples = {"BILL20260805"}) + @McpToolParam(description = "처리할 청구 접수 번호", required = true) + @Schema(pattern = "\\S", example = "BILL20260805") private String billingId; - @McpParameter(description = "심사 승인 여부 (예: APPROVE, REJECT)") - @McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"}, examples = {"APPROVED"}) + + @McpToolParam(description = "승인 처리 구분 (예: APPROVE, REJECT)") + @Schema(required = true, allowableValues = {"APPROVE", "REJECT"}, example = "APPROVE") private String approvalStatus; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BillingStatusRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BillingStatusRequest.java index 18f10f91..c2cff692 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BillingStatusRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BillingStatusRequest.java @@ -1,11 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpValidation; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; import lombok.Getter; import lombok.Setter; @@ -30,7 +33,8 @@ import lombok.Setter; @AllArgsConstructor public class BillingStatusRequest { - @McpParameter(description = "조회할 청구 접수 번호", required = true) - @McpValidation(examples = {"BILL20260805"}) + @McpToolParam(description = "조회할 청구 접수 번호", required = true) + @Schema(example = "BILL20260805") private String billingId; + } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BondCheckRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BondCheckRequest.java index 49b560aa..28560ecb 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BondCheckRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BondCheckRequest.java @@ -1,11 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpValidation; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; import lombok.Getter; import lombok.Setter; @@ -30,7 +33,8 @@ import lombok.Setter; @AllArgsConstructor public class BondCheckRequest { - @McpParameter(description = "확인하고자 하는 디지털 증권 발행 금액", required = true) - @McpValidation(examples = {"1000000"}) - private Long amount; + @McpToolParam(description = "확인하고자 하는 디지털 증권 발행 금액", required = true) + @Schema(pattern = "^[0-9]+$", example = "1000000") + private String amount; + } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BondIssueRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BondIssueRequest.java index 9b5e73e4..69dea77a 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BondIssueRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/BondIssueRequest.java @@ -1,11 +1,14 @@ 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.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Getter; import lombok.Setter; @@ -30,11 +33,12 @@ import lombok.Setter; @AllArgsConstructor public class BondIssueRequest { - @McpParameter(description = "발행할 디지털 증권 금액", required = true) - @McpValidation(minimum = 1, examples = {"1000000"}) - private Long amount; + @McpToolParam(description = "발행할 증권 금액 (숫자 문자열)", required = true) + @Schema(pattern = "^[0-9]+$", example = "1000000") + private String amount; - @McpParameter(description = "발행 대상 계좌 번호", required = true) - @McpValidation(pattern = "\\S", examples = {"1234567890"}) + + @McpToolParam(description = "발행 대상 계좌 번호", required = true) + @Schema(pattern = "\\S", example = "1234567890") private String targetAccount; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java index eb731e80..093c73e7 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java @@ -1,8 +1,10 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpAnyOf; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -18,40 +20,39 @@ import lombok.Setter; @Builder @NoArgsConstructor @AllArgsConstructor -@McpAnyOf({"claimNo", "contractNo"}) public class ClaimSearchRequest { - @McpParameter(description = "청구번호. CLM 다음 숫자 13자리 형식이다.") - @McpValidation(pattern = "^CLM[0-9]{13}$", examples = {"CLM2026070100123"}) + @McpToolParam(description = "청구번호. CLM 다음 숫자 13자리 형식이다.") + @Schema(pattern = "^CLM[0-9]{13}$", example = "CLM2026070100123") private String claimNo; - @McpParameter(description = "계약번호. 숫자 11자리 형식이다.") - @McpValidation(pattern = "^[0-9]{11}$", examples = {"10023456789"}) + @McpToolParam(description = "계약번호. 숫자 11자리 형식이다.") + @Schema(pattern = "^[0-9]{11}$", example = "10023456789") private String contractNo; - @McpParameter(description = "청구 상태 필터") - @McpValidation(examples = {"RECEIVED"}, allowedValues = { + @McpToolParam(description = "청구 상태 필터") + @Schema(example = "RECEIVED", allowableValues = { "RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED", "APPROVED", "PAID", "REJECTED", "WITHDRAWN" }) private String status; - @McpParameter(description = "청구 유형 필터") - @McpValidation(examples = {"MEDICAL"}, allowedValues = { + @McpToolParam(description = "청구 유형 필터") + @Schema(example = "MEDICAL", allowableValues = { "MEDICAL", "SURGERY", "HOSPITALIZATION", "DIAGNOSIS", "DEATH", "DISABILITY" }) private String claimType; - @McpParameter(description = "접수일 조회 시작일(YYYY-MM-DD)") - @McpValidation(format = "date", examples = {"2026-01-01"}) + @McpToolParam(description = "접수일 조회 시작일(YYYY-MM-DD)") + @Schema(format = "date", example = "2026-01-01") private String fromDate; - @McpParameter(description = "접수일 조회 종료일(YYYY-MM-DD)") - @McpValidation(format = "date", examples = {"2026-07-31"}) + @McpToolParam(description = "접수일 조회 종료일(YYYY-MM-DD)") + @Schema(format = "date", example = "2026-07-31") private String toDate; - @McpParameter(description = "반환할 최대 건수") - @McpValidation(minimum = 1, maximum = 50, defaultValue = "20", examples = {"20"}) - private Integer size; + @McpToolParam(description = "반환할 최대 건수 (기본값: 20, 최대: 50)") + @Schema(pattern = "^[1-9][0-9]?$|^50$", defaultValue = "20", example = "20") + private String size; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java index abd2d2d8..cd5ed033 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java @@ -1,8 +1,10 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpOutputSchema; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; @@ -20,46 +22,45 @@ import lombok.Setter; @Builder @NoArgsConstructor @AllArgsConstructor -@McpOutputSchema public class ClaimSearchResponse { - @McpParameter(description = "Execution result code.") - @McpValidation(required = true, allowedValues = {"SUCCESS", "FAILURE"}) + @McpToolParam(description = "Execution result code.") + @Schema(required = true, allowableValues = {"SUCCESS", "FAILURE"}) private String resultCode; - @McpParameter(description = "User-readable label for resultCode.") - @McpValidation(required = true, maxLength = 100) + @McpToolParam(description = "User-readable label for resultCode.") + @Schema(required = true, maxLength = 100) private String resultLabel; - @McpParameter(description = "Current claim processing status code.") - @McpValidation(required = true, allowedValues = { + @McpToolParam(description = "Current claim processing status code.") + @Schema(required = true, allowableValues = { "RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED", "APPROVED", "PAID", "REJECTED", "WITHDRAWN" }) private String status; - @McpParameter(description = "User-readable label for status.") - @McpValidation(required = true, maxLength = 100) + @McpToolParam(description = "User-readable label for status.") + @Schema(required = true, maxLength = 100) private String statusLabel; - @McpParameter(description = "Approved amount. Null before review; do not interpret null as zero.") - @McpValidation(minimum = 0, nullable = true) + @McpToolParam(description = "Approved amount. Null before review; do not interpret null as zero.") + @Schema(minimum = "0", nullable = true) private Long approvedAmount; - @McpParameter(description = "Present only when status is REJECTED; otherwise null.") - @McpValidation(maxLength = 200, nullable = true) + @McpToolParam(description = "Present only when status is REJECTED; otherwise null.") + @Schema(maxLength = 200, nullable = true) private String rejectionReason; - @McpParameter(description = "Claim summaries, ordered by received date descending.") - @McpValidation(required = true) + @McpToolParam(description = "Claim summaries, ordered by received date descending.") + @Schema(required = true) private List items; - @McpParameter(description = "True when additional results exist beyond this response.") - @McpValidation(required = true) + @McpToolParam(description = "True when additional results exist beyond this response.") + @Schema(required = true) private Boolean hasMore; - @McpParameter(description = "Total number of matched claims.") - @McpValidation(required = true, minimum = 0) + @McpToolParam(description = "Total number of matched claims.") + @Schema(required = true, nullable = true) private Integer totalCount; @Getter @@ -69,20 +70,18 @@ public class ClaimSearchResponse { @AllArgsConstructor public static class ClaimSummary { - @McpParameter(description = "Claim processing status code.") - @McpValidation(required = true) + @McpToolParam(description = "Claim processing status code.") private String status; - @McpParameter(description = "User-readable label for status.") - @McpValidation(required = true) + @McpToolParam(description = "User-readable label for status.") private String statusLabel; - @McpParameter(description = "Received date in YYYY-MM-DD format.") - @McpValidation(required = true, format = "date") + @McpToolParam(description = "Received date in YYYY-MM-DD format.") + @Schema(required = true, format = "date") private String receivedDate; - @McpParameter(description = "Approved amount. Null before review; do not interpret null as zero.") - @McpValidation(minimum = 0, nullable = true) + @McpToolParam(description = "Approved amount. Null before review; do not interpret null as zero.") + @Schema(nullable = true) private Long approvedAmount; } } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ContractDetailRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ContractDetailRequest.java index 815493ae..3fe86720 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ContractDetailRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ContractDetailRequest.java @@ -1,11 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpValidation; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; import lombok.Getter; import lombok.Setter; @@ -30,11 +33,12 @@ import lombok.Setter; @AllArgsConstructor public class ContractDetailRequest { - @McpParameter(description = "고객명", required = true) - @McpValidation(examples = {"홍길동"}) + @McpToolParam(description = "고객명", required = true) + @Schema(example = "홍길동") private String customerName; - @McpParameter(description = "조회할 계약 번호", required = true) - @McpValidation(examples = {"1234567890"}) + +@McpToolParam(description = "조회할 계약 번호", required = true) + @Schema(example = "1234567890") private String contractId; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ContractStatusRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ContractStatusRequest.java index 3926b2ca..01281c42 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ContractStatusRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ContractStatusRequest.java @@ -1,11 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpValidation; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; import lombok.Getter; import lombok.Setter; @@ -30,10 +33,11 @@ import lombok.Setter; @AllArgsConstructor public class ContractStatusRequest { - @McpParameter(description = "고객명", required = true) - @McpValidation(examples = {"홍길동"}) + @McpToolParam(description = "고객명", required = true) + @Schema(example = "홍길동") private String customerName; - @McpParameter(description = "조회할 계약 번호") + +@McpToolParam(description = "조회할 계약 번호") private String contractId; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/CustomerDetailRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/CustomerDetailRequest.java index 3abaa6f9..92c690d3 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/CustomerDetailRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/CustomerDetailRequest.java @@ -1,11 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpValidation; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; import lombok.Getter; import lombok.Setter; @@ -30,10 +33,11 @@ import lombok.Setter; @AllArgsConstructor public class CustomerDetailRequest { - @McpParameter(description = "고객명", required = true) - @McpValidation(examples = {"홍길동"}) + @McpToolParam(description = "고객명", required = true) + @Schema(example = "홍길동") private String customerName; - @McpParameter(description = "고객 식별 번호 (CID)") + +@McpToolParam(description = "고객 식별 번호 (CID)") private String customerId; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/CustomerGradeRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/CustomerGradeRequest.java index f2071c45..8ba28a81 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/CustomerGradeRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/CustomerGradeRequest.java @@ -1,11 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpValidation; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; import lombok.Getter; import lombok.Setter; @@ -30,10 +33,11 @@ import lombok.Setter; @AllArgsConstructor public class CustomerGradeRequest { - @McpParameter(description = "고객 이름 (예: 김신한)", required = true) - @McpValidation(examples = {"홍길동"}) + @McpToolParam(description = "고객 이름 (예: 김신한)", required = true) + @Schema(example = "홍길동") private String customerName; - @McpParameter(description = "고객 식별 번호 (CID)") + +@McpToolParam(description = "고객 식별 번호 (CID)") private String customerId; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/LeaveCountRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/LeaveCountRequest.java index 9c2a1c83..c6af49c0 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/LeaveCountRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/LeaveCountRequest.java @@ -1,11 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpValidation; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; import lombok.Getter; import lombok.Setter; @@ -30,7 +33,8 @@ import lombok.Setter; @AllArgsConstructor public class LeaveCountRequest { - @McpParameter(description = "연차 내역을 조회할 사원 번호", required = true) - @McpValidation(examples = {"EMP12345"}) + @McpToolParam(description = "연차 내역을 조회할 사원 번호", required = true) + @Schema(example = "EMP12345") private String employeeId; + } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java index f01b5335..5466af2c 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java @@ -1,8 +1,11 @@ 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 io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; /** @@ -22,14 +25,15 @@ import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class MetaCommonCodeRequest { - @McpParameter(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false) - @McpValidation(examples = {"GRP_001"}) + @McpToolParam(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false) + @Schema(example = "GRP_001") private String groupCode; - @McpParameter(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false) + +@McpToolParam(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false) private String codeName; - @McpParameter(description = "사용여부 (예: Y, N)", required = false) - @McpValidation(examples = {"Y"}) + @McpToolParam(description = "사용여부 (예: Y, N)", required = false) + @Schema(example = "Y") private String useYn; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java index a4108504..304dc6cd 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java @@ -1,8 +1,11 @@ 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 io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; /** @@ -22,15 +25,16 @@ import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class MetaTableRequest { - @McpParameter(description = "테이블 물리명 키워드 (예: TB_CUST_BAS, TB_CONT)", required = false) - @McpValidation(examples = {"TB_USER"}) + @McpToolParam(description = "테이블 물리명 키워드 (예: TB_CUST_BAS, TB_CONT)", required = false) + @Schema(example = "TB_USER") private String tableName; - @McpParameter(description = "테이블 논리명(한글) 키워드 (예: 고객기본, 계약)", required = false) - @McpValidation(examples = {"고객기본"}) + +@McpToolParam(description = "테이블 논리명(한글) 키워드 (예: 고객기본, 계약)", required = false) + @Schema(example = "고객기본") private String tableLogicalName; - @McpParameter(description = "스키마/소유자명 (예: DAPADM, SHLOWN)", required = false) - @McpValidation(examples = {"DAPADM"}) + @McpToolParam(description = "스키마/소유자명 (예: DAPADM, SHLOWN)", required = false) + @Schema(example = "DAPADM") private String owner; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringRequest.java index c2489154..8553125e 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringRequest.java @@ -1,7 +1,8 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import io.swagger.v3.oas.annotations.media.Schema; import com.fasterxml.jackson.annotation.JsonInclude; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; /** @@ -21,6 +22,6 @@ import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class SampleStringRequest { - @McpValidation(examples = {"test query"}) + @Schema(example = "test query") private String query; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/TemplateDownloadRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/TemplateDownloadRequest.java index 4ef4b71b..96340c54 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/TemplateDownloadRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/TemplateDownloadRequest.java @@ -1,6 +1,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto; -import io.shinhanlife.dap.lib.annotation.McpValidation; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -32,6 +33,6 @@ public class TemplateDownloadRequest { /** * 다운로드할 템플릿의 종류 ID (예: CUSTOMER_EXCEL, PRODUCT_PDF 등) */ - @McpValidation(examples = {"TPL_001"}) + @Schema(example = "TPL_001") private String templateId; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/VacationRegisterRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/VacationRegisterRequest.java index 316d48ab..184d0dea 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/VacationRegisterRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/VacationRegisterRequest.java @@ -1,11 +1,14 @@ 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.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Getter; import lombok.Setter; @@ -30,11 +33,12 @@ import lombok.Setter; @AllArgsConstructor public class VacationRegisterRequest { - @McpParameter(description = "연차를 등록할 사원 번호", required = true) - @McpValidation(pattern = "\\S", examples = {"EMP12345"}) + @McpToolParam(description = "연차를 등록할 사원 번호", required = true) + @Schema(pattern = "\\S", example = "EMP12345") private String employeeId; - @McpParameter(description = "휴가 일자 (YYYY-MM-DD 형식)", required = true) - @McpValidation(pattern = "^\\d{4}-\\d{2}-\\d{2}$", examples = {"2026-08-05"}) + +@McpToolParam(description = "휴가 일자 (YYYY-MM-DD 형식)", required = true) + @Schema(pattern = "^\\d{4}-\\d{2}-\\d{2}$", example = "2026-08-05") private String date; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BalanceUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BalanceUseCase.java index 1966f9df..8935f29b 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BalanceUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BalanceUseCase.java @@ -1,15 +1,12 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; + +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.cmm.dto.*; -@McpTool(routingType = "MCI", categoryKey = "cmm") public interface BalanceUseCase { - @McpFunction(register = false, displayName = "balance 툴", name = "oth.cmm.balance.inquiry", - description = "고객의 계좌 잔액을 조회합니다.", - prompt = "고객 계좌 잔액을 조회해줘.", - mappingId = "ACC_001" - ) + @McpTool(name = "oth.cmm.balance.inquiry", title = "잔고 조회 툴", description = "고객의 계좌 잔액을 조회합니다.") + @ToolHint(register = false, categoryKey = "cmm", mappingId = "ACC_001") Object execute(BalanceRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BillingProcessUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BillingProcessUseCase.java index 3071df0c..9d0fec93 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BillingProcessUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BillingProcessUseCase.java @@ -1,16 +1,13 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.cmm.dto.*; -@McpTool( - routingType = "MCI", - categoryKey = "cmm" -) public interface BillingProcessUseCase { 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, categoryKey = "cmm", mappingId = "BILL_002") Object processBilling(BillingProcessRequest data); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BondIssueUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BondIssueUseCase.java index 1290b73f..7c92a949 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BondIssueUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/BondIssueUseCase.java @@ -1,16 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; + +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.cmm.dto.*; -@McpTool( - routingType = "EAI", - categoryKey = "cmm" -) public interface BondIssueUseCase { 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, categoryKey = "cmm", mappingId = "BOND_002") Object issue(BondIssueRequest data); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchSchemaSampleUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchSchemaSampleUseCase.java index 8e0dea3f..c55be3a0 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchSchemaSampleUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchSchemaSampleUseCase.java @@ -1,26 +1,16 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpFunction; -import io.shinhanlife.dap.lib.annotation.McpTool; +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest; import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse; -@McpTool( - routingType = "DIRECT", - categoryKey = "cmm" -) public interface ClaimSearchSchemaSampleUseCase { - @McpFunction( - register = false, - displayName = "Claim search JSON Schema sample", - name = "oth.cmm.claim.search", - 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 - ) + @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)) + @ToolHint(register = false, categoryKey = "cmm", + inputSchemaResource = "classpath:mcp/schema/claim-search-resource-input-schema.json", + outputSchemaResource = "classpath:mcp/schema/claim-search-resource-output-schema.json") ClaimSearchResponse search(ClaimSearchRequest request); -} \ No newline at end of file +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CommonUtilityUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CommonUtilityUseCase.java index 200534b5..b7d2d2eb 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CommonUtilityUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CommonUtilityUseCase.java @@ -1,19 +1,18 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; + +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.cmm.dto.*; -@McpTool( - routingType = "HTTP", - categoryKey = "cmm" -) public interface CommonUtilityUseCase { 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, categoryKey = "cmm", mappingId = "HR_VAC_02") 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, categoryKey = "cmm", mappingId = "SECRET_001") Object secretTool(LeaveCountRequest data); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ContractInquiryUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ContractInquiryUseCase.java index 87dfcdd3..f3eee3e5 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ContractInquiryUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ContractInquiryUseCase.java @@ -1,16 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; + +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.cmm.dto.*; -@McpTool( - routingType = "HTTP", - categoryKey = "cmm" -) public interface ContractInquiryUseCase { 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, categoryKey = "cmm", mappingId = "CNTR_002") Object getDetail(ContractDetailRequest data); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerInfoUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerInfoUseCase.java index faf82c0a..1847f93c 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerInfoUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerInfoUseCase.java @@ -1,16 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; + +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.cmm.dto.*; -@McpTool( - routingType = "TCP", - categoryKey = "cmm" -) public interface CustomerInfoUseCase { 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, categoryKey = "cmm", mappingId = "CRM_002") Object getDetail(CustomerDetailRequest data); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java index dd5d7ddc..8ff9ce8a 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java @@ -1,7 +1,8 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpFunction; -import io.shinhanlife.dap.lib.annotation.McpTool; +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; /** @@ -18,20 +19,8 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; * * */ -@McpTool( - routingType = "MCI", - categoryKey = "cmm" -) public interface MetaCommonCodeUseCase { - @McpFunction( - displayName = "메타 통합코드 조회 툴", - name = "oth.cmm.common-code.lookup", - description = "메타 통합코드 목록을 조회해줘", - prompt = "메타 통합코드 목록을 조회해줘", - mappingId = "CLCNNB00001", - register = false, - requiresApproval = false, - openWorldHint = true - ) + @McpTool(name = "oth.cmm.common-code.lookup", title = "메타 공통코드 조회 툴", description = "메타 통합코드 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "cmm", mappingId = "CLCNNB00001") Object execute(MetaCommonCodeRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java index 0d7616c9..7eec1a6e 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java @@ -1,7 +1,8 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpFunction; -import io.shinhanlife.dap.lib.annotation.McpTool; +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; /** @@ -18,20 +19,8 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; * * */ -@McpTool( - routingType = "MCI", - categoryKey = "cmm" -) public interface MetaTableUseCase { - @McpFunction( - displayName = "메타 테이블 조회 툴", - name = "oth.cmm.meta.table", - description = "메타 테이블 정보 목록을 조회해줘", - prompt = "메타 테이블 정보 목록을 조회해줘", - mappingId = "CLCNNB00001", - register = false, - requiresApproval = false, - openWorldHint = true - ) + @McpTool(name = "oth.cmm.meta.table", title = "메타 테이블 조회 툴", description = "메타 테이블 정보 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "cmm", mappingId = "CLCNNB00001") Object execute(MetaTableRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/TemplateUtilityUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/TemplateUtilityUseCase.java index 1612921c..63015f61 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/TemplateUtilityUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/TemplateUtilityUseCase.java @@ -1,21 +1,14 @@ package io.shinhanlife.dap.mcc.biz.cmm.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; + +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.cmm.dto.*; import java.util.Map; -@McpTool( - routingType = "HTTP", - categoryKey = "cmm" -) public interface TemplateUtilityUseCase { - @McpFunction( - displayName = "템플릿 유틸리티", - name = "oth.cmm.template.url", - description = "요청한 템플릿(엑셀, 워드 등) 파일(양식)을 다운로드 받을 수 있는 시스템 URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.", - prompt = "요청하신 템플릿 양식 파일 다운로드 URL은 다음과 같습니다. 클릭하여 다운로드하세요:" - ) + @McpTool(name = "oth.cmm.template.url", title = "템플릿 유틸리티 툴", description = "요청한 템플릿(엑셀, 워드 등) 파일(양식)을 다운로드 받을 수 있는 시스템 URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.") + @ToolHint(categoryKey = "cmm") Map getTemplateFileUrl(TemplateDownloadRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java index b214d073..bd75039b 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java @@ -1,10 +1,11 @@ package io.shinhanlife.dap.mcc.biz.oth.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.oth.dto.*; -@McpTool(routingType = "MCI", categoryKey = "oth") public interface Onnba3011UseCase { - Object callOnnba3011(Onnba3011Request req); + @McpTool(name = "oth.onnba3011.call", description = "Onnba3011 호출 툴") + @ToolHint(categoryKey = "oth") + Object execute(Onnba3011Request req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java index c96edf38..bf8e2138 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java @@ -35,11 +35,9 @@ public class Onnba3011UseCaseImpl implements Onnba3011UseCase { private final Onnba3011Converter onnba3011Converter; /** * AI Agent가 호출하게 될 메서드입니다. - * @McpFunction 어노테이션 하나로 AI 도구로 자동 노출 및 라우팅됩니다. */ - @Override - public Object callOnnba3011(Onnba3011Request req) { + public Object execute(Onnba3011Request req) { log.info("[MCI Tool] 보종By가입설계한도계산조회 요청 수신."); try { diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteRequest.java index 94f98f50..36e5e2a6 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteRequest.java @@ -1,16 +1,19 @@ package io.shinhanlife.dap.mcc.biz.smp.dto; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import com.fasterxml.jackson.annotation.JsonInclude; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class DailyQuoteRequest { - @McpParameter(description = "카테고리 (예: 속담 등)", required = false) - @McpValidation(examples = {"속담"}) + @McpToolParam(description = "카테고리 (예: 속담 등)", required = false) + @Schema(example = "속담") private String category; } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateRequest.java index 3a2abf34..366c175d 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateRequest.java @@ -1,15 +1,18 @@ package io.shinhanlife.dap.mcc.biz.smp.dto; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import com.fasterxml.jackson.annotation.JsonInclude; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class ExchangeRateRequest { - @McpParameter(description = "환율 코드 (예: USD 등)", required = false) - @McpValidation(examples = {"USD"}) + @McpToolParam(description = "환율 코드 (예: USD 등)", required = false) + @Schema(example = "USD") private String currencyCode; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberRequest.java index e6d249ba..ea2314bd 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberRequest.java @@ -1,8 +1,11 @@ package io.shinhanlife.dap.mcc.biz.smp.dto; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import com.fasterxml.jackson.annotation.JsonInclude; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; /** @@ -22,7 +25,8 @@ import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class TeamMemberRequest { - @McpParameter(description = "조회할 팀 이름 (예: AX, MCP, TOOL, 전체 등)", required = false) - @McpValidation(examples = {"TOOL"}) + @McpToolParam(description = "조회할 팀 이름 (예: AX, MCP, TOOL, 전체 등)", required = false) + @Schema(example = "TOOL") private String teamName; + } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherRequest.java index d2978a30..9ea49678 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherRequest.java @@ -1,9 +1,10 @@ package io.shinhanlife.dap.mcc.biz.smp.dto; + + +import org.springframework.ai.mcp.annotation.McpToolParam; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; /** * @package io.shinhanlife.dap.mcc.dto @@ -20,8 +21,7 @@ import io.shinhanlife.dap.lib.annotation.McpValidation; * */ public record WeatherRequest( - @McpParameter(description = "도시를 입력하세여(예: 서울)", required = true) - @McpValidation(examples = {"서울"}) + @McpToolParam(description = "도시를 입력하세여(예: 서울)", required = true) String city ) { } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java index 7c7a88e0..9905e565 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java @@ -1,19 +1,13 @@ package io.shinhanlife.dap.mcc.biz.smp.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; + +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteRequest; import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteResponse; -@McpTool( - routingType = "DIRECT", - categoryKey = "smp" -) public interface DailyQuoteToolUseCase { - @McpFunction(register = false, displayName = "랜덤 명언 툴", name = "oth.smp.quote.daily", - description = "무작위로 영감을 주는 명언을 하나 가져옵니다.", - prompt = "오늘의 명언 하나 알려줘, 동기부여 명언 등", - mappingId = "QUOTE_001" - ) + @McpTool(name = "oth.smp.quote.daily", title = "오늘의 명언 툴", description = "무작위로 영감을 주는 명언을 하나 가져옵니다.") + @ToolHint(register = false, categoryKey = "smp", mappingId = "QUOTE_001") DailyQuoteResponse execute(DailyQuoteRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java index 2515d611..ba628bd5 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java @@ -1,21 +1,15 @@ package io.shinhanlife.dap.mcc.biz.smp.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; + +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest; import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse; import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest; import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse; -@McpTool( - routingType = "DIRECT", - categoryKey = "smp" -) public interface ExchangeRateToolUseCase { - @McpFunction(register = false, displayName = "실시간 환율 조회 툴", name = "oth.smp.exchange-rate.inquiry", - description = "원하는 통화의 실시간 환율을 조회합니다. (예: USD, EUR, JPY)", - prompt = "현재 달러 환율 알려줘, 엔화 환율은?", - mappingId = "EXCHANGE_001" - ) + @McpTool(name = "oth.smp.exchange-rate.inquiry", title = "실시간 환율 조회 툴", description = "원하는 통화의 실시간 환율을 조회합니다. (예: USD, EUR, JPY)") + @ToolHint(register = false, categoryKey = "smp", mappingId = "EXCHANGE_001") ExchangeRateResponse execute(ExchangeRateRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java index 3296f125..5bc204bd 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java @@ -1,23 +1,12 @@ package io.shinhanlife.dap.mcc.biz.smp.usecase; -import io.shinhanlife.dap.lib.annotation.McpFunction; -import io.shinhanlife.dap.lib.annotation.McpTool; +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest; -@McpTool( - routingType = "DIRECT", - categoryKey = "smp" -) public interface TeamMemberUseCase { - @McpFunction( - displayName = "신한라이프 MCP, TOOL 파트 구성원 조회", - name = "oth.smp.team.list", - description = "신한라이프 MCP, TOOL 파트 구성원을 조회합니다.", - prompt = "신한라이프 MCP, TOOL 파트 구성원을 조회해 줘. (주의: 응답 시 ** 등 마크다운 기호를 절대 사용하지 말고 평문으로만 출력해 줘)", - mappingId = "DIRECT0001", - register = false, - requiresApproval = false, - openWorldHint = true - ) + @McpTool(name = "oth.smp.team.list", title = "신한라이프 MCP, TOOL 파트 구성원 조회", description = "신한라이프 MCP, TOOL 파트 구성원을 조회합니다.", annotations = @McpTool.McpAnnotations(openWorldHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "smp", mappingId = "DIRECT0001") Object execute(TeamMemberRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java index cd929b31..1252d6a4 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java @@ -1,18 +1,12 @@ package io.shinhanlife.dap.mcc.biz.smp.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; + +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.smp.dto.*; -@McpTool( - routingType = "DIRECT", - categoryKey = "smp" -) public interface WeatherToolUseCase { - @McpFunction(register = false, displayName = "날씨 조회 툴", name = "oth.smp.weather.inquiry", - description = "특정 도시의 현재 날씨, 온도, 풍속 정보를 조회합니다.", - prompt = "서울 날씨 알려줘, 부산 기온 알려줘 등 실시간 기상 조회", - mappingId = "WEATHER_001" - ) + @McpTool(name = "oth.smp.weather.inquiry", title = "날씨 조회 툴", description = "특정 도시의 현재 날씨, 온도, 풍속 정보를 조회합니다.") + @ToolHint(register = false, categoryKey = "smp", mappingId = "WEATHER_001") WeatherResponse execute(WeatherRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java index af04360a..98d30aa0 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java @@ -1,8 +1,11 @@ package io.shinhanlife.dap.mcc.biz.sol.dto; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import com.fasterxml.jackson.annotation.JsonInclude; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; /** @@ -23,7 +26,8 @@ import lombok.Data; @JsonInclude(JsonInclude.Include.NON_NULL) public class SolReqDetailRequest { - @McpParameter(description = "상세 조회할 SOL 의뢰서 ID", required = true) - @McpValidation(examples = {"SR20260805"}) + @McpToolParam(description = "상세 조회할 SOL 의뢰서 ID", required = true) + @Schema(example = "SR20260805") private String srId; + } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListRequest.java index 377b27c7..517418ab 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListRequest.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListRequest.java @@ -1,8 +1,11 @@ package io.shinhanlife.dap.mcc.biz.sol.dto; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import com.fasterxml.jackson.annotation.JsonInclude; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Data; /** @@ -22,14 +25,15 @@ import lombok.Data; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class SolReqListRequest { - @McpParameter(description = "진행상태 (예: 진행중, 완료 등)", required = false) - @McpValidation(examples = {"RECEIVED"}) + @McpToolParam(description = "진행상태 (예: 진행중, 완료 등)", required = false) + @Schema(example = "RECEIVED") private String status; - @McpParameter(description = "조회기간 (예: 1개월, 3개월 등)", required = false) + +@McpToolParam(description = "조회기간 (예: 1개월, 3개월 등)", required = false) private String period; - @McpParameter(description = "조회대상 (예: 나의 업무, 전체 등)", required = false) - @McpValidation(examples = {"USER"}) + @McpToolParam(description = "조회대상 (예: 나의 업무, 전체 등)", required = false) + @Schema(example = "USER") private String target; } \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java index 7ec6ac38..4567e68a 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java @@ -1,7 +1,8 @@ package io.shinhanlife.dap.mcc.biz.sol.usecase; -import io.shinhanlife.dap.lib.annotation.McpFunction; -import io.shinhanlife.dap.lib.annotation.McpTool; +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; /** @@ -18,22 +19,9 @@ import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; * * */ -@McpTool( - routingType = "MCI", - categoryKey = "sol" -) public interface SolReqDetailUseCase { - @McpFunction( - displayName = "SolReqDetail 툴", - name = "oth.sol.request.detail", - description = "SOL 의뢰서 상세 조회", - prompt = "SOL 의뢰서 상세 조회해줘", - mappingId = "SOLG00000002", - register = false, - requiresApproval = false, - readOnlyHint = true, - openWorldHint = true - ) + @McpTool(name = "oth.sol.request.detail", title = "SolReqDetail 툴", description = "SOL 의뢰서 상세 조회", annotations = @McpTool.McpAnnotations(openWorldHint = true, readOnlyHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "sol", mappingId = "SOLG00000002") Object execute(SolReqDetailRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java index 1a1e9caa..00a7c491 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java @@ -1,23 +1,12 @@ package io.shinhanlife.dap.mcc.biz.sol.usecase; -import io.shinhanlife.dap.lib.annotation.McpFunction; -import io.shinhanlife.dap.lib.annotation.McpTool; +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest; -@McpTool( - routingType = "MCI", - categoryKey = "sol" -) public interface SolReqListUseCase { - @McpFunction( - displayName = "SolReqList 툴", - name = "oth.sol.request.list", - description = "SOL 의뢰서 목록 조회해줘", - prompt = "SOL 의뢰서 목록 조회해줘", - mappingId = "SOLG00000001", - register = false, - requiresApproval = false, - openWorldHint = true - ) + @McpTool(name = "oth.sol.request.list", title = "SolReqList 툴", description = "SOL 의뢰서 목록 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "sol", mappingId = "SOLG00000001") Object execute(SolReqListRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/oth/DapWasOthApplication.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/oth/DapWasOthApplication.java index e28f3517..4b831fb1 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/oth/DapWasOthApplication.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/oth/DapWasOthApplication.java @@ -19,10 +19,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Import; +import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration; @SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) @ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) @EnableCaching +@Import(ToolMcpServerConfiguration.class) public class DapWasOthApplication { public static void main(String[] args) { SpringApplication.run(DapWasOthApplication.class, args); diff --git a/dap-was-oth/src/main/resources/mcp/schema/claim-search-resource-input-schema.json b/dap-was-oth/src/main/resources/mcp/schema/claim-search-resource-input-schema.json new file mode 100644 index 00000000..56994fb2 --- /dev/null +++ b/dap-was-oth/src/main/resources/mcp/schema/claim-search-resource-input-schema.json @@ -0,0 +1,49 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "claimNo": { + "type": "string", + "description": "청구번호. CLM 이후 숫자 13자리 형식입니다.", + "pattern": "^CLM[0-9]{13}$", + "example": "CLM2026070100123" + }, + "contractNo": { + "type": "string", + "description": "계약번호. 숫자 11자리 형식입니다.", + "pattern": "^[0-9]{11}$", + "example": "10023456789" + }, + "status": { + "type": "string", + "description": "청구 상태 필터", + "enum": ["RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED", "APPROVED", "PAID", "REJECTED", "WITHDRAWN"], + "example": "RECEIVED" + }, + "claimType": { + "type": "string", + "description": "청구 유형 필터", + "enum": ["MEDICAL", "SURGERY", "HOSPITALIZATION", "DIAGNOSIS", "DEATH", "DISABILITY"], + "example": "MEDICAL" + }, + "fromDate": { + "type": "string", + "description": "접수일 조회 시작일 (YYYY-MM-DD)", + "format": "date", + "example": "2026-01-01" + }, + "toDate": { + "type": "string", + "description": "접수일 조회 종료일 (YYYY-MM-DD)", + "format": "date", + "example": "2026-07-31" + }, + "size": { + "type": "string", + "description": "반환할 최대 건수 (기본값: 20, 최대: 50)", + "pattern": "^[1-9][0-9]?$|^50$", + "default": "20", + "example": "20" + } + } +} diff --git a/dap-was-oth/src/main/resources/mcp/schema/claim-search-resource-output-schema.json b/dap-was-oth/src/main/resources/mcp/schema/claim-search-resource-output-schema.json new file mode 100644 index 00000000..b7844be3 --- /dev/null +++ b/dap-was-oth/src/main/resources/mcp/schema/claim-search-resource-output-schema.json @@ -0,0 +1,83 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "resultCode": { + "type": "string", + "description": "실행 결과 코드", + "enum": ["SUCCESS", "FAILURE"] + }, + "resultLabel": { + "type": "string", + "description": "resultCode의 사용자 표시 라벨", + "maxLength": 100 + }, + "status": { + "type": "string", + "description": "현재 청구 처리 상태 코드", + "enum": ["RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED", "APPROVED", "PAID", "REJECTED", "WITHDRAWN"] + }, + "statusLabel": { + "type": "string", + "description": "status의 사용자 표시 라벨", + "maxLength": 100 + }, + "approvedAmount": { + "anyOf": [ + {"type": "integer", "minimum": 0}, + {"type": "null"} + ], + "description": "승인 금액. 심사 전에는 null이며 0으로 해석하지 않습니다." + }, + "rejectionReason": { + "anyOf": [ + {"type": "string", "maxLength": 200}, + {"type": "null"} + ], + "description": "status가 REJECTED일 때만 존재하며, 그 외에는 null입니다." + }, + "items": { + "type": "array", + "description": "수령일 내림차순으로 정렬된 청구 요약 목록", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "description": "청구 처리 상태 코드" + }, + "statusLabel": { + "type": "string", + "description": "status의 사용자 표시 라벨" + }, + "receivedDate": { + "type": "string", + "description": "접수일 (YYYY-MM-DD 형식)", + "format": "date" + }, + "approvedAmount": { + "anyOf": [ + {"type": "integer", "minimum": 0}, + {"type": "null"} + ], + "description": "승인 금액. 심사 전에는 null" + } + }, + "required": ["receivedDate"] + } + }, + "hasMore": { + "type": "boolean", + "description": "이 응답 이후 추가 결과가 있는지 여부" + }, + "totalCount": { + "anyOf": [ + {"type": "integer", "minimum": 0}, + {"type": "null"} + ], + "description": "매칭된 청구 건수 합계" + } + }, + "required": ["resultCode", "resultLabel", "status", "statusLabel", "items", "hasMore"] +} diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequestSchemaTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequestSchemaTest.java index aad02162..10f92c57 100644 --- a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequestSchemaTest.java +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequestSchemaTest.java @@ -4,15 +4,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.databind.ObjectMapper; -import io.shinhanlife.dap.lib.annotation.McpFunction; import io.shinhanlife.dap.lib.util.JsonSchemaGenerator; import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.cmm.usecase.impl.ClaimSearchSchemaSampleUseCaseImpl; import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator;import io.shinhanlife.dap.mcc.biz.cmm.usecase.ClaimSearchSchemaSampleUseCase; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.springframework.ai.mcp.annotation.McpTool; class ClaimSearchRequestSchemaTest { @@ -34,10 +35,11 @@ class ClaimSearchRequestSchemaTest { void resolvesSchemaFromToolModuleResource() throws Exception { Method method = ClaimSearchSchemaSampleUseCase.class .getDeclaredMethod("search", ClaimSearchRequest.class); - McpFunction function = method.getAnnotation(McpFunction.class); + McpTool function = method.getAnnotation(McpTool.class); + ToolHint hint = method.getAnnotation(ToolHint.class); Map schema = new ToolSchemaResolver(new ObjectMapper()) - .resolve(function, ClaimSearchRequest.class); + .resolve(function, hint, ClaimSearchRequest.class); assertEquals("https://json-schema.org/draft/2020-12/schema", schema.get("$schema")); assertTrue(schema.containsKey("anyOf")); @@ -48,8 +50,10 @@ class ClaimSearchRequestSchemaTest { void resolvesOutputSchemaFromToolModuleResource() throws Exception { Method method = ClaimSearchSchemaSampleUseCase.class .getDeclaredMethod("search", ClaimSearchRequest.class); - McpFunction function = method.getAnnotation(McpFunction.class); - Map schema = new ToolSchemaResolver(new ObjectMapper()).resolveOutput(function); + McpTool function = method.getAnnotation(McpTool.class); + ToolHint hint = method.getAnnotation(ToolHint.class); + Map schema = new ToolSchemaResolver(new ObjectMapper()) + .resolveOutput(function, ClaimSearchResponse.class, hint); assertEquals("https://json-schema.org/draft/2020-12/schema", schema.get("$schema")); assertTrue(properties(schema).containsKey("resultCode")); assertTrue(properties(schema).containsKey("statusLabel")); @@ -62,7 +66,8 @@ class ClaimSearchRequestSchemaTest { Method method = ClaimSearchSchemaSampleUseCase.class .getDeclaredMethod("search", ClaimSearchRequest.class); Map schema = new ToolSchemaResolver(new ObjectMapper()) - .resolveOutput(method.getAnnotation(McpFunction.class)); + .resolveOutput(method.getAnnotation(McpTool.class), ClaimSearchResponse.class, + method.getAnnotation(ToolHint.class)); ClaimSearchResponse response = new ClaimSearchSchemaSampleUseCaseImpl().search(new ClaimSearchRequest()); ToolArgumentSchemaValidator validator = new ToolArgumentSchemaValidator(new ObjectMapper()); diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/dto/SmsSendRequest.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/dto/SmsSendRequest.java index 91898078..32bcdbeb 100644 --- a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/dto/SmsSendRequest.java +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/dto/SmsSendRequest.java @@ -1,11 +1,14 @@ package io.shinhanlife.dap.mcc.biz.sms.dto; + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.ai.mcp.annotation.McpToolParam; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; -import io.shinhanlife.dap.lib.annotation.McpParameter; -import io.shinhanlife.dap.lib.annotation.McpValidation; import lombok.Getter; import lombok.Setter; @@ -30,12 +33,13 @@ import lombok.Setter; @AllArgsConstructor public class SmsSendRequest { - @McpParameter(description = "수신자 전화번호", required = true) - @McpValidation(pattern = "^01[0-9]-?\\d{3,4}-?\\d{4}$", examples = {"01012345678"}) + @McpToolParam(description = "수신자 전화번호", required = true) + @Schema(pattern = "^01[0-9]-?\\d{3,4}-?\\d{4}$", example = "01012345678") private String phoneNumber; - @McpParameter(description = "전송할 메시지 내용", required = true) - @McpValidation(pattern = "\\S", examples = {"테스트 메시지입니다."}) + +@McpToolParam(description = "전송할 메시지 내용", required = true) + @Schema(pattern = "\\S", example = "테스트 메시지입니다.") private String message; diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/usecase/SmsToolUseCase.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/usecase/SmsToolUseCase.java index 74f28f74..0c544e8f 100644 --- a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/usecase/SmsToolUseCase.java +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/usecase/SmsToolUseCase.java @@ -1,10 +1,11 @@ package io.shinhanlife.dap.mcc.biz.sms.usecase; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.annotation.McpFunction; +import org.springframework.ai.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; import io.shinhanlife.dap.mcc.biz.sms.dto.*; -@McpTool(routingType = "EAI", categoryKey = "notification") public interface SmsToolUseCase { + @McpTool(name = "sms.send", title = "SMS 발송 툴", description = "SMS 발송 기능을 제공합니다.") + @ToolHint(register = false, categoryKey = "notification", mappingId = "SMS_SEND") Object sendSms(SmsSendRequest req); } diff --git a/docker-compose.yml b/docker-compose.yml index 6cc43e68..b241d65f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +31,9 @@ services: - AXHUB_SOURCE_DIR=/src - JAVA_TOOL_OPTIONS=-Xms64m -Xmx384m - SPRING_PROFILES_ACTIVE=${ACTIVE_PROFILE:-local} + - MCP_GATEWAY_FALLBACK_DEFAULT_URL=http://tool-oth:8084 + - MCP_GATEWAY_FALLBACK_ROUTES_SMS=http://tool-sms:8082 + - MCP_GATEWAY_FALLBACK_ROUTES_HR=http://tool-oth:8084 - MCP_GATEWAY_FALLBACK_DEFAULT_URL=http://was-oth:8084 - MCP_GATEWAY_FALLBACK_ROUTES_SMS=http://was-sms:8082 - MCP_GATEWAY_FALLBACK_ROUTES_HR=http://was-oth:8084