利用Spring AI 能快速搭建MCP服务器,通过内置的客户端能方便的与服务器进行工具调用、日志传输、进度通知、采样、启发式请求以及资源管理等。
一、以webmvc 方式启动MCP服务器
1. 添加依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
2. 以STREAMABLE模式配置服务器协议
spring:
application:
name: smart-mcp-server
ai:
mcp:
server:
enabled: true
type: SYNC
protocol: STREAMABLE
annotation-scanner:
enabled: true
name: my-mcp-server
version: 1.0.0
server:
port: 7000
两种协议STATELESS和STREAMABLE的主要区别是前者不支持双向操作,后者支持双向操作。这意味着在STATELESS协议下,不能在服务器执行工具调用的同时向客户端传输日志、进度信息、请求客户端采样,也不能ping客户端。
二、配置客户端
1. 添加依赖:
...
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
...
2. 修改配置文件
spring:
application:
name: smart-mcp-client
ai:
mcp:
client:
type: SYNC
streamable-http:
connections:
my-server-1:
url: http://localhost:7000
在不修改访问路径或端点时,两端默认的端点是/mcp
三、使用MCP服务器中的工具
1. 在客户端中使用工具
当配置正确后启动客户端程序,程序会自动去连接MCP服务器,如果连接不上会导致启动失败,通常会报404错误。要在ChatClient中使用MCP提供的工具,需要用到ToolCallbackProvider,所有MCP客户端注册的MCP工具将使用它的实例来提供,MCP 客户端会自动创建这个它的bean
@Bean
ChatClient defaultChatClient(ChatClient.Builder builder, ToolCallbackProvider toolCallbackProvider) {
return builder
.defaultTools(toolCallbackProvider)
.build();
}
可以通过如下方式看到客户端注册的全部工具
ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();
2. 在MCP服务器中声明工具
MCP相关注解都以Mcp开头,MCP工具的声明跟普通工具的声明几乎一样:
@Component
public class CalculatorTools {
@McpTool(name = "getCurrentDataTime", description = "Get the current date and time,in the format: yyyy‑MM‑dd HH:mm:ss")
public String getCurrentDataTime() {
LocalDateTime now = LocalDateTime.now();
return now.format(DateTimeFormatter.ofPattern("yyyy‑MM‑dd HH:mm:ss"));
}
@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;
}
}
工具的声明需要框架能够扫描到,一般放在@Component标记的类里面。
3. 日志传输
在MCP服务端运行工具时,可以向客户端传输日志来记录工具的执行情况。
(1) 服务端发送日志
在服务器的工具参数定义中使用McpSyncRequestContext。
@McpTool(name = "getCurrentDataTime", description = "Get the current date and time,in the format: yyyy‑MM‑dd HH:mm:ss")
public String getCurrentDataTime(
McpSyncRequestContext context
) {
LocalDateTime now = LocalDateTime.now();
context.info("handling tool calling: getCurrentDataTime");
return now.format(DateTimeFormatter.ofPattern("yyyy‑MM‑dd HH:mm:ss"));
}
McpSyncRequestContext有info、warning、debug、error 不同的日志级别可以使用。
(2)客户端接收日志
@Component
public class LoggingHandler {
@McpLogging(clients = "my-server-1")
public void handleLoggingMessage(McpSchema.LoggingMessageNotification notification) {
System.out.println("Received log: " + notification.level() +
" - " + notification.data());
}
这里要注意McpLogging的参数clients是指客户端配置中的连接名称,这里是my-server-1。如果使用错误不会报错,但不会收到日志信息。
4. 采样
MCP采样的逻辑是MCP在执行工具过程中,需要再次调用大模型。这里模拟一个实用场景,向大模型输入一个连接地址,大模型需要给出连接内容的总结。
(1) MCP服务器发送采样请求
MCP调用summarizeArticle工具,使用大模型传输过来的link参数,下载网页内容,然后传回客户端请求大模型总结。
@McpTool(name = "summarizeArticle",description = "Summarize the articles connected by the specified link.")
String summarizeArticle(
@McpToolParam(description = "The link address of the article",required = true)
String link,
McpSyncRequestContext context
) throws IOException {
//爬取网页内容
...
//把网页内容传回客户端请求采样,设置特别的温度值
McpSchema.CreateMessageResult samplingResult =context.sample(samplingSpec -> {
...
});
return samplingResult.content().toString();
}
调用context.sample()方法请求客户端采样。
(2)客户端处理采样请求
@McpSampling(clients = "my-server-1")
public McpSchema.CreateMessageResult handleSamplingRequest(McpSchema.CreateMessageRequest request) {
// 将不同的SampleingMessage转换为Message,这里只处理text类型
List<Message> list= request.messages().stream().map(
samplingMessage -> {
...
}
).toList();
//用 request.temperature(),request.systemPrompt(),list 请求大模型,并返回结果str给MCP服务器
...
return McpSchema.CreateMessageResult.builder(McpSchema.Role.ASSISTANT, str, "deepseek")
.build();
}
采样请求中的消息是SamplingMessage类型,需要转换成ChatClient能接收的Message类型,然后通过McpSchema.CreateMessageResult回传结果给MCP服务端。
5. 启发式请求
启发式请求是MCP服务器工具希望得到用户的更多信息,通常由用户输入。
(1) MCP服务器发送启发式请求
@McpTool(name = "getUserInfo",description = "Get user name and email")
String getUserInfo(
McpSyncRequestContext context
) {
...
HashMap<String,Object> requestedSchema=new HashMap<>();
...
McpSchema.ElicitFormRequest elicitFormRequest=McpSchema.ElicitFormRequest
.builder("input user`s name and email",requestedSchema)
.build();
McpSchema.ElicitResult result=context.elicit(elicitFormRequest);
...
//处理结果
if(result.action()==McpSchema.ElicitResult.Action.ACCEPT){
...
}
else if(result.action()==McpSchema.ElicitResult.Action.DECLINE){
...
}
}
关键步骤是调用context.elicit方法,需要传入一个McpSchema.ElicitRequest类型的参数,它目前有两个实现类,McpSchema.ElicitFormRequest 和McpSchema.ElicitUrlRequest , 它们的model类型分别是"form"和"url"。在这里使用前者,它的builder方法的第一次参数是传入需要提示用户的消息,第二个参数是一个Map对象,表示需要用户输入的相关项目。处理结果时间需要判断McpSchema.ElicitResult的action值。
(2)客户端处理启发式请求
@McpElicitation(clients = "my-server-1")
public McpSchema.ElicitResult handleElicitationRequest(McpSchema.ElicitRequest request) {
Map<String, Object> userData = new HashMap<String,Object>();
...
if(request.mode().equals("form")){
...
//装入用户输入的信息
}
...
if (userData != null) {
return new McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, userData);
} else {
return new McpSchema.ElicitResult(McpSchema.ElicitResult.Action.DECLINE, null);
}
}
关键步骤是根据不同的mode来处理不同的用户输入,根据MCP传过来的schema获得需要输入的项目,最后设置acion和Map类型的用户信息返回结果给服务端。
6. 进度通知
(1)MCP服务端发送进度通知
@McpTool(name = "addFromTo", description = "Summation of a sequence of natural numbers,the start number of the sequence must be smaller than the end number. ")
String addFromTo(
@McpToolParam(description = "this parameter represents the start of the sequence.") int start,
@McpToolParam(description = "this parameter represents the end of the sequence.") int end,
McpSyncRequestContext context
){
...
McpSchema.ProgressNotification notification=McpSchema.ProgressNotification.builder("111-11111-111",50).message("now progress is ").total(100.0).build();
context.progress(notification);
...
return "the result is "+result;
}
关键步骤是调用context.progress()方法,需要传教McpSchema.ProgressNotification对象,特别注意要传入一个进度token,如果没有这个token,客户端无法收到进度通知。这里的token是"111-11111-111"
(2)客户端接收进度通知
@McpProgress(clients = "my-server-1")
public void progressNotification(McpSchema.ProgressNotification notification){
...
}
四、MCP资源管理
1. 服务端资源管理
MCP可以存放资源,包括文字或者二进制资源,通过MCP客户端可以很方便的读取,下面以文字资源为例,模拟服务器端资源管理。用@McpResource注解定义资源方法,通过参数uri定义的位置访问资源,注意这里的路径变量key的使用。
...
@McpResource(
name = "poemResource",
description = "the poem libaray",
uri = "poem://{key}"
)
String poemResource(String key){
return poems.get(key);
}
...
2. 客户端获取资源
首先要获取客户端实例,Spring会自动创建客户端bean,这里需要注意,注入的MCP客户端实例是一个List集合对象,不是单个对象。
@Resource
List<McpSyncClient> mcpClients;
创建McpSchema.Resource对象,传入资源的uri,然后调用客户端的readResource()获取资源
String uri=String.format("poem://%s","静夜思");
resource=McpSchema.Resource.builder(uri,"poemResource").build();
McpSchema.TextResourceContents contents= (McpSchema.TextResourceContents) mcpClients.getFirst().readResource(resource).contents().getFirst();
String poemText=contents.text();
这里的"poemResource"是资源的名称,不能缺少,可以是任意字符串。Spring MCP定义了两种资源:BlobResourceContents二进制大对象资源和 TextResourceContents文本资源。
五、提示词管理
1.服务端提示词管理
用@McpPrompt注解定义提示词方法,@McpArg注解定义获取提示词需要的参数,通常用于替换提示词里面的变量。用McpSchema.PromptMessage对象封装提示词内容,用McpSchema.GetPromptResult构造提示词返回结果。
@McpPrompt(name ="travelGuideSystemPrompt",description = "Travel guide system prompt")
public McpSchema.GetPromptResult travelGuideSystemPrompt(
@McpArg (description = "the name of the robot",required = true)
String robotName){
...
List<McpSchema.PromptMessage> list=List.of(
...
);
return McpSchema.GetPromptResult.builder(list).description("Travel guide system prompt").build();
}
2. 客户端获取提示词
Map<String,Object> argMap= Map.of("robotName","peter");
McpSchema.GetPromptRequest request= McpSchema.GetPromptRequest.builder("travelGuideSystemPrompt").arguments(argMap).build();
McpSchema.TextContent content= (McpSchema.TextContent) mcpClients.getFirst().getPrompt(request).messages().getFirst().content();
return content.text();
用McpSchema.GetPromptRequest构造提示词获取请求,需要传入服务端提示词方法定义的name,用Map对象进行传参。
六、补全
MCP补全是让MCP服务器能为客户端的输入提供自动补全建议,类似 IDE 里的代码提示。Spring AI 支持提示词补全和资源补全,补全方法需要与存在的提示词和资源对应,客户端请求补全时需要提供提示词或资源方法的参数,来获取提示词和资源集合。
以上代码会逐步整理上传至gitee。