When I used MCP, I simultaneously used Spring AOP, but a CGLIB proxy class was not generated for Tool. I tried many methods, but none of them worked.

there is my mcpToll codes

@Configuration
@Log4j2
@DependsOn("dataSourceAspect")
public class McpToolsConfig {

    @Lazy
    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public ToolCallbackProvider functionRegistry() {
        log.info("===== functionRegistry Bean 初始化开始 =====");

        // 获取所有带有 @McpToolAnnotation 注解的Bean
        Map<String, Object> annotatedBeans = applicationContext.getBeansWithAnnotation(McpToolAnnotation.class);

        Collection<Object> tools = annotatedBeans.values();

        tools.forEach(tool -> {
            log.info("McpTool: {}, 代理状态: {}",
                    tool.getClass().getSimpleName(),
                    AopUtils.isAopProxy(tool));
        });

        return MethodToolCallbackProvider.builder()
                .toolObjects(tools.toArray())
                .build();
    }
}

Comment From: tzolov

Hi @Lyeluo , can you please share a functional example or a test that reproduces the issue

Comment From: leelance

Why does my AOP proxy test work correctly? Is it because the way I’m testing it is incorrect?

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface McpToolAnnotation {
  /**
   * name
   *
   * @return mcp bean tool name
   */
  String name();

  /**
   * mcp description
   *
   * @return description
   */
  String description() default "";
}
@Component
@McpToolAnnotation(name = "weatherTool", description = "query weather tools")
public class WeatherTool {

  @Tool(name = "getWeather", description = "Query the weather conditions of a city based on its name.")
  public String getWeather(@ToolParam(description = "city name") String city) {
    return city + ", It’s sunny today. ☀️";
  }

  @Transactional
  @Tool(name = "getWeatherAsync", description = "Query the weather conditions of a city based on its name.")
  public String getWeatherAsync(@ToolParam(description = "city name") String city) {
    return city + ", It’s sunny today. ☀️";
  }
}

@Component
@McpToolAnnotation(name = "mathTool", description = "calculate math tools")
public class MathTool {

  @Tool(name = "add", description = "Calculate the sum of two numbers")
  public int add(@ToolParam(description = "a") int a, @ToolParam(description = "b") int b) {
    return a + b;
  }

  @Tool(name = "multiply", description = "Calculate the product of two numbers")
  public int multiply(@ToolParam(description = "a") int a, @ToolParam(description = "b") int b) {
    return a * b;
  }
}
@Slf4j
@Configuration
@RequiredArgsConstructor
public class McpServerConfig {
  private final ApplicationContext applicationContext;

  @Bean
  public ToolCallbackProvider mcpToolCallbackProvider() {
    //get @McpToolAnnotation beans
    Map<String, Object> annotatedBeans = applicationContext.getBeansWithAnnotation(McpToolAnnotation.class);

    Collection<Object> tools = annotatedBeans.values();
    tools.forEach(tool -> log.info("==>mcp tool bean: {}, aop: {}", tool.getClass().getSimpleName(), AopUtils.isAopProxy(tool)));

    return MethodToolCallbackProvider.builder().toolObjects(tools.toArray()).build();
  }
}

The JUnit test code is as follows:

@Slf4j
@SpringBootTest(properties = {"spring.ai.mcp.client.sse.connections.server1.url=http://localhost:8080"})
class WeatherToolTests extends AbstractMcpJunit {
  @Autowired
  private List<McpSyncClient> mcpSyncClients;

  @Test
  void getWeather() {
    Map<String, Object> params = new HashMap<>();
    params.put("city", "ShangHai");

    List<McpSchema.TextContent> result = call(mcpSyncClients, "getWeather", params);

    log.info("==>result: {}", result.get(0).text());
    Assertions.assertNotNull(result);
  }

  @Test
  void getWeatherAsync() {
    Map<String, Object> params = new HashMap<>();
    params.put("city", "Beijing");

    List<McpSchema.TextContent> result = call(mcpSyncClients, "getWeatherAsync", params);

    log.info("==>result: {}", result.get(0).text());
    Assertions.assertNotNull(result);
  }
}

The loaded MCP logs are as follows, and the AOP log prints true.

