McpClientToolCallbackProvider
根据节点名称,匹配对应的 MCP 提供的 ToolCallback
package com.spring.ai.tutorial.graph.mcp.tool;
import com.spring.ai.tutorial.graph.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;
}
public Set<ToolCallback> findToolCallbacks(String nodeName) {
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);
}
ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();
for (ToolCallback toolCallback : toolCallbacks) {
ToolDefinition toolDefinition = toolCallback.getToolDefinition();
// mymcpclientserver1getCityTimeMethod
String name = toolDefinition.name();
for (String exceptMcpClientName : exceptMcpClientNames) {
if (name.startsWith(exceptMcpClientName)) {
defineCallback.add(toolCallback);
}
}
}
return defineCallback;
}
}
McpNode
通过 ToolCallbacks 为节点配置 MCP 工具
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallback;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import reactor.core.publisher.Flux;
/**
* MCP 节点实现
*/
public static class McpNode implements NodeAction {
private static final String NODENAME = "mcp-node";
private final ChatClient chatClient;
public McpNode(ChatClient.Builder chatClientBuilder, Set<ToolCallback> toolCallbacks) {
// 为节点配置 MCP 工具
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;
}
}
配置 MCP 节点
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallback;
import java.util.Set;
/**
* 配置 MCP 节点
*/
public static void configureMcpNode(ChatClient.Builder chatClientBuilder, Set<ToolCallback> toolCallbacks) {
McpNode mcpNode = new McpNode(chatClientBuilder, toolCallbacks);
System.out.println("MCP node configured successfully");
}
McpController
package com.spring.ai.tutorial.graph.mcp.controller;
import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/graph/mcp")
public class McpController {
private static final Logger logger = LoggerFactory.getLogger(McpController.class);
private final CompiledGraph compiledGraph;
public McpController(@Qualifier("mcpGraph") StateGraph stateGraph) throws GraphStateException {
this.compiledGraph = stateGraph.compile();
}
@GetMapping("/call")
public Map<String, Object> call(@RequestParam(value = "query", defaultValue = "北京时间现在几点钟", required = false) String query,
@RequestParam(value = "threadid", defaultValue = "yingzi", required = false) String threadId){
RunnableConfig runnableConfig = RunnableConfig.builder().threadId(threadId).build();
Map<String, Object> objectMap = new HashMap<>();
objectMap.put("query", query);
Optional<OverAllState> invoke = this.compiledGraph.invoke(objectMap, runnableConfig);
return invoke.map(OverAllState::data).orElse(new HashMap<>());
}
}
提供一个 MCP Server 服务,远端 or 本地都可以
这里本地启动一个MCP Server,提供一个时间服务
启动本地 MCP Server,提供时间服务

调用接口,触发本地端 MCP Server 提供的时间服务

本文整理自 Spring AI Alibaba 官方文档《分配 MCP 工具给指定节点》:https://java2ai.com/docs/frameworks/graph-core/examples/mcp-node

AI项目博客






