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

相关推荐
AngelPP4 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年4 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼4 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS5 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
warm3snow5 小时前
Claude Code 黑客马拉松:5 个获奖项目,没有一个是"纯码农"做的
ai·大模型·llm·agent·skill·mcp
程序员清风6 小时前
程序员兼职必看:靠谱软件外包平台挑选指南与避坑清单!
java·后端·面试
天翼云开发者社区6 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈6 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang6 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx
皮皮林5517 小时前
利用闲置 Mac 从零部署 OpenClaw 教程 !
java