1. 概述
随着大语言模型(LLMs)的兴起,团队越来越多地将AI集成到应用中。AI集成已不再局限于简单的问答。**模型上下文协议(MCP)**正是为这类集成提供解决方案的概念。
本教程将介绍如何使用Quarkus和LangChain4j构建MCP服务器和客户端。我们将创建一个简单聊天机器人,并通过MCP服务器扩展LLM的能力,使其能执行自定义任务——例如根据时区获取当前日期和服务器JVM信息。
2. 模型上下文协议(MCP)基础
Anthropic于2024年11月开源了模型上下文协议 (MCP)。MCP提供了一种标准化方式,将AI应用与外部数据源连接。在深入代码前,我们先理解为什么需要MCP以及它是什么。
2.1. 为什么需要模型上下文协议?
随着AI应用普及,通过LLM暴露组织和应用特定"上下文"的需求日益增长。因此,将这类上下文集成到AI工作流中需要仔细考量。
上下文可来自多种数据源:数据库、文件系统、搜索引擎等。由于数据源及其连接方式的多样性,将这些上下文集成到AI中构成了重大挑战。
这种集成可以内嵌到AI应用中,但会增加应用复杂度。此外,许多组织已有大量内部服务提供各类信息。我们理想中需要的是一种能将现有服务与AI松耦合集成的方案。
2.2. 模型上下文协议简介
现在我们理解了需要某种协议来整合多源上下文,而MCP正是为此而生。通过MCP,我们可以在原生LLM之上构建复杂代理和工作流,无需紧密耦合。
这类似于用户界面通过REST API与后端通信。一旦API契约确立,UI和后端只要契约不变,就能独立修改。
值得注意的是,尽管LLMs训练数据庞大,但它们无法获取当前或专有信息。探索MCP的简单方式是尝试让LLMs查询这类信息的服务。
在深入代码前,快速了解MCP架构及其组件:
MCP采用客户端-服务器架构,包含几个关键组件:
- MCP宿主:集成LLM并需要连接外部数据源的主应用
- MCP客户端:与MCP服务器建立1:1连接的组件
- MCP服务器:集成外部数据源并暴露交互功能的组件
为处理客户端-服务器通信,MCP提供两种传输通道:
- **标准输入/输出(stdio)**:通过标准输入/输出流与本地进程和命令行工具通信
- **服务器发送事件(SSE)**:基于HTTP的客户端-服务器通信
MCP是个复杂庞大的主题,可参考官方文档深入学习。
3. 创建自定义MCP服务器
虽然有许多预构建的MCP服务器可用,但本文将介绍使用Quarkus创建自定义MCP服务器的方法。
我们将构建两个简单工具:一个根据时区获取当前日期,另一个提供服务器JVM信息。
3.1. 创建Quarkus MCP服务器项目
前置条件:安装JDK和Quarkus CLI。满足条件后创建新项目:
quarkus create app --no-code -x qute,quarkus-mcp-server-sse quarkus-mcp-server
这将在quarkus-mcp-server
目录下创建包含Quarkus项目基础脚手架的新项目。
3.2. 依赖项
使用quarkus
CLI创建的项目会自动添加核心依赖(quarkus-arc
和quarkus-junit5
),以及命令中指定的扩展相关依赖。本例中扩展为qute
和quarkus-mcp-server-sse
:
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkiverse.mcp</groupId>
<artifactId>quarkus-mcp-server-sse</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute</artifactId>
</dependency>
</dependencies>
现在可以创建供LLM使用的工具了。
3.3. 定义和暴露自定义工具
我们将添加两个工具。大多数LLMs没有工具就无法获取当前信息。因此,若询问LLMs当前日期时间,它们可能回答训练数据截止时的日期时间。
为了测试通过MCP服务器增强LLM能力,我们创建一个提供用户(或调用者)时区当前日期时间的工具:
@Tool(description = "获取指定时区的当前时间")
public String getTimeInTimezone(
@ToolArg(description = "时区ID(如:America/Los_Angeles)") String timezoneId) {
try {
ZoneId zoneId = ZoneId.of(timezoneId);
ZonedDateTime zonedDateTime = ZonedDateTime.now(zoneId);
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.FULL)
.withLocale(Locale.getDefault());
return zonedDateTime.format(formatter);
} catch (Exception e) {
return "无效时区ID: " + timezoneId + "。请提供有效的IANA时区ID。";
}
}
我们将此工具封装在名为ToolBox
的普通Java类中。Quarkus的@Tool (io.quarkiverse.mcp.server.Tool)
注解告诉框架:被注解方法应作为MCP服务器的工具暴露。
同时,@ToolArg (io.quarkiverse.mcp.server.ToolArg)
注解告知框架:被注解参数应随提供描述一起暴露。
⚠️ 注意:description
字段应清晰说明调用者正确使用函数所需的信息。描述帮助LLM识别工具用途,从而在回答用户查询时选择合适工具。
再添加一个获取MCP服务器运行JVM信息的函数:
@Tool(description = "提供JVM系统信息,如可用处理器、空闲内存、总内存和最大内存")
public String getJVMInfo() {
StringBuilder systemInfo = new StringBuilder();
// 获取可用处理器
int availableProcessors = Runtime.getRuntime().availableProcessors();
systemInfo.append("可用处理器(核心数): ").append(availableProcessors).append("\n");
// 获取空闲内存
long freeMemory = Runtime.getRuntime().freeMemory();
systemInfo.append("空闲内存(字节): ").append(freeMemory).append("\n");
// 获取总内存
long totalMemory = Runtime.getRuntime().totalMemory();
systemInfo.append("总内存(字节): ").append(totalMemory).append("\n");
// 获取最大内存
long maxMemory = Runtime.getRuntime().maxMemory();
systemInfo.append("最大内存(字节): ").append(maxMemory).append("\n");
return systemInfo.toString();
}
3.4. 运行MCP服务器
现在有了几个工具,可以启动Quarkus开发模式快速测试。
为简化部署和开发,我们将服务器打包为uber-jar
。这样可以通过mvn install
发布到Maven仓库,方便共享和运行。
默认Quarkus使用HTTP端口8080。由于后续MCP客户端需要8080端口,我们将其改为9000
。在application.properties
文件中配置:
quarkus.package.jar.type=uber-jar
quarkus.http.port=9000
使用quarkus
CLI启动开发模式。服务器启动后,可通过http://localhost:9000/q/dev-ui/
访问:
quarkus dev
由于我们使用@Tool
注解暴露函数,开发模式会自动添加MCP服务器扩展。这允许我们使用Quarkus开发模式提供的Open API/Postman风格工具调用接口快速测试工具。
3.5. 测试工具
现在使用Quarkus Dev UI的MCP服务器扩展进行快速测试。
首先导航到Dev UI的tools标签页,点击MCP服务器扩展下的"Tools"链接:
也可通过URL http://localhost:9000/q/dev-ui/io.quarkiverse.mcp.quarkus-mcp-server-sse/tools
访问。注意我们定义的两个工具都可见,可通过"Call"按钮快速测试:
调用getTimeInTimezone
工具并提供特定时区。工具调用器已根据描述预填充JSON,我们修改为真实值:
{
"timezoneId": "Asia/Kolkata"
}
输入后点击"Call"按钮,响应区域立即返回JSON格式结果:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"isError": false,
"content": [
{
"text": "2025年5月18日星期日,下午7:33:26 印度标准时间",
"type": "text"
}
]
}
}
同样可测试其他函数。服务器端准备就绪,现在转向客户端。
4. 创建自定义MCP客户端
MCP服务器已准备就绪,包含几个自定义工具。现在要在LLM聊天中开放这些工具。我们将使用Quarkus和LangChain4j。LLM API使用本地运行的Ollama和Mistral模型,但也可使用LangChain4j支持的任何其他LLM。
4.1. 创建Quarkus MCP客户端项目
使用quarkus cli
创建新项目:
quarkus create app --no-code -x langchain4j-mcp,langchain4j-ollama,vertx-http quarkus-mcp-client
这将在quarkus-mcp-client
目录下创建包含Quarkus项目基础脚手架的新项目。
4.2. 依赖项
使用quarkus
CLI创建的项目会自动添加核心依赖(quarkus-arc
和quarkus-junit5
),以及命令中指定的扩展相关依赖。本例中扩展为langchain4j-mcp
、langchain4j-ollama
和vertx-http
:
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-mcp</artifactId>
<version>1.0.0.CR2</version>
</dependency>
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-ollama</artifactId>
<version>1.0.0.CR2</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http</artifactId>
</dependency>
4.3. 定义聊天机器人
我们将利用Quarkus和LangChain4j定义带自定义系统提示的聊天服务。
创建@RegisterAiService
注解的McpClientAI
接口。Quarkus使用此注解通过LangChain4j和配置的LLM自动生成LLM客户端服务:
@RegisterAiService
@SessionScoped
public interface McpClientAI {
@SystemMessage("""
你是由Mistral驱动的知识渊博且乐于助人的助手。
你能回答用户问题并提供清晰、简洁、准确的信息。
你还可以通过MCP服务器访问一组工具。
使用工具时,始终将工具响应转换为自然、人类可读的答案。
如果用户问题不明确,礼貌地请求澄清。
如果问题不需要使用工具,直接用你的知识回答。
始终以友好专业的方式沟通,确保回答易于理解。
"""
)
@McpToolBox("default")
String chat(@UserMessage String question);
}
此外,我们使用@SystemMessage
注解为chat()
方法附加自定义系统提示。此提示告知LLM它可通过MCP服务器访问工具,并应使用这些工具响应用户查询。
**通过@McpToolBox
注解告知Quarkus:MCP服务器配置名称为"default"**。Quarkus应将其用于McpClientAI服务。
现在需要告诉Quarkus在哪里找到LLM和工具以使用AI服务。
4.4. 连接LLM和工具
Quarkus提供多种配置选项自动配置LLM客户端和MCP客户端。需要在Quarkus的application.properties
文件中设置部分属性来连接LLM和MCP服务器。
首先设置LLM调用的超时时间。这是可选设置,但在较慢机器(如开发桌面)上运行LLM时可能需要配置更大值:
quarkus.langchain4j.timeout=10s
测试中,我们在本地机器端口11434
运行Ollama服务器,并在Ollama上加载Mistral模型。通过适当属性告知Quarkus:
quarkus.langchain4j.chat-model.provider=ollama
quarkus.langchain4j.ollama.chat-model.model-id=mistral
quarkus.langchain4j.ollama.base-url=http://localhost:11434
接下来指向之前创建的MCP服务器。根据配置,此服务器运行在端口9000
。此外,Quarkus MCP服务器自动配置在URI /mcp/sse
:
quarkus.langchain4j.mcp.default.transport-type=http
quarkus.langchain4j.mcp.default.url=http://localhost:9000/mcp/sse
此配置下,Quarkus自动建立与MCP服务器的连接,使工具可供聊天服务使用。属性名包含"default",应与@McpToolBox
注解参数中配置的名称匹配。现在使用Quarkus CLI启动开发模式。服务器启动后,可通过http://localhost:8080/q/dev-ui/
访问:
quarkus dev
由于我们使用@RegisterAiService
注解创建聊天服务,开发模式会自动添加LangChain4j核心扩展。这允许我们无需编写额外服务即可快速测试聊天客户端。LangChain4j代码扩展提供现成的聊天UI,连接到我们的聊天服务。
4.5. 测试聊天服务
现在使用Quarkus Dev UI的聊天UI扩展进行快速测试。首先导航到Dev UI的tools标签页,点击LangChain4j Core扩展下的"Chat"链接。也可通过URL http://localhost:8080/q/dev-ui/io.quarkiverse.langchain4j.quarkus-langchain4j-core/chat
访问。
可使用此工具与LLM聊天。我们用简单英语询问时间和系统信息,LLM现在使用MCP服务器提供的工具返回最新信息。
示例聊天记录如下:
我
你好AI
你好!今天有什么可以帮你的吗?如果需要特定任务帮助,随时告诉我,我会尽力协助。以下是我能做的一些示例: – 使用getSystemInfo()
获取系统信息,如可用处理器、空闲内存、总内存和最大内存。 – 使用getTimeInTimezone(timezoneId)
获取特定时区当前时间,其中timezoneId
是表示所需时区的字符串(如"America/Los_Angeles")。如有任何问题或需要进一步帮助,请告诉我!我
给我系统信息 工具
请求执行以下工具:请求ID = undefined, 工具名 = getSystemInfo, 参数 = { }
工具
请求ID = undefined的工具执行结果,工具名 = getSystemInfo, 状态 = 可用处理器(核心数): 8 空闲内存(字节): 94046840 总内存(字节): 182452224 最大内存(字节): 4171235328
AI
你的设备系统信息如下: – 可用处理器(核心数): 8 – 空闲内存: 94,046,840 字节 – 总内存: 182,452,224 字节 – 最大内存: 4,171,235,328 字节 可根据需要在程序中使用这些信息。我
印度现在几点 工具
请求执行以下工具:请求ID = undefined, 工具名 = getTimeInTimezone, 参数 = { "timezoneId" : "Asia/Kolkata" }
工具
请求ID = undefined的工具执行结果,工具名 = getTimeInTimezone, 状态 = 2025年5月10日星期六,下午4:26:37 印度标准时间
AI
印度(印度标准时间)当前时间是:2025年5月10日星期六下午4:26:37。
✅ 完成!我们构建了包含自定义工具的聊天服务。现在可以此服务为基础开发其他服务,如与LLM聊天的HTTP API。
⚠️ 注意:即使工具可用,是否使用取决于LLM能力。有时即使工具有助于回答,LLM也可能忽略工具。因此可能需要调整系统提示和工具描述,帮助LLM识别何时使用工具。
5. 总结
本文使用Quarkus配置了MCP服务器,并创建了连接此服务器的独立MCP客户端应用。我们使用Quarkus和LangChain4j构建客户端应用,底层通过Ollama API连接Mistral LLM。
我们通过提供获取JVM系统信息和当前时间的工具,增强了Mistral LLM的能力。没有工具时,LLM无法获取这些动态信息。本文使用的工具在实际项目中可能不常用,但它们展示了通过自定义工具增强LLM能力的可能性。
最后,MCP服务器允许我们使用自己的代码提供多种功能,而MCP客户端则能轻松将这些工具集成到连接LLM的应用中。
一如既往,代码可在GitHub获取。