forked from kimhyungsik/ax_hub_mcp_tool
fix(core): use AnnotationUtils and AopUtils for bulletproof proxy method scanning
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user