1. 为什么要在 Spring AI 里认真对待 MCP 的 JSON-RPC 层如果你正在用 Spring AI 1.x 做 Java 侧的 AI 应用大概率已经写过ChatClient调模型、用Tool注解暴露本地方法。但一旦工具不在本进程里——比如一个独立的检索服务、一个公司内部的订单查询服务、一个跑在另一台机器上的 Python 脚本——本地Tool就不够用了。这时候需要的是模型上下文协议MCP它把「AI 应用怎么调用外部工具」这件事标准化成了一套基于 JSON-RPC 2.0 的通信约定。MCP 能做什么简单说它让 AI 应用Host通过客户端Client连接到一个个独立的服务器Server服务器把资源、提示模板、工具以标准原语暴露出来。适合谁适合需要在 Java 项目里接入外部工具服务、又不想为每个工具写一套私有适配层的开发者。你可以把它理解成 AI 世界的 USB-C以前每个外部系统都要单独做一根线现在统一成一个接口。我试过在 Spring AI 1.x 里接一个自建的 MCP 服务最开始卡住的不是业务逻辑而是 JSON-RPC 的初始化握手和端点声明——文档里一笔带过实际配置时字段写错一个就静默失败。这篇就把这套配置骨架拆开从依赖到端点声明再到一次真实的工具调用链路验证全部落到可复制的代码。2. TaoToken 前置把模型侧和工具侧解耦在讲 MCP 配置之前先说清楚模型侧怎么接。MCP 解决的是「工具怎么暴露和调用」但工具调用最终还是要模型来决定「调哪个工具、传什么参数」。所以你需要一个能稳定响应工具调用tool calls的模型端点。我这边习惯用 TaoToken 作为模型接入层原因是它同时提供 OpenAI 兼容接口和 Anthropic 兼容接口Spring AI 1.x 里两种ChatModel实现都能直接对接不用为了换模型改 MCP 那层的代码。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 注意这个地址后面不加任何查询参数。具体到配置你需要在 TaoToken 控制台创建一个 API Key然后把它写进 Spring 的配置文件。控制台入口在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite API Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建好之后模型侧的配置大概是这样spring: ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: claude-sonnet-4-5 temperature: 0.2这里base-url指向 TaoToken 的 API 根路径Spring AI 的 OpenAI starter 会自动拼接/v1/chat/completions之类的路径。如果你用的是 Anthropic 兼容模式换成对应的 starter 和base-url即可MCP 那层的代码完全不用动——这正是把模型侧和工具侧解耦的价值。注意API Key 不要硬编码在application.yml里提交到仓库用环境变量或者配置中心注入。MCP 服务器如果也要认证同样走环境变量。3. 可复制配置MCP 客户端与服务端的 JSON-RPC 骨架3.1 依赖引入Spring AI 1.x 对 MCP 的支持拆成了客户端和服务端两个 starter。假设你用的是 Maven先在pom.xml里加dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId /dependency版本跟随你的 Spring AI BOM 走不要单独指定否则容易出现McpClient和McpServer的 API 不匹配。如果你只做客户端连接别人写好的 MCP 服务只引 client starter 就够了服务端 starter 是给「自己暴露工具给别人用」的场景。3.2 服务端声明 JSON-RPC 端点与工具服务端的核心是把一个 Spring Bean 里的方法注册成 MCP 工具。Spring AI 提供了Tool注解配合ToolCallbackProvider暴露出去。下面是一个最小可运行的服务端配置Configuration public class McpServerConfig { Bean public ToolCallbackProvider orderToolProvider(OrderService orderService) { return MethodToolCallbackProvider.builder() .toolObjects(orderService) .build(); } } Service public class OrderService { Tool(description 根据订单号查询订单状态返回状态码和更新时间) public OrderStatus queryOrder(ToolParam(description 订单号格式 ORD- 开头) String orderId) { // 真实场景这里查数据库或调内部 API return new OrderStatus(orderId, PAID, Instant.now()); } }然后在application.yml里声明 MCP 服务端的传输方式和端点spring: ai: mcp: server: name: order-mcp-server version: 1.0.0 protocol: STREAMABLE streamable-http: mcp-endpoint: /mcp port: 8081这里protocol: STREAMABLE对应新版推荐的 Streamable HTTP 传输mcp-endpoint就是 JSON-RPC 消息的入口路径。客户端会往http://localhost:8081/mcp发 POST 请求请求体是标准的 JSON-RPC 2.0 格式。3.3 客户端连接外部 MCP 服务客户端侧要声明「连哪个服务器、用什么传输」。Spring AI 1.x 支持在配置文件里直接声明多个 MCP 服务器连接spring: ai: mcp: client: enabled: true name: spring-ai-mcp-client version: 1.0.0 type: SYNC request-timeout: 30s streamable-http: connections: order-server: url: http://localhost:8081 endpoint: /mcptype: SYNC表示同步客户端适合请求-响应式的工具调用如果你要做流式或长任务可以换成ASYNC。connections下面可以挂多个服务器每个 key 是连接名url和endpoint拼起来就是完整的 JSON-RPC 端点。配置完之后Spring AI 会自动创建McpSyncClient并注册到ToolCallbackProvider里模型在对话时就能「看到」这些外部工具。你不需要手写 JSON-RPC 的initialize、tools/list、tools/call这些方法——框架帮你做了但理解它们有助于排障。3.4 JSON-RPC 消息长什么样为了后面排障方便这里贴一条真实的tools/call请求体你可以用 curl 直接打服务端验证{ jsonrpc: 2.0, id: 1, method: tools/call, params: { name: queryOrder, arguments: { orderId: ORD-20250101-001 } } }对应的成功响应{ jsonrpc: 2.0, id: 1, result: { content: [ { type: text, text: {\orderId\:\ORD-20250101-001\,\status\:\PAID\} } ] } }注意id在同一会话内不能重复也不能为null——这是 MCP 在 JSON-RPC 2.0 之上的硬性增强写自定义客户端时容易踩。4. 验证请求跑通一次工具调用链路配置写完怎么确认真的通了分两步先绕过模型直接验证 MCP 服务端再走完整链路让模型决定调用。4.1 直接打 JSON-RPC 端点服务端起在 8081 后先用 curl 发一个initialize请求curl -X POST http://localhost:8081/mcp \ -H Content-Type: application/json \ -d { jsonrpc: 2.0, id: 1, method: initialize, params: { protocolVersion: 2025-11-25, capabilities: {}, clientInfo: {name: curl-test, version: 1.0} } }如果返回里带result.capabilities.tools说明服务端能力协商成功。接着发tools/listcurl -X POST http://localhost:8081/mcp \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:2,method:tools/list,params:{}}你应该能看到queryOrder出现在工具列表里带inputSchema。这一步过了说明 JSON-RPC 层没问题问题只可能在客户端配置或模型侧。4.2 走完整链路写一个 Spring Boot 测试类注入ChatClient让它根据自然语言决定调用工具SpringBootTest class McpToolCallTest { Autowired private ChatClient chatClient; Test void shouldCallExternalOrderTool() { String reply chatClient.prompt() .user(帮我查一下订单 ORD-20250101-001 的状态) .call() .content(); System.out.println(reply); assertThat(reply).contains(PAID); } }跑起来后观察日志里有没有tools/call的 JSON-RPC 往返。成功的话模型会先返回一个 tool callSpring AI 把它转成 MCP 请求发给 8081拿到结果后再喂回模型生成最终回复。整个链路里模型侧走的是 TaoToken 的 API工具侧走的是本地 MCP 服务两边通过 Spring AI 的ToolCallbackProvider缝合。如果你只想先验证模型能不能正确识别工具可以打开模型对话页面手动试https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 把工具描述贴进去看它是否按预期生成调用参数。5. 本篇常见错排查5.1 客户端连不上日志只有一句 timeout先确认url和endpoint有没有拼错。url: http://localhost:8081加endpoint: /mcp拼出来是http://localhost:8081/mcp如果你在url里已经写了/mcp就会变成/mcp/mcp。另外 Streamable HTTP 的initialize是 POST但后续监听流是 GET如果服务端只放行了 POST握手会卡住。5.2 工具列表为空大概率是ToolCallbackProvider没被扫描到。检查Tool方法所在的类是不是 Spring BeanMethodToolCallbackProvider.builder().toolObjects(...)传进去的对象必须是被 Spring 管理的实例。另外Tool的方法参数要加ToolParam描述否则生成的inputSchema可能缺字段模型看不到参数说明就不会调。5.3 模型不调用工具先看模型侧返回里有没有tool_calls。如果模型压根没生成工具调用可能是工具描述太模糊或者模型本身对工具调用支持不好。换一个工具调用能力强的模型或者在 prompt 里明确「你必须使用 queryOrder 工具查询」。TaoToken 的模型列表里可以挑支持 function calling 的型号具体在文档页有说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。5.4 JSON-RPC id 重复导致会话中断如果你自己写了客户端重试逻辑注意每次请求的id要递增不能复用。MCP 规范明确要求同一会话内 id 不得重复复用了服务端可能直接断连。用 Spring AI 的McpSyncClient不用管这个框架内部维护了计数器。5.5 服务端返回 406 或 415Streamable HTTP 对Content-Type和Accept有要求。请求必须是application/json响应期望application/json或text/event-stream。如果你前面挂了网关检查网关有没有改写这两个头。6. 接下来怎么走MCP 的配置骨架搭起来之后真正花时间的是工具粒度的设计和权限边界。我的经验是一个 MCP 服务只暴露一组高内聚的工具别把订单、库存、用户全塞一个服务里否则模型在tools/list里看到几十个工具选择准确率会下降。另外服务端的Tool方法尽量幂等因为模型重试是常态。如果你要长期跑编码类 Agent把 MCP 客户端接进 IDE 或 CLI 工作流可以考虑用 Coding Plan 统一管理模型额度和调用https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。Claude Code 这类工具接 Anthropic 兼容端点的配置在 https://taotoken.net/claude-code?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 有说明MCP 那层配置和本文一致换的只是模型接入地址。最后留一个实操建议先把本文的服务端 curl 验证跑通再动客户端配置。JSON-RPC 层通了后面都是 Spring 的装配问题排障范围会小很多。