本文我以Pexels图片搜索服务为例,详细实战演示了MCP服务端和客户端的开发流程,包括配置、工具注册和单元测试。

MCP协议

什么是MCP呢?MCP是Model Context Protocol模型上下文协议,是一种开放标准,不是一种技术,MCP目的是增强AI与外部系统交互的能力,接入别人提供的服务来实现更多的功能,统一标准,降低使用和理解成本,打造服务生态,造福广大开发者。

那为什么MCP这么火,而工具调用却没那么火?这是因为工具调用是面向我们程序员,而MCP是大家都能直接接入使用。

SDK的三层架构:客户端/服务器层、会话层、传输层,客户端和服务端之间传输可以通过两种方式一种是标准的输入/输出Stdio模式,在本地就像在终端小黑框客户端和服务器聊的有来有回。一种是远程HTTP请求的SSE模式,SSE作用是让服务器持续不断的给客户端发消息。

MCP 架构的主要参与者是:

MCP 主机:协调和管理一个或多个 MCP 客户端的 AI 应用程序

MCP 客户端:维护与 MCP 服务器的连接并从 MCP 服务器获取上下文供 MCP 主机使用的组件

MCP 服务器:向 MCP 客户端提供上下文的程序

例如:Visual Studio Code 充当 MCP 主机。当 Visual Studio Code 建立与 MCP 服务器(如 Sentry MCP 服务器)的连接时,Visual Studio Code 运行时会实例化一个 MCP 客户端对象,该对象用于维护与 Sentry MCP 服务器的连接。 当 Visual Studio Code 随后连接到另一个 MCP 服务器(例如本地文件系统服务器)时,Visual Studio Code 运行时会实例化一个额外的 MCP 客户端对象来维护此连接,从而保持一对一 MCP 客户端与 MCP 服务器的关系。

Spring AI MCP开发模式

Spring AI在MCP官方Java SDK的基础上额外封装了一层,提供了和Spring Boot整合的SDK,支持客户端和服务端的普通调用和响应式调用。那让我们接下来看看如何使用Spring AI开发MCP客户端和服务端吧!

开发MCP客户端:可以参考一下Spring AI官方文档的MCP Client Boot Starters,提供了两种客户端SDK:1、Standard MCP Client,核心启动器提供了Stdio和SSE支持, 2、WebFlux Client,基于WebFlux的SSE传输实现,一般用第一种就好了。

步骤:引入依赖、配置连接、使用服务

步和异步客户端类型,还可以自定义定制客户端行为

开发MCP服务端:同理,参考Spring AI官方文档的MCP Server Boot Starters,提供了3种SDK,有Stdio、WebMVC SSE、WebFlux SSE,支持非响应式和响应式编程,一般建议引spring-ai-starter-mcp-server-webmvc,要用到时候看官方文档就好了。

入依赖、配置服务、开发服务

其他特性:提供工具、资源管理、提示词管理、根目录变更处理

MCP开发实战(图片搜索服务)

使用Pexels图片资源网站的API(免费!免费!)来构建图片搜索服务。

注册登录,然后点击右上角的三个点选择图片和视频API来生成Pexels API密钥,先复制一下密钥,然后我们就可以来使用Pexels来实现图片搜索服务。

MCP服务端开发

在dog-ai-agent根目录下新建一个dog-image-search-mcp-server模块,选用Maven和jdk21,下一步引入lombok然后自己选择一下springboot版本然后创建,创建完建议大家单独的打开这个项目,不要在当前的目录中打开它,因为要去搜索资源的话在当前的子目录获取到的路径是不正确的。右击创建的模块,选择Open In的explorer,然后会弹出文件的窗口,选择刚刚创建的文件右击选择idea打开,然后选择New Window。

刚才在创建时引入了lombok,那首先我们在pom.xml来引入Hutool的依赖和我们要用的MCP的WebMVC SSE的依赖:

<dependency>
  <groupId>cn.hutool</groupId> 
  <artifactId>hutool-all</artifactId> 
  <version>5.8.38</version>
</dependency>
<dependency>  
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-mcp-server</artifactId>
  <version>1.0.0</version>
</dependency>

注:引入WebMVC SSE依赖后,会自动注册SSE端点,包括消息和SSE端点,能够供客户端调用

