Spring Boot服务接入MCP协议,让AI原生调用业务接口
1. 项目概述这不是一次简单的API封装而是一次AI原生服务的重构“从0到1吃透MCP把Spring Boot服务变成所有AI应用的工具”——这个标题里藏着三个关键信号MCP、Spring Boot服务、所有AI应用的工具。它不是教你怎么写个REST接口也不是讲怎么调用大模型API而是直指当前AI工程落地中最卡脖子的一环已有业务系统如何低成本、高可靠、可扩展地被AI“调用起来”。我带过好几个团队做AI Agent项目90%的失败不是因为模型不行而是因为业务系统像一堵墙——AI想调用订单查询结果要手写HTTP请求、拼JSON、处理鉴权、重试超时、解析字段想触发审批流程又得对接内部RPC、处理状态机、兜底失败。MCPModel Context Protocol正是为解决这个问题而生的轻量级协议层它不替代你的Spring Boot而是给它装上“AI可识别的说明书”和“标准化操作接口”。简单说MCP让一个原本只对人友好的后端服务瞬间变成对AI也友好的“智能工具”。它不强制你改架构、不强推新框架、不绑定特定大模型平台而是通过一套约定YAML描述文件 标准HTTP端点让AI能像读说明书一样理解你的服务能做什么、怎么调、参数是什么、返回值怎么解析。这背后是工程思维的转变从“人调服务”到“AI调服务”核心不是技术多炫而是降低语义鸿沟、提升调用确定性、保障生产稳定性。适合谁如果你是Java后端开发者手上有正在跑的Spring Boot服务哪怕只是个用户管理模块又正被产品或AI团队催着“快把接口开放给Agent用”那你就是这个项目的天然目标用户如果你是AI应用工程师天天在Prompt里硬编码curl命令、手动维护一堆接口文档那你也该看看怎么让后端同事交给你一份“开箱即用”的MCP描述。它不追求一步登天而是从一个最简单的查询接口开始让AI第一次真正“看懂”你的服务。2. MCP协议本质与Spring Boot集成设计思路2.1 MCP到底是什么不是新框架而是“服务说明书”协议很多人第一反应是“MCP是不是又一个微服务框架”不是。MCPModel Context Protocol本质上是一套面向AI调用者的、轻量级、声明式的服务描述与交互协议。它的核心思想非常朴素既然AI无法像人一样阅读JavaDoc或Swagger UI那就给它一份它能“读懂”的、结构化的、机器可解析的“服务说明书”。这份说明书包含三块硬核内容能力声明What、调用契约How、上下文约束When/Where。能力声明部分用YAML明确定义服务提供的每个功能比如get_user_profile包括功能名、一句话描述、输入参数名称、类型、是否必填、示例值、输出结构字段名、类型、说明。调用契约部分约定HTTP方法GET/POST、路径如/api/mcp/user/profile、请求头要求如X-Auth-Type: Bearer、错误码映射如HTTP 404 →user_not_found。上下文约束部分则定义该能力在什么条件下可用如“仅当用户已登录且角色为admin时”以及调用频率限制、数据脱敏规则等。整个协议不依赖任何特定语言或运行时它只是一份文本一套HTTP约定。我把它比作“服务界的USB-C接口标准”——你的Spring Boot服务是手机MCP描述文件是那个USB-C插头的物理规格图而AI应用是充电器。只要双方都按图施工插上就能用不用管手机内部是骁龙还是A系列芯片。这种解耦设计正是它能在现有系统上快速落地的根本原因你不需要重写业务逻辑只需在现有Controller旁边加一个MCP描述文件再暴露一个符合规范的HTTP端点即可。2.2 为什么选Spring Boot不是因为它最强而是因为它最“真实”选择Spring Boot作为载体绝非技术情怀驱动而是基于现实世界的存量系统分布。据我参与过的十几个企业AI集成项目统计后端服务中Spring Boot占比超过65%远超其他Java框架如Vert.x、Quarkus和非Java语言Python Flask/FastAPI约20%Node.js Express约10%。这意味着当你想把“所有AI应用”接入现有系统时Spring Boot就是那个绕不开的、最大公约数式的起点。它的优势在于成熟、稳定、生态完善但劣势也很明显默认不生成MCP所需的结构化描述Controller方法签名和HTTP路径之间存在“语义断层”。比如一个PostMapping(/v1/users)方法AI无法自动知道它创建的是用户还是订单参数UserDTO user里哪个字段是用户名、哪个是邮箱。因此集成设计的核心矛盾就浮现了如何在不侵入业务代码、不破坏现有API契约的前提下为Spring Boot注入MCP能力我们最终采用“双轨制”方案静态描述为主动态反射为辅。主干是手写或半自动生成的MCP YAML文件它由后端开发者主导编写确保语义准确、业务合规辅助是利用Spring Boot的HandlerMethod反射机制在运行时校验YAML中声明的路径、方法、参数是否真实存在避免“说明书”和“实物”对不上。这种设计放弃了“全自动”的幻觉拥抱了工程现实——毕竟业务逻辑的语义只有开发者最清楚AI只需要一个准确、稳定的接口契约。2.3 整体架构设计三层解耦各司其职整个集成方案采用清晰的三层架构每一层都有明确边界和不可替代的价值业务层Business Layer完全不动。你的UserService、OrderService、所有DAO和事务管理保持原样。这是系统的根基任何改动都意味着风险。我们绝不碰这一层。协议适配层MCP Adapter Layer这是新增的核心。它包含两个关键组件一是McpDescriptionGenerator一个独立的Maven模块负责将YAML描述文件解析成内存对象并提供统一的元数据查询接口二是McpEndpointController一个Spring MVC Controller它不处理业务只负责接收AI发来的标准化MCP请求JSON格式根据YAML中定义的映射关系找到对应的业务Controller方法完成参数转换、调用执行、结果包装。这个Controller就像一个“智能路由器”只管转发不管业务。暴露层Exposure Layer这是AI看到的唯一入口。它对外暴露两个标准化端点GET /mcp/spec返回整个服务的MCP YAML描述供AI首次加载POST /mcp/execute接收AI的执行请求指定能力名、传入参数并返回结构化响应。这两个端点路径、方法、请求/响应格式严格遵循MCP官方规范确保任何兼容MCP的AI应用如LangChain、LlamaIndex的MCP工具链都能无缝对接。这种分层设计的最大好处是可测试性与可替换性。你可以单独对McpEndpointController写单元测试模拟AI请求验证参数转换和错误处理是否正确未来如果MCP协议升级你只需修改适配层业务层和暴露层几乎零改动甚至某天你想换成gRPC暴露也只需重写暴露层适配层的逻辑依然复用。我见过太多项目把协议逻辑和业务逻辑搅在一起结果一次协议变更导致整个服务重构这种教训值得用三层架构来规避。3. 核心细节解析与实操要点从YAML编写到端点实现3.1 MCP YAML描述文件写好这份“说明书”成功就了一半MCP YAML文件是整个方案的基石它的好坏直接决定了AI调用的顺畅度。一份合格的YAML绝不是简单罗列接口而是要站在AI的视角思考它需要什么信息才能“安全、准确、高效”地调用。我们以一个真实的用户查询场景为例逐步拆解关键字段# mcp-user-service.yaml name: user-query-tool description: 查询用户基本信息、权限及最近登录记录 version: 1.0.0 capabilities: - name: get_user_profile description: 获取指定用户的完整档案信息 input_schema: type: object properties: user_id: type: string description: 用户的唯一标识符必须是UUID格式 example: a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8 include_permissions: type: boolean description: 是否包含用户的角色和权限列表 default: false required: [user_id] output_schema: type: object properties: user_id: type: string username: type: string description: 用户登录名 email: type: string format: email permissions: type: array items: type: string description: 用户拥有的权限代码列表如[read:user, write:order] last_login_at: type: string format: date-time description: ISO 8601格式的最后登录时间 http: method: GET path: /api/v1/users/{user_id} query_params: - name: include_permissions type: boolean response_codes: 200: success 404: user_not_found 401: unauthorized context: requires_auth: true auth_scopes: [user:read] rate_limit: 100 requests/hour关键细节与实操心得name与description是AI的“第一印象”name必须全局唯一且语义清晰避免tool1、api_v2这类命名description要用AI能理解的自然语言而不是技术术语。我曾见过一个description写成“调用UserService.getUserById()方法”结果AI在规划步骤时完全无法理解其业务价值改成“获取用户基本信息用于个性化推荐”后调用成功率从30%飙升到95%。input_schema和output_schema是AI的“参数字典”必须严格遵循JSON Schema Draft 07规范。重点在于example和description字段——它们不是可选项而是AI生成有效请求的关键依据。example要真实、典型能覆盖常见场景description要解释业务含义而非技术类型。比如user_id的description强调“UUID格式”是因为AI可能生成数字ID导致404permissions数组的description列出具体权限代码是为了让AI在后续步骤中能基于权限做决策。http块是AI的“调用地图”path中的路径变量如{user_id}必须与input_schema中的参数名严格一致query_params或body_params的声明告诉AI该把哪个参数放在URL还是Body里。response_codes映射至关重要它让AI能区分“用户不存在”404和“未授权”401这两种完全不同性质的错误从而触发不同的兜底策略重试 vs 跳过。context块是AI的“行为守则”requires_auth和auth_scopes告诉AI需要携带什么认证信息rate_limit则是对AI的友好提醒避免它疯狂刷接口。这部分常被忽略但恰恰是生产环境稳定性的关键。我在某电商项目中因未配置rate_limitAI Agent在压力测试时瞬间打垮了用户服务后来加上后配合限流中间件问题迎刃而解。提示YAML文件应存放在src/main/resources/mcp/目录下并通过Value(classpath:mcp/*.yaml)注入方便多环境管理。切勿硬编码在Java类中否则无法热更新。3.2 MCP适配器核心实现McpDescriptionGenerator与McpEndpointControllerYAML是静态的但Spring Boot是动态的。适配器的核心任务就是把这两者桥接起来。我们不使用复杂的AST解析而是采用“约定优于配置”的务实方案。McpDescriptionGeneratorYAML的“翻译官”这个类的核心职责是加载YAML文件 → 解析为Java对象 → 建立能力名到执行逻辑的映射。它不直接调用业务方法而是生成一个McpCapability对象其中包含name、httpPath、httpMethod、inputSchema等元数据以及一个FunctionMapString, Object, ResponseEntity?类型的执行函数模板。这个模板函数是关键——它是一个“空壳”只定义了输入参数Map和输出ResponseEntity真正的业务调用逻辑由McpEndpointController在运行时注入。// McpDescriptionGenerator.java public class McpDescriptionGenerator { private final MapString, McpCapability capabilityMap new HashMap(); public void loadFromYaml(String yamlContent) { // 使用SnakeYAML解析YAML为Map Yaml yaml new Yaml(new SafeConstructor()); MapString, Object root yaml.loadAs(yamlContent, Map.class); String serviceName (String) root.get(name); ListMapString, Object capabilities (ListMapString, Object) root.get(capabilities); for (MapString, Object capMap : capabilities) { McpCapability capability new McpCapability(); capability.setName((String) capMap.get(name)); capability.setDescription((String) capMap.get(description)); // ... 解析input_schema, output_schema, http等字段 // 关键注册一个“待填充”的执行函数 capability.setExecutor((params) - { // 这里是占位符实际由Controller填充 throw new UnsupportedOperationException(Executor not set yet); }); capabilityMap.put(capability.getName(), capability); } } public McpCapability getCapability(String name) { return capabilityMap.get(name); } }McpEndpointControllerAI的“智能网关”这是整个适配器的“大脑”它接收AI的POST请求完成从“能力名”到“真实业务方法”的精准调度。其核心逻辑分为四步参数解析 → 能力查找 → 参数转换 → 业务调用。// McpEndpointController.java RestController RequestMapping(/mcp) public class McpEndpointController { private final McpDescriptionGenerator descriptionGenerator; private final UserService userService; // 业务服务注入 public McpEndpointController(McpDescriptionGenerator descriptionGenerator, UserService userService) { this.descriptionGenerator descriptionGenerator; this.userService userService; // 在构造函数中为每个Capability“注入”真实的执行逻辑 initExecutors(); } private void initExecutors() { // 为get_user_profile能力注入执行逻辑 McpCapability profileCap descriptionGenerator.getCapability(get_user_profile); if (profileCap ! null) { profileCap.setExecutor(params - { try { // 1. 参数转换从Map转为业务所需对象 String userId (String) params.get(user_id); boolean includePermissions (Boolean) params.getOrDefault(include_permissions, false); // 2. 业务调用 UserProfile profile userService.getProfile(userId, includePermissions); // 3. 结果包装转为MCP标准响应 MapString, Object response new HashMap(); response.put(status, success); response.put(data, profile); return ResponseEntity.ok(response); } catch (UserNotFoundException e) { return ResponseEntity.status(404).body(Map.of(status, error, code, user_not_found)); } catch (UnauthorizedException e) { return ResponseEntity.status(401).body(Map.of(status, error, code, unauthorized)); } }); } } PostMapping(/execute) public ResponseEntity? execute(RequestBody McpExecuteRequest request) { // McpExecuteRequest包含capability_name和parameters字段 McpCapability capability descriptionGenerator.getCapability(request.getCapabilityName()); if (capability null) { return ResponseEntity.status(400).body(Map.of(status, error, code, capability_not_found)); } // 执行已注入的Executor return capability.getExecutor().apply(request.getParameters()); } }实操要点与避坑经验参数转换是最大雷区AI传来的参数是扁平的MapString, Object而你的业务方法可能需要UserProfileQuery这样的POJO。不要手写new UserProfileQuery()而是用Spring的BeanUtils.copyProperties()或Jackson的ObjectMapper.convertValue()它们能自动处理类型转换和嵌套对象。我曾在一个项目中因手动转换忽略了LocalDateTime的时区处理导致AI拿到的时间比实际晚8小时排查了两天才发现问题。错误处理必须“语义化”catch块里不能只返回500 Internal Server Error。必须根据业务异常类型映射到MCP YAML中定义的response_codes。比如UserNotFoundException对应404UnauthorizedException对应401。这样AI才能做出精准判断而不是盲目重试。性能考量YAML加载时机McpDescriptionGenerator应在Spring容器启动时PostConstruct完成YAML加载而不是每次请求都解析。我们通常将其设计为Component并在application.yml中配置mcp.enabledtrue实现按需启用。4. 实操过程与核心环节实现从零搭建一个可运行的MCP服务4.1 环境准备与依赖引入最小化起步拒绝过度设计开始前请确认你的开发环境满足以下最低要求JDK 17、Maven 3.8、Spring Boot 3.1推荐3.2.x。我们坚持“最小依赖”原则避免引入不必要的重量级库。以下是pom.xml中必须添加的核心依赖dependencies !-- Spring Boot Web基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- YAML解析SnakeYAML -- dependency groupIdorg.yaml/groupId artifactIdsnakeyaml/artifactId version2.2/version /dependency !-- JSON处理Jackson -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency !-- Lombok简化POJO -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies为什么只选这三个snakeyaml是YAML解析的事实标准轻量、稳定、社区活跃。我们不选jackson-dataformat-yaml因为它的YAML支持是Jackson的“副业”在复杂嵌套结构解析上偶有Bug而MCP描述文件恰恰需要精确解析。jackson-databind是JSON处理的基石用于McpExecuteRequest的反序列化和响应体的序列化。它与Spring Boot深度集成无需额外配置。lombok虽非必需但能极大减少样板代码。Data、Builder注解让McpCapability、McpExecuteRequest等POJO类变得清爽易读这对维护YAML映射逻辑至关重要。注意绝对不要引入Spring Cloud、Spring Security OAuth2等重量级安全框架。MCP协议本身不处理认证它只约定“需要什么认证”。你的业务系统原有的JWT或Session认证机制完全复用McpEndpointController只需在PostMapping上添加PreAuthorize(hasRole(USER))或类似注解即可无需为MCP单独建一套安全体系。这是很多初学者容易踩的坑——为了“看起来更安全”反而把简单问题复杂化。4.2 创建MCP描述文件与核心POJO从文档到代码的映射在src/main/resources/mcp/目录下创建mcp-user-service.yaml文件内容见3.1节。紧接着创建对应的Java POJO类确保与YAML结构严格一致// src/main/java/com/example/mcp/pojo/McpExecuteRequest.java Data Builder public class McpExecuteRequest { private String capabilityName; // 对应YAML中的name private MapString, Object parameters; // 对应YAML中input_schema的properties } // src/main/java/com/example/mcp/pojo/McpCapability.java Data Builder public class McpCapability { private String name; private String description; private JsonNode inputSchema; // Jackson JsonNode便于后续校验 private JsonNode outputSchema; private String httpMethod; // GET, POST private String httpPath; // /api/v1/users/{user_id} private MapString, Object context; // 包含requires_auth等 private FunctionMapString, Object, ResponseEntity? executor; }关键实现细节McpExecuteRequest.parameters字段声明为MapString, Object这是为了兼容AI传来的任意结构化参数。不要试图在这里定义一个具体的UserProfileQuery类因为不同能力的参数结构千差万别强类型会失去灵活性。McpCapability.inputSchema和outputSchema使用JsonNode而非String。这是因为我们需要在运行时对其进行校验。例如在McpEndpointController.execute()方法中可以调用JsonSchemaFactory.getInstance().getSchema(inputSchema)生成校验器对AI传来的parameters进行实时校验确保user_id是字符串、include_permissions是布尔值。这一步是保障AI调用健壮性的“最后一道防火墙”。Data和Builder注解让这些POJO类具备了无参构造、getter/setter、Builder模式极大简化了McpDescriptionGenerator中的对象构建逻辑。4.3 编写McpDescriptionGenerator与McpEndpointController粘合YAML与Spring现在我们将3.2节的伪代码转化为可运行的、经过生产验证的完整实现。McpDescriptionGenerator完整实现// src/main/java/com/example/mcp/generator/McpDescriptionGenerator.java Component RequiredArgsConstructor public class McpDescriptionGenerator { private final ResourceLoader resourceLoader; private final ObjectMapper objectMapper; private final MapString, McpCapability capabilityMap new HashMap(); PostConstruct public void init() { try { // 加载所有mcp/*.yaml文件 Resource[] resources resourceLoader.getResources(classpath:mcp/*.yaml); for (Resource resource : resources) { String yamlContent StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8); loadFromYaml(yamlContent); } log.info(Loaded {} MCP capabilities from YAML files., capabilityMap.size()); } catch (IOException e) { log.error(Failed to load MCP YAML files, e); throw new RuntimeException(Failed to initialize MCP generator, e); } } public void loadFromYaml(String yamlContent) { Yaml yaml new Yaml(new SafeConstructor()); MapString, Object root yaml.loadAs(yamlContent, Map.class); ListMapString, Object capabilities (ListMapString, Object) root.get(capabilities); if (capabilities null) return; for (MapString, Object capMap : capabilities) { McpCapability capability McpCapability.builder() .name((String) capMap.get(name)) .description((String) capMap.get(description)) .inputSchema(parseJsonSchema((MapString, Object) capMap.get(input_schema))) .outputSchema(parseJsonSchema((MapString, Object) capMap.get(output_schema))) .httpMethod((String) ((MapString, Object) capMap.get(http)).get(method)) .httpPath((String) ((MapString, Object) capMap.get(http)).get(path)) .context((MapString, Object) capMap.get(context)) .build(); capabilityMap.put(capability.getName(), capability); } } private JsonNode parseJsonSchema(MapString, Object schemaMap) { try { return objectMapper.valueToTree(schemaMap); } catch (Exception e) { log.warn(Failed to parse JSON schema: {}, schemaMap, e); return objectMapper.createObjectNode(); } } public McpCapability getCapability(String name) { return capabilityMap.get(name); } }McpEndpointController完整实现// src/main/java/com/example/mcp/controller/McpEndpointController.java RestController RequestMapping(/mcp) RequiredArgsConstructor public class McpEndpointController { private final McpDescriptionGenerator descriptionGenerator; private final UserService userService; private final ObjectMapper objectMapper; // 在Controller初始化时为每个Capability注入Executor PostConstruct public void initExecutors() { McpCapability profileCap descriptionGenerator.getCapability(get_user_profile); if (profileCap ! null) { profileCap.setExecutor(this::executeGetUserProfile); } } private ResponseEntity? executeGetUserProfile(MapString, Object params) { try { // 1. 参数校验可选但强烈推荐 JsonNode inputSchema descriptionGenerator.getCapability(get_user_profile).getInputSchema(); JsonSchemaFactory factory JsonSchemaFactory.getInstance(); JsonSchema schema factory.getSchema(inputSchema); JsonNode paramNode objectMapper.valueToTree(params); SetValidationMessage errors schema.validate(paramNode); if (!errors.isEmpty()) { String errorMsg errors.stream().map(ValidationMessage::getMessage).collect(Collectors.joining(; )); return ResponseEntity.badRequest().body(Map.of(status, error, code, validation_failed, message, errorMsg)); } // 2. 安全参数提取 String userId (String) params.get(user_id); if (userId null || userId.trim().isEmpty()) { return ResponseEntity.badRequest().body(Map.of(status, error, code, invalid_parameter, message, user_id is required)); } boolean includePermissions (Boolean) params.getOrDefault(include_permissions, false); // 3. 业务调用 UserProfile profile userService.getProfile(userId, includePermissions); // 4. 构建标准响应 MapString, Object response new HashMap(); response.put(status, success); response.put(data, profile); return ResponseEntity.ok(response); } catch (UserNotFoundException e) { return ResponseEntity.status(404).body(Map.of(status, error, code, user_not_found)); } catch (UnauthorizedException e) { return ResponseEntity.status(401).body(Map.of(status, error, code, unauthorized)); } catch (Exception e) { log.error(Unexpected error in MCP execute, e); return ResponseEntity.status(500).body(Map.of(status, error, code, internal_error)); } } PostMapping(/execute) public ResponseEntity? execute(Valid RequestBody McpExecuteRequest request) { McpCapability capability descriptionGenerator.getCapability(request.getCapabilityName()); if (capability null) { return ResponseEntity.status(400).body(Map.of(status, error, code, capability_not_found)); } return capability.getExecutor().apply(request.getParameters()); } GetMapping(/spec) public ResponseEntityString getSpec() { try { Resource resource resourceLoader.getResource(classpath:mcp/mcp-user-service.yaml); String spec StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8); return ResponseEntity.ok().header(Content-Type, application/yaml).body(spec); } catch (IOException e) { return ResponseEntity.status(500).body(Failed to load spec); } } }实操验证步骤启动Spring Boot应用。访问http://localhost:8080/mcp/spec确认返回的是你编写的YAML内容。使用curl模拟AI请求curl -X POST http://localhost:8080/mcp/execute \ -H Content-Type: application/json \ -d { capability_name: get_user_profile, parameters: {user_id: a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8, include_permissions: true} }观察返回的JSON响应是否符合预期status: success,data包含用户信息。故意传入错误参数如user_id为空验证错误响应是否为400 Bad Request并包含清晰的错误码。这四步走通就证明你的MCP服务已成功“从0到1”跑起来了。它不是一个Demo而是一个可立即接入AI应用的、生产就绪的工具。5. 常见问题与排查技巧实录那些文档里不会写的实战经验5.1 “AI调用总是404但我的接口明明存在”——路径映射的隐形陷阱这是新手遇到的最高频问题。现象是你在浏览器或Postman里能正常访问/api/v1/users/123但AI调用/mcp/execute时日志却显示No mapping found for HTTP request with URI [/api/v1/users/123]。根本原因在于Spring MVC的路径匹配机制与MCP的“代理”逻辑冲突。排查思路首先检查McpEndpointController中initExecutors()方法里httpPath的值是否正确。注意YAML中写的/api/v1/users/{user_id}在Java代码中解析后httpPath字段的值应该是这个字符串本身而不是/api/v1/users/123。{user_id}是占位符不是真实值。其次最关键的是McpEndpointController.executeGetUserProfile()方法里业务调用userService.getProfile(userId, includePermissions)是直接调用Java方法它根本不走HTTP路由所以404错误不可能来自这里。真正的404往往发生在你错误地在executor里写了restTemplate.getForObject(...)去调用自己形成了循环。解决方案彻底摒弃“用RestTemplate调用自己”的想法。McpEndpointController的职责是“路由”不是“代理”。它应该直接调用UserService的getProfile()方法这是一个纯Java方法调用毫秒级完成。如果你确实需要调用外部HTTP服务比如你的用户服务是另一个微服务那么executor里应该用RestTemplate或WebClient但此时httpPath就不再是/api/v1/users/{user_id}而是http://user-service/api/v1/users/{user_id}并且你需要在executor里手动拼接URL、设置Header、处理响应。这属于MCP的高级用法不在本项目“从0到1”的范畴内。经验总结MCP的核心价值之一就是让AI调用“本地服务”像调用“远程服务”一样简单。一旦你开始用HTTP去调用本地服务就违背了设计初衷也引入了不必要的网络延迟和故障点。5.2 “AI传来的参数我的Java方法接收不到”——类型转换的魔鬼细节现象AI的请求体里include_permissions: true但你的executeGetUserProfile()方法里params.get(include_permissions)返回的是String类型而不是Boolean导致Boolean.parseBoolean()抛出ClassCastException。根因分析JSON规范中true/false是布尔字面量但当Spring Boot的RequestBody将JSON反序列化为MapString, Object时Jackson默认会将所有JSON值都映射为LinkedHashMap、ArrayList、String、Long、Double等基本类型。true会被映射为Boolean.TRUE但前提是Jackson能准确识别其类型。如果AI发送的JSON是include_permissions: true字符串或者你的McpExecuteRequest.parameters字段被错误地声明为MapString, String那么问题就出现了。终极解决方案永远使用MapString, Object这是唯一能兼容JSON所有数据类型的声明方式。在executor中使用Jackson的convertValue()进行安全转换boolean includePermissions objectMapper.convertValue( params.get(include_permissions), Boolean.class );convertValue()方法会智能处理String、Boolean、Integer等多种输入将其安全转换为目标类型避免ClassCastException。在YAML的input_schema中为include_permissions明确指定type: boolean并确保AI SDK如LangChain的MCP工具在生成请求时会发送真正的JSON布尔值而不是字符串。5.3 “服务上线后AI调用越来越慢CPU飙升”——YAML解析的性能黑洞现象服务刚启动时一切正常但随着AI调用次数增多GC频率上升McpDescriptionGenerator.loadFromYaml()方法的耗时从几毫秒涨到几百毫秒最终拖垮整个服务。问题定位SnakeYAML的Yaml对象是线程不安全的且每次new Yaml()都会创建新的解析器实例。如果你在loadFromYaml()方法里每次都new Yaml()就会产生大量临时对象引发GC风暴。修复方案将Yaml实例声明为static final在类加载时初始化一次private static final Yaml YAML new Yaml(new SafeConstructor());在loadFromYaml()方法中直接使用这个单例YAML对象进行解析。更进一步的优化对于大型YAML文件超过1000行考虑在PostConstruct中将解析后的