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.JsonPropertyDescription;
|
||||
import io.shinhanlife.dap.lib.annotation.McpParameter;
|
||||
import io.shinhanlife.dap.lib.annotation.McpValidation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -59,6 +60,20 @@ 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());
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
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")
|
||||
private String accountNumber;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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,8 +31,10 @@ import lombok.Setter;
|
||||
public class BillingProcessRequest {
|
||||
|
||||
@McpParameter(description = "처리할 청구 접수 번호", required = true)
|
||||
@McpValidation(pattern = "\\S")
|
||||
private String billingId;
|
||||
|
||||
@McpParameter(description = "심사 승인 여부 (예: APPROVE, REJECT)")
|
||||
@McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"})
|
||||
private String approvalStatus;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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,8 +31,10 @@ import lombok.Setter;
|
||||
public class BondIssueRequest {
|
||||
|
||||
@McpParameter(description = "발행할 디지털 증권 금액", required = true)
|
||||
@McpValidation(minimum = 1)
|
||||
private Long amount;
|
||||
|
||||
@McpParameter(description = "발행 대상 계좌 번호", required = true)
|
||||
@McpValidation(pattern = "\\S")
|
||||
private String targetAccount;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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,8 +31,10 @@ import lombok.Setter;
|
||||
public class VacationRegisterRequest {
|
||||
|
||||
@McpParameter(description = "연차를 등록할 사원 번호", required = true)
|
||||
@McpValidation(pattern = "\\S")
|
||||
private String employeeId;
|
||||
|
||||
@McpParameter(description = "휴가 일자 (YYYY-MM-DD 형식)", required = true)
|
||||
@McpValidation(pattern = "^\\d{4}-\\d{2}-\\d{2}$")
|
||||
private String date;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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,9 +31,11 @@ import lombok.Setter;
|
||||
public class SmsSendRequest {
|
||||
|
||||
@McpParameter(description = "수신자 전화번호", required = true)
|
||||
@McpValidation(pattern = "^01[0-9]-?\\d{3,4}-?\\d{4}$")
|
||||
private String phoneNumber;
|
||||
|
||||
@McpParameter(description = "전송할 메시지 내용", required = true)
|
||||
@McpValidation(pattern = "\\S")
|
||||
private String message;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user