feat: validate MCP tool input constraints
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 1s

This commit is contained in:
jade
2026-07-27 13:01:40 +09:00
parent d4953d60ec
commit 63187429b4
8 changed files with 120 additions and 4 deletions

View File

@@ -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 {};
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;