然后我们要在resource目录下来编写两套服务端配置文件,分别实现Stdio和SSE模式传输,首先将原本的application.properties改为application-stdio.yml进行Refactor,然后将生成的一行代码删掉,编写以下代码:

spring:
    ai: 
        mcp:   
            server:  
                name: dog-image-search-mcp-server  
                version: 0.0.1   
                type: SYNC   
                # stdio  
                stdio: true 
    # stdio 
    main:  
        web-application-type: none 
        banner-mode: off

接着继续搞SSE的,在resource目录下新建一个application-sse.yml,编写以下代码:

spring:
    ai: 
        mcp: 
        server:   
            name: dog-image-search-mcp-server  
            version: 0.0.1  
            type: SYNC  
            # sse   
            stdio: false

补充:可能你的左边目录包很多红色,这不是报错,这是因为没有添加到Git管理,我们右击dog-image-search-mcp-server找到Git选择Add就可以了。

为了方便灵活切换这两套配置,我们在resource目录下新建一个application.yml,编写以下配置代码:

spring:
    application: 
        name: dog-image-search-mcp-server 
    profiles: 
        active: stdio
server: 
    port: 8127

然后在dogimagesearchmcpserver下新建一个tools包,再在包下新建一个ImageSearchTool类,那我们要如何编写搜索图片的java代码呢,可以进入Pexels的右上角三个点的图片和视频API,然后点击文档,找到Photos下的Search for Photos,将这个文档丢给AI帮我们生成基于Hutool工具库的HTTP工具类的自动搜索图片功能的java代码,然后复制粘贴到我们的ImageSearchTool类,代码如下:

@Service
public class ImageSearchTool { 
    // 替换为你的 Pexels API 密钥(需从官网申请)
    private static final String API_KEY = "你的 API Key";  
    // Pexels 常规搜索接口(请以文档为准)
    private static final String API_URL = "https://api.pexels.com/v1/search"; 
    @Tool(description = "search image from web") 
    public String searchImage(@ToolParam(description = "Search query keyword") String query) { 
        try {   
            return String.join(",", searchMediumImages(query));   
        } catch (Exception e) {  
            return "Error search image: " + e.getMessage();  
        } 
    }  
    /**  
    * 搜索中等尺寸的图片列表 
    *   
    * @param query  
    * @return
    */  
    public List<String> searchMediumImages(String query) {   
        // 设置请求头(包含API密钥)
        Map<String, String> headers = new HashMap<>(); 
        headers.put("Authorization", API_KEY); 
        // 设置请求参数(仅包含query,可根据文档补充page、per_page等参数) 
        Map<String, Object> params = new HashMap<>();  
        params.put("query", query);
        // 发送 GET 请求   
        String response = HttpUtil.createGet(API_URL)   
        .addHeaders(headers)  
        .form(params)   
        .execute()        
        .body(); 
        // 解析响应JSON(假设响应结构包含"photos"数组,每个元素包含"medium"字段) 
        return JSONUtil.parseObj(response)  
        .getJSONArray("photos")  
        .stream()          
        .map(photoObj -> (JSONObject) photoObj)   
        .map(photoObj -> photoObj.getJSONObject("src"))  
        .map(photo -> photo.getStr("medium"))    
        .filter(StrUtil::isNotBlank)   
        .collect(Collectors.toList());  
    }
}

记得要将API key替换成自己的,然后生成对应的单元测试进行测试一下,代码如下:

@SpringBootTest
class ImageSearchToolTest { 
    @Resource  
    private ImageSearchTool imageSearchTool; 
    @Test 
    void searchImage() {    
        String result = imageSearchTool.searchImage("dog");    
        Assertions.assertNotNull(result); 
    }
}

接着在启动类定义ToolCallbackProvider Bean注册工具,代码如下:

@SpringBootApplication
public class DogImageSearchMcpServerApplication { 
    public static void main(String[] args) { 
        SpringApplication.run(DogImageSearchMcpServerApplication.class, args);
    }  
    @Bean  
    public ToolCallbackProvider imageSearchTools(ImageSearchTool imageSearchTool) {   
        return MethodToolCallbackProvider.builder()   
        .toolObjects(imageSearchTool)    
        .build();  
    }
}

然后呢,客户端是怎么基于本地模式调用我们的MCP服务呢?是客户端会先找到我们MCP的配置,找到启动MC服务P的命令,开一个子进程去运行这个服务。所以呢,我们接下来要在Maven中去package给我们的本地MCP服务打个包,接着打包成功后会在target目录下看到这个jar包,客户端运行时候就是运行这个jar包。

