Spring AI MCP(Model Context Protocol) 实践

依赖导入

以 Gradle 为例

添加快照仓库地址

txt 复制代码
repositories {
    maven { url 'https://repo.spring.io/milestone' }
    maven { url 'https://repo.spring.io/snapshot' }
}

添加依赖

txt 复制代码
dependencies {
    implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-SNAPSHOT'
    implementation 'org.springframework.ai:spring-ai-starter-mcp-client:1.0.0-SNAPSHOT'
    implementation 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc:1.0.0-SNAPSHOT'
}

构建 Chat Client

创建 Chat Client 并添加工具,使大模型可以调用提供的工具。包括官方已实现的基本工具和自定义实现。

添加官方已实现的基本工具(实现访问本地文件和数据库)

  1. 向 application.properties 文件中添加以下配置项
properties 复制代码
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json
  1. 向 src/main/resources 目录下添加 mcp-servers-config.json 文件
    配置中的 npx 使用的为绝对路径
json 复制代码
{
	"mcpServers": {
		"filesystem": {
			"command": "D:\\nodejs\\22.12.0\\npx.cmd",
			"args": [
				"-y",
				"@modelcontextprotocol/server-filesystem",
				"D:\\tmp"
			]
		},
		"postgres": {
			"command": "D:\\nodejs\\22.12.0\\npx.cmd",
			"args": [
				"-y",
				"@modelcontextprotocol/server-postgres",
				"postgresql://postgres:password@localhost:5432/test"
			]
		}
	}
}

添加自定义实现

  1. 获取天气(美国地区)信息的工具(Spring AI example 实现)
java 复制代码
package com.example.mcp.server.service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@Service
public class WeatherService {

	private static final String BASE_URL = "https://api.weather.gov";

	private final RestClient restClient;

