From 5fe8590695c53ff81cf8888716f37eb1c78ef68b Mon Sep 17 00:00:00 2001 From: jade Date: Wed, 29 Jul 2026 23:03:11 +0900 Subject: [PATCH] feat: use MCP SDK for tool pod calls --- .../dap/mcg/service/ExecuteService.java | 11 +- .../dap/mcg/transport/HttpToolInvoker.java | 102 ++++++++++-------- .../dap/mcg/transport/ToolInvoker.java | 2 +- dap-tool-core/build.gradle | 3 +- .../mcc/mcp/ToolMcpServerConfiguration.java | 26 +++++ .../mcc/mcp/ToolPodMcpToolSynchronizer.java | 74 +++++++++++++ .../presentation/BusinessToolController.java | 19 ++-- .../ToolArgumentSchemaValidator.java | 28 +++++ .../dap/lib/util/JsonSchemaGeneratorTest.java | 16 +-- .../mcp/ToolMcpServerConfigurationTest.java | 22 ++++ .../ToolArgumentSchemaValidatorTest.java | 25 +++++ 11 files changed, 254 insertions(+), 74 deletions(-) create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java create mode 100644 dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java create mode 100644 dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java index dfb7384f..e4c51559 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java @@ -261,7 +261,6 @@ public class ExecuteService { if (metadata.getPodUrl() != null && !metadata.getPodUrl().isEmpty()) { targetUrl = metadata.getPodUrl(); } - String executeApiUrl = targetUrl + "/mcp/" + metadata.getName(); Map headers = new java.util.HashMap<>(); headers.put("trace-id", context.requestId()); @@ -279,16 +278,16 @@ public class ExecuteService { JsonNode data = null; try { - data = toolInvoker.invoke(pagePayload, executeApiUrl, headers); + data = toolInvoker.invoke(metadata.getName(), pagePayload, targetUrl, headers); } catch (org.springframework.web.client.RestClientResponseException e) { // HTTP 4xx, 5xx 에러는 연결 오류가 아니라 비즈니스 로직 오류이거나 검증 실패이므로 원본 에러를 그대로 반환 throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod HTTP 에러 (" + e.getStatusCode() + "): " + e.getResponseBodyAsString()); } catch (Exception e) { - if (executeApiUrl.contains("http://tool-")) { - String fallbackUrl = executeApiUrl.replaceAll("http://tool-[a-zA-Z0-9-]+", "http://localhost"); + if (targetUrl.contains("http://tool-")) { + String fallbackUrl = targetUrl.replaceAll("http://tool-[a-zA-Z0-9-]+", "http://localhost"); log.warn(" [ExecuteService] 호스트를 찾을 수 없어 localhost로 재시도합니다: {}", fallbackUrl); try { - data = toolInvoker.invoke(pagePayload, fallbackUrl, headers); + data = toolInvoker.invoke(metadata.getName(), pagePayload, fallbackUrl, headers); } catch (Exception ex) { throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod 호출 실패 (localhost 재시도 포함): " + ex.getMessage()); } @@ -385,4 +384,4 @@ public class ExecuteService { public void shutdown() { executor.shutdownNow(); } -} \ No newline at end of file +} diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/HttpToolInvoker.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/HttpToolInvoker.java index 54445066..b6827dd5 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/HttpToolInvoker.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/HttpToolInvoker.java @@ -1,69 +1,81 @@ package io.shinhanlife.dap.mcg.transport; - -/** - * @package io.shinhanlife.dap.mcg.transport - * @className HttpToolInvoker - * @description AX HUB 시스템 처리 클래스 - * @author 0986406 - * @create 2026.09.01 - *
- * ---------- 개정이력 ----------
- * 수정일      수정자    수정내용
- * ---------- -------- ---------------------------
- * 2026.09.01  0986406    최초생성
- * 
- * 
- */ -import io.shinhanlife.dap.mcg.resilience.FailureType; -import io.shinhanlife.dap.mcg.resilience.ToolExecutionException; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestClient; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; - +import com.fasterxml.jackson.databind.node.TextNode; +import io.modelcontextprotocol.client.McpClient; +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; +import io.modelcontextprotocol.spec.McpSchema; +import io.shinhanlife.dap.mcg.resilience.FailureType; +import io.shinhanlife.dap.mcg.resilience.ToolExecutionException; +import java.net.http.HttpRequest; +import java.time.Duration; import java.util.Map; -import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +/** MCP SDK client used for Gateway-to-Tool-Pod calls over Streamable HTTP. */ @Slf4j @Component public class HttpToolInvoker implements ToolInvoker { - private final RestClient restClient; private final ObjectMapper objectMapper; public HttpToolInvoker(ObjectMapper objectMapper) { this.objectMapper = objectMapper; - this.restClient = RestClient.create(); } @Override - public JsonNode invoke(Map payload, String targetUrl, Map headers) { - try { - RestClient.RequestBodySpec requestSpec = restClient.post() - .uri(targetUrl) - .contentType(MediaType.APPLICATION_JSON); - - if (headers != null) { - headers.forEach(requestSpec::header); + public JsonNode invoke(String toolName, Map arguments, String podUrl, Map headers) { + String endpoint = podUrl.replaceAll("/+$", "") + "/mcp"; + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(); + if (headers != null) { + headers.forEach(requestBuilder::header); + } + + HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(endpoint) + .requestBuilder(requestBuilder) + .connectTimeout(Duration.ofSeconds(5)) + .build(); + try (McpSyncClient client = McpClient.sync(transport) + .clientInfo(new McpSchema.Implementation("dap-gateway", "1.0.0")) + .requestTimeout(Duration.ofSeconds(30)) + .build()) { + client.initialize(); + McpSchema.CallToolResult result = client.callTool(McpSchema.CallToolRequest.builder() + .name(toolName) + .arguments(arguments) + .build()); + if (Boolean.TRUE.equals(result.isError())) { + throw new ToolExecutionException(FailureType.BUSINESS_ERROR, "Tool Pod MCP error: " + textContent(result)); } - - Object httpResult = requestSpec.body(payload) - .retrieve() - .body(Object.class); - - return extractData(objectMapper.valueToTree(httpResult)); - } catch (Exception e) { - throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod HTTP 호출 실패: " + e.getMessage(), e); + return result.structuredContent() != null + ? objectMapper.valueToTree(result.structuredContent()) + : textContentAsJson(result); + } catch (ToolExecutionException error) { + throw error; + } catch (Exception error) { + throw new ToolExecutionException(FailureType.SERVER_ERROR, + "Tool Pod MCP call failed: " + error.getMessage(), error); } } - private JsonNode extractData(JsonNode root) { - if (!root.path("success").asBoolean(true)) { - throw new ToolExecutionException(FailureType.BUSINESS_ERROR, "Tool 서버 업무 오류: " + root.path("error").asText()); + private JsonNode textContentAsJson(McpSchema.CallToolResult result) { + String text = textContent(result); + try { + return objectMapper.readTree(text); + } catch (Exception ignored) { + return TextNode.valueOf(text); } - return root.has("data") ? root.get("data") : root; + } + + private String textContent(McpSchema.CallToolResult result) { + return result.content().stream() + .filter(McpSchema.TextContent.class::isInstance) + .map(McpSchema.TextContent.class::cast) + .map(McpSchema.TextContent::text) + .findFirst() + .orElse(""); } } diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/ToolInvoker.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/ToolInvoker.java index 74e2db22..649638d1 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/ToolInvoker.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/transport/ToolInvoker.java @@ -22,5 +22,5 @@ import java.util.Map; * Tool 서버 호출 transport의 최소 공통 인터페이스입니다. */ public interface ToolInvoker { - JsonNode invoke(Map payload, String targetUrl, Map headers); + JsonNode invoke(String toolName, Map arguments, String podUrl, Map headers); } diff --git a/dap-tool-core/build.gradle b/dap-tool-core/build.gradle index 66471abf..97785ca4 100644 --- a/dap-tool-core/build.gradle +++ b/dap-tool-core/build.gradle @@ -4,6 +4,7 @@ plugins { dependencies { api 'org.springframework.boot:spring-boot-starter-web' + api 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc' api 'org.springframework.boot:spring-boot-starter-validation' api 'org.springframework.boot:spring-boot-starter-data-redis' @@ -24,7 +25,7 @@ dependencies { api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1' api 'com.fasterxml.jackson.core:jackson-databind:2.17.1' - api 'com.networknt:json-schema-validator:1.4.0' + api 'com.networknt:json-schema-validator:3.0.0' api 'org.springframework.kafka:spring-kafka:3.2.0' api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' } diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java new file mode 100644 index 00000000..b07916ab --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java @@ -0,0 +1,26 @@ +package io.shinhanlife.dap.mcc.mcp; + +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +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; + +/** Exposes every Tool Pod through the MCP Streamable HTTP transport. */ +@Configuration +public class ToolMcpServerConfiguration { + + @Bean + @Primary + public HttpServletStreamableServerTransportProvider toolMcpTransportProvider() { + return HttpServletStreamableServerTransportProvider.builder() + .mcpEndpoint("/mcp") + .build(); + } + + @Bean + public ServletRegistrationBean toolMcpServlet( + HttpServletStreamableServerTransportProvider transportProvider) { + return new ServletRegistrationBean<>(transportProvider, "/mcp"); + } +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java new file mode 100644 index 00000000..4fe9ca75 --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java @@ -0,0 +1,74 @@ +package io.shinhanlife.dap.mcc.mcp; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.server.McpServerFeatures; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.spec.McpSchema; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import io.shinhanlife.dap.mcc.presentation.BusinessToolController; +import io.shinhanlife.dap.mcc.usecase.ToolRegistryHeartbeatSender; +import java.util.LinkedHashMap; +import java.util.Map; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +/** Registers the Tool Pod's existing annotated tools with its MCP SDK server. */ +@Component +public class ToolPodMcpToolSynchronizer { + private final McpSyncServer mcpServer; + private final ToolRegistryHeartbeatSender heartbeatSender; + private final BusinessToolController businessToolController; + private final ObjectMapper objectMapper; + + public ToolPodMcpToolSynchronizer(McpSyncServer mcpServer, ToolRegistryHeartbeatSender heartbeatSender, + BusinessToolController businessToolController, ObjectMapper objectMapper) { + this.mcpServer = mcpServer; + this.heartbeatSender = heartbeatSender; + this.businessToolController = businessToolController; + this.objectMapper = objectMapper; + } + + @EventListener(ApplicationReadyEvent.class) + public void registerLocalTools() { + heartbeatSender.getAllScannedTools().stream() + .filter(tool -> Boolean.TRUE.equals(tool.getVisible())) + .forEach(tool -> mcpServer.addTool(specification(tool))); + } + + private McpServerFeatures.SyncToolSpecification specification(ToolMetadata tool) { + McpSchema.Tool mcpTool = McpSchema.Tool.builder() + .name(tool.getName()) + .description(tool.getDescription() == null || tool.getDescription().isBlank() ? tool.getName() + " Tool" : tool.getDescription()) + .inputSchema(tool.getParametersSchema() == null ? emptySchema() : tool.getParametersSchema()) + .annotations(McpSchema.ToolAnnotations.builder() + .readOnlyHint(Boolean.TRUE.equals(tool.getReadOnlyHint())) + .destructiveHint(Boolean.TRUE.equals(tool.getDestructiveHint())) + .idempotentHint(Boolean.TRUE.equals(tool.getIdempotentHint())) + .openWorldHint(Boolean.TRUE.equals(tool.getOpenWorldHint())).build()) + .build(); + return McpServerFeatures.SyncToolSpecification.builder().tool(mcpTool) + .callHandler((context, request) -> invoke(tool.getName(), request.arguments())).build(); + } + + private McpSchema.CallToolResult invoke(String toolName, Map arguments) { + ResponseEntity response = businessToolController.executeDynamicTool(toolName, null, null, null, arguments); + boolean failed = !response.getStatusCode().is2xxSuccessful(); + Object body = response.getBody(); + try { + return McpSchema.CallToolResult.builder().addTextContent(objectMapper.writeValueAsString(body)) + .structuredContent(body).isError(failed).build(); + } catch (Exception error) { + return McpSchema.CallToolResult.builder().addTextContent(String.valueOf(body)).isError(failed).build(); + } + } + + private Map emptySchema() { + Map schema = new LinkedHashMap<>(); + schema.put("type", "object"); + schema.put("properties", Map.of()); + schema.put("additionalProperties", false); + return schema; + } +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java index 3c722d3d..64bde2c2 100644 --- a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java @@ -16,10 +16,7 @@ package io.shinhanlife.dap.mcc.presentation; * */ import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; +import com.networknt.schema.Error; import io.shinhanlife.dap.lib.annotation.McpFunction; import io.shinhanlife.dap.lib.annotation.McpTool; import io.shinhanlife.dap.lib.config.McpProperties; @@ -58,6 +55,7 @@ public class BusinessToolController { private final ObjectMapper objectMapper; private final McpProperties mcpProperties; private final ToolRegistryHeartbeatSender toolRegistryHeartbeatSender; + private final ToolArgumentSchemaValidator toolArgumentSchemaValidator; // 내부 조회용 로컬 Tool 목록 엔드포인트 @GetMapping("/mcp/api/v1/tools/local") @@ -149,17 +147,12 @@ public class BusinessToolController { if (!Map.class.isAssignableFrom(paramType)) { try { Map autoSchema = JsonSchemaGenerator.generateSchema(paramType); - String fullSchemaJson = objectMapper.writeValueAsString(autoSchema); - - JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - JsonSchema schema = factory.getSchema(fullSchemaJson); - - Set errors = schema.validate(objectMapper.valueToTree(arguments)); + List errors = toolArgumentSchemaValidator.validate(autoSchema, arguments); if (!errors.isEmpty()) { log.error("[Tool] 파라미터 유효성 검증 실패: {}", errors); List errorMessages = new ArrayList<>(); - for (ValidationMessage vm : errors) { - errorMessages.add(vm.getMessage()); + for (Error validationError : errors) { + errorMessages.add(validationError.getMessage()); } Map errorDetails = new HashMap<>(); errorDetails.put("status", "422"); @@ -229,4 +222,4 @@ public class BusinessToolController { return ResponseEntity.status(502).body(errorBody); } } -} \ No newline at end of file +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java new file mode 100644 index 00000000..876144ca --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidator.java @@ -0,0 +1,28 @@ +package io.shinhanlife.dap.mcc.presentation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import java.util.List; +import java.util.Map; +import org.springframework.stereotype.Component; + +/** Validates tool arguments with the NetworkNT version selected by the MCP SDK. */ +@Component +public class ToolArgumentSchemaValidator { + + private final ObjectMapper objectMapper; + + public ToolArgumentSchemaValidator(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public List validate(Map schemaDefinition, Map arguments) throws Exception { + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = schemaRegistry.getSchema(objectMapper.writeValueAsString(schemaDefinition)); + return schema.validate(objectMapper.writeValueAsString(arguments), InputFormat.JSON); + } +} diff --git a/dap-tool-core/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java b/dap-tool-core/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java index c7e648a8..55eaf59e 100644 --- a/dap-tool-core/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java +++ b/dap-tool-core/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java @@ -5,15 +5,15 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; +import com.networknt.schema.Error; +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 java.util.Set; import org.junit.jupiter.api.Test; /** @@ -65,10 +65,10 @@ class JsonSchemaGeneratorTest { @Test void validatorRejectsInvalidNestedValue() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); - JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7) + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7) .getSchema(objectMapper.writeValueAsString(JsonSchemaGenerator.generateSchema(NestedRequest.class))); - Set errors = schema.validate(objectMapper.valueToTree(Map.of( - "child", Map.of("businessDate", "2026-07-28")))); + List errors = schema.validate(objectMapper.writeValueAsString(Map.of( + "child", Map.of("businessDate", "2026-07-28"))), InputFormat.JSON); assertFalse(errors.isEmpty()); } diff --git a/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java new file mode 100644 index 00000000..c0fb8101 --- /dev/null +++ b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java @@ -0,0 +1,22 @@ +package io.shinhanlife.dap.mcc.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.servlet.ServletRegistrationBean; + +class ToolMcpServerConfigurationTest { + + private final ToolMcpServerConfiguration configuration = new ToolMcpServerConfiguration(); + + @Test + void exposesOnlyExactMcpEndpointSoLegacyMcpApiPathsRemainAvailable() { + HttpServletStreamableServerTransportProvider transport = configuration.toolMcpTransportProvider(); + ServletRegistrationBean registration = configuration.toolMcpServlet(transport); + + assertEquals("/mcp", registration.getUrlMappings().iterator().next()); + assertFalse(registration.getUrlMappings().contains("/mcp/*")); + } +} diff --git a/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java new file mode 100644 index 00000000..e10dcc7d --- /dev/null +++ b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java @@ -0,0 +1,25 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ToolArgumentSchemaValidatorTest { + + private final ToolArgumentSchemaValidator validator = new ToolArgumentSchemaValidator(new ObjectMapper()); + + @Test + void validatesDraft7SchemaWithTheRuntimeNetworkntVersion() throws Exception { + Map schema = Map.of( + "type", "object", + "properties", Map.of("name", Map.of("type", "string")), + "required", List.of("name")); + + assertTrue(validator.validate(schema, Map.of("name", "Hong")).isEmpty()); + assertFalse(validator.validate(schema, Map.of()).isEmpty()); + } +}