diff --git a/common/common-core/pom.xml b/common/common-core/pom.xml
index 3639cf8..8c32b92 100644
--- a/common/common-core/pom.xml
+++ b/common/common-core/pom.xml
@@ -117,5 +117,20 @@
4.4.0
compile
+
+
+ com.github.krrr
+ mcp-java8-sdk
+ 0.8.1
+
+
+
+
+ jitpack.io
+ https://jitpack.io
+ true
+ true
+
+
diff --git a/common/common-core/src/main/java/apelet/common/core/mcp/annotation/McpToolClass.java b/common/common-core/src/main/java/apelet/common/core/mcp/annotation/McpToolClass.java
new file mode 100644
index 0000000..6eb2f1e
--- /dev/null
+++ b/common/common-core/src/main/java/apelet/common/core/mcp/annotation/McpToolClass.java
@@ -0,0 +1,30 @@
+package apelet.common.core.mcp.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * MCP 工具方法注解:标注在业务方法上,启动时由 McpToolScanner 扫描自动生成 MCP Tool 并注册。
+ *
name 与 description 即 MCP Tool 的 name / description,inputSchema 由方法参数上的 @McpToolParam 组装。
+ *
+ * @author chenchuchuan
+ * @date 2026-08-31
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface McpToolClass {
+
+ /**
+ * MCP Tool 名称,客户端 tools/list 看到的 name。
+ */
+ String name();
+
+ /**
+ * MCP Tool 描述,供大模型理解用途。
+ */
+ String description();
+}
diff --git a/common/common-core/src/main/java/apelet/common/core/mcp/annotation/McpToolParam.java b/common/common-core/src/main/java/apelet/common/core/mcp/annotation/McpToolParam.java
new file mode 100644
index 0000000..de219e7
--- /dev/null
+++ b/common/common-core/src/main/java/apelet/common/core/mcp/annotation/McpToolParam.java
@@ -0,0 +1,48 @@
+package apelet.common.core.mcp.annotation;
+
+import apelet.common.core.mcp.model.McpToolParamType;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * MCP 工具方法参数注解:标注在 @McpToolClass 方法的参数上,以及 type=OBJECT 的 schemaClass 字段上。
+ * 启动扫描时按注解属性组装 inputSchema;调用工具时按 fieldName 从入参取值绑定到方法实参。
+ *
+ * @author chenchuchuan
+ * @date 2026-08-31
+ */
+@Documented
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface McpToolParam {
+
+ /**
+ * 入参字段名,即客户端传参时使用的 key。
+ * 未配置时取 Java 参数名(默认编译带调试信息,可通过 DefaultParameterNameDiscoverer 获取)。
+ */
+ String fieldName() default "";
+
+ /**
+ * 字段类型(JSON Schema 的 type),默认字符串。
+ */
+ McpToolParamType type() default McpToolParamType.STRING;
+
+ /**
+ * 字段描述,供大模型理解入参含义。
+ */
+ String description() default "";
+
+ /**
+ * 是否必填,默认 false。
+ */
+ boolean required() default false;
+
+ /**
+ * type=OBJECT 时必填,指向的对象类型,其 @McpToolParam 标注字段会递归组装成嵌套 schema。
+ */
+ Class> schemaClass() default Void.class;
+}
diff --git a/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolConfig.java b/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolConfig.java
new file mode 100644
index 0000000..b6af926
--- /dev/null
+++ b/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolConfig.java
@@ -0,0 +1,91 @@
+package apelet.common.core.mcp.config;
+
+import apelet.common.core.mcp.transport.StreamableHttpServerTransportProvider;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.server.McpServerFeatures;
+import io.modelcontextprotocol.server.McpSyncServer;
+import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.function.RouterFunction;
+import org.springframework.web.servlet.function.ServerResponse;
+
+import java.util.List;
+
+/**
+ * MCP Server 装配:把业务工具暴露为 MCP tool,供大模型(客户端)调用。
+ * 基于 mcp-java8-sdk 0.8.1(构造器 API,非新版 builder())。
+ * 该 SDK 不提供 listTools(),故注册时同步收集一份工具清单供调试接口读取(见 McpToolScanner.getTools())。
+ * 双传输并存:SSE(/mcp/sse + /mcp/message)+ Streamable HTTP(/mcp 单端点)。
+ * 工具由 McpToolScanner 启动时扫描 @McpToolClass 自动生成并统一注入。
+ *
+ * @author chenchuchuan
+ * @date 2026-08-27
+ */
+@Configuration
+public class McpToolConfig {
+
+ /**
+ * MCP 消息专用 ObjectMapper:应用注入的 ObjectMapper 在序列化 @JsonTypeInfo 多态对象
+ * (如 CallToolResult.content 的 TextContent)时丢失 type 判别符,导致客户端反序列化失败,
+ * 故 MCP 的序列化统一走此普通 mapper(普通 mapper 会正常输出 "type":"text")。
+ */
+ private static final ObjectMapper MCP_OBJECT_MAPPER = new ObjectMapper();
+
+ @Autowired
+ private McpToolScanner mcpToolScanner;
+
+ /**
+ * MCP 传输层(Spring MVC SSE):SSE 长连接端点 + JSON-RPC message 端点。
+ * 端点路径带 /tenantadmin 前缀(本应用无 servlet context-path,模块 urlPrefix 统一用 /tenantadmin/xxx)。
+ */
+ @Bean
+ public WebMvcSseServerTransportProvider mcpTransport() {
+ return new WebMvcSseServerTransportProvider(MCP_OBJECT_MAPPER, "/tenantadmin/mcp/message", "/tenantadmin/mcp/sse");
+ }
+
+ /**
+ * 把 SSE transport 内部的 RouterFunction(SSE/message 两个端点)注册进 Spring MVC。
+ */
+ @Bean
+ public RouterFunction mcpEndpoints(WebMvcSseServerTransportProvider mcpTransport) {
+ return mcpTransport.getRouterFunction();
+ }
+
+ /**
+ * 构建同步 MCP Server(SSE 传输),并注册业务工具。
+ */
+ @Bean
+ public McpSyncServer mcpSyncServer(WebMvcSseServerTransportProvider mcpTransport) {
+ return registerTools(McpServer.sync(mcpTransport).serverInfo("business-mcp-server", "1.0.0")).build();
+ }
+
+ /**
+ * MCP 传输层(Streamable HTTP,单端点 POST /tenantadmin/mcp)。
+ * 端点由 McpStreamableController 提供,不走 RouterFunction(避免应用转换器二次加工响应)。
+ */
+ @Bean
+ public StreamableHttpServerTransportProvider streamableHttpTransport() {
+ return new StreamableHttpServerTransportProvider(MCP_OBJECT_MAPPER);
+ }
+
+ /**
+ * 构建同步 MCP Server(Streamable HTTP 传输),注册同一套业务工具。
+ */
+ @Bean
+ public McpSyncServer mcpStreamableSyncServer(StreamableHttpServerTransportProvider streamableHttpTransport) {
+ return registerTools(McpServer.sync(streamableHttpTransport).serverInfo("business-mcp-server", "1.0.0")).build();
+ }
+
+ /**
+ * 注册 MCP 工具(SSE 与 Streamable 两个 server 共用同一套注册逻辑)。
+ * 工具清单由 McpToolScanner 扫描 @McpToolClass 自动组装,这里批量注入。
+ */
+ private McpServer.SyncSpecification registerTools(McpServer.SyncSpecification spec) {
+ List specifications = mcpToolScanner.getToolSpecifications();
+ return specifications.isEmpty() ? spec : spec.tools(specifications);
+ }
+
+}
diff --git a/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolScanner.java b/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolScanner.java
new file mode 100644
index 0000000..f1a7edb
--- /dev/null
+++ b/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolScanner.java
@@ -0,0 +1,499 @@
+package apelet.common.core.mcp.config;
+
+import apelet.common.core.mcp.annotation.McpToolClass;
+import apelet.common.core.mcp.annotation.McpToolParam;
+import apelet.common.core.mcp.model.McpToolArgInfo;
+import apelet.common.core.mcp.model.McpToolParamType;
+import apelet.common.core.object.TokenData;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import io.modelcontextprotocol.server.McpServerFeatures;
+import io.modelcontextprotocol.spec.McpSchema;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.*;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * MCP 工具扫描注册器:启动时从 Spring 容器取 @RestController/@Controller/@Component 等组件 bean,
+ * 扫描其上 @McpToolClass 标注的方法,自动组装 McpSchema.Tool 并生成调用 handler。
+ * 直接基于容器 bean 扫描(getBeanNamesForAnnotation),不做 classpath 资源扫描,
+ * 避免 fat jar 嵌套 jar 里包名通配(**)扫不到的问题,也无需 scan-packages 等配置。
+ * 组装结果以 SyncToolSpecification 列表暴露给 McpToolConfig 注入 SSE + Streamable 两个 server。
+ *
+ * @author chenchuchuan
+ * @date 2026-08-31
+ */
+@Slf4j
+@Component
+public class McpToolScanner {
+
+ /**
+ * 方法参数名发现器:优先 -parameters,兜底 LocalVariableTable(默认编译带调试信息)。
+ */
+ private static final DefaultParameterNameDiscoverer PARAM_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
+ private final ApplicationContext applicationContext;
+ /**
+ * MCP 调用使用的默认用户Id(MCP 无 HTTP 登录态,服务内 TokenData.takeFromRequest() 会 NPE,调用前注入)。
+ */
+ @Value("${apelet.mcp.default-user-id:0}")
+ private Long defaultUserId;
+ /**
+ * 已组装好的工具注册项(供 spec.tools 批量注入)。
+ */
+ @Getter
+ private List toolSpecifications = Collections.emptyList();
+ /**
+ * 已注册的工具清单(SDK 不提供 listTools(),供调试接口读取)。
+ */
+ private List toolList = Collections.emptyList();
+
+ public McpToolScanner(ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ /**
+ * 按返回类型给动态代理一个安全的默认值(对象 null、字符串空、集合空、数组空、数值 0、布尔 false)。
+ */
+ private static Object defaultValue(Class> returnType) {
+ if (returnType == String.class) {
+ return "";
+ }
+ if (returnType == boolean.class) {
+ return false;
+ }
+ if (returnType == int.class) {
+ return 0;
+ }
+ if (returnType == long.class) {
+ return 0L;
+ }
+ if (returnType == List.class || returnType == Map.class || returnType == Set.class) {
+ return Collections.emptyMap();
+ }
+ if (returnType.isArray()) {
+ return Array.newInstance(returnType.getComponentType(), 0);
+ }
+ return null;
+ }
+
+ @PostConstruct
+ public void init() {
+ List specifications = new ArrayList<>();
+ List tools = new ArrayList<>();
+ // 直接从 Spring 容器取组件 bean 扫描,不做 classpath 资源扫描。
+ // 原因:ClassPathScanningCandidateComponentProvider 走 PathMatchingResourcePatternResolver 递归匹配 classpath 资源,
+ // 应用打成 fat jar 后 @McpToolClass 类在 BOOT-INF/lib/*.jar 嵌套 jar 里,包名带 `**` 时匹配不上,导致扫不到;
+ // 改为从容器取 bean 则完全不受打包方式影响。@RestController/@Controller 都被 @Component 元注解标注,
+ // getBeanNamesForAnnotation(Component.class) 全部命中,@Service/@Repository 等组件同样覆盖。
+ Set> scanned = new HashSet<>();
+ for (String beanName : applicationContext.getBeanNamesForAnnotation(RestController.class)) {
+ Class> clazz = applicationContext.getType(beanName);
+ if (clazz == null) {
+ continue;
+ }
+ clazz = ClassUtils.getUserClass(clazz);
+ if (!scanned.add(clazz)) {
+ continue;
+ }
+ this.scanClassMethods(specifications, tools, clazz, beanName);
+ }
+ this.toolSpecifications = specifications;
+ this.toolList = tools;
+ log.info("MCP 工具扫描完成,共注册 {} 个 tool:{}", tools.size(),
+ tools.stream().map(McpSchema.Tool::getName).collect(Collectors.toList()));
+ }
+
+ /**
+ * 扫描单个 bean 类的 @McpToolClass 方法并注册。
+ */
+ private void scanClassMethods(List specifications,
+ List tools, Class> clazz, String beanName) {
+ for (Method method : clazz.getDeclaredMethods()) {
+ // 只暴露 public 方法为 MCP 工具,私有/受保护方法不注册。
+ if (!Modifier.isPublic(method.getModifiers())) {
+ continue;
+ }
+ McpToolClass ann = method.getAnnotation(McpToolClass.class);
+ if (ann == null) {
+ continue;
+ }
+ this.registerTool(specifications, tools, clazz, beanName, method, ann);
+ }
+ }
+
+ public List getTools() {
+ return toolList;
+ }
+
+ /**
+ * 组装单个工具:inputSchema + Tool + 调用 handler。
+ */
+ private void registerTool(List specifications,
+ List tools, Class> clazz, String beanName, Method method, McpToolClass ann) {
+ List argInfos = this.buildArgInfos(method);
+ String inputSchema = this.buildInputSchema(argInfos);
+ McpSchema.Tool tool = new McpSchema.Tool(ann.name(), ann.description(), inputSchema);
+ // 懒取 bean:只有真正带 @McpToolClass 方法的类才触发初始化;拿到的是代理实例,invoke 时走 AOP。
+ Object bean = applicationContext.getBean(beanName);
+ McpServerFeatures.SyncToolSpecification specification = new McpServerFeatures.SyncToolSpecification();
+ specification.setTool(tool);
+ specification.setCall((exchange, args) -> this.invoke(bean, method, argInfos, args));
+ specifications.add(specification);
+ tools.add(tool);
+ log.info("MCP 工具已注册:{} (inputSchema={})", tool.getName(), inputSchema);
+ }
+
+ /**
+ * 组装方法参数绑定信息:@McpToolParam 未配置 fieldName 时取 Java 参数名。
+ */
+ private List buildArgInfos(Method method) {
+ Parameter[] parameters = method.getParameters();
+ String[] paramNames = PARAM_NAME_DISCOVERER.getParameterNames(method);
+ List argInfos = new ArrayList<>();
+ for (int i = 0; i < parameters.length; i++) {
+ McpToolParam ann = parameters[i].getAnnotation(McpToolParam.class);
+ if (ann == null) {
+ log.warn("MCP 工具方法 [{}#{}] 参数 [{}] 缺少 @McpToolParam,跳过该参数", method.getDeclaringClass().getSimpleName(), method.getName(), i);
+ continue;
+ }
+ String fieldName = StrUtil.isNotBlank(ann.fieldName()) ? ann.fieldName()
+ : (paramNames != null && paramNames.length > i ? paramNames[i] : "arg" + i);
+ argInfos.add(this.toArgInfo(i, parameters[i].getType(), parameters[i].getParameterizedType(), ann, fieldName));
+ }
+ return argInfos;
+ }
+
+ /**
+ * 由注解 + Java 类型组装元信息(方法参数与对象字段共用)。
+ */
+ private McpToolArgInfo toArgInfo(int paramIndex, Class> paramType, Type genericType, McpToolParam ann, String fieldName) {
+ Class> elementType = ann.type() == McpToolParamType.ARRAY ? this.inferElementType(genericType, paramType) : null;
+ Class> schemaClass = ann.type() == McpToolParamType.OBJECT ? (ann.schemaClass() == Void.class ? paramType : ann.schemaClass()) : null;
+ return new McpToolArgInfo(paramIndex, fieldName, ann.type(), ann.description(), paramType, elementType, schemaClass, ann.required());
+ }
+
+ /**
+ * 组装 inputSchema:{"type":"object","properties":{...},"required":[...]}。
+ */
+ private String buildInputSchema(List argInfos) {
+ JSONObject root = new JSONObject(true);
+ root.put("type", "object");
+ JSONObject properties = new JSONObject(true);
+ JSONArray required = new JSONArray();
+ for (McpToolArgInfo info : argInfos) {
+ properties.put(info.getFieldName(), this.buildElementSchema(info));
+ if (info.isRequired()) {
+ required.add(info.getFieldName());
+ }
+ }
+ if (!properties.isEmpty()) {
+ root.put("properties", properties);
+ }
+ if (!required.isEmpty()) {
+ root.put("required", required);
+ }
+ return root.toJSONString();
+ }
+
+ /**
+ * 组装单个参数/字段的 JSON Schema;OBJECT 递归展开 schemaClass 的 @McpToolParam 字段。
+ */
+ private JSONObject buildElementSchema(McpToolArgInfo info) {
+ JSONObject schema = new JSONObject(true);
+ schema.put("type", info.getType().getJsonType());
+ if (StrUtil.isNotBlank(info.getDescription())) {
+ schema.put("description", info.getDescription());
+ }
+ if (info.getType() == McpToolParamType.OBJECT && info.getSchemaClass() != null) {
+ JSONObject properties = new JSONObject(true);
+ JSONArray required = new JSONArray();
+ this.collectObjectSchema(info.getSchemaClass(), properties, required);
+ if (!properties.isEmpty()) {
+ schema.put("properties", properties);
+ }
+ if (!required.isEmpty()) {
+ schema.put("required", required);
+ }
+ } else if (info.getType() == McpToolParamType.ARRAY && info.getElementType() != null) {
+ JSONObject items = new JSONObject(true);
+ items.put("type", this.toJsonType(info.getElementType()));
+ schema.put("items", items);
+ }
+ return schema;
+ }
+
+ /**
+ * 收集对象类型中 @McpToolParam 标注字段的嵌套 schema。
+ */
+ private void collectObjectSchema(Class> schemaClass, JSONObject properties, JSONArray required) {
+ for (Field field : schemaClass.getDeclaredFields()) {
+ McpToolParam ann = field.getAnnotation(McpToolParam.class);
+ if (ann == null) {
+ continue;
+ }
+ String fieldName = StrUtil.isNotBlank(ann.fieldName()) ? ann.fieldName() : field.getName();
+ properties.put(fieldName, this.buildElementSchema(this.toArgInfo(-1, field.getType(), field.getGenericType(), ann, fieldName)));
+ if (ann.required()) {
+ required.add(fieldName);
+ }
+ }
+ }
+
+ /**
+ * 推断数组元素类型:List 泛型实参或数组组件类型,未知默认 String。
+ */
+ private Class> inferElementType(Type genericType, Class> rawType) {
+ if (genericType instanceof ParameterizedType) {
+ Type actual = ((ParameterizedType) genericType).getActualTypeArguments()[0];
+ if (actual instanceof Class) {
+ return (Class>) actual;
+ }
+ }
+ return rawType.isArray() ? rawType.getComponentType() : String.class;
+ }
+
+ /**
+ * Java 类型 → JSON Schema type。
+ */
+ private String toJsonType(Class> javaType) {
+ if (javaType == BigDecimal.class || javaType == Double.class || javaType == Float.class
+ || javaType == double.class || javaType == float.class) {
+ return "number";
+ }
+ if (javaType == Long.class || javaType == Integer.class || javaType == Short.class || javaType == Byte.class
+ || javaType == long.class || javaType == int.class) {
+ return "integer";
+ }
+ if (javaType == Boolean.class || javaType == boolean.class) {
+ return "boolean";
+ }
+ return "string";
+ }
+
+ /**
+ * 调用工具方法:按元信息从入参 map 取参、转换类型、反射调用,结果包成 CallToolResult。
+ * MCP 调用运行在 reactor 调度线程,无 servlet 请求上下文,需先挂一个动态代理的 request/response
+ * (承载 TokenData)再调用,否则 @OperationLog 等切面在 RequestContextHolder 上强转 NPE。
+ */
+ private McpSchema.CallToolResult invoke(Object bean, Method method, List argInfos, Map args) {
+ try {
+ //todo 这里要求带上token MCP 无 HTTP 登录态,注入默认用户 TokenData,避免服务内 takeFromRequest() 拿到 null 抛 NPE。
+ TokenData tokenData = new TokenData();
+ tokenData.setUserId(this.defaultUserId);
+ return this.withRequestContext(tokenData, () -> {
+ Object[] invokeArgs = this.bindArgs(args, argInfos, method.getParameterCount());
+ Object result = method.invoke(bean, invokeArgs);
+ String text = result == null ? "" : (result instanceof String ? (String) result : JSON.toJSONString(result));
+ return new McpSchema.CallToolResult(Collections.singletonList(new McpSchema.TextContent(text)), false);
+ });
+ } catch (Exception e) {
+ Throwable cause = e instanceof InvocationTargetException ? ((InvocationTargetException) e).getTargetException() : e;
+ log.error("MCP 工具 [{}] 调用失败", method.getName(), cause);
+ return new McpSchema.CallToolResult(Collections.singletonList(new McpSchema.TextContent(cause.getMessage())), true);
+ }
+ }
+
+ /**
+ * 在指定 TokenData 的请求上下文下执行动作:当前线程没有请求上下文时挂动态代理的 request/response,
+ * 执行完恢复原样。
+ */
+ private T withRequestContext(TokenData tokenData, ThrowingSupplier action) throws Exception {
+ RequestAttributes previous = RequestContextHolder.getRequestAttributes();
+ boolean created = previous == null;
+ if (created) {
+ ServletRequestAttributes attributes = new ServletRequestAttributes(mcpMockRequest(), mcpMockResponse());
+ RequestContextHolder.setRequestAttributes(attributes);
+ }
+ TokenData.addToRequest(tokenData);
+ try {
+ return action.get();
+ } finally {
+ if (created) {
+ RequestContextHolder.resetRequestAttributes();
+ }
+ }
+ }
+
+ /**
+ * 动态代理的 HttpServletRequest:仅承载 getAttribute/setAttribute(供 TokenData),其余方法返回安全默认值。
+ */
+ private HttpServletRequest mcpMockRequest() {
+ return (HttpServletRequest) Proxy.newProxyInstance(this.getClass().getClassLoader(),
+ new Class>[]{HttpServletRequest.class}, new MockRequestHandler());
+ }
+
+ /**
+ * 动态代理的 HttpServletResponse:setHeader 等方法空操作。
+ */
+ private HttpServletResponse mcpMockResponse() {
+ return (HttpServletResponse) Proxy.newProxyInstance(this.getClass().getClassLoader(),
+ new Class>[]{HttpServletResponse.class}, (proxy, method, args1) -> defaultValue(method.getReturnType()));
+ }
+
+ /**
+ * 按 argInfos 的 paramIndex 填充方法实参数组(数组长度按方法形参总数,未标注解的参数保持 null)。
+ */
+ private Object[] bindArgs(Map args, List argInfos, int parameterCount) {
+ Object[] invokeArgs = new Object[parameterCount];
+ for (McpToolArgInfo info : argInfos) {
+ invokeArgs[info.getParamIndex()] = this.convertValue(args.get(info.getFieldName()), info);
+ }
+ return invokeArgs;
+ }
+
+ /**
+ * 按元信息把入参 JSON 值转换为 Java 值(简单类型 / 数组 / 对象递归)。
+ */
+ private Object convertValue(Object raw, McpToolArgInfo info) {
+ if (raw == null) {
+ return null;
+ }
+ switch (info.getType()) {
+ case STRING:
+ return String.valueOf(raw);
+ case BOOLEAN:
+ return Boolean.valueOf(raw.toString());
+ case INTEGER:
+ return this.toScalar(raw, info.getParamType());
+ case NUMBER:
+ return this.toScalar(raw, info.getParamType());
+ case ARRAY:
+ return this.toArray(raw, info.getParamType(), info.getElementType());
+ case OBJECT:
+ return this.toObject(raw, info.getSchemaClass());
+ default:
+ return raw;
+ }
+ }
+
+ /**
+ * 构造数组实参:支持 List 与 E[],元素按元素类型转换。
+ */
+ private Object toArray(Object raw, Class> paramType, Class> elementType) {
+ List> source = raw instanceof List ? (List>) raw : Arrays.asList((Object[]) raw);
+ List