feat: validate MCP tool input constraints
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 1s
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
|
* <pre>
|
||||||
|
* ---------- revision history ----------
|
||||||
|
* date author description
|
||||||
|
* ---------- --------- ---------------------------
|
||||||
|
* 2026.07.27 0986406 initial creation
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface McpValidation {
|
||||||
|
boolean required() default false;
|
||||||
|
String pattern() default "";
|
||||||
|
long minimum() default Long.MIN_VALUE;
|
||||||
|
String[] allowedValues() default {};
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package io.shinhanlife.dap.lib.util;
|
|||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||||
|
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -58,6 +59,20 @@ public class JsonSchemaGenerator {
|
|||||||
if ((jsonProp != null && jsonProp.required()) || (paramAnnotation != null && paramAnnotation.required())) {
|
if ((jsonProp != null && jsonProp.required()) || (paramAnnotation != null && paramAnnotation.required())) {
|
||||||
requiredList.add(field.getName());
|
requiredList.add(field.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
McpValidation validation = field.getAnnotation(McpValidation.class);
|
||||||
|
if (validation != null && validation.required() && !requiredList.contains(field.getName())) {
|
||||||
|
requiredList.add(field.getName());
|
||||||
|
}
|
||||||
|
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.allowedValues().length > 0) {
|
||||||
|
fieldSchema.put("enum", List.of(validation.allowedValues()));
|
||||||
|
}
|
||||||
|
|
||||||
properties.put(field.getName(), fieldSchema);
|
properties.put(field.getName(), fieldSchema);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package io.shinhanlife.dap.lib.util;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.lib.util
|
||||||
|
* @className JsonSchemaGeneratorTest
|
||||||
|
* @description JSON Schema constraint generation test
|
||||||
|
* @author 0986406
|
||||||
|
* @create 2026.07.27
|
||||||
|
* <pre>
|
||||||
|
* ---------- revision history ----------
|
||||||
|
* date author description
|
||||||
|
* ---------- --------- ---------------------------
|
||||||
|
* 2026.07.27 0986406 initial creation
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
class JsonSchemaGeneratorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void includesMcpParameterConstraintsInGeneratedSchema() {
|
||||||
|
Map<String, Object> schema = JsonSchemaGenerator.generateSchema(ValidatedRequest.class);
|
||||||
|
Map<String, Map<String, Object>> properties = properties(schema);
|
||||||
|
|
||||||
|
assertTrue(((List<String>) schema.get("required")).contains("phoneNumber"));
|
||||||
|
assertTrue(((List<String>) schema.get("required")).contains("approvalStatus"));
|
||||||
|
assertEquals("^01[0-9]{8,9}$", properties.get("phoneNumber").get("pattern"));
|
||||||
|
assertEquals(1L, properties.get("amount").get("minimum"));
|
||||||
|
assertEquals(List.of("APPROVE", "REJECT"), properties.get("approvalStatus").get("enum"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Map<String, Map<String, Object>> properties(Map<String, Object> schema) {
|
||||||
|
return (Map<String, Map<String, Object>>) schema.get("properties");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ValidatedRequest {
|
||||||
|
@McpParameter(description = "recipient phone number", required = true)
|
||||||
|
@McpValidation(pattern = "^01[0-9]{8,9}$")
|
||||||
|
private String phoneNumber;
|
||||||
|
|
||||||
|
@McpParameter(description = "issue amount", required = true)
|
||||||
|
@McpValidation(minimum = 1)
|
||||||
|
private Long amount;
|
||||||
|
|
||||||
|
@McpParameter(description = "approval result")
|
||||||
|
@McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"})
|
||||||
|
private String approvalStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,10 +16,12 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||||
|
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class BalanceRequest {
|
public class BalanceRequest {
|
||||||
@McpParameter(description = "고객의 계좌번호 (- 제외)", required = true)
|
@McpParameter(description = "고객의 계좌번호 (- 제외)", required = true)
|
||||||
|
@McpValidation(pattern = "\\S")
|
||||||
private String accountNumber;
|
private String accountNumber;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||||
|
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -30,8 +31,10 @@ import lombok.Setter;
|
|||||||
public class BillingProcessRequest {
|
public class BillingProcessRequest {
|
||||||
|
|
||||||
@McpParameter(description = "처리할 청구 접수 번호", required = true)
|
@McpParameter(description = "처리할 청구 접수 번호", required = true)
|
||||||
|
@McpValidation(pattern = "\\S")
|
||||||
private String billingId;
|
private String billingId;
|
||||||
|
|
||||||
@McpParameter(description = "심사 승인 여부 (예: APPROVE, REJECT)")
|
@McpParameter(description = "심사 승인 여부 (예: APPROVE, REJECT)")
|
||||||
|
@McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"})
|
||||||
private String approvalStatus;
|
private String approvalStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||||
|
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -30,8 +31,10 @@ import lombok.Setter;
|
|||||||
public class BondIssueRequest {
|
public class BondIssueRequest {
|
||||||
|
|
||||||
@McpParameter(description = "발행할 디지털 증권 금액", required = true)
|
@McpParameter(description = "발행할 디지털 증권 금액", required = true)
|
||||||
|
@McpValidation(minimum = 1)
|
||||||
private Long amount;
|
private Long amount;
|
||||||
|
|
||||||
@McpParameter(description = "발행 대상 계좌 번호", required = true)
|
@McpParameter(description = "발행 대상 계좌 번호", required = true)
|
||||||
|
@McpValidation(pattern = "\\S")
|
||||||
private String targetAccount;
|
private String targetAccount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||||
|
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -30,8 +31,10 @@ import lombok.Setter;
|
|||||||
public class VacationRegisterRequest {
|
public class VacationRegisterRequest {
|
||||||
|
|
||||||
@McpParameter(description = "연차를 등록할 사원 번호", required = true)
|
@McpParameter(description = "연차를 등록할 사원 번호", required = true)
|
||||||
|
@McpValidation(pattern = "\\S")
|
||||||
private String employeeId;
|
private String employeeId;
|
||||||
|
|
||||||
@McpParameter(description = "휴가 일자 (YYYY-MM-DD 형식)", required = true)
|
@McpParameter(description = "휴가 일자 (YYYY-MM-DD 형식)", required = true)
|
||||||
|
@McpValidation(pattern = "^\\d{4}-\\d{2}-\\d{2}$")
|
||||||
private String date;
|
private String date;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||||
|
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -30,10 +31,12 @@ import lombok.Setter;
|
|||||||
public class SmsSendRequest {
|
public class SmsSendRequest {
|
||||||
|
|
||||||
@McpParameter(description = "수신자 전화번호", required = true)
|
@McpParameter(description = "수신자 전화번호", required = true)
|
||||||
|
@McpValidation(pattern = "^01[0-9]-?\\d{3,4}-?\\d{4}$")
|
||||||
private String phoneNumber;
|
private String phoneNumber;
|
||||||
|
|
||||||
@McpParameter(description = "전송할 메시지 내용", required = true)
|
@McpParameter(description = "전송할 메시지 내용", required = true)
|
||||||
|
@McpValidation(pattern = "\\S")
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user