ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【Spring AI MCP】一、MCP 原理详解:从协议握手到工具调用的完整链路拆解

【Spring AI MCP】一、MCP 原理详解:从协议握手到工具调用的完整链路拆解 1. 从一次“工具调用失败”说起MCP 到底在解决什么如果你最近在写 Spring AI 的 Agent 或者工具调用大概率遇到过这种场景本地写了一个Tool方法模型在对话里死活不调用或者调用了但参数对不上返回一个空对象。你翻日志发现请求里 tools 字段格式和模型期望的不一致改完这边那边又崩了。这个问题的根子不在 Spring AI而在于“模型怎么知道有哪些工具、怎么把参数传回来”这件事过去每个平台各写各的。MCP全称 Model Context Protocol就是把这个过程标准化的一套通信协议。你可以把它理解成 AI 世界的 USB 接口以前每个外设工具、检索、多模态输入都要配一根专用线现在统一成一个插口客户端和服务端按同一套 schema 握手、协商能力、注册工具、发起调用、回传结果。Spring AI 从 1.0 开始把 MCP 做进了 starter 和注解体系让 Java 开发者既能当客户端去消费别人的 MCP Server也能把自己的 Spring 服务暴露成 MCP Server 给别的 AI 用。这篇聚焦的是原理链路一次工具调用从客户端发起到服务端执行再回到模型上下文中间到底经过了哪些环节。适合已经写过 Spring AI 基础对话、想搞清楚 MCP 通信全貌的人。读完你能拿到一份可复制的 MCP 客户端配置骨架并亲手验证一次工具调用是否真的走通了。至于模型侧的统一入口我会在接入部分说明怎么用 TaoToken 的 Key 和 API 通道把请求发出去避免在多个平台之间来回切配置。2. 协议握手与能力协商MCP 连接建立的第一公里MCP 的连接不是“打开就发请求”它有一个明确的初始化阶段。客户端和服务端要先交换各自支持的协议版本、能力集capabilities确认双方都能接受之后才进入正常的请求-响应循环。这一步在 Spring AI 里被封装得比较深但理解它对排查“连上了却没反应”非常关键。2.1 初始化请求里到底传了什么MCP 的初始化是一个 JSON-RPC 风格的请求核心字段包括protocolVersion、capabilities、clientInfo。客户端告诉服务端我支持哪些能力比如roots文件系统根目录列表变化通知、sampling让服务端反向请求模型生成。服务端回一个serverInfo和它自己的能力比如tools、resources、prompts是否可用。在 Spring AI 的客户端配置里这些通常不需要你手写但你要知道它们存在。比如你用的是 STDIO 传输客户端启动时会拉起一个子进程通过标准输入输出交换这些 JSON 消息如果用 SSE 或 Streamable-HTTP就是走 HTTP 长连接。传输方式不同握手消息的载体不同但内容结构一致。2.2 能力协商决定了后面能调什么协商结果直接决定后续可用功能。如果服务端在capabilities里没有声明tools那客户端就算发了tools/list请求也会被拒绝。我踩过的坑是自己写了一个 MCP Server只实现了资源读取忘了在能力声明里加 tools结果客户端一直报“method not found”。后来把capabilities.tools显式打开工具列表才正常返回。这里有个容易混淆的点MCP 的能力协商和模型本身是否支持 function calling 是两回事。MCP 管的是客户端与服务端之间的能力对齐模型侧的工具调用格式由 Spring AI 在中间做转换。所以即使底层模型对 tools 字段支持得不好只要 MCP 链路通了Spring AI 仍有机会通过提示词降级来兜底。3. 工具注册与调用返回一次完整链路的逐层拆解握手完成后真正的工具调用链路分四步客户端拉取工具列表、模型决定调用哪个工具、客户端把调用请求发给服务端、服务端执行并回传结果。每一步都有对应的 MCP 方法和数据结构下面逐层拆。3.1 工具列表是怎么被“注册”进来的MCP 服务端启动后会把自己能提供的工具以tools/list的形式暴露出来。每个工具包含name、description、inputSchema。inputSchema是 JSON Schema描述参数类型、是否必填。Spring AI 客户端拿到这个列表后会把它转换成模型能理解的工具定义塞进请求的 tools 字段。这里的关键是工具不是硬编码在客户端里的而是运行时从服务端动态拉取。这意味着你改服务端的工具实现客户端重启后就能拿到新列表不需要两边同时改代码。对于多工具 Agent 场景这个动态性很重要。3.2 模型返回 tool_call 之后发生了什么模型在对话中决定调用某个工具时返回的不是最终答案而是一个tool_call结构里面包含工具名和参数 JSON。Spring AI 的 MCP 客户端拦截到这个结构后不会直接执行本地方法而是把它包装成 MCP 的tools/call请求通过已建立的传输通道发给服务端。服务端收到tools/call根据工具名找到对应的处理方法把参数反序列化成方法入参执行然后把返回值序列化成 MCP 响应。响应里包含content数组可以是文本、图片或资源引用。客户端再把这段内容作为工具执行结果追加到对话上下文里发起下一轮模型请求。3.3 返回结果如何回到模型上下文这一步是很多人忽略的工具执行结果不是直接展示给用户的而是作为一条tool角色的消息回到模型。模型看到工具结果后才生成最终的自然语言回答。所以如果你发现工具明明执行了但模型回答里没体现大概率是结果消息的格式不对或者模型没把它当上下文。Spring AI 在这块做了标准化处理但如果你自己拼请求要注意tool_call_id必须和模型返回的 id 对应否则模型无法把结果和调用关联起来。4. 可复制的 MCP 客户端配置骨架下面这份配置基于 Spring AI 的 MCP Client Starter传输方式用 STDIO适合本地起一个 MCP Server 做验证。你只需要替换命令和参数即可。spring: ai: mcp: client: enabled: true name: demo-mcp-client version: 1.0.0 type: SYNC request-timeout: 30s stdio: connections: weather-server: command: java args: - -jar - ./mcp-weather-server.jar对应的 Java 配置类用来注入 MCP 客户端并手动触发一次工具列表拉取Configuration public class McpClientConfig { Bean public CommandLineRunner mcpProbe(McpSyncClient mcpClient) { return args - { ListMcpSchema.Tool tools mcpClient.listTools(); tools.forEach(t - System.out.println(tool: t.name() schema: t.inputSchema())); }; } }如果你用的是 SSE 传输把stdio换成sse配置url指向服务端的 SSE 端点即可。Streamable-HTTP 类似只是端点路径不同。三种传输的握手内容一致区别只在消息怎么传。5. 验证一次工具调用是否真的走通配置写完启动应用观察日志里有没有tools/list的响应。如果工具列表打印出来了说明握手和能力协商通过。接下来做一次真实调用。5.1 用模型对话触发工具调用在 TaoToken 的模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite里选一个支持 function calling 的模型输入一句会触发工具的话比如“帮我查一下北京现在的天气”。如果 MCP 链路正常你会看到模型返回一个工具调用请求而不是直接编一个天气。5.2 观察服务端日志确认执行服务端这边应该打印出收到tools/call的日志以及工具方法的入参。如果服务端没反应检查客户端和服务端的传输通道是否真的建立了STDIO 模式下子进程有没有正常启动。5.3 结果回传后的模型回答工具执行完结果回到模型模型生成最终回答。这时候你看到的天气数据应该是工具返回的真实数据而不是模型幻觉。如果模型回答里说“我无法获取实时天气”说明工具结果没被正确追加到上下文回去检查tool_call_id和消息角色。6. 本篇常见错排查报错一No tool found with name xxx服务端工具名和客户端请求的不一致。检查服务端Tool注解的 name 属性以及客户端拉取到的列表里是否有这个名字。大小写敏感。报错二Connection refused或子进程启动失败STDIO 模式下command和args拼出来的命令在本地能不能直接跑通先在终端手动执行一遍确认 jar 路径和 Java 环境没问题。SSE 模式下检查端口和路径。报错三工具执行了但模型不采纳结果大概率是结果消息格式问题。Spring AI 内部会处理但如果你自定义了 MCP 客户端逻辑确保返回的 content 类型是模型能识别的文本或结构化数据。报错四握手阶段超时request-timeout设得太短或者服务端初始化逻辑太重。先把超时调到 60s 试试再优化服务端启动速度。7. 接入位置与后续动作MCP 链路跑通之后模型侧的请求总得有个统一的出口。我现在的做法是把模型调用统一走 TaoToken 的 API 通道Key 在控制台生成https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite。这样 MCP 客户端负责工具链路模型请求走统一 Key两边解耦换模型不用改 MCP 配置。如果你要长期跑编码类 Agent可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite它针对长时间编码会话做了额度优化。API Key 的生成入口在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite拿到 Key 之后填到 Spring AI 的模型客户端配置里就行。下一步建议你亲手把上面的 STDIO 配置跑一遍观察日志里tools/list和tools/call的完整消息。看懂这两条消息MCP 的通信全貌就基本清楚了。
返回列表