<dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
        </dependency>

这是被调用端的配置

有两种方式: 一种是被调用端使用stdio,一种是sse

stdio是没有端口的,只能在你本机上引入jar包使用,但是SSE是设置端口,别的机器配置你这个工具运行的端口后就可以调用你这工具。

下面先来讲stdio方式

然后在yml文件中配置:

spring:
  application:
    name: yu-image-search-mcp-server
  profiles:
    active: stdio
server:
  port: 8127
  
spring:
  ai:
    mcp:
      server:
        name: yu-image-search-mcp-server
        version: 0.0.1
        type: SYNC
        # stdio
        stdio: true
  # stdio
  main:
    web-application-type: none
    banner-mode: off

1. spring.main.web-application-type: none(核心)

  • 直译:Spring 主程序的 Web 应用类型 = 无
  • 通俗解释:告诉 Spring Boot “这个应用不是 Web 应用”,不会启动内嵌的 Tomcat/Jetty/Undertow 等 Web 服务器。
  • 适用场景
    • 纯控制台程序(比如你之前写的动态参数测试、定时任务、批处理程序);
    • 不需要对外提供 HTTP 接口的后台服务(比如只做数据处理、调用其他接口的工具类程序);
  • 对比理解
    • 如果设为 servlet:启动 Tomcat,支持传统 Servlet 架构(Spring MVC);
    • 如果设为 reactive:启动 Netty,支持响应式 Web 架构(Spring WebFlux);
    • 如果设为 none:不启动任何 Web 服务器,应用启动后不会监听任何端口(比如 8080)。

2. spring.main.banner-mode: off

  • 直译:Spring 主程序的 Banner 展示模式 = 关闭
  • 通俗解释:禁用 Spring Boot 启动时控制台打印的那个标志性的 “SPRING BOOT” 字符画(Banner)。
  • 效果对比
    • 默认(不配置 / 设为 console):启动时控制台会输出:

      plaintext

        .   ____          _            __ _ _
       /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
      ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
       \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
        '  |____| .__|_| |_|_| |_\__, | / / / /
       =========|_|==============|___/=/_/_/_/
       :: Spring Boot ::                (v3.4.4)
      
    • 设为 off:启动时不会输出上述字符画,控制台只显示核心日志(比如启动耗时、组件加载信息)。
  • 其他可选值
    • log:把 Banner 输出到日志文件(而非控制台);
    • console:默认值,输出到控制台。
  • .

然后下一步:

写一个tool类,这是以获取照片工具为例,就是给ai一句话比如说:找个彭于晏的照片,然后他就会去在pexel中获取照片。

package com.yupi.yuaiagent.tools;

import jakarta.annotation.Resource;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 集中的工具注册类
 */
@Configuration
public class ToolRegistration {

    @Value("${search-api.api-key}")
    private String searchApiKey;
    @Resource
    private ToolCallbackProvider toolCallbackProvider;

    @Bean
    public ToolCallback[] allTools() {
        FileOperationTool fileOperationTool = new FileOperationTool();
        WebSearchTool webSearchTool = new WebSearchTool(searchApiKey);
        WebScrapingTool webScrapingTool = new WebScrapingTool();
        ResourceDownloadTool resourceDownloadTool = new ResourceDownloadTool();
        TerminalOperationTool terminalOperationTool = new TerminalOperationTool();
        PDFGenerationTool pdfGenerationTool = new PDFGenerationTool();
        TerminateTool terminateTool = new TerminateTool();
        // 2. 实例化自定义的文件读取工具
//        FileReadingTool fileReadingTool = new FileReadingTool();
        return ToolCallbacks.from(package com.yupi.yuimagesearchmcpserver.tools;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@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());
    }
}

                fileOperationTool,
//                fileReadingTool, // 新增自定义工具
                toolCallbackProvider,//MCP工具的调用
                webSearchTool,
                webScrapingTool,
                resourceDownloadTool,
                terminalOperationTool,
                pdfGenerationTool,
                terminateTool
        );
    }
}

都是模版代码,你只需要配置你的key就可以。

然后需要在启动类配置:

