fix(core): use AnnotationUtils and AopUtils for bulletproof proxy method scanning

This commit is contained in:
jade
2026-07-09 11:34:23 +09:00
parent 1db259dbc9
commit 367743b461
2 changed files with 23 additions and 13 deletions

View File

@@ -26,6 +26,8 @@ import io.shinhanlife.axhub.biz.mcp.tool.config.McpProperties;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import io.shinhanlife.axhub.biz.mcp.tool.util.JsonSchemaGenerator; import io.shinhanlife.axhub.biz.mcp.tool.util.JsonSchemaGenerator;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -71,15 +73,12 @@ public class BusinessToolController {
// 1. 대상 Bean 및 Method 찾기 (ApplicationContext 활용) // 1. 대상 Bean 및 Method 찾기 (ApplicationContext 활용)
Map<String, Object> arguments = params != null ? (Map<String, Object>) params.get("arguments") : null; Map<String, Object> arguments = params != null ? (Map<String, Object>) params.get("arguments") : null;
Object targetBean = null; Object targetBean = null;
Method targetMethod = null;
McpFunction targetFunctionAnnotation = null;
Map<String, Object> toolBeans = applicationContext.getBeansWithAnnotation(McpTool.class); Map<String, Object> toolBeans = applicationContext.getBeansWithAnnotation(McpTool.class);
outerLoop: outerLoop:
for (Object bean : toolBeans.values()) { for (Object bean : toolBeans.values()) {
Class<?> userClass = ClassUtils.getUserClass(bean); Class<?> targetClass = AopUtils.getTargetClass(bean);
for (Method method : userClass.getDeclaredMethods()) { for (Method method : targetClass.getDeclaredMethods()) {
McpFunction mcpFunc = method.getAnnotation(McpFunction.class); McpFunction mcpFunc = AnnotationUtils.findAnnotation(method, McpFunction.class);
if (mcpFunc != null) { if (mcpFunc != null) {
String baseName = mcpFunc.name(); String baseName = mcpFunc.name();
String expectedName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() String expectedName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
@@ -97,15 +96,26 @@ public class BusinessToolController {
} }
if (targetBean == null || targetMethod == null) { if (targetBean == null || targetMethod == null) {
log.error("[Tool] 실행할 함수(Method)를 찾을 수 없습니다: {}", functionName); List<String> 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<String, Object> error = new HashMap<>(); Map<String, Object> error = new HashMap<>();
error.put("jsonrpc", "2.0"); error.put("jsonrpc", "2.0");
error.put("error", Map.of("code", -32601, "message", "실행할 함수를 찾을 수 없습니다: " + functionName)); error.put("error", Map.of("code", -32601, "message", "실행할 함수를 찾을 수 없습니다: " + functionName));
error.put("id", payload != null ? payload.get("id") : null); error.put("id", payload != null ? payload.get("id") : null);
return error; return error;
} }
// --- 비공개 툴(register=false)은 외부 노출(Redis)만 제외하고, 직접 실행은 허용하도록 변경 ---
// (기존 차단 로직 제거됨) // (기존 차단 로직 제거됨)
// 2. 파라미터 유효성 검증 (JSON Schema) // 2. 파라미터 유효성 검증 (JSON Schema)

View File

@@ -68,11 +68,11 @@ public class ToolRegistryHeartbeatSender {
private void scanAndBuildMetadata() { private void scanAndBuildMetadata() {
Map<String, Object> toolBeans = applicationContext.getBeansWithAnnotation(McpTool.class); Map<String, Object> toolBeans = applicationContext.getBeansWithAnnotation(McpTool.class);
for (Object bean : toolBeans.values()) { for (Object bean : toolBeans.values()) {
Class<?> userClass = ClassUtils.getUserClass(bean); Class<?> targetClass = org.springframework.aop.support.AopUtils.getTargetClass(bean);
McpTool toolAnnotation = userClass.getAnnotation(McpTool.class); McpTool toolAnnotation = org.springframework.core.annotation.AnnotationUtils.findAnnotation(targetClass, McpTool.class);
for (Method method : userClass.getDeclaredMethods()) { for (Method method : targetClass.getDeclaredMethods()) {
McpFunction functionAnnotation = method.getAnnotation(McpFunction.class); McpFunction functionAnnotation = org.springframework.core.annotation.AnnotationUtils.findAnnotation(method, McpFunction.class);
if (functionAnnotation != null) { if (functionAnnotation != null) {
String baseName = functionAnnotation.name(); String baseName = functionAnnotation.name();
String finalName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() String finalName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()