MCP简要说明
MCP(Model Context Protocol) 是一种由 Anthropic 于 2024 年 11 月发布的开源协议,旨在标准化大型语言模型(LLM)与外部数据源和工具的交互。它像"AI的USB-C接口",通过统一接口让 AI 模型无缝连接数据库、文件、API 等外部资源。
作用:
- 简化集成:减少为每个工具或数据源编写自定义接口的工作,降低开发复杂性。
- 提供上下文:让 AI 访问实时、特定领域的外部数据,提升回答的准确性和相关性。
- 增强功能:支持 AI 执行操作(如发送邮件、查询数据库),而不仅是生成文本。
- 安全与灵活性:通过标准化协议确保安全访问,并允许动态发现和切换工具。
MCP 通过客户端-服务器架构(MCP Client 和 Server)实现,广泛应用于 AI 助手、开发工具等场景,助力构建更智能、互联的 AI 应用。
开发环境
JDK17、maven 3.8.6、springBoot 3.4.4、springAi 1.0.0-M6
效果展示
调用sse配置http服务


调用stdio配置mcp服务
快速接入百度地图MCP Server:lbs.baidu.com/faq/api?tit...

mcp-server
java代码:
McpServerApplication.java
typescript
package com.ahucoding.rocket.mcpserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
}
McpServerConfig.kava
typescript
package com.ahucoding.rocket.mcpserver.cfg;
import com.ahucoding.rocket.mcpserver.service.BookService;
import com.ahucoding.rocket.mcpserver.service.WeatherService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays;
import java.util.Map;
@Configuration
@EnableWebMvc
public class McpServerConfig implements WebMvcConfigurer {
// 允许服务器公开可由语言模型调用的工具
@Bean
public ToolCallbackProvider openLibraryToolsOne(BookService bookService) {
return MethodToolCallbackProvider.builder().toolObjects(bookService).build();
}
// 允许服务器公开可由语言模型调用的工具
@Bean
public ToolCallbackProvider openLibraryToolsTwo(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
// 允许服务器公开可由语言模型调用的工具
@Bean
public ToolCallbackProvider openLibraryToolsThree() {
return ToolCallbackProvider.from(Arrays.asList(toolCallbackOne, toolCallbackTwo));
}
private ToolCallback toolCallbackOne = new ToolCallback() {
@Override
public ToolDefinition getToolDefinition() {
return ToolDefinition.builder()
.name("Weather1")
.description("大龙电台,根据城市名称获取天气预报,格式:西安、北京、上海等")
.inputSchema("""
{
"type": "object",
"properties": {
"cityName": {
"type": "string",
"description": "城市名称,例如:西安、北京、上海等"
}
},
"required": ["cityName"]
}
""")
.build();
}
public record TianQi(String cityName) {
}
@Override
public String call(String toolInput) {
ObjectMapper objectMapper = new ObjectMapper();
TianQi tq= null;
try {
tq = objectMapper.readValue(toolInput,TianQi.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
Map<String, String> mockData = Map.of(
"西安", "晴天",
"北京", "小雨",
"上海", "大雨",
"河北", "阴天",
"邢台", "大暴雨",
"邯郸", "暴雪"
);
System.out.println("ToolCallback 大龙电台,天气服务,查询城市:"+tq.cityName);
return mockData.getOrDefault(tq.cityName, "抱歉:未查询到对应城市!");
}
};
private ToolCallback toolCallbackTwo = new ToolCallback() {
@Override
public ToolDefinition getToolDefinition() {
return ToolDefinition.builder()
.name("Weather2")
.description("巨龙电台,根据城市名称获取天气预报,格式:西安、北京、上海等")
.inputSchema("""
{
"type": "object",
"properties": {
"cityName": {
"type": "string",
"description": "城市名称,例如:西安、北京、上海等"
}
},
"required": ["cityName"]
}
""")
.build();
}
public record TianQi(String cityName) {
}
@Override
public String call(String toolInput) {
ObjectMapper objectMapper = new ObjectMapper();
TianQi tq= null;
try {
tq = objectMapper.readValue(toolInput,TianQi.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
Map<String, String> mockData = Map.of(
"西安", "晴天",
"北京", "小雨",
"上海", "大雨",
"河北", "阴天",
"邢台", "小雨",
"邯郸", "暴雪"
);
System.out.println("ToolCallback 巨龙电台,天气服务,查询城市:"+tq.cityName);
return mockData.getOrDefault(tq.cityName, "抱歉:未查询到对应城市!");
}
};
}
BookService.java
typescript
package com.ahucoding.rocket.mcpserver.service;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookService {
public record Book(List<String> isbn, String title, List<String> authorName) {
}
@Tool(description = "通过书名获取数据列表")
public List<Book> getBooks(String title) {
System.out.printf("调用了 根据书名获取数的列表 > title=%s\n", title);
// 这里模拟查询DB操作
return List.of(new Book(List.of("ISBN-88888888888"), "SpringAI教程", List.of("红专写的书")));
}
@Tool(description = "通过作者名获取数据列表")
public List<String> getBookTitlesByAuthor(String authorName) {
System.out.printf("调用了 根据书作者获取数的列表 > title=%s\n", authorName);
// 这里模拟查询DB操作
return List.of(authorName+"SpringAI教程");
}
}
WeatherService.java
typescript
package com.ahucoding.rocket.mcpserver.service;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class WeatherService {
/**
* SpringAI @Tool注解,方便创建和维护MCP工具
* name : 定义了工具的名称
* description : 指明工具的功能
*/
@Tool(name = "Weather0", description = "小龙电台,根据城市名称获取天气预报,格式:西安、北京、上海等")
public String getWeatherByCity(String city) {
System.out.println("MethodToolCallbackProvider 小龙电台,天气服务,查询城市:"+city);
Map<String, String> mockData = Map.of(
"西安", "晴天",
"北京", "小雨",
"上海", "大雨",
"河北", "阴天",
"邢台", "多云",
"邯郸", "暴雪"
);
return mockData.getOrDefault(city, "抱歉:未查询到对应城市!");
}
}
POM.XML
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<artifactId>mcp-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mcp-server</name>
<description>mcp-server</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webmvc</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--
SSE 连接将在30秒后终止bug: https://github.com/modelcontextprotocol/java-sdk/pull/21
-->
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webmvc</artifactId>
<version>0.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yaml
yaml
spring:
application:
name: mcp-server
ai:
mcp:
server:
name: webmvc-mcp-server
version: 1.0.0
type: SYNC
sse-message-endpoint: /mcp/messages
call-mcp-server
java代码
CallMcpServerApplication.java
typescript
package com.ahucoding.rocket.callmcpserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CallMcpServerApplication {
public static void main(String[] args) {
SpringApplication.run(CallMcpServerApplication.class, args);
}
}
McpClientCfg.java
java
package com.ahucoding.rocket.callmcpserver.cfg;
import io.modelcontextprotocol.client.McpClient;
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Configuration
public class McpClientCfg implements McpSyncClientCustomizer {
@Override
public void customize(String name, McpClient.SyncSpec spec) {
// do nothing
spec.requestTimeout(Duration.ofSeconds(30));
}
}
ChatController.java
kotlin
package com.ahucoding.rocket.callmcpserver.view;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import io.modelcontextprotocol.client.McpSyncClient;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* @author jianzhang
* 2025/03/18/下午8:00
*/
@RestController
@RequestMapping("/dashscope/chat-client")
@CrossOrigin("*")
public class ChatController {
private final ChatClient chatClient;
private final ChatMemory chatMemory = new InMemoryChatMemory();
public ChatController(ChatClient.Builder chatClientBuilder, List<McpSyncClient> mcpSyncClients, ToolCallbackProvider tools) {
this.chatClient = chatClientBuilder
// 它定义了聊天机器人在回答问题时应当遵循的风格和角色定位。
// .defaultSystem("以专业天气预报主持人的风格回答问题。")
// 指定聊天客户端可用的工具
.defaultTools(tools)
.defaultOptions(DashScopeChatOptions.builder().withTopP(0.7).build())
.build();
}
@RequestMapping(value = "/generate_stream", method = RequestMethod.GET)
public Flux<ChatResponse> generateStream(HttpServletResponse response, @RequestParam("id") String id, @RequestParam("prompt") String prompt) {
response.setCharacterEncoding("UTF-8");
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
return this.chatClient.prompt()
.user(prompt)
.advisors(messageChatMemoryAdvisor)
.stream()
.chatResponse()
.onErrorResume(e -> {
System.out.println("Error: " + e.getMessage());
return Mono.empty();
});
}
@GetMapping("/test")
public String test() {
return "hello world";
}
}
IndexController.java
kotlin
package com.ahucoding.rocket.callmcpserver.view;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String chat(Model model) {
return "index";
}
}
POM.XML
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ahucoding.rocket</groupId>
<artifactId>call-mcp-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>call-mcp-server</name>
<description>call-mcp-server</description>
<properties>
<java.version>17</java.version>
<spring.ai.alibaba>1.0.0-M6.1</spring.ai.alibaba>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>${spring.ai.alibaba}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yaml
yaml
server:
port: 9999
# https://docs.spring.io/spring-ai/reference/1.0/api/mcp/mcp-client-boot-starter-docs.html
spring:
ai:
mcp:
client:
enabled: true # 是否启用MCP客户端,默认:true
request-timeout: 60s # MCP客户端请求超时时间,默认:20s
type: SYNC # MCP客户端类型,默认:SYNC(同步)
name: call-mcp-server # MCP客户端名称,默认:spring-ai-mcp-client
stdio:
servers-configuration: classpath:mcp-server.json
# SSE配置
sse:
connections:
server1:
url: http://127.0.0.1:8080
# 通义大模型配置
dashscope:
api-key: sk-XXXXXXXXXX
chat:
options:
model: qwen-max
workspace-id: "llm-XXXXXXXXXX"
mcp-server.json
json
{
"mcpServers": {
"baidu-map": {
"command": "uv",
"args": [
"run",
"--with",
"mcp[cli]",
"mcp",
"run",
"D:\anzhuang\baidu_map_mcp_server\map.py"
],
"env": {
"BAIDU_MAPS_API_KEY": "RZ3K***********xA9amF"
}
}
}
}
AI交互页面
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 对话助手</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto p-4 max-w-3xl">
<!-- 标题 -->
<div class="text-center mb-8">
<h1 class="text-3xl font-bold text-gray-800">AI 对话助手</h1>
<p class="text-gray-600 mt-2">基于 Spring AI 的流式对话系统 By AhuCodingBeast</p>
</div>
<!-- 聊天容器 -->
<div id="chat-container" class="bg-white rounded-xl shadow-lg p-4 mb-4 h-[500px] overflow-y-auto space-y-4">
<!-- 初始欢迎消息 -->
<div class="ai-message flex items-start gap-3">
<div class="bg-green-100 p-3 rounded-lg max-w-[85%]">
<span class="text-gray-800">您好!我是AI助手,有什么可以帮您?</span>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="flex gap-2">
<input type="text" id="message-input"
class="flex-1 border border-gray-300 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="输入您的问题...">
<button id="send-button"
class="bg-blue-500 text-white px-6 py-3 rounded-xl hover:bg-blue-600 transition-colors flex items-center">
<span>发送</span>
<svg id="loading-spinner" class="hidden w-4 h-4 ml-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
</button>
</div>
</div>
<script>
const chatContainer = document.getElementById('chat-container');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
const loadingSpinner = document.getElementById('loading-spinner');
// 发送消息处理
function handleSend() {
const message = messageInput.value.trim();
if (!message) return;
// 添加用户消息
addMessage(message, 'user');
messageInput.value = '';
// 构建API URL
const apiUrl = new URL('http://localhost:9999/dashscope/chat-client/generate_stream');
apiUrl.searchParams.append('id', '01');
apiUrl.searchParams.append('prompt', message);
// 显示加载状态
sendButton.disabled = true;
loadingSpinner.classList.remove('hidden');
// 创建EventSource连接
const eventSource = new EventSource(apiUrl);
let aiMessageElement = null;
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log(data);
const content = data.result?.output?.text || '';
const finishReason = data.result?.metadata?.finishReason;
// 创建消息容器(如果不存在)
if (!aiMessageElement) {
aiMessageElement = addMessage('', 'ai');
}
// 追加内容
if (content) {
aiMessageElement.querySelector('.message-content').textContent += content;
autoScroll();
}
// 处理结束
if (finishReason === 'STOP') {
eventSource.close();
sendButton.disabled = false;
loadingSpinner.classList.add('hidden');
}
} catch (error) {
console.error('解析错误:', error);
}
};
eventSource.onerror = (error) => {
console.error('连接错误:', error);
eventSource.close();
sendButton.disabled = false;
loadingSpinner.classList.add('hidden');
addMessage('对话连接异常,请重试', 'ai', true);
};
}
// 添加消息到容器
function addMessage(content, type, isError = false) {
const messageDiv = document.createElement('div');
messageDiv.className = `${type}-message flex items-start gap-3`;
const bubble = document.createElement('div');
bubble.className = `p-3 rounded-lg max-w-[85%] ${
type === 'user'
? 'bg-blue-500 text-white ml-auto'
: `bg-green-100 ${isError ? 'text-red-500' : 'text-gray-800'}`
}`;
const contentSpan = document.createElement('span');
contentSpan.className = 'message-content';
contentSpan.textContent = content;
bubble.appendChild(contentSpan);
messageDiv.appendChild(bubble);
chatContainer.appendChild(messageDiv);
autoScroll();
return bubble;
}
// 自动滚动到底部
function autoScroll() {
chatContainer.scrollTop = chatContainer.scrollHeight;
}
// 事件监听
sendButton.addEventListener('click', handleSend);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
});
</script>
</body>
</html>
至此结束!!!