使用 Spring AI 创建 MCP 服务器

本文提供了使用 Spring AI 创建模型上下文协议服务器的分步指南,并阐述了使用 MCP 的优势。

在本篇博客中,您将学习如何使用 Spring AI 创建一个模型上下文协议服务器。您将看到创建自己的 MCP 服务器是多么简单。

引言

模型上下文协议(Model Context Protocol)为将大型语言模型(LLM)连接到各种数据源和工具提供了一种标准化的方式。这句话中的"标准化"一词非常重要。这意味着与数据源和工具的集成变得比以往容易得多。除此之外,MCP 服务器通过额外的知识或功能来增强您的 LLM,使其成为一个更强大的助手。想象一下,您可以要求 LLM 为您预订假期。

根据您的偏好,它将在互联网上搜索合适的地点、预订酒店、预订航班等等,然后您就可以出发了!当然,LLM 需要能够为您预订酒店和航班。这种附加功能可以由 MCP 服务器提供。这听起来很吸引人,但也有些令人担忧。为了预订酒店,MCP 服务器需要知道您的个人详细信息并需要访问您的信用卡。这可能不是一个好主意。建议对敏感操作使用人工介入(HITL),以便您可以批准或拒绝某项操作。然而,MCP 服务器将使您的生活变得更加轻松。

在本博客中,您将学习如何使用 Spring Boot 和 Spring AI 创建自己的 MCP 服务器。没有 MCP 客户端,服务器将毫无用处。MCP 客户端与一个或多个 MCP 服务器交互,并且是控制方。作为 MCP 客户端,您将(滥)用 IntelliJ DevoxxGenie 插件。DevoxxGenie 实际上是一个 AI 编码助手,但您也可以使用它来测试您的 MCP 服务器。在下一篇博客中,您将创建自己的 MCP 客户端。

本篇博客中使用的源代码可在 GitHubserver 目录中找到。

先决条件

阅读本博客的先决条件是:

  • 基础的 Java 知识;

  • 基础的 Spring Boot 知识;

  • 基础的 LMStudio 知识;

  • 基础的 IntelliJ 和 DevoxxGenie 知识。

构建 MCP 服务器

您将构建的 MCP 服务器具有以下功能:

  • 返回我喜爱的艺术家列表

  • 返回我喜爱的歌曲列表

LLM 不会拥有关于此信息的任何知识,而当它能够访问这些工具时,希望它会利用它们。本应用深受 Dan Vega 的《使用 Java 创建您的第一个模型上下文协议服务器》的启发。

访问 Spring Initializr 并添加依赖项 Model Context Protocol Server。这将在 pom.xml 中添加以下依赖项。

xml 复制代码
<dependency>

<groupId>org.springframework.ai</groupId>

<artifactId>spring-ai-starter-mcp-server</artifactId>

</dependency>

创建 Artist 的数据模型。

java 复制代码
public record Artist(String name) {

}

创建 Song 的数据模型。

java 复制代码
public record Song(Artist artist, String title) {

}

创建 ArtistService。该服务将包含一个我喜爱的艺术家列表,但在实际应用中,这些信息将存储在数据库中。通过使用 @Tool 注解来定义一个名为 get_artists 的工具。为工具提供名称和描述。LLM 将使用该描述来了解工具的功能。

java 复制代码
@Service

public class ArtistService {

  


private final List<Artist> artists = new ArrayList<>();

  


@Tool(name = "get_artists", description = "获取 Gunter 喜爱的艺术家完整列表")

public List<Artist> getArtists() {

return artists;

}

  


@PostConstruct

public void init() {

artists.addAll(List.of(

new Artist("Bruce Springsteen"),

new Artist("JJ Johnson")

));

}

  


}

以类似的方式,创建 SongService

java 复制代码
@Service

public class SongService {

  


private final List<Song> songs = new ArrayList<>();

  


@Tool(name = "get_songs", description = "获取 Gunter 喜爱的歌曲完整列表")

public List<Song> getSongs() {

return songs;

}

  


@PostConstruct

public void init() {

songs.addAll((List.of(

new Song(new Artist("Bruce Springsteen"), "My Hometown"),

new Song(new Artist("JJ Johnson"), "Lament")

)));

}

  


}

您通过一个 Bean 来注册这些工具。

java 复制代码
@SpringBootApplication

public class MyMcpServerPlanetApplication {

  


public static void main(String[] args) {

SpringApplication.run(MyMcpServerPlanetApplication.class, args);

}

  


@Bean

public ToolCallbackProvider mcpServices(ArtistService artistService, SongService songService) {

return MethodToolCallbackProvider.builder()

.toolObjects(artistService, songService)

.build();

}

  


}

最后,添加以下 application.properties 配置。

properties 复制代码
spring.main.web-application-type=none

spring.ai.mcp.server.name=mcp-server

spring.ai.mcp.server.version=0.0.1

  


spring.main.banner-mode=off

logging.pattern.console=

此配置做了几件重要的事情:

  • 禁用 Web 应用程序:由于 MCP 使用 STDIO 传输,因此不需要 Web 服务器。

  • 设置服务器名称和版本:这用于向客户端标识 MCP 服务器。

  • 禁用横幅和控制台日志记录:这对于 STDIO 传输正常工作至关重要。