注意:这边可能又会报错不支持发行版21,我们的文档开头有说如何解决,如果忘了可以滑到开头去看看。如果还不行,就在Setting里面的Build Tool里的Maven下的Runner将JRE改为21应该就可以了。

那MCP服务端开发完成,咱们来开发MCP客户端吧。

MCP客户端开发

在根项目中开发客户端,调用刚才创建的图片搜索服务,首先引入MCP客户端MCP Client依赖(刚刚已经引入过了),接着来编写我们的配置,打开resource目录下的mcp-servers.json,里面有我们前面配置过的高德地图,记得去ignore密钥噢,不要把密钥提交到git仓库。我们继续,mcp-servers.json最终代码如下:

{
  "mcpServers": {
    "amap-maps": { 
      "command": "npx.cmd", 
      "args": [   
        "-y",  
        "@amap/amap-maps-mcp-server"  
      ],  
      "env": {
        "AMAP_MAPS_API_KEY": "your-api-key"   
      }   
    }, 
    "dog-image-search-mcp-server": { 
      "command": "java", 
      "args": [ 
        "-Dspring.ai.mcp.server.stdio=true",
        "-Dspring.main.web-application-type=none",  
        "-Dlogging.pattern.console=",    
        "-jar",  
        "dog-image-search-mcp-server/target/dog-image-search-mcp-server-0.0.1-SNAPSHOT.jar"  
      ],   
      "env": {}
    } 
  }
}

然后在test里的TravelAppTest编写图片搜索服务的单元测试,将之前地图的测试注释掉进行测试,代码如下:

@Test
void doChatWithMcp() {  
    String chatId = UUID.randomUUID().toString();  
    // 测试地图 MCP
    //        String message = "我在福建福州东街口,请帮我找个约会地点";
    //        String answer =  travelApp.doChatWithMcp(message, chatId);
    //        Assertions.assertNotNull(answer);  
    // 测试图片搜索 MCP   
    String message = "帮我搜索一些萨摩耶狗在游泳池里玩耍的图片";  
    String answer =  travelApp.doChatWithMcp(message, chatId);  
    Assertions.assertNotNull(answer);   
}
}

到此,我们思考一个问题,你要开发这个图片搜索的功能,如果是你你会选择开发工具调用还是开发MCP服务呢?其实很简单,能用工具调用就用工具调用,引入MCP依赖再开发挺麻烦的。

下面我们来试试SSE连接方式,首先修改MCP服务端的配置文件,就是dog-image-search-mcp-server的application.yml中将stdio改为sse,然后在启动类中以Debug启动。然后修改客户端的配置文件,要将stdio的注释掉:

mcp:
      client:
        sse:
          connections:
            server1:
              url: http://localhost:8127
#        stdio:
#          servers-configuration: classpath:mcp-servers.json

运行doChatWithMcp测试一下。

MCP部署方案有两种:本地部署和远程部署

MCP安全问题

1、权限失控风险

用户过度授权(如允许AI“读写所有文件”或“访问全部网络”)。一旦AI被恶意诱导或存在逻辑漏洞,将利用这些宽泛权限造成数据泄露、系统破坏等严重损失。

2、服务器端攻击风险

MCP连接的第三方工具服务器可能“作恶”或有漏洞。恶意服务器可提供虚假数据、窃取AI发送的敏感信息(如数据库凭证),或利用服务器漏洞发起进一步攻击。

3、提示词注入与越权操作

攻击者通过精心构造的输入,诱导AI绕过正常逻辑,滥用其已获得的工具权限。例如,让一个被授权总结邮件的AI,利用文件写入工具去删除系统文件,执行非预期的高危操作。

4、供应链与依赖风险

MCP工具(Server)通常来自开源社区或第三方,可能包含恶意代码或存在未修补的漏洞。广泛使用的工具若被污染或存在漏洞,会导致大规模的安全事件,影响所有集成该工具的应用。

5、数据泄露与隐私暴露

AI通过MCP访问大量敏感数据源(邮件、数据库、API),处理不当极易导致信息泄露。AI可能在回复中完整输出敏感信息;对话记录被窃取;或上下文残留敏感数据并在后续对话中无意泄露。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