From 041f939c334cca7356074fcc6840ec34df86c0f2 Mon Sep 17 00:00:00 2001 From: jade Date: Tue, 11 Aug 2026 10:28:46 +0900 Subject: [PATCH] fix: honor tool registration flag --- .../lib/mcp/ToolRegistryHeartbeatSender.java | 12 ++-- .../dap/lib/util/ToolScaffolder.java | 2 +- .../mcp/ToolRegistryHeartbeatSenderTest.java | 56 +++++++++++++++++++ .../mcc/biz/oth/usecase/Onnba3011UseCase.java | 2 +- 4 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java index d168d6fb..3744cb07 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java @@ -91,11 +91,13 @@ public class ToolRegistryHeartbeatSender { String subToolName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() ? mcpProperties.getNamespace() + "_" + rawSubToolName : rawSubToolName; - - // 요청에 따라 @ToolHint 의 register 값과 상관없이 무조건 등록(true) 처리합니다. - boolean isRegister = true; - if (hintAnnotation != null && !hintAnnotation.register()) { - log.info(" [HeartbeatSender] '{}' 툴은 원래 register=false 이지만 강제로 외부 등록(Redis) 대상에 포함합니다. (최종 이름: {})", baseName, subToolName); + // @ToolHint(register = false)인 Tool은 메타데이터 조회에는 남기되, + // Gateway 등록 및 heartbeat 전송 대상에서는 제외합니다. + // ToolHint가 없는 기존 Tool은 이전 동작과 동일하게 등록합니다. + boolean isRegister = hintAnnotation == null || hintAnnotation.register(); + if (!isRegister) { + log.info(" [HeartbeatSender] '{}' Tool is excluded from Gateway registration because register=false. (tool name: {})", + baseName, subToolName); } ToolMetadata meta = new ToolMetadata(); diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java index c73861ab..c60409ae 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java @@ -81,7 +81,7 @@ public class ToolScaffolder { String inputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-input-schema.json" : null; String outputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-output-schema.json" : null; - String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true, null, inputSchemaResource, outputSchemaResource); + String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, false, null, inputSchemaResource, outputSchemaResource); System.out.println(result); } diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java new file mode 100644 index 00000000..9fb55a49 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java @@ -0,0 +1,56 @@ +package io.shinhanlife.dap.lib.mcp; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.context.ApplicationContext; + +class ToolRegistryHeartbeatSenderTest { + + @Test + void excludesRegisterFalseToolFromGatewayRegistrationTargets() throws Exception { + ApplicationContext applicationContext = mock(ApplicationContext.class); + when(applicationContext.getBeansOfType(Object.class)).thenReturn(Map.of("disabledTool", new DisabledTool())); + ToolRegistryHeartbeatSender sender = new ToolRegistryHeartbeatSender( + applicationContext, new ObjectMapper(), new McpProperties(), mock(ToolSchemaResolver.class)); + + setField(sender, "podUrl", "http://localhost:8084"); + + sender.init(); + + assertThat(sender.getAllScannedTools()).singleElement() + .extracting(tool -> tool.getIsRegistered()) + .isEqualTo(false); + assertThat(registeredTools(sender)).isEmpty(); + } + + private void setField(Object target, String name, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } + @SuppressWarnings("unchecked") + private List registeredTools(ToolRegistryHeartbeatSender sender) throws Exception { + Field field = ToolRegistryHeartbeatSender.class.getDeclaredField("registeredTools"); + field.setAccessible(true); + return (List) field.get(sender); + } + + static class DisabledTool { + + @McpTool(name = "test_disabled_tool") + @ToolHint(register = false) + void execute() { + } + } +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java index 6af755c0..be27dcaf 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java @@ -6,6 +6,6 @@ import io.shinhanlife.dap.mcc.biz.oth.dto.*; public interface Onnba3011UseCase { @McpTool(name = "oth_oth_onnba3011_call", description = "Onnba3011 호출 툴") - @ToolHint(categoryKey = "oth", register = true) + @ToolHint(categoryKey = "oth", register = false) Object execute(Onnba3011Request req); }