接着上篇:https://blog.csdn.net/sinat_15906013/article/details/147052013,我们介绍了,什么是MCP?使用cline插件/cherry-studio安装了Mcp Server,本篇我们要借助spring-ai实现MCP Client和Server。
使用spring-ai的话,需要spring-boot3和JDK17。
MCP Server
- 引入mcp-server依赖
xml
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M7</spring-ai.version>
</properties>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 使用@Tool声明方法,使用@ToolParam声明参数
java
@Service
public class EchoService {
/**
* echo
*
* @param msg
* @return String
*/
@Tool(description = "echo")
public String echo(@ToolParam(description = "消息") String msg) {
return msg;
}
}
- 将EchoService注入到ToolCallbackProvider
java
@Configuration
public class McpServerConfig {
@Bean
public ToolCallbackProvider allTools(EchoService echoService) {
return MethodToolCallbackProvider.builder()
.toolObjects(echoService)
.build();
}
}
- 单元测试
java
class ToolTest {
private final static String url = "http://localhost:8899";
private static McpSyncClient client;
@BeforeAll
public static void init() {
var transport = new WebFluxSseClientTransport(WebClient.builder().baseUrl(url));
client = McpClient.sync(transport).build();
client.initialize();
client.ping();
}
@Test
public void test() {
// List and demonstrate tools
ListToolsResult toolsList = client.listTools();
System.out.println("Available Tools = " + toolsList);
CallToolResult echo = client.callTool(new CallToolRequest("echo",
Map.of("msg", "hello world")));
System.out.println("echo: " + echo);
client.closeGracefully();
}
}
- 扩展mcp-server不仅提供了tool,还有prompt和resource
java
@Configuration
public class PromptManagement {
@Bean
public List<McpServerFeatures.SyncPromptSpecification> myPrompts() {
var prompt = new McpSchema.Prompt("greeting", "A friendly greeting prompt",
List.of(new McpSchema.PromptArgument("name", "The name to greet", true)));
var promptSpecification = new McpServerFeatures.SyncPromptSpecification(prompt, (exchange, getPromptRequest) -> {
String nameArgument = (String) getPromptRequest.arguments().get("name");
if (nameArgument == null) { nameArgument = "friend"; }
var userMessage = new McpSchema.PromptMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello " + nameArgument + "! How can I assist you today?"));
return new McpSchema.GetPromptResult("A personalized greeting message", List.of(userMessage));
});
return List.of(promptSpecification);
}
}
java
@Configuration
public class ResourceManagement {
@Bean
public List<McpServerFeatures.SyncResourceSpecification> myResources() {
var systemInfoResource = new McpSchema.Resource("file:///logs/app.log", "Application Logs", "Application Logs", "text/plain",
new McpSchema.Annotations(List.of(McpSchema.Role.USER), 1.0));
var resourceSpecification = new McpServerFeatures.SyncResourceSpecification(systemInfoResource, (exchange, request) -> {
try {
var systemInfo = Map.of();
String jsonContent = new ObjectMapper().writeValueAsString(systemInfo);
return new McpSchema.ReadResourceResult(
List.of(new McpSchema.TextResourceContents(request.uri(), "application/json", jsonContent)));
} catch (Exception e) {
throw new RuntimeException("Failed to generate system info", e);
}
});
return List.of(resourceSpecification);
}
}
参考前面的单元测试:client.listTools()
,下面两种应该也不在话下。
MCP Client
- 引入mcp-client依赖
xml
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M7</spring-ai.version>
</properties>
<!-- web&webflux start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- web&webflux end -->
<!-- model start -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.ai</groupId>-->
<!-- <artifactId>spring-ai-starter-model-openai</artifactId>-->
<!-- </dependency>-->
<!-- model end -->
<!-- MCP start -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
<!-- MCP end -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
-
在application.properties配置stdio方式的mcp-server
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json
发没发现,配合和之前的客户端一致,mcp-servers-config.json如下:
json
{
"mcpServers": {
"time": {
"command": "cmd",
"args": [
"/c",
"uvx",
"mcp-server-time",
"--local-timezone=Asia/Shanghai"
]
},
"filesystem": {
"command": "cmd",
"args": [
"/c",
"npx",
"-y",
"@modelcontextprotocol/server-filesystem",
"D:\\SourceCode\\bsp-mcp"
]
}
}
}
- 在application.yaml配置模型和sse方式的mcp-server
yaml
spring:
ai:
ollama:
base-url: http://192.168.199.255:11434
chat:
options:
model: qwen2.5:32b
mcp:
client:
toolcallback:
enabled: true
sse:
connections:
amap:
url: https://mcp.amap.com?key=xxx
my:
url: http://localhost:8899
- 配置模型集成MCP
ChatModel是对话模型,常见的还有Embedding是关于RAG的。
OllamaChatModel是支持function call的,我们就以它为例。
来自:https://docs.spring.io/spring-ai/reference/api/index.html#_ai_model_api
java
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient client(OllamaChatModel ollamaChatModel,
SyncMcpToolCallbackProvider syncMcpToolCallbackProvider) {
return ChatClient.builder(ollamaChatModel)
.defaultTools(syncMcpToolCallbackProvider)
.build();
}
}
- 对外提供API
java
@RequiredArgsConstructor
@RestController
public class MCPController {
private final ChatClient client;
private final List<McpSyncClient> mcpClientList;
@GetMapping("/ai/mcp/generate")
public Map<String, String> generate(@RequestParam(value = "message", defaultValue = "几点了?") String message) {
return Map.of("generation", client.prompt(message).call().content());
}
@GetMapping("/ai/mcp/servers")
public List<McpSyncClient> servers() {
return mcpClientList;
}
@GetMapping("/ai/mcp/tools")
public McpSchema.ListToolsResult tools(String name) {
return mcpClientList.stream().filter(mcpSyncClient ->
StringUtils.endsWithIgnoreCase(name, mcpSyncClient.getServerInfo().name())
).findFirst().get().listTools();
}
@GetMapping("/ai/mcp/resources")
public McpSchema.ListResourcesResult resources(String name) {
return mcpClientList.stream().filter(mcpSyncClient ->
StringUtils.endsWithIgnoreCase(name, mcpSyncClient.getServerInfo().name())
).findFirst().get().listResources();
}
@GetMapping("/ai/mcp/prompts")
public McpSchema.ListPromptsResult prompts(String name) {
return mcpClientList.stream().filter(mcpSyncClient ->
StringUtils.endsWithIgnoreCase(name, mcpSyncClient.getServerInfo().name())
).findFirst().get().listPrompts();
}
}
抓包分析交互流程

来自:https://docs.spring.io/spring-ai/reference/api/index.html#_tool_calling_api