From ebaff567c67c07f6526a6f13922d134ed27f10f1 Mon Sep 17 00:00:00 2001 From: jade Date: Tue, 18 Aug 2026 18:12:37 +0900 Subject: [PATCH] =?UTF-8?q?=ED=88=B4=20=EA=B2=80=EC=83=89=20=EC=8B=9C=20in?= =?UTF-8?q?terface=20->=20=EA=B5=AC=ED=98=84=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dap/lib/mcp/McpToolMethodRegistry.java | 146 ++++++++++++++++-- 1 file changed, 135 insertions(+), 11 deletions(-) diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java index 86d99947..04ad13ac 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java @@ -2,9 +2,11 @@ package io.shinhanlife.dap.lib.mcp; import io.shinhanlife.dap.lib.annotation.GrowToolHint; import io.shinhanlife.dap.lib.config.McpProperties; + import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.Map; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springaicommunity.mcp.annotation.McpTool; @@ -14,6 +16,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -31,26 +35,46 @@ public class McpToolMethodRegistry { @EventListener(ApplicationReadyEvent.class) public void initialize() { - Map discovered = new LinkedHashMap<>(); + Map discovered = new LinkedHashMap<>(); for (Object bean : applicationContext.getBeansOfType(Object.class).values()) { Class targetClass = AopUtils.getTargetClass(bean); for (Method declaredMethod : targetClass.getDeclaredMethods()) { - McpTool annotation = AnnotationUtils.findAnnotation(declaredMethod, McpTool.class); - if (annotation == null) { + + /* + * Tool Annotation 검색 + * + * 1순위 : Interface + * 2순위 : 구현체 + */ + ToolAnnotationMetadata metadata = findToolAnnotationMetadata(targetClass, declaredMethod); + + if (metadata == null) { continue; } - RegisteredTool tool = new RegisteredTool( - bean, - findInvocableMethod(bean, declaredMethod), - annotation, - AnnotationUtils.findAnnotation(declaredMethod, GrowToolHint.class)); + McpTool annotation = metadata.mcpTool(); + GrowToolHint hint = metadata.growToolHint(); + + /* + * Annotation은 Interface에서 가져올 수 있지만 + * 실제 호출 Method는 구현체 Method를 사용한다. + */ + RegisteredTool tool = new RegisteredTool(bean, findInvocableMethod(bean, declaredMethod), annotation, hint); + + /* + * 원래 Tool Name 등록 + */ register(discovered, annotation.name(), tool); + + /* + * namespace alias 등록 + */ registerNamespaceAlias(discovered, annotation.name(), tool); + + log.info("[Tool Registry] Registered Tool: {} -> {}#{}", annotation.name(), targetClass.getSimpleName(), declaredMethod.getName()); } } - tools = Map.copyOf(discovered); log.info("[Tool Registry] {} executable tool names cached", tools.size()); } @@ -59,22 +83,113 @@ public class McpToolMethodRegistry { return tools.get(toolName); } - private void registerNamespaceAlias(Map discovered, String toolName, - RegisteredTool tool) { + /** + * @McpTool / @GrowToolHint 검색 + *

+ * 우선순위 + *

+ * 1. Interface Method + * 2. 구현체 Method + */ + private ToolAnnotationMetadata findToolAnnotationMetadata(Class targetClass, Method declaredMethod) { + + /* + * ========================================== + * STEP 1. + * Interface부터 검색 + * ========================================== + */ + for (Class interfaceClass : ClassUtils.getAllInterfacesForClassAsSet(targetClass)) { + + /* + * 구현체 Method와 동일한 Signature를 가진 + * Interface Method를 찾는다. + */ + Method interfaceMethod = ReflectionUtils.findMethod(interfaceClass, declaredMethod.getName(), declaredMethod.getParameterTypes()); + + if (interfaceMethod == null) { + continue; + } + + /* + * Interface의 @McpTool 검색 + */ + McpTool mcpTool = AnnotationUtils.findAnnotation(interfaceMethod, McpTool.class); + if (mcpTool == null) { + continue; + } + + /* + * @McpTool을 Interface에서 발견했다면 + * @GrowToolHint 역시 같은 Interface Method에서 가져온다. + */ + GrowToolHint growToolHint = AnnotationUtils.findAnnotation(interfaceMethod, GrowToolHint.class); + log.debug("[Tool Registry] Interface @McpTool found: " + "{}#{} -> {}", interfaceClass.getSimpleName(), interfaceMethod.getName(), mcpTool.name()); + return new ToolAnnotationMetadata(mcpTool, growToolHint); + } + + /* + * ========================================== + * STEP 2. + * Interface에서 발견되지 않은 경우 + * 구현체 Method 검색 + * ========================================== + */ + McpTool mcpTool = AnnotationUtils.findAnnotation(declaredMethod, McpTool.class); + + if (mcpTool == null) { + return null; + } + + GrowToolHint growToolHint = AnnotationUtils.findAnnotation(declaredMethod, GrowToolHint.class); + log.debug("[Tool Registry] Implementation @McpTool found: " + "{}#{} -> {}", targetClass.getSimpleName(), declaredMethod.getName(), mcpTool.name()); + return new ToolAnnotationMetadata(mcpTool, growToolHint); + } + + /** + * Namespace가 존재하는 경우 + *

+ * ex) + *

+ * pct_notice_list + *

+ * namespace = sys + *

+ * sys_pct_notice_list + *

+ * 두 이름으로 동일 Tool을 조회할 수 있도록 등록 + */ + private void registerNamespaceAlias(Map discovered, String toolName, RegisteredTool tool) { + String namespace = mcpProperties.getNamespace(); + if (StringUtils.hasText(namespace)) { + register(discovered, namespace + "_" + toolName, tool); } } + /** + * Tool Registry 등록 + */ private void register(Map discovered, String toolName, RegisteredTool tool) { + RegisteredTool existing = discovered.putIfAbsent(toolName, tool); + if (existing != null && existing != tool) { + throw new IllegalStateException("Duplicate @McpTool name: " + toolName); } } + /** + * 실제 호출 가능한 Method 확보 + *

+ * Annotation 검색은 Interface에서 하더라도 + * Tool 실행은 구현체 Bean을 대상으로 수행해야 한다. + */ private Method findInvocableMethod(Object bean, Method declaredMethod) { + try { return bean.getClass().getMethod(declaredMethod.getName(), declaredMethod.getParameterTypes()); } catch (NoSuchMethodException ignored) { @@ -82,6 +197,15 @@ public class McpToolMethodRegistry { } } + /** + * Annotation 검색 결과 + */ + private record ToolAnnotationMetadata(McpTool mcpTool, GrowToolHint growToolHint) { + } + + /** + * 실제 실행 Registry 정보 + */ public record RegisteredTool(Object bean, Method method, McpTool annotation, GrowToolHint hint) { } } \ No newline at end of file