2025-09-23 15:19:00.255  INFO 22072 --- [           main] c.o.d.a.aiserver.config.McpServerConfig ---[  36] : ==>mcp tool bean: MathTool, aop: false
2025-09-23 15:19:00.256  INFO 22072 --- [           main] c.o.d.a.aiserver.config.McpServerConfig ---[  36] : ==>mcp tool bean: WeatherTool$$SpringCGLIB$$0, aop: true
2025-09-23 15:19:00.511  INFO 22072 --- [           main] o.s.a.m.s.a.McpServerAutoConfiguration  ---[ 174] : Enable tools capabilities, notification: true
2025-09-23 15:19:00.555  INFO 22072 --- [           main] o.s.a.m.s.a.McpServerAutoConfiguration  ---[ 174] : Registered tools: 4
2025-09-23 15:19:00.556  INFO 22072 --- [           main] o.s.a.m.s.a.McpServerAutoConfiguration  ---[ 174] : Enable resources capabilities, notification: true
2025-09-23 15:19:00.556  INFO 22072 --- [           main] o.s.a.m.s.a.McpServerAutoConfiguration  ---[ 174] : Enable prompts capabilities, notification: true

Comment From: Lyeluo

Hi @Lyeluo , can you please share a functional example or a test that reproduces the issue

ok, that's my aspect config code

@Log4j2
@Aspect
@Component("dataSourceAspect")
public class DataSourceAspect {
    @Autowired
    private TenantDatasourceConfig tenantDatasourceConfig;

    // 添加构造方法,打印初始化日志
    public DataSourceAspect() {
        log.info("===== DataSourceAspect 切面Bean开始初始化 =====");
    }

    @Pointcut("@annotation(tenantIdSwitch)")
    public void pointcut(TenantIdSwitch tenantIdSwitch) {
    }

    @Before("pointcut(tenantIdSwitch)")
    public void before(JoinPoint joinPoint, TenantIdSwitch tenantIdSwitch) throws Throwable {
            Object[] args = joinPoint.getArgs();
            int paramIndex = tenantIdSwitch.tenantIdIndex();
            if (paramIndex >= 0 && paramIndex < args.length) {

                Object param = args[paramIndex];
                String tenantId = null;
                if (param instanceof BaseVO) {
                    tenantId = ((BaseVO) param).getTenantId();
                } else if (param instanceof String) {
                    tenantId = String.valueOf(args[paramIndex]);
                }

                if (!StrUtil.isEmpty(tenantId)) {
                    String datasourceName = getDatasourceByTenantId(tenantId);
                    if (datasourceName != null) {
                        DynamicDataSourceContextHolder.push(datasourceName);
                    }
                }
            }
    }
    }

that's my service code

@McpToolAnnotation
@Service
public class KTTools {

    @Resource
    private KTService ktService;

    @TenantIdSwitch(tenantIdIndex = 0)
    @Tool(name = "get_kt_info", description = """
            查询知识点基础信息,返回以下数据:
            1. 知识点名称
            2. 知识点编码
            3. 知识点难易度
            4. 知识点掌握度
            5. 任务名称
            6. 关联的知识点信息
            7. 与关联知识点的关系
            """)
    public List<KtResRelVO> getKtInfo(@ToolParam(description = "租户id") String tenantId, @ToolParam(description = "学期ID") String termId,
                                      @ToolParam(description = "基础课程ID") String baseCourseId, @ToolParam(description = "教学班级ID") String teachClassId,
                                      @ToolParam(description = "数据版本号集合") List<String> versionId, @ToolParam(description = "知识点名称集合", required = false) List<String> ktNameList,
                                      @ToolParam(description = "知识点标签", required = false) String ktTags, @ToolParam(description = "知识点难易度", required = false) String ktDifficulty,
                                      @ToolParam(description = "知识点掌握度", required = false) String ktMastery, @ToolParam(description = "知识点分类", required = false) String ktClassifyCode,
                                      @ToolParam(description = "前修知识点集合", required = false) List<String> preKtNameList, @ToolParam(description = "关联知识点id", required = false) String ktRelatedId,
                                      @ToolParam(description = "知识点关联资源名称", required = false) String resName, @ToolParam(description = "知识点关联的资源类型(有以下类型:学习资料,测验,作业,项目,考试,教学资料,试题)", required = false) String resType,
                                      @ToolParam(description = "知识点ID集合", required = false) List<String> ktIdList) {

        return ktService.getKtInfo(tenantId, termId, baseCourseId, teachClassId, versionId, ktNameList, ktTags, ktDifficulty, ktMastery, ktClassifyCode, preKtNameList, ktRelatedId, resName, resType, ktIdList);
    }
}

and i have already configured that it must use cglib like this

@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class McpServerApplication  {

I found when i start my project using intelliJ IDEA, the pointcut can run correct, when i package with maven and use the same config , the 'before poincut' will not run .