
去年底一个同事问我怎么让 Claude 直接查我们系统的订单状态。我的第一反应是写个 Function Calling把接口描述塞进 Prompt 里让模型按格式返回参数再在业务层拦截调用。折腾了两天跑通了但代码丑得不忍看——换一个模型就要重新适配一套格式换 Cursor 就要再折腾一遍。后来发现 MCP五分钟把同一个接口接上了 Claude DesktopCursor 里也直接可用代码就加了三个注解。这篇把这个过程完整写出来代码可以直接复制跑。MCP 和 Function Calling 差在哪不搞清楚后面会踩坑两个技术经常被混为一谈但本质上解决的不是同一个问题。Function Calling 是Prompt 级别的能力。你在请求里告诉模型「有这几个函数参数格式如下」模型决定要不要调用返回一个结构化调用指令你的代码去执行再把结果塞回 Prompt。整个过程绑定在一次模型请求里工具描述随 Prompt 走换个模型得重写一遍格式换个 AI 客户端得重新适配一套。MCPModel Context Protocol是协议级别的能力。你的服务独立部署成一个 MCP Server任何支持 MCP 协议的客户端——Claude Desktop、Cursor、自己写的 Agent——都能发现和调用你的工具不依赖具体模型一次开发到处复用。Anthropic 在 2024 年 11 月开源 MCP 规范目前 Claude、ChatGPT、VS Code Copilot、Cursor、Gemini 等主流 AI 工具都已支持已有 5000 个公开 MCP Server。可以理解为 AI 工具调用的 USB-C 接口以前每根线的接口不一样现在统一了。Function Calling vs MCP 架构对比示意图MCP 协议里有三种原语ToolAI 可以执行的操作有副作用、ResourceAI 可以读取的数据幂等只读、Prompt预置的提示词模板。Spring AI 为三者都提供了对应注解。环境准备加依赖就行Spring AI 1.0.0-M6 开始提供 MCP Server Starters根据传输协议选一个协议怎么选后面讲!-- 生产首选WebMVC SSE 协议支持多客户端同时连接 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId /dependency!-- 响应式技术栈用这个 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webflux/artifactId /dependency!-- 纯本地工具不需要网络Claude Desktop 用子进程方式拉起 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId /dependencyBOM 版本管理推荐避免版本冲突dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0-M7/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement前置要求Java 17Spring Boot 3.2。McpTool把现有方法变成 AI 可调用的工具在任何 Spring Bean 的方法上加McpToolSpring AI 自动扫描注册成 MCP ToolJSON Schema 也是自动生成的不需要手写。实际例子把订单查询接口暴露出去import org.springframework.ai.mcp.spring.annotation.McpTool; import org.springframework.ai.mcp.spring.annotation.McpToolParam; import org.springframework.stereotype.Component; Component publicclass OrderMcpTools { privatefinal OrderService orderService; public OrderMcpTools(OrderService orderService) { this.orderService orderService; } McpTool( name queryOrder, description 根据订单号查询订单状态和详情支持查询最近 90 天内的订单。 返回字段包括订单状态、实付金额、下单时间、收货地址、物流单号。 ) public OrderDetail queryOrder( McpToolParam(description 订单号格式为 ORD-XXXXXXXXXX10 位数字后缀, required true) String orderId, McpToolParam(description 是否返回商品明细列表默认 falsetrue 时额外返回每个商品的名称和数量) boolean includeItems ) { // 直接复用现有业务逻辑不需要改一行 return orderService.getOrderDetail(orderId, includeItems); } McpTool( name listRecentOrders, description 查询用户最近的订单列表返回最多 20 条按下单时间倒序排列 ) public ListOrderSummary listRecentOrders( McpToolParam(description 用户 IDLong 类型, required true) Long userId, McpToolParam(description 查询天数范围1-90 之间默认 30) int days ) { return orderService.listRecentOrders(userId, days); } }description的写法很关键——这是模型决定「要不要调用这个工具、传什么参数」的主要依据不是给开发者看的注释。「订单号格式为 ORD-XXXXXXXXXX10 位数字后缀」比「the order ID」有用得多模型在推断参数时会参考这个描述。application.yml里开启注解扫描spring: ai: mcp: server: type: SYNC # SYNC 同步 / ASYNC 响应式 protocol: SSE # 传输协议后面详细讲 annotation-scanner: enabled: true正常启动 Spring Boot 应用MCP Server 就跑起来了。Spring AI MCP Server 内部架构图踩坑记录返回值必须可 JSON 序列化。如果OrderDetail里有LocalDateTime要确保 Jackson 加了时间模块jackson-datatype-jsr310否则模型收到的是序列化异常而不是工具执行结果。更隐蔽的是OptionalT类型Jackson 默认不处理直接返回T或null比较稳。传输协议选型STDIO vs SSE vs Streamable-HTTPSpring AI MCP Server 支持三种传输协议选错了要么本地能用生产跑不了要么部署方式不对协议适用场景Starter 依赖yml 配置STDIO本地工具、Claude Desktop 直接拉起进程spring-ai-starter-mcp-serverstdio: trueSSE生产部署多 AI 客户端同时连接spring-ai-starter-mcp-server-webmvcprotocol: SSEStreamable-HTTP云原生、K8s 横向扩容、负载均衡场景spring-ai-starter-mcp-server-webmvcprotocol: STREAMABLESTDIO 最容易踩的坑Claude Desktop 用 STDIO 协议时是把你的 Spring Boot 应用当成子进程直接拉起的。这意味着服务不能有额外的 HTTP 端口监听会报冲突而且绝对不能用System.out.println——标准输出被 MCP 协议占用乱写会直接污染消息帧导致工具调用解析失败。把所有日志配到文件或stderr就好!-- logback.xmlSTDIO 模式下把控制台输出全部转到 STDERR -- appender nameSTDERR classch.qos.logback.core.ConsoleAppender targetSystem.err/target encoder pattern%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender生产环境用 SSE保持持久 HTTP 连接服务端可以主动推送工具执行进度。多个 AI 客户端可以同时连服务正常作为 Spring Boot 应用独立部署和现有 REST 接口共用同一个端口。Streamable-HTTP 适合无状态部署每次请求独立没有持久连接K8s 横向扩容时不需要考虑连接亲和性。如果你的服务部署在有负载均衡的环境里优先考虑这个协议。让 Claude Desktop 找到你的 MCP ServerSTDIO 模式本地调试最方便找到 Claude Desktop 配置文件——macOS 在~/Library/Application Support/Claude/claude_desktop_config.jsonWindows 在%APPDATA%\Claude\claude_desktop_config.json——加入{ mcpServers: { order-service: { command: java, args: [ -jar, /absolute/path/to/your-mcp-server.jar ] } } }SSE 模式服务已独立部署{ mcpServers: { order-service: { url: http://localhost:8080/sse } } }如果服务需要鉴权在请求头里带 Token{ mcpServers: { order-service: { url: http://your-server/sse, headers: { Authorization: Bearer your-token } } } }重启 Claude Desktop 后工具栏会出现锤子图标点开能看到你注册的所有 MCP Tool 名称和描述。直接在对话框里问「帮我查一下订单 ORD-20260425001 的状态」Claude 会自动识别出需要调用queryOrder工具填入参数执行再把结果转成自然语言回复。Claude Desktop 调用 MCP Server 完整时序图进阶McpResource 暴露只读数据McpTool适合「执行操作」下单、更新状态McpResource适合「读取数据」商品详情、配置项。区别在语义和使用时机Resource 是幂等只读的AI 客户端在构建上下文时会主动拉取而不是等到要执行操作时才调用。Component publicclass ProductMcpResources { McpResource( uri product://{productId}/info, name 商品信息, description 根据商品 ID 获取商品基本信息包括价格、库存状态、分类 ) public String getProductInfo(String productId) { Product product productService.findById(productId); // Resource 返回字符串JSON 格式或结构化文本都行 return objectMapper.writeValueAsString(product); } McpResource( uri config://feature-flags, name 功能开关配置, description 返回当前所有功能开关的状态供 AI 了解系统当前支持的能力 ) public String getFeatureFlags() { return featureFlagService.getAllFlags().toString(); } }实际用下来McpResource在「让 AI 了解你的系统状态」这个场景特别有用。比如先让 AI 读一遍功能开关配置再决定推荐哪些操作逻辑上比每次调 Tool 干净。常见问题Q现有服务改造需要动多少代码加依赖、写一个Component包装类调用现有 Service在方法上加注解。不需要修改任何现有业务逻辑原来的 REST 接口照常工作两套并存互不影响。Qdescription 怎么写才有效受众是 AI 模型不是开发者。要说清楚工具做什么、适合什么场景、参数有什么限制格式、范围、枚举值、默认值。「查询订单」比「根据订单号查询仅支持 90 天内返回状态和物流信息」少了太多有效信息模型填参数时更容易出错。Q和 Spring AI 的 FunctionCallback 是什么关系完全独立的两套机制。FunctionCallback是你自己写 AI 应用时给模型 Prompt 绑定工具Function CallingMcpTool是把你的服务暴露给外部 AI 客户端MCP 协议。可以同时用——比如你的服务既是一个 MCP Server 供外部客户端调内部同时用FunctionCallback驱动自己的业务 AI 流程。QSTDIO 和 SSE 模式下日志怎么配STDIO 模式下System.out会污染 MCP 消息流必须把所有日志重定向到文件或stderr把 logback.xml 里的 Console Appender target 改成System.err。SSE 模式没这个限制按正常配置就好。Q生产环境需要鉴权吗MCP 协议本身没有内置鉴权默认任何人都能调你的 MCP Server。生产环境建议在 Spring Security 层加 HTTP Bearer Token 或 API Key 验证然后在客户端配置文件里的headers字段带上凭证。MCP 现在还在快速演进Streamable-HTTP 是最近才定稿的传输协议比 SSE 更适合云原生无状态部署场景。Spring AI 的注解 API 在各个 milestone 版本之间有调整遇到问题先确认用的版本和文档是否对应。说到底MCP 解决的核心问题是「工具定义和模型解耦」——你写一次任何支持 MCP 的 AI 客户端都能用不用跟着每个模型的格式调整。这个方向是对的生态成熟度也在快速追上来。