@SpringBootApplication
public class YuImageSearchMcpServerApplication {
    // 1. 启动类(基础,无需解释)
    public static void main(String[] args) {
        SpringApplication.run(YuImageSearchMcpServerApplication.class, args);
    }

    // 2. 核心Bean:注册图片搜索工具为MCP可识别的Provider
    @Bean
    public ToolCallbackProvider imageSearchTools(ImageSearchTool imageSearchTool) {
        // 3. 构建器模式创建MethodToolCallbackProvider(Spring AI官方推荐)
        return MethodToolCallbackProvider.builder()
                // 4. 把自定义工具注入到Provider中
                .toolObjects(imageSearchTool)
                .build();
    }
}

public ToolCallbackProvider imageSearchTools(ImageSearchTool imageSearchTool) 里面的参数就是刚刚写的那个工具类,然后进行mavn package进行打成jar包。

补充:

因为ImageSearchTool类用到了hutool工具所以需要引用hutool依赖

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.37</version>
</dependency>

下一步:

就是在调用方干的事

依旧引入依赖

yml文件配置:

mcp:
#      client:
#        stdio:
#          servers-configuration: classpath:mcp-servers.json

然后json文件需要这样写:

{
  "mcpServers": {
    "yu-image-search-mcp-server": {
      "command": "java",
      "args": [
        "-Dspring.ai.mcp.server.stdio=true",
        "-Dspring.main.web-application-type=none",
        "-Dlogging.pattern.console=",
        "-jar",
        "yu-image-search-mcp-server/target/yu-image-search-mcp-server-0.0.1-SNAPSHOT.jar"
      ],
      "env": {}
    }
  }
}
"yu-image-search-mcp-server/target/yu-image-search-mcp-server-0.0.1-SNAPSHOT.jar"这个就是那个jar包。

然后下面是:

业务代码:

      @Resource
        private ToolCallbackProvider toolCallbackProvider;
    
        /**
         * AI 恋爱报告功能(调用 MCP 服务)
         *
         * @param message
         * @param chatId
         * @return
         */
        public String doChatWithMcp(String message, String chatId) {
            ChatResponse chatResponse = chatClient
                    .prompt()
                    .user(message)
                    .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId))
                    // 开启日志,便于观察效果
                    .advisors(new MyLoggerAdvisor())
                    .toolCallbacks(toolCallbackProvider)
                    .call()
                    .chatResponse();
            String content = chatResponse.getResult().getOutput().getText();
            log.info("content: {}", content);
            return content;
        }

只需引入    @Resource
        private ToolCallbackProvider toolCallbackProvider;这个然后在写在toolcallbacks里面就可以了。

下面看启动类:

    @Test
    void doChatWithMcp() {
        String chatId = UUID.randomUUID().toString();
        // 测试地图 MCP
//        String message = "我的另一半居住在上海静安区,请帮我找到 5 公里内合适的约会地点";
//        String answer =  loveApp.doChatWithMcp(message, chatId);
//        Assertions.assertNotNull(answer);
        // 测试图片搜索 MCP
        String message = "帮我搜索一些哄另一半开心的图片";
        String answer =  loveApp.doChatWithMcp(message, chatId);
        Assertions.assertNotNull(answer);
    }

下面来讲SSE方式。

依赖引入就不讲了。

下面是被调用端:

下面直接来看yml配置:

spring:
  application:
    name: yu-image-search-mcp-server
  profiles:
    active: sse
server:
  port: 8127
  
spring:
  ai:
    mcp:
      server:
        name: yu-image-search-mcp-server
        version: 0.0.1
        type: SYNC
        # sse
        stdio: false

这里是用来区分所以多写了个yml。

然后剩下的都不变跟上面那个是一样的。

下面来看调用端:

   依旧yml:

  #    mcp:
  #      client:
  #        sse:
  #          connections:
  #            server1:
  #              url: http://localhost:8127

这个端口就是工具运行的那个端口。

然后剩下的就都不变,只是不用配置json文件,记住sse和stdio这两种方式只能选择一种。

下面是MCP开发最佳实践

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