MCP服务开发和进阶
这是被调用端的配置。有两种方式: 一种是被调用端使用stdio,一种是ssestdio是没有端口的,只能在你本机上引入jar包使用,但是SSE是设置端口,别的机器配置你这个工具运行的端口后就可以调用你这工具。然后在yml文件中配置:1. (核心)直译:Spring 主程序的 Web 应用类型 = 无通俗解释:告诉 Spring Boot “这个应用不是 Web 应用”,不会启动内嵌的 Tomcat
<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开发最佳实践




更多推荐


所有评论(0)