03.使用spring-ai玩转MCP

接着上篇: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

  1. 引入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>
  1. 使用@Tool声明方法,使用@ToolParam声明参数
java 复制代码
@Service
public class EchoService {
    /**
     * echo
     *
     * @param msg
     * @return String
     */
    @Tool(description = "echo")
    public String echo(@ToolParam(description = "消息") String msg) {
        return msg;
    }
}
  1. 将EchoService注入到ToolCallbackProvider
java 复制代码
@Configuration
public class McpServerConfig {
    @Bean
    public ToolCallbackProvider allTools(EchoService echoService) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(echoService)
                .build();
    }
}
  1. 单元测试
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();
    }
}
  1. 扩展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

  1. 引入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>
  1. 在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"
      ]
    }
  }
}
  1. 在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
  1. 配置模型集成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();
    }
}
  1. 对外提供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

相关推荐
lilye662 分钟前
精益数据分析(19/126):走出数据误区,拥抱创业愿景
前端·人工智能·数据分析
极客智谷11 分钟前
深入理解Java线程池:从原理到实战的完整指南
java·后端
信息快讯13 分钟前
【机器学习驱动的智能化电池管理技术与应用】
人工智能·机器学习
程序员小陈在成都18 分钟前
Spring Ioc源码引入:什么是IoC,IoC解决了什么问题
spring
代码不行的搬运工19 分钟前
HTML快速入门-4:HTML <meta> 标签属性详解
java·前端·html
孟健19 分钟前
MCP 哪家强?深度分析 Cline、Cursor、Trae、Coze 四大平台
mcp
进来有惊喜25 分钟前
循环神经网络RNN---LSTM
人工智能·rnn·深度学习
Chrome深度玩家26 分钟前
如何下载Google Chrome适用于AI语音交互的特制版
前端·人工智能·chrome
Xiaoxiaoxiao020926 分钟前
GAEA情感坐标背后的技术原理
人工智能·web3·区块链