之前做个几个大模型的应用,都是使用Python语言,后来有一个项目使用了Java,并使用了Spring AI框架。随着Spring AI不断地完善,最近它发布了1.0正式版,意味着它已经能很好的作为企业级生产环境的使用。对于Java开发者来说真是一个福音,其功能已经能满足基于大模型开发企业级应用。借着这次机会,给大家分享一下Spring AI框架。
注意:由于框架不同版本改造会有些使用的不同,因此本次系列中使用基本框架是 Spring AI-1.0.0,JDK版本使用的是19,Spring-AI-Alibaba-1.0.0.3-SNAPSHOT。
代码参考: https://github.com/forever1986/springai-study
上一章演示了Graph框架的并行执行流程,并剖析了其中ParallelNode的实现逻辑。这一章来讲一下Graph如何访问MCP。
1 示例演示
代码参考lesson26子模块中的graph-mcp子模块
示例说明:通过构建一个Graph图,其中定义一个访问MCP节点
1.1 初始化
1)本次MCP服务采用前面lesson10子模块的sse-server子模块作为MCP Server,启动该服务
2)在lesson26子模块下,新建graph-mcp子模块,其pom引入如下:
<dependencies>
<!-- 引入智谱的model插件 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-graph-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
<!-- 需要引入gson插件 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
</dependencies>
3)新建application.properties配置文件
# 聊天模型
spring.ai.zhipuai.api-key=你的智谱API KEY
spring.ai.zhipuai.chat.options.model=GLM-4-Flash-250414
spring.ai.zhipuai.chat.options.temperature=0.7
# 指定mcp-servers的URL
spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.sse.connections.server1.url=http://localhost:8081
spring.ai.graph.nodes.node2servers.mcp-node=server1
4)新建McpNodeProperties读取配置spring.ai.graph.nodes开头
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Map;
import java.util.Set;
/**
* 为了方便解析配置多少个MCP服务
*/
@ConfigurationProperties(prefix = McpNodeProperties.PREFIX)
public class McpNodeProperties {
public static final String PREFIX = "spring.ai.graph.nodes";
private Map<String, Set<String>> node2servers;
public Map<String, Set<String>> getNode2servers() {
return node2servers;
}
public void setNode2servers(Map<String, Set<String>> node2servers) {
this.node2servers = node2servers;
}
}
1.2 构建图和节点
1)新建McpClientToolCallbackProvider类,用于读取toolcall
import com.demo.lesson26.mcp.config.McpNodeProperties;
import org.apache.commons.compress.utils.Lists;
import org.glassfish.jersey.internal.guava.Sets;
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpClientCommonProperties;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
@Service
public class McpClientToolCallbackProvider {
private final ToolCallbackProvider toolCallbackProvider;
private final McpClientCommonProperties commonProperties;
private final McpNodeProperties mcpNodeProperties;
public McpClientToolCallbackProvider(ToolCallbackProvider toolCallbackProvider,
McpClientCommonProperties commonProperties, McpNodeProperties mcpNodeProperties) {
this.toolCallbackProvider = toolCallbackProvider;
this.commonProperties = commonProperties;
this.mcpNodeProperties = mcpNodeProperties;
}
/**
* 通过配置的spring.ai.graph.nodes的名称,获取从Spring AI中获取到的toolCall
*/
public Set<ToolCallback> findToolCallbacks(String nodeName) {
// 获取配置文件中spring.ai.graph.nodes开头的数据
Set<ToolCallback> defineCallback = Sets.newHashSet();
Set<String> mcpClients = mcpNodeProperties.getNode2servers().get(nodeName);
if (mcpClients == null || mcpClients.isEmpty()) {
return defineCallback;
}
List<String> exceptMcpClientNames = Lists.newArrayList();
for (String mcpClient : mcpClients) {
// my-mcp-client
String name = commonProperties.getName();
// mymcpclientserver1
String prefixedMcpClientName = McpToolUtils.prefixedToolName(name, mcpClient);
exceptMcpClientNames.add(prefixedMcpClientName);
}
// 从Spring AI的MCP客户端获取到的toolCall,放到defineCallback,以方便注册到MCPNode中的chatClient
ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();
for (ToolCallback toolCallback : toolCallbacks) {
ToolDefinition toolDefinition = toolCallback.getToolDefinition();
String name = toolDefinition.name();
for (String exceptMcpClientName : exceptMcpClientNames) {
if (name.startsWith(exceptMcpClientName)) {
defineCallback.add(toolCallback);
}
}
}
return defineCallback;
}
}
2)构建McpNode节点
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import com.demo.lesson26.mcp.tool.McpClientToolCallbackProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallback;
import reactor.core.publisher.Flux;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 自定义MCP节点
*/
public class McpNode implements NodeAction {
private static final Logger logger = LoggerFactory.getLogger(McpNode.class);
private static final String NODENAME = "mcp-node";
private final ChatClient chatClient;
public McpNode(ChatClient.Builder chatClientBuilder, McpClientToolCallbackProvider mcpClientToolCallbackProvider) {
// 获取mcp-node前缀定义的工具,注册到chatClient中
Set<ToolCallback> toolCallbacks = mcpClientToolCallbackProvider.findToolCallbacks(NODENAME);
for (ToolCallback toolCallback : toolCallbacks) {
logger.info("Mcp Node load ToolCallback: " + toolCallback.getToolDefinition().name());
}
this.chatClient = chatClientBuilder
.defaultToolCallbacks(toolCallbacks.toArray(ToolCallback[]::new))
.build();
}
@Override
public Map<String, Object> apply(OverAllState state) {
String query = state.value("query", "");
Flux<String> streamResult = chatClient.prompt(query).stream().content();
String result = streamResult.reduce("", (acc, item) -> acc + item).block();
HashMap<String, Object> resultMap = new HashMap<>();
resultMap.put("mcpcontent", result);
return resultMap;
}
}
3)设置配置类McpGaphConfiguration构建图:
import com.alibaba.cloud.ai.graph.GraphRepresentation;
import com.alibaba.cloud.ai.graph.KeyStrategy;
import com.alibaba.cloud.ai.graph.KeyStrategyFactory;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.action.AsyncNodeAction;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;
import com.demo.lesson26.mcp.node.McpNode;
import com.demo.lesson26.mcp.tool.McpClientToolCallbackProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
@Configuration
@EnableConfigurationProperties({ McpNodeProperties.class })
public class McpGaphConfiguration {
private static final Logger logger = LoggerFactory.getLogger(McpGaphConfiguration.class);
@Autowired
private McpClientToolCallbackProvider mcpClientToolCallbackProvider;
@Bean
public StateGraph mcpGraph(ChatClient.Builder chatClientBuilder) throws GraphStateException {
KeyStrategyFactory keyStrategyFactory = () -> {
HashMap<String, KeyStrategy> keyStrategyHashMap = new HashMap<>();
// 用户输入
keyStrategyHashMap.put("query", new ReplaceStrategy());
keyStrategyHashMap.put("mcpcontent", new ReplaceStrategy());
return keyStrategyHashMap;
};
// 构建图
StateGraph stateGraph = new StateGraph(keyStrategyFactory)
.addNode("mcp", AsyncNodeAction.node_async(new McpNode(chatClientBuilder, mcpClientToolCallbackProvider)))
.addEdge(StateGraph.START, "mcp")
.addEdge("mcp", StateGraph.END);
// 添加 PlantUML 打印
GraphRepresentation representation = stateGraph.getGraph(GraphRepresentation.Type.PLANTUML,
"mcp flow");
logger.info("\n=== mcp UML Flow ===");
logger.info(representation.content());
logger.info("==================================\n");
return stateGraph;
}
}
4)新建启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Lesson26MCPApplication {
public static void main(String[] args) {
SpringApplication.run(Lesson26MCPApplication.class, args);
}
}
1.3 演示效果
http://localhost:8080/graph/mcp
结语:本章演示了Graph中如何访问MCP服务,可见其架构的可扩展性,在Spring AI Alibaba中有一个com.alibaba.cloud.ai.graph.node.McpNode的MCP访问节点实现,但是该节点只是一个固定MCP访问,即需要传入方法和参数,并没有配置大模型。如果你构建的Graph中只是简单调用MCP服务,则可以直接使用com.alibaba.cloud.ai.graph.node.McpNode节点。前面通过几章对Graph框架进行了比较详细的讲解,这是因为在实际应用中,一个应用一般都是一个流程,而非一撮而就,所以使用Graph场景非常多。下一章将讲Spring AI Alibaba的nl2sql,这个是一个基于Graph构建的生成SQL的实际案例,你就可以见识到复杂的工作流。
Spring AI系列上一章:《Spring AI 系列之三十四 - Spring AI Alibaba-Graph框架之并行执行》
Spring AI系列下一章:《Spring AI 系列之三十六 - Spring AI Alibaba-nl2sql》