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

相关推荐
IT_陈寒几秒前
Java线程池用错参数,我的服务居然悄悄崩溃了
前端·人工智能·后端
滕州市燕猫虎计算机科技工作室个体工商户2 分钟前
IDEA:Command line is too long
java·ide·intellij-idea
why-geo9 分钟前
Hermes Agent 与 Python 的会产生什么样的碰撞
人工智能·python·ai编程
正经教主16 分钟前
【FDE系列】阶段2:Day 29:FastAPI 进阶 — Pydantic 模型与完整 CRUD 实战
人工智能·python·fde
时速GEO系统32 分钟前
初元 AI V1.1 版本升级,首个正式版发布一周・实现生成、部署、上线、优化全流程自动化闭环
人工智能·数据挖掘·node.js
步行cgn33 分钟前
Spring p 命名空间注入详解
java·前端·spring
咕泡科技36 分钟前
咕泡科技FDE系列最新产品重磅发布!
人工智能·大模型·ai落地·fde·前沿部署工程师
YatHinLay36 分钟前
MyBatis 流式查询实战:ResultHandler 处理海量数据
java
犀利豆1 小时前
为什么全世界的 AI 都画不好一只骑自行车的鹈鹕
人工智能·llm·aigc
天天被压力1 小时前
【跨市场数据实战 #08】可转债折价机会怎么筛:3个接口抓比价、列表和实时盘口
java·人工智能·python