1. 从一次工具调用失败说起MCP 到底在解决什么问题我第一次接触 MCP 是在给一个 Spring Boot 项目做 AI 能力接入的时候。当时的需求很朴素让大模型能读取项目里的数据库表结构然后根据自然语言生成对应的查询语句。按照传统做法我得写一堆胶水代码——定义工具函数、写 JSON Schema 描述参数、处理模型返回的调用请求、再把结果拼回对话上下文。每接一个新工具这套流程就要重来一遍而且不同模型平台的工具描述格式还不一样换一个模型就得改一轮适配层。这个场景其实非常典型。大模型本身只会“说话”它没有手也没有脚不能直接读你的文件、查你的数据库、调你的接口。所谓“让模型调用工具”本质上是我们自己在模型和真实世界之间搭了一座桥。问题在于这座桥过去是每家各修各的OpenAI 有自己的 function calling 格式Anthropic 有自己的 tool use 规范国内各家平台又各有各的写法。你为一个平台写的工具换个平台基本要重写。MCPModel Context Protocol模型上下文协议要解决的就是这个“各修各桥”的问题。它做的事情说白了就是给“模型如何发现工具、如何描述工具、如何调用工具、如何拿回结果”这一整套交互定义了一套标准协议。你可以把它理解成 AI 世界的 USB-C 接口——以前每个设备一个充电口现在统一成一个标准谁都能插。这里有个关键点很多人一开始会搞混MCP 不是某个具体工具也不是某个模型的能力它是一个协议。协议意味着它规定了“怎么通信”但不规定“通信什么”。就像 HTTP 协议规定了请求响应的格式但具体传的是网页还是图片那是上层的事。MCP 规定了 MCP Host、MCP Client、MCP Server 之间的交互方式但具体暴露哪些工具、哪些资源完全由你自己决定。那为什么现在 MCP 突然火了我的观察是三个因素叠加。第一大模型能力到了一定水平单靠对话已经不够用了大家都要做 Agent而 Agent 的核心就是工具调用第二工具调用的碎片化问题已经严重到影响开发效率市场需要一个统一标准第三Anthropic 把 MCP 开源出来并且有 Claude Desktop 这样的实际载体去跑通它让协议从纸面变成了可用的东西。到了 Spring AI 2.0 这一代Java 生态也开始原生支持 MCP这对我们做企业级开发的人来说意义很大——终于不用在 Java 项目里手搓一套工具调用框架了。这篇文章我会从协议原理讲到 Spring AI 实战把 MCP 的 Host、Client、Server 三个角色拆开讲清楚再结合 Spring Boot 项目给出可落地的代码和配置。中间会穿插我自己踩过的坑比如工具描述写不好导致模型死活不调用、本地文件访问的权限边界怎么划、多个 MCP Server 怎么管理等等。不管你是刚听说 MCP 想搞明白它是什么还是已经在用 Spring AI 想深入实战应该都能找到对你有用的部分。2. 拆开 MCP 的三个角色Host、Client、Server 各干什么2.1 用“餐厅”类比理解三者关系MCP 的架构里有三个核心角色MCP Host、MCP Client、MCP Server。官方文档的描述比较抽象我用一个餐厅的类比来解释你一下就懂了。MCP Host 就是“餐厅前台”它是用户直接打交道的那个应用比如 Claude Desktop、Cursor、或者你自己用 Spring Boot 写的一个 AI 助手。Host 负责接收用户的问题管理整个对话流程决定什么时候需要调用工具。MCP Client 是“传菜员”它由 Host 创建和管理专门负责和某一个 MCP Server 通信。注意一个 Host 可以创建多个 Client每个 Client 对应一个 Server。就像餐厅里一个传菜员负责一个档口川菜档口一个传菜员粤菜档口一个传菜员互不干扰。MCP Server 是“后厨档口”它真正提供能力。比如一个 Server 提供数据库查询能力一个 Server 提供文件读取能力一个 Server 提供调用某个外部 API 的能力。Server 不关心用户说了什么它只负责响应 Client 发来的标准化请求。这个分工的好处在于解耦。Host 不需要知道每个工具的具体实现它只需要通过 Client 去问 Server“你能干什么”Server 返回一份能力清单Host 把这份清单转成模型能理解的格式模型决定调用哪个Host 再通过 Client 把调用请求发给对应的 Server。整条链路清晰且可扩展。2.2 MCP Server 到底暴露什么Tools、Resources、PromptsMCP Server 对外提供的能力分三类这个分类很重要因为它决定了你在写 Server 的时候该把功能放在哪一类。Tools工具是最常用的一类代表“可以执行的动作”。比如查询数据库、发送邮件、创建工单、调用第三方接口。Tools 的特点是模型可以主动调用而且通常会产生副作用改了数据、发了请求。每个 Tool 需要定义名称、描述、参数 Schema模型根据这些信息决定是否调用。Resources资源代表“可以读取的数据”。比如文件内容、数据库表结构、配置信息、日志片段。Resources 和 Tools 的区别在于Resources 是只读的、被动的通常是 Host 主动去读取然后塞进上下文而不是模型主动调用。你可以把 Resources 理解成“给模型看的参考资料”。Prompts提示模板是一类比较特殊的能力它允许 Server 提供预定义的提示词模板。比如一个代码审查 Server 可以提供“审查这段代码”的模板Host 拿到模板后可以直接用。这块在实际项目里用得相对少但做垂直领域应用时很有价值。我自己的经验是刚开始做 MCP Server 的时候很多人会把所有东西都塞进 Tools包括那些本该是 Resources 的只读数据。这样做的后果是模型会频繁“调用工具”去读一些其实可以直接放进上下文的东西既浪费 token 又增加延迟。判断标准很简单如果这个操作是只读的、结果可以缓存、不需要模型做决策那它大概率应该是 Resource如果需要模型根据情况决定要不要执行、执行什么参数那才是 Tool。2.3 通信层JSON-RPC 2.0 与两种传输方式MCP 的通信基于JSON-RPC 2.0。这是一个很成熟的远程调用协议请求和响应都是 JSON 格式包含 method、params、id 这些字段。选 JSON-RPC 而不是 REST 的原因我理解是因为 MCP 的交互模式更接近“方法调用”而不是“资源操作”而且 JSON-RPC 天然支持双向通信和通知机制。传输方式上MCP 支持两种stdio标准输入输出和HTTP with SSEServer-Sent Events。stdio 方式下MCP Server 作为一个子进程被 Host 启动双方通过标准输入输出流通信。这种方式适合本地工具比如读取本地文件、操作本地数据库。优点是简单、无需网络配置、进程隔离天然安全。缺点是只能本机用没法跨机器共享。HTTP SSE 方式下MCP Server 作为一个独立的 HTTP 服务运行Client 通过 HTTP 发请求Server 通过 SSE 推送消息。这种方式适合远程服务、多客户端共享的场景。比如你部署一个公司内部的 MCP Server所有同事的 AI 助手都能连上来用。在 Spring AI 里这两种方式都有对应的 Starter 支持。本地开发调试我一般用 stdio部署到服务器给团队用就切到 HTTP SSE。切换成本很低主要是改配置和依赖。提示stdio 模式下 Server 的日志千万不要往标准输出打因为标准输出是通信通道打日志会污染协议数据导致解析失败。日志要打到标准错误或者文件里。这个坑我踩过排查了半天才发现是日志把 JSON-RPC 消息冲掉了。3. Spring AI 2.0 接入 MCP从依赖到跑通第一个 Server3.1 环境准备与依赖选型在 Spring Boot 项目里接入 MCP核心依赖是 Spring AI 提供的 MCP Starter。截至我写这篇内容时Spring AI 2.0 已经提供了比较完整的 MCP 支持主要分两个方向做 MCP Client让你的应用能调用别人的 MCP Server和做 MCP Server让你的应用对外提供 MCP 能力。先看版本选择。Spring AI 2.0 要求 Spring Boot 3.xJDK 17 起步。如果你还在用 Spring Boot 2.x 或者 JDK 8那得先升级这个没有捷径。我建议直接用 Spring Boot 3.2 以上的版本配合 Spring AI 2.0 的 BOM 管理依赖版本避免版本冲突。Maven 依赖方面做 Client 和做 Server 引入的 Starter 不同!-- 做 MCP Client连接别人的 MCP Server -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-client-spring-boot-starter/artifactId /dependency !-- 做 MCP Server对外提供 MCP 能力 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-spring-boot-starter/artifactId /dependency如果你要用 stdio 方式还需要额外引入对应的传输实现依赖。Spring AI 把传输层做了抽象stdio 和 HTTP SSE 是两套不同的实现按需引入。这里有个选型上的经验如果你的 MCP Server 是给本地 AI 工具比如桌面版 AI 助手用的选 stdio如果是给团队或线上服务用的选 HTTP SSE。不要为了“看起来高级”硬上 HTTPstdio 在本地场景下更简单更稳。3.2 写一个能查数据库表结构的 MCP Server光说概念没意思直接上一个我实际项目里用过的例子一个能查询数据库表结构和字段信息的 MCP Server。这个场景很实用因为让 AI 帮你写 SQL 的时候它得先知道表长什么样。首先定义工具类。Spring AI 提供了注解方式来声明 MCP 工具核心是Tool注解Component public class DatabaseSchemaTools { private final JdbcTemplate jdbcTemplate; public DatabaseSchemaTools(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } Tool(description 查询指定数据库中所有表的名称列表) public ListString listTables(ToolParam(description 数据库名称) String schemaName) { String sql SELECT table_name FROM information_schema.tables WHERE table_schema ?; return jdbcTemplate.queryForList(sql, String.class, schemaName); } Tool(description 查询指定表的字段信息包括字段名、类型、是否可空、注释) public ListMapString, Object describeTable( ToolParam(description 数据库名称) String schemaName, ToolParam(description 表名称) String tableName) { String sql SELECT column_name, data_type, is_nullable, column_comment FROM information_schema.columns WHERE table_schema ? AND table_name ? ORDER BY ordinal_position ; return jdbcTemplate.queryForList(sql, schemaName, tableName); } }然后在配置类里把这个工具注册到 MCP ServerConfiguration public class McpServerConfig { Bean public ToolCallbackProvider databaseToolProvider(DatabaseSchemaTools tools) { return MethodToolCallbackProvider.builder() .toolObjects(tools) .build(); } }最后在application.yml里配置 Server 的基本信息spring: ai: mcp: server: name: database-schema-server version: 1.0.0 type: SYNC跑起来之后这个 Server 就对外暴露了两个工具listTables和describeTable。任何支持 MCP 的 Host 连上来都能发现并调用这两个工具。3.3 工具描述写得好不好直接决定模型调不调用上面代码里最容易被忽视、但实际最影响效果的是Tool和ToolParam里的description。我踩过的最大一个坑就是工具描述写得太简略模型根本不知道什么时候该用它。举个反面例子。我一开始把描述写成“查询表列表”结果模型在用户问“帮我看看这个数据库有哪些表”的时候居然不去调用这个工具而是自己编了一段 SQL 让用户去执行。后来我把描述改成“查询指定数据库中所有表的名称列表当用户需要了解数据库中有哪些表时使用”模型立刻就正确调用了。这里的原则是工具描述要同时说清楚“这个工具做什么”和“什么时候该用它”。参数描述也一样不要只写“表名”要写“要查询的表名称必须是数据库中已存在的表”。模型是靠这些文字来判断的你写得越明确它的调用准确率越高。还有一个细节工具名称尽量用动词开头比如listTables、describeTable、createOrder不要用tableInfo这种名词。动词开头的名称能让模型更容易理解这是一个“动作”。4. 让 Spring Boot 应用变成 MCP Client连接与调用实战4.1 Client 配置连接本地和远程 Server做 MCP Client 的场景通常是你自己的 Spring Boot 应用想调用别人已经写好的 MCP Server。比如你想让应用具备读取本地文件的能力而社区已经有一个现成的文件系统 MCP Server你直接连上去用就行不用自己重写。Spring AI 里配置 Client 连接 stdio 类型的 Serverspring: ai: mcp: client: stdio: connections: filesystem: command: npx args: - -y - modelcontextprotocol/server-filesystem - /path/to/allowed/directory这段配置的意思是启动一个子进程运行文件系统 MCP Server允许访问指定目录。Host 应用启动时会自动拉起这个子进程并建立连接。连接 HTTP SSE 类型的 Server 则是这样spring: ai: mcp: client: sse: connections: remote-tools: url: http://localhost:8080/mcp/sse配置好之后Spring AI 会自动完成 MCP 的初始化握手拉取 Server 的能力清单并把工具注册到 ChatClient 里。你在写业务代码的时候完全不用关心 MCP 的通信细节直接像调用普通方法一样用就行。4.2 把 MCP 工具接入 ChatClientClient 连上 Server 之后下一步是把 MCP 提供的工具接入到对话流程里。Spring AI 的ChatClient支持自动发现并注册 MCP 工具Configuration public class ChatClientConfig { Bean public ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider mcpToolProvider) { return builder .defaultSystem(你是一个能操作文件和数据库的助手需要时请调用工具。) .defaultToolCallbacks(mcpToolProvider) .build(); } }这样配置之后每次对话时模型都能看到 MCP Server 提供的所有工具。当用户的问题需要用到这些工具时模型会自动发起调用Spring AI 负责把调用请求通过 MCP Client 转发给 Server拿到结果后再喂回给模型继续生成回答。这里有个性能上的注意点如果 MCP Server 提供的工具很多比如几十个每次对话都把全部工具描述塞进上下文会消耗大量 token。我的做法是按业务场景拆分多个 ChatClient每个 Client 只挂载相关的工具子集。比如“数据库助手”只挂数据库工具“文件助手”只挂文件工具。这样既省 token 又提高模型选择工具的准确率。4.3 多 Server 管理与工具命名冲突实际项目里你往往会同时连接多个 MCP Server。比如一个文件 Server、一个数据库 Server、一个内部 API Server。这时候会遇到两个问题工具名称冲突和调用路由。工具名称冲突是指两个不同的 Server 提供了同名工具。Spring AI 的处理方式是在工具名前加 Server 前缀来区分但更稳妥的做法是自己在 Server 端就把工具名起得足够独特比如db_listTables、fs_readFile从源头避免冲突。调用路由方面Spring AI 的 Client 会自动根据工具来源把调用请求发给对应的 Server你不需要手动指定。但前提是每个 Server 的连接配置正确且工具注册时来源信息没有丢失。我遇到过一次因为配置里两个 Server 用了相同的连接名导致工具注册混乱的情况排查起来很费劲。所以连接名一定要唯一且有意义这是个小细节但很重要。5. 实战中绕不开的那些坑权限、超时与调试5.1 本地文件访问的权限边界怎么划用文件系统 MCP Server 的时候最容易出问题的就是权限。默认情况下Server 只能访问你启动时指定的目录这是它的安全边界。但实际用的时候你会发现模型有时候会尝试访问边界外的路径然后调用失败。我的建议是启动文件 Server 时只挂载真正需要的那一个或几个目录不要图省事挂载整个用户目录或者根目录。一方面安全另一方面模型在受限范围内选择文件反而更准确。如果确实需要访问多个目录就启动多个 Server 实例每个实例管一个目录通过工具名前缀区分。另外要注意路径的写法。stdio 模式下传的目录参数最好用绝对路径相对路径在不同工作目录下启动会出问题。这个坑我在 Windows 和 Linux 上都踩过表现是 Server 启动成功但访问文件时报“路径不存在”。5.2 超时设置与长任务处理MCP 调用默认是有超时的。如果某个工具执行时间比较长比如查询一个大表、调用一个慢接口很容易触发超时导致调用失败。Spring AI 里可以配置超时时间spring: ai: mcp: client: request-timeout: 60s但我的经验是与其无限延长超时不如把长任务拆成异步模式。让工具立即返回一个任务 ID然后提供另一个工具让模型轮询任务状态。这样既避免了超时又不会让模型干等着。当然这增加了工具设计的复杂度是否值得取决于你的具体场景。对于大多数查询类操作把超时设到 30 到 60 秒基本够用。还有一个容易忽略的点stdio 模式下如果 Server 进程崩溃了Client 这边会收到连接断开的错误。生产环境用 HTTP SSE 模式会更稳一些因为 Server 是独立进程崩溃了可以重启不影响 Host。5.3 调试 MCP 调用的实用手段调试 MCP 最直接的办法是看日志。Spring AI 提供了 MCP 相关的日志开关打开之后能看到完整的 JSON-RPC 请求和响应logging: level: org.springframework.ai.mcp: DEBUG打开之后你会看到类似这样的日志Client 发送tools/list请求Server 返回工具清单Client 发送tools/call请求Server 返回执行结果。通过这个日志你能清楚地知道模型到底调用了哪个工具、传了什么参数、拿到了什么结果。如果模型该调用工具却没调用先看日志里有没有tools/call记录。没有的话问题出在模型决策环节大概率是工具描述不够清晰。有调用但结果不对就看参数传得对不对以及 Server 端执行逻辑有没有问题。还有一个技巧用 MCP Inspector 这类工具单独测试 Server。它是一个独立的调试界面能直接连上你的 MCP Server列出所有工具手动填参数调用。这样可以把 Server 的问题和 Host、模型的问题分开排查效率高很多。6. 从能跑到好用MCP 工程化的几个进阶思路6.1 工具粒度设计粗一点还是细一点工具粒度是个需要权衡的问题。粒度太细比如getUserName、getUserAge、getUserEmail各是一个工具模型要调好几次才能拼出完整信息效率低。粒度太粗比如一个executeSql工具什么都能干模型容易乱用而且安全风险大。我的经验法则是按“业务动作”而不是“数据字段”来划分工具。比如“查询用户完整信息”是一个工具“更新用户联系方式”是另一个工具。每个工具对应一个完整的、有业务意义的操作。这样模型容易理解调用次数也合理。对于确实需要灵活性的场景可以提供一两个“通用工具”作为兜底但要加上严格的参数校验和权限控制。比如executeReadOnlySql只允许 SELECT 语句其他一律拒绝。6.2 把 MCP Server 当微服务来治理当你的 MCP Server 多起来之后就需要考虑治理问题了。我的做法是把每个 MCP Server 当成一个微服务来对待独立部署、独立配置、独立监控。具体来说每个 Server 有自己的健康检查接口有自己的日志文件有自己的资源限制。用一个统一的注册中心或者配置文件来管理所有 Server 的连接信息。这样新增或下线一个 Server 只需要改配置不需要改代码。Spring AI 的 MCP Client 支持从配置文件动态加载多个 Server 连接配合 Spring Boot 的配置管理能力可以做到不同环境开发、测试、生产连不同的 Server 集合。这个在团队协作场景下特别有用。6.3 安全边界哪些能力绝对不能开放最后必须说一下安全。MCP 让模型能调用真实世界的工具这既是它的价值也是它的风险。有几类能力我建议绝对不要通过 MCP 开放给模型第一任意命令执行。不要做一个“执行 shell 命令”的工具哪怕加了白名单也不安全因为白名单很容易被绕过。第二无限制的写操作。删除数据、修改配置这类操作要么不开放要么加上严格的人工确认环节。模型可能会因为理解偏差执行错误的写操作。第三敏感信息读取。密钥、密码、个人隐私数据不要通过 MCP 暴露即使模型只是“读”也可能把信息带进对话上下文造成泄露。我的原则是MCP 工具默认只读写操作必须显式设计并加确认。这个原则能挡掉大部分安全事故。7. 我个人的一些实践体会MCP 这个协议本身不复杂复杂的是怎么把它用好。我用了几个月下来最大的体会是协议标准化解决的是“能不能连”的问题但“连得好不好用”还是取决于工具设计。同样一套 MCP 框架工具描述写得清楚、粒度划分合理、权限边界明确模型用起来就顺手反过来工具一堆但描述含糊模型要么不调用要么乱调用体验还不如不用。另一个体会是关于 Spring AI 的。Spring AI 2.0 对 MCP 的支持已经比较成熟了注解式声明工具、自动注册、多 Server 管理这些都有现成方案Java 开发者上手成本不高。但要注意版本匹配Spring AI 迭代很快不同版本之间 API 有变化升级前一定先看官方迁移文档。我就因为没看文档直接升版本导致一批工具注册代码编译不过白白花了一下午排查。最后分享一个小技巧刚开始做 MCP 的时候先用 MCP Inspector 把 Server 单独调通确认工具能正常列出和调用再去接 Host 和模型。这样能把问题分层Server 的问题在 Server 层解决模型的问题在模型层解决不要混在一起排查。这个习惯帮我省了很多时间。MCP 生态现在还在快速演进新的 Server 实现、新的 Host 支持、新的协议特性都在不断出现。保持关注官方规范和 Spring AI 的更新同时把基础的工具设计和安全边界打牢剩下的就是按需扩展了。