feat: support explicit MCP input schemas
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 2m18s

This commit is contained in:
jade
2026-08-04 10:50:25 +09:00
parent 52f585fa45
commit 2957272cc3
16 changed files with 429 additions and 19 deletions

View File

@@ -0,0 +1,57 @@
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 lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 보험금 청구 조회 Tool의 입력 DTO 샘플이다.
* 청구번호 또는 계약번호 중 하나를 반드시 입력받는다.
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@McpAnyOf({"claimNo", "contractNo"})
public class ClaimSearchRequest {
@McpParameter(description = "청구번호. CLM 다음 숫자 13자리 형식이다.")
@McpValidation(pattern = "^CLM[0-9]{13}$", examples = {"CLM2026070100123"})
private String claimNo;
@McpParameter(description = "계약번호. 숫자 11자리 형식이다.")
@McpValidation(pattern = "^[0-9]{11}$", examples = {"10023456789"})
private String contractNo;
@McpParameter(description = "청구 상태 필터")
@McpValidation(allowedValues = {
"RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED",
"APPROVED", "PAID", "REJECTED", "WITHDRAWN"
})
private String status;
@McpParameter(description = "청구 유형 필터")
@McpValidation(allowedValues = {
"MEDICAL", "SURGERY", "HOSPITALIZATION",
"DIAGNOSIS", "DEATH", "DISABILITY"
})
private String claimType;
@McpParameter(description = "접수일 조회 시작일(YYYY-MM-DD)")
@McpValidation(format = "date", examples = {"2026-01-01"})
private String fromDate;
@McpParameter(description = "접수일 조회 종료일(YYYY-MM-DD)")
@McpValidation(format = "date", examples = {"2026-07-31"})
private String toDate;
@McpParameter(description = "반환할 최대 건수")
@McpValidation(minimum = 1, maximum = 50, defaultValue = "20")
private Integer size;
}

View File

@@ -0,0 +1,24 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
import io.shinhanlife.dap.lib.annotation.McpFunction;
import io.shinhanlife.dap.lib.annotation.McpTool;
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
@McpTool(
routingType = "DIRECT",
categoryKey = "cmm"
)
public interface ClaimSearchSchemaSampleUseCase {
@McpFunction(
register = false,
displayName = "청구 조회 JSON Schema 샘플",
name = "sample.claim.search.resource",
description = "inputSchemaResource를 사용하는 청구 조회 Tool 샘플입니다.",
prompt = "청구번호 또는 계약번호로 보험금 청구를 조회해줘.",
inputSchemaResource = "classpath:tool-schemas/claim-search-resource-input-schema.json",
readOnlyHint = true,
idempotentHint = true
)
Object search(ClaimSearchRequest request);
}

View File

@@ -0,0 +1,22 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
import io.shinhanlife.dap.mcc.biz.cmm.usecase.ClaimSearchSchemaSampleUseCase;
import java.util.Map;
import org.springframework.stereotype.Service;
/**
* inputSchemaResource 적용 방식을 보여주는 비노출 샘플 Tool이다.
* 실제 MCI/EIMS 연동은 추가하지 않는다.
*/
@Service
public class ClaimSearchSchemaSampleUseCaseImpl implements ClaimSearchSchemaSampleUseCase {
@Override
public Object search(ClaimSearchRequest request) {
return Map.of(
"message", "inputSchemaResource JSON Schema sample",
"request", request == null ? Map.of() : request
);
}
}

View File

@@ -0,0 +1,37 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"claimNo": {
"type": "string",
"description": "청구번호. CLM 다음 숫자 13자리 형식이다.",
"pattern": "^CLM[0-9]{13}$",
"examples": ["CLM2026070100123"]
},
"contractNo": {
"type": "string",
"description": "계약번호. 숫자 11자리 형식이다.",
"pattern": "^[0-9]{11}$",
"examples": ["10023456789"]
},
"status": {
"type": "string",
"enum": [
"RECEIVED", "REVIEWING", "ADDITIONAL_DOC_REQUIRED",
"APPROVED", "PAID", "REJECTED", "WITHDRAWN"
]
},
"size": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 20
}
},
"required": [],
"additionalProperties": false,
"anyOf": [
{ "required": ["claimNo"] },
{ "required": ["contractNo"] }
]
}

View File

@@ -0,0 +1,31 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.shinhanlife.dap.lib.util.JsonSchemaGenerator;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
class ClaimSearchRequestSchemaTest {
@Test
void generatesClaimOrContractSearchSchema() {
Map<String, Object> schema = JsonSchemaGenerator.generateSchema(ClaimSearchRequest.class);
Map<String, Map<String, Object>> properties = properties(schema);
assertEquals(false, schema.get("additionalProperties"));
assertEquals("^CLM[0-9]{13}$", properties.get("claimNo").get("pattern"));
assertEquals(50L, properties.get("size").get("maximum"));
assertEquals(20L, properties.get("size").get("default"));
assertEquals(List.of(
Map.of("required", List.of("claimNo")),
Map.of("required", List.of("contractNo"))), schema.get("anyOf"));
}
@SuppressWarnings("unchecked")
private Map<String, Map<String, Object>> properties(Map<String, Object> schema) {
return (Map<String, Map<String, Object>>) schema.get("properties");
}
}