构建 jar 文件。

shell 复制代码
mvn clean verify

这将在 target 目录中生成一个 jar 文件:target/mcp-server-0.0.1-SNAPSHOT.jar

测试 MCP 服务器

为了测试 MCP 服务器,需要一个 MCP 客户端。如前所述,将使用 DevoxxGenie 来实现此目的。

将 MCP 服务器添加到 DevoxxGenie 的 MCP 设置中。请注意,您需要对命令和参数使用完整路径。

  • 名称:MyMcpServerPlanet

  • 传输类型:STDIO

  • 命令/<java installation directory>/bin/java

  • 参数-jar /home/<project directory>/mymcpserverplanet/server/target/mymcpserverplanet-0.0.1-SNAPSHOT.jar

)

单击"测试连接并获取工具 "按钮。按钮的标题应变更为"连接成功!找到 2 个工具"。

与 DevoxxGenie 一起,使用 LMStudio 作为推理引擎,模型 qwen3-8b 在 GPU 上运行。

输入提示:给我一个 Gunter 喜爱的艺术家列表。

该请求被发送到 LMStudio,可用的工具也随请求一起发送。如果您在 LMStudio 中启用了调试日志,可以检查该请求。

json 复制代码
2025-07-20 11:45:42 [DEBUG]

Received request: POST to /v1/chat/completions with body {

"model": "qwen3-8b",

"messages": [

{

"role": "system",

"content": "You are a software developer IDEA plugin with expe... <日志中已截断> ...at is correct and relevant to the code or plugin.\n"

},

{

"role": "user",

"content": "<ProjectPath>\n/home/<project directory>... <日志中已截断> ... list of gunter's favorite artists\n</UserPrompt>\n\n"

}

],

"temperature": 0,

"top_p": 0.9,

"stream": false,

"max_tokens": 8000,

"tools": [

{

"type": "function",

"function": {

"name": "get_songs",

"description": "获取 Gunter 喜爱的歌曲完整列表",

"parameters": {

"type": "object",

"properties": {},

"required": []

}

}

},

{

"type": "function",

"function": {

"name": "get_artists",

"description": "获取 Gunter 喜爱的艺术家完整列表",

"parameters": {

"type": "object",

"properties": {},

"required": []

}

}

}

]

}

LLM 将意识到它自己无法回答这个问题。然而,LLM 确实识别出有一个可用的工具可以回答这个问题。因此,LLM 将请求客户端调用 MCP 工具。DevoxxGenie 内置了人工介入机制,并将请求您的批准。

点击"批准 "按钮。MCP 服务器将执行 get_artists 工具,并将响应发送回 LLM。LLM 将使用此响应来给出适当的答案。

收到以下响应,并且完全正确。观察 LLM 的思维过程也很有趣。

plain 复制代码
<think> 好的,用户要求提供 Gunter 喜爱的艺术家列表。我调用了 `get_artists` 函数,返回了两位艺术家:Bruce Springsteen 和 JJ Johnson。现在他们可能想知道是否还有更多或者需要进一步帮助。

  


我应该确认结果,也许可以询问他们是否还需要其他什么。由于之前的响应是成功的,保持积极的语气很好。工具调用没有错误,所以这里不需要额外的函数调用。 </think>

  


以下是 Gunter 喜爱的艺术家:

  


* **Bruce Springsteen**

* **JJ Johnson**

  


如果您需要更多信息或帮助,请告诉我!

让我们使用以下提示检查 get_songs 工具:给我一个 Gunter 喜爱的歌曲列表

遵循相同的过程,响应再次正确。

plain 复制代码
<think> 好的,用户要求提供 Gunter 喜爱的歌曲列表。我使用了 get_songs 函数,它检索到了两首歌曲。现在我需要清晰地呈现这些信息。让我用艺术家和歌名来格式化每首歌曲。确保响应易于阅读,也许用项目符号。检查是否需要任何额外信息,但既然用户只想要一个列表,保持简单应该是最好的。 </think>

  


以下是 Gunter 喜爱的歌曲列表:

  


1. **Bruce Springsteen** - *《My Hometown》*

2. **JJ Johnson** - *《Lament》*

  


如果您想了解这些曲目的更多详细信息,请告诉我!

添加搜索功能

让我们向两个 MCP 工具添加一些基本的搜索功能。

ArtistService 添加一个 search_artist 工具。

java 复制代码
@Tool(name = "search_artist", description = "从 Gunter 喜爱的艺术家中搜索单个艺术家")

public Artist getArtist(String name) {

return artists.stream()

.filter(artist -> artist.name().equalsIgnoreCase(name))

.findFirst()

.orElse(null);

}

SongService 添加一个 search_song 工具。

java 复制代码
@Tool(name = "search_song", description = "从 Gunter 喜爱的歌曲中搜索单首歌曲")

public Song getSong(String title) {

return songs.stream()

.filter(song -> song.title().equalsIgnoreCase(title))

.findFirst()

.orElse(null);

}

