From 367743b461df57afdeabbe3021a48156f4cefb23 Mon Sep 17 00:00:00 2001 From: jade Date: Thu, 9 Jul 2026 11:34:23 +0900 Subject: [PATCH] fix(core): use AnnotationUtils and AopUtils for bulletproof proxy method scanning --- .../presentation/BusinessToolController.java | 28 +++++++++++++------ .../service/ToolRegistryHeartbeatSender.java | 8 +++--- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/presentation/BusinessToolController.java b/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/presentation/BusinessToolController.java index b251991..65cf01c 100644 --- a/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/presentation/BusinessToolController.java +++ b/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/presentation/BusinessToolController.java @@ -26,6 +26,8 @@ import io.shinhanlife.axhub.biz.mcp.tool.config.McpProperties; import java.lang.reflect.Method; import io.shinhanlife.axhub.biz.mcp.tool.util.JsonSchemaGenerator; import org.springframework.util.ClassUtils; +import org.springframework.aop.support.AopUtils; +import org.springframework.core.annotation.AnnotationUtils; import lombok.extern.slf4j.Slf4j; @@ -71,15 +73,12 @@ public class BusinessToolController { // 1. 대상 Bean 및 Method 찾기 (ApplicationContext 활용) Map arguments = params != null ? (Map) params.get("arguments") : null; Object targetBean = null; - Method targetMethod = null; - McpFunction targetFunctionAnnotation = null; - Map toolBeans = applicationContext.getBeansWithAnnotation(McpTool.class); outerLoop: for (Object bean : toolBeans.values()) { - Class userClass = ClassUtils.getUserClass(bean); - for (Method method : userClass.getDeclaredMethods()) { - McpFunction mcpFunc = method.getAnnotation(McpFunction.class); + Class targetClass = AopUtils.getTargetClass(bean); + for (Method method : targetClass.getDeclaredMethods()) { + McpFunction mcpFunc = AnnotationUtils.findAnnotation(method, McpFunction.class); if (mcpFunc != null) { String baseName = mcpFunc.name(); String expectedName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() @@ -97,15 +96,26 @@ public class BusinessToolController { } if (targetBean == null || targetMethod == null) { - log.error("[Tool] 실행할 함수(Method)를 찾을 수 없습니다: {}", functionName); + List availableFunctions = new ArrayList<>(); + for (Object bean : toolBeans.values()) { + Class targetCls = AopUtils.getTargetClass(bean); + for (Method m : targetCls.getDeclaredMethods()) { + McpFunction func = AnnotationUtils.findAnnotation(m, McpFunction.class); + if (func != null) { + String baseName = func.name(); + String expName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() + ? mcpProperties.getNamespace() + "_" + baseName : baseName; + availableFunctions.add(expName + " (in " + targetCls.getSimpleName() + ")"); + } + } + } + log.error("[Tool] 실행할 함수(Method)를 찾을 수 없습니다: {}. 현재 스캔된 툴 메서드 목록: {}", functionName, availableFunctions); Map error = new HashMap<>(); error.put("jsonrpc", "2.0"); error.put("error", Map.of("code", -32601, "message", "실행할 함수를 찾을 수 없습니다: " + functionName)); error.put("id", payload != null ? payload.get("id") : null); return error; } - - // --- 비공개 툴(register=false)은 외부 노출(Redis)만 제외하고, 직접 실행은 허용하도록 변경 --- // (기존 차단 로직 제거됨) // 2. 파라미터 유효성 검증 (JSON Schema) diff --git a/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/ToolRegistryHeartbeatSender.java b/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/ToolRegistryHeartbeatSender.java index c126806..6f0d33f 100644 --- a/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/ToolRegistryHeartbeatSender.java +++ b/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/service/ToolRegistryHeartbeatSender.java @@ -68,11 +68,11 @@ public class ToolRegistryHeartbeatSender { private void scanAndBuildMetadata() { Map toolBeans = applicationContext.getBeansWithAnnotation(McpTool.class); for (Object bean : toolBeans.values()) { - Class userClass = ClassUtils.getUserClass(bean); - McpTool toolAnnotation = userClass.getAnnotation(McpTool.class); + Class targetClass = org.springframework.aop.support.AopUtils.getTargetClass(bean); + McpTool toolAnnotation = org.springframework.core.annotation.AnnotationUtils.findAnnotation(targetClass, McpTool.class); - for (Method method : userClass.getDeclaredMethods()) { - McpFunction functionAnnotation = method.getAnnotation(McpFunction.class); + for (Method method : targetClass.getDeclaredMethods()) { + McpFunction functionAnnotation = org.springframework.core.annotation.AnnotationUtils.findAnnotation(method, McpFunction.class); if (functionAnnotation != null) { String baseName = functionAnnotation.name(); String finalName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()