
一、MCP 必知必会什么是 MCPMCPModel Context Protocol模型上下文协议是一种开放标准目的是增强 AI 与外部系统的交互能力。MCP 为 AI 提供了与外部工具、资源和服务交互的标准化方式让 AI 能够访问最新数据、执行复杂操作并与现有系统集成。根据 官方定义MCP 是一种开放协议它标准化了应用程序如何向大模型提供上下文的方式。可以将 MCP 想象成 AI 应用的 USB 接口。就像 USB 为设备连接各种外设和配件提供了标准化方式一样MCP 为 AI 模型连接不同的数据源和工具提供了标准化的方法。我们一定要记住 MCP 它是个协议或者标准它本身并不提供什么服务只是定义好了一套规范让服务提供者和服务使用者去遵守。这样的好处显而易见就像 HTTP 协议一样现在前端向后端发送请求基本都是用 HTTP 协议什么 get / post 请求类别、什么 401、404 状态码这些标准能有效降低开发者的理解成本。所以MCP他并不是什么具体的技术他只是服务方和消费者之间的一种协议这种协议是来标准化被服务方的协议当服务方提供的服务是通过MCP协议提供的那么消费者就必须要遵守MCP协议举个简单的例子我们的“恋爱大师Agent”现在要添加一个根据地理位置推荐周边约会地点的工具我们需要调用“高德地图”的服务如果高德地图服务可以直接调用那么我们在程序中直接调用他们家的服务即可但是如果高德地图的服务是通过“MCP”提供的那么我们就必须要遵守MCP协议才能调用MCP 架构1、宏观架构MCP 的核心是 “客户端client - 服务器sever” 架构其中 MCP 客户端主机可以连接到多个服务器。客户端主机是指希望访问 MCP 服务的程序比如 Claude Desktop、IDE、AI 工具或部署在服务器上的项目。2、SDK 3 层架构如果我们要在程序中使用 MCP 或开发 MCP 服务可以引入 MCP 官方的 SDK比如 Java SDK。让我们先通过 MCP 官方文档了解 MCP SDK 的架构主要分为 3 层分别来看每一层的作用客户端 / 服务器层McpClient 处理客户端操作而 McpServer 管理服务器端协议操作。两者都使用 McpSession 进行通信管理。会话层McpSession通过 DefaultMcpSession 实现管理通信模式和状态。传输层McpTransport处理 JSON-RPC 消息序列化和反序列化支持多种传输实现比如 Stdio 标准 IO 流传输和 HTTP SSE 远程传输。客户端和服务端需要先经过下面的流程建立连接之后才能正常交换消息流程解释如下1. MCP Client 连接 MCP Server- stdio启动本地 Server 子进程- Streamable HTTP访问远程 Server 的 HTTP 地址2. Client 发 initialize 初始化请求- 告诉 Server我支持的协议版本、我的能力、我的信息3. Server 返回 initialize 响应- 告诉 Client最终协议版本、Server 的能力、Server 的信息4. Client 发 initialized 通知- 表示初始化完成可以开始正常工作了-------------------------------------------以下为“信息交换”内容------------------------------------------------5. Client 按需发现服务能力- tools/list- resources/list- prompts/list6. Client 调用具体能力- tools/call- resources/read- prompts/get7. Server 返回结果3、MCP 客户端(MCP Client)简单来说MCP Client就是“使用、发现、调用”服务端能力的一方MCP Client 是 MCP 架构中的关键组件主要负责和 MCP 服务器建立连接并进行通信。它能自动匹配服务器的协议版本、确认可用功能、负责数据传输和 JSON-RPC 交互。此外它还能发现和使用各种工具、管理资源、和提示词系统进行交互。连接方式MCP 客户端还支持一些额外特性比如根管理、采样控制以及同步或异步操作。为了适应不同场景它提供了多种数据传输方式包括Stdio 标准输入 / 输出适用于本地调用Streamable HTTP远程网络服务通信适用于远程调用基于 Java HttpClient 和 WebFlux 的 SSE 传输适用于远程调用注意现在已经不再使用这种方式的传输只有一些老的客户端还在使用请注意这里提及的所谓“本地”“远程”只是他们的“连接形态”并不是说调用的“工具位置”例如使用http远程连接时工具可以在别人的服务器上也可以在我们自己电脑上也可以是局域网中。他们之间的具体区别stdio更像“本机拉起一个工具进程”HTTP更像“访问一个已经在某处运行的服务”客户端可以通过不同传输方式调用不同的 MCP 服务可以是本地的、也可以是远程的。如图因此数据传输方式的结论可以这么记小型、本地、个人工具stdio 远程部署、多人共享、服务化Streamable HTTP4、MCP 服务端MCP Server 也是整个 MCP 架构的关键组件主要用来为客户端提供各种工具、资源和功能支持。它负责处理客户端的请求包括解析协议、提供工具、管理资源以及处理各种交互信息。同时它还能记录日志、发送通知并且支持多个客户端同时连接保证高效的通信和协作。和客户端一样它也可以通过多种方式进行数据传输比如 Stdio 标准输入 / 输出、Streamable HTTP 传输满足不同应用场景。这种设计使得客户端和服务端完全解耦任何语言开发的客户端都可以调用 MCP 服务。如图这张图比较老了其中的SSE可以替换成Streamable HTTP 传输MCP 核心概念很多同学以为 MCP 协议就只能提供工具给别人调用但实际上MCP 协议的本领可大着呢按照官方的说法总共有 6 大核心概念。大家简单了解一下即可除了 Tools 工具之外的其他概念都不是很实用如果要进一步学习可以阅读对应的官方文档。Resources 资源让服务端向客户端提供各种数据比如文本、文件、数据库记录、API 响应等客户端可以决定什么时候使用这些资源。使 AI 能够访问最新信息和外部知识为模型提供更丰富的上下文。Prompts 提示词服务端可以定义可复用的提示词模板和工作流供客户端和用户直接使用。它的作用是标准化常见的 AI 交互模式比如能作为 UI 元素如斜杠命令、快捷操作呈现给用户从而简化用户与 LLM 的交互过程。Tools 工具MCP 中最实用的特性服务端可以提供给客户端可调用的函数使 AI 模型能够执行计算、查询信息或者和外部系统交互极大扩展了 AI 的能力范围。Sampling 采样允许服务端通过客户端向大模型发送生成内容的请求反向请求。使 MCP 服务能够实现复杂的智能代理行为同时保持用户对整个过程的控制和数据隐私保护。Roots 根目录MCP 协议的安全机制定义了服务器可以访问的文件系统位置限制访问范围为 MCP 服务提供安全边界防止恶意文件访问。Transports 传输定义客户端和服务器间的通信方式包括 Stdio本地进程间通信和 SSE网络实时通信确保不同环境下的可靠信息交换。如果要开发 MCP 服务我们主要关注前 3 个概念当然Tools 工具是重中之重二、使用 MCP云平台使用 MCP以阿里云百炼为例参考 官方 MCP 文档我们可以直接使用官方预置的 MCP 服务或者部署自己的 MCP 服务到阿里云平台上。我们本次就拿“按照位置推荐约会地点”这个功能来举例把MCP服务选用前文提及的高德地图我们自己在该平台上新建一个应用让AI帮我们编写一下系统提示词随后找到下方的MCP服务随后我们可以在用户对话窗口问他相关问题看看它的MCP服务是怎么样被调用的可以看到该服务的调用其实本质上就是我们之前的工具调用框架把用户请求和工具说明一同发给AIAI更具用户请求和工具自主调用工具工具执行完成后把结果返回给AIAI整理结果发送给用户软件客户端使用 MCP以Workbuddy工具为例来展示在常见的Agent软件中如何配置MCP首先我们要在MCP市场中查询相关的MCP服务看一下MCP服务的相关协议The MCP Server Registry, Inspector Gateway | Glama在搜索框中搜索gaodemap高德地图查看相关协议与配置用法配置相关MCP服务需要服务商提供的key来到高德开放平台创建应用来获取相对应的key我的应用 | 高德控制台最后来到workbuddy自主配置MCP把在MCP市场的配置获取到的key填写好最后来测试mcp的调用情况程序中使用 MCP接下来让我们在我们的项目中启用mcp服务启用mcp服务我们需要用到SpringAI的官方依赖所以首先我们先引入依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId version1.1.2/version /dependency接下来我们在resource目录下新建mcp-servers.json配置定义需要用到的 MCP 服务图中可以看一下这份json配置的实际意义重点修改 Spring 配置文件编写 MCP 客户端配置。由于是本地运行 MCP 服务所以使用 stdio 模式并且要指定 MCP 服务配置文件的位置。代码如下注意层级在“ai”这个层级之下配置文件都编写完成接下来我们只需要在链路中使用配置好的工具高德地图即可首先引入ToolCallbackProvider的对象用于获取 MCP Client 发现到的工具把 MCP 工具包装成 Spring AI 能识别的ToolCallback核心让ChatClient可以把这些工具交给大模型当模型发起调用时把调用转发给对应的 MCP Server然后我们从上面的“工具调用与AI交互”的方法稍微改一下其实就是改了个工具参数就大功告成了让我们写一些相关的测试方法来验证一下返回结果如下点击链接发现就是高德地图所提供的图片链接证明我们的MCP配置与调用成功了所以纵观下来我们只是编写了几个配置文件引入了一个toolCallbackProvider的对象直接在调用链里面使用即可真正的MCP服务高德内容就在于我们引入的js文件中我们只需要调用即可三、Spring AI MCP 开发模式Spring AI 在 MCP 官方 Java SDK 的基础上额外封装了一层提供了和 Spring Boot 整合的 SDK支持客户端和服务端的普通调用和响应式调用。下面分别学习如何使用 Spring AI 开发 MCP 客户端和服务端。其实我们要干的事情很简单本质上就是写配置调用/暴露工具客户端和服务端区别只是配置文件意义上的不同和后续对工具的处理上不同在此之前我们再来回顾一下MCP的客户端和服务端到底是什么知道了实质和本质才好开发MCP实质所以我们可以看到客户端就是“服务”的调用方服务端就是服务的“提供方”至于“服务”的形式他可以是很多种我们之前在程序中使用高德的服务它的服务就是js文件我们后面的实战会开发服务端所提供的服务我们就选用jar的形式MCP 客户端开发客户端开发主要基于 Spring AI MCP Client Boot Starter能够自动完成客户端的初始化、管理多个客户端实例、自动清理资源等。1、引入依赖spring-ai-starter-mcp-client核心启动器提供 STDIO 和基于 HTTPstreamable的支持dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency2、配置连接引入依赖后需要配置与服务器的连接Spring AI MCP Client 的连接配置通常有两种常见方式二选一即可不需要把两种都同时写上。方式一纯 YAML 配置。适合连接数量少、希望把所有内容统一放在application.yml里的项目。spring: ai: mcp: client: stdio: connections: amap-maps: command: D:\潘\AI\node.exe args: - D:\潘\AI\mcp-servers\amap\build\index.js env: AMAP_MAPS_API_KEY: ${AMAP_MAPS_API_KEY}方式二YAML JSON 配置。YAML 只负责告诉 Spring AI 去读哪个 JSON 文件 JSON 负责描述一个或多个 stdio MCP Server。这个方式适合复用 Claude Desktop 风格的mcpServers配置。spring: ai: mcp: client: stdio: servers-configuration: classpath:mcp-servers.json对应的 JSON 例如{ mcpServers: { amap-maps: { command: D:\\潘\\AI\\node.exe, args: [ D:\\潘\\AI\\mcp-servers\\amap\\build\\index.js ], env: { AMAP_MAPS_API_KEY: ${AMAP_MAPS_API_KEY} } } } }远程Streamable HTTP通常直接在 YAML 里配置地址和认证参数不需要这个 stdio JSON 文件。如果更偏向 WebFlux 体系可以看spring-ai-starter-mcp-client-webflux。客户端通用配置spring: ai: mcp: client: enabled: true name: spring-ai-mcp-client version: 1.0.0 initialized: true request-timeout: 20s type: SYNC toolcallback: enabled: true如果使用stdio.servers-configuration它代表 YAML JSON 方式如果使用stdio.connections它代表纯 YAML 方式。两者不要给同一个 Server 同时配两份。、3、使用服务我们在client使用mcp提供的工具之前要先注入ToolCallbackProviderBean从中获取到 ToolCallback 工具对象这样才能利用 MCP 服务提供的工具来增强 AI 的能力Autowired private SyncMcpToolCallbackProvider toolCallbackProvider; ChatResponse response chatClient .prompt() .user(message) .tools(toolCallbackProvider) .call() .chatResponse();MCP 服务端开发服务端开发主要基于 Spring AI MCP Server Boot Starter能够自动配置 MCP 服务端组件使开发者能够轻松创建 MCP 服务向 AI 客户端提供工具、资源和提示词模板从而扩展 AI 模型的能力范围1、引入依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId /dependency2、配置服务配置传输模式不同的传输模式有着不同的配置方法但是也就是在server层级下一句话的事情stdio和streamable和stateless着三种的不同spring: ai: mcp: server: stdio: true # 或 protocol: STREAMABLE # 或 protocol: STATELESS3、开发服务无论采用哪种传输方式开发 MCP 服务的过程都是类似的跟开发工具调用一样直接使用Tool或者McpTool都是一样的注解标记服务类中的方法。例使用McpTool注解Component public class CalculatorTools { McpTool(name add, description Add two numbers together) public int add( McpToolParam(description First number, required true) int a, McpToolParam(description Second number, required true) int b) { return a b; } }McpTool暴露工具。McpResource暴露资源。McpPrompt暴露提示词模板。McpComplete用于补全。例使用Tool注解Service public class WeatherService { Tool(description 获取指定城市的天气信息) public String getWeather( ToolParameter(description 城市名称如北京、上海) String cityName) { return 城市 cityName 的天气是晴天温度22°C; } }然后在 Spring Boot 项目启动时注册一个ToolCallbackProviderBean 即可SpringBootApplication public class McpServerApplication { Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } }MCP 工具类Spring AI 还提供了一系列 辅助 MCP 开发的工具类用于 MCP 和 ToolCallback 之间的互相转换。也就是说开发者可以直接将之前开发的工具转换为 MCP 服务极大提高了代码复用性所以说我们之前项目开发的几个工具理论上都可以通过这个工具类把他们转换为MCP服务但是不建议这么做工具和MCP混用是很不好的习惯四、实战--开发MCP客户端服务端服务端开发(图片搜索)我们本质上就是开发一个“图片搜索”的工具然后把装载这个工具的整一个模块打包即可1.在我们项目的根目录下新建一个模块用于单独创建服务端注意建议在新项目中单独打开该模块不要直接在原项目的子文件夹中操作否则可能出现路径上的问题。2.引入相关依赖3.编写yml配置文件我们本次开发服务端选用StreamableHTTP的连接方式想用stdio连接方式的自己配置即可4.工具开发我们本次开发的工具的功能是“图片搜索”因此我们依然可以选用外部服务然后根据我们外部服务的官方文档让AI助手帮我们完成相应的代码这也是我们上一章节《工具调用》开发工具的常用开发方式关于“图片搜索”功能我们选用https://pixabay.com/这个网址点击“API”字样选项跳转相关开发文档页面该页面有你专属的APIkey和开发说明我们直接复制这戏金额开发用的文档让AI帮我们开发相关代码注意你自己的key不要暴露开发代码如下/** * Pixabay 图片搜索 MCP 工具。 * * p工具本身只负责参数校验、调用 Pixabay API 和整理返回结果 * 主类中的 ToolCallbackProvider Bean 负责发现并注册这个带有 Tool 的 Spring Bean。/p */ Service public class PixabayImageTool { private final RestClient restClient; private final ObjectMapper objectMapper; private final String apiKey; private final String baseUrl; public PixabayImageTool( RestClient.Builder restClientBuilder, ObjectMapper objectMapper, Value(${pixabay.api-key}) String apiKey, Value(${pixabay.base-url}) String baseUrl) { this.restClient restClientBuilder.baseUrl(baseUrl).build(); this.objectMapper objectMapper; this.apiKey apiKey; this.baseUrl baseUrl; } Tool( name search_images, description Search Pixabay royalty-free images by keyword and return image URLs, authors, and source attribution. ) public String searchImages( ToolParam(description Search keywords, for example: sunset beach or 东莞约会地点, required true) String query, ToolParam(description Language code, for example zh or en. Default: zh, required false) String language, ToolParam(description Image type: all, photo, illustration, or vector. Default: photo, required false) String imageType, ToolParam(description Orientation: all, horizontal, or vertical. Default: all, required false) String orientation, ToolParam(description Page number, starting from 1. Default: 1, required false) Integer page, ToolParam(description Number of results per page, from 3 to 200. Default: 10, required false) Integer perPage) { String normalizedQuery requireQuery(query); String normalizedLanguage defaultValue(language, zh); String normalizedImageType defaultValue(imageType, photo); String normalizedOrientation defaultValue(orientation, all); int normalizedPage page null ? 1 : page; int normalizedPerPage perPage null ? 10 : perPage; validatePage(normalizedPage, normalizedPerPage); validateEnum(language, normalizedLanguage, cs, da, de, en, es, fr, id, it, hu, nl, no, pl, pt, ro, sk, fi, sv, tr, vi, th, bg, ru, el, ja, ko, zh); validateEnum(imageType, normalizedImageType, all, photo, illustration, vector); validateEnum(orientation, normalizedOrientation, all, horizontal, vertical); URI requestUri UriComponentsBuilder.fromUriString(baseUrl) .queryParam(key, apiKey) .queryParam(q, normalizedQuery) .queryParam(lang, normalizedLanguage) .queryParam(image_type, normalizedImageType) .queryParam(orientation, normalizedOrientation) .queryParam(safesearch, true) .queryParam(page, normalizedPage) .queryParam(per_page, normalizedPerPage) .build() .encode() .toUri(); try { JsonNode response restClient.get() .uri(requestUri) .retrieve() .body(JsonNode.class); return toToolResult(response, normalizedQuery, normalizedPage, normalizedPerPage); } catch (RestClientResponseException exception) { return Pixabay 图片搜索失败HTTP 状态码 exception.getStatusCode().value() 原因 exception.getResponseBodyAsString(); } catch (Exception exception) { return Pixabay 图片搜索失败 exception.getMessage(); } } private String toToolResult( JsonNode response, String query, int page, int perPage) throws Exception { MapString, Object result new LinkedHashMap(); result.put(source, Pixabay); result.put(source_notice, 搜索结果展示时请注明图片来源 Pixabay。图片 URL 仅适合临时展示长期使用前请先下载到自己的服务器。); result.put(query, query); result.put(page, page); result.put(per_page, perPage); result.put(total, response null ? 0 : response.path(total).asInt(0)); result.put(total_hits, response null ? 0 : response.path(totalHits).asInt(0)); var images new java.util.ArrayListMapString, Object(); if (response ! null response.has(hits)) { for (JsonNode hit : response.path(hits)) { MapString, Object image new LinkedHashMap(); image.put(id, hit.path(id).asLong()); image.put(type, hit.path(type).asText()); image.put(tags, hit.path(tags).asText()); image.put(preview_url, hit.path(previewURL).asText()); image.put(webformat_url, hit.path(webformatURL).asText()); image.put(large_image_url, hit.path(largeImageURL).asText()); image.put(page_url, hit.path(pageURL).asText()); image.put(user, hit.path(user).asText()); images.add(image); } } result.put(images, images); return objectMapper.writeValueAsString(result); } private String requireQuery(String query) { if (query null || query.isBlank()) { throw new IllegalArgumentException(query 不能为空); } if (query.length() 100) { throw new IllegalArgumentException(query 不能超过 100 个字符); } return query.trim(); } private String defaultValue(String value, String defaultValue) { return value null || value.isBlank() ? defaultValue : value.trim(); } private void validatePage(int page, int perPage) { if (page 1) { throw new IllegalArgumentException(page 必须大于等于 1); } if (perPage 3 || perPage 200) { throw new IllegalArgumentException(perPage 必须在 3 到 200 之间); } } private void validateEnum(String field, String value, String... allowedValues) { for (String allowedValue : allowedValues) { if (allowedValue.equals(value)) { return; } } throw new IllegalArgumentException(field 的值不合法 value); } }写好代码后我们再回到yml配置文件中配置好我们的服务和keykey推荐配置再环境变量中5.编写测试方法查看工具是否可以成功使用查看自己的配置有无问题测试搜索图片关键词为“小蛋糕”待会查看图片搜索结果是否含有蛋糕图片测试返回结果如下可以返回图片的url访问图片url发现图片是“小蛋糕”相关图片测试成功6.测试完成后我们需要再主类中通过定义ToolCallbackProvider的Bean 来注册工具7.注册完工具我们需要打包该模块让该模块成为可以被调用的“服务”打包完成后发现target目录下有相关可执行的jar包到时候客户端使用服务的时候就会依赖这个包这里需要注意一下如果你刚刚配置APIkey是和我一样配置再环境变量中的那么打包的时候需要把这个key同样配置再Windows的系统环境变量否则打包会失败读取不到key客户端开发开发完服务端后我们接着开发客户端其实我们之前在“程序中使用MCP”已经算是开发过客户端了因为要使用高德的MCP服务那就必须要用客户端去调用他的服务我们当时的连接方式是stdio但是我们刚才开发的服务端是StreamableHTTP方式因此我们需要修改一下我们的客户端其实我们只需要改一下我们之前配置好的yml配置文件即可修改完配置文件即可启用我们之前配置好的jar包服务打开powershell输入对应命令cd D:\AI_project\AI_agent_YU\Image_Mcpjava -jar target\Image_Mcp-0.0.1-SNAPSHOT.jar这就是StreamableHTTP和stdin连接方式的区别StreamableHTTP连接方式需要自己在服务器(可以是本机作为服务器也可以是云服务器)上先启用服务然后客户端再去连接这个服务再去调用而不是和stdin连接一样自动拉起一个服务启用服务完成后编写测试方法查看该服务是否可以正确使用返回结果如下点击返回的图片url发现图片是正确的服务调用成功