使用 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

相关推荐
谷哥的小弟42 分钟前
Brave Search MCP服务器安装以及客户端连接配置
搜索引擎·大模型·spring ai·mcp·brave search
太空眼睛1 小时前
【MCP】使用SpringBoot基于Streamable-HTTP构建MCP-Client
spring boot·ai·llm·sse·mcp·mcp-client·streamable
kaizq11 小时前
AI-MCP-SQLite-SSE本地服务及CherryStudio便捷应用
python·sqlite·llm·sse·mcp·cherry studio·fastmcp
太空眼睛14 小时前
【MCP】使用SpringBoot基于Streamable-HTTP构建MCP-Server
spring boot·sse·curl·mcp·mcp-server·spring-ai·streamable
康de哥1 天前
MCP Unity + Claude Code 配置关键步骤
unity·mcp·claude code
田井中律.1 天前
MCP协议
mcp
通义灵码1 天前
Qoder 支持通过 DeepLink 添加 MCP Server
人工智能·github·mcp
酩酊仙人3 天前
fastmcp构建mcp server和client
python·ai·mcp
kwg1263 天前
本地搭建 OPC UA MCP 服务
python·agent·mcp
小小工匠3 天前
LLM - 从通用对话到自治智能体:Agent / Skills / MCP / RAG 三层架构实战
agent·rag·skill·mcp