构建 jar 文件并使用 DevoxxGenie 获取新工具。

输入以下提示:Bruce Springsteen 是 Gunter 喜爱的艺术家之一吗?

这次调用了 search_artist 工具,响应再次正确。

plain 复制代码
<think> 好的,用户询问 Bruce Springsteen 是否是 Gunter 喜爱的艺术家之一。我使用 search_artist 函数并传入了他的名字。响应返回了艺术家 "Bruce Springsteen"。现在我需要检查他是否在 Gunter 的喜爱列表中。

  


等等,可用的函数是 get_artists 和 search_artist。search_artist 函数可能检查艺术家是否存在于数据库中。既然响应返回了该艺术家,那意味着 Bruce Springsteen 确实在 Gunter 喜爱的艺术家列表中。

  


所以答案应该是肯定的。我应该根据函数的响应来确认这一点。既然用户的问题已经得到回答,不需要进一步的操作。只需清晰地呈现结果即可。 </think>

  


是的,Bruce Springsteen 是 Gunter 喜爱的艺术家之一。搜索确认了他在收藏中。

输入以下提示:滚石乐队是 Gunter 喜爱的艺术家之一吗?

调用了 search_artist 工具,由于未找到滚石乐队,LLM 断定他们不在我喜爱的艺术家列表中。

plain 复制代码
<think> 好的,让我试着弄清楚这里发生了什么。用户询问滚石乐队是否是 Gunter 喜爱的艺术家之一。我使用 search_artist 函数,传入了名称 "Rolling Stones",但响应是 null。这意味着该函数在 Gunter 的喜爱列表中没有找到该名称的艺术家。

  


嗯,也许数据库中艺术家的名称不完全是 "Rolling Stones"。会不会有拼写错误或不同的拼写方式?或者也许他们在系统中以另一个名称而闻名。或者,也许是因为数据不存在,所以函数没有返回任何结果。

  


既然用户是在询问 Gunter 的喜爱列表,我应该检查是否有一种方法可以首先列出所有喜爱的艺术家。get_artists 函数可能会有所帮助。如果我调用它,也许我可以看到滚石乐队是否在列表中。但是等等,用户可能不想要所有艺术家,只是确认这一个特定的。

  


或者,也许 search_artist 函数需要不同的参数。让我再次检查工具定义。search_artist 函数需要一个 "name" 参数,这是必需的。那么查询应该是大小写不敏感的?或者可能是部分匹配?用户可能指的是 "Rolling Stones",但数据库中的存储方式不同。

  


另一个想法:也许滚石乐队不在 Gunter 喜爱的艺术家列表中。搜索返回了 null,所以可能他们不在。但我应该通过先检查所有艺术家来确认。我应该向用户建议这一点。等等,但用户是专门询问滚石乐队。也许我应该告知他们搜索没有找到任何结果,并询问他们是否想改为检查所有艺术家。

  


等等,响应是 null,所以函数没有返回任何数据。这意味着该艺术家不在列表中。所以答案应该是否定的,滚石乐队不是 Gunter 喜爱的艺术家之一。但是函数调用会不会有错误?让我再检查一遍。函数是 search_artist,名称是 "Rolling Stones"。如果数据库中有确切的名称,那么它应该返回。如果没有,则返回 null。

  


所以结论是滚石乐队不在 Gunter 的喜爱列表中。或者,可能存在拼写错误。但既然用户是直接询问,我应该根据函数的响应来回答。 </think>

  


在 Gunter 喜爱的艺术家列表中,对艺术家 "Rolling Stones" 的搜索未返回任何结果。这表明滚石乐队**目前并未**列为 Gunter 喜爱的艺术家之一。您想改为查看他喜爱的艺术家的完整列表吗?

结论

在本博客中,您学习了如何使用 Spring Boot 和 Spring AI 创建一个 MCP 服务器。创建 MCP 服务器和添加工具非常容易。您可以专注于工具的功能,而这正是您想要的。


【注】本文译自:Creating an MCP Server With Spring AI

相关推荐
CoderJia程序员甲2 小时前
GitHub 热榜项目 - 日榜(2025-11-23)
python·开源·github·mcp
小鱼儿亮亮7 小时前
MCP快速入门 及 MCP stdio 传输方式示例
ai编程·mcp
却尘1 天前
当你的 MCP 服务器上云后崩了:深度解析 Transport 限制与高级特性
aigc·ai编程·mcp
qqxhb3 天前
零基础MCP——第5章编程实战基础(网页、数据分析、调试与重构)
重构·数据挖掘·数据分析·多模态·mcp
魁首3 天前
初识 ACP (Agent Client Protocol)
人工智能·ai编程·mcp
阿虎儿4 天前
如何使用MCP Inspector调试MCP工具
mcp
Learn-Share_HY4 天前
[Python]如何用uv套件建置python專案與虛擬環境?
python·ai·virtualenv·uv·server·mcp·cline
FreeCode4 天前
基于MCP的代码执行:构建更高效率的智能体
后端·agent·mcp
数据智能老司机4 天前
多智能体系统的上下文工程——构建上下文感知的多智能体系统
llm·agent·mcp