	public WeatherService() {
		this.restClient = RestClient.builder()
				.baseUrl(BASE_URL)
				.defaultHeader("Accept", "application/geo+json")
				.defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
				.build();
	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public record Points(@JsonProperty("properties") Props properties) {

		@JsonIgnoreProperties(ignoreUnknown = true)
		public record Props(@JsonProperty("forecast") String forecast) {
		}
	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public record Forecast(@JsonProperty("properties") Props properties) {

		@JsonIgnoreProperties(ignoreUnknown = true)
		public record Props(@JsonProperty("periods") List<Period> periods) {
		}

		@JsonIgnoreProperties(ignoreUnknown = true)
		public record Period(@JsonProperty("number") Integer number, @JsonProperty("name") String name,
				@JsonProperty("startTime") String startTime, @JsonProperty("endTime") String endTime,
				@JsonProperty("isDaytime") Boolean isDayTime, @JsonProperty("temperature") Integer temperature,
				@JsonProperty("temperatureUnit") String temperatureUnit,
				@JsonProperty("temperatureTrend") String temperatureTrend,
				@JsonProperty("probabilityOfPrecipitation") Map probabilityOfPrecipitation,
				@JsonProperty("windSpeed") String windSpeed, @JsonProperty("windDirection") String windDirection,
				@JsonProperty("icon") String icon, @JsonProperty("shortForecast") String shortForecast,
				@JsonProperty("detailedForecast") String detailedForecast) {
		}
	}

	@Tool(description = "根据经度和纬度获取天气预报。")
	public String getWeatherForecastByLocation(@ToolParam(description = "纬度") double latitude, @ToolParam(description = "经度") double longitude) {

		var points = restClient.get()
			.uri("/points/{latitude},{longitude}", latitude, longitude)
			.retrieve()
			.body(Points.class);

		var forecast = restClient.get().uri(points.properties().forecast()).retrieve().body(Forecast.class);

		String forecastText = forecast.properties().periods().stream().map(p -> {
			return String.format("""
					%s:
					温度: %s %s
					风向: %s %s
					天气预报: %s
					""", p.name(), p.temperature(), p.temperatureUnit(), p.windSpeed(), p.windDirection(),
					p.detailedForecast());
		}).collect(Collectors.joining());

		return forecastText;
	}
}
  1. 获取本地系统信息工具
java 复制代码
package com.example.mcp.server.service;

import java.util.HashMap;
import java.util.Map;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;

@Service
public class SystemService {


	@Tool(description = "获取本地系统信息,如操作系统名称、版本、Java版本等。")
    public Map<String, String> getSystemInfo() {
        Map<String, String> info = new HashMap<>();
        info.put("os.name", System.getProperty("os.name"));
        info.put("os.version", System.getProperty("os.version"));
        info.put("os.arch", System.getProperty("os.arch"));
        info.put("java.version", System.getProperty("java.version"));
        info.put("user.name", System.getProperty("user.name"));
        return info;
    }
}
  1. 组装为 ToolCallbackProvider
java 复制代码
package com.example.mcp.server.config;

import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.example.mcp.server.service.SystemService;
import com.example.mcp.server.service.WeatherService;

@Configuration
public class McpServerConfig {

	@Bean
	public ToolCallbackProvider customTools(WeatherService weatherService, SystemService systemService) {
		return MethodToolCallbackProvider.builder().toolObjects(weatherService, systemService).build();
	}
}

配置 Chat Client

  1. 向 application.properties 文件中添加模型配置(这里使用Sealos AI Proxy
properties 复制代码
spring.ai.openai.api-key=your api key
spring.ai.openai.base-url=https://aiproxy.gzg.sealos.run
spring.ai.openai.chat.options.model=qwen-coder-plus
  1. 添加配置类
java 复制代码
package com.example.mcp.client.config;

import java.util.List;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.modelcontextprotocol.client.McpSyncClient;
import jakarta.annotation.Resource;

@Configuration
public class ChatClientConfig {

	@Resource(name = "customTools")
	private ToolCallbackProvider customToolCallbackProvider;

	@Bean
	public ChatClient chatClient(ChatClient.Builder builder, List<McpSyncClient> mcpSyncClients) {
		return builder
				.defaultAdvisors(new SimpleLoggerAdvisor(), new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
				.defaultTools(new SyncMcpToolCallbackProvider(mcpSyncClients))
				.defaultTools(customToolCallbackProvider)
				.build();
	}
}

构建 Chat Rest API

DTO

  1. ChatRequest
java 复制代码
package com.example.mcp.client.model;

import lombok.Data;

@Data
public class ChatRequest {

	private String message;
}
  1. ChatResponse
java 复制代码
package com.example.mcp.client.model;

import lombok.Data;

@Data
public class ChatResponse {

	private String content;
}

Controller

  1. ChatController
java 复制代码
package com.example.mcp.client.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.mcp.client.model.ChatRequest;
import com.example.mcp.client.model.ChatResponse;

import jakarta.annotation.Resource;

@RestController
@RequestMapping("/api/chat")
public class ChatController {

	@Resource
	private ChatClient chatClient;

	@PostMapping
	public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
		ChatResponse response = new ChatResponse();
		response.setContent(chatClient.prompt().user(request.getMessage()).call().content());
		return ResponseEntity.ok(response);
	}
}

对话验证

  1. 有哪些工具可以使用
  2. 洛杉矶今天天气如何
  3. 数据中一共有多少张表
  4. 请在目录D:\tmp下创建空白文件: hello MCP.txt,并写入内容:Hi~

参考

  1. docs.spring.io/spring-ai/r...
  2. github.com/spring-proj...
  3. mcpcn.com/docs/
相关推荐
大模型真好玩6 小时前
LangChain1.0速通指南(一)——LangChain1.0核心升级
人工智能·agent·mcp
“负拾捌”19 小时前
基于NodeJs实现一个MCP客户端(会话模式和无会话模式)
javascript·ai·node.js·大模型·mcp
华为云开发者联盟2 天前
【新特性】 版本速递 | 华为云Versatile智能体平台 新增特性介绍(2025年10月发布)
人工智能·华为云开发者联盟·ai agent·mcp·华为云versatile
字节跳动安全中心2 天前
开源!可信MCP,AICC机密计算新升级!
安全·llm·mcp
大模型真好玩2 天前
低代码Agent开发框架使用指南(六)—Coze 变量与长期记忆
人工智能·coze·mcp
字节跳动开源2 天前
开源可信MCP,AICC机密计算新升级!
mcp
数据智能老司机2 天前
使用 Python 入门 Model Context Protocol(MCP)——深入解析模型上下文协议(MCP)
llm·agent·mcp
YUELEI1183 天前
Mcp 基础
mcp
大模型真好玩4 天前
LangGraph实战项目:从零手搓DeepResearch(四)——OpenDeepResearch源码解析与本地部署
人工智能·agent·mcp